Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Home-scoped commands at `~/.archon/commands/`** — personal command helpers now reusable across every repo. Resolution precedence: `<repoRoot>/.archon/commands/` > `~/.archon/commands/` > bundled defaults. Surfaced in the Web UI workflow-builder node palette under a dedicated "Global (~/.archon/commands/)" section.
- **Home-scoped scripts at `~/.archon/scripts/`** — personal Bun/uv scripts now reusable across every repo. Script nodes (`script: my-helper`) resolve via `<repoRoot>/.archon/scripts/` first, then `~/.archon/scripts/`. Repo-scoped scripts with the same name override home-scoped ones silently; within a single scope, duplicate basenames across extensions still throw (unchanged from prior behavior).
- **1-level subfolder support for workflows, commands, and scripts.** Files can live one folder deep under their respective `.archon/` root (e.g. `.archon/workflows/triage/foo.yaml`) and resolve by name or filename regardless of subfolder. Matches the existing `defaults/` convention. Deeper nesting is ignored silently — see docs for the full convention.
- **`'global'` variant on `WorkflowSource`** — workflows at `~/.archon/workflows/` and commands at `~/.archon/commands/` now render with a distinct source label (no longer coerced to `'project'`). Web UI badges updated.
- **`getHomeWorkflowsPath()`, `getHomeCommandsPath()`, `getHomeScriptsPath()`, `getLegacyHomeWorkflowsPath()`** helpers in `@archon/paths`, exported for both internal discovery and external callers that want to target the home scope directly.
- **`discoverScriptsForCwd(cwd)`** in `@archon/workflows/script-discovery` — merges home-scoped + repo-scoped scripts with repo winning on name collisions. Used by the DAG executor and validator; callers no longer need to know about the two-scope shape.
- **Three-path env model with operator-visible log lines.** The CLI and server now load env vars from `~/.archon/.env` (user scope) and `<cwd>/.archon/.env` (repo scope, overrides user) at boot, both with `override: true`. A new `[archon] loaded N keys from <path>` line is emitted per source (only when N > 0). `[archon] stripped N keys from <cwd> (...)` now also prints when stripCwdEnv removes target-repo env keys, replacing the misleading `[dotenv@17.3.1] injecting env (0) from .env` preamble that always reported 0. The `quiet: true` flag suppresses dotenv's own output. (#1302)
- **`archon setup --scope home|project` and `--force` flags.** Default is `--scope home` (writes `~/.archon/.env`). `--scope project` targets `<cwd>/.archon/.env` instead. `--force` overwrites the target wholesale rather than merging; a timestamped backup is still written. (#1303)
- **Merge-only setup writes with timestamped backups.** `archon setup` now reads the existing target file, preserves non-empty values, carries user-added custom keys forward, and writes a `<target>.archon-backup-<ISO-ts>` before every rewrite. Fixes silent PostgreSQL→SQLite downgrade and silent token loss on re-run. (#1303)
Expand All @@ -28,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **Home-scoped workflow location moved to `~/.archon/workflows/`** (was `~/.archon/.archon/workflows/` — a double-nested path left over from reusing the repo-relative discovery helper for home scope). The new path sits next to `~/.archon/workspaces/`, `archon.db`, and `config.yaml`, matching the rest of the `~/.archon/` convention. If Archon detects workflows at the old location, it emits a one-time WARN per process with the exact migration command: `mv ~/.archon/.archon/workflows ~/.archon/workflows && rmdir ~/.archon/.archon`. The old path is no longer read — users must migrate manually (clean cut, no deprecation window). Rollback caveat: if you downgrade after migrating, move the directory back to the old location.
- **Workflow discovery no longer takes a `globalSearchPath` option.** `discoverWorkflows()` and `discoverWorkflowsWithConfig()` now consult `~/.archon/workflows/` automatically — every caller gets home-scoped discovery for free. Previously-missed call sites in the chat command handler (`command-handler.ts`), the Web UI workflow picker (`api.ts GET /api/workflows`), and the orchestrator's single-codebase resolve path now see home-scoped workflows without needing a maintainer patch at every new call site. Closes #1136; supersedes that PR (credits @jonasvanderhaegen for surfacing the bug class).
- **Dashboard nav tab** now shows a numeric count of running workflows instead of a binary pulse dot. Reads from the existing `/api/dashboard/runs` `counts.running` field; same 10s polling interval.
- **Workflow run destructive actions** (Abandon, Cancel, Delete, Reject) now use a proper confirmation dialog matching the codebase-delete UX, replacing the browser's native `window.confirm()` popups. Each dialog includes context-appropriate copy describing what the action does to the run record.

Expand All @@ -41,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed

- **`globalSearchPath` option** from `discoverWorkflows()` and `discoverWorkflowsWithConfig()`. Callers that previously passed `{ globalSearchPath: getArchonHome() }` should drop the argument; home-scoped discovery is now automatic.
- **`@anthropic-ai/claude-agent-sdk/embed` import** — the Bun `with { type: 'file' }` asset-embedding path and its `$bunfs` extraction logic. The embed was a bundler-dependent optimization that failed silently when Bun couldn't produce a usable virtual FS path (#1210, #1087); it is replaced by explicit binary-path resolution.

### Fixed
Expand Down
12 changes: 9 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -719,9 +719,15 @@ async function createSession(conversationId: string, codebaseId: string) {
- Opt-out: Set `defaults.loadDefaultCommands: false` or `defaults.loadDefaultWorkflows: false` in `.archon/config.yaml`
- **After adding, removing, or editing a default file, run `bun run generate:bundled`** to refresh the embedded bundle. `bun run validate` (and CI) run `check:bundled` and will fail loudly if the generated file is stale.

**Global workflows** (user-level, applies to every project):
- Path: `~/.archon/.archon/workflows/` (or `$ARCHON_HOME/.archon/workflows/`)
- Load priority: bundled < global < repo-specific (repo overrides global by filename)
**Home-scoped ("global") workflows, commands, and scripts** (user-level, applies to every project):
- Workflows: `~/.archon/workflows/` (or `$ARCHON_HOME/workflows/`)
- Commands: `~/.archon/commands/` (or `$ARCHON_HOME/commands/`)
- Scripts: `~/.archon/scripts/` (or `$ARCHON_HOME/scripts/`)
- Source label: `source: 'global'` on workflows and commands (scripts don't have a source label)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update the API docs to include source: 'global'.

This section now documents the new global source label, but the workflow endpoint docs later still describe GET /api/workflows/:name as returning only source: 'project' | 'bundled'. Please update that API line too so Web/API consumers see the new enum value.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` at line 726, The API docs mention the workflow endpoint GET
/api/workflows/:name as returning source: 'project' | 'bundled' but the spec now
includes a new global source value; update the endpoint description to list
source: 'project' | 'bundled' | 'global' (or the enum/union that represents
sources used elsewhere), and ensure any examples or schema blocks for GET
/api/workflows/:name (and related commands/workflows response schemas) reflect
the new "global" value so Web/API consumers see the added enum member.

- Load priority: bundled < global < project (repo overrides global by filename or script name)
- Subfolders: supported 1 level deep (e.g. `~/.archon/workflows/triage/foo.yaml`). Deeper nesting is ignored silently.
- Discovery is automatic — `discoverWorkflowsWithConfig(cwd, loadConfig)` and `discoverScriptsForCwd(cwd)` both read home-scoped paths unconditionally; no caller option needed
- **Migration from pre-0.x `~/.archon/.archon/workflows/`**: if Archon detects files at the old location it emits a one-time WARN with the exact `mv` command and does NOT load from there. Move with: `mv ~/.archon/.archon/workflows ~/.archon/workflows && rmdir ~/.archon/.archon`
- See the docs site at `packages/docs-web/` for details

### Error Handling
Expand Down
10 changes: 4 additions & 6 deletions packages/cli/src/commands/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ describe('workflowListCommand', () => {
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Found 1 workflow(s)'));
});

it('passes globalSearchPath to discoverWorkflowsWithConfig', async () => {
it('calls discoverWorkflowsWithConfig with (cwd, loadConfig) — home scope is internal', async () => {
const { discoverWorkflowsWithConfig } = await import('@archon/workflows/workflow-discovery');
(discoverWorkflowsWithConfig as ReturnType<typeof mock>).mockResolvedValueOnce({
workflows: [],
Expand All @@ -319,11 +319,9 @@ describe('workflowListCommand', () => {

await workflowListCommand('/test/path');

expect(discoverWorkflowsWithConfig).toHaveBeenCalledWith(
'/test/path',
expect.any(Function),
expect.objectContaining({ globalSearchPath: '/home/test/.archon' })
);
// After the globalSearchPath refactor, discovery reads ~/.archon/workflows/
// on every call with no option — every caller inherits home-scope for free.
expect(discoverWorkflowsWithConfig).toHaveBeenCalledWith('/test/path', expect.any(Function));
});

it('should throw error when discoverWorkflows fails', async () => {
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/commands/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@archon/core';
import { WORKFLOW_EVENT_TYPES, type WorkflowEventType } from '@archon/workflows/store';
import { configureIsolation, getIsolationProvider } from '@archon/isolation';
import { createLogger, getArchonHome } from '@archon/paths';
import { createLogger } from '@archon/paths';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Include the home workflow directory in the failure hint.

discoverWorkflowsWithConfig now reads home-scoped workflows internally, so permission failures from the home path are currently wrapped with a repo-only hint.

💬 Proposed clearer hint
-import { createLogger } from '@archon/paths';
+import { createLogger, getHomeWorkflowsPath } from '@archon/paths';
@@
     throw new Error(
-      `Error loading workflows: ${err.message}\nHint: Check permissions on .archon/workflows/ directory.`
+      `Error loading workflows: ${err.message}\nHint: Check permissions on .archon/workflows/ and ${getHomeWorkflowsPath()} directories.`
     );

Also applies to: 122-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/workflow.ts` at line 13, The failure hint currently
assumes only a repo-scoped workflows path; update the error handling around
discoverWorkflowsWithConfig to include the home workflow directory as well
(i.e., when constructing the permission-failure hint, mention both the repo
workflows path and the home workflows path). Locate the catch/logging that wraps
permission errors for discoverWorkflowsWithConfig (also the similar logic around
lines 122-129) and expand the hint text to reference the home workflow directory
name/path used by the CLI (so permission errors originating from home-scoped
workflows are not mischaracterized as repo-only).

import { createWorkflowDeps } from '@archon/core/workflows/store-adapter';
import { discoverWorkflowsWithConfig } from '@archon/workflows/workflow-discovery';
import { resolveWorkflowName } from '@archon/workflows/router';
Expand Down Expand Up @@ -119,9 +119,9 @@ function renderWorkflowEvent(event: WorkflowEmitterEvent, verbose: boolean): voi
*/
async function loadWorkflows(cwd: string): Promise<WorkflowLoadResult> {
try {
return await discoverWorkflowsWithConfig(cwd, loadConfig, {
globalSearchPath: getArchonHome(),
});
// Home-scoped workflows at ~/.archon/workflows/ are discovered automatically —
// no option needed since the discovery helper reads them unconditionally.
return await discoverWorkflowsWithConfig(cwd, loadConfig);
} catch (error) {
const err = error as Error;
throw new Error(
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/orchestrator/orchestrator-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { formatToolCall } from '@archon/workflows/utils/tool-formatter';
import { classifyAndFormatError } from '../utils/error-formatter';
import { toError } from '../utils/error';
import { getAgentProvider, getProviderCapabilities } from '@archon/providers';
import { getArchonHome, getArchonWorkspacesPath } from '@archon/paths';
import { getArchonWorkspacesPath } from '@archon/paths';
import { syncArchonToWorktree } from '../utils/worktree-sync';
import { syncWorkspace, toRepoPath } from '@archon/git';
import type { WorkspaceSyncResult } from '@archon/git';
Expand Down Expand Up @@ -388,9 +388,9 @@ async function discoverAllWorkflows(conversation: Conversation): Promise<Discove
let config: MergedConfig | undefined;

try {
const result = await discoverWorkflowsWithConfig(getArchonWorkspacesPath(), loadConfig, {
globalSearchPath: getArchonHome(),
});
// Home-scoped workflows at ~/.archon/workflows/ are discovered automatically
// by discoverWorkflowsWithConfig — no option needed.
const result = await discoverWorkflowsWithConfig(getArchonWorkspacesPath(), loadConfig);
Comment on lines +391 to +393
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid discovering home workflows twice for project-scoped conversations.

This call now includes home-scoped workflows internally, and the later repo discovery in this function does too. A broken home workflow can therefore be reported twice in allErrors; either discover once for scoped conversations or de-duplicate errors before returning.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 391 - 393,
The call to discoverWorkflowsWithConfig(getArchonWorkspacesPath(), loadConfig)
causes home-scoped workflows to be discovered twice (once here and again during
repo discovery), producing duplicate entries in allErrors; update the logic so
home workflows are only discovered once for scoped conversations — either by
adding an option/flag to discoverWorkflowsWithConfig to exclude home-scoped
workflows when performing project-scoped discovery (and use that flag here or in
the later repo discovery), or by de-duplicating error entries before returning
(e.g., collapse errors by unique workflow identifier/path and error message).
Locate and modify the usage of discoverWorkflowsWithConfig and the allErrors
aggregation in orchestrator-agent (references: discoverWorkflowsWithConfig,
getArchonWorkspacesPath, allErrors) to implement one of these fixes and ensure
duplicate error reporting is eliminated.

workflows = [...result.workflows];
allErrors.push(...result.errors);
} catch (error) {
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/orchestrator/orchestrator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1153,10 +1153,11 @@ describe('orchestrator-agent handleMessage', () => {

await handleMessage(platform, 'chat-456', 'help');

// Discovery is called positionally with (cwd, loadConfig) — no options arg.
// Home-scoped workflows (~/.archon/workflows/) are discovered internally.
expect(mockDiscoverWorkflows).toHaveBeenCalledWith(
'/home/test/.archon/workspaces',
expect.any(Function),
{ globalSearchPath: '/home/test/.archon' }
expect.any(Function)
);
});

Expand Down
16 changes: 14 additions & 2 deletions packages/core/src/utils/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ import { join, basename } from 'path';
/**
* Recursively find all .md files in a directory and its subdirectories.
* Skips hidden directories and node_modules.
*
* `maxDepth` caps how many folders deep the walk descends. Default is
* `Infinity` (no cap) so callers that copy arbitrary subtrees (e.g.
* `packages/core/src/handlers/clone.ts`) preserve existing behavior.
*/
export async function findMarkdownFilesRecursive(
rootPath: string,
relativePath = ''
relativePath = '',
options?: { maxDepth?: number }
): Promise<{ commandName: string; relativePath: string }[]> {
const maxDepth = options?.maxDepth ?? Infinity;
const currentDepth = relativePath ? relativePath.split(/[/\\]/).filter(Boolean).length : 0;
const results: { commandName: string; relativePath: string }[] = [];
const fullPath = join(rootPath, relativePath);

Expand All @@ -23,7 +30,12 @@ export async function findMarkdownFilesRecursive(
}

if (entry.isDirectory()) {
const subResults = await findMarkdownFilesRecursive(rootPath, join(relativePath, entry.name));
if (currentDepth >= maxDepth) continue;
const subResults = await findMarkdownFilesRecursive(
rootPath,
join(relativePath, entry.name),
options
);
results.push(...subResults);
} else if (entry.isFile() && entry.name.endsWith('.md')) {
results.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Workflows live in `.archon/workflows/` relative to the working directory:

Archon discovers workflows recursively - subdirectories are fine. If a workflow file fails to load (syntax error, validation failure), it's skipped and the error is reported via `/workflow list`.

> **Global workflows:** For workflows that apply to every project, place them in `~/.archon/.archon/workflows/`. Global workflows are overridden by same-named repo workflows. See [Global Workflows](/guides/global-workflows/).
> **Global workflows:** For workflows that apply to every project, place them in `~/.archon/workflows/`. Global workflows are overridden by same-named repo workflows. See [Global Workflows](/guides/global-workflows/).

> **CLI vs Server:** The CLI reads workflow files from wherever you run it (sees uncommitted changes). The server reads from the workspace clone at `~/.archon/workspaces/owner/repo/`, which only syncs from the remote before worktree creation. If you edit a workflow locally but don't push, the server won't see it.

Expand Down
Loading
Loading