diff --git a/packages/docs-web/src/content/docs/guides/authoring-commands.md b/packages/docs-web/src/content/docs/guides/authoring-commands.md index 849952c0d3..224db6a566 100644 --- a/packages/docs-web/src/content/docs/guides/authoring-commands.md +++ b/packages/docs-web/src/content/docs/guides/authoring-commands.md @@ -28,6 +28,8 @@ A command is a **markdown file** that serves as a detailed instruction set for a Commands live in `.archon/commands/` relative to the working directory and are loaded at runtime. +> **`defaults/` is maintainer-territory:** `.archon/commands/defaults/` is reserved for commands shipped with Archon itself (embedded into the binary at build time). For your own commands use `.archon/commands/` (project-scoped) or `~/.archon/commands/` (home-scoped). Every file under `defaults/` must be committed in git — `bun run validate` will error if untracked files are found there. + > **CLI vs Server:** The CLI reads commands from wherever you run it (sees uncommitted changes). The server reads from `~/.archon/workspaces/owner/repo/`, which only syncs from the remote before worktree creation — so changes must be committed and pushed for the server to pick them up. Commands use this structure: diff --git a/packages/docs-web/src/content/docs/guides/authoring-workflows.md b/packages/docs-web/src/content/docs/guides/authoring-workflows.md index 408fdb8e90..fd8e134d18 100644 --- a/packages/docs-web/src/content/docs/guides/authoring-workflows.md +++ b/packages/docs-web/src/content/docs/guides/authoring-workflows.md @@ -41,6 +41,8 @@ nodes: > ``` > Same-named files in `.archon/workflows/` override the bundled defaults. +> **`defaults/` is maintainer-territory:** `.archon/workflows/defaults/` and `.archon/commands/defaults/` are reserved for workflows/commands shipped with Archon itself — they are embedded into the binary at build time and every file there must be committed in git. For your own drafts use `.archon/workflows/` (project-scoped, committed to your repo) or `~/.archon/workflows/` (home-scoped, personal). Running `bun run generate:bundled` (or `bun run validate`) will exit with an error if it finds any untracked files in `defaults/`. + --- ## File Location diff --git a/scripts/generate-bundled-defaults.ts b/scripts/generate-bundled-defaults.ts index 1abcea74f9..c8bacc863e 100644 --- a/scripts/generate-bundled-defaults.ts +++ b/scripts/generate-bundled-defaults.ts @@ -24,6 +24,10 @@ */ import { access, readFile, readdir, writeFile } from 'fs/promises'; import { join, resolve } from 'path'; +import { execFile as execFileCb } from 'child_process'; +import { promisify } from 'util'; + +const execFile = promisify(execFileCb); const REPO_ROOT = resolve(import.meta.dir, '..'); const COMMANDS_DIR = join(REPO_ROOT, '.archon/commands/defaults'); @@ -52,6 +56,31 @@ async function ensureDir(dir: string, label: string): Promise { } } +async function assertNoUntrackedFiles(dir: string, label: string): Promise { + let stdout: string; + try { + ({ stdout } = await execFile('git', ['ls-files', '--others', '--exclude-standard', dir], { + cwd: REPO_ROOT, + })); + } catch (e) { + const err = e as NodeJS.ErrnoException; + if (err.code === 'ENOENT') { + // git binary not found — skip the check (unlikely for a dev environment). + return; + } + throw err; + } + const untracked = stdout.trim().split('\n').filter(Boolean); + if (untracked.length > 0) { + const list = untracked.map(f => ` ${f}`).join('\n'); + throw new Error( + `${label} contains untracked files that would be embedded into the binary bundle:\n${list}\n\n` + + 'Untracked file in defaults/ — stage and commit (git add + git commit),\n' + + 'or move to .archon/workflows/ (project-scope) or ~/.archon/workflows/ (home-scope).' + ); + } +} + async function collectFiles(dir: string, extensions: readonly string[]): Promise { const entries = await readdir(dir); const matched = entries @@ -137,6 +166,11 @@ async function main(): Promise { ensureDir(WORKFLOWS_DIR, 'Workflows defaults'), ]); + await Promise.all([ + assertNoUntrackedFiles(COMMANDS_DIR, 'Commands defaults (.archon/commands/defaults/)'), + assertNoUntrackedFiles(WORKFLOWS_DIR, 'Workflows defaults (.archon/workflows/defaults/)'), + ]); + const [commands, workflows] = await Promise.all([ collectFiles(COMMANDS_DIR, ['.md']), collectFiles(WORKFLOWS_DIR, ['.yaml', '.yml']),