{"owner":"coleam00","repo":"Archon","hasSkills":true,"totalSkillsCount":139,"totalTokensCount":342267,"categories":["subagent-persona","command-prompt","anthropic-skill","root-instruction","claude-rule","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".claude/agents/code-reviewer.md",".claude/agents/code-simplifier.md",".claude/agents/codebase-analyst.md",".claude/agents/codebase-explorer.md",".claude/agents/comment-analyzer.md",".claude/agents/docs-impact.md",".claude/agents/pr-test-analyzer.md",".claude/agents/rulecheck-agent.md",".claude/agents/sdk-verifier.md",".claude/agents/silent-failure-hunter.md",".claude/agents/triage-agent.md",".claude/agents/type-design-analyzer.md",".claude/agents/web-researcher.md",".claude/commands/commit.md",".claude/commands/create-command.md",".claude/commands/execute.md",".claude/commands/github_bug_fix/implement-fix.md",".claude/commands/github_bug_fix/rca.md",".claude/commands/handoff.md",".claude/commands/plan-feature.md",".claude/commands/prime-backend.md",".claude/commands/prime-frontend.md",".claude/commands/prime-isolation.md",".claude/commands/prime-workflows.md",".claude/commands/prime.md",".claude/commands/review-doc.md",".claude/commands/validate.md",".claude/commands/validation/code-review-fix.md",".claude/commands/validation/code-review.md",".claude/commands/validation/execution-report.md",".claude/commands/validation/system-review.md",".claude/commands/validation/validate-2.md",".claude/skills/archon-cli/SKILL.md",".claude/skills/archon-cli/authoring/authoring-workflows.md",".claude/skills/archon-cli/authoring/node-reference.md",".claude/skills/archon-cli/authoring/prompting-mistakes.md",".claude/skills/archon-cli/authoring/variables.md",".claude/skills/archon-cli/operate/managing-runs.md",".claude/skills/archon-cli/operate/queues.md",".claude/skills/archon-cli/operate/running.md",".claude/skills/archon-cli/operate/troubleshooting.md",".claude/skills/archon-cli/setup/setup-and-config.md",".claude/skills/docker-extend/SKILL.md",".claude/skills/playwright-cli/SKILL.md",".claude/skills/playwright-cli/references/element-attributes.md",".claude/skills/playwright-cli/references/playwright-tests.md",".claude/skills/playwright-cli/references/request-mocking.md",".claude/skills/playwright-cli/references/running-code.md",".claude/skills/playwright-cli/references/session-management.md",".claude/skills/playwright-cli/references/storage-state.md",".claude/skills/playwright-cli/references/test-generation.md",".claude/skills/playwright-cli/references/tracing.md",".claude/skills/playwright-cli/references/video-recording.md",".claude/skills/release/SKILL.md",".claude/skills/rulecheck/SKILL.md",".claude/skills/rulecheck/rules-guide.md",".claude/skills/rulecheck/workshop-guide.md",".claude/skills/test-release/SKILL.md",".claude/skills/triage/SKILL.md",".claude/skills/triage/workshop-guide.md","AGENTS.md","CLAUDE.md","packages/adapters/src/community/chat/README.md","packages/adapters/src/community/forge/README.md","packages/chat-contract/README.md","packages/docs-web/public/brand/README.md","packages/docs-web/src/content/docs/adapters/community/discord.md","packages/docs-web/src/content/docs/adapters/community/gitea.md","packages/docs-web/src/content/docs/adapters/community/gitlab.md","packages/docs-web/src/content/docs/adapters/github-app-setup.md","packages/docs-web/src/content/docs/adapters/github.md","packages/docs-web/src/content/docs/adapters/index.md","packages/docs-web/src/content/docs/adapters/slack.md","packages/docs-web/src/content/docs/adapters/telegram.md","packages/docs-web/src/content/docs/adapters/web.md","packages/docs-web/src/content/docs/brand/index.md","packages/docs-web/src/content/docs/contributing/cli-internals.md","packages/docs-web/src/content/docs/contributing/dx-quirks.md","packages/docs-web/src/content/docs/contributing/index.md","packages/docs-web/src/content/docs/contributing/new-developer-guide.md","packages/docs-web/src/content/docs/contributing/releasing.md","packages/docs-web/src/content/docs/deployment/cloud.md","packages/docs-web/src/content/docs/deployment/docker.md","packages/docs-web/src/content/docs/deployment/e2e-testing-wsl.md","packages/docs-web/src/content/docs/deployment/e2e-testing.md","packages/docs-web/src/content/docs/deployment/index.md","packages/docs-web/src/content/docs/deployment/local.md","packages/docs-web/src/content/docs/deployment/reverse-proxy.md","packages/docs-web/src/content/docs/deployment/windows.md","packages/docs-web/src/content/docs/getting-started/ai-assistants.md","packages/docs-web/src/content/docs/getting-started/configuration.md","packages/docs-web/src/content/docs/getting-started/first-workflow.md","packages/docs-web/src/content/docs/getting-started/installation.md","packages/docs-web/src/content/docs/getting-started/operate.md","packages/docs-web/src/content/docs/getting-started/quick-start.md","packages/docs-web/src/content/docs/getting-started/sdlc-factory.md","packages/docs-web/src/content/docs/getting-started/updating.md","packages/docs-web/src/content/docs/getting-started/why-archon.md","packages/docs-web/src/content/docs/guides/approval-nodes.md","packages/docs-web/src/content/docs/guides/authoring-commands.md","packages/docs-web/src/content/docs/guides/container-isolation.md","packages/docs-web/src/content/docs/guides/global-workflows.md","packages/docs-web/src/content/docs/guides/hooks.md","packages/docs-web/src/content/docs/guides/index.md","packages/docs-web/src/content/docs/guides/loop-nodes.md","packages/docs-web/src/content/docs/guides/mcp-servers.md","packages/docs-web/src/content/docs/guides/multi-repo-projects.md","packages/docs-web/src/content/docs/guides/proactive-workflows.md","packages/docs-web/src/content/docs/guides/publishing-plugins.md","packages/docs-web/src/content/docs/guides/script-nodes.md","packages/docs-web/src/content/docs/guides/skills.md","packages/docs-web/src/content/docs/guides/workflow-triggers.md","packages/docs-web/src/content/docs/guides/worktrees.md","packages/docs-web/src/content/docs/reference/api.md","packages/docs-web/src/content/docs/reference/architecture.md","packages/docs-web/src/content/docs/reference/archon-directories.md","packages/docs-web/src/content/docs/reference/cli.md","packages/docs-web/src/content/docs/reference/commands.md","packages/docs-web/src/content/docs/reference/configuration.md","packages/docs-web/src/content/docs/reference/database.md","packages/docs-web/src/content/docs/reference/forge.md","packages/docs-web/src/content/docs/reference/index.md","packages/docs-web/src/content/docs/reference/node-execution.md","packages/docs-web/src/content/docs/reference/provider-capabilities.md","packages/docs-web/src/content/docs/reference/sdlc-workflows.md","packages/docs-web/src/content/docs/reference/security.md","packages/docs-web/src/content/docs/reference/troubleshooting.md","packages/docs-web/src/content/docs/reference/variables.md","packages/docs-web/src/content/docs/reference/workflow-language.md","packages/forge/README.md","packages/provider-contract/README.md","packages/providers/src/codex/protocol/README.md","packages/web/src/experiments/console/README.md","packages/web/src/experiments/console/builder/README.md","plugins/chat-slack/archon-plugin.json","plugins/forge-github/archon-plugin.json","plugins/provider-claude/archon-plugin.json","plugins/provider-codex/archon-plugin.json","plugins/provider-pi/archon-plugin.json"],"skills":{".claude/agents/code-reviewer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcode-reviewer.md -->",".claude/agents/code-simplifier.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcode-simplifier.md -->",".claude/agents/codebase-analyst.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcodebase-analyst.md -->",".claude/agents/codebase-explorer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcodebase-explorer.md -->",".claude/agents/comment-analyzer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcomment-analyzer.md -->",".claude/agents/docs-impact.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fdocs-impact.md -->",".claude/agents/pr-test-analyzer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fpr-test-analyzer.md -->",".claude/agents/rulecheck-agent.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Frulecheck-agent.md -->",".claude/agents/sdk-verifier.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fsdk-verifier.md -->",".claude/agents/silent-failure-hunter.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fsilent-failure-hunter.md -->",".claude/agents/triage-agent.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Ftriage-agent.md -->",".claude/agents/type-design-analyzer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Ftype-design-analyzer.md -->",".claude/agents/web-researcher.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fweb-researcher.md -->",".claude/commands/commit.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fcommit.md -->",".claude/commands/create-command.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fcreate-command.md -->",".claude/commands/execute.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fexecute.md -->",".claude/commands/github_bug_fix/implement-fix.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fgithub_bug_fix%2Fimplement-fix.md -->",".claude/commands/github_bug_fix/rca.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fgithub_bug_fix%2Frca.md -->",".claude/commands/handoff.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fhandoff.md -->",".claude/commands/plan-feature.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fplan-feature.md -->",".claude/commands/prime-backend.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-backend.md -->",".claude/commands/prime-frontend.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-frontend.md -->",".claude/commands/prime-isolation.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-isolation.md -->",".claude/commands/prime-workflows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-workflows.md -->",".claude/commands/prime.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime.md -->",".claude/commands/review-doc.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Freview-doc.md -->",".claude/commands/validate.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidate.md -->",".claude/commands/validation/code-review-fix.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fcode-review-fix.md -->",".claude/commands/validation/code-review.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fcode-review.md -->",".claude/commands/validation/execution-report.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fexecution-report.md -->",".claude/commands/validation/system-review.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fsystem-review.md -->",".claude/commands/validation/validate-2.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fvalidate-2.md -->",".claude/skills/archon-cli/SKILL.md":"---\nname: archon-cli\ndescription: |\n  Drive Archon through its CLI: run AI workflows on a repo, watch and answer the runs\n  that need a decision, drive the SDLC pack's queues (intake, build queue, merge queue,\n  discoveries), set up Archon or change its config, and author and validate workflows.\n  Use when the user says \"use archon\", \"run archon\", \"archon workflow\", \"fix issue #N\n  with archon\", \"have archon review this PR\", \"what's running / check run <id>\",\n  \"approve/reject/answer/cancel/resume that run\", \"run the build queue\", \"drain\n  discoveries\", \"set up archon\", \"configure archon\", \"change my archon config\",\n  \"create a workflow\", \"author a workflow\", or asks how to write a better Archon prompt.\n  NOT for: doing the coding work yourself. Archon delegates it to isolated runs.\nargument-hint: \"[workflow | run-id | intent]\"\n---\n\n# Archon CLI\n\nArchon runs multi-step AI workflows through the `archon` CLI. Git projects run in\nisolated worktrees by default; registered folder projects run in place. You sit on\ntop of the runs: you write the brief, launch, watch one stream, answer the decisions\nthat reach you, and read what each run left behind.\n\nRoute by intent:\n\n| The user wants to... | Read |\n|---|---|\n| Run a workflow on real work | `operate/running.md` |\n| Inspect, answer, cancel or resume runs | `operate/managing-runs.md` |\n| Drive the SDLC pack's queues | `operate/queues.md` |\n| Find out why a run failed or hangs | `operate/troubleshooting.md` |\n| Set up Archon or change config | `setup/setup-and-config.md` |\n| Author or change a workflow | `authoring/authoring-workflows.md` (then `node-reference.md` and `variables.md` in the same folder) |\n| Improve a node prompt or a run message | `authoring/prompting-mistakes.md` |\n\n- When the intent is clear (\"build me a workflow for X\", \"run archon-ship on issue\n  #42\"), go straight to the reference. Do not ask.\n- When it is ambiguous, ask which of these the user wants.\n- On a machine where `archon doctor` fails, start with `setup/setup-and-config.md`.\n\n## The operating loop\n\n1. **Discover.** `archon workflow list --json` gives compact descriptions. For each\n   plausible candidate, read the full description with `archon workflow list <name>\n   --full`. Choose from full descriptions, never from names you remember.\n2. **Check the brief.** The message, issue body or document the run reads is the\n   contract the run is measured against. It must state the problem, why it is worth\n   solving, why now, the desired outcome, the invariants and the acceptance. If any\n   is missing, say which, propose wording, and get the user's agreement before\n   launching. `operate/running.md` has the full check.\n3. **Launch detached.** Pass the work as a message, the workflow's declared inputs\n   with `--input`, or both:\n\n   ```bash\n   archon workflow run <workflow> --branch <branch-name> \"<the work>\" --detach\n   archon workflow run <workflow> --input <name>=<value> --input <name>=<value> --detach\n   ```\n\n4. **Watch once.** Run one stream as a background task of your harness, however many\n   runs you launched:\n\n   ```bash\n   archon workflow attention --watch --json        # this project; add --all for the install\n   ```\n\n   Each line is JSON. `attention` means a run needs a decision, `resolved` means it no\n   longer does, and `terminal` means a run ended. Act on the line, then keep reading.\n5. **Answer deliberately.** Read the item's question, its recommendation and the\n   evidence it links. Answer with a reason, detached:\n   `archon workflow respond <run-id> <decision> \"<reason>\" --detach`.\n   `operate/managing-runs.md` covers every form.\n6. **Read the result.** A completed run is not a verdict. `archon workflow get\n   <run-id> --json` gives the normalized `outcome` and `leave_behind.artifactFiles`.\n   Read the report before telling the user what the run concluded.\n\n## Hard rules\n\n- **Never launch on a thin brief.** A weak input does not produce a weak result. It\n  produces a confident, well-formed answer to the wrong question, at full price.\n- **Launch and answer with `--detach`.** A detached run starts its own session and\n  survives your shell. A foreground `respond` or `resume` continues the run inside\n  your shell, and closing the shell stops it.\n- **One watcher, not one per run.** One `workflow attention --watch` stream covers\n  every run in scope, including runs that triggers or other sessions start. A\n  `workflow wait <run-id>` per run is fine for one or two runs; with dozens it\n  contends for the database.\n- **Stop work only when you mean to end it.** `archon workflow cancel` stops the run\n  mid-node, and that node's provider session and unsaved work are lost. Let a run reach\n  its next pause or end when you only want to stop launching more. When one turn is\n  stuck on a tool or background command that never finishes, `archon workflow\n  end-turn <run-id>` ends that turn and keeps the run (`operate/managing-runs.md`,\n  \"A stalled turn\").\n- **Merge only what was reviewed.** When you merge a pull request by hand, pin the\n  merge to the reviewed head (`gh pr merge <n> --match-head-commit <sha>`), read the\n  open review threads first, and sync your checkout afterwards.\n- **Author agent-first.** In a workflow you write, an agent node with typed output\n  judges, scripts read facts and act at boundaries, and YAML only wires them.\n  `authoring/authoring-workflows.md` (\"Agent-first\") has the rule and an example.\n\n## Gotchas\n\n- The current directory selects the project for discovery, launches and project\n  listings. `workflow status`, `workflow runs` and `workflow attention` default to\n  that project; add `--all` for the whole install. For a git project, run from the\n  repo root. Register a non-git folder with `workflow run --folder`. A full run ID\n  works from any directory.\n- Attention items are paused runs. A running run stuck on a stalled tool call is\n  not an item: `workflow wait <run-id>` and `workflow status` show it.\n- `workflow status` and `workflow runs` set `scopeFallback: true` in JSON when an\n  unregistered project produced an install-wide result.\n- Prefer `--json` whenever you will parse output.\n- `archon --help` is authoritative. If a flag this skill names is rejected, trust the\n  CLI's help and tell the user about the drift.\n",".claude/skills/archon-cli/authoring/authoring-workflows.md":"---\nname: archon-cli-authoring\ndescription: Design and write an Archon workflow: outcome-first decomposition, gate design, node types, loop channels, packaging, fixtures. Read when creating or editing workflow YAML.\n---\n\n# Authoring Workflows\n\nDesign and write an Archon workflow — for code work, business operations, or any\ngoverned process. The method is outcome-first; the mechanics follow from it.\nExemplars: the bundled sdlc pack in any Archon checkout under\n`.archon/workflows/sdlc/<primitive>/` — read a pack YAML before writing your own.\nArchon is not code-only: triage an inbox of tickets, reconcile accounts,\nscreen applicants, keep CRM records honest — the same node types, gates, and\ndiscipline apply. What changes per domain is where the gates come from (below).\n\n## The design sequence\n\nWork through these questions in order, with the user where marked:\n\n1. **Desired outcome.** What artifact or world-state exists when this succeeds?\n   One sentence. If it cannot be stated as an outcome, it is not ready to build.\n2. **Specificity (with the user).** Is this workflow written against ONE project\n   or outcome, or meant to be generally reusable? This changes everything\n   downstream: a project-specific workflow may hardcode that repo's real gates,\n   real commands, real rules, and speak in its vocabulary — which makes it far\n   more powerful than any generic equivalent. Ask before designing.\n3. **Reuse check.** Which of the USER'S OWN workflows already do part of this?\n   (`archon workflow list`, then read their YAML.) Compose those via `include:`.\n\n   **The bundled sdlc pack is NOT a component library.** Its workflows exist to\n   teach how good workflows are written — read them as worked examples of gate\n   placement, prompt contracts, and fixture discipline, never `include:` them\n   into a user workflow by default. Their prompts and gates are deliberately\n   general so they fit any project; a user workflow composed out of them inherits\n   that generality and loses the specificity that made it valuable. If the user\n   asks for \"something like archon-ship but for my repo\", write a new workflow in\n   their project's terms, using the pack only as the craft reference.\n4. **Primitives.** Thinking in primitives is right: name the small jobs that\n   compose into the outcome — investigate, decide, implement, verify, publish;\n   or intake, enrich, judge, route, notify for an ops queue.\n   Package each coherent reusable primitive as a workflow folder with declared\n   `inputs:` and `returns:`. It can then run alone or compose into a parent with\n   `include:` and `with:`. Nodes implement a primitive's internals and the small\n   one-off glue in a parent: use an AI node for judgment and code for a checkable\n   fact. The primitive set comes from THIS outcome, not from copying another\n   workflow's node list.\n5. **Gate placement (with the user).** Where must the run stop before\n   continuing, and what decides? Three gate kinds, all first-class:\n\n   - **Deterministic gates** — a `bash:`/`script:` node owning an exit code or a\n     `when:` on a declared field. Use wherever a checkable fact settles it —\n     in code work: tests pass, file exists; in ops: totals reconcile, every\n     row has an owner, the export count matches the source. Cheapest and least\n     arguable.\n   - **Prompt command gates** — an independent verifier judging another agent's\n     output against an evidence bar. Start the verifier with `context: fresh`,\n     have it read the diff/artifact/entry, check claims and criteria, and return a\n     structured boolean that drives the loop or branch. When gate design is\n     unknown, START HERE: an agent checking agents is the most powerful\n     general-purpose gate and catches what no fixed check anticipated. Keep the\n     verdict structured (`output_format` boolean) so a machine consumes it; keep\n     the judgment prose in the prompt. Grounded on an **authored sidecar** — a\n     versioned rubric/directions file in the repo — an agent judgment gate stands\n     in for a typical human reviewer: same criteria, same questions, recorded\n     answers, every run.\n   - **Attention pauses** (`wait: { attention: ..., form: approval }`) — only\n     two cases justify them: an **interactive** workflow where someone is present\n     at the console and reads each iteration, or a **super load-bearing** action — irreversible,\n     public-facing, or expensive beyond easy rollback (deploy, post publicly,\n     delete data, send customer-facing communication). Everything else should be\n     decidable by code or by another agent. The pause holds the run durably in the\n     attention queue, whether it started in the foreground, detached, or from a\n     trigger. Container-isolated workflows cannot hold one: Archon refuses a\n     workflow with any `wait:` node under container isolation before creating the\n     run. Reject is an answer, not a cancel: add a `cancel:` node behind\n     `when: \"$<id>.output.decision == 'reject'\"` where rejecting should end the run.\n     The older `approval:` node is deprecated.\n\n   On a project-specific workflow the user often already knows some of these —\n   \"never push before I see the diff\", \"stop if the migration touches billing\",\n   \"no applicant advances without both scorecards agreeing\" — collect them\n   verbatim; they become attention pauses, cancel nodes, or `when:` conditions.\n   Everything else defaults to a prompt command gate until a real run proves\n   which part can be made deterministic.\n\n   **Gates are latent in every domain.** Any process humans govern already\n   invented its controls: double-entry, reconciliation, four-eyes review,\n   checklists, read-backs. Elicitation is the authoring work — have a strong\n   model propose candidate gates from domain knowledge, then have the user (the\n   domain expert) validate which ones actually bind. A proposed gate earns trust\n   only after it has demonstrably refused a planted bad outcome — prove gates red\n   the way you prove fixtures red.\n6. **Data flow.** What does each node hand the next? Declared values\n   (`$node.output`, `output_format` fields, workflow `inputs:`/`returns:`) carry\n   JSON values; artifacts (`$ARTIFACTS_DIR`) carry files. The producing node owns\n   the schema, including for script results and composed workflows. Read\n   `node-reference.md` → Result contracts before declaring a result, and\n   `variables.md` → Artifact pointers when returning a file reference.\n7. **Prompting** per node → see `prompting-mistakes.md` in this folder before writing prose.\n8. **Wiring** into YAML (below), then **fixtures**: every authored workflow ships\n   a dry-run fixture under `<pack>/<workflow>/fixtures/*.stubs.yaml` declaring its\n   expected outcome. Prove red paths red before trusting them. Fixtures prove the\n   paths that dry-run can execute. A reachable `workflow:` or `include:` +\n   `fan_out:` node fails dry-run by design, so prove that path with a small real\n   acceptance run while keeping fixtures for the surrounding wiring.\n9. **Validate** before any real run. None of these steps spends on a model:\n\n   ```bash\n   archon validate workflows <name>                    # schema, references, command files\n   archon workflow run <name> --dry-run --stubs-init stubs.yaml   # scratch stub scaffold\n   archon workflow run <name> --dry-run --stubs stubs.yaml\n   archon workflow test <name>                         # every declared fixture\n   ```\n\n   `--stubs-init` writes a block-style YAML stub scaffold to fill in. Keep it outside\n   `fixtures/`: a dry-run stub file is not a fixture. A fixture file declares a `fixture:` block, and\n   `--stubs` refuses it (\"this is a fixture file; run it with 'workflow test'\"). Copy\n   the stub values you need into a fixture and run it with `workflow test`. A dry run\n   walks the graph with stubbed node outputs and prints each node's resolved provider\n   and model; add `--exec-code` only for trusted `bash:` and `script:` nodes. Then make a planted\n   bad case fail: a fixture that never went red proves nothing. Finish with one real\n   run on a genuine task.\n\n## Agent-first: who decides what\n\nArchon's rule is **YAML coordinates. Code computes. Agents judge.** Give each kind\nof work to the one that does it well:\n\n- **Agents judge.** Reading a comment, a diff, a ticket or a CI log and deciding what\n  it means is a prompt node that returns a small typed result (`output_format`).\n  A script that tries the same judgment freezes one guess into code.\n- **Scripts give facts and act at boundaries.** A script reads the forge, git, files\n  or the CLI and hands the agent facts, or performs the write the agent decided.\n  It never re-judges what the agent already judged. Compute in a `bash:` or\n  `script:` node only when the rule is genuinely fixed (parsing JSON, counting,\n  comparing heads) or a typed invariant must hold before an irreversible action.\n- **YAML coordinates.** It holds topology, gates on typed fields, joins, sessions and\n  waits. Do not grow `when:` into logic; compute the decision in a node and gate on\n  its field.\n- **Natural language is not a wire format.** Never regex or keyword-match prose,\n  whether a user's message, an agent's output or a vendor's error text, to decide\n  what happens next. Let an agent read it and return a typed value.\n\nBad: a script classifies a review comment with a pattern, and the pattern is the\ndecision.\n\n```yaml\n- id: classify\n  bash: |\n    if printf '%s' \"$COMMENT\" | grep -qiE 'lgtm|approve|ship it'; then echo ready; else echo changes; fi\n- id: merge\n  depends_on: [classify]\n  when: \"$classify.output == 'ready'\"\n  script: merge\n  runtime: bun\n```\n\nGood: an agent reads the comment and returns a typed verdict, and the script only\nacts on it.\n\n```yaml\n- id: verdict\n  prompt: |\n    Read the review comment in $ARTIFACTS_DIR/comment.md. Decide whether the\n    reviewer approves merging this change as it stands.\n  output_format:\n    type: object\n    properties:\n      ready: { type: boolean }\n      reason: { type: string }\n    required: [ready, reason]\n- id: merge\n  depends_on: [verdict]\n  when: \"$verdict.output.ready == true\"\n  script: merge\n  runtime: bun\n```\n\nTest at the same seams. A script gets a test at its tool boundary: it writes an issue\nonce, reads back a pushed head, merges only what was approved. Agent judgment is\nchecked by outcome, through dry-run fixtures (`archon workflow test`) and real runs,\nnever by a test that pins a prompt's wording, the YAML's shape or the route a run\ntakes. Do not retest what the engine owns (`when:`, includes, loops, output\nvalidation). In an Archon checkout, `.archon/workflow-language-constitution.md` and\n`.archon/workflows/sdlc/CONSTITUTION.md` (sections 1 to 5 and 13) state these rules\nin full.\n\n## Start simple, then adapt to what runs teach\n\nThe first version is deliberately under-engineered:\n\n- Use few nodes, short prompts, and key gates. Do not add determinism without evidence;\n  before a real run, it is only a guess about where things fail.\n- Dogfood it immediately on something real — a genuine task in the actual repo,\n  not a demo. The run itself is the test.\n- Adapt from evidence, not anticipation: a failure shows which gate was missing;\n  a wasted review shows which check earned its place. Add machinery only where a\n  logged occurrence demands it.\n- Iterate in place: edit the folder, rerun the same kind of task, compare the\n  artifacts. The workflow converges on the project instead of being designed\n  up front.\n\nAll mechanical detail lives in `node-reference.md` (every node type and field,\nwhen:/trigger_rule wiring, fixtures) with the full substitution semantics in\n`variables.md` — read both when writing YAML.\n\n## File layout — one folder per workflow\n\nKeep each workflow in its own folder. If its scripts use pack-level shared\nmodules, copy the pack when moving it between repos. To start a workflow:\n\n```bash\nmkdir -p .archon/workflows/my-pack/my-workflow/{commands,scripts,fixtures}\n```\n\n```text\n.archon/workflows/<pack>/<workflow>/\n├── <workflow>.yaml      # name, description, inputs:, returns:, nodes:\n├── commands/*.md        # prompt files referenced by command: nodes\n├── scripts/*.py|.ts     # deterministic logic (Python/uv preferred, TS/bun fine)\n└── fixtures/*.stubs.yaml\n```\n\nSubstantive prompts live in command files, not inline. Inline `bash:` only for\none-or-two-line glue; anything with branching is a script file. Never shell as a\nscript language for logic. Ask the user their script-language preference if they\nwill maintain it. Declare Python dependencies inline (`deps:` for `uv`); keep\n`node_modules` and virtual environments out of the packaged script tree.\n\nPut shared `.ts`, `.js`, or `.py` modules and `.md` documents in\n`<pack>/.shared/` (subfolders are supported). This reserved directory supplies\nmodules and documents, never workflow definitions or `script:` targets. Documents\nship beside the modules in every build, so a script finds one relative to its own\nlocation and hands its path to a prompt node through `with:`. Missing packaged script targets fail at load time. From `<workflow>/scripts/publish.ts`, Bun can import\n`../../.shared/result.ts`. Python files can add\n`str(Path(__file__).resolve().parents[2] / \".shared\")` to `sys.path` using\n`pathlib.Path`, then import normally; Archon does not inject `PYTHONPATH`.\nAdjust the relative depth for grouped scripts.\n\nThe same paths work in project/global source, bundled binaries, and frozen\ncaptures. Keep imports inside the pack and require no npm packages or target\nproject configuration. Use regular module files: the binary generator rejects\nsymlinks under `.shared`. Write outputs to `ARTIFACTS_DIR` or `STATE_DIR`, never\nbeside a script; Python bytecode caching is disabled in runs and executable fixtures.\n\n## Node types — one per node\n\n`command` (prompt file) · `prompt` (inline) · `bash` (shell, no AI) · `script`\n(py/ts, no AI) · `loop` (iterate one AI node) · `loop_group` (repeat a sub-DAG) ·\n`approval` (deprecated; use `wait: attention`) · `cancel` (terminate with reason, usually behind `when:`) ·\n`wait` (durable suspension, or an `attention:` question answered in a form) · `workflow` (governed child run) · `include`\n(plain load-time composition or runtime-width in-run composition with `fan_out:`).\n\nSelection rules of thumb:\n\n- A machine-verifiable check → `bash:`/`script:` node, never an AI asked to \"check\".\n- Model judgment that gates downstream spend → AI node with `output_format` boolean\n  + a downstream gate or loop channel reading that field.\n- Repeated act→verify cycle → `loop_group` with the verify node's structured\n  boolean driving completion.\n\n## Loop completion channels — pick by tier\n\n- Completion externally checkable → `until_bash: \"<exit-0 check>\"`.\n- Completion is the model's judgment → `output_format` + `until_field: <bool-field>`.\n- Prose sentinel (`until: TOKEN`) is deprecated; prefer the channels above.\n  Declare at least one channel per loop.\n\n## Structured output: only where a machine decides\n\nDeclare `output_format` when a `when:`, `until_field`, or script consumes the\nfield. Otherwise let output stay whole-string prose — schemas are a reliability\ntax on non-enforcing providers. Provider-agnostic YAML always: tiers\n(`small|medium|large`) never literal model ids, no `provider:` keys in bundled work.\n\n## Isolation and interactivity\n\n- At workflow level, `worktree.enabled: true` pins worktree isolation. Omission\n  leaves the choice to the caller. On a `workflow:` child node,\n  `isolation: worktree` explicitly gives the child its own worktree. A worktree\n  bounds git files; it does NOT isolate `~/.archon`, env, database, or sessions.\n- A `wait: attention` node pauses the run in the attention queue until it is answered. It\n  needs no workflow-level declaration and does not stop a detached or triggered launch.\n- Composition (`include:`) runs inside the parent's single run/isolation/audit\n  trail. Use `workflow:` sub-runs only when a separately governed launch is the\n  actual requirement.\n\n## Common mistakes\n\nRead before writing YAML:\n\n- **Prose between nodes as a wire format.** Emitting a token so the next node can\n  grep it. Fix: structured output + typed reads, or a script exit code.\n- **AI doing what code should.** \"Check if tests pass\" as a prompt. Fix: bash/script\n  node owning the exit code.\n- **Parsing model prose to decide.** Regex over `$node.output`. Same fix.\n- **Hardcoding the project's toolchain** (`tsc`, `pytest`) in default-workflow\n  gates. Deterministic checks verify only what Archon owns (git state, artifacts,\n  exit codes of agent-discovered commands).\n- **Silent fallbacks.** An optional input that degrades quietly instead of failing.\n  Fail loud with the missing thing named.\n- **Trusting exit codes / run status as verdicts.** A declined task exits 0;\n  gate on artifacts existing and validating, and on declared outcome fields.\n- **Skipping fixtures.** Unfalsified wiring is unknown wiring. Expected-red paths\n  especially: seen red, or they prove nothing.\n- **Inline everything.** Long prompts inline in YAML, logic inline in bash.\n  Files age better, diff better, and are reusable across nodes.\n",".claude/skills/archon-cli/authoring/node-reference.md":"# Node Reference\n\nMechanical reference: every node type, its fields, how nodes wire together, and\nhow to prove wiring with fixtures. For design judgment (gates, primitives,\nprompting) read `authoring-workflows.md` in this folder first.\n\n## Workflow-level fields\n\n```yaml\nname: my-workflow            # unique; user workflows may shadow bundled names\ndescription: |\n  What this does and when to use it. Shown by `archon workflow list`.\nmodel: large                 # tier keyword (small|medium|large) or @alias — never a literal model id\ninputs:                      # declared signature; callers pass via --input name=value\n  work:\n    default: \"\"\n    description: Optional work override; prompts read $ARGUMENTS explicitly for fallback\nreturns: implement           # which node's output is the workflow's terminal output\noutcome_field: green         # persist one authored boolean beside run status (e.g. green/ready/rooted)\nworktree:\n  enabled: true              # pin worktree isolation. Bounds git files ONLY —\n                             # not ~/.archon, env vars, database, or sessions\nmutates_checkout: false      # declare when the workflow never touches the working tree\n```\n\nAn input default does not fall back to the invocation message automatically.\nWhen both are valid sources, give the model both values and state the precedence:\n\n```yaml\n- id: implement\n  prompt: |\n    Optional work override: $INPUTS.work\n    Original invocation: $ARGUMENTS\n    Use the work override when it is non-empty; otherwise use the original invocation.\n```\n\n## The eleven node types\n\nExactly one of these appears per node: `command`, `prompt`, `bash`, `script`,\n`loop`, `loop_group`, `approval`, `cancel`, `wait`, `workflow`, `include`.\n`approval` is deprecated; new pauses use `wait: { attention: ..., form: ... }`.\n\n### command / prompt — AI nodes\n\n```yaml\n- id: plan\n  command: plan              # loads <workflow-folder>/commands/plan.md — prefer files over inline prose\n  depends_on: [investigate]\n```\n```yaml\n- id: classify\n  prompt: \"Classify $ARGUMENTS. Set type to BUG or FEATURE.\"\n  allowed_tools: []          # no tools needed for pure judgment\n  output_format:             # declare ONLY when a machine consumes a field (gate/until_field/script)\n    type: object\n    properties:\n      type: { type: string, enum: [BUG, FEATURE] }\n    required: [type]\n```\n\nKey fields on AI nodes: `model`/`provider` (tiers), `output_format` (+ `output_type`\nfor typed artifact sidecars), `allowed_tools`/`denied_tools`, `effort`/`thinking`,\n`retry` (default 2x on transient), `persist_session` (continues across runs only on a\nprovider that supports session forks; others start fresh each run), `idle_timeout`.\n\nSession context is explicit when it matters:\n\n- Omitted context inherits the prior compatible provider session in a sequential\n  layer and starts fresh in a parallel layer.\n- `context: fresh` always starts clean. Use it for independent verification.\n- `context: shared` explicitly inherits the ambient session in a sequential\n  layer. It is invalid where parallel structure makes that ancestry ambiguous.\n- `context: { resume: plan }` forks the named completed upstream node's session.\n  Use it when one exact ancestry matters; the provider must support session forks.\n  Inside a `loop_group` body the source can be an enclosing node, whose session the\n  body continues across iterations, or an upstream node in the same body, whose\n  session of that iteration the consumer forks. The same-body form suits a follow-up that asks the agent to\n  finish a step it skipped.\n\n### bash — deterministic shell, no AI\n\n```yaml\n- id: test-gate\n  bash: bun run test\n  timeout: 300000            # ms; stdout captured as $test-gate.output\n```\nExit code decides pass/fail. On `timeout`, Archon signals a host node's process tree,\nso work behind `bun run` or another wrapper stops too. A process that leaves the node's\nprocess group (`setsid`) escapes. In a container node, a timeout ends only the local\n`docker exec` client, and the command inside the container may keep running. In a detached run on macOS or Linux, a timeout whose\ncleanup cannot be confirmed fails the node, even with `on_timeout: skip`.\n\nThe same node shape verifies non-code work:\n`bash: python3 scripts/reconcile.py --period 2026-08` (totals must balance),\nor a script that checks every imported row has an owner. One-or-two-line glue\nonly; anything with branching is a script node.\n\n### script — TypeScript (bun) or Python (uv), no AI\n\n```yaml\n- id: verify-report\n  script: verify-report      # loads <workflow-folder>/scripts/verify-report.py\n  runtime: uv\n  depends_on: [implement, review]\n  with:                      # typed value transport into INPUTS_<UPPER_SNAKE> env vars\n    green: \"$implement.output.green\"\n    prior: {from: \"$review.output\", if_skipped: \"none\"}\n```\n\nPrefer named script files over inline bodies. Python (`runtime: uv`) preferred,\nTypeScript (`runtime: bun`) fine. `deps:` adds uv dependencies.\n\n### Result contracts — the producer owns the schema\n\nUse the producing node's `output_format` for a JSON result. `returns:` selects\nthat node; it does not declare another schema. No value-shape declaration belongs\non `inputs:` or `returns:`.\n\n```yaml\nname: certified-result\ndescription: Certify a deterministic result and bind its field downstream\nreturns: build\nnodes:\n  - id: build\n    bash: printf '%s' '{\"ready\":true}'\n    output_format:\n      type: object\n      properties:\n        ready: {type: boolean}\n      required: [ready]\n  - id: consume\n    depends_on: [build]\n    with:\n      ready: \"$build.output.ready\"\n    runtime: uv\n    script: |\n      import os\n      print(os.environ[\"INPUTS_READY\"])\n```\n\nA `bash:` or `script:` node with `output_format` must print one strict JSON\ndocument satisfying that schema. Send diagnostics to stderr. There is no JSON\nrepair or reask; a contract violation fails the node and is not retried, even\nwith `retry:`. A schema that cannot compile is a load error. Without\n`output_format`, exec stdout remains raw text. Named scripts use the same rule;\nthe small bash example above keeps the handoff visible.\n\nAn `include:` alias exposes the selected producer's contract through flattening.\nA `workflow:` caller receives the child's selected result and declared field\nnames, persisted for cold resume. Do not repeat the schema on the caller:\n`output_format` on a `workflow:` node is a load error; on `include:` it is ignored\nwith a warning. A schemaless child stays schemaless. Fan-out returns an ordered\narray of item values; consume the whole aggregate, not a field on the array.\n\nFor field-access and file-pointer rules, read `variables.md` → Node Output Details and\nArtifact pointers. The docs' `reference/workflow-language.md` → Result contracts\ngives the full composition examples.\n\n### loop — iterate ONE AI job\n\n```yaml\n- id: implement\n  loop:\n    command: implement\n    until_field: done        # boolean in this node's output_format\n    max_iterations: 5\n  depends_on: [plan]\n  output_format:\n    type: object\n    properties:\n      done: {type: boolean}\n    required: [done]\n```\n\nCompletion channels — declare at least one, pick by tier:\n\n- `until_bash: \"<exit-0 check>\"` — completion externally checkable (deterministic)\n- `until_field: <bool>` — completion is the model's judgment, via `output_format`\n- `until: TOKEN` — deprecated prose sentinel; use one of the channels above\n\nAlso: `fresh_context: true` (each iteration starts clean) and `$LOOP_PREV_OUTPUT`\nfor previous-iteration state.\nA failed iteration fails the loop at `max_iterations` — bound exhaustion fails\ntruthfully rather than blessing a bad last attempt.\n\n### loop_group — repeat a sub-DAG\n\n```yaml\n- id: fix-cycle\n  depends_on: [review-findings]\n  loop_group:\n    nodes:\n      - id: fix\n        prompt: \"Address findings: $review-findings.output\"\n      - id: recheck\n        prompt: \"Verify each finding is fixed with evidence.\"\n        depends_on: [fix]\n        context: fresh\n        output_format: {type: object, properties: {ready: {type: boolean}}, required: [ready]}\n    until_bash: 'ready=$recheck.output.ready; test \"$ready\" = \"true\"'\n    max_iterations: 5\n```\n\nBody is sealed (no external `depends_on`); outer outputs reach bodies via\n`$nodeId.output`. In a `loop_group` body, `$LOOP_PREV.<body-id>.output` reads the\nprevious iteration: prompts text-substitute it, while `when:` conditions resolve it\nas a typed condition reference. A failed body node fails the group immediately.\n\n### approval — deprecated\n\n`approval:` still loads and runs, but the loader warns once per workflow file, naming\neach node and its replacement. Do not write new ones. Replace:\n\n- `approval: { message }` → `wait: { attention: <message>, form: approval }`. A legacy\n  reject cancels the run; the attention form continues with `{decision: 'reject'}`, so\n  add a `cancel:` node with `when: \"$<id>.output.decision == 'reject'\"` where rejecting\n  should end the run.\n- `approval.decisions` → `wait: { attention, form: select, options }` with the same ids\n  and labels; same `{decision, text}` output.\n- `capture_response` → read the answer's `$<id>.output.text`; every form records text.\n- `on_reject` / `$REJECTION_REASON` → a rework node with\n  `when: \"$<id>.output.decision == 'reject'\"` that reads `$<id>.output.text`, inside a\n  `loop_group` if it should repeat.\n\n### cancel — terminate deliberately\n\n```yaml\n- id: refuse\n  cancel: \"This workflow handles bugs only.\"\n  when: \"$classify.output.type != 'BUG'\"\n```\n\n### workflow / include — reuse another workflow\n\nUse `workflow:` to start a governed child run with its own artifacts, gates, cost,\nand audit trail. Use `include:` to flatten a reusable workflow block into the\ncurrent run at load time.\n\n```yaml\n- id: review-child\n  workflow: archon-review\n  with:\n    scope: \"$INPUTS.branch\"\n  isolation: worktree\n\n- id: review-inline\n  include: archon-review\n  with:\n    scope: \"$INPUTS.branch\"\n```\n\nThe `workflow:` target name is static YAML but resolves when the node executes,\nso a prior node may author the child workflow. `workflow:` also accepts `input:`\ninstead of `with:` and can declare `fan_out:`. A plain `include:` resolves and\nexpands fully at load time; the directive carries only structural graph fields\nplus `with:` and has no execution surface of its own. The include's `when:` gates the\nwhole block. Inside a `loop_group` it reads `$LOOP_PREV` from the loop that contains\nthe include.\n\nOn a `workflow:` node, `fan_out.max_parallel` limits unfinished children (default\n`5`). `max_parallel: all` starts every item at once: use it when each child asks an\nindependent attention question, because a paused child keeps its slot and a numeric\nwindow leaves later questions unstarted. Under `join: all_done` the node completes once\nevery child is terminal, even when some failed, never started or were cancelled; the run\nrecords each slot's outcome for operators (`workflow get`), but a downstream node that\njudges the results reads the `archon_failed` markers in `$<id>.output`.\n\nPut `fan_out:` on `include:` when runtime data determines how many copies of a\nstatic block should run inside the parent run:\n\n```yaml\n- id: list-scopes\n  bash: printf '[\"packages/cli\",\"packages/workflows\"]'\n\n- id: review-each\n  include: archon-review\n  depends_on: [list-scopes]\n  fan_out:\n    items: \"$list-scopes.output\"\n    as: scope               # required; each instance receives $INPUTS.scope\n    max_parallel: 1         # serial escape when the included block may write\n    join: all_done\n```\n\nThis form creates no child runs. It returns an item-ordered JSON array under\n`$review-each.output`. With `join: all_done`, a failed instance contributes an\n`archon_failed` marker; `all_success` fails the node. To run instances\nconcurrently, the complete included block must declare `mutates_checkout: false`.\nApproval gates, durable waits, and other suspension paths are not supported\ninside composed fan-out; put them before or after the fan-out.\n\n### wait — suspend durably\n\n```yaml\n- id: pause-for-ci\n  wait:\n    duration_ms: 300000\n```\n\nA wait declares exactly one condition: `duration_ms`, an ISO timestamp under\n`until`, `event` plus `deadline_ms`, or `attention`. Archon persists the suspension and\nreleases the worker slot. A time or event wait's fixed structured output includes\n`status` (`satisfied` or `expired`) and `waited_ms`; event waits may also return `event`\nand `payload`.\n\n`attention` asks a question that pauses the run until someone answers it in its `form`:\n\n```yaml\n- id: ask\n  depends_on: [plan]\n  wait:\n    attention: \"$plan.output.question\"\n    form: select              # message (default) | approval | select | multi_select\n    options:                  # select and multi_select only; or one whole \"$plan.output.options\"\n      - { id: use-main, label: \"Base on main\" }\n      - { id: use-dev }\n    labels:                   # optional; plain strings, runtime refs resolved at pause\n      about: \"$plan.output.topic\"\n```\n\nThe next node reads the answer: `{text}` for `message`, `{decision, text}` for `approval`\nand `select`, `{decisions, text}` for `multi_select`. In the `approval` form `reject` is\nan answer and never cancels the run; branch on `$ask.output.decision` with `when:`:\n\n```yaml\n- id: review\n  depends_on: [plan]\n  wait:\n    attention: \"Approve this plan? $plan.output\"\n    form: approval\n- id: stop\n  depends_on: [review]\n  when: \"$review.output.decision == 'reject'\"\n  cancel: \"Plan rejected.\"\n- id: implement\n  depends_on: [review]\n  when: \"$review.output.decision == 'approve'\"\n  command: implement\n```\n\nPause only for an interactive session or a super load-bearing action. The run pauses\nin the attention queue until answered, however it was started. To ask after each pass\nof a repeated body, end a `loop_group` body in the wait (it must be the body's sole\nterminal sink) and read the answer next iteration as `$LOOP_PREV.<wait-id>.output.text`.\n\n`labels` mean nothing to the engine; they let the user's handler workflows pick the\npause out of `archon workflow attention --label k=v`.\n\n## Wiring nodes together\n\n**`depends_on`** creates edges; independent nodes in one topological layer run\nconcurrently.\n\n**Data flow** — declared values carry scalars, artifacts carry documents:\n\n- `$nodeId.output` — whole text of an upstream node; unknown/skipped producer → `''`\n- `$nodeId.output.field` — strict JSON access: missing key **fails the consumer**\n  (never silent-empty); declared-optional field resolves to `''`\n- `with:` bindings (#2637) — prompt/command files read bound values as\n  `$INPUTS.<name>`; bash/script processes receive `INPUTS_<UPPER_SNAKE>` env vars.\n  `{from, if_skipped}` discriminates skipped producers from ran ones; a failed\n  producer fails the binding\n- `$INPUTS.<name>` — declared workflow inputs, also delivered as\n  `INPUTS_<UPPER_SNAKE>` to bash/script\n- `$ARTIFACTS_DIR` — pre-created directory for reports/evidence humans read.\n  Throwaway checkouts and dependency installs go under `$ARTIFACTS_DIR/.archon/scratch/`\n  (create it with `mkdir -p`), which Archon deletes once the run ends and its nodes have\n  stopped (a container run's when its container is removed); the rest of\n  `$ARTIFACTS_DIR` is kept\n- In `bash:` and `script:` sources, user-controlled variables (`$ARGUMENTS`,\n  `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`,\n  `$CONTEXT`) are **not** spliced as raw text (injection guard); they arrive as\n  environment variables (`ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`,\n  `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT`, `EXTERNAL_CONTEXT`,\n  `ISSUE_CONTEXT`). Read an environment-delivered value in `bash:` as `\"$ARGUMENTS\"`.\n  In `script:` read it via `process.env.ARGUMENTS` (bun) or `os.environ['ARGUMENTS']`\n  (uv/python).\n  A `loop_group` body's `bash:` node is the exception: the loop scope splices the\n  reply (`$LOOP_USER_INPUT`) into its source, shell-quoted, so the reply arrives\n  already quoted. Read the spliced reply as an already-quoted value — assign it\n  and quote the variable (`value=$LOOP_USER_INPUT; printf '%s' \"$value\"`) — rather\n  than as `\"$LOOP_USER_INPUT\"`.\n  `$nodeId.output` refs ARE substituted; give prose producers an `output_format`\n  before assigning their output directly in a script.\n\n**`when:` conditions** skip a node when false (skipped propagates to dependants):\n\n```yaml\nwhen: \"$classify.output.type == 'BUG'\"\nwhen: \"$score.output.confidence >= '0.9' && $gate.output.proceed == true\"\n```\n\nOperators: `==`, `!=`, `<`, `>`, `<=`, `>=` (numbers auto-parse, fail-closed on\nnon-numeric), compound with `&&`/`||` (`&&` binds tighter, no parentheses).\nMalformed expression → node skipped + warning. Dot-access contract violation →\nnode FAILS loudly. Guard optional paths with `when:` or `trigger_rule`.\n\n**`trigger_rule`** — join semantics over dependencies:\n\n| Value | Behavior |\n|-------|----------|\n| `all_success` | all deps succeeded (default) |\n| `one_success` | at least one dep succeeded |\n| `none_failed_min_one_success` | none failed AND ≥1 succeeded (skips OK) |\n| `all_done` | all deps terminal (completed, failed, OR skipped) |\n\n## Fixtures — prove wiring without AI spend\n\nEvery authored workflow ships dry-run fixtures in\n`<workflow-folder>/fixtures/<name>.stubs.yaml`:\n\n```yaml\n# Declared expectations + node stubs. Every non-reserved key is a node output.\nfixture:\n  expect: completed          # or failed / paused / cancelled\n  fail-node: assert-changed  # required when expect: failed — must match exactly\n  inputs:\n    branch: task-42\nimplement:\n  done: true\n  green: true\n  summary: \"stub summary\"\nexec-code: false             # true executes bash/script nodes for real (requires a git checkout)\n```\n\nRun them:\n\n```bash\narchon workflow test                    # everything discovered\narchon workflow test my-workflow        # one workflow's fixtures\n```\n\nA reachable attention wait pauses the simulation unless the fixture stubs it; the stub\nstands in for the answer and must match the form's output, e.g.\n`review: { decision: approve, text: \"\" }`. Deprecated `approval:` gates auto-approve.\n\nExpected-red fixtures are the important ones: a guard that has never been seen\nfailing proves nothing. Prove reds red before trusting them. `workflow test`\nmirrors what a real run does with its two directories: named scripts and command\nfiles come from one frozen capture of your working tree taken per invocation —\nuncommitted edits included, so authoring works as normal — while exec-code nodes\nexecute against a scratch worktree of HEAD. So a script that reaches outside\n`.archon/` with a `__file__`-relative path fails the fixture exactly as it would\nfail the run, and nothing a node executes can touch your checkout. Exec-code\nfixtures require a git checkout and fail explicitly outside one. Validate\nload-time errors with `archon workflow list`.\n",".claude/skills/archon-cli/authoring/prompting-mistakes.md":"---\nname: archon-cli-prompting-mistakes\ndescription: Common prompt mistakes when invoking Archon workflows or writing node prompts. Short, negative-form guidance. Read before dispatching work or authoring prompts.\n---\n\n# Prompting Basics\n\n## General\n\n1. Agents are smart — take advantage of that. Each command node runs a real\n   coding-agent harness under the hood, with all its capabilities.\n2. Agents inside Archon do not necessarily know about Archon itself, this run,\n   or past failures. Do not reference context the agent has no awareness of.\n\n# Prompting Mistakes\n\nShort list. Applies in two places: the **message you pass when invoking a\nworkflow** (it becomes `$ARGUMENTS` for the whole run) and the **prompts inside\ncommand/prompt nodes** you author.\n\n## The mistakes\n\n1. **Vague outcome, implied method.** \"Look into the auth thing.\" Say what done\n   looks like and where context lives: \"Fix issue #42. Use the issue, comments,\n   and current source as evidence. Preserve the documented login behavior.\"\n2. **Smuggling your diagnosis.** If you want the run's investigation tested,\n   ask for the fix in ordinary language — do not hand it your suspected root\n   cause as fact unless you have proven it.\n3. **Narrating to nobody.** Unattended nodes produce artifacts, commits, and\n   declared fields — not chat. Prompts that say \"report to me below\" produce\n   output nothing ever reads. Point every instruction at the artifact it serves.\n4. **No evidence bar.** A judging prompt without \"cite the causal line / drop\n   anything that rests on 'might'\" collects plausible filler. State what counts\n   as proof, and demand silence otherwise.\n5. **Trusting exit codes as verdicts.** A declined task exits 0. Gates read\n   artifacts and declared fields, not success bits.\n6. **Prose wire formats.** Asking a node to end with a magic token the next node\n   greps. Use structured output fields or script exit codes instead.\n7. **Everything-but-the-kitchen-sink scope.** One node, one job, stated non-goals.\n   \"Also update docs and tests and the changelog\" dilutes all three. Negative\n   scope (\"do not touch the lockfile\") is as load-bearing as positive.\n8. **Assuming memory.** Each node may be a fresh session with no prior context.\n   Restate the input path, artifact path, and constraint inside each prompt.\n9. **Stop rules left unstated.** Tell the agent when to stop and surface:\n   ambiguity → present options; missing context → name it. Never \"use judgment\"\n   on decisions with real cost.\n10. **Soft failure language.** \"Try to...\" invites theater. Put checkable failure\n    in a `bash:`/`script:` node whose exit code fails the run, or have a judging\n    node return a structured boolean and gate on it deterministically.\n\n## The one-line test\n\nBefore sending any prompt — invocation or node — ask: *could a competent engineer\nwho has never seen this conversation act on exactly this text?* If the answer\nneeds context that lives only in your head, the prompt is not done.\n",".claude/skills/archon-cli/authoring/variables.md":"# Variable Substitution Reference\n\nVariables are placeholders in command files and workflow prompts that get replaced at execution time.\n\n## Variable Table\n\n| Variable | Scope | Description |\n|----------|-------|-------------|\n| `$ARGUMENTS` | All modes | The user's original message passed to the workflow |\n| `$USER_MESSAGE` | All modes | Same as `$ARGUMENTS` — both resolve to the user's message |\n| `$WORKFLOW_ID` | All modes | Unique workflow run ID (for tracking and logging) |\n| `$ARTIFACTS_DIR` | All modes | Pre-created directory for this workflow run's artifacts. Write outputs here. Scratch work (a throwaway checkout, a dependency install) goes under `$ARTIFACTS_DIR/.archon/scratch/`, which Archon deletes once the run ends and its nodes have stopped (a container run's when its container is removed) |\n| `$BASE_BRANCH` | All modes | The run's resolved or recorded base branch. Throws if referenced but unresolved. See the [Variable Reference](https://archon.diy/reference/variables/#workflow-variables) for resolution and child inheritance rules |\n| `$DOCS_DIR` | All modes | Documentation directory (config `docs.path`, default `docs/`). Never throws |\n| `$CONTEXT` | All modes | GitHub issue/PR context (if available from platform). Empty string if unavailable |\n| `$EXTERNAL_CONTEXT` | All modes | Alias for `$CONTEXT` |\n| `$ISSUE_CONTEXT` | All modes | Alias for `$CONTEXT` |\n| `$LOOP_USER_INPUT` | Loop / loop_group prompts | **Deprecated** with interactive loop gates; new loops end a `loop_group` body in a `wait: attention` node and read `$LOOP_PREV.<wait-id>.output.text`. Feedback from `/workflow approve <id> <text>` at an interactive loop gate. Populated ONLY on the first iteration after a resume; empty string everywhere else |\n| `$LOOP_PREV_OUTPUT` | Loop prompts | Previous iteration's cleaned output (completion tags stripped). Empty on iteration 1. Key tool for `fresh_context: true` loops that need to know what the last pass did |\n| `$LOOP_PREV.<nodeId>.output[.field]` | loop_group body prompts and `when:` conditions | Previous iteration's output of a specific body node. Empty on iteration 1. Field access follows the strict contract below (except genuinely-absent prior output → `''`). In a body `when:`, it is a typed condition reference rather than text substitution |\n| `$REJECTION_REASON` | Legacy `approval.on_reject` prompts | **Deprecated.** The reason from `/workflow reject <id> <reason>`. Empty string everywhere else. New workflows read an attention wait's `$<wait-id>.output.text` from a node behind `when: \"$<wait-id>.output.decision == 'reject'\"` |\n| `$nodeId.output` | DAG only | Full text output of a completed upstream node. Unknown/skipped producer → `''` |\n| `$nodeId.output.field` | DAG only | JSON field access — **strict**: an unresolvable field FAILS the consuming node (see below) |\n\n## Where Variables Are Substituted\n\n- **Command files** (`.archon/commands/*.md`) — the core set (`$ARGUMENTS`/`$USER_MESSAGE`, `$WORKFLOW_ID`, `$ARTIFACTS_DIR`, `$BASE_BRANCH`, `$DOCS_DIR`, `$CONTEXT` family), plus `$nodeId.output[.field]` when the command runs as a DAG node. A command file referenced by `loop.command` or a `loop_group` body receives the same populated loop variables as an inline loop prompt. Ordinary DAG command nodes receive `''` for loop/rejection variables. `$REJECTION_REASON` is populated only in an `approval.on_reject` prompt\n- **Inline `prompt:` fields** — in DAG prompt nodes, loop prompts, and loop_group body prompts\n- **`bash:` scripts** — SPECIAL: user-controlled variables (`$ARGUMENTS`, `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`, `$CONTEXT`) are **NOT spliced as raw text** into the script (shell-injection guard). A `loop_group` body's `bash:` node is the exception: the loop scope splices the reply (`$LOOP_USER_INPUT`) into its source, shell-quoted, so the reply arrives already quoted. Read the spliced reply as an already-quoted value — assign it and quote the variable (`value=$LOOP_USER_INPUT; printf '%s' \"$value\"`) — rather than as `\"$LOOP_USER_INPUT\"`. In `bash:`, they arrive as **environment variables** instead: `ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`, `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT` (+ `EXTERNAL_CONTEXT`/`ISSUE_CONTEXT`), plus `ARTIFACTS_DIR`, `LOG_DIR`, `BASE_BRANCH` — read an environment-delivered value with `\"$ARGUMENTS\"` as normal shell env access. `$nodeId.output` refs ARE substituted: small string values are single-quoted inline; values exceeding 32,768 UTF-16 code units (JavaScript `string.length`) spill to a file and substitute as unquoted `$(cat '<path>')`, which drops trailing newlines; the command substitution splits words and expands globs when used as a bare argument. Assign first (`value=$nodeId.output`), then quote the shell variable (`printf '%s' \"$value\"`) to handle both forms. See [Shell quoting guidance](https://archon.diy/reference/variables/#shell-quoting-in-bash-vs-script).\n- **`script:` bodies** — like `bash:`, user-controlled variables (`$ARGUMENTS`, `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`, `$CONTEXT`) are **NOT spliced as raw text** into the source (injection guard, #2115). They arrive as **environment variables** — `ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`, `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT` (+ `EXTERNAL_CONTEXT`/`ISSUE_CONTEXT`), plus `ARTIFACTS_DIR`, `LOG_DIR`, `BASE_BRANCH` and managed per-project env — read via `process.env.ARGUMENTS` (bun) / `os.environ['ARGUMENTS']` (uv/python) — env vars are the general injection-safe channel for untrusted values. A literal `$ARGUMENTS`/`$USER_MESSAGE`/`$CONTEXT` left in the source is not substituted: the text stays in the source. `$nodeId.output` values are still substituted **raw** (not shell-quoted). Direct assignment (`const data = $nodeId.output;`) is safe **only with `runtime: bun` when the producer uses `output_format`**. JSON booleans and nulls are not Python literals, so a `runtime: uv` script should bind the value with `with: { data: \"$nodeId.output\" }` and parse `os.environ['INPUTS_DATA']` with `json.loads`. For arbitrary-text producers (bash/script stdout or prose), use the same environment binding instead of embedding the raw value in source, then parse defensively only if the text is expected to contain JSON. **Avoid wrapping `$nodeId.output` in a `String.raw` template literal** — it silently breaks when the output contains a backtick (common in AI-generated markdown and `output_format` payloads)\n\n## Substitution Order\n\nA template is substituted in one pass. Each reference written in the template resolves once, and the value is inserted exactly as written: a message, issue context, loop reply, rejection reason, previous output, or input value that contains `$nodeId.output`, `$INPUTS.<name>`, a variable name, or `$&` reaches the node unchanged. Where two grammars match at the same `$`, the earlier group wins:\n\n1. Loop references inside a `loop_group` body (`$LOOP_PREV.<nodeId>.output`, the group's `$LOOP_USER_INPUT`)\n2. Workflow variables (`$WORKFLOW_ID`, `$ARGUMENTS`, `$ARTIFACTS_DIR`, `$BASE_BRANCH`, `$DOCS_DIR`, `$INPUTS.<name>`, `$CONTEXT`, loop/rejection vars)\n3. Node output references (`$nodeId.output`, `$nodeId.output.field`) — DAG mode only\n\n## Context Auto-Append\n\nIf `$CONTEXT` / `$EXTERNAL_CONTEXT` / `$ISSUE_CONTEXT` is NOT present anywhere in the prompt template but context exists (e.g., from a GitHub issue), it is automatically appended at the end after a `---` separator.\n\n## NOT Supported: `$1` … `$9` Positional Arguments\n\nDespite older docs suggesting otherwise, positional `$1`…`$9` are **not substituted** by the workflow engine — command files and prompts receive the whole message only, via `$ARGUMENTS`/`$USER_MESSAGE`. (A legacy positional-substitution helper exists in the codebase but is not wired into the execution path.) Parse arguments inside the prompt or with a `bash:`/`script:` node instead.\n\n## Node Output Details (DAG Only)\n\n`$nodeId.output` resolves to the full text output of the upstream node. If the node used `output_format:` (structured output), the output is the JSON-stringified validated result. Bash/script output without `output_format` is stdout with the trailing newline trimmed; with it, stdout is certified as JSON under the same result contract (see `node-reference.md` → Result contracts). Loop/loop_group output is the final iteration's output with completion-signal tags stripped. An attention wait outputs its answer by form: `{text}` for `message`, `{decision, text}` for `approval` and `select`, `{decisions, text}` for `multi_select`; read fields as `$ask.output.decision` and `$ask.output.text`. A deprecated gate with authored `approval.decisions` also outputs JSON `{decision, text}`. A deprecated gate without authored decisions keeps the old behavior: its approval comment is output only when `capture_response: true`, otherwise `''`. Unknown or skipped producers resolve to an empty string (with a warning logged).\n\n`$nodeId.output.field` and nested paths such as `$nodeId.output.proposal.action` are **strict** (no-silent-drop) — they either resolve or **fail the consuming node**. Paths are object field names only: no array indexing, wildcards or expressions. The same paths work in templates, `when:` (which needs a scalar), whole-value `with:` bindings and `$LOOP_PREV.<id>.output.a.b`. Do not flatten a producer's schema to reach a nested field.\n\n- Producer has `output_format`: every segment must be a declared `properties` entry; a declared field or parent that is absent or null resolves to `''` (declared-optional). An undeclared segment, at any depth, fails `archon validate workflows` when the producer's schema is known at load, and fails the consumer otherwise (typo protection).\n- Schemaless producer (no `output_format` on the producing node): the output must be a JSON object containing every key on the path, with an object at each intermediate step — anything else (non-JSON output, missing key, a scalar or array mid-path) fails the consumer.\n- Producer skipped or pending: fails the consumer — guard the reference with `when:` or a permissive `trigger_rule`.\n\nValues: strings pass through; numbers/booleans stringify; objects/arrays are JSON-stringified.\n\nFor `workflow:` results, these field rules use the child's `returns:` node's\nschema, whose declared paths travel with the value and survive cold resume. The\ncaller cannot add or narrow the contract. An `include:` alias uses its selected\nproducer directly after flattening.\n\n### Artifact pointers\n\nReturn a small JSON value containing a pointer when a machine consumer needs a\nfile result. The reserved shape is:\n\n```json\n{\"type\":\"archon_artifact\",\"run_id\":\"<producing run id>\",\"path\":\"review/report.md\"}\n```\n\nUse the actual `WORKFLOW_ID` value for `run_id`, not the literal variable name.\nThe producer must first write the file under its `$ARTIFACTS_DIR`. Sibling keys\non the pointer are allowed. Before persisting the result, the engine validates\ntagged pointers against the producing run: its own run id, a nonempty relative\npath with no `..` segment or NUL, lexical containment, and an existing regular\nfile. Absolute/escaping paths, directories, missing files, and another run's id\nfail the producer.\n\nA `workflow:` parent and fan-out aggregate relay pointers unchanged without\nrevalidating them against the parent. Events and resume retain the run id and\nrelative path. The engine does not expand the pointer into an absolute path,\nread its contents into a prompt, or provide an in-workflow resolver. For prompt\nhandoffs inside the producing run, keep using `$ARTIFACTS_DIR/<path>`.\n\nThe reader owns authorization, run reachability, and realpath containment at\nread time. Producer validation is not a read-side symlink guarantee; the artifact\nAPI's outstanding realpath check is tracked in #3160. Full semantics live in\nthe repository's `reference/variables.md` → Artifact pointers in a result.\n",".claude/skills/archon-cli/operate/managing-runs.md":"---\nname: archon-cli-manage-run\ndescription: Inspect, control, and resolve Archon workflow runs: list/get/status, approve, reject, respond, cancel vs abandon, resume. Read when a run is paused, failed, or the user asks about one.\n---\n\n# Managing runs\n\nEvery run-control verb, what it actually does, and the gate semantics that are\neasy to get wrong. Project listings scope by cwd; full run IDs remain globally\naddressable.\n\n## Output contract\n\n- `--json` → one clean JSON object on stdout; logs suppressed. Use when parsing.\n- No flag → human-readable text; diagnostics on stderr.\n\n## Overriding models for one run\n\nWhen the user asks to run on different models for a specific run, rebind the\ntier keywords or aliases at launch — the persistent config stays untouched:\n\n```bash\n# Rebind one or more tiers / @aliases for this run only; repeat per binding\narchon workflow run archon-ship --branch fix/x \"...\" \\\n  --model large=pi/minimax/minimax-m3 \\\n  --model small=openai/gpt-5-mini\n```\n\nSpec format: `<tier|@alias>=<provider>/<model-ref>` where `<model-ref>` is what\nthe named provider expects (for Pi backends that is itself a `<vendor>/<model>`\nref, e.g. `pi/minimax/minimax-m3`). An unknown tier/alias errors with the list\nof defined names.\n\nVerify before dispatching real work:\n\n```bash\n# dry-run prints each node's resolved provider/model without AI spend\narchon workflow run <workflow> \"test\" --dry-run --model large=...   # see 'runs on:' lines\n```\n\nFor reusable alternate setups, prefer a config layer instead of long flag lists\n(see `../setup/setup-and-config.md`, \"Alternate config layers\").\n\n## Verbs\n\n| Goal | Command |\n|---|---|\n| List recent runs (this project) | `archon workflow runs --json` |\n| Across all projects | `archon workflow runs --all --json` |\n| Filter by status / cap rows | `archon workflow runs --status running --limit 50 --json` |\n| One run's status/error | `archon workflow get <run-id> --json` |\n| One run with per-node detail | `archon workflow get <run-id> --verbose --json` |\n| One run with raw event rows | `archon workflow get <run-id> --verbose --events --json` |\n| List a run's artifact files | `archon workflow artifacts <run-id> --json` |\n| Read one artifact | `archon workflow artifacts <run-id> <path>` |\n| List registered projects (and their `--project` keys) | `archon project list --json` |\n| Active runs (this project) | `archon workflow status --json` |\n| Active runs (all projects) | `archon workflow status --all --json` |\n| Block until the run ends or needs attention | `archon workflow wait <run-id> --json` |\n| Every run that needs someone (this project / all) | `archon workflow attention --json [--all] [--label k=v]` |\n| The queue item one run stands for | `archon workflow attention <run-id> --json` |\n| Every change in the queue, as it happens | `archon workflow attention --watch --json [--all] [--label k=v] [--after <cursor>]` |\n| Classify an item without answering it | `archon workflow attention label <run-id> --occurrence <id> --set k=v --json` |\n| Resolve a gate with any declared decision | `archon workflow respond <run-id> <decision> [text]` |\n| Answer an attention wait in its form | `archon workflow answer <run-id> --occurrence <id> [--choose <id>]... [--text <t>]` |\n| Approve (default vocabulary) | `archon workflow approve <run-id> [text]` |\n| Reject (default vocabulary) | `archon workflow reject <run-id> \"<reason>\"` |\n| End a stalled turn on a running run, keep the run | `archon workflow end-turn <run-id>` |\n| Stop a running run (its live owner first) | `archon workflow cancel <run-id>` |\n| Mark paused/failed/orphaned run cancelled | `archon workflow abandon <run-id>` |\n| Resume failed/paused from completed nodes | `archon workflow resume <run-id>` |\n\n## Answering a decision well\n\nAn attention item is a question a run cannot settle. Most are written to be answered\nby whoever is on top of the runs, which is often you, not the user. Answer the ones\nthe user's standing direction already decides; bring the user only the ones that are\nunclear, risky, or outside what they have told you.\n\n1. Read the whole item: what is being decided, why it matters, the recommendation\n   with its evidence and what would change it, the choices and what each does, and\n   what happens if nobody answers. Open the evidence links before agreeing.\n2. Choose one of the declared choices. When none fits and the item offers `other`,\n   choose it and say what to do instead.\n3. Give a reason in the text. Workflows record the answer's text with the decision,\n   and some write it down as a ruling: the SDLC build queue and merge items owned by\n   a build add it to the issue body as a dated \"Current scope\" section that later\n   rounds work from. Enrolled own PRs keep the answer on the work item's merge\n   decision entry and write no issue section. A bare choice teaches\n   the next round nothing.\n4. Answer detached, so the continuation does not run in your shell:\n\n   ```bash\n   archon workflow respond <run-id> <choice> \"<reason>\" --detach\n   ```\n\n5. Pass `--occurrence <id>` when you answer from an item you read earlier: the\n   `occurrence` on its stream line, or `attention.occurrence` from `workflow wait\n   --json`. If the run has moved to a different pause, the answer is refused and the\n   error names the current occurrence, instead of landing on a question you never\n   read.\n\n## The approve/resume two-step\n\nWith `--json`, `approve`/`reject`/`respond` **record the decision and stop** —\nthis command does not execute the run (streaming output would corrupt the JSON).\nThe answered gate is a due continuation: a running `archon serve`, or\n`archon workflow wake [--watch]`, continues it without anyone resuming by hand.\nContainer runs (including a container write-back gate) are the exception: the\nhosts cannot continue them, so run `archon workflow resume <run-id>` yourself.\nTo record AND continue in this process:\n\n```bash\narchon workflow approve <run-id> \"ship it\"   # no --json: records + auto-resumes; background task!\n```\n\nA `wait: attention` pause (`attention.kind: \"attention\"`) is answered in the `form` it\nreports, with one of the `options` ids it lists:\n\n```bash\narchon workflow answer <run-id> --occurrence <id> --text \"done\" --json            # message\narchon workflow answer <run-id> --occurrence <id> --choose use-dev --json         # approval/select\narchon workflow answer <run-id> --occurrence <id> --choose a --choose b --json    # multi_select\n```\n\n`approve`/`reject`/`respond <option>` are shorthand for the same answer. In the\n`approval` form `reject` is an answer and does not cancel the run. Like a gate answer,\nthe answered wait is a due continuation. Answer only what the user actually decided; an\nundeclared option is refused. A `legacy:` occurrence is a wait paused before answers\nexisted: it takes no answer, so `workflow resume` it once its action is done.\n\nTo work the whole queue instead of one run, `archon workflow attention --json` lists\nevery item that needs someone, each with its `kind`, `occurrence`, `form`, `options`,\n`labels`, typed `project` and pointers to its transcript and artifacts. Answer an item\nby its `runId` and `occurrence` exactly as above. Labels are the user's routing data:\n`--label ci=other` selects one class, and `attention label` writes a classification\nwithout answering. Archon never routes by them.\n\nAn item of kind `usage_limit` asks nothing. Its run hit a provider usage limit that\nreported a reset time, and waits until then. A node that failed only downstream of the\nlimited one, such as an `all_done` node reading its output, does not turn the wait into a\nfailure. At the reset, a continuation host (`archon serve`, `archon workflow wake`, or\nthe CLI process that started the run) runs the failed nodes again. Cancel or abandon the\nrun to stop it.\n\nA workflow can ask several independent questions at once by fanning out one child run\nper question. Each child is its own item, with its own `runId` and `occurrence`, and\nits `fanOut.parentRunId` names the parent. `attention <parent-run-id>` returns only the\nfirst open child, so list the queue to find them all, and answer each child by its own\n`runId`. Answers can arrive in any order; the parent stays paused until every child\nfinishes.\n\n`workflow wait` follows one run whose id you hold. To follow every run in scope,\nincluding runs started later by triggers, schedules or other sessions, run\n`archon workflow attention --watch --json --all` as a background task. It prints one\nJSON line per change: each `attention` line is an item that needs attention, carrying\nthe same item the list returns, so treat it as a wake and answer by its `runId` and\n`occurrence`. `resolved` means it no longer does, and `terminal` means a run ended. The\nlines before `ready` are the whole open set. After a restart, `--after <cursor>`\nreplays what came after that cursor. A cursor names a feed event, not a line, and one\nevent can emit two lines with the same cursor (a `resolved` and the `attention` that\nreplaces it, or a `resolved` and a `terminal`). `--after` skips the whole event, so\ncheckpoint on a `bookmark` line you handled, or on the cursor before an event you\nhandled only partly, and accept duplicate lines on replay. Exit `4` means the store\ncannot read the stream's cursor: rerun without `--after`.\n\nOr deliberately in two steps:\n\n```bash\narchon workflow approve <run-id> \"ship it\" --json   # recorded, resumable: true\narchon workflow resume <run-id>                     # executes; background task\n```\n\nTo let the continuation own a background process, add `--detach` to\n`approve`/`reject`/`respond`/`resume`. The parent validates the run and returns an\nack with `continues: true`; the detached child records the decision and continues\nwithout `--json`. Detached `resume` acknowledges after engine acceptance clears the\nold pause, so immediately follow it with `workflow wait`. A new pause is still\nreported. If acceptance is unconfirmed after 60 seconds, inspect the run before\nretrying; the child may still continue.\n\n## Deprecated interactive-loop gates: comment or no comment is a decision\n\nRead the gate state first:\n\n```bash\narchon workflow get <run-id> --json | jq .metadata.approval.completionSignaled\n```\n\n- `true` and you approve **without** a comment → accept & complete: the node\n  finalizes from its already-computed output, no re-run.\n- You approve **with** a comment → another full iteration runs using your text\n  as feedback.\n- Reject always needs a reason. Gates with authored `decisions:` expose it as\n  `$gate.output.text`; only deprecated `approval.on_reject` prompts receive\n  `$REJECTION_REASON`. At an attention wait, reject is an answer\n  (`decision: reject`) and the run continues.\n\nChoose deliberately after reading what the gate produced — not by reflex.\n\n## Cancel vs abandon\n\n- `cancel` stops a running run. When another process owns it, that process is\n  stopped and its tree confirmed gone before `cancelled` is recorded. A run the\n  cancelling server executes itself, and a `workflow:` sub-run with no owner of\n  its own (it runs inside its root), is marked `cancelled` and stops at its\n  executor's next status check. Otherwise it refuses and changes nothing when no\n  owner answers. Use this to kill real work. Cancel still ends the run, and prints a\n  warning (`cleanupWarnings` in `--json`), when a process outside the run's process\n  group still holds a node's output, and on macOS and Linux when a host `bash:` or\n  `script:` command started or completed, because it may have left background work\n  running. The warning confirms neither a survivor nor cleanup, and Archon does not\n  signal or name such a process. Find it with `lsof` on the run's checkout, check whose\n  it is, and stop it before you reuse or remove the checkout.\n- `abandon` is for paused or failed runs, or a \"running\" row whose owner is gone\n  (crashed host). If an owner still answers on this host, it stops it first, like\n  `cancel`. If none answers, it records `cancelled` and prints the host and pid the\n  run recorded; when that host is another machine, check there before abandoning.\n  It also removes the worktree the run created, uncommitted files included, and keeps\n  the branch. Commit or copy anything you need from the worktree first. When stopping\n  the owner produced one of cancel's warnings, abandon keeps the checkout for you to\n  inspect instead.\n\n## A stalled turn: end-turn, cancel or resume\n\nAn agent or loop node can sit open on a provider tool call or reported background\ncommand that never finishes, such as a `while true; do ...; sleep 30; done` poll. The\nidle watchdog does not fire while that work is open. After\n`workflows.toolCallAttentionMs` without progress (30 minutes by default), the run raises\n`stalled_tool_calls` attention. The run stays `running`, so it is not an attention-queue\nitem and the `--watch` stream does not report it. `workflow wait <run-id>`\nreturns it as `attention.kind: \"stalled_tool_calls\"`, and `workflow status` prints it\nunder the run.\n\n- **`archon workflow end-turn <run-id>`** when the stalled work is done or useless and\n  the rest of the run is worth keeping. It records a request; the live owner checks\n  about every ten seconds and ends that turn if the same stall is still there. The\n  attempt fails as `timeout`, and the node's retry policy decides what happens next.\n  By default that is a fresh attempt, which can stall the same way. Exit `0` means the\n  request was recorded, not acted on: follow with `workflow wait <run-id>`. It refuses\n  a run that is not `running` or has nothing stalled.\n- **`cancel`** when the whole run should stop.\n- **`resume`** refuses a running run. Use it once the run has failed or paused, for\n  example after an ended turn exhausts its retries.\n\nA silent turn with no open tool or background work is a different case: its node's\n`idle_timeout` fails it as before, with no request needed.\n\n## Respond: gates beyond approve/reject\n\nSome gates declare decisions beyond the default pair (`respond <run-id> <decision>\n[text]`). Read the run's metadata to see which decisions its gates declare before\nguessing one.\n\n## Judging a finished run\n\nTerminal ≠ good. The three JSON modes expose different data:\n\n| Command | Read |\n|---|---|\n| `get <id> --json` | top-level normalized `outcome` (`succeeded`/`failed`) and `leave_behind.artifactFiles` |\n| `get <id> --verbose --json` | ordered `nodes` summaries and parse warnings |\n| `get <id> --verbose --events --json` | raw `events` rows |\n\n`outcome_field` selects a boolean from the return node, but the run stores only\nthe normalized top-level outcome. It does not expose the authored field name\n(`green`, `ready`, or `rooted`) as another top-level property. Locate the report\nthrough `leave_behind.artifactFiles`, then read the artifact before reporting to\nthe user.\n\nA completed `workflow:` fan-out node is not completed child work: under the default\n`join: all_done` it completes once every child is terminal, whatever each child's\nresult. When some slots did not complete, `workflow get <run-id>` prints a\n`Fan-out work:` line with the tally and those slots' outcomes. The full per-slot record (`completed`, `failed`,\n`not_started`, or a `cancelled_*` outcome naming who cancelled it) is\n`terminal_record.nodes[].fan_out_outcome` with `--json`.\n\nReport outcomes with their evidence: \"completed, review verdict ready:false —\n2 findings remain, report at <path>\" beats \"done\".\n\n## Discoveries: the SDLC pack's out-of-scope findings\n\nA convention of the bundled SDLC workflows (`archon-ship`, `archon-deliver`,\n`archon-review`, `archon-upkeep`), not of workflows in general. Their review lenses\nrecord proven findings that fall outside the run's accepted scope as discovery records\ninstead of blocking findings: raw per-producer files at\n`<artifacts>/discoveries/*.json` (records of `title`, `claim`, `evidence`, `relation:\nunrelated | scope_conflict | note`, `source_node`), consolidated by the review into\nthe verdict's `discoveries` records and a readable `discoveries.md`.\n\nThe run that found them files nothing. Its terminal report lists them, and\n`archon-discoveries` later decides every recorded discovery across finished runs:\nfile an issue with a full brief, track it on the open issue that already covers it,\ndrop it with a reason, or ask a maintainer. Run that drain rather than filing\ndiscoveries by hand (see `queues.md`, \"Discoveries drain\").\n\nWhen the user asks about one run's discoveries before a drain has run:\n\n1. Read the report's Discoveries section and, when it names any, open\n   `discoveries.md` (find it through `leave_behind.artifactFiles`).\n2. On a failed run, also check the raw `discoveries/*.json` records: consolidation\n   runs late, so a run that died mid-flight can hold records no report mentions.\n3. Tell the user what the run found. Do not re-litigate the verdict from them:\n   `unrelated` and `note` records never affected the run's readiness.\n\n## When a run looks wrong\n\n- Paused unexpectedly → `get --verbose`; look at the failing node's error and the\n  gate metadata.\n- Failed mid-flight → fix the cause if you can name it, then `resume <run-id>`\n  (background task). Resume skips completed nodes.\n- A \"running\" row with nothing alive → verify the process is truly gone, then\n  `abandon`.\n- Logs live under `~/.archon/workspaces/<project>/logs/`; artifacts under\n  `.../artifacts/runs/<run-id>/`. `archon doctor` checks the installation itself.\n",".claude/skills/archon-cli/operate/queues.md":"---\nname: archon-cli-queues\ndescription: Drive the SDLC pack's queues by hand: intake, the build queue, the merge queue and the discoveries drain. Their inputs, what each run does, the decisions they raise and how to answer them. Read when launching or tending any of them.\n---\n\n# Driving the SDLC queues\n\nThe bundled SDLC pack turns issues into merged pull requests through four queue\nworkflows. Each run does one step and stops only for a decision it cannot make:\n\n| Workflow | One run |\n|---|---|\n| `archon-intake` | Records an issue event and triages the best waiting issue. The verdict becomes the item's stage and a route label. |\n| `archon-build-queue` | Claims the best waiting triaged item, or the one `number` names, and takes it to a pull request whose review says Ready, one round per run. |\n| `archon-merge-queue` | Claims the items the build queue finished and merges what policy allows, one at a time, through a gate. |\n| `archon-discoveries` | Decides the out-of-scope findings finished runs recorded: file, track, drop or ask. |\n\nThe pack's README (`.archon/workflows/sdlc/README.md`) is the full contract. This\nreference covers launching and tending the queues by hand.\n\n## Before the first run\n\n- `archon workflow run archon-forge-check` must pass in the checkout. It proves the\n  pack can read the repository through its forge source (`gh` by default).\n- `.archon/sdlc/policy/maintainers.json` on the default branch lists the forge logins\n  whose issues are triaged and whose review comments count. An entry can be an object,\n  `{ \"login\": \"<login>\", \"merge_queue\": \"own-prs\" }`, which also sends that\n  maintainer's own open pull requests through the merge queue, except one a work item\n  already names, such as a pull request the build queue delivered. Without the file\n  every author is an outsider: intake labels their issues `archon-needs-maintainer`\n  and runs no agent.\n- The forge account the pack's review runs post as must also be on\n  `maintainers.json`. The merge queue counts only a review comment a listed\n  maintainer wrote; with no trusted review, no verdict is Ready and nothing merges.\n- The merge gate requires green CI at the pull request's head. A repository with no\n  CI checks reads as `none`, not green, so nothing merges there.\n- Policy documents at `.archon/sdlc/policy/<topic>.md` add to the pack's defaults\n  rather than replacing them. Where a line in the project's file conflicts with the\n  default, the project's line wins.\n- The pack installs no trigger bindings. Nothing runs on its own until the operator\n  adds one. The README shows a binding per queue; until then, launch by hand.\n\n## Inputs\n\nEvery queue takes the repository as two inputs: `repo_host` (such as `github.com`)\nand `repo_path` (`<owner>/<repo>`). Launch from the repository's checkout, detached.\n\n```bash\nR=\"--input repo_host=github.com --input repo_path=<owner>/<repo>\"\n\narchon workflow run archon-intake $R --input number=<issue> --detach\narchon workflow run archon-build-queue $R --detach\narchon workflow run archon-merge-queue $R --detach\narchon workflow run archon-discoveries $R --detach\n```\n\nOptional inputs, with their defaults in each workflow's YAML:\n\n- `archon-intake`: `action` (`opened` by default; `edited`, `reopened`, or `added`\n  for a label), `label` and `label_actor` for a label event.\n- `archon-build-queue`: `number` (the issue to build; empty claims the first in\n  order), `retriage_as` (a maintainer login; set, a run that names no item also\n  re-triages blocked items that no longer look blocked) and `round_limit` (default\n  `3`), the rounds after which a run that wants another also judges whether the rounds\n  converge.\n- `archon-merge-queue`: `limit` (default `10` items claimed), `merge_method`\n  (`squash` or `merge`) and `retriage_as` (a maintainer login; set, an issue still\n  open after its pull request merged goes back to intake).\n- `archon-discoveries`: `since` and `until` (ISO-8601) and `limit` (default `20` runs\n  per pass).\n\nIntake, the merge queue and the discoveries drain never change their checkout. They\ncreate no worktree and run in the launch checkout as it stands, so launch them from a\ncheckout that tracks the default branch. The build queue changes its checkout, so each\nrun gets its own worktree on a run-named branch. A completed round removes that\nworktree when it is clean and every commit is on a remote, and keeps the branch;\notherwise the run names the path and what removing it would lose.\n\n## Intake\n\nRun intake once per issue event. For a new issue, `--input number=<issue>` is enough.\n\nAn edit or a plain rerun does not re-triage a maintainer's issue that was already\ntriaged. To re-triage one, add the `archon-triage` label on the forge and launch the\nlabel event, naming who added it:\n\n```bash\narchon workflow run archon-intake $R --input number=<issue> \\\n  --input action=added --input label=archon-triage --input label_actor=<maintainer-login> --detach\n```\n\n`label_actor` must be on `maintainers.json`. The same event with a listed maintainer\npromotes an outside author's issue.\n\nRead the result from the issue's labels:\n\n- `archon-ready`, `archon-plan` or `archon-investigate`: routed to the build queue.\n- `archon-needs-contract`: the body misses one of the six brief elements. Fix the\n  body, then re-triage.\n- `archon-blocked`: a prerequisite or a decision must land first. Re-triage once it\n  has.\n- `archon-close`: a duplicate or already delivered. Intake closes it when the triage\n  policy allows.\n- `archon-needs-maintainer`: an outside author's issue, waiting for promotion.\n\n## Build queue\n\nA run claims the waiting item that sorts first and works it for one round. To build\na chosen issue, name it:\n\n```bash\narchon workflow run archon-build-queue $R --input number=<issue> --detach\n```\n\nA named issue that cannot be claimed refuses the run before any agent, with the\nreason: it has no work item, another run holds it, or it does not wait for the build\nqueue. Launch one run per item you want worked at the same time; the launch's\n`--config` decides which provider builds it.\n\nA round ends one of three ways, recorded on the item:\n\n- `done`: the pull request's latest review says Ready at its head. The merge queue\n  takes it from there. A pull request already reviewed Ready at its head is marked\n  ready without another round.\n- `continue`: the issue already answers what stopped the round. The run starts the\n  item's next round at once, as a detached `archon-build-queue --input number=<n>`\n  run on the same run config and the same `retriage_as` and `round_limit`.\n  If that launch fails, the item waits for the next queue run.\n- `ask`: the run pauses as one attention item labelled `build: route`, `build: stop`\n  or `build: scope`.\n\nAn item the merge queue returns waits for a new build round, with the merge queue's\nbrief. A re-triage after the queue's last decision starts the item over.\n\nDo not resume a failed build-queue run once a later build-queue run has released its\nclaim. A run that names no item releases every finished run's claim, whichever item it\nthen picks. A run named for the same `number` releases it too; one named for a\ndifferent `number` leaves it alone. A resume skips the completed `facts` node that\nclaimed the item, so the resumed run works the whole round, then its `apply` refuses\nbecause the item is no longer claimed by it. Launch a fresh run with\n`--input number=<issue>` instead.\n\n## Merge queue\n\nA sweep claims every waiting item up to `limit` and merges what policy allows, one\nat a time, through a gate that requires a Ready verdict for the head, green CI there\nand a default branch whose CI is not red. A pull request that merges cleanly into the\ndefault branch merges, even when it is behind, so one sweep can land many. The queue\nnever pushes or waits for CI: a conflict goes back to the build queue, and the default\nbranch's CI on a merge is the next sweep's first fact. Claims keep two sweeps off the\nsame item.\n\nA verdict counts only at the head it reviewed: after a push, the pull request goes\nback to the build queue for a new round. A maintainer's own pull request\n(`merge_queue: \"own-prs\"`) is an own-PR work item with no build record to return to,\nso where another would go back, the sweep asks instead and offers no `return`.\n\nA pull request whose read fails twice in a row is parked with the read error until\nthe build queue readies the item again. A maintainer's own pull request has no build\nrecord, so nothing readies it again: once parked, it stays parked, and you merge it by\nhand.\n\nA sweep merges a clear pull request on its own. An unclear one, such as a very large\nchange, pauses as an attention item labelled `merge: pr` with the choices `merge`,\n`return`, `park` and `other`. A red default branch pauses as `merge: base` with the\none choice `hold`, and the gate refuses every merge until it is green. A run raises at\nmost one attention item: the red default branch first, otherwise the first pull\nrequest the sweep asked about. A later sweep decides the rest again.\n\nA `merge` answer approves the pull request at the head it asked about, and a sweep\nthe answer starts, on the answered run's `limit`, `merge_method` and\n`retriage_as`, merges it once the gate passes. The approval carries to a later head only when the sweep\njudges that everything since is merges of the default branch and their conflict\nresolutions, and the verdict is Ready at the later head. A `return` hands the item\nback to the build queue, whose next round starts at once on the build queue's own\ninput defaults. A `park` stops work on the item until the build queue readies it again.\n\nFor an own pull request, `park` and `other` stop queue work until a later sweep\nobserves a head different from the head asked about. At the same head it stays\nparked, even after green CI or a new review. The `other` text is recorded as the\nruling on the work item; it starts no build round.\n\n## Discoveries drain\n\nEach pass decides the records of up to `limit` finished runs, oldest first. Repeat\npasses until one finds nothing queued: its `decide` node is skipped\n(`archon workflow get <run-id> --verbose --json`). Stop early when a pass pauses with a\n`discoveries: question` item, and answer it first.\n\nIssues a pass files carry no labels. Run intake on each one to triage it.\n\n## Answering queue decisions\n\nQueue items follow one shape: what is being decided, why it matters, the\nrecommendation with its evidence and what would change it, the choices and what each\ndoes, and what happens if nobody answers. Answer them as `managing-runs.md`\n(\"Answering a decision well\") describes:\n\n```bash\narchon workflow respond <run-id> <choice> \"<reason>\" --detach\n```\n\nThe build queue and merge items owned by a build write the ruling into the issue\nbody as a dated \"Current scope\" section, and later rounds work from it. An enrolled\nown PR keeps the answer on its work item's merge decision entry; it writes no issue\nsection. Write the reason as a ruling a later round can act on: what is in scope,\nwhat is not, and why. An answer that\ncontinues an item (a build `continue`, a merge `merge` or `return`) starts the follow-up\nrun at once, on the run config the answered run started with, so you don't relaunch. A\nfollow-up in the same queue also keeps the answered run's input selections.\n\n- When the recommendation fits the user's standing direction, answer it yourself.\n- When it is unclear, risky or not decided by anything the user has said, bring it to\n  the user with the item's own summary.\n- `other` exists on every pull-request and build item. Use it when no declared choice\n  fits, and say exactly what to do instead.\n\n## Merging by hand\n\nA pull request the queues did not deliver has no work item, so the merge queue does\nnot take it, unless its author's `maintainers.json` entry sets\n`merge_queue: \"own-prs\"`. To merge one yourself:\n\n1. Read the review verdict and the open review threads, including automated\n   reviewers' inline comments.\n2. Merge at the head you read: `gh pr merge <n> --squash --match-head-commit <sha>`.\n   A push since your read makes the merge fail instead of landing unreviewed code.\n3. Update your checkout to the new default branch.\n",".claude/skills/archon-cli/operate/running.md":"# Running workflows\n\nHow to discover, select and launch Archon workflows against real work, and how to watch\nwhat you launched.\n\n## Discover what exists\n\n```bash\narchon workflow list                 # human-readable compact descriptions and errors\narchon workflow list --json          # compact descriptions + descriptionTruncated state\narchon workflow list <name> --full   # exact description for one candidate\narchon plugin add owner/repo     # install a workflow pack published on GitHub\n```\n\nThe live list is authoritative. Bundled workflows ship under the `archon-` prefix\n(e.g. `archon-ship`, `archon-review`, `archon-deliver`, `archon-investigate`,\n`archon-triage`, `archon-upkeep`); user-authored workflows may\nclaim any name and override a bundled one in that repo.\n\nIn JSON output, `descriptionTruncated: true` means `description` is a discovery preview,\nnot the complete routing contract. Human output marks the same state with `[truncated]`.\nUse the previews to identify plausible candidates, then run `archon workflow list <name>\n--full` for each candidate before choosing or launching. Map intent from those full\ndescriptions — never from memory of names you have seen elsewhere. If two names plausibly\nmatch, read both full descriptions, then say which you picked and why in one line.\n\n## The input is the contract\n\nEverything the run does is measured against its input — the message, the issue body, or the\ndocument it reads. Archon governs how the work happens; it cannot supply what the user\nactually wanted. This is the highest-leverage moment in the whole flow, and it is before any\nmoney is spent.\n\nCheck the input names all six. Solution steering is a seventh, optional, and last:\n\n1. **Problem to solve** — what is wrong today, concretely.\n2. **Why it is worth solving** — the cost of leaving it.\n3. **Why now** — what makes this the moment.\n4. **Desired outcome** — what is observably different afterwards.\n5. **Invariants** — what must stay true across any acceptable implementation.\n6. **Acceptance** — how completion is checked.\n\n**When something is missing, do not fix it silently and do not launch anyway.** Tell the user\nwhich of the six is absent, propose concrete wording, and let them decide. They own the\ncontract; your job is to notice it is thin before it costs them a run.\n\nA useful shape:\n\n> Before I launch: this brief states the problem and the outcome, but not the invariants or\n> acceptance. Without acceptance the run decides for itself when it is done. Suggest adding:\n> \"Acceptance: X passes, Y is covered by a test, Z is unchanged.\" Want me to run it with that,\n> or would you rather word it yourself?\n\nTwo failure modes worth naming, because they look like diligence:\n\n- **Naming a solution before the problem is settled.** It narrows the run to the first guess\n  and hides better answers. If the user supplied one, keep it, but make sure the problem is\n  stated too — a run that only knows the proposed fix cannot tell you it was the wrong fix.\n- **Dropping steering the user actually holds.** Optional does not mean unwanted. If they have\n  said anywhere in the conversation how they want this done — reuse a primitive, avoid a\n  dependency, migrate rather than rewrite — carry it into the input as explicit steering. An\n  unstated preference cannot be honoured, and the user discovers it only in the diff.\n- **Silence about uncertainty.** An assumption stated in the brief is something the run can\n  contradict. An assumption left out is one it will quietly inherit.\n\nIf the input is a GitHub issue, read the actual body before launching rather than trusting the\ntitle. Recommend edits to the issue itself when it is thin — the issue is the durable contract,\nand the next run against it inherits whatever you leave there.\n\n## Invocation\n\n```bash\n# Default: isolated worktree, detached, non-blocking\narchon workflow run <workflow> --branch <branch-name> \"<message>\" --detach\n\n# Start from a specific base instead of the current branch\narchon workflow run <workflow> --branch <name> --from <base> \"<message>\" --detach\n\n# Foreground (blocks the shell)\narchon workflow run <workflow> --branch <name> \"<message>\"\n\n# No isolation — only when the user explicitly asks to work on the checkout directly\narchon workflow run <workflow> --no-worktree \"<message>\"\n\n# First run for a registered non-git folder project (runs in place; no branch flags)\narchon workflow run <workflow> --folder \"<message>\"\n\n# From another clone of a registered repo: the run belongs to that project\narchon workflow run <workflow> --project <owner/repo> --branch <name> \"<message>\" --detach\n```\n\n`--project <id|name>` takes a name or ID from `archon project list`. The run uses the\nproject's identity, env, artifacts and logs, and never registers the clone you launch\nfrom. Workflow source and config come from the launch directory. A worktree is cut from\nthe registered checkout, so a branch that exists only in your clone is not there; add\n`--no-worktree` to execute in the launch directory instead. It cannot be combined with\n`--folder`, `--base-branch`, `--resume` or `--dry-run`.\n\nRules:\n\n1. **Detached is the default posture.** Workflows are long-running; `--detach`\n   returns immediately and the run appears in `archon workflow runs`. In your\n   harness, even foreground invocations belong in a background task.\n2. **For git projects, pass `--branch`** unless the user chose otherwise. Derive the name\n   from the domain of work — code: `fix/<topic-or-issue-N>`, `feat/<name>`,\n   `review/pr-N`; ops/process runs: e.g. `ops/<queue>-<item>`,\n   `upkeep/<dependency>`, `triage/<source>`. To run on\n   different models for one run, see `managing-runs.md` (\"Overriding models for one\n   run\").\n   A worktree created for `--branch` stays when the run ends; `archon isolation\n   cleanup --merged` removes it after its pull request merges. Without `--branch`, a\n   completed run removes the worktree it created on its own generated branch when the\n   checkout is clean and every commit is on a remote, keeping the branch. A kept\n   checkout is named in the run's output with what removing it would lose.\n   Folder projects run in place and reject `--branch`, `--from`, and `--base`.\n3. **One workflow per shell.** Multiple pieces of work = separate invocations with\n   separate branches; they cannot conflict because each gets its own worktree.\n4. **The message is the work order.** Write it as you would brief a competent\n   engineer: what outcome is wanted, where the context lives (issue number, plan\n   path), and any constraint the user actually stated. See\n   `../authoring/prompting-mistakes.md` before writing it.\n5. **Interactive workflows need a human at their gates.** Launch them in the\n   foreground. When they pause, read the rendered gate message and declared\n   decisions from `archon workflow get <run-id> --json` under\n   `metadata.approval`; assistant JSONL rows are AI transcripts, not the gate\n   contract. Resolve the gate per `managing-runs.md`. Continuation\n   actions on the paused run may use `--detach`.\n\n## Inputs\n\nA workflow that declares `inputs:` takes them as `--input <name>=<value>`, one flag\nper input. `archon workflow list <name> --full` shows the description; the workflow's\nYAML lists each input with its default and meaning. An input without a default is\nrequired, and an undeclared name is refused.\n\n```bash\narchon workflow run archon-intake \\\n  --input repo_host=github.com --input repo_path=<owner>/<repo> --input number=42 --detach\n```\n\nInputs and a message can be combined. Queue workflows such as the SDLC pack's take\ninputs only; `queues.md` lists them.\n\n## Monitoring\n\n```bash\narchon workflow attention --watch --json    # one stream: decisions and run ends, this project\narchon workflow attention --watch --json --all   # the same across the install\narchon workflow wait <run-id> --json        # block on one run until it ends or needs attention\narchon workflow runs --json                 # recent runs for this project\narchon workflow status --json               # active runs (running or paused) for this project\narchon workflow get <run-id> --json         # one run: status, outcome, artifacts\narchon workflow get <run-id> --verbose --json  # plus per-node detail\n```\n\nTerminal statuses: `completed`, `failed`, `cancelled`.\n\n**Watch with one stream.** Right after your first launch, run `archon workflow\nattention --watch --json` as a background task of your harness. It follows every run\nin scope, including runs started later by triggers, schedules or other sessions, and\nprints one JSON line per change:\n\n- `attention`: a run needs a decision. The line carries the whole item; answer it by\n  its `runId` and `occurrence` (see `managing-runs.md`).\n- `resolved`: the item no longer needs attention.\n- `terminal`: a run ended, with its `status` and `outcome`. Read its result.\n- `owner_lost`: a run says it is running but its process is gone.\n\nThe lines before `ready` are the current open set. Keep the `cursor` of the last line\nyou handled; after a restart, `--after <cursor>` replays what you missed.\n\n`workflow wait <run-id>` blocks on one run and suits one or two runs. Do not arm one\nwait per run for a batch: dozens of waiting processes contend for the database, and a\nsingle stream already wakes you per run. Never chain waits in one shell (`wait A &&\nwait B` sleeps on A while B pauses unattended).\n\n**Status is not verdict.** A `completed` run can carry a negative `outcome`. Read it\nwith `get --json`, find the report under `leave_behind.artifactFiles`, and use a\nseparate `get --verbose --json` call for node summaries. Read the report before\ntelling the user what the run concluded. For the bundled SDLC workflows the report\nmay list discoveries: findings outside the run's scope. The run records them but\nfiles nothing; `archon-discoveries` decides and files them (see `queues.md`).\n\n## Continuing finished work\n\n```bash\narchon workflow runs --open                                 # find the prior run id\narchon workflow run docs --adopt <run-id> \"Now also update the docs\"  # new run on the adopted worktree/branch\narchon complete fix/issue-42                                # remove worktree + local branch; keeps the remote branch\narchon isolation cleanup                                    # prune stale environments (7d)\narchon isolation cleanup --merged                           # prune merged ones now\n```\n\n## Resuming after failure\n\nA failed or paused run resumes from completed nodes:\n\n```bash\narchon workflow resume <run-id>\narchon workflow resume <run-id> --detach\n```\n\nDetached resume returns after engine acceptance, before completion. Your attention\nstream reports the run's next pause or end. If the\n60-second acceptance confirmation fails, inspect the run before retrying: the\nchild may still continue.\n\nThe ambient sequential session cursor is not reconstructed by a cold resume.\nExplicit `context: { resume: node-id }` ancestry is restored from the completed\nsource's saved handle, and a paused loop with `fresh_context: false` continues its\npre-pause session when the provider supports it. Use durable artifacts for every\nhandoff that must survive regardless of provider session availability.\n",".claude/skills/archon-cli/operate/troubleshooting.md":"# Troubleshooting Workflows\n\nWhere to look when a workflow fails, hangs, or does the wrong thing.\n\n## Log Locations\n\nWorkflow run logs are written as JSONL per run:\n\n```text\n~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl\n```\n\nEach line is a structured event. The discriminator is the `type` field. Values (see `packages/workflows/src/logger.ts` for the canonical list):\n\n| `type` | Meaning |\n|--------|---------|\n| `workflow_start` / `workflow_complete` / `workflow_error` | Run lifecycle |\n| `workflow_resume` | A resumed execution picked the run back up here. The first execution writes `workflow_start`; every resume writes one `workflow_resume` instead, so the rows after the n-th one belong to the run's (n+1)-th segment. Transcripts written before this row existed repeat `workflow_start` on resume |\n| `node_start` / `node_complete` / `node_error` / `node_skipped` | Node lifecycle |\n| `node_suspended` | A node paused the run; `content` names why (a gate kind such as `approval`, or `wait`) |\n| `gate_decision` | How a gate was resolved — `step` (the gate node), `decision` (`approved`, `rejected`, or an author-declared decision), and `content` (the operator's comment or rejection reason, as the run's `approval_received` event stores it: an approve without a comment records `Approved`, and the field is absent when the event stores neither). No actor is recorded. Written by the process that approved or rejected, which is often not the one running the workflow |\n| `assistant` | AI assistant message — has `content` field with the full AI output |\n| `tool` | SDK tool invocation — has `tool_name` and `tool_input` |\n| `exec_output` | What a `bash`/`script` node or `until_bash` probe printed — `stdout_tail`, `stderr_tail`, `exit_code` |\n| `watchdog_reset` | A streamed chunk renewed a node's idle watchdog — `chunk_type` only, never content. Renewals closer together than `WATCHDOG_RESET_BURST_GAP_MS` form a burst, recorded as its first and last renewal; `chunk_count` is the renewals since the node's previous record (absent on older transcripts, which wrote one row per renewal). A burst's last record can land after later rows, so order by `ts` |\n| `validation` | Historical compatibility only; current runs do not emit this type |\n\n> **Loop iteration lifecycle events are NOT in the JSONL file.** `loop_iteration_started` / `loop_iteration_completed` go through the workflow event emitter (WebSocket / `workflow_events` DB table) — query the DB or the Web UI dashboard for those. Per-iteration rows that DO reach the JSONL are keyed `<node-id>-iteration-<n>`: a loop's `node_complete` row, its `watchdog_reset` rows, and the `exec_output` row for each `until_bash` probe.\n\nFind the run ID from `archon workflow runs --status failed` (or `archon workflow runs` for the most recent run of any status; `workflow status` only shows active runs). Then:\n\n```bash\n# Last assistant message (what the AI said before failure)\njq -sr 'map(select(.type == \"assistant\") | .content) | last // empty' <log-file>\n\n# All error events (node failures + workflow-level failures)\njq 'select(.type == \"node_error\" or .type == \"workflow_error\")' <log-file>\n\n# What a specific node actually printed (evidence for \"did this really do X?\")\njq 'select(.type == \"exec_output\" and .step == \"<node-id>\")' <log-file>\n\n# Where the run was resumed, and how each gate was decided\njq 'select(.type == \"workflow_resume\" or .type == \"gate_decision\")' <log-file>\n\n# When a node's stream last renewed its watchdog, and with what chunk type\njq -s 'map(select(.type == \"watchdog_reset\" and .step == \"<node-id>\")) | sort_by(.ts) | last' <log-file>\n\n# Full event stream\ncat <log-file> | jq .\n```\n\nAdapter logs (Slack / Telegram / Web / GitHub) are emitted to stderr when `LOG_LEVEL=debug` is set on the server.\n\n## Artifact Locations\n\n```text\n~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/\n```\n\nInspect artifacts when a multi-node workflow produces wrong output. The failing node's upstream artifact is usually where the problem originated.\n\nNodes with `output_type:` also get engine-written sidecars under `runs/<run-id>/nodes/` (`<node-id>.md` + `<node-id>.meta.json`), and — for session-persisted workflows — mirrored copies under `artifacts/scopes/<workflow>/<scope>/nodes/` that survive across runs.\n\n```bash\nls ~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/\ncat ~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/issues/issue-42.md\n```\n\nArtifacts are **external** to the repo on purpose — they don't pollute git. They are kept until you remove them, except `.archon/scratch/` inside the run's directory, which holds throwaway checkouts and installs and is deleted once the run is completed, failed or cancelled and its nodes have stopped (for a container run, when its container is removed). A run whose nodes Archon cannot confirm stopped keeps its scratch.\n\n## Common Failure Modes\n\n### \"No base branch could be resolved\"\n\nA node references `$BASE_BRANCH`, but the run has no resolved base. Causes:\n- A git run where no `--base`, `worktree.baseBranch`, or codebase default was set and git detection failed. A shared-checkout child inherits this from its parent.\n- A folder project, which has no git base.\n- An isolated child that reused an existing branch without its original child run record. Its historical base is unknown, so config and parent flags cannot recover it.\n\n**Fix:**\n1. For a top-level git run, pass `--base <branch>` on `archon workflow run`, or set `worktree.baseBranch: main` (or `dev`, or whatever) in `.archon/config.yaml`. `--from` picks the starting point and does not set `$BASE_BRANCH`.\n2. For the reused-branch child, start a new parent run so the child cuts a new isolated branch with a known base.\n3. Or remove the `$BASE_BRANCH` reference if the node doesn't need it.\n\nA resumed run keeps its recorded base unless it is retargeted with `--base`. See [`$BASE_BRANCH` fail-fast](https://archon.diy/reference/variables/#base_branch-fail-fast).\n\n### \"Claude Code not found\" / \"Codex CLI binary not found\"\n\nCompiled-binary builds of Archon no longer embed Claude Code / Codex — you install them separately and Archon resolves the binary via env var or config.\n\n**Fix (Claude):**\n- Install: `curl -fsSL https://claude.ai/install.sh | bash` (or `npm install -g @anthropic-ai/claude-code`)\n- Set `CLAUDE_BIN_PATH=/path/to/claude` in `~/.archon/.env`, OR\n- Set `assistants.claude.claudeBinaryPath: /absolute/path` in `.archon/config.yaml`\n- Autodetect covers `$HOME/.local/bin/claude` (native installer) — no config needed if you used that path\n\n**Fix (Codex):**\n- Install: `npm install -g @openai/codex` (or platform-specific instructions)\n- Set `CODEX_BIN_PATH=/path/to/codex` or `assistants.codex.codexBinaryPath` in config\n- Autodetect covers the standard npm / Homebrew locations per platform\n\nSee [archon.diy/getting-started/installation/](https://archon.diy/getting-started/installation/) for full platform-specific install paths.\n\n### Workflow shows `running` for a long time but nothing happens\n\nFour possibilities:\n\n1. **The AI is actually working.** Check `~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl` — if you see recent `tool` or `assistant` events in the tail, it's fine. Wait.\n2. **The server crashed and left an orphan row.** Server startup no longer auto-fails orphaned `running` rows (per the \"Do not guess lifecycle ownership\" rule — `AGENTS.md`). Transition it manually:\n   - Any cancel (Web UI Cancel, `archon workflow cancel <run-id>`, chat `/workflow cancel [id]`) stops a live owner process before marking the row cancelled, and refuses when no owner answers — an orphan row gets that refusal, with the host and pid it recorded\n   - Verified orphan: `archon workflow abandon <run-id>` (or Abandon in the Web UI after the refused Cancel, or `/workflow abandon <id>` in chat) — marks the row cancelled\n3. **A node is past its `idle_timeout`.** The default is 30 minutes of complete silence (the timer resets on every streamed message — it's a deadlock detector, not a work limiter). Override with per-node `idle_timeout` (ms) if a node legitimately goes quiet for longer.\n4. **A tool call or background command never finished.** While the provider reports open work, `idle_timeout` does not fire. `archon workflow wait <run-id>` and `archon workflow status` show `stalled_tool_calls` attention once that work has made no progress for `workflows.toolCallAttentionMs` (30 minutes by default). `archon workflow end-turn <run-id>` ends that turn without cancelling the run; see `managing-runs.md`, \"A stalled turn\".\n\n### Workflow fails mid-way; how do I resume?\n\nFind the failed run, then resume it explicitly:\n\n```bash\narchon workflow runs\narchon workflow get <run-id>\narchon workflow resume <run-id>\n```\n\n`archon workflow resume <run-id>` targets that exact run and reuses its recorded working path/worktree. As a convenience, `archon workflow run my-workflow --resume` resumes the most recent resumable run for that workflow at the invocation cwd. A bare `archon workflow run my-workflow` starts a fresh run.\n\n**Session caveat:** A cold resume does not reconstruct the ambient sequential\n`context: shared` cursor. Explicit `context: { resume: node-id }` ancestry is\nrestored from saved run-scoped handles, and a deprecated interactive loop with\n`fresh_context: false` resumes its pre-pause session when the provider supports\nit. Artifact-based handoff remains the provider-independent path.\n\n### Paused run with no chat message\n\nA run is paused at a gate or an attention wait, but no message reached the conversation that started it.\n\n**Cause:** the platform could not deliver the pause notice. The run still waits. The failure is logged as `platform_message_send_failed` and recorded on the run as a `pause_notice_undelivered` event, which the console's run view shows. Answer the run by id: `archon workflow status`, then `approve`, `reject`, or `respond`.\n\n### `MCP server connection failed: <server>`\n\nArchon reports only the MCP servers a node declares with `mcp:`. Status for any other server goes to the debug log as `dag.mcp_server_status`. A declared server is required: on Claude and Codex the node fails as `misconfigured` before its first model call, with an error naming the server and its cause. Check the config JSON path, the server's `command` and `args`, and any env vars it references.\n\n### A node was skipped and I don't know why\n\nSkips carry a **reason** in the persisted `node_skipped` event (`archon workflow get <run-id> --verbose --json`, or the `workflow_events` table):\n\n| Reason | Meaning | Fix |\n|--------|---------|-----|\n| `trigger_rule` | Upstream states didn't satisfy the join rule (default `all_success` — a skipped upstream counts as not-success and cascades) | Use `one_success` / `none_failed_min_one_success` / `all_done` on merge nodes after `when:`-gated branches |\n| `when_condition` | The `when:` expression evaluated false | Expected behavior — check the upstream output it compared against |\n| `when_condition_parse_error` | The `when:` expression has invalid SYNTAX — fail-closed skip | Fix the expression (six operators, `&&`/`\\|\\|`, no parentheses) |\n| `prior_success` | Resume: the node completed in a prior run | Expected; use `always_run: true` to force re-execution |\n\n### Node FAILED with an `OutputRefError`-style message about `$node.output.field`\n\nNot a skip — strict field access failed loudly: the field isn't in the producer's `output_format` schema (typo), the schemaless producer's output isn't JSON / lacks the key, or the producer never ran. Fix the reference, add the field to the schema, or guard with `when:`/`trigger_rule`. (Only whole-text `$node.output` degrades silently to `''`.)\n\n### Node output is empty / a declared-optional field resolves to empty string\n\nCommon causes:\n\n1. Upstream node was **skipped** — whole-text `$node.output` is `''` for skipped producers.\n2. The field is declared in the producer's `output_format` but was absent/null in the actual output (declared-optional → `''` by design).\n3. Bash/script node printed to stderr, not stdout. Only stdout is captured.\n4. For script nodes, non-zero exit on a non-existent file / missing import fails the node. Check the run log for `node_error` entries.\n\n### A workflow-level field seems to have no effect\n\nInvalid values for optional workflow-level fields (`effort`, `sandbox`, `fallbackModel`, `betas`, `tags`, `worktree.enabled`, `mutates_checkout`) are **warn-and-drop**: the workflow still loads and runs, the field is discarded, and a warn-level `*_ignored` loader log is written (stderr for CLI commands, the server log for `archon serve`). AI-only fields on bash/script nodes, unknown keys, and deprecations are parse warnings instead. A key that belongs to another node type, such as `timeout:` on a prompt node, is a load error that names the node, the key and the node types that accept it. Run `archon validate workflows` or `archon workflow list` to see the warnings, or set `LOG_LEVEL=debug` to see them in the logs. Check both before assuming the field is broken.\n\n### Persisted session didn't restore (cold resume)\n\nWhen a node resumes a session that no longer exists, what happens depends on the provider. Pi starts a fresh session with a warning. Claude rejects the resume; when Archon confirms on the host that the transcript is gone and the rejection came before any output or tool call, it runs the node once more on a fresh session with the same warning. A second rejection, a rejection after output, a container run, or a named resume (`context: { resume }`) fails the node. Codex fails the node every time, so clear its saved session with `archon workflow reset-sessions <workflow>`. The fresh-session warning includes pointers to prior typed artifacts (`output_type` sidecars in the cross-run scope dir) so the prompt/agent can re-read lost context. If sessions are repeatedly cold, check server logs; clear broken state with `archon workflow reset-sessions <workflow>`.\n\n### `cannot fork a session` warning\n\nThe node's provider cannot fork a session (no `sessionFork` in the [provider capabilities](https://archon.diy/reference/provider-capabilities/)), so a `persist_session` node on it never continues an earlier run's session: every run starts fresh. `archon validate workflows` reports this when the provider is written in the workflow file. Once an earlier run has saved a session for this node and provider, each later run also posts this warning in chat and records a `node_session_not_continued` event naming the session it skipped. This is expected, not broken state, and `reset-sessions` does not change it. To carry context across runs, move the node to a provider that forks sessions, or have it write what the next run needs as an artifact and read that instead. If neither applies, remove `persist_session` from the node.\n\n## Useful Diagnostic Commands\n\n```bash\n# Environment sanity first: providers, DB, adapters (forge access: archon workflow run archon-forge-check)\narchon doctor\n\n# One run, any status, with raw events — the fastest \"why did node X skip/fail\"\narchon workflow get <run-id> --verbose --events --json | jq '.events[]'\n\n# Recent runs for this project, all statuses (\"did the review pass?\")\narchon workflow runs --json | jq '.runs[]'\n\n# Recent runs of any status (find a failed run's ID)\narchon workflow runs --status failed --limit 10\n\n# Active runs for this project (running / paused only — finished runs never appear here)\narchon workflow status --json | jq '.runs[]'\n\n# Active runs across all projects\narchon workflow status --all --json | jq '.runs[]'\n\n# Human-readable status of any active runs\narchon workflow status\n\n# Active worktrees and their last activity\narchon isolation list\n\n# Validate a specific workflow before running\narchon validate workflows my-workflow\n\n# Validate a specific command\narchon validate commands my-command\n\n# Dump the last 50 lines of a workflow's log\ntail -n 50 ~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl | jq .\n\n# Increase log verbosity (workflow run)\narchon workflow run my-workflow --verbose \"...\"\n\n# Increase server log verbosity\nLOG_LEVEL=debug bun run start\n```\n\n## Escalation: when nothing makes sense\n\n1. Run `archon version` and note the version.\n2. Run `archon validate workflows <name>` and capture the output.\n3. Grab the last ~50 lines of the run's JSONL log.\n4. Check the `CHANGELOG.md` for known issues / recent changes to the subsystem you're hitting.\n5. File an issue at https://github.com/coleam00/Archon/issues with version, validate output, log tail, and the YAML.\n",".claude/skills/archon-cli/setup/setup-and-config.md":"---\nname: archon-cli-setup-config\ndescription: Install Archon, verify with doctor, connect provider credentials, and change config.yaml (tiers, aliases, adapters). Read when setting up Archon or changing its configuration.\n---\n\n# Setup and config\n\nInstall Archon, verify it, and change its configuration. Run from the repo you\nwant to work on unless setting up globally.\n\n## First: is it already working?\n\n```bash\narchon --version      # CLI present?\narchon doctor         # installed providers, database, adapters\narchon doctor --full  # also probes the OpenCode runtime SDK\narchon workflow run archon-forge-check  # can the SDLC workflows reach the forge?\n```\n\nIf `doctor` is green, skip to Configuration. Only set up what is actually broken.\nFor the SDLC pack's queues, also read `../operate/queues.md` (\"Before the first run\").\n\n## Install\n\nPrerequisites: `git`, and Bun for source installs (a compiled binary needs only\nitself).\n\n```bash\n# Compiled binary; asks which providers to install\ncurl -fsSL https://archon.diy/install | bash\ncurl -fsSL https://archon.diy/install | bash -s -- --providers codex   # no question\n\n# Source install\ngit clone <archon-repo> && cd Archon\nbun install\ncd packages/cli && bun link && cd ../..\n\n# or run from a checkout without linking\nbun --cwd packages/cli src/cli.ts --help\n```\n\n## Providers (compiled binaries)\n\nA compiled binary ships without Claude, Codex and Pi; it runs only the ones\ninstalled. No provider is ever picked silently: a run needing a missing one\nfails before it starts and names the command.\n\n```bash\narchon provider add codex            # or: claude codex pi\narchon setup --providers codex           # same, then auth prompts\n```\n\nAn installed provider must match the CLI version: after upgrading Archon, rerun\n`archon provider add <ids>`. `archon doctor` shows what is installed and any\nstale provider. Source checkouts and Docker run all three from source; there\n`provider add` refuses.\n\n## Credentials — the setup wizard and its pieces\n\n```bash\narchon setup          # interactive wizard: providers + credentials + config + bundled skill\n```\n\nThe wizard composes these, which also work standalone:\n\n```bash\narchon ai list                      # which providers are connected\narchon ai key set anthropic        # API key (prompt/stdin) — anthropic, openai, ...\narchon ai login anthropic          # Claude Pro/Max subscription (Claude provider only)\narchon ai login openai             # ChatGPT/Codex subscription\narchon ai login github-copilot     # GitHub Copilot subscription\narchon auth github                  # GitHub identity via device flow\n```\n\nNotes:\n\n- Subscription logins cover Claude Pro/Max, ChatGPT/Codex, and GitHub Copilot.\n  API keys remain available through `ai key set`.\n- On multi-user installs each human connects their own credentials; runs execute\n  on the starter's keys. Solo installs just need one working provider.\n- `gh` must be authenticated (`gh auth status`) for any GitHub-touching workflow.\n\n## Config: two scopes, one schema\n\n| Scope | File | Wins |\n|---|---|---|\n| Repo | `<repo>/.archon/config.yaml` | over global |\n| Global/user | `~/.archon/config.yaml` | defaults |\n\nRead both before changing anything; change the narrowest scope that fits the ask.\n\nKey sections:\n\n```yaml\nassistants:\n  claude:\n    model: <default model for claude nodes>\n    # provider-specific options live here too\ntiers:                     # how much model a step deserves; keywords small/medium/large\n  large:\n    provider: claude\n    model: claude-<...>\n    effort: high\naliases:                   # @name shortcuts resolvable in workflows\n  \"@fast\":\n    provider: ...\n    model: ...\ndefaults:\n  loadDefaultWorkflows: true   # bundled archon-* pack\n  loadDefaultCommands: true\ndocs:\n  path: docs/                  # where $DOCS_DIR points\nenv:                           # per-project env vars injected into execution\nworktree:                      # repo scope only — see \"Files agents need in worktrees\"\n  baseBranch: main\n  copyFiles:\n    - .env\n```\n\n### Files agents need in worktrees\n\n`git worktree add` carries **tracked** files only. Everything gitignored is absent\nfrom a fresh worktree — `.env` above all — so an agent cannot start the project's\nserver, run integration tests, or reproduce anything reading local credentials\nfrom the checkout it was handed. Nothing errors; the agent just finds no config.\n\nIf a user asks why something works in their checkout but not in a run, check this\nfirst. The fix is one entry:\n\n```yaml\n# <repo>/.archon/config.yaml\nworktree:\n  copyFiles:\n    - .env\n```\n\nRules worth knowing before suggesting entries:\n\n- **Repo scope only.** `worktree` is read from `<repo>/.archon/config.yaml`.\n  Putting it in `~/.archon/config.yaml` parses and does nothing.\n- **Copy the real gitignored file, never a tracked template.** Suggesting\n  `.env.example` is wrong twice: a worktree already has it, and materialising a\n  placeholder `.env` makes a server start against fake credentials instead of\n  failing.\n- **Entries are plain paths.** Source and destination are always identical; there\n  is no rename syntax.\n- **Missing entries are skipped silently**, so listing an optional path is safe\n  and a contributor without the file is unaffected.\n- Nothing is copied unless listed. Workflows, commands, and scripts do **not**\n  need an entry — a run freezes its own source.\n\nOther common entries: `.vscode/`, `.claude/`, and local-only fixture or plans\ndirectories. Full semantics, including path-traversal rejection and the\n`worktree.path` interaction, are in `/reference/configuration/`.\n\nPrefer the typed commands over hand-editing when they exist — they validate:\n\n```bash\narchon ai tier set large claude claude-<model> --effort high\narchon ai tier list --json     # configured vs built-in defaults\narchon ai alias set @fast pi minimax/MiniMax-M3\narchon ai key set openai                         # masked prompt, or key via stdin\n```\n\nTier, alias, and default commands write install-wide settings unless you pass\n`--scope user`. Use the user scope for one person's preferences on a shared\ninstall; use the install scope for shared defaults.\n\nAfter edits, confirm with `archon doctor` and one cheap probe:\n`archon workflow list` should load with no errors.\n\n## Alternate config layers (per-run model setups)\n\nA user who wants a second model setup — say, a cheap free-model combo or a\npremium one — does not need to edit their main config. Create a **sparse YAML\nlayer** holding only the keys it overrides, and load it per run with `--config`:\n\n```yaml\n# e.g. .archon/config.free.yaml (operator-local; `.archon/config.*.yaml` is gitignored,\n# and `.archon/config.example.yaml` is the committed template to copy)\ntiers:\n  small:\n    provider: pi\n    model: minimax/MiniMax-M3\n  large:\n    provider: pi\n    model: minimax/MiniMax-M3\naliases:\n  \"@fast\":\n    provider: pi\n    model: minimax/MiniMax-M3\n```\n\nValid layer keys: `assistant` or `defaultAssistant`, `assistants`, `tiers`,\n`aliases`, `workflows`, `docs` (with `path`), and `env`. A tier or alias entry\nstores the Archon provider separately from the model. For `provider: pi`, the\nmodel is Pi's inner `<vendor>/<model>` reference, such as\n`minimax/MiniMax-M3`. The outer `pi/` appears only in a single-string CLI\nbinding such as `--model large=pi/minimax/MiniMax-M3`.\n\n```bash\narchon workflow run archon-ship --branch fix/x \"...\" --config .config.free.yaml\n```\n\nNotes:\n- The layer applies to that fresh run and its descendants only; nothing persists.\n- Mutually exclusive with resuming an existing run.\n- One-off, ad-hoc rebinding without a file: use `--model tier=provider/model`\n  instead — see `../operate/managing-runs.md`.\n\nTo help a user decide scope: persistent default → edit config.yaml (below);\na named alternate setup they will reuse → a config layer;\na one-time experiment → `--model` flags.\n\n## Adapters (Slack / Telegram / GitHub / Discord / Web)\n\nAdapters let platforms drive Archon remotely. Each configures via env vars and is\nverified by `doctor`. The web UI is the zero-config surface:\n\n```bash\narchon serve            # web console; downloads the server and UI on first run (default port 3090)\n```\n\nSet up a chat adapter only when the user actually asks to drive Archon from that\nplatform — do not proactively configure Slack/Telegram/Discord.\n\n## Project initialization for workflow authoring\n\nCreating `.archon/` content (workflows, commands, scripts) belongs to the authoring\ncapability: see `../authoring/authoring-workflows.md`. Setup only ensures the CLI works.\n",".claude/skills/docker-extend/SKILL.md":"---\nname: docker-extend\ndescription: |\n  Use when: User wants to extend Docker with custom tools, personalize the Docker environment,\n  or set up user-specific Docker customization.\n  Triggers: 'extend docker', 'docker-extend', 'add tools to docker', 'customize docker',\n            'add my tools to the container', 'personalize docker setup', 'docker user setup',\n            'install tools in docker'.\n  Does: Interactively sets up Dockerfile.user and docker-compose.override.yml so users can\n  add personal tools to their Docker environment without affecting maintainer files or\n  committing user-specific config to git.\nargument-hint: '[optional: tools to install, e.g. \"vim ripgrep jq\"]'\n---\n\n# Docker Extend\n\nSets up personal Docker tool customization using the project's gitignored override pattern.\nYour `Dockerfile.user` and `docker-compose.override.yml` are yours — never committed to git.\n\n## Step 1: Detect Context\n\nDetermine if the user is in dev (source build) or deploy (GHCR) mode:\n\n!`if [ -f docker-compose.yml ] && grep -q 'build: \\.' docker-compose.yml 2>/dev/null; then echo \"DEV_MODE: Building from source (root docker-compose.yml)\"; elif [ -f deploy/docker-compose.yml ]; then echo \"DEPLOY_MODE: Using GHCR image (deploy/docker-compose.yml)\"; else echo \"UNKNOWN_MODE: No docker-compose.yml found in root or deploy/\"; fi`\n\n**Decision:**\n- `DEV_MODE` → work with `Dockerfile.user` + `docker-compose.override.yml` (repo root)\n- `DEPLOY_MODE` → work with `deploy/Dockerfile.user` + `deploy/docker-compose.override.yml`\n- `UNKNOWN_MODE` → ask the user which directory their `docker-compose.yml` lives in before proceeding\n\n## Step 2: Check Current State\n\n**DEV_MODE — check root files:**\n\n!`echo \"=== Dockerfile.user ===\" && (cat Dockerfile.user 2>/dev/null || echo \"(not found — will create from example)\") && echo \"\" && echo \"=== docker-compose.override.yml ===\" && (cat docker-compose.override.yml 2>/dev/null || echo \"(not found — will create from example)\")`\n\n**DEPLOY_MODE — check deploy/ files:**\n\n!`echo \"=== deploy/Dockerfile.user ===\" && (cat deploy/Dockerfile.user 2>/dev/null || echo \"(not found — will create from example)\") && echo \"\" && echo \"=== deploy/docker-compose.override.yml ===\" && (cat deploy/docker-compose.override.yml 2>/dev/null || echo \"(not found — will create from example)\")`\n\n**If both files already exist:** Skip to Step 4 (add tools).\n**If files are missing:** Proceed to Step 3 (copy from examples).\n**If example files are missing too:** Tell the user the project hasn't added the example files yet and to check with the maintainers or the docs.\n\n## Step 3: Copy Example Files\n\nRun only the copy commands for whichever files are missing. Do NOT overwrite existing files.\n\n**DEV_MODE:**\n\n```bash\n# Only if Dockerfile.user does not exist:\ncp Dockerfile.user.example Dockerfile.user\n\n# Only if docker-compose.override.yml does not exist:\ncp docker-compose.override.example.yml docker-compose.override.yml\n```\n\n**DEPLOY_MODE:**\n\n```bash\n# Only if deploy/Dockerfile.user does not exist:\ncp deploy/Dockerfile.user.example deploy/Dockerfile.user\n\n# Only if deploy/docker-compose.override.yml does not exist:\ncp deploy/docker-compose.override.example.yml deploy/docker-compose.override.yml\n```\n\nAfter running the copy commands, confirm to the user which files were created.\n\n## Step 4: Add Tools\n\nParse `$ARGUMENTS` for tool names (space-separated, e.g., `vim ripgrep jq`).\n\n**If tools were specified:** Edit the appropriate `Dockerfile.user` to add (or uncomment) the `RUN apt-get install` block with the requested packages. Use the Edit tool — do not rewrite the entire file.\n\nThe block to add/modify:\n```dockerfile\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    <tool1> \\\n    <tool2> \\\n && rm -rf /var/lib/apt/lists/*\n```\n\n**If no tools were specified:** Show the user the file and ask which tools they want to add. Then edit the file accordingly.\n\n## Step 5: Rebuild\n\nShow the user the rebuild command for their context. Do NOT run it automatically.\n\n**DEV_MODE:**\n```bash\ndocker compose build && docker compose up -d\n```\n\n**DEPLOY_MODE:**\n```bash\ncd deploy && docker compose build && docker compose up -d\n```\n\n## Summary\n\nTell the user:\n1. Which files were created (if any)\n2. Which tools were added to `Dockerfile.user` (if any)\n3. The rebuild command to run when ready\n4. A reminder that `Dockerfile.user` and `docker-compose.override.yml` are gitignored — they're personal and won't be committed\n",".claude/skills/playwright-cli/SKILL.md":"---\nname: playwright-cli\ndescription: Automate browser interactions, test web pages and work with Playwright tests.\nallowed-tools: Bash(playwright-cli:*) Bash(npx:*) Bash(npm:*)\n---\n\n# Browser Automation with playwright-cli\n\n## Quick start\n\n```bash\n# open new browser\nplaywright-cli open\n# navigate to a page\nplaywright-cli goto https://playwright.dev\n# interact with the page using refs from the snapshot\nplaywright-cli click e15\nplaywright-cli type \"page.click\"\nplaywright-cli press Enter\n# take a screenshot (rarely used, as snapshot is more common)\nplaywright-cli screenshot\n# close the browser\nplaywright-cli close\n```\n\n## Commands\n\n### Core\n\n```bash\nplaywright-cli open\n# open and navigate right away\nplaywright-cli open https://example.com/\nplaywright-cli goto https://playwright.dev\nplaywright-cli type \"search query\"\nplaywright-cli click e3\nplaywright-cli dblclick e7\n# --submit presses Enter after filling the element\nplaywright-cli fill e5 \"user@example.com\"  --submit\nplaywright-cli drag e2 e8\n# drop files or data onto an element (from outside the page)\nplaywright-cli drop e4 --path=./image.png\nplaywright-cli drop e4 --data=\"text/plain=hello world\"\nplaywright-cli hover e4\nplaywright-cli select e9 \"option-value\"\nplaywright-cli upload ./document.pdf\nplaywright-cli check e12\nplaywright-cli uncheck e12\nplaywright-cli snapshot\n# search the snapshot for text or a regexp, returns matching nodes with surrounding context\nplaywright-cli find \"Sign in\"\nplaywright-cli find --regex \"Sign (in|up)\"\n# wrap the regexp in slashes to add flags, e.g. /i for case-insensitive\nplaywright-cli find --regex \"/sign (in|up)/i\"\nplaywright-cli eval \"document.title\"\nplaywright-cli eval \"el => el.textContent\" e5\n# get element id, class, or any attribute not visible in the snapshot\nplaywright-cli eval \"el => el.id\" e5\nplaywright-cli eval \"el => el.getAttribute('data-testid')\" e5\nplaywright-cli dialog-accept\nplaywright-cli dialog-accept \"confirmation text\"\nplaywright-cli dialog-dismiss\nplaywright-cli resize 1920 1080\nplaywright-cli close\n```\n\n### Navigation\n\n```bash\nplaywright-cli go-back\nplaywright-cli go-forward\nplaywright-cli reload\n```\n\n### Keyboard\n\n```bash\nplaywright-cli press Enter\nplaywright-cli press ArrowDown\nplaywright-cli keydown Shift\nplaywright-cli keyup Shift\n```\n\n### Mouse\n\n```bash\nplaywright-cli mousemove 150 300\nplaywright-cli mousedown\nplaywright-cli mousedown right\nplaywright-cli mouseup\nplaywright-cli mouseup right\nplaywright-cli mousewheel 0 100\n```\n\n### Save as\n\n```bash\nplaywright-cli screenshot\nplaywright-cli screenshot e5\nplaywright-cli screenshot --filename=page.png\nplaywright-cli screenshot --hires\nplaywright-cli pdf --filename=page.pdf\n```\n\n### Tabs\n\n```bash\nplaywright-cli tab-list\nplaywright-cli tab-new\nplaywright-cli tab-new https://example.com/page\nplaywright-cli tab-close\nplaywright-cli tab-close 2\nplaywright-cli tab-select 0\n```\n\n### Storage\n\n```bash\nplaywright-cli state-save\nplaywright-cli state-save auth.json\nplaywright-cli state-load auth.json\n\n# Cookies\nplaywright-cli cookie-list\nplaywright-cli cookie-list --domain=example.com\nplaywright-cli cookie-get session_id\nplaywright-cli cookie-set session_id abc123\nplaywright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure\nplaywright-cli cookie-delete session_id\nplaywright-cli cookie-clear\n\n# LocalStorage\nplaywright-cli localstorage-list\nplaywright-cli localstorage-get theme\nplaywright-cli localstorage-set theme dark\nplaywright-cli localstorage-delete theme\nplaywright-cli localstorage-clear\n\n# SessionStorage\nplaywright-cli sessionstorage-list\nplaywright-cli sessionstorage-get step\nplaywright-cli sessionstorage-set step 3\nplaywright-cli sessionstorage-delete step\nplaywright-cli sessionstorage-clear\n```\n\n### Network\n\n```bash\nplaywright-cli route \"**/*.jpg\" --status=404\nplaywright-cli route \"https://api.example.com/**\" --body='{\"mock\": true}'\nplaywright-cli route-list\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n### DevTools\n\n```bash\nplaywright-cli console\nplaywright-cli console warning\nplaywright-cli requests\nplaywright-cli request 5\nplaywright-cli run-code \"async page => await page.context().grantPermissions(['geolocation'])\"\nplaywright-cli run-code --filename=script.js\nplaywright-cli tracing-start\nplaywright-cli tracing-stop\nplaywright-cli video-start video.webm\nplaywright-cli video-chapter \"Chapter Title\" --description=\"Details\" --duration=2000\nplaywright-cli video-stop\n\n# annotate each subsequent action (click, type, ...) with a callout naming the action and highlighting the target\nplaywright-cli video-show-actions --duration=600 --position=top-right\nplaywright-cli video-hide-actions\n\n# launch the dashboard for UI review / design feedback — user annotates the page, you receive the annotated screenshot, snapshot, and notes\nplaywright-cli show --annotate\n\n# generate a Playwright locator for an element from its ref or selector\nplaywright-cli generate-locator e5 --raw\n\n# show a persistent highlight overlay for an element, optionally with a custom style\nplaywright-cli highlight e5\nplaywright-cli highlight e5 --style=\"outline: 3px dashed red\"\n# hide a single element highlight, or all page highlights when no target is given\nplaywright-cli highlight e5 --hide\nplaywright-cli highlight --hide\n```\n\n## Raw output\n\nThe global `--raw` option strips page status, generated code, and snapshot sections from the output, returning only the result value. Use it to pipe command output into other tools. Commands that don't produce output return nothing.\n\n```bash\nplaywright-cli --raw eval \"JSON.stringify(performance.timing)\" | jq '.loadEventEnd - .navigationStart'\nplaywright-cli --raw eval \"JSON.stringify([...document.querySelectorAll('a')].map(a => a.href))\" > links.json\nplaywright-cli --raw snapshot > before.yml\nplaywright-cli click e5\nplaywright-cli --raw snapshot > after.yml\ndiff before.yml after.yml\nTOKEN=$(playwright-cli --raw cookie-get session_id)\nplaywright-cli --raw localstorage-get theme\n```\n\nFor structured output wrapping every reply as JSON, pass --json\n```bash\nplaywright-cli list --json\n```\n\n## Open parameters\n```bash\n# Use specific browser when creating session\nplaywright-cli open --browser=chrome\nplaywright-cli open --browser=firefox\nplaywright-cli open --browser=webkit\nplaywright-cli open --browser=msedge\n\n# Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit).\n# Prefer this when a mobile layout is acceptable: mobile pages are usually\n# lighter, so snapshots are smaller and cheaper.\nplaywright-cli open --mobile\nplaywright-cli open --device=\"iPhone 15\"\n\n# Use persistent profile (by default profile is in-memory)\nplaywright-cli open --persistent\n# Use persistent profile with custom directory\nplaywright-cli open --profile=/path/to/profile\n\n# Connect to browser via Playwright Extension\nplaywright-cli attach --extension=chrome\n\n# Connect to a running Chrome or Edge by channel name\nplaywright-cli attach --cdp=chrome\nplaywright-cli attach --cdp=msedge\n\n# Connect to a running browser via CDP endpoint\nplaywright-cli attach --cdp=http://localhost:9222\n\n# Start with config file\nplaywright-cli open --config=my-config.json\n\n# Close the browser\nplaywright-cli close\n# Detach from an attached browser (leaves the external browser running)\nplaywright-cli -s=msedge detach\n# Delete user data for the default session\nplaywright-cli delete-data\n```\n\n## URLs with `&` on Windows\n\nOn Windows, `cmd.exe` and PowerShell treat `&` as a command separator, so URLs with multiple query parameters get truncated before `playwright-cli` runs. Escape `&` with `^&` in `cmd.exe`, or use `--%` in PowerShell:\n\n```batch\nplaywright-cli goto \"https://example.com/?a=1^&b=2\"\n```\n\n```powershell\nplaywright-cli --% goto \"https://example.com/?a=1&b=2\"\n```\n\n## Snapshots\n\nAfter each command, playwright-cli provides a snapshot of the current browser state.\n\n```bash\n> playwright-cli goto https://example.com\n### Page\n- Page URL: https://example.com/\n- Page Title: Example Domain\n### Snapshot\n[Snapshot](.playwright-cli/page-2026-02-14T19-22-42-679Z.yml)\n```\n\nYou can also take a snapshot on demand using `playwright-cli snapshot` command. All the options below can be combined as needed.\n\n```bash\n# default - save to a file with timestamp-based name\nplaywright-cli snapshot\n\n# save to file, use when snapshot is a part of the workflow result\nplaywright-cli snapshot --filename=after-click.yaml\n\n# snapshot an element instead of the whole page\nplaywright-cli snapshot \"#main\"\n\n# limit snapshot depth for efficiency, take a partial snapshot afterwards\nplaywright-cli snapshot --depth=4\nplaywright-cli snapshot e34\n\n# include each element's bounding box as [box=x,y,width,height]\nplaywright-cli snapshot --boxes\n\n# search a large snapshot instead of capturing it all — returns matching nodes\n# with 3 lines of context around each match (like grep -C)\nplaywright-cli find \"Add to cart\"\nplaywright-cli find --regex \"\\\\$[0-9]+\\\\.[0-9]{2}\"\n```\n\n## Targeting elements\n\nBy default, use refs from the snapshot to interact with page elements.\n\n```bash\n# get snapshot with refs\nplaywright-cli snapshot\n\n# interact using a ref\nplaywright-cli click e15\n```\n\nYou can also use css selectors or Playwright locators.\n\n```bash\n# css selector\nplaywright-cli click \"#main > button.submit\"\n\n# role locator\nplaywright-cli click \"getByRole('button', { name: 'Submit' })\"\n\n# test id\nplaywright-cli click \"getByTestId('submit-button')\"\n```\n\n## Browser Sessions\n\n```bash\n# create new browser session named \"mysession\" with persistent profile\nplaywright-cli -s=mysession open example.com --persistent\n# same with manually specified profile directory (use when requested explicitly)\nplaywright-cli -s=mysession open example.com --profile=/path/to/profile\nplaywright-cli -s=mysession click e6\nplaywright-cli -s=mysession close  # stop a named browser\nplaywright-cli -s=mysession delete-data  # delete user data for persistent session\n\nplaywright-cli list\n# Close all browsers\nplaywright-cli close-all\n# Forcefully kill all browser processes\nplaywright-cli kill-all\n```\n\n## Installation\n\nIf global `playwright-cli` command is not available, try a local version via `npx playwright cli`:\n\n```bash\nnpx --no-install playwright --version\n```\n\nWhen local version is available, use `npx playwright cli` in all commands. Otherwise, install `playwright-cli` as a global command:\n\n```bash\nnpm install -g @playwright/cli@latest\n```\n\n## Example: Form submission\n\n```bash\nplaywright-cli open https://example.com/form\nplaywright-cli snapshot\n\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Multi-tab workflow\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tab-new https://example.com/other\nplaywright-cli tab-list\nplaywright-cli tab-select 0\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Debugging with DevTools\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli console\nplaywright-cli requests\nplaywright-cli close\n```\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tracing-start\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli tracing-stop\nplaywright-cli close\n```\n\n## Example: Interactive session\n\nAsk the user for UI review or design feedback. The user draws boxes on the live page and types comments; you receive the annotated screenshot, the snapshot of the marked region, and the user's notes. Use this whenever the user asks for \"UI review\", \"design feedback\", or to \"ask the user what they think / want / mean\":\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli show --annotate\n```\n\n## Specific tasks\n\n* **Running and Debugging Playwright tests** [references/playwright-tests.md](references/playwright-tests.md)\n* **Request mocking** [references/request-mocking.md](references/request-mocking.md)\n* **Running Playwright code** [references/running-code.md](references/running-code.md)\n* **Browser session management** [references/session-management.md](references/session-management.md)\n* **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)\n* **Test generation (plan / generate / heal)** [references/test-generation.md](references/test-generation.md)\n* **Tracing** [references/tracing.md](references/tracing.md)\n* **Video recording** [references/video-recording.md](references/video-recording.md)\n* **Inspecting element attributes** [references/element-attributes.md](references/element-attributes.md)\n",".claude/skills/playwright-cli/references/element-attributes.md":"# Inspecting Element Attributes\n\nWhen the snapshot doesn't show an element's `id`, `class`, `data-*` attributes, or other DOM properties, use `eval` to inspect them.\n\n## Examples\n\n```bash\nplaywright-cli snapshot\n# snapshot shows a button as e7 but doesn't reveal its id or data attributes\n\n# get the element's id\nplaywright-cli eval \"el => el.id\" e7\n\n# get all CSS classes\nplaywright-cli eval \"el => el.className\" e7\n\n# get a specific attribute\nplaywright-cli eval \"el => el.getAttribute('data-testid')\" e7\nplaywright-cli eval \"el => el.getAttribute('aria-label')\" e7\n\n# get a computed style property\nplaywright-cli eval \"el => getComputedStyle(el).display\" e7\n```\n",".claude/skills/playwright-cli/references/playwright-tests.md":"# Running Playwright Tests\n\nTo run Playwright tests, use the `npx playwright test` command, or a package manager script. To avoid opening the interactive html report, use `PLAYWRIGHT_HTML_OPEN=never` environment variable.\n\n```bash\n# Run all tests\nPLAYWRIGHT_HTML_OPEN=never npx playwright test\n\n# Run all tests through a custom npm script\nPLAYWRIGHT_HTML_OPEN=never npm run special-test-command\n```\n\n# Debugging Playwright Tests\n\nTo debug a failing Playwright test, run it with `--debug=cli` option. This command will pause the test at the start and print the debugging instructions.\n\n**IMPORTANT**: run the command in the background and check the output until \"Debugging Instructions\" is printed. Make sure to stop the command after you have finished.\n\nOnce instructions containing a session name are printed, use `playwright-cli` to attach the session and explore the page.\n\n```bash\n# Run the test\nPLAYWRIGHT_HTML_OPEN=never npx playwright test --debug=cli\n# ...\n# ... debugging instructions for \"tw-abcdef\" session ...\n# ...\n\n# Attach to the test\nplaywright-cli attach tw-abcdef\n```\n\nKeep the test running in the background while you explore and look for a fix.\nThe test is paused at the start, so you should step over or pause at a particular location\nwhere the problem is most likely to be.\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code.\nThis code appears in the output and can be copied directly into the test. Most of the time, a specific locator or an expectation should be updated, but it could also be a bug in the app. Use your judgement.\n\nAfter fixing the test, stop the background test run. Rerun to check that test passes.\n",".claude/skills/playwright-cli/references/request-mocking.md":"# Request Mocking\n\nIntercept, mock, modify, and block network requests.\n\n## CLI Route Commands\n\n```bash\n# Mock with custom status\nplaywright-cli route \"**/*.jpg\" --status=404\n\n# Mock with JSON body\nplaywright-cli route \"**/api/users\" --body='[{\"id\":1,\"name\":\"Alice\"}]' --content-type=application/json\n\n# Mock with custom headers\nplaywright-cli route \"**/api/data\" --body='{\"ok\":true}' --header=\"X-Custom: value\"\n\n# Remove headers from requests\nplaywright-cli route \"**/*\" --remove-header=cookie,authorization\n\n# List active routes\nplaywright-cli route-list\n\n# Remove a route or all routes\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n## URL Patterns\n\n```\n**/api/users           - Exact path match\n**/api/*/details       - Wildcard in path\n**/*.{png,jpg,jpeg}    - Match file extensions\n**/search?q=*          - Match query parameters\n```\n\n## Advanced Mocking with run-code\n\nFor conditional responses, request body inspection, response modification, or delays:\n\n### Conditional Response Based on Request\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/login', route => {\n    const body = route.request().postDataJSON();\n    if (body.username === 'admin') {\n      route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) });\n    } else {\n      route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) });\n    }\n  });\n}\"\n```\n\n### Modify Real Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/user', async route => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.isPremium = true;\n    await route.fulfill({ response, json });\n  });\n}\"\n```\n\n### Simulate Network Failures\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/offline', route => route.abort('internetdisconnected'));\n}\"\n# Options: connectionrefused, timedout, connectionreset, internetdisconnected\n```\n\n### Delayed Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/slow', async route => {\n    await new Promise(r => setTimeout(r, 3000));\n    route.fulfill({ body: JSON.stringify({ data: 'loaded' }) });\n  });\n}\"\n```\n",".claude/skills/playwright-cli/references/running-code.md":"# Running Custom Playwright Code\n\nUse `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands.\n\n## Syntax\n\n```bash\nplaywright-cli run-code \"async page => {\n  // Your Playwright code here\n  // Access page.context() for browser context operations\n}\"\n```\n\nYou can also load the function from a file:\n\n```bash\nplaywright-cli run-code --filename=./my-script.js\n```\n\n\nThe code must be a single function expression, it is wrapped in `(...)` and evaluated.\nimport/export/require syntax is not supported.\n\n## Geolocation\n\n```bash\n# Grant geolocation permission and set location\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });\n}\"\n\n# Set location to London\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 });\n}\"\n\n# Clear geolocation override\nplaywright-cli run-code \"async page => {\n  await page.context().clearPermissions();\n}\"\n```\n\n## Permissions\n\n```bash\n# Grant multiple permissions\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions([\n    'geolocation',\n    'notifications',\n    'camera',\n    'microphone'\n  ]);\n}\"\n\n# Grant permissions for specific origin\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read'], {\n    origin: 'https://example.com'\n  });\n}\"\n```\n\n## Media Emulation\n\n```bash\n# Emulate dark color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'dark' });\n}\"\n\n# Emulate light color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'light' });\n}\"\n\n# Emulate reduced motion\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ reducedMotion: 'reduce' });\n}\"\n\n# Emulate print media\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ media: 'print' });\n}\"\n```\n\n## Wait Strategies\n\n```bash\n# Wait for network idle\nplaywright-cli run-code \"async page => {\n  await page.waitForLoadState('networkidle');\n}\"\n\n# Wait for specific element\nplaywright-cli run-code \"async page => {\n  await page.locator('.loading').waitFor({ state: 'hidden' });\n}\"\n\n# Wait for function to return true\nplaywright-cli run-code \"async page => {\n  await page.waitForFunction(() => window.appReady === true);\n}\"\n\n# Wait with timeout\nplaywright-cli run-code \"async page => {\n  await page.locator('.result').waitFor({ timeout: 10000 });\n}\"\n```\n\n## Frames and Iframes\n\n```bash\n# Work with iframe\nplaywright-cli run-code \"async page => {\n  const frame = page.locator('iframe#my-iframe').contentFrame();\n  await frame.locator('button').click();\n}\"\n\n# Get all frames\nplaywright-cli run-code \"async page => {\n  const frames = page.frames();\n  return frames.map(f => f.url());\n}\"\n```\n\n## File Downloads\n\n```bash\n# Handle file download\nplaywright-cli run-code \"async page => {\n  const downloadPromise = page.waitForEvent('download');\n  await page.getByRole('link', { name: 'Download' }).click();\n  const download = await downloadPromise;\n  await download.saveAs('./downloaded-file.pdf');\n  return download.suggestedFilename();\n}\"\n```\n\n## Clipboard\n\n```bash\n# Read clipboard (requires permission)\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read']);\n  return await page.evaluate(() => navigator.clipboard.readText());\n}\"\n\n# Write to clipboard\nplaywright-cli run-code \"async page => {\n  await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!');\n}\"\n```\n\n## Page Information\n\n```bash\n# Get page title\nplaywright-cli run-code \"async page => {\n  return await page.title();\n}\"\n\n# Get current URL\nplaywright-cli run-code \"async page => {\n  return page.url();\n}\"\n\n# Get page content\nplaywright-cli run-code \"async page => {\n  return await page.content();\n}\"\n\n# Get viewport size\nplaywright-cli run-code \"async page => {\n  return page.viewportSize();\n}\"\n```\n\n## JavaScript Execution\n\n```bash\n# Execute JavaScript and return result\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(() => {\n    return {\n      userAgent: navigator.userAgent,\n      language: navigator.language,\n      cookiesEnabled: navigator.cookieEnabled\n    };\n  });\n}\"\n\n# Pass arguments to evaluate\nplaywright-cli run-code \"async page => {\n  const multiplier = 5;\n  return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier);\n}\"\n```\n\n## Error Handling\n\n```bash\n# Try-catch in run-code\nplaywright-cli run-code \"async page => {\n  try {\n    await page.getByRole('button', { name: 'Submit' }).click({ timeout: 1000 });\n    return 'clicked';\n  } catch (e) {\n    return 'element not found';\n  }\n}\"\n```\n\n## Complex Workflows\n\n```bash\n# Login and save state\nplaywright-cli run-code \"async page => {\n  await page.goto('https://example.com/login');\n  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n  await page.getByRole('textbox', { name: 'Password' }).fill('secret');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await page.waitForURL('**/dashboard');\n  await page.context().storageState({ path: 'auth.json' });\n  return 'Login successful';\n}\"\n\n# Scrape data from multiple pages\nplaywright-cli run-code \"async page => {\n  const results = [];\n  for (let i = 1; i <= 3; i++) {\n    await page.goto(\\`https://example.com/page/\\${i}\\`);\n    const items = await page.locator('.item').allTextContents();\n    results.push(...items);\n  }\n  return results;\n}\"\n```\n",".claude/skills/playwright-cli/references/session-management.md":"# Browser Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Browser Sessions\n\nUse `-s` flag to isolate browser contexts:\n\n```bash\n# Browser 1: Authentication flow\nplaywright-cli -s=auth open https://app.example.com/login\n\n# Browser 2: Public browsing (separate cookies, storage)\nplaywright-cli -s=public open https://example.com\n\n# Commands are isolated by browser session\nplaywright-cli -s=auth fill e1 \"user@example.com\"\nplaywright-cli -s=public snapshot\n```\n\n## Browser Session Isolation Properties\n\nEach browser session has independent:\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Browser Session Commands\n\n```bash\n# List all browser sessions\nplaywright-cli list\n\n# Stop a browser session (close the browser)\nplaywright-cli close                # stop the default browser\nplaywright-cli -s=mysession close   # stop a named browser\n\n# Stop all browser sessions\nplaywright-cli close-all\n\n# Forcefully kill all daemon processes (for stale/zombie processes)\nplaywright-cli kill-all\n\n# Delete browser session user data (profile directory)\nplaywright-cli delete-data                # delete default browser data\nplaywright-cli -s=mysession delete-data   # delete named browser data\n```\n\n## Environment Variable\n\nSet a default browser session name via environment variable:\n\n```bash\nexport PLAYWRIGHT_CLI_SESSION=\"mysession\"\nplaywright-cli open example.com  # Uses \"mysession\" automatically\n```\n\n## Common Patterns\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all browsers\nplaywright-cli -s=site1 open https://site1.com &\nplaywright-cli -s=site2 open https://site2.com &\nplaywright-cli -s=site3 open https://site3.com &\nwait\n\n# Take snapshots from each\nplaywright-cli -s=site1 snapshot\nplaywright-cli -s=site2 snapshot\nplaywright-cli -s=site3 snapshot\n\n# Cleanup\nplaywright-cli close-all\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nplaywright-cli -s=variant-a open \"https://app.com?variant=a\"\nplaywright-cli -s=variant-b open \"https://app.com?variant=b\"\n\n# Compare\nplaywright-cli -s=variant-a screenshot\nplaywright-cli -s=variant-b screenshot\n```\n\n### Persistent Profile\n\nBy default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk:\n\n```bash\n# Use persistent profile (auto-generated location)\nplaywright-cli open https://example.com --persistent\n\n# Use persistent profile with custom directory\nplaywright-cli open https://example.com --profile=/path/to/profile\n```\n\n## Attaching to a Running Browser\n\nUse `attach` to connect to a browser that is already running, instead of launching a new one.\n\n### Attach by channel name\n\nConnect to a running Chrome or Edge instance by its channel name. The browser must have remote debugging enabled — navigate to `chrome://inspect/#remote-debugging` in the target browser and check \"Allow remote debugging for this browser instance\".\n\n```bash\n# Attach to Chrome\nplaywright-cli attach --cdp=chrome\n\n# Attach to Chrome Canary\nplaywright-cli attach --cdp=chrome-canary\n\n# Attach to Microsoft Edge\nplaywright-cli attach --cdp=msedge\n\n# Attach to Edge Dev\nplaywright-cli attach --cdp=msedge-dev\n```\n\nSupported channels: `chrome`, `chrome-beta`, `chrome-dev`, `chrome-canary`, `msedge`, `msedge-beta`, `msedge-dev`, `msedge-canary`.\n\nWhen `--session` is not provided, the session is named after the channel (e.g. `--cdp=msedge` creates a session called `msedge`), so parallel attaches to Chrome and Edge don't collide on `default`. Pass `--session=<name>` to override.\n\n### Attach via CDP endpoint\n\nConnect to a browser that exposes a Chrome DevTools Protocol endpoint:\n\n```bash\nplaywright-cli attach --cdp=http://localhost:9222\n```\n\n### Attach via browser extension\n\nConnect to a browser with the Playwright extension installed:\n\n```bash\nplaywright-cli attach --extension\n```\n\n### Detach\n\nTear down an attached session without affecting the external browser:\n\n```bash\n# Detach the default attached session\nplaywright-cli detach\n\n# Detach a specific attached session\nplaywright-cli -s=msedge detach\n```\n\n`detach` only works on sessions created via `attach`. For sessions created via `open`, use `close`.\n\n## Default Browser Session\n\nWhen `-s` is omitted, commands use the default browser session:\n\n```bash\n# These use the same default browser session\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli close  # Stops default browser\n```\n\n## Browser Session Configuration\n\nConfigure a browser session with specific settings when opening:\n\n```bash\n# Open with config file\nplaywright-cli open https://example.com --config=.playwright/my-cli.json\n\n# Open with specific browser\nplaywright-cli open https://example.com --browser=firefox\n\n# Open in headed mode\nplaywright-cli open https://example.com --headed\n\n# Open with persistent profile\nplaywright-cli open https://example.com --persistent\n```\n\n## Best Practices\n\n### 1. Name Browser Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nplaywright-cli -s=github-auth open https://github.com\nplaywright-cli -s=docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nplaywright-cli -s=s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Stop browsers when done\nplaywright-cli -s=auth close\nplaywright-cli -s=scrape close\n\n# Or stop all at once\nplaywright-cli close-all\n\n# If browsers become unresponsive or zombie processes remain\nplaywright-cli kill-all\n```\n\n### 3. Delete Stale Browser Data\n\n```bash\n# Remove old browser data to free disk space\nplaywright-cli -s=oldsession delete-data\n```\n",".claude/skills/playwright-cli/references/storage-state.md":"# Storage Management\n\nManage cookies, localStorage, sessionStorage, and browser storage state.\n\n## Storage State\n\nSave and restore complete browser state including cookies and storage.\n\n### Save Storage State\n\n```bash\n# Save to auto-generated filename (storage-state-{timestamp}.json)\nplaywright-cli state-save\n\n# Save to specific filename\nplaywright-cli state-save my-auth-state.json\n```\n\n### Restore Storage State\n\n```bash\n# Load storage state from file\nplaywright-cli state-load my-auth-state.json\n\n# Reload page to apply cookies\nplaywright-cli open https://example.com\n```\n\n### Storage State File Format\n\nThe saved file contains:\n\n```json\n{\n  \"cookies\": [\n    {\n      \"name\": \"session_id\",\n      \"value\": \"abc123\",\n      \"domain\": \"example.com\",\n      \"path\": \"/\",\n      \"expires\": 1893456000,\n      \"httpOnly\": true,\n      \"secure\": true,\n      \"sameSite\": \"Lax\"\n    }\n  ],\n  \"origins\": [\n    {\n      \"origin\": \"https://example.com\",\n      \"localStorage\": [\n        { \"name\": \"theme\", \"value\": \"dark\" },\n        { \"name\": \"user_id\", \"value\": \"12345\" }\n      ]\n    }\n  ]\n}\n```\n\n## Cookies\n\n### List All Cookies\n\n```bash\nplaywright-cli cookie-list\n```\n\n### Filter Cookies by Domain\n\n```bash\nplaywright-cli cookie-list --domain=example.com\n```\n\n### Filter Cookies by Path\n\n```bash\nplaywright-cli cookie-list --path=/api\n```\n\n### Get Specific Cookie\n\n```bash\nplaywright-cli cookie-get session_id\n```\n\n### Set a Cookie\n\n```bash\n# Basic cookie\nplaywright-cli cookie-set session abc123\n\n# Cookie with options\nplaywright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax\n\n# Cookie with expiration (Unix timestamp)\nplaywright-cli cookie-set remember_me token123 --expires=1893456000\n```\n\n### Delete a Cookie\n\n```bash\nplaywright-cli cookie-delete session_id\n```\n\n### Clear All Cookies\n\n```bash\nplaywright-cli cookie-clear\n```\n\n### Advanced: Multiple Cookies or Custom Options\n\nFor complex scenarios like adding multiple cookies at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.context().addCookies([\n    { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true },\n    { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' }\n  ]);\n}\"\n```\n\n## Local Storage\n\n### List All localStorage Items\n\n```bash\nplaywright-cli localstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli localstorage-get token\n```\n\n### Set Value\n\n```bash\nplaywright-cli localstorage-set theme dark\n```\n\n### Set JSON Value\n\n```bash\nplaywright-cli localstorage-set user_settings '{\"theme\":\"dark\",\"language\":\"en\"}'\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli localstorage-delete token\n```\n\n### Clear All localStorage\n\n```bash\nplaywright-cli localstorage-clear\n```\n\n### Advanced: Multiple Operations\n\nFor complex scenarios like setting multiple values at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    localStorage.setItem('token', 'jwt_abc123');\n    localStorage.setItem('user_id', '12345');\n    localStorage.setItem('expires_at', Date.now() + 3600000);\n  });\n}\"\n```\n\n## Session Storage\n\n### List All sessionStorage Items\n\n```bash\nplaywright-cli sessionstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli sessionstorage-get form_data\n```\n\n### Set Value\n\n```bash\nplaywright-cli sessionstorage-set step 3\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli sessionstorage-delete step\n```\n\n### Clear sessionStorage\n\n```bash\nplaywright-cli sessionstorage-clear\n```\n\n## IndexedDB\n\n### List Databases\n\n```bash\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(async () => {\n    const databases = await indexedDB.databases();\n    return databases;\n  });\n}\"\n```\n\n### Delete Database\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    indexedDB.deleteDatabase('myDatabase');\n  });\n}\"\n```\n\n## Common Patterns\n\n### Authentication State Reuse\n\n```bash\n# Step 1: Login and save state\nplaywright-cli open https://app.example.com/login\nplaywright-cli snapshot\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\n\n# Save the authenticated state\nplaywright-cli state-save auth.json\n\n# Step 2: Later, restore state and skip login\nplaywright-cli state-load auth.json\nplaywright-cli open https://app.example.com/dashboard\n# Already logged in!\n```\n\n### Save and Restore Roundtrip\n\n```bash\n# Set up authentication state\nplaywright-cli open https://example.com\nplaywright-cli eval \"() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }\"\n\n# Save state to file\nplaywright-cli state-save my-session.json\n\n# ... later, in a new session ...\n\n# Restore state\nplaywright-cli state-load my-session.json\nplaywright-cli open https://example.com\n# Cookies and localStorage are restored!\n```\n\n## Security Notes\n\n- Never commit storage state files containing auth tokens\n- Add `*.auth-state.json` to `.gitignore`\n- Delete state files after automation completes\n- Use environment variables for sensitive data\n- By default, sessions run in-memory mode which is safer for sensitive operations\n",".claude/skills/playwright-cli/references/test-generation.md":"# Test generation (plan → generate → heal)\n\nEnd-to-end workflow for authoring and maintaining Playwright tests with `playwright-cli`. Every `playwright-cli` action emits the equivalent Playwright TypeScript, and that generated code is the raw material for every test. The sections below can be used independently:\n\n- **How generation works** — the core mechanic everything else relies on: actions become TypeScript, plus how to add assertions.\n- **Plan** — explore the app, produce a spec file describing what to test.\n- **Generate** — turn a spec into Playwright test files. Update the spec if it's vague or stale.\n- **Heal** — diagnose failing tests, fix the code, reconcile the spec with reality.\n\nPlan / generate / heal lean on the same mechanic: run `npx playwright test --debug=cli` in the background, then `playwright-cli attach tw-XXXX` to drive the paused page interactively. See [playwright-tests.md](playwright-tests.md) for the debug/attach mechanics.\n\n---\n\n## 0. How generation works\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. This code appears in the output and can be copied directly into your test files.\n\n```bash\n# Start a session\nplaywright-cli open https://example.com/login\n\n# Take a snapshot to see elements\nplaywright-cli snapshot\n# Output shows: e1 [textbox \"Email\"], e2 [textbox \"Password\"], e3 [button \"Sign In\"]\n\n# Fill form fields - generates code automatically\nplaywright-cli fill e1 \"user@example.com\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n\nplaywright-cli fill e2 \"password123\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n\nplaywright-cli click e3\n# Ran Playwright code:\n# await page.getByRole('button', { name: 'Sign In' }).click();\n```\n\n### Building a test file\n\nCollect the generated code into a Playwright test:\n\n```typescript\nimport { test, expect } from '@playwright/test';\n\ntest('login flow', async ({ page }) => {\n  // Generated code from playwright-cli session:\n  await page.goto('https://example.com/login');\n  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n  await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n  await page.getByRole('button', { name: 'Sign In' }).click();\n\n  // Add assertions\n  await expect(page).toHaveURL(/.*dashboard/);\n});\n```\n\n### Use semantic locators\n\nThe generated code uses role-based locators when possible, which are more resilient:\n\n```typescript\n// Generated (good - semantic)\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Avoid (fragile - CSS selectors)\nawait page.locator('#submit-btn').click();\n```\n\n### Explore before recording\n\nTake snapshots to understand the page structure before recording actions:\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli snapshot\n# Review the element structure\nplaywright-cli click e5\n```\n\n### Add assertions manually\n\nGenerated code captures actions but not assertions. Add expectations in your test using one of the recommended matchers:\n\n- `toBeVisible()` — element is rendered and visible\n- `toHaveText(text)` — element text content matches\n- `toHaveValue(value) / toBeEmpty()` — input/select value matches\n- `toBeChecked() / toBeUnchecked()` — checkbox state matches\n- `toMatchAriaSnapshot(snapshot)` — page (or locator) matches a partial accessibility snapshot\n\nUse `playwright-cli generate-locator <target>` to produce the locator expression for the assertion, and the snapshot/eval commands to capture the expected value.\n\nWhen asserting text content, make sure that generated locator does not contain text from the element itself. `getByTestId()` or `getByLabel()` usually work well with asserting text. When locator is text-based, prefer `toBeVisible()` instead.\n\nSnapshot to be matched does not have to contain all the information - only capture what's necessary for the assertion. You can use regular expressions for unstable values.\n\n```bash\n# Get a stable locator for an element ref to use in the assertion\nplaywright-cli --raw generate-locator e5\n# getByRole('button', { name: 'Submit' })\n\n# Capture expected text content for toHaveText\nplaywright-cli --raw eval \"el => el.textContent\" e5\n\n# Capture expected input value for toHaveValue/toBeEmpty\nplaywright-cli --raw eval \"el => el.value\" e5\n\n# Capture expected aria snapshot for toMatchAriaSnapshot/toBeChecked\n# (whole page, or use a ref to scope to a region)\nplaywright-cli --raw snapshot\nplaywright-cli --raw snapshot e5\n```\n\n```typescript\n// Generated action\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Manual assertions using the outputs above:\nawait expect(page.getByRole('alert', { name: 'Success' })).toBeVisible();\nawait expect(page.getByTestId('main-header')).toHaveText('Welcome, user');\nawait expect(page.getByRole('textbox', { name: 'Email' })).toHaveValue('user@example.com');\nawait expect(page.getByRole('checkbox', { name: 'Enable notifications' })).toBeChecked();\n\n// toMatchAriaSnapshot on the whole page, finds a matching region\nawait expect(page).toMatchAriaSnapshot(`\n  - heading \"Welcome, user\"\n  - link /\\\\d+ new messages?/\n  - button \"Sign out\"\n`);\n\n// toMatchAriaSnapshot scoped to a region\nawait expect(page.getByRole('navigation')).toMatchAriaSnapshot(`\n  - link \"Home\"\n  - link /\\\\d+ new messages?/\n  - link \"Profile\"\n`);\n```\n\n---\n\n## 1. Planning\n\nGoal: produce a spec file (e.g. `specs/<feature>.plan.md`) that enumerates the scenarios to test. **Always** write the spec to a file.\n\n### 1.1 Prerequisite: workspace\n\nCheck the workspace has Playwright installed before anything else:\n\n```bash\n# Either of these confirms a workspace:\ntest -f playwright.config.ts || test -f playwright.config.js\nnpx --no-install playwright --version\n```\n\nIf there is no Playwright install, bootstrap one and let the user pick the defaults:\n\n```bash\nnpm init playwright@latest\n```\n\n### 1.2 Prerequisite: seed test\n\nA **seed test** is a minimal test that lands the page in the state every scenario starts from: navigation to the app, any required login, feature flags, etc. Scenarios assume a fresh start *after* the seed. `--debug=cli` pauses *inside* this test, so the seed is where every planning and generation session begins.\n\nMinimum viable seed:\n\n```ts\n// tests/seed.spec.ts\nimport { test } from '@playwright/test';\n\ntest('seed', async ({ page }) => {\n  await page.goto('https://example.com/');\n});\n```\n\nPreferred — push navigation into a fixture so scenario tests reuse it:\n\n```ts\n// tests/fixtures.ts\nimport { test as baseTest } from '@playwright/test';\nexport { expect } from '@playwright/test';\n\nexport const test = baseTest.extend({\n  page: async ({ page }, use) => {\n    await page.goto('https://example.com/');\n    await use(page);\n  },\n});\n```\n\n```ts\n// tests/seed.spec.ts\nimport { test } from './fixtures';\n\ntest('seed', async ({ page }) => {\n  // Fixture already navigates. This empty body tells agents where to start.\n});\n```\n\nIf no seed exists, create one that at least navigates to the app.\n\n### 1.3 Explore the app\n\nLaunch the app via the seed in the background and attach:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/seed.spec.ts --debug=cli\n# wait for \"Debugging Instructions\" and the session name tw-XXXX\nplaywright-cli attach tw-XXXX\n```\n\nResume so the seed runs, then probe the app:\n\n```bash\nplaywright-cli resume                   # resume so that seed test runs fully\nplaywright-cli snapshot                 # inventory of interactive elements\nplaywright-cli click e5                 # follow a flow\nplaywright-cli eval \"location.href\"     # read URL / state\nplaywright-cli show --annotate          # ask the user to point at something\n```\n\nMap out:\n\n- Interactive surfaces (forms, buttons, lists, filters, modals).\n- Primary user journeys end-to-end.\n- Edge cases: empty states, validation errors, very long input, boundary values.\n- Persistence: reload, local/session storage, URL fragments.\n- Navigation: which controls change the URL, back/forward behaviour.\n\n**Important**: Do not just open the app url with playwright-cli, always go through the test to capture any custom setup done there.\n**Important**: Stop the background test when done exploring.\n\n### 1.4 Write the spec file\n\nSave under `specs/<feature>.plan.md`. Use this structure:\n\n```markdown\n# <Feature> Test Plan\n\n## Application Overview\n\n<One paragraph describing what the feature does and why it matters.>\n\n## Test Scenarios\n\n### 1. <Group Name>\n\n**Seed:** `tests/seed.spec.ts`\n\n#### 1.1. <kebab-case-scenario-name>\n\n**File:** `tests/<group>/<kebab-case-scenario-name>.spec.ts`\n\n**Steps:**\n  1. <Concrete user step>\n    - expect: <observable outcome>\n    - expect: <another observable outcome>\n  2. <Next step>\n    - expect: <outcome>\n\n#### 1.2. <next-scenario>\n...\n\n### 2. <Next Group>\n\n**Seed:** `tests/seed.spec.ts`\n...\n```\n\nGuidelines:\n\n- Each scenario is independent and starts from the seed's fresh state — never chain scenarios.\n- Scenario names are kebab-case and match the test file name (`should-add-single-todo` → `should-add-single-todo.spec.ts`).\n- Cover happy path, edge cases, validation, negative flows, persistence.\n- Write steps at the user level (\"Type 'Buy milk' into the input\"), not the API level (\"call `fill`\").\n- Put observable outcomes in `- expect:` bullets; each becomes an assertion during generation.\n\n---\n\n## 2. Generate\n\nGoal: take a spec file and produce Playwright test files. Optionally update the spec if it has drifted.\n\n### 2.1 Inputs\n\n- **Spec file**, e.g. `specs/basic-operations.plan.md`.\n- **Target**: either a single scenario (e.g. `1.2`), a whole group (`1`), or all.\n- **Seed file**, read from the `**Seed:**` line of the scenario's group.\n\n### 2.2 Generate one scenario\n\nFor each target scenario, in sequence (never in parallel — scenarios share the seed session):\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test <seed-file> --debug=cli   # background\nplaywright-cli attach tw-XXXX\n# resume\n```\n\n**Do not** just open the app url with playwright-cli, always go through the test to capture any custom setup done there.\n\nWalk the scenario's `Steps:` one by one with `playwright-cli`, treating the spec as the plan and the live app as the source of truth. If a step is vague (\"click the button\" — which button?), references an element that no longer exists, or contradicts the app's actual behaviour, use your judgement: update the spec to match what the app really does, then keep going. Editing the spec mid-generation is expected.\n\nEvery action prints the equivalent Playwright TypeScript (see [How generation works](#0-how-generation-works)):\n\n```bash\nplaywright-cli snapshot                         # find refs\nplaywright-cli fill e3 \"John Doe\"               # -> page.getByRole('textbox', {...}).fill(...)\nplaywright-cli press Enter\nplaywright-cli click e7\n```\n\nFor each `- expect:` bullet, add an explicit assertion. See [How generation works](#0-how-generation-works) for details.\n\nCollect the generated code and write the test file at the path given in the spec:\n\n```ts\n// spec: specs/basic-operations.plan.md\n// seed: tests/seed.spec.ts\nimport { test, expect } from './fixtures';   // or '@playwright/test' if no fixtures file\n\ntest.describe('Signing in and out', () => {\n  test('should sign in', async ({ page }) => {\n    // 1. Navigate to the application\n    // (handled by the seed fixture)\n\n    // 2. Type 'John Doe' into the username field\n    await page.getByRole('textbox', { name: 'username' }).fill('John Doe');\n\n    // 3. Type password\n    await page.getByRole('textbox', { name: 'password' }).fill('TestPassword');\n\n    // 4. Press Enter to submit\n    await page.getByRole('textbox', { name: 'password' }).press('Enter');\n\n    await expect(page.getByRole('heading')).toContainText('Welcome, John Doe!');\n  });\n});\n```\n\nRules:\n\n- **One test per file.** File path, describe name, and test name come verbatim from the spec (minus the ordinal).\n- Prefix each numbered step with a `// N. <step text>` comment before its actions.\n- Use the describe group name verbatim from the spec (no `1.` ordinal).\n- Import from `./fixtures` if the project has one; otherwise `@playwright/test`.\n- **Important**: close the CLI session and stop the background test before moving to the next scenario.\n\n### 2.3 Generate multiple scenarios\n\nLoop 2.2 over the targeted scenarios one at a time, restarting the seed between each so every test starts from a clean page. This is safe to parallelise due to unique generated session names - just make sure each test run is stopped.\n\n### 2.4 Run generated tests\n\nAfter generation, run the new tests once:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/<group>/<scenario>.spec.ts\n```\n\nAny failure goes to Section 3.\n\n---\n\n## 3. Heal\n\nGoal: fix failing tests, and update the spec if the app's intended behaviour changed.\n\n### 3.1 Find failing tests\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test\n```\n\nRecord the list of failing `<file>:<line>` entries and process them one at a time. Do not attempt parallel fixes — shared state and the single CLI session make that fragile.\n\n### 3.2 Debug one failure\n\nRun the single failing test in debug mode in the background, then attach:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/<group>/<scenario>.spec.ts:<line> --debug=cli\n# wait for \"Debugging Instructions\" and the tw-XXXX session name\nplaywright-cli attach tw-XXXX\n```\n\nThe test is paused at the start. Step forward or run to until just before the failing action or assertion, then diagnose:\n\n```bash\nplaywright-cli snapshot                # did the element change / move / rename?\nplaywright-cli console                 # app-side errors?\nplaywright-cli requests                # failed request? wrong payload?\nplaywright-cli show --annotate         # ask the user to point somewhere\n```\n\nCommon causes: selector drift, new wrapper element, label/ARIA rename, timing (transition, async load), assertion text updated in the app, test data leaking between runs.\n\nRehearse the corrected interaction with `playwright-cli` — the generated code in the output is what you paste back into the test.\n\n### 3.3 Apply the fix\n\nEdit the test file: update the locator, assertion, step order, or inputs to match the corrected behaviour. Stop the background debug run. Rerun the single test to confirm green.\n\nNever skip hooks or add sleeps as a fix. Never use `networkidle`.\n\n### 3.4 Reconcile with the spec\n\nOpen the spec referenced by the `// spec:` header in the test file and locate the scenario that matches the test.\n\n- **Fix was purely technical** (locator drift, better assertion shape) and the spec's user-level behaviour still matches the app → leave the spec alone.\n- **Fix changed user-visible steps, inputs, order, or expected outcomes** that the spec describes → update the spec to match reality. Keep the scenario id and file path stable; only the step / expect lines change.\n- **Unclear whether the app change is intentional** (spec is stale) **or a regression** (test was right, app is wrong) → **stop and ask the user**. Provide:\n  - the scenario id (e.g. `2.3`),\n  - the spec lines that no longer match,\n  - the observed app behaviour (quote a snapshot excerpt or a concrete outcome).\n\nOnly after the user answers, either update the spec (intentional change) or file/flag the test as covering a bug (regression).\n\n### 3.5 Iteration and giving up\n\n- Fix failures one at a time; rerun after each.\n- If after thorough investigation you are confident the test is correct but the app is wrong *and* the user has confirmed it's a bug: mark the test `test.fixme(...)` with a comment pointing at the user's decision or issue link. Never silently skip.\n\n---\n\n## Cross-references\n\n| For... | See |\n|---|---|\n| `--debug=cli` / attach mechanics | [playwright-tests.md](playwright-tests.md) |\n| Mocking requests during exploration/generation | [request-mocking.md](request-mocking.md) |\n| Managing the CLI browser session | [session-management.md](session-management.md) |\n",".claude/skills/playwright-cli/references/tracing.md":"# Tracing\n\nCapture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.\n\n## Basic Usage\n\n```bash\n# Start trace recording\nplaywright-cli tracing-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli click e1\nplaywright-cli fill e2 \"test\"\n\n# Stop trace recording\nplaywright-cli tracing-stop\n```\n\n## Trace Output Files\n\nWhen you start tracing, Playwright creates a `traces/` directory with several files:\n\n### `trace-{timestamp}.trace`\n\n**Action log** - The main trace file containing:\n- Every action performed (clicks, fills, navigations)\n- DOM snapshots before and after each action\n- Screenshots at each step\n- Timing information\n- Console messages\n- Source locations\n\n### `trace-{timestamp}.network`\n\n**Network log** - Complete network activity:\n- All HTTP requests and responses\n- Request headers and bodies\n- Response headers and bodies\n- Timing (DNS, connect, TLS, TTFB, download)\n- Resource sizes\n- Failed requests and errors\n\n### `resources/`\n\n**Resources directory** - Cached resources:\n- Images, fonts, stylesheets, scripts\n- Response bodies for replay\n- Assets needed to reconstruct page state\n\n## What Traces Capture\n\n| Category | Details |\n|----------|---------|\n| **Actions** | Clicks, fills, hovers, keyboard input, navigations |\n| **DOM** | Full DOM snapshot before/after each action |\n| **Screenshots** | Visual state at each step |\n| **Network** | All requests, responses, headers, bodies, timing |\n| **Console** | All console.log, warn, error messages |\n| **Timing** | Precise timing for each operation |\n\n## Use Cases\n\n### Debugging Failed Actions\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://app.example.com\n\n# This click fails - why?\nplaywright-cli click e5\n\nplaywright-cli tracing-stop\n# Open trace to see DOM state when click was attempted\n```\n\n### Analyzing Performance\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://slow-site.com\nplaywright-cli tracing-stop\n\n# View network waterfall to identify slow resources\n```\n\n### Capturing Evidence\n\n```bash\n# Record a complete user flow for documentation\nplaywright-cli tracing-start\n\nplaywright-cli open https://app.example.com/checkout\nplaywright-cli fill e1 \"4111111111111111\"\nplaywright-cli fill e2 \"12/25\"\nplaywright-cli fill e3 \"123\"\nplaywright-cli click e4\n\nplaywright-cli tracing-stop\n# Trace shows exact sequence of events\n```\n\n## Trace vs Video vs Screenshot\n\n| Feature | Trace | Video | Screenshot |\n|---------|-------|-------|------------|\n| **Format** | .trace file | .webm video | .png/.jpeg image |\n| **DOM inspection** | Yes | No | No |\n| **Network details** | Yes | No | No |\n| **Step-by-step replay** | Yes | Continuous | Single frame |\n| **File size** | Medium | Large | Small |\n| **Best for** | Debugging | Demos | Quick capture |\n\n## Best Practices\n\n### 1. Start Tracing Before the Problem\n\n```bash\n# Trace the entire flow, not just the failing step\nplaywright-cli tracing-start\nplaywright-cli open https://example.com\n# ... all steps leading to the issue ...\nplaywright-cli tracing-stop\n```\n\n### 2. Clean Up Old Traces\n\nTraces can consume significant disk space:\n\n```bash\n# Remove traces older than 7 days\nfind .playwright-cli/traces -mtime +7 -delete\n```\n\n## Limitations\n\n- Traces add overhead to automation\n- Large traces can consume significant disk space\n- Some dynamic content may not replay perfectly\n",".claude/skills/playwright-cli/references/video-recording.md":"# Video Recording\n\nCapture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).\n\n## Basic Recording\n\n```bash\n# Open browser first\nplaywright-cli open\n\n# Start recording\nplaywright-cli video-start demo.webm\n\n# Add a chapter marker for section transitions\nplaywright-cli video-chapter \"Getting Started\" --description=\"Opening the homepage\" --duration=2000\n\n# Navigate and perform actions\nplaywright-cli goto https://example.com\nplaywright-cli snapshot\nplaywright-cli click e1\n\n# Add another chapter\nplaywright-cli video-chapter \"Filling Form\" --description=\"Entering test data\" --duration=2000\nplaywright-cli fill e2 \"test input\"\n\n# Stop and save\nplaywright-cli video-stop\n```\n\n## Best Practices\n\n### 1. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nplaywright-cli video-start recordings/login-flow-2024-01-15.webm\nplaywright-cli video-start recordings/checkout-test-run-42.webm\n```\n\n### 2. Record entire hero scripts.\n\nWhen recording a video for the user or as a proof of work, it is best to create a code snippet and execute it with run-code.\nIt allows inserting appropriate pauses between the actions and annotating the video. There are new Playwright APIs for that.\n\n1) Perform scenario using CLI and take note of all locators and actions. You'll need those locators to request their bounding boxes for highlight.\n2) Create a file with the intended script for video (below). Use pressSequentially w/ delay for nice typing, make reasonable pauses.\n3) Use playwright-cli run-code --filename your-script.js\n\n**Important**: Overlays are `pointer-events: none` — they do not interfere with page interactions. You can safely keep sticky overlays visible while clicking, filling, or performing any actions on the page.\n\n```js\nasync page => {\n  await page.screencast.start({ path: 'video.webm', size: { width: 1280, height: 800 } });\n  await page.goto('https://demo.playwright.dev/todomvc');\n\n  // Show a chapter card — blurs the page and shows a dialog.\n  // Blocks until duration expires, then auto-removes.\n  // Use this for simple use cases, but always feel free to hand-craft your own beautiful\n  // overlay via await page.screencast.showOverlay().\n  await page.screencast.showChapter('Adding Todo Items', {\n    description: 'We will add several items to the todo list.',\n    duration: 2000,\n  });\n\n  // Perform action\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Walk the dog', { delay: 60 });\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');\n  await page.waitForTimeout(1000);\n\n  // Show next chapter\n  await page.screencast.showChapter('Verifying Results', {\n    description: 'Checking the item appeared in the list.',\n    duration: 2000,\n  });\n\n  // Add a sticky annotation that stays while you perform actions.\n  // Overlays are pointer-events: none, so they won't block clicks.\n  const annotation = await page.screencast.showOverlay(`\n    <div style=\"position: absolute; top: 8px; right: 8px;\n      padding: 6px 12px; background: rgba(0,0,0,0.7);\n      border-radius: 8px; font-size: 13px; color: white;\">\n      ✓ Item added successfully\n    </div>\n  `);\n\n  // Perform more actions while the annotation is visible\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Buy groceries', { delay: 60 });\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');\n  await page.waitForTimeout(1500);\n\n  // Remove the annotation when done\n  await annotation.dispose();\n\n  // You can also highlight relevant locators and provide contextual annotations.\n  const bounds = await page.getByText('Walk the dog').boundingBox();\n  await page.screencast.showOverlay(`\n    <div style=\"position: absolute;\n      top: ${bounds.y}px;\n      left: ${bounds.x}px;\n      width: ${bounds.width}px;\n      height: ${bounds.height}px;\n      border: 1px solid red;\">\n    </div>\n    <div style=\"position: absolute;\n      top: ${bounds.y + bounds.height + 5}px;\n      left: ${bounds.x + bounds.width / 2}px;\n      transform: translateX(-50%);\n      padding: 6px;\n      background: #808080;\n      border-radius: 10px;\n      font-size: 14px;\n      color: white;\">Check it out, it is right above this text\n    </div>\n  `, { duration: 2000 });\n\n  await page.screencast.stop();\n}\n```\n\nEmbrace creativity, overlays are powerful.\n\n### Overlay API Summary\n\n| Method | Use Case |\n|--------|----------|\n| `page.screencast.showChapter(title, { description?, duration?, styleSheet? })` | Full-screen chapter card with blurred backdrop — ideal for section transitions |\n| `page.screencast.showOverlay(html, { duration? })` | Custom HTML overlay — use for callouts, labels, highlights |\n| `disposable.dispose()` | Remove a sticky overlay added without duration |\n| `page.screencast.hideOverlays()` / `page.screencast.showOverlays()` | Temporarily hide/show all overlays |\n\n## Tracing vs Video\n\n| Feature | Video | Tracing |\n|---------|-------|---------|\n| Output | WebM file | Trace file (viewable in Trace Viewer) |\n| Shows | Visual recording | DOM snapshots, network, console, actions |\n| Use case | Demos, documentation | Debugging, analysis |\n| Size | Larger | Smaller |\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n",".claude/skills/release/SKILL.md":"---\nname: release\ndescription: |\n  Create a release from dev branch. Generates changelog entries from commits,\n  bumps version, and creates a PR to main.\n\n  TRIGGERS - Use this skill when user says:\n  - \"/release\" - create a patch release (default)\n  - \"/release minor\" - create a minor release\n  - \"/release major\" - create a major release\n  - \"make a release\", \"cut a release\", \"ship it\", \"release to main\"\n---\n\n# Release Skill\n\nCreates a release by comparing dev to main, generating changelog entries from commits, bumping the version, and creating a PR. After the tag is pushed and the release workflow finishes building binaries, updates the Homebrew formula with the real SHA256 values from the published `checksums.txt`, syncs the `coleam00/homebrew-archon` tap, and verifies the end-to-end install path via `/test-release`.\n\n> **⚠️ CRITICAL — Homebrew formula SHAs cannot be known until after the release workflow builds binaries.**\n>\n> The `version` field in `homebrew/archon.rb` and the `sha256` fields must be updated **atomically**. Never update one without the other.\n>\n> The correct sequence is:\n> 1. Tag is pushed → release workflow fires → binaries built → `checksums.txt` uploaded\n> 2. Fetch `checksums.txt` from the published release\n> 3. Parse the SHA256 per platform\n> 4. Update `homebrew/archon.rb` with the new version AND the new SHAs in a single commit\n> 5. Sync to the `coleam00/homebrew-archon/Formula/archon.rb` tap repo\n>\n> Updating the formula's `version` field without also updating the `sha256` values creates a stale, misleading formula that looks valid but produces checksum mismatches on install. This has happened before (v0.3.0: version updated to 0.3.0 but SHAs were still from v0.2.13). Always do both or neither.\n\n## Process\n\n### Step 1: Validate State\n\n```bash\n# Must be on dev branch with clean working tree\ngit checkout dev\ngit pull origin dev --no-rebase\ngit status --porcelain  # must be empty\ngit fetch origin main\n```\n\nIf not on dev or working tree is dirty, abort with a clear message.\n\n**Then check that dev actually contains main.** Anything committed directly to\nmain — a docs typo fix, a hotfix, the previous release's formula commit — stays\nstranded there until someone merges it back. The release PR would then propose\n*reverting* it, and the next release inherits the drift.\n\n```bash\nif git merge-base --is-ancestor origin/main origin/dev; then\n  echo \"dev contains main — OK\"\nelse\n  echo \"DRIFT: commits exist on main that dev does not have:\"\n  git log origin/dev..origin/main --oneline\n  echo \"\"\n  echo \"Resync before releasing:\"\n  echo \"  git checkout dev && git pull origin main --no-rebase && git push origin dev\"\n  exit 1\nfi\n```\n\nObserved on the 0.7.1 release: `ae704a73 Update docs.mdx (#2155)` had been\ncommitted straight to main after 0.7.0 and never merged back. Someone had\nnoticed the *content* gap and hand-forward-ported it via a separate PR (#2403),\nso dev had the change but not the commit — and `main` was still not an ancestor\nof `dev`. Resolve this before Step 2; do not carry it into the release PR.\n\n### Step 1.5: Pre-flight compiled-binary smoke test (MANDATORY before any other step)\n\n> **Why this is first**: releases have ended up with zero working binaries because a module-init crash or bundler bug only surfaces in `bun build --compile` output, not in `bun run`. CI catches it — but only AFTER the tag is pushed and a GitHub Release is created. By then the damage (empty release, broken `releases/latest`, broken `install.sh`) is already live. Failing here, before any user-visible change, keeps the blast radius at \"no release was cut.\"\n\nRun locally on the native target. This takes ~15-30s and is cheaper than discovering the problem after tag+release.\n\n```bash\n# Guard: only run this for Node/Bun projects with a CLI entry point + build-binaries script.\nif [ -f scripts/build-binaries.sh ] && [ -f packages/cli/src/cli.ts ]; then\n  TMP_BINARY=$(mktemp)\n  trap \"rm -f $TMP_BINARY\" EXIT\n\n  # Compile for the native target only (not full cross-compile — that's CI's job).\n  # Match the real release flags so any bundler quirk reproduces locally.\n  bun build \\\n    --compile \\\n    --minify \\\n    --target=bun \\\n    --outfile=\"$TMP_BINARY\" \\\n    packages/cli/src/cli.ts\n\n  # Smoke test: the binary must start and exit 0 on a safe, non-interactive command.\n  # Use `--help` (NOT `version`). The `version` command's compiled-binary code\n  # path depends on BUNDLED_IS_BINARY=true, which is set by scripts/build-binaries.sh\n  # — but we're doing a bare `bun build --compile` here to keep the smoke fast,\n  # so BUNDLED_IS_BINARY is still `false`. That sends `version` down the dev\n  # branch of version.ts which tries to read package.json from a path that only\n  # exists in node_modules, producing a false-positive ENOENT. `--help` has no\n  # such dev/binary branch and exercises the same module-init graph we're\n  # actually testing. Must NOT touch network, database, or require env vars.\n  if ! \"$TMP_BINARY\" --help > /tmp/archon-preflight.log 2>&1; then\n    echo \"ERROR: compiled binary crashed at startup\"\n    cat /tmp/archon-preflight.log\n    echo \"\"\n    echo \"This usually means a dependency has a module-init-time side effect that\"\n    echo \"fails in a compiled binary context (readFileSync of a path that only\"\n    echo \"exists in node_modules, etc.). Fix before cutting the release — do NOT\"\n    echo \"proceed to version bump.\"\n    exit 1\n  fi\n\n  # Also grep for known crash markers that exit 0 but print a fatal error\n  # (some module-init errors are caught by top-level try/catch but still log).\n  if grep -qE \"Expected CommonJS module|TypeError:|ReferenceError:|SyntaxError:\" /tmp/archon-preflight.log; then\n    echo \"ERROR: compiled binary emitted a runtime error despite exit 0\"\n    cat /tmp/archon-preflight.log\n    exit 1\n  fi\n\n  echo \"Pre-flight binary smoke: PASSED\"\nfi\n```\n\nIf this fails, **abort the release entirely** — do not bump version, do not modify CHANGELOG, do not create a PR. Surface the error to the user, point at the failing output, and stop. Recovery is: fix the bundler / dependency issue on a feature branch, merge to dev, re-run `/release`.\n\n**Common failure modes this catches:**\n- Bun `--bytecode` flag producing broken bytecode for the current module graph\n- A dependency (e.g. an SDK) reading `package.json` or other files at module top level via paths that resolve fine in `node_modules/` but not next to a compiled binary\n- Circular imports that break under minification but work under plain `bun run`\n- A newly added package that ships CJS with an unusual wrapper shape\n\n### Step 2: Detect Stack and Current Version\n\nDetect the project's package manager and version file:\n\n1. **Check for `pyproject.toml`** — Python project, version in `version = \"x.y.z\"`\n2. **Check for `package.json`** — Node/Bun project, version in `\"version\": \"x.y.z\"`\n3. **Check for `Cargo.toml`** — Rust project, version in `version = \"x.y.z\"`\n4. **Check for `go.mod`** — Go project (version from git tags only, no file to bump)\n\nIf none found, abort: \"Could not detect project stack — no version file found.\"\n\nRead the current version from the detected file.\n\n### Step 3: Determine Version Bump\n\n**Bump rules based on argument:**\n- No argument or `patch` (default): `0.1.0 -> 0.1.1`\n- `minor`: `0.1.3 -> 0.2.0`\n- `major`: `0.3.5 -> 1.0.0`\n\n### Step 4: Collect Commits\n\n> **Do NOT use `git log main..dev`.** This repo **squash**-merges the release PR\n> into main, so main receives one new commit per release and never contains dev's\n> individual commits. `main..dev` therefore accumulates *every commit ever\n> squashed* and grows release over release. On 0.7.1 it returned **61** commits\n> when only **25** were new — the other 36 were already shipped in 0.7.0 and\n> already written into its changelog. Followed literally, it re-logs most of the\n> previous release.\n\nThe real boundary is dev's own last `Release x.y.z` commit:\n\n```bash\n# The previous release commit ON DEV (not the squashed one on main).\n#\n# The `$` anchor is load-bearing. Both commits exist on dev after Step 9's sync:\n#   6c6945ce  Release 0.7.1           <- dev's own version-bump commit  (WANT)\n#   c71f7f52  Release 0.7.1 (#2435)   <- main's squash, pulled back     (WRONG)\n# Picking the squash commit is catastrophic: its ancestry does not include dev's\n# individual history, so the range balloons to the whole repo (383 commits when\n# tested). Match the bare subject only.\nLAST_RELEASE=$(git log origin/dev \\\n  --grep='^Release [0-9]+\\.[0-9]+\\.[0-9]+$' --extended-regexp \\\n  --format='%H' -n 1)\n\n# First-ever release: fall back to the root commit. Without this the range\n# becomes `..origin/dev`, which git resolves against HEAD and which returns\n# ZERO commits — the skill would report \"nothing to release\" and stop.\nif [ -z \"$LAST_RELEASE\" ]; then\n  LAST_RELEASE=$(git rev-list --max-parents=0 origin/dev | tail -n 1)\nfi\necho \"Previous release commit: $(git log -1 --oneline \"$LAST_RELEASE\")\"\n\n# Everything after it is genuinely new, minus the release-plumbing commits\n# described below.\ngit log \"$LAST_RELEASE\"..origin/dev --oneline --no-merges \\\n  --grep='^Release [0-9]+\\.[0-9]+\\.[0-9]+' \\\n  --grep='^chore: update Homebrew formula for v' \\\n  --grep='^chore\\(homebrew\\):' \\\n  --extended-regexp --invert-grep\n```\n\n> The backslashes in `^chore\\(homebrew\\):` are required. Under `--extended-regexp`\n> bare `(` and `)` are grouping operators, so the unescaped form matches the\n> literal text `chorehomebrew:` and silently fails to filter anything.\n\nTwo commit kinds in that range are **release plumbing, not changelog material**,\nand the `--invert-grep` above drops them:\n\n- `Release x.y.z (#NNNN)` — main's squash commit, pulled back by Step 9's sync\n- `chore: update Homebrew formula for vx.y.z` / `chore(homebrew): …` — the CI job\n  and the Step 10 commit; note these appear as *two different SHAs* with the same\n  content, one per branch\n\n> The `Release` pattern is anchored to a full `x.y.z` version deliberately. A bare\n> `^Release ` would also swallow a legitimate feature commit whose subject starts\n> with that word. If a real commit is ever dropped, widen the range by hand rather\n> than loosening the pattern.\n\n**Sanity-check the boundary before drafting.** Cross-reference against what the\nprevious version already documented — any overlap means the range is wrong.\nDerive both headings from the current version so this does not rot:\n\n```bash\n# CURRENT_VERSION is the value read in Step 2, before the bump (e.g. 0.7.1)\nPREV_MAJOR_MINOR_PATCH=\"$CURRENT_VERSION\"\n# The heading immediately above the one we are about to write:\nawk -v prev=\"## [$PREV_MAJOR_MINOR_PATCH]\" '\n  index($0, prev) == 1 { on = 1; next }\n  on && /^## \\[/       { exit }\n  on                   { print }\n' CHANGELOG.md | grep -oE '#[0-9]{3,5}' | sort -u\n```\n\nIf a PR number in your commit range appears in that list, stop and re-derive the\nboundary — do not write it twice.\n\n**Also read the `[Unreleased]` section.** Contributors may write entries there by\nhand. They are input to this release, not a boundary error:\n\n```bash\nawk '\n  /^## \\[Unreleased\\]/ { on = 1; next }\n  on && /^## \\[/      { exit }\n  on                  { print }\n' CHANGELOG.md\n```\n\nIf no new commits, abort: \"Nothing to release — dev is up to date with main.\"\n\n### Step 5: Draft Changelog Entries\n\nRead the commit messages and the actual diffs (`git diff \"$LAST_RELEASE\"..origin/dev`) to understand what changed.\n\nPrefer the PR title and its `## Summary` section over the raw commit subject —\nthey state the user-visible problem, which is what a changelog entry needs:\n\n```bash\ngh pr view <N> --repo coleam00/Archon --json number,title,body,closingIssuesReferences\n```\n\nDraft entries only for changes the `[Unreleased]` entries from Step 4 do not\nalready describe, so each change appears once. Hand-written entries often cite\nthe issue a PR closes (`closingIssuesReferences`) rather than the PR, or cite\nnothing; judge by what the entry says, not by its number. Keep the substance of\nhand-written entries (migration tables, exact details) and tidy only their wording\nand placement. If you can't tell whether an entry covers a PR, ask in Step 7.\n\n**Categorize into Keep a Changelog sections, in this order (omit empty ones):**\n1. **Breaking** — changes that break existing behavior users may rely on\n2. **Security** — vulnerability fixes and security-relevant hardening\n3. **Added** — new features, new files, new capabilities\n4. **Changed** — modifications to existing behavior\n5. **Deprecated** — features still available but discouraged or planned for removal\n6. **Removed** — deleted features or code\n7. **Fixed** — bug fixes\n\n**Writing rules:**\n- Write entries as a human would — clear, concise, user-facing language\n- Do NOT just copy commit messages verbatim — rewrite them into proper changelog entries\n- Group related commits into single entries where it makes sense\n- Include PR numbers in parentheses when available: `(#12)`\n- Each entry should start with a noun or gerund describing WHAT changed\n- Skip internal-only changes (CI tweaks, typo fixes) unless they affect behavior\n- One blank line between sections\n\n### Step 6: Update Files\n\n1. **Version file** — update version to new value:\n   - `package.json`: update `\"version\": \"x.y.z\"`\n   - `pyproject.toml`: update `version = \"x.y.z\"`\n   - `Cargo.toml`: update `version = \"x.y.z\"`\n\n2. **Workspace version sync** (monorepo only):\n   - If `scripts/sync-versions.sh` exists, run `bash scripts/sync-versions.sh` to sync all `packages/*/package.json` versions to match the root version. The script also refreshes `bun.lock` itself, so the workspace versions land in the lockfile too.\n\n3. **Lockfile refresh** (stack-dependent) — skip the matching line if step 2's script already refreshed the lockfile:\n   - `package.json` + `bun.lock`: run `bun install`\n   - `package.json` + `package-lock.json`: run `npm install --package-lock-only`\n   - `pyproject.toml` + `uv.lock`: run `uv lock --quiet`\n   - `Cargo.toml`: run `cargo update --workspace`\n\n4. **`CHANGELOG.md`** — write one new version section below `[Unreleased]` from\n   the `[Unreleased]` entries and the Step 5 drafts, one `### <Category>` heading\n   per non-empty category in the Step 5 order:\n\n```markdown\n## [x.y.z] - YYYY-MM-DD\n\nOne-line summary of the release.\n\n### Breaking\n\n- Entry one (#PR)\n\n### Added\n\n- Entry one (#PR)\n- Entry two (#PR)\n\n### Fixed\n\n- Entry one (#PR)\n```\n\n**`### Breaking` must come first**, immediately after the one-line summary. Step 9's\nrelease workflow reads this section verbatim into the GitHub release body, so this\nfile's own section order is what makes breaking changes appear first on the release page.\n\nLeave the `[Unreleased]` header with nothing under it.\n\n### Step 7: Present for Review\n\nShow the user:\n1. The detected stack and version file\n2. The version bump (old -> new)\n3. The full changelog section that will be added\n4. The list of commits being included\n5. Any `[Unreleased]` entry you were unsure covers a PR, next to that PR\n\nAsk: \"Does this look good? I'll commit and create the PR.\"\n\n### Step 8: Commit and PR\n\nOnly after user approval:\n\n```bash\n# Stage version file, workspace packages, lockfile, and changelog\ngit add <version-file> packages/*/package.json <lockfile> CHANGELOG.md\ngit commit -m \"Release x.y.z\"\n\n# Push dev\ngit push origin dev\n\n# Create PR: dev -> main\ngh pr create --base main --head dev \\\n  --title \"Release x.y.z\" \\\n  --body \"$(cat <<'EOF'\n## Release x.y.z\n\n{changelog section content}\n\n---\n\nMerging this PR releases x.y.z to main.\nEOF\n)\"\n```\n\nReturn the PR URL to the user.\n\n### Step 9: Tag, Release, and Sync After Merge\n\nAfter the PR is merged (either by the user or via `gh pr merge`):\n\n```bash\n# Fetch the merge commit on main\ngit fetch origin main\n\n# Tag the merge commit\ngit tag vx.y.z origin/main\ngit push origin vx.y.z\n\n# Create a GitHub Release from the tag. --generate-notes is a placeholder: the\n# push above already triggered .github/workflows/release.yml, and once its\n# `release` job finishes building binaries (5-10 min), its \"Build release\n# notes\" step overwrites this release's body with CHANGELOG.md's own\n# `## [x.y.z]` section (Breaking first, per Step 6) plus install instructions.\n# That job is the single source of truth for the body, so it does not matter\n# that this step's placeholder is throwaway, or which of the two finishes\n# first — do NOT hand-craft curated notes here, they would just be discarded.\ngh release create vx.y.z --title \"vx.y.z\" --generate-notes\n\n# Sync dev with main so both branches are identical\ngit checkout dev\ngit pull origin main --no-rebase\ngit push origin dev\n\n# Verify the sync actually converged — do not assume it did. FAIL CLOSED:\n# continuing past a failed sync publishes a formula and tap from a tree that\n# does not match what was released.\ngit fetch origin\nif git merge-base --is-ancestor origin/main origin/dev; then\n  echo \"dev contains main — OK\"\nelse\n  echo \"STILL DIVERGED — stranded on main:\"\n  git log origin/dev..origin/main --oneline\n  exit 1\nfi\n```\n\n> **`--no-rebase` is required, not optional.** Without it, git aborts with\n> `fatal: Need to specify how to reconcile divergent branches` on any machine\n> that has not set `pull.rebase`. It also pins the behaviour to the merge this\n> step actually wants — a `pull.rebase=true` config would otherwise rewrite dev's\n> history, which is exactly what the warning below forbids.\n\n> **Expect to run this twice.** The CI `update-homebrew` job pushes its own\n> formula commit to dev while you are working, so the `git push origin dev` here\n> (and again in Step 10) can be rejected with `Updates were rejected`. That is\n> normal: `git pull origin dev --no-rebase`, then push again. On 0.7.1 both this\n> step and Step 10 required a second pass.\n\n> **Important**: This sync ensures dev has the merge commit from main. Without it,\n> dev and main diverge. The CI `update-homebrew` job only pushes the formula\n> commit to dev — it does not bring the PR merge commit onto dev. This manual\n> `git pull origin main --no-rebase` is what ensures dev has the merge commit.\n\n> **Do NOT** use `git pull origin main --ff-only` or `git reset --hard origin/main`\n> for this sync. Fast-forward is impossible across a squash merge — main's squash\n> commit has a different SHA than dev's release commit, so dev is never\n> fast-forwardable to main. And resetting dev to main rewrites dev's history,\n> which severs every open PR's merge-base from its original commit and balloons\n> their diffs to thousands of lines (confirmed against v0.3.10's release: PRs\n> went from `+80/-1` to `+6626/-300` after a `git reset --hard origin/main` on\n> dev). The plain `git pull origin main --no-rebase` above creates a regular merge commit on\n> dev. The merge bubble in dev's `git log` is the right cost for preserving\n> open-PR sanity. If the merge produces a `homebrew/archon.rb` conflict during a\n> recovery flow, resolve with `git checkout origin/main -- homebrew/archon.rb`\n> (note: `origin/main`, NOT `main` — local main is often stale because the\n> release pushes via `git push origin dev:main` without fast-forwarding the local\n> branch).\n\nThe GitHub Release is distinct from the git tag — without it, the release won't appear on the repository's Releases page. Always create it.\n\nIf the user merges the PR themselves and comes back, still offer to tag, release, and sync.\n\n### Step 9.5: Add the new tag's SQLite vintage fixture\n\nA release tag is a released schema shape. When `vx.y.z` changes the SQLite schema, `check:sqlite-vintages` (the `schema-upgrade` CI job) fails on every PR until the tag's fixture is committed. A tag that keeps the previous schema gets no fixture and needs nothing. Generate it on a branch off dev and commit whatever it writes:\n\n```bash\ngit fetch --tags origin\nbun run generate:sqlite-vintages   # writes packages/core/src/db/fixtures/sqlite-vintages/vx.y.z.sql\nbun run check:sqlite-vintages\n```\n\n### Step 10: Wait for Release Workflow and Update Homebrew Formula\n\n> **Note**: The `update-homebrew` CI job in `.github/workflows/release.yml` runs automatically\n> after the release job and handles the formula update + push to dev (part of Step 10).\n> Step 11 (tap sync to `coleam00/homebrew-archon`) is always manual. Check the Actions tab\n> before running Step 10 manually.\n\nAfter the tag is pushed, `.github/workflows/release.yml` builds platform binaries and uploads them to the GitHub release. This takes 5-10 minutes. The Homebrew formula SHA256 values cannot be known until these binaries exist.\n\n**Wait for all assets to appear on the release:**\n\n```bash\necho \"Waiting for release workflow to finish uploading binaries...\"\nWORKFLOW_FAILED=0\nfor i in {1..30}; do\n  ASSET_COUNT=$(gh release view \"vx.y.z\" --repo coleam00/Archon --json assets --jq '.assets | length')\n  # Expect 7 assets: 5 binaries (darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64.exe) + archon-web.tar.gz + checksums.txt\n  if [ \"$ASSET_COUNT\" -ge 7 ]; then\n    echo \"All $ASSET_COUNT assets uploaded\"\n    break\n  fi\n\n  # Short-circuit: if the release workflow itself has failed, stop waiting.\n  # Hanging for 15 min when CI already crashed just delays the recovery path.\n  WORKFLOW_STATUS=$(gh run list --workflow release.yml --event push --limit 1 --json conclusion,status --jq '.[0] | \"\\(.status)|\\(.conclusion)\"')\n  if [[ \"$WORKFLOW_STATUS\" == \"completed|failure\" ]]; then\n    echo \"Release workflow FAILED — no point waiting longer\"\n    WORKFLOW_FAILED=1\n    break\n  fi\n\n  echo \"  Assets so far: $ASSET_COUNT/7 — waiting 30s (attempt $i/30)...\"\n  sleep 30\ndone\n\nif [ \"$WORKFLOW_FAILED\" -eq 1 ] || [ \"$ASSET_COUNT\" -lt 7 ]; then\n  # Triage: rerun once in case it's transient, then check again.\n  RUN_ID=$(gh run list --workflow release.yml --event push --limit 1 --json databaseId --jq '.[0].databaseId')\n  echo \"Release workflow failed on run $RUN_ID. Rerunning failed jobs once to confirm...\"\n  gh run rerun \"$RUN_ID\" --failed\n  gh run watch \"$RUN_ID\" --exit-status --interval 30 || true\n\n  # Re-check asset count + run status after rerun.\n  ASSET_COUNT=$(gh release view \"vx.y.z\" --repo coleam00/Archon --json assets --jq '.assets | length')\n  if [ \"$ASSET_COUNT\" -ge 7 ]; then\n    echo \"Rerun succeeded — all assets now present\"\n  else\n    echo \"\"\n    echo \"===== DETERMINISTIC CI FAILURE =====\"\n    echo \"The release workflow failed on two consecutive runs. This is NOT a flake.\"\n    echo \"The tag and release exist but have no (or incomplete) assets.\"\n    echo \"\"\n    echo \"install.sh and similar 'releases/latest' paths are now 404-ing.\"\n    echo \"Proceeding with Homebrew/tap sync would publish a formula pointing at\"\n    echo \"missing or inconsistent binaries.\"\n    echo \"\"\n    echo \"Jump to the 'Recovery: deterministic release CI failure' section at the\"\n    echo \"bottom of this skill and execute it. Do NOT continue past this point.\"\n    exit 1\n  fi\nfi\n```\n\n**Fetch checksums.txt and extract SHA256 values:**\n\n```bash\nTMP_DIR=$(mktemp -d)\ngh release download \"vx.y.z\" --repo coleam00/Archon --pattern \"checksums.txt\" --dir \"$TMP_DIR\"\n\nDARWIN_ARM64_SHA=$(awk '/archon-darwin-arm64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nDARWIN_X64_SHA=$(awk '/archon-darwin-x64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nLINUX_ARM64_SHA=$(awk '/archon-linux-arm64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nLINUX_X64_SHA=$(awk '/archon-linux-x64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\n\n# Sanity check — all four must be present and non-empty\nfor var in DARWIN_ARM64_SHA DARWIN_X64_SHA LINUX_ARM64_SHA LINUX_X64_SHA; do\n  if [ -z \"${!var}\" ]; then\n    echo \"ERROR: $var is empty — checksums.txt may be malformed\"\n    cat \"$TMP_DIR/checksums.txt\"\n    exit 1\n  fi\ndone\n\nrm -rf \"$TMP_DIR\"\n```\n\n**Update `homebrew/archon.rb` in the main repo atomically with version AND SHAs:**\n\nRewrite the formula file using the exact template below. Before using it, diff it against the current `homebrew/archon.rb`: if the current formula has a block the template lacks, update the template first (v0.12.0's first regeneration dropped `caveats` this way). Do NOT edit in place with sed — the whole file should be regenerated from this template so there is zero risk of partial updates.\n\n```bash\ncat > homebrew/archon.rb << EOF\n# Homebrew formula for Archon CLI\n# To install: brew install coleam00/archon/archon\n#\n# This formula downloads pre-built binaries from GitHub releases.\n# For development, see: https://github.com/coleam00/Archon\n\nclass Archon < Formula\n  desc \"Governed agentic automation engine\"\n  homepage \"https://github.com/coleam00/Archon\"\n  version \"x.y.z\"\n  license \"MIT\"\n\n  on_macos do\n    on_arm do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-arm64\"\n      sha256 \"${DARWIN_ARM64_SHA}\"\n    end\n    on_intel do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-x64\"\n      sha256 \"${DARWIN_X64_SHA}\"\n    end\n  end\n\n  on_linux do\n    on_arm do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-arm64\"\n      sha256 \"${LINUX_ARM64_SHA}\"\n    end\n    on_intel do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-x64\"\n      sha256 \"${LINUX_X64_SHA}\"\n    end\n  end\n\n  def install\n    binary_name = case\n    when OS.mac? && Hardware::CPU.arm?\n      \"archon-darwin-arm64\"\n    when OS.mac? && Hardware::CPU.intel?\n      \"archon-darwin-x64\"\n    when OS.linux? && Hardware::CPU.arm?\n      \"archon-linux-arm64\"\n    when OS.linux? && Hardware::CPU.intel?\n      \"archon-linux-x64\"\n    end\n\n    bin.install binary_name => \"archon\"\n  end\n\n  def caveats\n    <<~EOS\n      Archon ships without AI providers. Add the ones you use:\n        archon setup\n      or directly:\n        archon provider add <claude|codex|pi>\n\n      Adding a provider connects Archon to your agent CLI or SDK; it does not install that CLI.\n\n      After `brew upgrade archon`, run `archon provider add` again for each\n      provider you use; installed providers must match the CLI version.\n    EOS\n  end\n\n  test do\n    # Basic version check - archon version should exit with 0 on success\n    assert_match version.to_s, shell_output(\"#{bin}/archon version\")\n  end\nend\nEOF\n```\n\n**Commit the formula update to main, then sync back to dev:**\n\n```bash\ngit checkout main\ngit pull origin main --no-rebase\ngit add homebrew/archon.rb\ngit commit -m \"chore(homebrew): update formula to vx.y.z\"\ngit push origin main\n\n# Sync dev with main so the formula update is on both branches\ngit checkout dev\ngit pull origin main --no-rebase\ngit push origin dev   # may be rejected — see below\n```\n\n**The CI `update-homebrew` job races you here.** It writes its own formula commit\ndirectly to dev (e.g. `chore: update Homebrew formula for vx.y.z`) while you are\ncommitting the same change to main. Both touch the same four `sha256` lines. The\npush above then fails with `Updates were rejected`:\n\n```bash\ngit pull origin dev --no-rebase   # merges CI's commit; usually resolves clean\ngit push origin dev\n```\n\n**A clean merge is not proof the SHAs are right.** Two commits editing the same\nlines can merge without conflict and still leave the wrong values. Verify against\nthe published checksums before pushing — this is the one file where a wrong value\nbreaks every user's install:\n\n```bash\nVERSION=x.y.z   # without the leading v\ngh release download \"v$VERSION\" --repo coleam00/Archon --pattern checksums.txt --dir /tmp/rel\n\nfail=0\n\n# Assert the formula version matches the release. A stale version points every\n# URL at the wrong release while carrying the new digests.\nformula_version=$(awk -F'\"' '/^[[:space:]]*version \"/ {print $2; exit}' homebrew/archon.rb)\nif [ \"$formula_version\" != \"$VERSION\" ]; then\n  echo \"  BAD version: formula says '$formula_version', release is '$VERSION'\"\n  fail=1\nfi\n\n# Compare each digest against the sha256 that FOLLOWS ITS OWN url line. A naive\n# `grep -q \"$digest\" formula` only asks whether the value appears anywhere, so\n# two platforms with swapped hashes both pass — and an empty digest degenerates\n# to `grep -q \"\"`, which matches every file.\nfor p in archon-darwin-arm64 archon-darwin-x64 archon-linux-arm64 archon-linux-x64; do\n  real=$(awk -v p=\"$p\" '$2 ~ \"(^|/)\" p \"$\" {print $1}' /tmp/rel/checksums.txt | head -1)\n  if ! printf '%s' \"$real\" | grep -qE '^[0-9a-f]{64}$'; then\n    echo \"  BAD $p: no 64-char digest in checksums.txt (got '$real')\"\n    fail=1\n    continue\n  fi\n  in_formula=$(awk -v p=\"$p\" '\n    index($0, \"/\" p \"\\\"\") { seen = 1; next }\n    seen && /sha256 \"/    { gsub(/.*sha256 \"|\".*/, \"\"); print; exit }\n  ' homebrew/archon.rb)\n  if [ \"$in_formula\" = \"$real\" ]; then\n    echo \"  OK   $p\"\n  else\n    echo \"  BAD  $p: formula has '$in_formula', release has '$real'\"\n    fail=1\n  fi\ndone\n\n[ \"$fail\" -eq 0 ] || { echo \"formula verification FAILED — do not push\"; exit 1; }\necho \"formula verified against v$VERSION checksums\"\n```\n\nEvery line must print `OK` and the version must match. On any `BAD`, regenerate\nthe formula from the Step 10 template rather than hand-editing — a hand edit is\nhow a digest ends up under the wrong platform in the first place.\n\nFinally, confirm convergence. **Fail closed** — Step 11 publishes to the tap that\nusers install from, so do not proceed on an unconverged tree:\n\n```bash\ngit fetch origin\nconverged=1\ngit merge-base --is-ancestor origin/main origin/dev \\\n  || { echo \"DIVERGED: dev does not contain main\"; converged=0; }\nstranded=$(git log origin/dev..origin/main --oneline | wc -l | tr -d ' ')\n[ \"$stranded\" -eq 0 ] || { echo \"STRANDED: $stranded commit(s) on main not on dev:\"; \\\n  git log origin/dev..origin/main --oneline; converged=0; }\n[ \"$converged\" -eq 1 ] || { echo \"sync incomplete — resolve before Step 11\"; exit 1; }\necho \"branches converged — OK\"\n```\n\n### Step 11: Sync the Homebrew Tap Repo\n\nThe `coleam00/homebrew-archon` repository hosts the actual tap formula that Homebrew reads when users run `brew tap coleam00/archon && brew install coleam00/archon/archon`. The file `coleam00/Archon/homebrew/archon.rb` is the source-of-truth template; the file `coleam00/homebrew-archon/Formula/archon.rb` is what users actually install from. These must be kept in sync.\n\n```bash\nTAP_DIR=$(mktemp -d)\ngit clone git@github.com:coleam00/homebrew-archon.git \"$TAP_DIR\"\ncp homebrew/archon.rb \"$TAP_DIR/Formula/archon.rb\"\n\ncd \"$TAP_DIR\"\nif git diff --quiet; then\n  echo \"Tap formula already matches — no sync needed\"\nelse\n  git add Formula/archon.rb\n  git commit -m \"chore: sync formula to vx.y.z\"\n  git push origin main\nfi\ncd -\nrm -rf \"$TAP_DIR\"\n```\n\nIf the `git clone` fails with a permissions error, the user running the release skill does not have push access to `coleam00/homebrew-archon`. Ask them to request push access from the repo owner, or to perform the sync manually via the GitHub web UI. Do not skip this step silently — the release is not complete until the tap is synced.\n\n### Step 12: Verify the Release End-to-End\n\nAfter the formula is synced, the final verification step is to actually install the released binary via Homebrew and run smoke tests. Use the `test-release` skill:\n\n```\n/test-release brew x.y.z\n```\n\nThis will:\n- Install via `brew tap coleam00/archon && brew install coleam00/archon/archon`\n- Verify the binary reports the correct version and `Build: binary`\n- Verify bundled workflows load\n- Verify the SDK spawn path works (a minimal archon-investigate run)\n- Verify the env-leak guard strips sensitive keys from a leaky .env (Test 4)\n- Uninstall cleanly\n- Produce a PASS/FAIL report\n\n**If `/test-release brew` fails, the release is not ready to announce.** File a hotfix issue for whatever broke, cut `x.y.z+1` with the fix, and re-run this skill. Do NOT advertise a release that fails `test-release`.\n\nAlso run `/test-release curl-mac x.y.z` to cover the curl install path. The two install paths test slightly different things (Homebrew tests the tap formula, curl tests `install.sh` and checksums from the release) and both need to work for users to have a reliable install experience.\n\nIf you have a VPS available, also run `/test-release curl-vps x.y.z <vps-target>` to verify the Linux binary.\n\n## Recovery: deterministic release CI failure\n\nReached here because Step 10 detected two consecutive workflow failures. The tag `vx.y.z` is pushed, the GitHub release exists, but assets are missing or incomplete. Every `install.sh` run currently resolves `releases/latest` to this broken release and 404s on download. Homebrew users are safe because Step 10's atomic formula update was blocked.\n\n**Do not re-run the release workflow a third time hoping it succeeds.** If the failure was reproducible twice, it's a code bug — you need to ship code to fix it.\n\n### Immediate mitigation (restore `install.sh`)\n\nDelete the GitHub Release so `releases/latest` falls back to the previous version. Keep the git tag — tag immutability matters and there are no shipped artifacts pointing at it anyway.\n\n```bash\ngh release delete \"vx.y.z\" --yes\n# Do NOT delete the tag:\n#   git push --delete origin vx.y.z   ← do not run\n# Tag stays so git history records the attempt; no release means no assets\n# means releases/latest resolves to the prior working release.\n```\n\nVerify:\n\n```bash\ngh api repos/coleam00/Archon/releases/latest --jq '.tag_name'\n# should now print the prior version (e.g. v0.3.6), not vx.y.z\n```\n\n### Diagnose\n\nThe release workflow logs tell you which target failed and at what stage (compile vs. smoke-test vs. upload):\n\n```bash\ngh run list --workflow release.yml --limit 2 --json databaseId,conclusion\ngh run view <RUN_ID> --log-failed\n```\n\nCommon causes:\n- **Bundler/bytecode bug** — Bun `--bytecode` produces invalid output for the current module graph. Symptom: `TypeError: Expected CommonJS module to have a function wrapper` at binary startup. Historically caused by a new dependency's CJS/ESM shape interacting with `--bytecode` — dropping the flag or lazy-importing the offending module has been the fix.\n- **Module-init crash** — a dependency does `readFileSync('package.json')` or similar at module top level via a path that exists in `node_modules/` but not next to a compiled binary. Symptom: every binary subcommand crashes immediately; error typically mentions a missing file adjacent to `process.execPath`. Fix by lazy-importing the dependency behind the code path that actually uses it.\n- **Smoke-test timeout on Windows** — not actually a bug in the code; the Windows runner is slow. Rerun once; if it recurs, bump the test timeout.\n\nStep 1.5 now runs a local compiled-binary smoke test before any user-visible step. If the failure mode above reproduces locally, you've found it. If it doesn't, the bug is platform-specific (Windows cross-compile, Linux glibc, etc.) and you need the CI logs.\n\n### Fix and re-release as the NEXT patch\n\n**Do not reuse `vx.y.z`.** Cut `vx.y.(z+1)` (or next-minor if warranted) with the fix. Rationale:\n- Tag immutability: `vx.y.z` is already recorded in git history and release cache\n- Semver clarity: users and tooling should see a new version number when the bits change\n- Audit trail: \"v0.3.7 was cut but had no shipped binaries; v0.3.8 is the first release with <fix>\" is cleaner than rewriting v0.3.7\n\nSteps:\n\n1. Cut a fix branch off dev, implement the fix, PR to dev, merge.\n2. Re-run `/release` (it will bump to the next patch — e.g. `0.3.8` — automatically).\n3. Step 1.5's pre-flight smoke will catch the same bug locally if the fix didn't actually fix it. Iterate until it passes before tagging.\n\n### CHANGELOG note for the hotfix release\n\nInclude a line in the new release's CHANGELOG that references the broken prior version so users understand why there's no binary artifact under that tag:\n\n```markdown\n### Fixed\n\n- **First release with working compiled binaries after vx.y.z's <bug>.** vx.y.z was tagged but its binary smoke test failed deterministically (see RUN_ID in CI history). The tag is preserved for history; this release (vx.y.(z+1)) is the first with shipped binaries. `install.sh` and Homebrew were never updated to vx.y.z, so users were not exposed to the broken state.\n```\n\n### What NOT to do\n\n- **Do not force-push or rewrite the tag.** Once a tag exists, it's a public promise of that SHA. Deleting and re-creating to a different SHA is tag-spoofing and breaks any downstream that cached the original.\n- **Do not skip this recovery path to \"just push more binaries to the broken release\".** The release exists with a specific commit SHA; uploading binaries built from a newer SHA creates binary/source drift that is hard to diagnose later.\n- **Do not update the Homebrew formula before v0.3.(z+1) is fully shipped.** The formula should always point at a version with all 7 assets uploaded and `/test-release brew` passing.\n\n## Important Rules\n\n- NEVER force push\n- **NEVER derive the changelog from `git log main..dev`.** main squash-merges, so\n  that range accumulates every previously-released commit and grows each release\n  (61 vs 25 actual on 0.7.1). Use dev's last `Release x.y.z` commit as the\n  boundary, and cross-check the result against the previous version's changelog\n  entries — any PR number appearing in both means the boundary is wrong.\n- **ALWAYS pass `--no-rebase` to `git pull`.** The bare form aborts with\n  `Need to specify how to reconcile divergent branches` unless `pull.rebase` is\n  configured, and a `pull.rebase=true` config would rewrite dev's history.\n- **ALWAYS verify sync convergence rather than assuming it.**\n  `git merge-base --is-ancestor origin/main origin/dev` after every sync step.\n  A commit made directly to main stays stranded silently otherwise.\n- **NEVER trust a clean `homebrew/archon.rb` merge.** The CI `update-homebrew`\n  job edits the same `sha256` lines on dev that you edit on main; the merge can\n  succeed and still be wrong. Diff the four values against the published\n  `checksums.txt` before pushing.\n- **Flag a version bump that contradicts the commit range.** If `patch` was\n  requested but the range contains `feat:` commits or new user-facing CLI\n  surface, say so at the Step 7 review and let the user decide — do not silently\n  ship features as a patch.\n- **NEVER skip Step 1.5 (pre-flight compiled-binary smoke).** If the stack is a Bun/Node project with a build-binaries script, the `bun build --compile` smoke test runs before version bump, PR, or tag. Skipping it means every bundler regression or module-init crash only surfaces after the tag is pushed — by which point `releases/latest` is already 404-ing for every user. The ~30s cost is paid to keep the failure mode local.\n- If Step 1.5 fails, **abort the release** and fix the underlying issue on a feature branch. Do not \"just skip it\" and hope CI doesn't repro the problem.\n- NEVER skip the review step — always show the changelog before committing\n- NEVER include \"Co-Authored-By: Claude\" or any AI attribution in the commit\n- NEVER add emoji to changelog entries unless the user asks\n- If the user says \"ship it\" without specifying bump type, default to patch\n- The commit message is just `Release x.y.z` — clean and simple\n- **NEVER update `homebrew/archon.rb` version field without also updating the `sha256` values**. They must move together atomically. The correct SHAs only exist after the release workflow finishes building binaries — see Step 10. Updating the version field alone produces a stale formula that looks valid but causes checksum mismatches on install.\n- **NEVER skip Step 11 (tap sync).** The `coleam00/Archon/homebrew/archon.rb` file is only a template; users install from `coleam00/homebrew-archon/Formula/archon.rb`. If you update one without the other, users get stale or wrong data.\n- **NEVER announce a release that failed `/test-release brew`.** A release that installs but crashes on first invocation is worse than no release — it burns user trust. If the release verification fails, cut a hotfix before telling anyone the release exists.\n",".claude/skills/rulecheck/SKILL.md":"---\nname: rulecheck\ndescription: |\n  Autonomous rule adherence checker. Scans the codebase for rule violations,\n  fixes the highest-impact ones in an isolated worktree, runs full validation,\n  creates a PR. Uses memory to track progress across runs.\ndisable-model-invocation: true\nagent: rulecheck-agent\nargument-hint: \"[focus area]\"\n---\n\n# Rulecheck\n\nLaunch the rulecheck-agent to autonomously scan and fix AGENTS.md rule violations.\n\n## Your Job (Main Agent)\n\nYou are the orchestrator. Your ONLY job is to launch the rulecheck-agent and\nreport its results when it completes. You do NOT do the scanning or fixing yourself.\n\n1. **Launch the rulecheck-agent** with the focus area (if any): `$ARGUMENTS`\n2. **Wait for it to complete** — do NOT poll, tail, or check on it. You will be\n   notified automatically when it finishes.\n3. **Report ONLY what the agent told you** — relay its final message verbatim\n   or summarize it. Do NOT search for PRs, read files, or fabricate results.\n   If the agent hit its context limit, say so. If it didn't produce a PR URL\n   in its output, don't go looking for one.\n\n## Rules for You\n\n- **Do NOT scan the codebase yourself** — that's the agent's job\n- **Do NOT grep, read source files, or run linters** — the agent handles all of that\n- **Do NOT edit any files** — you are the orchestrator, not the fixer\n- **Do NOT try to resume or check on the agent** while it's running — just wait\n- **Do NOT do the agent's work if it fails or hits context limits** — report the failure to the user and stop. NEVER pick up where the agent left off. You are not in a worktree and would be editing main directly.\n- **Do NOT update project memory** — the agent maintains its own memory at `.claude/agent-memory/rulecheck-agent/`. Do not duplicate run results into your project memory.\n- Trust the agent. It runs in an isolated worktree and will create a PR when done.\n",".claude/skills/rulecheck/rules-guide.md":"# Rules Guide — Where to Find Project Rules\n\nReference for the rulecheck agent. Focus on rules that **linters can't enforce** —\narchitectural principles, patterns, and conventions from AGENTS.md.\n\n## Primary Source: `AGENTS.md` (Root)\n\nRead this file thoroughly, but derive checks only from its normative rules —\nsections like `Project Overview` and `Product Direction` are descriptive\ncontext, not something to enforce. Root `CLAUDE.md` is only a one-line pointer\nto this file — reading that instead gets you nothing.\n\n### Engineering Principles (each is a concrete rule, not a slogan)\n\nRead `AGENTS.md` for the repository's engineering rules.\n\nAlso read `.archon/engineering.md` for the aspirational engineering standard.\n",".claude/skills/rulecheck/workshop-guide.md":"# Rulecheck — Workshop Guide\n\n## What This Skill Demonstrates\n\nAn autonomous code quality agent that combines **17 Claude Code features** into one\ngenuinely useful tool: custom agents, safety hooks, all four hook types (command,\nprompt, http, agent), LLM-as-judge, persistent memory, Slack notifications, and\nworktree isolation.\n\nReplaces the existing advisory-only `code-rulecheck` agent with a fully autonomous\nversion that finds violations, fixes them, validates, creates PRs, and learns.\n\n**Full demo steps**: See [part2-guide.md](../../workshop/part2-guide.md) — Feature 8.\n\n## Architecture\n\n```\nUser invokes /rulecheck [focus area]\n        │\n        ▼\n┌──────────────────────────┐\n│  SKILL.md                │  agent: rulecheck-agent\n│  (orchestrator)          │  disable-model-invocation: true\n│                          │  argument-hint: \"[focus area]\"\n│  Launches agent,         │\n│  reports results         │  NO context: fork\n└────────┬─────────────────┘\n         │ delegates to agent\n         ▼\n┌──────────────────────────┐\n│  rulecheck-agent.md     │  isolation: worktree\n│  (.claude/agents/)       │  background: true\n│                          │  memory: project\n│  model: sonnet           │  permissionMode: acceptEdits\n│  maxTurns: 500           │\n│                          │\n│  hooks:                  │\n│  ├─ PreToolUse [Bash]    │──→ block-dangerous.sh (safety gate)\n│  ├─ PostToolUse [Edit]   │──→ bun run lint:fix (auto-fix)\n│  └─ Stop                 │\n│     ├─ command           │──→ slack-notify.sh (reads summary JSON)\n│     ├─ http              │──→ POST to Slack webhook URL\n│     └─ agent             │──→ meta-judge (LLM evaluation)\n└────────┬─────────────────┘\n         │ works in worktree\n         ▼\n┌──────────────────────────┐\n│  Worktree                │\n│  1. Read memory          │\n│  2. Read AGENTS.md rules │\n│  3. Deep scan source     │\n│  4. Group violations     │\n│  5. Fix one group        │\n│  6. Check own changes    │\n│  7. Write summary JSON   │\n│  8. gh pr create         │\n│  9. Update memory        │\n└──────────────────────────┘\n         │\n         ▼\n   Results reported to main conversation\n   + PR created + Slack notified + memory updated\n```\n\n## Features to Walk Through\n\n### 1. `agent: rulecheck-agent` — Custom Agent Delegation (no fork)\n\n**What is it?** The `agent:` field delegates to a custom agent with its own\nsystem prompt, model, hooks, and memory configuration.\n\n**In this skill**: The skill acts as an **orchestrator** — it launches the agent\nand reports results. There is no `context: fork`. The skill stays active in the\nmain conversation while the agent runs in the background in its own worktree.\n\n**Why no fork?** The rulecheck agent uses `background: true` and\n`isolation: worktree`, so it already runs in a completely separate context.\nForking would add an unnecessary layer. The skill just launches and waits.\n\n### 2. `disable-model-invocation: true` — User-Only Trigger\n\n**What is it?** Prevents Claude from auto-invoking this skill. Since it creates\nPRs and pushes branches (side effects), only the user should trigger it.\n\n### 3. `argument-hint: \"[focus area]\"` — Usage Hint\n\n**What is it?** Shows in `/help` and tab completion to tell users what arguments\nthe skill accepts. Optional — the agent works without arguments too.\n\n### 4. `!`command`` — Dynamic Context Injection\n\n**What is it?** Shell commands that execute at skill load time (before Claude sees\nthe prompt). Stdout replaces the placeholder.\n\n**In this skill**: Injects current branch, recent git log, and lint status so the\nagent starts with real context instead of having to fetch it.\n\n### 5. Supporting Files — On-Demand Loading\n\n**What is it?** Markdown files linked from the skill body with `[name](file.md)`.\nClaude loads them only when needed, keeping the initial prompt small.\n\n**In this skill**: `rules-guide.md` documents where to find rules and how to rank\nviolations. The agent loads it during the scan phase.\n\n### 6. `isolation: worktree` — Git Worktree Isolation\n\n**What is it?** The agent works in a temporary git worktree — a separate working\ndirectory with its own branch. Changes are isolated from the main repo.\n\n**In this skill**: The agent edits files and creates commits without affecting the\nuser's working directory. If something goes wrong, the worktree is disposable.\n\n### 7. `background: true` — Concurrent Execution\n\n**What is it?** The agent runs in the background. The user can continue working\nin the main conversation while the agent scans, fixes, and creates a PR.\n\n### 8. `memory: project` — Persistent Memory\n\n**What is it?** The agent has a persistent memory directory at\n`.claude/agent-memory/rulecheck-agent/`. It reads from and writes to `MEMORY.md`\nacross runs.\n\n**In this skill**: The agent remembers what it fixed last time, what's in the\nbacklog, and meta-judge feedback — so each run builds on the previous one.\n\n### 9. `permissionMode: acceptEdits` — Auto-Accept Edits\n\n**What is it?** File edits (Edit/Write tools) are automatically approved without\nuser confirmation. Other tools still require approval per the normal flow.\n\n**In this skill**: The agent needs to edit many files to fix violations. Prompting\nfor each edit would defeat the purpose of autonomous execution.\n\n### 10. `maxTurns: 500` — Safety Cap\n\n**What is it?** Limits the agent to 500 API round-trips. Prevents runaway agents\nthat loop endlessly. Set high here because the agent does a lot of work (scan,\nfix, validate, commit, push, PR, memory update).\n\n### 11. `hooks:` in Agent Frontmatter\n\n**What is it?** Hooks defined directly in the agent's YAML frontmatter. They\nactivate whenever this agent runs, regardless of which skill invokes it.\n\nThis skill demonstrates all four hook types:\n\n#### 11a. `type: \"command\"` — Shell Script Hooks\n\n**PreToolUse [Bash]**: `block-dangerous.sh` reads the command from stdin, checks\nagainst a blocklist (force push, git clean, hard reset, rm -rf), and exits 2 to\nblock dangerous commands.\n\n**PostToolUse [Edit|Write]**: Runs `bun run lint:fix` after every file edit to\nauto-correct formatting issues before they accumulate.\n\n**Stop**: `slack-notify.sh` reads the agent's summary file and sends a formatted\nSlack message with what was fixed, PR link, and remaining opportunities. This\ndemonstrates **inter-hook communication** — the agent writes a JSON file, the\nhook reads it.\n\n#### 11b. `type: \"http\"` — HTTP Webhook\n\n**Stop**: POSTs the event directly to a Slack webhook URL. No script needed —\njust a URL in the frontmatter. This is the simplest way to notify external\nservices. Compare with 11a above: the command hook gives you full control over\nthe message format, while the HTTP hook sends the raw event with zero code.\n\n#### 11c. `type: \"agent\"` — LLM Meta-Judge\n\n**Stop**: A third Stop hook spawns a subagent that evaluates the rulecheck's\nexecution. It reviews what was fixed, assesses prioritization quality, and writes\nstructured feedback to memory for the next run.\n\n### 12. `statusMessage` — Hook Progress Indicators\n\n**What is it?** A string shown in the Claude Code spinner while a hook runs.\nGives users visibility into what's happening during hook execution.\n\n**In this skill**: \"Checking command safety...\", \"Auto-fixing lint issues...\",\n\"Notifying Slack...\", \"Posting run event to Slack...\", \"Running meta-judge evaluation...\"\n\n### 13. `$ARGUMENTS` — Skill Arguments\n\n**What is it?** The text after the skill name (e.g., `/rulecheck error handling`)\nis available as `$ARGUMENTS` in the skill body and passed through to the agent.\n\n### 14. `${CLAUDE_SESSION_ID}` — Session Identifier\n\n**What is it?** Environment variable with the current session ID. Used in the\nmeta-judge prompt to tag feedback with the session that produced it.\n\n### 15. Summary File Pattern — Inter-Hook Communication\n\n**What is it?** The agent writes `.claude/archon/rulecheck-last-run.json` as a\nstructured summary. The Slack hook reads this file to format its notification.\n\nThis is a practical pattern for passing data between the agent and its hooks\nwhen the hook needs more than what's in the event JSON.\n\n## Live Demo Steps\n\n1. **Show the architecture** — open all files:\n   - `.claude/skills/rulecheck/SKILL.md` (skill entry point)\n   - `.claude/agents/rulecheck-agent.md` (the autonomous agent)\n   - `.claude/skills/rulecheck/hooks/block-dangerous.sh` (safety gate)\n   - `.claude/skills/rulecheck/hooks/slack-notify.sh` (Slack notification)\n   - `.claude/skills/rulecheck/rules-guide.md` (supporting reference)\n\n2. **Trace the delegation chain**:\n   skill → `agent: rulecheck-agent` → `isolation: worktree` + `background: true`\n\n3. **Show the hooks**: PreToolUse safety gate, PostToolUse lint auto-fix,\n   Stop Slack (command + http) + meta-judge (agent)\n\n4. **Test the safety hook**:\n   ```bash\n   echo '{\"tool_input\":{\"command\":\"git push --force\"}}' | .claude/skills/rulecheck/hooks/block-dangerous.sh\n   # Should exit 2 with error message\n   echo '{\"tool_input\":{\"command\":\"bun run lint\"}}' | .claude/skills/rulecheck/hooks/block-dangerous.sh\n   # Should exit 0\n   ```\n\n5. **Invoke**: `/rulecheck type safety`\n   - Show: background execution, user can keep working\n   - Show: the agent scanning, fixing, validating in the worktree\n   - Show: PR creation and Slack notification\n\n6. **Show the outputs**:\n   - PR on GitHub\n   - Slack notification\n   - Memory file (what was learned)\n   - Meta-judge feedback\n\n## Comparison: Before vs After\n\n| | Advisory Code-Rulecheck | Autonomous Rulecheck |\n|---|---|---|\n| Format | `.claude/agents/code-rulecheck.md` | Skill + agent + hooks |\n| Execution | Inline, blocks conversation | Background, worktree-isolated |\n| Output | Advisory report (no changes) | Actual fixes + PR |\n| Isolation | None (reads in-place) | Git worktree |\n| Safety | None | PreToolUse command blocklist |\n| Validation | None | Tests, lint, type-check on changes; CI is the full gate |\n| Notifications | None | Slack (command hook + HTTP hook) |\n| Learning | None | Persistent memory + meta-judge |\n| Autonomy | Reports findings only | Finds, fixes, validates, PRs |\n\n## Talking Points\n\n- \"This is 17 features in one skill. Each one is simple — the power is in composition.\"\n- \"The safety hook is a shell script. It reads JSON, checks a blocklist, exits 2 to block. No framework needed.\"\n- \"Memory makes the agent better over time. Each run builds on the last — it remembers what's in the backlog.\"\n- \"The meta-judge is an LLM evaluating another LLM. It writes structured feedback the agent reads next run.\"\n- \"Worktree isolation means the agent can break things safely. Your working directory is untouched.\"\n- \"Background execution means you keep working. The agent runs, creates a PR, notifies Slack — you review when ready.\"\n- \"The old code-rulecheck was advisory. This one actually fixes things. Same domain, fundamentally different capability.\"\n",".claude/skills/test-release/SKILL.md":"---\nname: test-release\ndescription: |\n  Verify a released archon binary works end-to-end via a specific install path.\n  Use when: cutting a new release, reproducing a user bug report on the released\n  version, or validating that a hotfix binary actually works after a re-tag.\n  Triggers: \"test the release\", \"test 0.3.1 via brew\", \"verify the curl install\",\n            \"smoke test the binary\", \"did the release binary work\",\n            \"run /test-release\", \"verify the release\".\n  NOT for: testing dev work (use bun link directly), testing unreleased changes\n  (build locally via scripts/build-binaries.sh first), or running the full\n  validate suite (bun run validate is separate).\nargument-hint: \"[brew|curl-mac|curl-vps] [optional: version to verify] [optional: vps-target]\"\n---\n\n# Test Release\n\nAutomated smoke test for a released archon binary. Covers three install paths:\n\n- `brew` — Homebrew tap on macOS (tests the formula and checksums)\n- `curl-mac` — `curl install.sh` on macOS (tests the install script, sandboxed to a temp dir)\n- `curl-vps` — `curl install.sh` on a remote Linux VPS (tests the Linux binary and full install path)\n\nEvery path installs the binary, runs a fixed smoke test suite, and cleans up. The dev `bun link` binary is never touched and remains the default `archon` on PATH throughout.\n\n**When NOT to use this skill:**\n\n- There is no release yet — build a local binary via `bash scripts/build-binaries.sh` and run it from `dist/binaries/` directly\n- You want to test the dev clone — use `bun run validate` or invoke source directly via `bun packages/cli/src/cli.ts`\n- You want to test the full server + web UI deploy flow — use the cloud-init from `deploy/cloud-init.yml` on a real VPS\n\n## Local build for pre-release QA\n\nTo build a binary locally with the exact same flags and constants that CI uses,\ninvoke `scripts/build-binaries.sh` directly. The script supports two modes:\n\n```bash\n# Multi-target mode (builds all 4 local platforms into dist/binaries/)\nVERSION=0.3.1 GIT_COMMIT=abc12345 bash scripts/build-binaries.sh\n\n# Single-target mode (matches one CI matrix job)\nVERSION=0.3.1 \\\nGIT_COMMIT=abc12345 \\\nTARGET=bun-darwin-arm64 \\\nOUTFILE=dist/test-archon-darwin-arm64 \\\nbash scripts/build-binaries.sh\n\n# Verify the binary — use the path from the mode you built:\n#   multi-target → ./dist/binaries/archon-darwin-arm64\n#   single-target → the OUTFILE you passed above\n./dist/test-archon-darwin-arm64 version\n# Expected: Archon CLI v0.3.1, Build: binary, Git commit: abc12345\n```\n\nRun this **before tagging a release** to catch build-time-constant issues\nlocally. The script is the canonical entry point — both local dev and the\nrelease workflow call it the same way, so a green local build means the CI\nbuild will exercise the same code path.\n\n## Phase 1 — Determine scope\n\nParse the arguments. The skill takes up to three:\n\n1. **Install path** (`brew` | `curl-mac` | `curl-vps`): which install flow to exercise\n2. **Expected version** (optional): the version tag the release should report, e.g. `0.3.1`. If not provided, fetch it:\n\n```bash\ngh release list --repo coleam00/Archon --limit 1 --json tagName --jq '.[0].tagName'\n```\n\n3. **VPS target** (only for `curl-vps`): SSH target in the form `user@host` or `host` (uses default SSH config)\n\nIf any argument is missing, ask the user for clarification BEFORE doing anything. Never guess the install path or the expected version.\n\nConfirm the plan with the user before proceeding to Phase 2. Output should look like:\n\n```\nAbout to test:\n  Path:     brew (Homebrew tap on macOS)\n  Version:  0.3.1 (expected)\n  Cleanup:  will uninstall after tests (brew uninstall + untap)\n            If `archon-stable` symlink is detected in Phase 2, it will be\n            restored at the end of Phase 5 by reinstalling the tap formula.\n\nProceed? (y/N)\n```\n\nDo not continue without explicit confirmation. Release testing touches install state and the user should be aware.\n\n## Phase 2 — Pre-flight\n\nBefore touching anything:\n\n1. Capture the current dev binary state for reference:\n\n```bash\nwhich -a archon\narchon version 2>&1 | head -5\n```\n\nRecord the path and version of the dev binary so the final report can show \"dev binary was untouched\".\n\n2. Verify prerequisites for the chosen path:\n\n   - **brew**: `brew --version` must succeed. If not, abort with \"Homebrew not installed — see https://brew.sh/\"\n   - **curl-mac**: `curl --version` must succeed (effectively always true on macOS)\n   - **curl-vps**: `ssh <target> 'uname -a'` must succeed. If not, abort with \"Cannot SSH to <target>\". Also verify `ssh <target> 'command -v curl'` returns a path.\n\n3. Confirm the release exists on GitHub:\n\n```bash\ngh release view v<version> --repo coleam00/Archon --json tagName,assets --jq '{tag: .tagName, assetCount: (.assets | length)}'\n```\n\nIf the release does not exist or has no assets, abort with a clear message. Do not proceed to install a non-existent release.\n\n4. **Detect persistent `archon-stable` install (brew path only).** If the user has renamed a prior brew install to `archon-stable` (the dual-homebrew pattern — see `~/.config/fish/functions/brew-upgrade-archon.fish`), Phase 5's `brew uninstall` will wipe it. Capture the state so Phase 5b can restore it:\n\n```bash\nARCHON_STABLE_WAS_INSTALLED=\"\"\nif [ -L /opt/homebrew/bin/archon-stable ] || [ -L /usr/local/bin/archon-stable ]; then\n  ARCHON_STABLE_WAS_INSTALLED=\"yes\"\n  echo \"Detected persistent archon-stable — will restore after Phase 5 uninstall.\"\nfi\n```\n\nExport `ARCHON_STABLE_WAS_INSTALLED` into the environment used by Phase 5b. Only applies to the `brew` path — `curl-mac` and `curl-vps` don't go through brew and don't disturb `archon-stable`.\n\n## Phase 3 — Install\n\n### Path: brew\n\n```bash\nbrew tap coleam00/archon\nbrew install coleam00/archon/archon\nBINARY=\"$(brew --prefix coleam00/archon/archon)/bin/archon\"\n```\n\nCapture `$BINARY` for Phase 4. Verify the file exists and is executable.\n\n### Path: curl-mac\n\nInstall to a dedicated tmp directory so the dev `bun link` binary stays on PATH unchanged:\n\n```bash\nINSTALL_DIR=/tmp/archon-test-release-$(date +%s)\nmkdir -p \"$INSTALL_DIR\"\ncurl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | INSTALL_DIR=\"$INSTALL_DIR\" bash\nBINARY=\"$INSTALL_DIR/archon\"\n```\n\n> **The env var must prefix `bash`, not `curl`.** In `VAR=x cmd1 | cmd2` the\n> assignment applies only to `cmd1`, so `INSTALL_DIR=… curl … | bash` sets it for\n> the *download* and leaves the *installer* on its `/usr/local/bin` default. The\n> failure is confusing rather than obvious: the script downloads and verifies the\n> checksum, then demands sudo and dies with\n> `sudo: a terminal is required to read the password`. Observed on the 0.7.1 test.\n>\n> `scripts/install.sh` carries the same broken form in its own header example\n> (`INSTALL_DIR=~/.local/bin curl -fsSL ... | bash`), so users copying it hit this\n> too. Fix both together; see issue #2436.\n\nVerify `$BINARY` exists and is executable. Capture the install directory for cleanup.\n\n### Path: curl-vps\n\nRun the install script on the VPS:\n\n```bash\nssh <target> 'curl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash'\n```\n\nDetermine where the binary landed — `install.sh` uses `/usr/local/bin/archon` by default, or falls back to `$HOME/.local/bin/archon` if `/usr/local/bin` is not writable:\n\n```bash\nssh <target> 'command -v archon'\n```\n\nCapture the remote path as `$REMOTE_BINARY`. For the rest of Phase 4, wrap every command as `ssh <target> '<cmd>'`.\n\n### Capture SHA256 and version\n\nImmediately after install, capture:\n\n```bash\n# Local paths (brew / curl-mac)\nshasum -a 256 \"$BINARY\" | awk '{print $1}'\n\"$BINARY\" version 2>&1\n\n# Remote path (curl-vps)\nssh <target> \"shasum -a 256 $REMOTE_BINARY || sha256sum $REMOTE_BINARY\" | awk '{print $1}'\nssh <target> \"$REMOTE_BINARY version\" 2>&1\n```\n\nRecord both for the report. The SHA256 lets us confirm later that a user reporting a bug is running the exact same artifact we tested.\n\n## Phase 4 — Smoke tests\n\nRun these in order against `$BINARY` (or `ssh <target> $REMOTE_BINARY` for curl-vps). **Always use the full binary path, never the `archon` on PATH**, so there is no ambiguity about which binary is under test.\n\nEach test should capture the full command output for the final report. If a test fails, continue to the next test (so the report is complete) but mark the overall result as FAIL.\n\n### Test 1 — Version reports correctly\n\n```bash\n\"$BINARY\" version\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- Output contains `Archon CLI v<expected-version>`\n- Output contains `Build: binary` (not `Build: source (bun)`)\n- Output contains a non-`unknown` git commit (i.e., `Git commit: <sha>`)\n\n**Common failures:**\n\n- Exit code non-zero → pino-pretty crash (#960) or similar startup failure\n- Wrong version reported → binary is stale or the build script failed to update `bundled-build.ts`\n- `Build: source (bun)` → `BUNDLED_IS_BINARY` was not set to `true` during the build (regression of #979)\n- `Git commit: unknown` → build script did not capture the commit\n\n### Test 2 — Bundled workflows load\n\nFrom 0.12.0, release binaries ship without providers, and a config that names one fails to load until it's installed. Run `\"$BINARY\" provider add claude codex pi` (the ones the config uses) before this test. That is the documented upgrade step, and it is part of what this test checks.\n\nCreate a temporary git repository so the CLI has something to operate on:\n\n```bash\nTESTREPO=/tmp/archon-test-repo-$(date +%s)\nmkdir -p \"$TESTREPO\"\ncd \"$TESTREPO\"\ngit init -q\ngit commit -q --allow-empty -m init\n\"$BINARY\" workflow list\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- Output lists the bundled `sdlc` pack workflows: archon-triage, archon-investigate, archon-plan, archon-implement, archon-pr, archon-review, archon-validate, archon-deliver, archon-upkeep, archon-ship\n- No errors about missing workflow files or JSON parse failures\n\n**Common failures:**\n\n- Empty list → bundled defaults were not embedded in the binary (regression of the `isBinaryBuild` detection path)\n- `Not in a git repository` → working directory handling bug\n- Parse errors → the embedded JSON is corrupt or stale\n\n### Test 3 — SDK path works (archon-investigate workflow)\n\n**Prerequisite.** Compiled binaries require Claude Code installed on the host and a configured binary path. Before running this test, ensure one of:\n\n```bash\n# Option A — env var (easy for ad-hoc testing)\n# After the native installer (Anthropic's default):\nexport CLAUDE_BIN_PATH=\"$HOME/.local/bin/claude\"\n# Or after npm global install:\nexport CLAUDE_BIN_PATH=\"$(npm root -g)/@anthropic-ai/claude-code/cli.js\"\n\n# Option B — config file (persistent)\n#   Add to ~/.archon/config.yaml:\n#   assistants:\n#     claude:\n#       claudeBinaryPath: /absolute/path/to/claude\n```\n\nThen in the same `$TESTREPO`:\n\n```bash\n\"$BINARY\" workflow run archon-investigate --no-worktree \"say hello and nothing else\" 2>&1 | tee /tmp/archon-test-investigate.log\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- The Claude subprocess spawns successfully (no `spawn EACCES`, `ENOENT`, or `process exited with code 1` in the early output)\n- No `Claude Code CLI not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)\n- A response is produced (any response — even just \"hello\" — proves the SDK round-trip works)\n\n**Common failures:**\n\n- `Claude Code not found` → `CLAUDE_BIN_PATH` / `claudeBinaryPath` is unset or points at a non-existent file. Fix the path and re-run.\n- `Module not found \"/Users/runner/...\"` → regression of #1210: the resolver was bypassed and the SDK's `import.meta.url` fallback leaked a build-host path. Investigate `packages/providers/src/claude/provider.ts` and the resolver.\n- `Credit balance is too low` → auth is pointing at an exhausted API key (check `CLAUDE_USE_GLOBAL_AUTH` and `~/.archon/.env`)\n- `unable to determine transport target for \"pino-pretty\"` → #960 regression, binary crashes on TTY\n- `package.json not found (bad installation?)` → #961 regression, `isBinaryBuild` detection broken\n- Process exits before producing output → generic spawn failure, capture stderr\n\n### Test 3b — Resolver error path (run without `CLAUDE_BIN_PATH`)\n\nQuickly verify the resolver fails loud when nothing is configured:\n\n```bash\n(unset CLAUDE_BIN_PATH; \"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" 2>&1 | tee /tmp/archon-test-no-path.log)\n```\n\n**Pass criteria (when no `~/.archon/config.yaml` configures `claudeBinaryPath`):**\n\n- Error message contains `Claude Code not found`\n- Error message mentions both `CLAUDE_BIN_PATH` and `claudeBinaryPath` as remediation options\n- No `Module not found` stack traces referencing the CI filesystem\n\nIf you *do* have `claudeBinaryPath` set globally, skip this test or temporarily rename `~/.archon/config.yaml`.\n\n### Test 4 — Env-leak guard strips sensitive keys from .env\n\nCreate a second throwaway repo with a fake sensitive key:\n\n```bash\nLEAKREPO=/tmp/archon-test-leak-$(date +%s)\nmkdir -p \"$LEAKREPO\"\ncd \"$LEAKREPO\"\ngit init -q\ngit commit -q --allow-empty -m init\nprintf 'ANTHROPIC_API_KEY=sk-ant-test-fake\\n' > .env\n\"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" 2>&1 | tee /tmp/archon-test-leak.log\n```\n\n**Pass criteria** (current behaviour — the guard **strips**, it does not refuse):\n\n- Output contains a strip line naming the repo and the source file, of the form:\n  `[archon] stripped N keys from <repo> (.env) to prevent target repo env from leaking into Archon processes`\n- `N` equals the number of keys planted (1 for the `.env` above) — a count of `0`\n  means the file was not read at all, which is a real regression\n- The workflow is then allowed to proceed and complete normally\n\nAssert on the strip line — pinned to **this** repo and **this** key count, so a\nstrip from some other directory cannot produce a false pass. Capture the exit\nstatus too: under the strip design the workflow is expected to succeed, so a\nnon-zero exit is its own failure.\n\n```bash\n\"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" > /tmp/archon-test-leak.log 2>&1\nleak_exit=$?\n\n# $LEAKREPO may be a symlinked path (/tmp -> /private/tmp on macOS); the binary\n# logs the resolved form, so compare against that.\nresolved_repo=$(cd \"$LEAKREPO\" && pwd -P)\nexpected=\"[archon] stripped 1 keys from ${resolved_repo} (.env) to prevent target repo env from leaking into Archon processes\"\n\nif [ \"$leak_exit\" -ne 0 ]; then\n  echo \"FAIL: workflow exited $leak_exit — the guard strips and proceeds, it should not abort\"\nelif grep -qxF \"$expected\" /tmp/archon-test-leak.log; then\n  echo \"PASS: env-leak guard stripped exactly the planted key from this repo\"\nelse\n  echo \"FAIL: expected strip line absent. Got:\"\n  grep -F '[archon] stripped' /tmp/archon-test-leak.log || echo \"  (no strip line at all — guard inactive)\"\nfi\n```\n\nAn exact-match assertion is deliberate here. A looser pattern such as\n`stripped [1-9][0-9]* keys .*\\.env` passes on **any** positive count from **any**\npath, so a strip belonging to a different repository — or a count inflated by an\nunrelated `.env` — reads as success while the regression it is meant to catch\ngoes unnoticed.\n\n> **History — do not re-assert the old criteria.** This test previously expected a\n> *refusal* (`Cannot add codebase` / `Cannot run workflow`, non-zero exit), which\n> was the #1036/#1038/#983 behaviour. The current design is the two-layer strip\n> guard: the target repo's `.env` is read, dangerous keys are removed from the\n> environment handed to Archon subprocesses, and the run continues. On the 0.7.1\n> test the old assertions reported FAIL against a binary whose guard was working\n> correctly — it stripped exactly the 1 planted key. Verify the security property\n> (the key never reaches the subprocess), not the obsolete remediation text.\n\n**Common failures:**\n\n- No strip line at all → the guard is not active; the `.env` is reaching subprocesses\n- `stripped 0 keys` → the file was located but not parsed\n- Strip line names the wrong file or repo → path resolution regression\n\nClean up the leak test repo:\n\n```bash\nrm -rf \"$LEAKREPO\"\n```\n\n### Test 5 — Isolation list works (sanity check)\n\nIn the same `$TESTREPO`:\n\n```bash\n\"$BINARY\" isolation list\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- No errors (the list may be empty if no worktrees have been created, which is fine)\n\nThis catches regressions in the isolation subsystem that would not surface from the other tests.\n\n### Test 6 — Cleanup test repos\n\n```bash\nrm -rf \"$TESTREPO\"\n```\n\nFor `curl-vps` path, also clean up any remote test repos created via SSH.\n\n## Phase 5 — Uninstall\n\n**Always run uninstall, even if Phase 4 failed.** The goal is to leave the system in the same state as before the test.\n\n### Path: brew\n\n```bash\nbrew uninstall coleam00/archon/archon\nbrew untap coleam00/archon\n```\n\nVerify the dev binary is still the default:\n\n```bash\nwhich -a archon\n# should show only the ~/.bun/bin/archon path, not a brew path\n\narchon version | head -1\n# should match the dev version captured in Phase 2\n```\n\n**Restore `archon-stable` if it existed before the test** (dual-homebrew pattern — see Phase 2 item 4):\n\n```bash\nif [ -n \"$ARCHON_STABLE_WAS_INSTALLED\" ]; then\n  echo \"Restoring archon-stable (detected before test)...\"\n  brew tap coleam00/archon\n  brew install coleam00/archon/archon\n  BREW_BIN=\"$(brew --prefix)/bin\"\n  if [ -e \"$BREW_BIN/archon\" ]; then\n    mv \"$BREW_BIN/archon\" \"$BREW_BIN/archon-stable\"\n    echo \"archon-stable restored: $(archon-stable version 2>/dev/null | head -1)\"\n  else\n    echo \"WARNING: brew install succeeded but $BREW_BIN/archon missing — check formula\"\n  fi\nfi\n```\n\n> **Note on the restored version**: this reinstalls from whatever the tap currently ships, which is typically the release you just tested (so `archon-stable` ends up at the newly-tested version). That's usually what the operator wants — you just verified the new release works, and you want `archon-stable` pointed at it. If you were testing an older version for back-version QA, the restored `archon-stable` will be the *current* tap formula, not the pre-test version. For that rare case, the operator should re-run `brew-upgrade-archon` manually after the test.\n\n### Path: curl-mac\n\n```bash\nrm -rf \"$INSTALL_DIR\"\n```\n\n### Path: curl-vps\n\n```bash\nssh <target> \"sudo rm -f /usr/local/bin/archon || rm -f \\$HOME/.local/bin/archon\"\n```\n\nOptional: the user may want to LEAVE the VPS binary installed for ongoing QA. Ask before removing.\n\n## Phase 6 — Report\n\nProduce a structured report with:\n\n- **Header**: release version tested, install path, timestamp, SHA256 of the tested binary\n- **Environment**: dev binary path + version (proof the dev install was not disturbed)\n- **Test results table**: one row per test with PASS / FAIL / SKIP\n- **Captured output**: for any FAIL, include the exact command, exit code, and last 20 lines of stderr/stdout\n- **Overall verdict**: PASS if all tests passed, FAIL if any test failed\n- **Next steps**: if FAIL, suggest concrete actions (file a hotfix issue, re-tag, check the build workflow, etc.)\n\nExample PASS report:\n\n```\nTest Release Report — archon v0.3.1 via brew\n────────────────────────────────────────────\nTested at:    2026-04-08 15:42 UTC\nBinary SHA:   e62eb73547b3740d56f242859b434a91d3830360a0d18f14de383da0fd7a0be6\nBinary path:  /opt/homebrew/Cellar/archon/0.3.1/bin/archon\nDev binary:   /Users/rasmus/.bun/bin/archon → ../install/.../cli.ts (unchanged)\n\n  [PASS]  Test 1  version reports 0.3.1, Build: binary, commit abc1234\n  [PASS]  Test 2  workflow list returned 21 bundled workflows\n  [PASS]  Test 3  workflow run archon-investigate produced output\n  [PASS]  Test 4  env-leak guard stripped exactly the planted key\n  [PASS]  Test 5  isolation list executed without errors\n  [PASS]  Cleanup brew uninstall + untap clean, dev binary unchanged\n\nOverall: PASS\n\nThis release is safe to announce. Next steps:\n  - Update release notes on GitHub if not done already\n  - Announce on whatever channels you use\n```\n\nExample FAIL report:\n\n```\nTest Release Report — archon v0.3.1 via curl-vps\n────────────────────────────────────────────────\nTested at:    2026-04-08 15:42 UTC\nBinary SHA:   0cf83e15e6af228e3c3473467ca30fa7525b6d7069818d85f97a115ea703d708\nBinary path:  user@vps:/usr/local/bin/archon\nDev binary:   /Users/rasmus/.bun/bin/archon (unchanged)\n\n  [PASS]  Test 1  version reports 0.3.1, Build: binary\n  [FAIL]  Test 2  workflow list returned 0 workflows\n\n    Command:  archon workflow list\n    Exit:     0\n    Output:\n      Discovering workflows in: /tmp/archon-test-repo-1712590923\n      Found 0 workflow(s):\n\n  [SKIP]  Test 3  SDK test skipped because Test 2 failed\n  [SKIP]  Test 4  env-leak guard test skipped because Test 2 failed\n  [PASS]  Test 5  isolation list executed without errors\n  [PASS]  Cleanup VPS binary removed\n\nOverall: FAIL\n\nLikely cause: bundled workflows were not embedded in the binary.\nCheck the build workflow for missing asset embedding, or verify that\nBUNDLED_WORKFLOWS in packages/workflows/src/defaults/bundled-defaults.ts\nwas populated at build time.\n\nNext steps:\n  1. File a P0 hotfix issue with the captured output\n  2. Do NOT announce v0.3.1 until the hotfix ships as v0.3.2\n  3. Consider adding a CI guard that blocks releases if BUNDLED_WORKFLOWS is empty\n```\n\n## Key behaviors\n\n- **Never touch the dev `bun link` binary.** Always use the installed binary path for Phase 4 tests. Verify before and after.\n- **Clean up on failure.** If Phase 4 fails mid-way, still run Phase 5 so the next run starts clean.\n- **Capture SHA256 immediately after install.** This lets bug reports reference the exact artifact under test.\n- **Explicit confirmation before install.** Never surprise the user by installing a second binary.\n- **Report the dev binary state in both preamble and postamble.** Proof that the test did not disturb the dev environment.\n- **Exit non-zero if any test failed.** The skill should propagate failure so automated wrappers (CI, scripts) can detect it.\n\n## Related\n\n- `scripts/build-binaries.sh` — builds the binary artifacts that end up in releases\n- `.github/workflows/release.yml` — builds and publishes the binary on tag push\n- `homebrew/archon.rb` — Homebrew tap formula (updated per release)\n- `scripts/install.sh` — the curl install script\n- `scripts/install-local.sh` / `install-local.ps1` — local-file install harnesses (for pre-release QA of binaries built from a branch, not from GitHub releases)\n- `/release` skill — the release procedure itself (opposite side of the flow)\n",".claude/skills/triage/SKILL.md":"---\nname: triage\ndescription: |\n  Triage GitHub issues by applying type, effort, priority, and area labels.\n  Runs in an isolated context to avoid polluting the main conversation with\n  issue details. Delegates to a specialized triage agent with label validation hooks.\nargument-hint: \"[unlabeled|all|N|N-M]\"\ndisable-model-invocation: true\ncontext: fork\nagent: triage-agent\nallowed-tools: Bash(gh *), Read, Glob, Grep\n---\n\n# Triage GitHub Issues\n\nTriage issues for this repository by applying appropriate labels.\n\n## Repository Context\n\n- **Current repo**: !`gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo \"unknown\"`\n- **Open issues**: !`gh issue list --state open --json number --jq 'length' 2>/dev/null || echo \"?\"`\n- **Existing labels**: !`gh label list --json name -q '.[].name' 2>/dev/null | head -20 || echo \"none found\"`\n\n---\n\n## Scope\n\nDetermine which issues to triage based on the arguments: **$ARGUMENTS**\n\n| Argument | Behavior |\n|----------|----------|\n| (empty) | Only unlabeled issues (default) |\n| `unlabeled` | Only issues without any labels |\n| `all` | All open issues |\n| `N` | Specific issue (e.g., `67`) |\n| `N-M` | Range of issues inclusive (e.g., `60-67`) |\n\n## Process\n\n1. **Fetch available labels** — run `gh label list --json name,description` to understand\n   the label taxonomy. Labels are organized into type, effort, priority, and area categories.\n\n2. **Fetch target issues** — based on the scope above. For each issue, fetch the full body:\n   ```bash\n   gh issue view {number} --json number,title,body,labels\n   ```\n\n3. **For each issue**:\n   - Read the title and full body carefully\n   - If needed, explore the codebase (`Glob`, `Grep`, `Read`) to understand the affected code\n   - Classify: one type, one effort, one priority, one or more areas\n   - Track relationships with other issues (duplicates, related, blocking)\n   - Apply labels:\n     ```bash\n     gh issue edit {number} --add-label \"type,effort/level,P#,area.domain\"\n     ```\n   - Skip issues that already have complete labeling (type + effort + priority + area)\n   - For partially labeled issues, only add missing label categories\n\n4. **Output a triage summary**:\n\n   ```\n   ## Triage Summary\n\n   | Issue | Title | Labels Applied | Reasoning |\n   |-------|-------|----------------|-----------|\n   | #67 | ... | bug, effort/low, P1, core.config | ... |\n\n   **Totals:**\n   - Issues triaged: X\n   - Already labeled (skipped): Y\n   - By priority: P0(n), P1(n), P2(n), P3(n)\n\n   ## Relationships Discovered\n\n   | Issues | Relationship | Notes |\n   |--------|--------------|-------|\n   | #61, #62 | Related | Both involve config/logging UX |\n   ```\n\n## Rules\n\n- **Don't hardcode labels** — always fetch current labels first, they may change\n- **Respect existing labels** — don't remove labels, only add missing ones\n- **Check issue body** — titles alone aren't enough context\n- **Use the codebase** — if understanding a relationship requires seeing how modules connect, look\n- **When uncertain, note it** — flag ambiguous issues in the summary rather than guessing\n",".claude/skills/triage/workshop-guide.md":"# Triage — Workshop Guide\n\n## What This Skill Demonstrates\n\nA command upgraded to a skill + custom agent pair, showcasing **context forking**,\n**custom agent delegation**, **prompt hooks as guardrails**, and **restricted toolsets**.\n\n**Full demo steps**: See [part2-guide.md](../../workshop/part2-guide.md) — Feature 7.\n\n## Architecture\n\n```\nUser invokes /triage 42\n        │\n        ▼\n┌─────────────────────┐\n│  SKILL.md           │  context: fork + agent: triage-agent\n│  (entry point)      │  Injects repo context via !`command`\n│  allowed-tools:     │  Restricts tools to gh CLI + read-only\n│    Bash(gh *)       │\n│    Read, Glob, Grep │\n└────────┬────────────┘\n         │ forks into isolated context\n         ▼\n┌─────────────────────┐\n│  triage-agent.md    │  Custom agent with PostToolUse hook\n│  (.claude/agents/)  │  Hook: type \"prompt\" validates every\n│                     │  gh issue edit command to ensure\n│  model: sonnet      │  all 4 label categories are present\n│  hooks: PostToolUse │\n└─────────────────────┘\n         │\n         ▼\n   Summary returned to main conversation\n   (intermediate tool calls discarded)\n```\n\n## Features to Walk Through\n\n### 1. `context: fork` — Isolated Execution\n\n**What is it?** A skill frontmatter field that runs the skill in a **new isolated\nsubagent context** instead of inline in the main conversation. The skill body\nbecomes the task prompt for the subagent. When the subagent finishes, only its\nfinal response returns to the main conversation — all intermediate tool calls,\nfile reads, and reasoning are discarded.\n\n**Key properties:**\n- The forked context has **no access** to the main conversation history\n- Only the **final summary** flows back — intermediate work is discarded\n- `CLAUDE.md` files are still loaded in the forked context; this project's root file is only a pointer to `AGENTS.md`, which holds the project rules\n- The fork cannot spawn further subagents (no nesting)\n- Forked skills must contain **concrete tasks**, not just reference material\n\n**In this skill**: Triaging 20 issues could consume 50K+ tokens of context\n(issue bodies, label lists, grep results). Without forking, all of that pollutes\nthe main conversation. With forking, only the structured triage summary returns.\n\n### 2. `agent: triage-agent` — Custom Agent Delegation\n\n**What is it?** When a skill has `context: fork`, the `agent:` field controls\nwhich agent type runs the forked context. Options:\n\n| Agent | Model | Tools | Use case |\n|-------|-------|-------|----------|\n| `Explore` | Haiku (fast, cheap) | Read-only | File search, codebase exploration |\n| `Plan` | Inherits from main | Read-only | Planning and research |\n| `general-purpose` | Inherits from main | All tools | Complex multi-step tasks |\n| Any custom agent | Whatever agent defines | Whatever agent defines | Domain-specific work |\n\nCustom agents are `.md` files in `.claude/agents/` with their own frontmatter\n(model, tools, hooks, memory, permissions) and a markdown body that serves as\nthe system prompt.\n\n**In this skill**: Instead of `agent: general-purpose`, this skill delegates to\n`triage-agent` — a custom agent defined in `.claude/agents/triage-agent.md` with:\n- A triage specialist system prompt (label taxonomy, classification rules)\n- Its own PostToolUse hook for label validation\n- Restricted tool access\n\n**Teaching moment**: Skills define *what* to do (scope, context, arguments).\nAgents define *how* to do it (persona, tools, guardrails). Separating them\nmakes both composable — the same agent could be used by multiple skills.\n\n### 3. `type: \"prompt\"` Hook — LLM as Guardrail\n\n**What is it?** One of four hook handler types. A prompt hook sends the event\ncontext to an LLM (Haiku by default) for a single-turn yes/no evaluation.\nThe LLM must return `{\"ok\": true}` to allow the action or\n`{\"ok\": false, \"reason\": \"...\"}` to block it.\n\n**How it differs from the other hook types:**\n- `type: \"command\"` — runs a shell script, blocks via exit code 2\n- `type: \"http\"` — POSTs JSON to a URL, blocks via JSON response\n- `type: \"prompt\"` — asks an LLM to evaluate, blocks via `{\"ok\": false}`\n- `type: \"agent\"` — spawns a subagent with Read/Grep/Glob to verify conditions\n\nPrompt hooks are ideal for **semantic validation** — cases where the decision\nrequires understanding intent, not just pattern matching.\n\n**In this skill**: The triage agent has a PostToolUse hook on Bash. When it\ndetects a `gh issue edit --add-label` command, the LLM verifies all four label\ncategories (type, effort, priority, area) are present.\n\n**What happens if validation fails**: The LLM returns\n`{\"ok\": false, \"reason\": \"Missing effort label\"}` and Claude receives that\nfeedback. It can fix the label application before moving to the next issue.\n\n**Smart filtering**: The prompt instructs the LLM to return `{\"ok\": true}`\nfor non-label commands (`gh issue list`, `gh label list`) — no false positives.\n\n### 4. `allowed-tools` — Restricted Toolset\n\n**What is it?** A skill frontmatter field that controls which tools Claude can\nuse **without per-use permission prompts** when the skill is active. It acts as\nboth an allowlist (pre-approves listed tools) and a restriction (only these\ntools are available in the forked context).\n\n**Wildcard patterns** are supported:\n- `Bash(gh *)` — only `gh` subcommands, no arbitrary shell\n- `Bash(npm *)` — only npm commands\n- `Read, Glob, Grep` — read-only codebase access\n\n**In this skill**: `allowed-tools: Bash(gh *), Read, Glob, Grep` means the\nagent can interact with GitHub and read the codebase, but can't run arbitrary\nshell commands, write files, or edit code. This is a security boundary.\n\n### 5. `disable-model-invocation: true`\n\n**What is it?** Prevents Claude from auto-invoking this skill. By default, Claude\nreads skill descriptions and can invoke them when it determines they're relevant.\nSetting this to `true` means only the user can trigger it via `/triage`.\n\n**In this skill**: Triage modifies GitHub issues (side effects). Only the user\nshould decide when to apply labels.\n\n### 6. Dynamic Context Injection (`!`command``)\n\n**What is it?** Shell commands in the skill body using `!`command`` syntax that\nexecute as preprocessing — before Claude sees the prompt. The command's stdout\nreplaces the placeholder. This is not tool use; it happens at skill load time.\n\n**In this skill**: Three commands inject live repo data:\n- Repository name (`gh repo view`)\n- Open issue count (`gh issue list | length`)\n- Existing label taxonomy (`gh label list`)\n\nThe agent starts with real context instead of instructions to go fetch it.\n\n## Live Demo Steps\n\n1. **Show the architecture** — open both files side by side:\n   - `.claude/skills/triage/SKILL.md` (the skill entry point)\n   - `.claude/agents/triage-agent.md` (the specialist agent)\n\n2. **Point out the delegation chain**: skill → `context: fork` → `agent: triage-agent`\n\n3. **Invoke on a single issue**: `/triage 42`\n   - Show the `statusMessage` spinner: \"Validating label application...\"\n   - Point out: the main conversation stays clean while triage happens in the fork\n\n4. **Show the summary** that returns to the main conversation\n   - All the intermediate work (fetching issues, reading code, applying labels) is gone\n   - Only the structured summary survives\n\n5. **Show the hook validation** — if a label application was incomplete, the prompt\n   hook would have caught it and Claude would have self-corrected\n\n## Comparison: Before vs After\n\n| | Old Command | New Skill + Agent |\n|---|---|---|\n| Format | `.claude/commands/archon/triage.md` | Skill + `.claude/agents/triage-agent.md` |\n| Execution | Inline (pollutes main context) | Forked (isolated context window) |\n| Validation | None | Prompt hook validates every label application |\n| Tool access | All Bash commands | `Bash(gh *)` only — no arbitrary shell |\n| Agent persona | Generic | Specialized triage agent with domain knowledge |\n| Context cost | All issue data stays in conversation | Only summary returns |\n\n## Talking Points\n\n- \"Context forking is about information hygiene — the triage work happens in a disposable context.\"\n- \"Custom agents let you embed domain expertise. The triage rules live in the agent, the scope lives in the skill.\"\n- \"The prompt hook is an LLM guardrail — it catches incomplete label applications before they reach GitHub.\"\n- \"`Bash(gh *)` is a security boundary — the agent can interact with GitHub but can't run arbitrary commands.\"\n- \"This pattern — skill as entry point, custom agent as specialist — is how you build composable workflows.\"\n","AGENTS.md":"# Working on Archon\n\nArchon is a self-hostable, governed agentic automation engine. It runs workflows that mix deterministic steps, AI agents, human gates, and audit trails. Coding automation is its most mature surface, but the engine is intended for general business operations too.\n\nThis file is the canonical project guidance for coding agents. Keep it short, durable, and about judgment. Put changing product direction in [`.archon/direction.md`](.archon/direction.md), workflow-language design in [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md), aspirational engineering craft and review values in [`.archon/engineering.md`](.archon/engineering.md), user documentation in the docs site, and machine-checkable rules in code, types, tests, lint, or CI.\n\n## Read before changing code\n\n- Treat the source, its call sites, and its tests as the current evidence. The request defines the goal and constraints, but a prompt may describe old code.\n- Read every relevant consumer before changing a producer, contract, path, identifier, schema, or generated artifact.\n- Reproduce bugs when practical. If reproduction is impossible, establish another concrete causal chain before editing.\n- Do not trust a passing test by itself. Check that it exercises the behavior it claims to protect.\n- A question is read-only. Explain first; change code only when the user asks for a change.\n- Do not stop at editing. Finish the requested change and verify the outcome.\n- If the implementation keeps getting more complicated, stop and reconsider the model. Solve the underlying problem.\n\n## Product boundaries\n\n- Archon is a governed automation engine, not a replacement for the coding agents it orchestrates.\n- One install serves one operator or client. Client isolation belongs at the deployment layer. Multiple users within one install are supported and are not multi-tenancy.\n- Keep the conversation layer platform-agnostic. Platform adapters translate transport details; core behavior should not depend on Slack, Telegram, GitHub, Discord, CLI, or Web UI semantics.\n- Keep the core self-hostable. Do not make a proprietary hosted service a runtime dependency.\n- Keep the core small and flexible. Agentic engineering changes too quickly for one monolith to encode every useful workflow or integration; give users stable seams to assemble and extend the rest.\n- Treat bundled workflows as reference implementations, not a frozen compatibility surface.\n- A git worktree separates checkouts; it is not an execution or security sandbox.\n- Consult [`.archon/direction.md`](.archon/direction.md) before making product-scope or architecture-direction decisions.\n\n## Engineering taste\n\n### Prefer the smallest truthful system\n\n- Apply KISS and YAGNI. Do not add configuration, interfaces, lifecycle states, compatibility paths, or guards without a current requirement or concrete failure mode.\n- Before adding machinery, look for dead, redundant, or superseded machinery on the same path that can disappear.\n- Fix drift on the path you touch rather than preserving it for a cleaner diff. Two sites that should agree and do not are a defect, not a diff-size decision.\n- Duplicate small local logic when it keeps ownership clear. Extract only after a pattern has repeated and stabilized.\n- Prefer explicit control flow and narrow typed interfaces over meta-programming, shared mutable state, or hidden coupling.\n- Keep policy, transport, persistence, and execution concerns separate.\n- Consider a focused rewrite when it produces a smaller, clearer result without widening risk.\n\n### Let types carry invariants\n\n- Keep TypeScript strict. Avoid `any` and broad assertions when a sound type can express the state.\n- Two declarations that must agree to stay correct, kept in agreement by discipline rather than by a mechanism, are a present defect and not a future risk. Treat a hand-synced pair as a bug the moment it exists, and as a live one once the sides already disagree. Derive from the owner instead: an import, a derived or mapped type, a generated artifact with an owning script, or an enforced conformance test. A \"keep in sync\" comment is none of those; it records the defect rather than clearing it.\n- Import external SDK types rather than restating them. Where a boundary genuinely cannot share one definition, the copy owes a conformance test.\n- Use discriminated unions and constructors to make invalid states hard to represent.\n- Keep interfaces narrow. Extend an existing interface only when the new method belongs to the same responsibility.\n\n### Fail clearly\n\n- Fail early on unsupported, ambiguous, or unsafe states. Silent fallback in an agent runtime can waste money or broaden capabilities.\n- Never silently swallow an error or broaden permissions.\n- Translate errors at the boundary that can explain them, while preserving the original evidence for logs and diagnostics.\n- A fallback must be intentional, safe, documented near the branch, and observable enough to debug.\n- Never log credentials, secret values, user-message contents, or unnecessary personal data.\n\n### Write for the next maintainer\n\n- Comments should explain a non-obvious invariant, ownership decision, or reason a tempting alternative is wrong. Do not narrate the code.\n- Delete stale comments and documentation when behavior changes.\n- Use one precise name for each concept. Prefer direct prose, active voice, and concrete mechanisms over slogans or vague summaries.\n- Avoid hardcoded examples that will become false as files, counts, providers, or routes change.\n\n## Agentic-system boundaries\n\n### Natural language is not a wire format\n\n- Let the agent interpret the full natural-language message. Do not reconstruct intent with regexes, keyword lists, exact prose tokens, or hand-written parsers.\n- Put determinism after interpretation: give the agent typed tools, then validate resolved identifiers, arguments, permissions, and invariants at the tool boundary.\n- If exact syntax is required, expose a structured interface such as a CLI flag, typed tool, button action, or schema.\n- In a workflow, prose inside a node is model reasoning. Prose passed between nodes as a token to parse is an invented protocol; use structured output, typed inputs, exit status, or another explicit engine channel.\n- Vendor output is prose too. Classifying git, SDK, or vendor error text with a pattern is a last resort, not a carve-out: prefer the structured channel (exit code, typed error class, `--json` or porcelain output), then agent classification into a typed value, then an honest unclassified failure carrying the original evidence. A match that survives anchors to a machine token embedded in the message (an errno, an error code) and may enrich a report — never gate retry, fallback, or suppression, where a vendor rewording silently changes behavior.\n\n### Do not guess lifecycle ownership\n\n- A process must not mark non-terminal work failed, cancelled, or abandoned when it cannot distinguish a live owner from an orphaned process.\n- Surface ambiguous ownership and give the operator an explicit action.\n- Timers remain appropriate for recoverable operations such as retry backoff, subprocess timeouts, and cleanup of already-terminal data.\n- Terminal state transitions and completion signals belong to the engine, not to workflow-specific scripts that reinterpret run state.\n\n### Project guidance should be available, not sprayed everywhere\n\n- This `AGENTS.md` is the project-guidance channel for agent nodes. Provider integrations should preserve that contract through the provider's native context mechanism.\n- `AGENTS.md` may point to focused project context such as direction or language-design documents.\n- Do not inject every project document into every node. A node should receive only the context its responsibility requires.\n- The engine and reusable workflow packs must not hardcode a project's context paths.\n\n### Preserve provider configuration; scope capabilities\n\n- Provider integrations must preserve the provider's native user and project configuration, including its global and project guidance files. The user's provider configuration owns whether those sources load; Archon must not replace a provider home directory or add a parallel context switch.\n- Workflow AI nodes start without ambient skills, plugins, or MCP servers. A node receives only the skills, plugins, and MCP servers it explicitly names. Enforce that boundary without hiding unrelated provider settings.\n- Pi extensions are user-owned provider configuration, not Archon-managed plugins. Load them as Pi configures them; any tools or MCP clients an extension exposes are the user's responsibility.\n- Leave native prompt templates and commands under provider control unless the provider automatically advertises them to the model as a skill-like capability. In that case they follow the same explicit node-scoping rule as skills and plugins.\n- Preserve the selected agent provider's native authentication contract. A Claude Code subscription runs through the Claude Agent SDK; using an Anthropic model through another provider is not Claude Code subscription support. Do not reroute or relabel provider identity to reuse credentials.\n- Provider-boundary tests must prove both halves of the contract: native settings and guidance remain visible, while undeclared skills, plugins, and MCP servers remain unavailable and explicitly named capabilities load.\n\n## Workflow-language boundary\n\nThe governing rule is: **YAML coordinates. Code computes. Agents judge.** Read [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md) before changing the YAML surface. Read [`.archon/workflows/sdlc/CONSTITUTION.md`](.archon/workflows/sdlc/CONSTITUTION.md) before changing the SDLC pack.\n\n- YAML should expose only what the engine must see to govern a run: topology, gates, joins, retries, sessions, artifacts, durable waits, and reusable structure.\n- Computation belongs inside a `bash:`, `script:`, or `prompt:` node. Choosing a script instead of a prompt is an ordinary reliability and maintainability decision, not a constitutional rule.\n- A new YAML field or expression feature must add governance value that existing node bodies and wiring cannot provide.\n- Do not grow `when:` one operator at a time. Compute a complex decision in a node and gate on structured output.\n- Composition must preserve a graph the engine can validate and govern. Runtime-created independent work belongs in a child run, not hidden dynamic YAML structure.\n- Provider capabilities normally belong in provider config, tiers, or aliases. A new node field must earn genuine per-node variance.\n- Implicit behavior must be documented, defeatable where appropriate, visible, and fail-safe.\n\n## Architecture and ownership\n\n- Preserve package boundaries. Inspect package manifests and current imports before adding a dependency; do not rely on a copied package inventory.\n- Provider SDK dependencies and SDK-specific option translation belong in `@archon/providers`.\n- Workflow execution depends on injected contracts, not on core database or adapter implementations.\n- Platform-specific parsing and authorization belong inside the owning adapter. Core receives normalized intent and identity.\n- The Web client consumes generated API types through its local API layer; it does not import server or workflow runtime packages.\n- The engine (`packages/`) and the bundled SDLC pack (`.archon/workflows`, `.archon/scripts`) don't import each other's code, in source or in tests. They meet through the workflow language, the CLI and the provider and forge contracts.\n- Shared schemas are the source of truth for persisted and API shapes. Keep OpenAPI generation and runtime validation on the same schema path.\n- Use the type system, schema validation, and package boundaries to enforce architecture before adding prose rules.\n\n## Data and compatibility\n\n- Database schema evolution is additive-only. There is no migration ledger or version gate, and older binaries may open the same database.\n- Never rename, retype, or drop a shipped table or column. A new `NOT NULL` column needs a default that remains valid for older writers.\n- Keep SQLite and PostgreSQL schemas aligned unless a narrow, documented exception is already enforced.\n- Test upgrades from shipped schema shapes, not only fresh database creation.\n- Keep generated or mirrored artifacts synchronized through their owning scripts. Find those scripts in `package.json` and adjacent tests instead of copying a list here.\n- Persisted identifiers, paths, and wire values are contracts. Trace them through every reader before changing their meaning.\n\n## Git and workspace safety\n\n- `main` is the release branch and `dev` is the working base. Never commit directly to either for feature work, and never force-push `main` or `master`.\n- Preserve user changes, including untracked files. Do not use `git clean -fd`, destructive resets, or broad cleanup commands on user-owned workspaces.\n- Let git report conflicts and dirty state. Do not hide or automatically resolve a state the operator owns.\n- Hard reset is allowed only for an explicit reset mode on an Archon-owned checkout.\n- Run artifacts, logs, and state belong outside the repository under the Archon workspace. Do not stage them.\n- When stopping a process, target the recorded PID or exact bound port. Never kill by a broad process-name match.\n- Keep commits and pull requests focused. Do not mix unrelated cleanup into the requested outcome.\n\n## Running Archon workflows\n\nThe input is the contract. A run is only as accurate as the brief it starts from, and a\nworkflow cannot recover from a premise that was never stated — it will produce a confident,\nwell-formed answer to the wrong question. Treat writing the input as the work, not the\npreamble to it.\n\nBefore launching a run, the input — an issue body, a message, a document, whatever the run\nreads — must state:\n\n- the problem to solve;\n- why it is worth solving;\n- why now;\n- the desired outcome;\n- the invariants that must hold;\n- what acceptance looks like.\n\nSolution steering is optional and belongs last, but omitting it is a decision: the run then\nchooses its own approach. State a constraint you actually hold — reuse this primitive, no new\ndependency, migrate rather than rewrite — because an unstated preference cannot be honoured.\nNaming an implementation *before* the problem is settled narrows the run to your first guess.\n\nDo not launch a run against an input missing any of the six. Fix the input first, and say\nwhat you changed — a thin brief is cheaper to correct before a run than after one.\n\n## Tests and validation\n\n- Add tests for meaningful behavior and failure modes. Do not preserve deleted features through regression-test clutter.\n- The engine tests what it owns (the workflow language, script nodes, state, waits, resume) with its own minimal fixtures, never with a pack workflow. The pack tests only its own scripts at a tool boundary and never retests the engine's behavior.\n- Prefer deterministic, cheap tests. Remove accidental I/O and unnecessary subprocesses before increasing timeouts.\n- Remove test temp trees with `removeTempTree` or `trackTempRoots`: from `@archon/paths/test-utils` in engine and `scripts/` tests, and from the pack's own `.archon/scripts/__tests__/temp-roots.ts` in pack tests.\n- Spawn a subprocess only when the subprocess is the subject under test. To suppress an inherited env key for a Bun child, pass it as `''`; deleting it allows `.env` loading to restore it.\n- Bun's module mocks pollute the process cache. Use the package test scripts that preserve isolation; do not run `bun test` from the repository root.\n- Run lint through `bun run lint`. Besides Biome, it runs the repository checks Biome cannot express, such as the workflow persistence boundary.\n- Check what you changed: run the tests that cover it, lint and type-check the packages you touched, and run `bun run check:ratchet` before you push. CI refuses a change the ratchet refuses, and it takes seconds locally against a fetched `origin/dev`.\n- CI is the full validation. Don't run `bun run validate` to finish a change. `scripts/validate.ts` owns the check list and CI selects from it; workflow fixtures run locally only through `--only`.\n- Schema changes also need the PostgreSQL upgrade check. [`CONTRIBUTING.md`](CONTRIBUTING.md) lists it with its command.\n- For visual or runtime behavior, add direct evidence when static tests cannot prove the outcome.\n- Destructive verification, including DDL, migrations, and data writes, runs only against a scratch database you create and drop. A configured live DSN is read-only at most; when only a live resource exists, stop and surface it to the operator.\n\n## Pull requests\n\n- Branch from `dev` and open the pull request back to `dev`.\n- Use [`.github/pull_request_template.md`](.github/pull_request_template.md). Keep its required sections, remove instructional comments and irrelevant conditional sections, and describe the problem before the implementation.\n- Review the complete diff against the actual base branch before opening the pull request.\n- Link the source issue with a closing keyword when the work has one.\n- Keep titles concise and outcome-focused. Do not add AI attribution to commits or pull requests.\n\n## Find current facts\n\nDo not expand this file with copied reference material. Use the owning source:\n\n- Product direction: [`.archon/direction.md`](.archon/direction.md)\n- Code structure, flows, extension points and enforced boundaries: [`.archon/architecture.md`](.archon/architecture.md)\n- Workflow-language design: [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md)\n- Engineering craft and review values: [`.archon/engineering.md`](.archon/engineering.md)\n- CLI: [`packages/docs-web/src/content/docs/reference/cli.md`](packages/docs-web/src/content/docs/reference/cli.md) and `archon --help`\n- Workflow authoring: [`packages/docs-web/src/content/docs/reference/workflow-language.md`](packages/docs-web/src/content/docs/reference/workflow-language.md) and workflow schemas\n- Database behavior: [`packages/docs-web/src/content/docs/reference/database.md`](packages/docs-web/src/content/docs/reference/database.md), migrations, and adapter tests\n- Available commands and generated-file checks: root and package `package.json` scripts\n","CLAUDE.md":"Agent rules: read @AGENTS.md\n","packages/adapters/src/community/chat/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fadapters%2Fsrc%2Fcommunity%2Fchat%2FREADME.md -->","packages/adapters/src/community/forge/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fadapters%2Fsrc%2Fcommunity%2Fforge%2FREADME.md -->","packages/chat-contract/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fchat-contract%2FREADME.md -->","packages/docs-web/public/brand/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fpublic%2Fbrand%2FREADME.md -->","packages/docs-web/src/content/docs/adapters/community/discord.md":"---\ntitle: Discord\ndescription: Connect Archon to Discord for AI coding assistance in servers and DMs.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\n:::note\nDiscord is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to Discord so you can interact with your AI coding assistant from any Discord server or DM.\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Discord account\n- \"Manage Server\" permission on the Discord server you want to add the bot to\n\n## Create Discord Bot\n\n1. Visit [Discord Developer Portal](https://discord.com/developers/applications)\n2. Click \"New Application\" > Enter a name > Click \"Create\"\n3. Go to the \"Bot\" tab in the left sidebar\n4. Click \"Add Bot\" > Confirm\n\n## Get Bot Token\n\n1. Under the Bot tab, click \"Reset Token\"\n2. Copy the token (starts with a long alphanumeric string)\n3. **Save it securely** -- you won't be able to see it again\n\n## Enable Message Content Intent (Required)\n\n1. Scroll down to \"Privileged Gateway Intents\"\n2. Enable **\"Message Content Intent\"** (required for the bot to read messages)\n3. Save changes\n\n:::caution\nSkipping this step causes Discord to reject the bot's connection with\n`Used disallowed intents`. Archon will log\n`discord.start_failed_continuing_without_adapter` and keep the rest of\nthe server running, but the Discord adapter will be unavailable until\nthe intent is enabled and the server is restarted.\n:::\n\n## Invite Bot to Your Server\n\n1. Go to \"OAuth2\" > \"URL Generator\" in the left sidebar\n2. Under \"Scopes\", select:\n   - `bot`\n3. Under \"Bot Permissions\", select:\n   - Send Messages\n   - Read Message History\n   - Create Public Threads (optional, for thread support)\n   - Send Messages in Threads (optional, for thread support)\n4. Copy the generated URL at the bottom\n5. Paste it in your browser and select your server\n6. Click \"Authorize\"\n\n**Note:** You need \"Manage Server\" permission to add bots.\n\n## Set Environment Variable\n\n```ini\nDISCORD_BOT_TOKEN=your_bot_token_here\n```\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users, enable Developer Mode in Discord:\n1. User Settings > Advanced > Enable \"Developer Mode\"\n2. Right-click on users > \"Copy User ID\"\n3. Add to environment:\n\n```ini\nDISCORD_ALLOWED_USER_IDS=123456789012345678,987654321098765432\n```\n\n## Configure Streaming Mode (Optional)\n\n```ini\nDISCORD_STREAMING_MODE=batch  # batch (default) | stream\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Configure Mention Requirement (Optional)\n\nBy default the bot only activates in servers when @mentioned (DMs are exempt). On single-user or private servers you can opt out so the bot responds to any authorized message:\n\n```ini\nDISCORD_REQUIRE_MENTION=false  # true (default) | false\n```\n\nOnly the literal value `false` disables the mention requirement. Mentions that are present are still stripped from the message.\n\n## Usage\n\nSelect a registered project by submitting `/setproject <name>` in the conversation or thread where you want to work. To register a local directory first, submit `/register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\nThe bot responds to:\n- **Direct Messages**: Just send messages directly\n- **Server Channels**: @mention the bot (e.g., `@YourBotName help me with this code`) — or any authorized message when `DISCORD_REQUIRE_MENTION=false`\n- **Threads**: Bot maintains context in thread conversations\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","packages/docs-web/src/content/docs/adapters/community/gitea.md":"---\ntitle: Gitea\ndescription: Connect Archon to a Gitea instance for issue and PR automation.\ncategory: adapters\narea: adapters\naudience: [operator]\nsidebar:\n  order: 6\n---\n\n:::note\nGitea is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to a self-hosted Gitea instance so you can interact with your AI coding assistant from Gitea issues and pull requests.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Gitea instance with API access enabled\n- A Gitea personal access token (or dedicated bot account token)\n- Public endpoint for webhooks (or a tunnel for local development)\n\n:::tip\n`GITEA_TOKEN` also enables authenticated cloning of private Gitea and Forgejo repositories. If you set it for the adapter, clone authentication works automatically — no extra configuration needed.\n:::\n\n## Step 1: Create a Gitea Token\n\n1. Log in to your Gitea instance\n2. Go to **Settings > Applications > Manage Access Tokens**\n3. Create a new token with repository read/write permissions\n4. Copy the token -- you will need it for Step 3\n\n## Step 2: Generate a Webhook Secret\n\nOn Linux/Mac:\n```bash\nopenssl rand -hex 32\n```\n\nOn Windows (PowerShell):\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret for Steps 3 and 4.\n\n## Step 3: Set Environment Variables\n\n```ini\nGITEA_URL=https://gitea.example.com\nGITEA_TOKEN=your_personal_access_token\nGITEA_WEBHOOK_SECRET=your_secret_from_step_2\n```\n\nAll three variables are required. The adapter starts automatically when all three are set.\n\n**Optional variables:**\n\n```ini\n# Restrict who can trigger the bot (comma-separated usernames, case-insensitive)\nGITEA_ALLOWED_USERS=alice,bob\n\n# Custom @mention name (defaults to BOT_DISPLAY_NAME, then \"Archon\")\nGITEA_BOT_MENTION=archon\n```\n\n## Step 4: Configure Gitea Webhook\n\nGo to your repository settings in Gitea:\n- Navigate to **Settings > Webhooks > Add Webhook > Gitea**\n\n**Webhook Configuration:**\n\n| Field | Value |\n|-------|-------|\n| **Target URL** | `https://your-domain.com/webhooks/gitea` |\n| **HTTP Method** | `POST` |\n| **Content Type** | `application/json` |\n| **Secret** | Paste the secret from Step 2 |\n| **Events** | Issues, Issue Comments, Pull Requests, Pull Request Comments |\n\nClick **Add Webhook** and use the **Test Delivery** button to verify connectivity.\n\nWebhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** and are not processed.\n\n## Usage\n\nInteract by @mentioning the bot in issue or PR **comments**:\n\n```\n@archon can you analyze this bug?\n@archon review this implementation\n@archon /workflow run investigate \"why does the auth flow reject valid tokens?\"\n```\n\n**First mention behavior:**\n- Reuses an existing local registration at its saved checkout path. Otherwise, registers and clones the repository to `~/.archon/workspaces/`\n- Detects and loads commands from `.archon/commands/` if present\n- Injects full issue/PR context (title, description, labels) for the AI assistant\n\n**Subsequent mentions:**\n- Resumes the existing conversation\n- Maintains full context across comments\n\n:::note\nOnly comments trigger the bot. @mentions in issue or PR descriptions are ignored -- descriptions often contain example commands or documentation that are not intended as bot invocations.\n:::\n\n## How It Works\n\nThe Gitea adapter is a webhook-based forge adapter, similar to the GitHub adapter:\n\n- **Transport**: Receives HTTP POST webhooks from Gitea\n- **Signature verification**: HMAC SHA-256 over the exact received bytes, using the `X-Gitea-Signature` header\n- **Streaming mode**: Always batch (assembles the assistant reply after processing; long replies may span multiple comments; see [batch mode](/reference/configuration/#batch-mode) for filtering and fallback behavior)\n- **Conversation ID format**: `owner/repo#number` for issues, `owner/repo!number` for PRs\n- **Self-loop prevention**: Bot comments include a hidden HTML marker (`<!-- archon-bot-response -->`) to avoid re-triggering on its own messages\n- **Retry logic**: Transient network errors (timeouts, connection resets) are retried up to 3 times with exponential backoff\n\n### Close/Merge Cleanup\n\nWhen an issue is closed or a PR is merged/closed, the adapter automatically cleans up any associated worktree isolation environment.\n\n## Adding More Repositories\n\nAdd a webhook with the same secret to each repository you want the bot to monitor. The webhook secret must be identical across all repos pointing to the same Archon instance.\n\n## Further Reading\n\n- [Configuration](/reference/configuration/) -- Full environment variable reference\n- [Security](/reference/security/) -- Webhook verification and authorization details\n","packages/docs-web/src/content/docs/adapters/community/gitlab.md":"---\ntitle: GitLab\ndescription: Connect Archon to GitLab for AI coding assistance in issues and merge requests.\ncategory: adapters\narea: adapters\naudience: [operator]\nsidebar:\n  order: 7\n---\n\n:::note\nGitLab is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to a GitLab instance (gitlab.com or self-hosted) so you can interact with your AI coding assistant from issues and merge requests.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- GitLab project with issues and merge requests enabled\n- GitLab Personal Access Token or Project Access Token with `api` scope\n- Public endpoint for webhooks (see ngrok setup below for local development)\n\n:::tip\n`GITLAB_TOKEN` also enables authenticated cloning of private GitLab repositories. If you set it for the adapter, clone authentication works automatically — no extra configuration needed.\n:::\n\n## Step 1: Create a GitLab Access Token\n\n### Personal Access Token (recommended for getting started)\n\n1. Go to **GitLab → User Settings → Access Tokens**\n2. Create a token with:\n   - **Name**: `archon`\n   - **Scopes**: `api`\n   - **Expiration**: Set as needed\n3. Copy the token (starts with `glpat-`)\n\n### Project Access Token (recommended for production)\n\n1. Go to **Project → Settings → Access Tokens**\n2. Create a token with:\n   - **Role**: Developer or Maintainer\n   - **Scopes**: `api`\n3. This creates a bot user scoped to the project\n\n## Step 2: Generate Webhook Secret\n\n```bash\nopenssl rand -hex 32\n```\n\nWindows (PowerShell):\n\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret — you'll need it for steps 3 and 4.\n\n## Step 3: Expose Local Server (Development Only)\n\n```bash\nngrok http 3090\n# Copy the HTTPS URL (e.g., https://abc123.ngrok-free.app)\n```\n\nFor production, use your deployed server URL directly.\n\n## Step 4: Configure GitLab Webhook\n\nNavigate to **Project → Settings → Webhooks → Add new webhook**:\n\n| Field | Value |\n|-------|-------|\n| **URL** | `https://your-domain.com/webhooks/gitlab` |\n| **Secret token** | The secret from Step 2 |\n| **Triggers** | Enable: `Comments`, `Issues events`, `Merge request events` |\n| **SSL verification** | Enable (recommended) |\n\nClick \"Add webhook\" and use **Test → Note events** to verify.\n\n:::note\nGitLab uses a plain secret token in the `X-Gitlab-Token` header (not HMAC like GitHub). The token must match your `GITLAB_WEBHOOK_SECRET` exactly.\n:::\n\n## Step 5: Set Environment Variables\n\n```ini\nGITLAB_URL=https://gitlab.com\nGITLAB_TOKEN=glpat-your-token-here\nGITLAB_WEBHOOK_SECRET=your-secret-here\n```\n\nOptional:\n\n```ini\nGITLAB_ALLOWED_USERS=alice,bob\nGITLAB_BOT_MENTION=archon\n```\n\nSee the [full environment variable reference](/reference/configuration/) for details.\n\n## Usage\n\nMention your bot in issue or MR comments:\n\n```\n@archon can you analyze this bug?\n@archon /status\n@archon review this implementation\n```\n\n**First mention** reuses an existing local registration at its saved checkout path; otherwise it registers and clones the repository. It detects `.archon/commands/` if present and injects full issue/MR context. For a one-level namespace, the checkout is `~/.archon/workspaces/<group>/<project>/source`. Nested namespaces are flattened after the top-level group: `org/team/api` uses `~/.archon/workspaces/org/team-api/source`. If another project owns that storage name, registration allocates a suffix such as `org/team-api-2`, with its checkout under `~/.archon/workspaces/org/team-api-2/source`. See the [directory reference](/reference/archon-directories/) for the full workspace layout.\n\n**Subsequent mentions** resume the existing conversation with full context.\n\n## Conversation ID Format\n\n| Type | Format | Example |\n|------|--------|---------|\n| Issue | `group/project#iid` | `myteam/api#42` |\n| Merge Request | `group/project!iid` | `myteam/api!15` |\n| Nested group | `group/subgroup/project#iid` | `org/team/api#7` |\n\n## Supported Events\n\n| GitLab Event | Action |\n|-------------|--------|\n| **Note Hook** (comment with @mention) | Triggers AI conversation |\n| **Issue Hook** (close) | Cleans up isolation environment |\n| **MR Hook** (close/merge) | Cleans up isolation environment |\n| Issue/MR opened | Ignored (descriptions are not commands) |\n\n## Adding Additional Projects\n\nAdd the same webhook to other projects:\n\n```bash\nglab api projects/<PROJECT_ID>/hooks \\\n  --method POST \\\n  -f url=\"https://YOUR_DOMAIN/webhooks/gitlab\" \\\n  -f token=\"YOUR_WEBHOOK_SECRET\" \\\n  -f note_events=true \\\n  -f issues_events=true \\\n  -f merge_requests_events=true\n```\n\nOr via GitLab UI with the same secret.\n\n## Troubleshooting\n\n| Issue | Cause | Fix |\n|-------|-------|-----|\n| `gitlab.invalid_webhook_token` | Secret mismatch | Ensure `GITLAB_WEBHOOK_SECRET` matches the webhook config exactly |\n| Clone hangs | macOS Keychain credential helper | The adapter disables it automatically |\n| `404 Project Not Found` | Token lacks access | Ensure token has `api` scope and project access |\n| `403 You are not allowed` | Insufficient permissions | Use a token with Developer role or higher |\n| No webhook delivery | ngrok URL changed | Update the webhook URL after restarting ngrok |\n| Webhook auto-disabled | 4+ consecutive failures | Fix the issue, then send a test event to re-enable |\n\n## glab CLI Reference\n\nThe AI agent uses `glab` CLI commands. Install and authenticate:\n\n```bash\nbrew install glab\nglab auth login\n```\n\n| Command | Purpose |\n|---------|---------|\n| `glab issue view <IID>` | View issue details |\n| `glab issue note <IID> -m \"...\"` | Comment on issue |\n| `glab mr view <IID>` | View merge request |\n| `glab mr diff <IID>` | View MR diff |\n| `glab mr note <IID> -m \"...\"` | Comment on MR |\n| `glab mr create --title \"...\" --description \"...\"` | Create MR |\n","packages/docs-web/src/content/docs/adapters/github-app-setup.md":"---\ntitle: GitHub App Setup\ndescription: Run Archon's bot identity as a registered GitHub App with multi-installation routing.\ncategory: adapters\narea: adapters\naudience: [operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\nThis is the recommended GitHub auth mode for teams sharing one Archon instance. It replaces the shared `GITHUB_TOKEN` PAT with a registered GitHub App so that:\n\n- Bot comments appear as `archon[bot]` with the App badge, not under an operator's personal account.\n- Installation access tokens rotate automatically every ~1h (smaller blast radius if leaked).\n- Webhooks centralise — one URL per App covers every installation.\n- A team's repos can span multiple GitHub orgs (or a mix of orgs and personal accounts) — Archon routes per-(owner, repo) to the right installation transparently.\n\nSolo installs that only need the PAT model can ignore this page; see [GitHub](/adapters/github/) for the legacy setup.\n\n## When to use App mode vs. PAT mode\n\n| Situation                                                           | Recommended mode |\n| ------------------------------------------------------------------- | ---------------- |\n| Solo developer, single GitHub account                               | PAT              |\n| Team of 2+ sharing one Archon instance                              | App              |\n| Repos across multiple orgs                                          | App              |\n| You want bot comments to attribute as `<slug>[bot]` with App badge  | App              |\n| You want short-lived (1h) tokens instead of long-lived PAT          | App              |\n\nArchon refuses to start with **both** modes configured. Pick one set of env vars.\n\n## Step 1: Register the GitHub App\n\n1. Go to <https://github.com/settings/apps/new> (or `https://github.com/organizations/<org>/settings/apps/new` for an org-owned App).\n2. Fill in:\n   - **GitHub App name** — e.g. `Archon Bot`. The slug derived from this (visible in the App URL) is what you'll later set as `GITHUB_APP_SLUG`. Self-filter compares against `<slug>[bot]`.\n   - **Homepage URL** — your team's Archon URL, e.g. `https://archon.example.com/`.\n   - **Webhook URL** — `https://archon.example.com/webhooks/github`.\n   - **Webhook secret** — same value as your `WEBHOOK_SECRET` env var.\n\n   Webhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** before the signature is checked; Archon stops reading them and creates no receipt or workflow start. This limit applies only to webhook routes.\n3. Uncheck **Active** on the user authorisation callback URL — Archon doesn't use OAuth in PR-B.\n\n## Step 2: Permissions (fine-grained)\n\n**Repository permissions:**\n\n| Permission       | Access       | Used for                                          |\n| ---------------- | ------------ | ------------------------------------------------- |\n| Actions          | Read & Write | Observing GitHub Actions workflow runs and rerunning failed CI |\n| Checks           | Read         | Waking workflow CI waits when a check concludes   |\n| Contents         | Read & Write | Cloning, reading repo metadata, pushing branches, and merging pull requests |\n| Workflows        | Read & Write | Required only when runs create or change files under `.github/workflows/` |\n| Issues           | Read & Write | `createComment` + `listComments`                  |\n| Pull requests    | Read & Write | `pulls.get` + comment posting                     |\n| Metadata         | Read         | Mandatory (auto-included)                         |\n\nGitHub refuses pushes that create or change files under `.github/workflows/` unless the App has **Workflows: Read & Write**, even when **Contents: Read & Write** is granted. See [GitHub's Git access permissions](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app#choosing-permissions-for-git-access).\n\n**Account permissions:** none.\n\n## Step 3: Subscribe to webhook events\n\nSubscribe to:\n\n- Issue comments\n- Check runs\n- Pull request review comments\n- Pull request\n- Issues (used for `closed` cleanup)\n\nExisting installations must accept the added Checks permission before check-run wake-ups work. If a check-run webhook is unavailable, delivery still progresses after the wait's five-minute deadline and probes CI again.\n\n## Step 4: Generate a private key\n\n1. After saving the App, scroll to **Private keys** and click **Generate a private key**.\n2. Save the downloaded `.pem` file in a location only readable by the Archon process — e.g. `/etc/archon/github-app.pem`.\n\n## Step 5: Install the App\n\nInstall the App on every org or personal account that holds repos your team operates on:\n\n1. From the App settings page, click **Install App**.\n2. Pick the org → grant access to all repos (or selected repos).\n3. Repeat for every org/account.\n\n> **Multi-installation:** Archon resolves `owner/repo → installation_id` via `GET /repos/{owner}/{repo}/installation` automatically. No per-install config is needed. `GITHUB_APP_INSTALLATION_ID` only pins the installation used for adapter API operations — see below.\n\n## Step 6: Configure Archon\n\nAdd the following to `~/.archon/.env` for the install or `<cwd>/.archon/.env` for the project:\n\n```dotenv\nGITHUB_APP_ID=123456                 # numeric App ID, visible on the App settings page\nGITHUB_APP_PRIVATE_KEY_PATH=/etc/archon/github-app.pem\nWEBHOOK_SECRET=<same value as on the GitHub side>\n\n# Optional:\n# GITHUB_APP_SLUG=archon-bot         # defaults to 'archon'; set this if you named your App\n#                                    # differently. The bot's posted-comment login is `<slug>[bot]`.\n# GITHUB_APP_INSTALLATION_ID=98765   # use a fixed installation for adapter API operations.\n#                                    # Repository tokens still verify the repository installation.\n```\n\nCLI workflow run, `--detach`, resume, wake, and signal use `GITHUB_APP_ID` plus either private-key setting. They do not require `WEBHOOK_SECRET`; that setting is required for the server's webhook adapter. Each detached child initializes its own provider from the inherited configuration.\n\nIncomplete App settings, malformed App or installation IDs, an unusable RSA private key, and a malformed per-user encryption key fail before workflow execution. App/key correspondence and installation access are checked remotely when resolving the repository token. A resolution failure fails the run before any node executes; it cannot fall back to another credential. With no App settings, CLI behavior is unchanged. CLI runs follow the same per-user policy as server runs: connected user credentials override the bot, and unconnected users have GitHub credentials scrubbed unless org fallback is explicitly enabled.\n\nEach workflow execution, including a resumed segment, resolves the codebase repository's installation token. The provider caches fresh tokens in memory and refreshes them near expiry; tokens are not cached on disk. A node's environment stays fixed during one execution, so this does not rotate tokens inside a workflow that runs longer than the token lifetime.\n\n### Inline private key (alternative)\n\nIf you can't write a file (e.g. a managed PaaS), set the PEM contents inline:\n\n```dotenv\nGITHUB_APP_PRIVATE_KEY=\"-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----\"\n```\n\nArchon normalises the literal `\\n` sequence in `.env`-quoted values into real newlines.\n\n### Unset the PAT\n\nArchon refuses to start if both modes are configured. Remove `GITHUB_TOKEN` from your env when switching to App mode.\n\n## Step 7: Restart Archon and verify\n\n1. Restart the server.\n2. Confirm the startup log shows `github.adapter_mode_app` with your slug.\n3. Trigger a webhook from a repo where the App is installed (e.g. comment `@archon ping` on an issue).\n4. Confirm the bot's reply appears as `<slug>[bot]` with the App badge in the GitHub UI.\n\n## Step 8 (optional): Enable per-user GitHub identity\n\nBy default GitHub API requests and pushes authenticate as the **bot** (`<slug>[bot]`), while commit authorship uses the checkout's ambient git identity. On a multi-user install teammates can connect their own GitHub identity for authentication and commit authorship.\n\nEnable the feature by adding two env vars on top of App mode:\n\n```bash\n# The App's Client ID (App settings → \"Client ID\", starts with Iv1./Iv23…).\n# Distinct from the numeric GITHUB_APP_ID.\nGITHUB_APP_CLIENT_ID=Iv23xxxxxxxxxxxx\n# 32-byte key used to encrypt stored per-user tokens at rest (AES-256-GCM).\nTOKEN_ENCRYPTION_KEY=$(openssl rand -hex 32)\n```\n\nThen, on the GitHub App settings page, enable **Device Flow** (under \"Identifying and authorizing users\"). The feature gate activates when `GITHUB_APP_ID` and `TOKEN_ENCRYPTION_KEY` are both set; `GITHUB_APP_CLIENT_ID` is required for the connect flow itself.\n\nTeammates connect once via any surface:\n\n- **CLI:** `archon auth github`\n- **Web UI:** Settings → **Connect GitHub**\n\nOnce enabled:\n\n- Run commits use the initiating connected user's GitHub login as author name and `<numeric_id>+<login>@users.noreply.github.com` as author email. The committer remains the ambient git identity. This applies to new and adopted worktrees, in-place runs, and folder projects without changing repository git config. Unconnected users and runs without an initiating user keep ambient authorship.\n- Workflows declaring `requires: [github]` **hard-block** unconnected users before any worktree/clone/AI cost.\n- An unconnected user's workflow `gh`/`git` has its GitHub token **scrubbed** by default (rather than silently using the shared org/bot token). The host's `GH_ENTERPRISE_TOKEN`/`GITHUB_ENTERPRISE_TOKEN` are cleared for every per-user run. The scrub also hides the host's stored logins: `gh` gets an empty config directory, so a `gh auth login` on the server is not used, and git's credential helpers (keychain, credential store, `gh auth git-credential`) are cleared. GitHub commands in such a run fail with an authentication error until the user connects. SSH remotes still use the host's SSH keys. Opt back into the shared token with `ARCHON_ALLOW_ORG_GITHUB_TOKEN_FALLBACK=true`.\n- Rotating `TOKEN_ENCRYPTION_KEY` invalidates all stored user tokens — everyone must reconnect.\n\nThis is fully backward compatible: leave the two vars unset and the App keeps working as the bot only. See the [configuration reference](/reference/configuration/#per-user-github-identity-app-mode-optional) for the full env var table.\n\n## Operational notes\n\n### Token rotation is invisible\n\nInstallation tokens are valid for 1h. Archon caches each `installation_id → token` pair and refreshes ~5 minutes before expiry on the next access. No background timer; no leaked handles.\n\n### `event.installation.id` short-circuits the lookup\n\nEvery webhook delivery from a GitHub App carries `installation.id`. Archon primes its `owner/repo → installation_id` cache from the payload, so the next outbound call to that repo skips the `GET /repos/{owner}/{repo}/installation` round trip.\n\n### 401 forces a single retry\n\nA 401 from any installation Octokit evicts the cached token and the call is retried once with a fresh token. Persistent 401s propagate as the original error.\n\n### Long-running workflow `git push`\n\nWorkflows that span >1h need a fresh token to push from the cloned worktree. In App mode, Archon installs a git credential helper after cloning and refreshes it whenever it sets up an existing repository, including webhook handling and adding a codebase by URL: the worktree's `.git/config` points at `~/.archon/bin/git-credential-archon`, which talks back to Archon's internal endpoint for a fresh installation token on each operation. Git skips credential helpers when a remote URL carries credentials, so the same setup removes any token embedded in the repository's `https://github.com` remote URLs, such as one left by an older PAT-mode clone. The helper reads the server's port from `ARCHON_PORT`, which Archon sets for the processes it launches. A `git` command run from an independent shell on the Archon host gets no credential unless it exports `ARCHON_PORT` with the server's port.\n\nThe helper is embedded in compiled binaries. Each setup replaces an outdated managed script and registers the helper and GitHub path forwarding in the repository's local Git config. Existing repositories are refreshed on their next setup, not swept at server startup. If helper installation or registration fails, repository setup stops before URL registration, sync, or workflow dispatch; a later setup retries registration and repairs partial config.\n\n### Long-running workflow `gh`\n\nA run's `GH_TOKEN` and `GITHUB_TOKEN` hold the installation token resolved when the run, or a resumed segment, starts. That token can have as little as 5 minutes left. When the run's process has `ARCHON_PORT` (every run the server dispatches, and a CLI run that exports the server's port), Archon also puts `~/.archon/bin/gh-shim` first on the run's `PATH`. On each call the shim asks the same internal endpoint for a current token for the run's repository and runs the real `gh` with it in the environment. If it gets no token, it exits 1 with a `gh (archon shim):` error instead of running `gh` with a stale token. A `GH_TOKEN` that is not an installation token, such as one a step sets itself, passes through unchanged. A per-user token or scrubbed token from [per-user GitHub identity](#step-8-optional-enable-per-user-github-identity) turns the shim off for that run.\n\n`gh` keeps the start-time token, and fails once it expires, in these cases:\n\n- **A CLI run without `ARCHON_PORT`.** No endpoint is reachable, so Archon logs `workflow.gh_token_refresh_unavailable` each time it resolves the run's GitHub environment. That happens when the run starts, when it runs worktree setup commands, and when it resumes, so one run can log it several times. The CLI mints its token at start, so it lasts about an hour. Export `ARCHON_PORT` with an App-mode server's port to cover longer runs.\n- **`gh` called by a Codex agent.** Codex's shell snapshot restores a fixed `PATH`, so its shell never finds the shim. Archon does not change that Codex setting. Bash, script and loop nodes, and Claude and Pi agents, use the shim.\n- **Windows.** The shim is a POSIX shell script, so a run on Windows logs the same warning and keeps the start-time token.\n\nRun output redacts the start-time token only. A token the shim fetches later lives in the `gh` process's environment, so it is not redacted if a step prints it, for example with `gh auth token`.\n\n### Internal endpoint security — REQUIRED\n\nThe credential-helper backend is exposed at `POST /internal/git-credential` and hands out live installation access tokens. **It MUST NOT be reachable from outside the Archon host.**\n\nArchon enforces this at startup: with App mode active and the server bound to a non-loopback interface (e.g. `0.0.0.0`), the process **refuses to start** and exits with `github_app.internal_endpoint_public_bind_rejected`. Two correct configurations:\n\n1. **Recommended (bare-metal / systemd) — bind Archon to `127.0.0.1`** (`HOST=127.0.0.1`) and put a reverse proxy in front. Configure the proxy to drop `/internal/*` paths.\n2. **Opt-in escape hatch — `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`** combined with `HOST=0.0.0.0` (or unset). Use ONLY when your reverse proxy already drops `/internal/*` AND your deployment topology genuinely requires the upstream to bind non-loopback (e.g. a container network where loopback isn't reachable from the proxy). **Docker / docker-compose deployments fall here** — the app container *must* bind `0.0.0.0` inside the container for docker-proxy to forward traffic, so option 1 isn't directly usable. See the [Canonical Docker setup](#canonical-docker-setup) below. Startup logs `github_app.internal_endpoint_exposed_acknowledged` so the choice is auditable.\n\nExample Caddy snippet that drops `/internal/*` (bare-metal):\n\n```txt\nexample.com {\n  @internal path /internal/*\n  respond @internal 404\n  reverse_proxy 127.0.0.1:3090\n}\n```\n\n### Canonical Docker setup\n\nThe canonical Archon Docker stack (`docker-compose.yml` + `docker-compose.override.yml` + Caddy) publishes the app container's port `3000` and runs Caddy as a reverse proxy. Because the app container can't bind `127.0.0.1` and still have docker-proxy forward traffic to it, option 1 above isn't directly usable. Take option 2 with three layers of defense:\n\n**1. In `docker-compose.override.yml` — bind the published host port to loopback only.**\n\n```yaml\nservices:\n  app:\n    ports: !override\n      - \"127.0.0.1:${PORT:-3000}:${PORT:-3000}\"\n```\n\nThe `!override` tag (compose-spec) replaces the base file's `ports` list instead of merging. After this, port `3000` is reachable only via the Docker network (where Caddy lives) or from the host itself — not from the public internet.\n\n**2. In `Caddyfile` — drop `/internal/*` requests.**\n\nThe maintained `Caddyfile.example` includes this denial. Keep it in your deployed `Caddyfile`; if you use an older or custom configuration, insert it before the fallthrough `handle { }` block:\n\n```txt\nhandle /internal/* {\n  respond \"Not Found\" 404\n}\n```\n\nCaddy evaluates `handle` blocks by path specificity, so `/internal/*` (more specific) wins over the generic fallthrough regardless of order.\n\n**3. In `.env` — opt out of the loopback-bind guard.**\n\n```ini\n# Safe because:\n#  1. docker-compose binds port 3000 to 127.0.0.1 only (override above)\n#  2. Caddy drops /internal/* (handle block above)\nARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1\n```\n\nWithout this flag, Archon refuses to start in App mode and exits with `github_app.internal_endpoint_public_bind_rejected`.\n\n**Apply + verify:**\n\n```sh\n# Apply the override + Caddyfile + env changes, then restart app + caddy.\ndocker compose up -d --force-recreate app caddy\n\n# From outside the host — must be 404/403 from the proxy.\n# Use POST (the endpoint's actual method) — a GET probe can false-pass when\n# the proxy 404s unmatched GETs while still forwarding the POST upstream.\ncurl -i -X POST https://your-archon/internal/git-credential \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"host\":\"github.com\",\"path\":\"any/repo\"}'\n\n# On the host — port should bind to loopback only\nss -tlnp | grep :3000\n# Expected: 127.0.0.1:3000   (NOT 0.0.0.0:3000)\n\n# Startup log should contain:\ndocker compose logs app --tail 200 | grep \"internal_endpoint_exposed_acknowledged\"\n# This event confirms the opt-out path was taken (audit trail).\n```\n\nIf the external POST probe returns anything other than `404` or `403` from the proxy — i.e. anything that suggests the request reached the Archon process — **stop and fix the proxy config** before going live. The endpoint hands out live installation tokens to whoever can reach it.\n\n## Migration from PAT mode\n\n1. Register the App and install on your orgs (Steps 1–5 above).\n2. Add `GITHUB_APP_*` env vars (Step 6). In Docker, put them in the container environment (the compose `.env`), not `/.archon/.env`; see [GitHub CLI authentication](/deployment/docker/#github-credentials).\n3. **Remove** `GITHUB_TOKEN` from your env (or comment it out). Archon refuses to start if both are set.\n4. **Remove** `GH_TOKEN` if you set it to the same PAT. Archon starts with it set, but the GitHub CLI in the container keeps using the old PAT.\n5. Restart Archon.\n6. Webhook URLs configured per-repo against the PAT-mode setup can stay or be removed — the App's single webhook URL covers everything once it's installed. New repos auto-join via App installation. While both webhooks are active, a comment delivered by each is deduplicated at ingest, so the bot still responds once.\n\nIn App mode, adding a private GitHub codebase by URL and inspecting its base branch use the repository's installation token; no PAT is needed. The clone keeps a credential-free remote URL and receives the credential helper before registration completes. If the App is not installed, Archon reports installation guidance. Adding an already-registered URL refreshes the helper without cloning again when the checkout exists. A missing Archon-managed checkout is cloned again at its registered path. A missing operator-owned checkout must be restored or repaired with `/update-project`.\n\nRun `archon doctor` to validate local App configuration after migration. A passing App check confirms the ID and RSA key are valid locally; it does not confirm GitHub accepts them or that the App has repository access.\n\n## Troubleshooting\n\n### `AppPrivateKeyError: Provided value is not a valid PEM-encoded private key`\n\n- Check the file content includes `-----BEGIN ... PRIVATE KEY-----` and `-----END ... PRIVATE KEY-----`.\n- For inline keys, ensure the `.env` value preserves newlines (either literal newlines in a multi-line value or the `\\n` escape inside double quotes).\n\n### `AppNotInstalledError: The Archon GitHub App is not installed on \"<owner>\"`\n\n- The App is not installed on that owner's org/account. Use the install link in the error message to add it.\n\n### 401 loop on GitHub API calls\n\nRepeated 401s on outbound API calls (`createComment`, `listComments`, `repos.get`, `pulls.get`) point at installation / token issues, not webhook config. Walk through:\n\n- Verify the App is installed on the target `owner` and has not been suspended or uninstalled. Visit `https://github.com/settings/installations` (or the org equivalent) to confirm.\n- Verify the private key in your env (`GITHUB_APP_PRIVATE_KEY` or `_PATH`) matches the same App that `GITHUB_APP_ID` points at. Mismatched key+ID is a common cause of \"401 from JWT\" errors at token-issuance time.\n- Verify `GITHUB_APP_INSTALLATION_ID` (if set) still corresponds to a live installation — uninstall + reinstall assigns a new ID.\n\n### GitHub refuses a branch push\n\nAn installation token needs Contents write access to push branches. If the branch creates or changes files under `.github/workflows/`, GitHub also requires Workflows write access. Check the App and installation permissions:\n\nVerify the App's permissions against the [permissions table](#step-2-permissions-fine-grained). Permission changes require operators to **review and accept** the new permissions on every installation; until accepted, that installation keeps its old permissions.\n\n### Webhook deliveries fail signature verification\n\nA webhook-secret mismatch causes `github.signature_mismatch` / `github.signature_length_mismatch` errors at the `POST /webhooks/github` endpoint — distinct from outbound API 401s. If GitHub's webhook delivery page shows red ❌ next to the delivery (rather than your bot just silently not responding), check:\n\n- `WEBHOOK_SECRET` in Archon's env matches the value entered in the GitHub App's webhook configuration page exactly.\n\n### Bot comments still appear under your personal account\n\n- You're still in PAT mode. Check `process.env.GITHUB_TOKEN` is unset and `GITHUB_APP_ID` is set; restart.\n\n### Server refused to start: `github_app.internal_endpoint_public_bind_rejected`\n\n- App mode is active but the server is bound to a non-loopback interface. This is a fail-fast guard — the `/internal/git-credential` endpoint hands out live installation access tokens and would leak credentials to the network. Either set `HOST=127.0.0.1` (bare-metal / systemd), or, if you're on Docker, follow the [Canonical Docker setup](#canonical-docker-setup) above (port→127.0.0.1 binding + Caddy `/internal/*` drop + `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`).\n\n### Server log shows `github_app.internal_endpoint_exposed_acknowledged`\n\n- You set `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`. Double-check that your reverse proxy actually drops `/internal/*` — a `curl https://your-archon/internal/git-credential -d '{\"host\":\"github.com\",\"path\":\"any/repo\"}'` from outside the host must return 404 or 403 from the proxy (NOT a token from Archon). On the canonical Docker stack, also confirm `ss -tlnp | grep :3000` shows `127.0.0.1:3000` (not `0.0.0.0:3000`).\n","packages/docs-web/src/content/docs/adapters/github.md":"---\ntitle: GitHub\ndescription: Connect Archon to GitHub via webhooks to interact from issues and pull requests.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 4\n---\n\nConnect Archon to GitHub so you can interact with your AI coding assistant from issues and pull requests.\n\n> **Teams sharing one Archon instance:** prefer the [GitHub App setup](./github-app-setup.md). The PAT-mode setup on this page is the legacy path — it still works for solo installs, but bot comments all post under the PAT owner's avatar and tokens never auto-rotate. App mode gives you `<slug>[bot]` attribution, 1h token rotation, multi-org support, and one webhook URL.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- GitHub repository with issues enabled\n- A GitHub personal access token set as `GITHUB_TOKEN` in `~/.archon/.env`\n- Public endpoint for webhooks (see ngrok setup below for local development)\n\n## Step 1: Generate Webhook Secret\n\nOn Linux/Mac:\n```bash\nopenssl rand -hex 32\n```\n\nOn Windows (PowerShell):\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret -- you'll need it for steps 3 and 4.\n\n## Step 2: Expose Local Server (Development Only)\n\n### Using ngrok (Free Tier)\n\n```bash\n# Install ngrok: https://ngrok.com/download\n# Or: choco install ngrok (Windows)\n# Or: brew install ngrok (Mac)\n\n# Start tunnel\nngrok http 3090\n\n# Copy the HTTPS URL (e.g., https://abc123.ngrok-free.app)\n# Free tier URLs change on restart\n```\n\nKeep this terminal open while testing.\n\n### Using Cloudflare Tunnel (Persistent URLs)\n\n```bash\n# Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/\ncloudflared tunnel --url http://localhost:3090\n\n# Get persistent URL from Cloudflare dashboard\n```\n\nPersistent URLs survive restarts.\n\n**For production deployments**, use your deployed server URL (no tunnel needed).\n\n## Step 3: Configure GitHub Webhook\n\nGo to your repository settings:\n- Navigate to: `https://github.com/owner/repo/settings/hooks`\n- Click \"Add webhook\"\n- **Note**: For multiple repositories, you'll need to add the webhook to each one individually\n\n**Webhook Configuration:**\n\n| Field | Value |\n|-------|-------|\n| **Payload URL** | Local: `https://abc123.ngrok-free.app/webhooks/github`<br>Production: `https://your-domain.com/webhooks/github` |\n| **Content type** | `application/json` |\n| **Secret** | Paste the secret from Step 1 |\n| **SSL verification** | Enable SSL verification (recommended) |\n| **Events** | Select \"Let me select individual events\":<br>- Check runs<br>- Issues<br>- Issue comments<br>- Pull requests |\n\nClick \"Add webhook\" and verify it shows a green checkmark after delivery.\n\nWebhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** before the signature is checked; Archon stops reading them and creates no receipt or workflow start. This limit applies only to webhook routes.\n\n## Step 4: Set Environment Variables\n\n```ini\nWEBHOOK_SECRET=your_secret_from_step_1\n```\n\n**Important**: The `WEBHOOK_SECRET` must match exactly what you entered in GitHub's webhook configuration.\n\n## Step 5: Configure Streaming (Optional)\n\nThe GitHub adapter always uses `batch` mode (hardcoded). It assembles the reply before delivery and splits long replies into multiple comments.\n\n## Usage\n\nInteract by @mentioning your bot in issue or PR **comments**:\n\n```\n@archon can you analyze this bug?\n@archon prime the codebase\n@archon review this implementation\n```\n\n**First mention behavior:**\n- Reuses an existing local registration at its saved checkout path, including a subdirectory of a Git checkout. Otherwise, registers and clones the repository to `~/.archon/workspaces/owner/repo/source/`, or to a suffixed path such as `owner/repo-2/source/` when that project name is taken.\n- For an existing registration, readiness skips syncing the checkout to preserve local changes.\n- Detects and loads commands from `.archon/commands/` if present\n- Injects full issue/PR context for the AI assistant\n\n**Subsequent mentions:**\n- Resumes existing conversation\n- Maintains full context across comments\n\n:::note\nOnly comments trigger the bot. @mentions in issue or PR descriptions are ignored -- descriptions often contain example commands or documentation that are not intended as bot invocations.\n:::\n\n## Adding Additional Repositories\n\nOnce your server is running, add more repos by creating a webhook with the same secret.\n\n**Via GitHub UI:** Repo Settings > Webhooks > Add webhook\n- **Payload URL**: Your server URL + `/webhooks/github`\n- **Content type**: `application/json`\n- **Secret**: Same `WEBHOOK_SECRET` from your `.env`\n- **Events**: Check runs, Issues, Issue comments, Pull requests\n\n**Via CLI:**\n\n```bash\n# Get your existing webhook secret\nWEBHOOK_SECRET=$(grep WEBHOOK_SECRET .env | cut -d= -f2)\n\n# Add webhook to new repo (replace OWNER/REPO)\ngh api repos/OWNER/REPO/hooks --method POST \\\n  -f \"config[url]=https://YOUR_DOMAIN/webhooks/github\" \\\n  -f \"config[content_type]=json\" \\\n  -f \"config[secret]=$WEBHOOK_SECRET\" \\\n  -f \"events[]=check_run\" \\\n  -f \"events[]=issues\" \\\n  -f \"events[]=issue_comment\" \\\n  -f \"events[]=pull_request\"\n```\n\n**Important**: The webhook secret must be identical across all repos.\n\nCheck-run deliveries wake CI waits quickly. They are not required for correctness: when GitHub does not deliver one, the workflow reaches its deadline and probes CI again.\n\n## GitHub Enterprise Server\n\nTo run the adapter against a self-hosted GitHub Enterprise Server, set its base URL:\n\n```ini\nGITHUB_URL=https://github.example.com\n```\n\nWith `GITHUB_URL` set, the adapter calls `https://github.example.com/api/v3`, clones from `https://github.example.com/<owner>/<repo>.git`, and `GH_TOKEN`/`GITHUB_TOKEN` authenticate HTTPS clones of that exact scheme, host and port. Leave it unset for github.com. A value with a path, query or credentials is rejected: the adapter and the GitHub App fail startup, and clones to the Enterprise host get no GitHub token. Clones from other forges are unaffected.\n\n- **Webhook:** create it on the Enterprise repository or organization, pointing at the same `/webhooks/github` endpoint with the same secret (Step 3).\n- **GitHub App mode:** register the App on the Enterprise instance (**Settings → Developer settings → GitHub Apps**) with the permissions listed in [GitHub App Setup](/adapters/github-app-setup/). The App's API calls and installation tokens go to the Enterprise instance.\n- **SDLC workflows:** the forge plugin needs a `forge.hosts` entry for the Enterprise host; see [forge plugin configuration](/reference/forge/#plugin-configuration).\n\nCurrent limits: in App mode, git operations after the first clone (sync, workflow pushes) and per-user identity (`archon auth github`, the device flow) still target github.com. Use PAT mode on Enterprise Server for now.\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","packages/docs-web/src/content/docs/adapters/index.md":"---\ntitle: Platform Adapters\ndescription: Overview of all platform adapters available for connecting to Archon.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 0\n---\n\nArchon supports multiple platform adapters. Each adapter connects Archon to a different communication channel, letting you trigger workflows and interact with AI agents from wherever you work.\n\n## Core Adapters\n\n| Adapter | Transport | Auth Required | Setup |\n|---------|-----------|---------------|-------|\n| [**Web UI**](/adapters/web/) | SSE streaming | None | Built-in |\n| [**CLI**](/reference/cli/) | stdout | None | Built-in |\n| [**Slack**](/adapters/slack/) | Socket Mode, as a chat plugin | Plugin + Bot and App tokens | [Setup guide](/adapters/slack/) |\n| [**Telegram**](/adapters/telegram/) (deprecated) | Bot API polling | Bot token | [Setup guide](/adapters/telegram/) |\n| [**GitHub**](/adapters/github/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/github/) |\n\n## Community Adapters\n\nCommunity adapters follow the same `IPlatformAdapter` interface but target platforms outside the core set.\n\n| Adapter | Transport | Auth Required | Setup |\n|---------|-----------|---------------|-------|\n| [**Discord**](/adapters/community/discord/) | WebSocket | Bot token | [Setup guide](/adapters/community/discord/) |\n| [**Gitea**](/adapters/community/gitea/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/community/gitea/) |\n| [**GitLab**](/adapters/community/gitlab/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/community/gitlab/) |\n\n## How Adapters Work\n\nSlack runs as a chat plugin: a separate process the server starts from an installed plugin, speaking the chat plugin protocol. The other adapters are built into the server. All bundled adapters implement the `IPlatformAdapter` interface. They handle:\n\n- **Message ingestion** -- receiving messages from the platform and forwarding them to Archon's orchestrator\n- **Response delivery** -- streaming or batching AI responses back to the platform\n- **Authorization** -- optional user whitelists to restrict access\n- **Conversation tracking** -- mapping platform-specific identifiers (thread IDs, chat IDs, issue numbers) to Archon conversations\n\n## Choosing an Adapter\n\n- **Web UI** is the fastest way to get started -- no tokens or external services needed.\n- **Slack** is ideal for mobile access and team collaboration.\n- **GitHub** integrates directly into your issue and PR workflow.\n- **Discord** works well for community or team servers.\n\nYou can run multiple adapters simultaneously. Any bundled adapter with the required environment variables set will start automatically when you launch the server. Slack also needs its plugin installed; see the [Slack guide](/adapters/slack/).\n","packages/docs-web/src/content/docs/adapters/slack.md":"---\ntitle: Slack\ndescription: Install the Slack chat plugin and connect Archon to Slack over Socket Mode -- works behind firewalls with no public URL needed.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 2\n---\n\nConnect Archon to Slack so you can interact with your AI coding assistant from any Slack workspace.\n\nSlack runs as a chat plugin, `chat-slack`, released with Archon. The server starts the plugin as its own process and talks to it over the chat plugin protocol; the server itself contains no Slack code. You create a Slack app, set its two tokens in the server environment, and install the plugin. Docker provisions the image-provided plugin automatically when `SLACK_BOT_TOKEN` is configured.\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Slack workspace where you have permission to install apps\n- An Archon release that publishes the Slack plugin asset (`archon-chat-slack-<os>-<arch>`)\n\n## Overview\n\nArchon uses **Socket Mode** for Slack integration, which means:\n\n- No public HTTP endpoints needed\n- Works behind firewalls\n- Simpler local development\n- Not suitable for Slack App Directory (fine for personal/team use)\n\n## Step 1: Create a Slack App\n\n1. Go to [Slack API Apps](https://api.slack.com/apps)\n2. Log in if prompted\n3. Choose the workspace for your app\n4. Click **Create New App**\n5. Choose **From scratch**\n6. Enter:\n   - **App Name**: Any name (this is what you will use to @mention the bot)\n   - **Workspace**: Select your workspace\n7. Click **Create App**\n\n## Step 2: Enable Socket Mode\n\n1. In the left sidebar, click **Socket Mode**\n2. Toggle **Enable Socket Mode** to ON\n3. When prompted, create an App-Level Token:\n   - **Token Name**: `socket-mode`\n   - **Scopes**: Add `connections:write`\n   - Click **Generate**\n4. **Copy the token** (starts with `xapp-`) -- this is your `SLACK_APP_TOKEN`\n\n## Step 3: Configure Bot Scopes\n\n1. In the left sidebar, click **OAuth & Permissions**\n2. Scroll down to **Scopes** > **Bot Token Scopes**\n3. Add these scopes to bot token scopes:\n   - `app_mentions:read` -- Receive @mention events\n   - `chat:write` -- Send and edit messages (covers `chat.update` on the bot's own messages)\n   - `channels:history` -- Read messages in public channels (for thread context)\n   - `channels:join` -- Allow bot to join public channels\n   - `groups:history` -- Read messages in private channels (optional)\n   - `im:history` -- Read DM history (for DM support)\n   - `im:write` -- Send DMs\n   - `im:read` -- Read DM history (for DM support)\n   - `mpim:history` -- Read group DM history (optional)\n   - `mpim:write` -- Send group DMs\n   - `reactions:write` -- Add lifecycle reactions (🔄 / ✅ / ❌) to the triggering message\n   - `commands` -- Required for the `/archon` and `/archon-workflow` slash commands\n   - `users:read` -- Look up real names via `users.info` for user attribution. Without this scope messages still flow, but real names do not appear in the Archon database.\n\n## Step 4: Subscribe to Events\n\n1. In the left sidebar, click **Event Subscriptions**\n2. Toggle **Enable Events** to ON\n3. Under **Subscribe to bot events**, add:\n   - `app_mention` -- When someone @mentions your bot\n   - `message.im` -- Direct messages to your bot\n   - `message.channels` -- Messages in public channels (optional, for broader context)\n   - `message.groups` -- Messages in private channels (optional)\n4. Click **Save Changes**\n\n## Step 4b: Enable Interactivity\n\nInteractive buttons (gate decisions and Cancel on workflow runs) ride the same\nSocket Mode connection -- no public URL needed.\n\n1. In the left sidebar, click **Interactivity & Shortcuts**\n2. Toggle **Interactivity** to ON\n3. Leave the **Request URL** field blank -- Socket Mode handles routing\n4. Click **Save Changes**\n\n## Step 4c: Register Slash Commands\n\nTwo slash commands give the team an alternative to @mention:\n\n| Command | What it does |\n| --- | --- |\n| `/archon <message>` | Starts a new thread in the current channel. To continue an existing thread, post `@archon <message>` in that thread. |\n| `/archon-workflow <subcommand>` | Direct workflow control. Supports `list`, `status`, `get <id>`, `run <name> <args>`, `approve <id> [comment]`, `reject <id> [reason]`, `respond <id> <decision> [text]`, `abandon <id>`, `resume <id>`. |\n\nFor each command:\n\n1. In the left sidebar, click **Slash Commands**\n2. Click **Create New Command**\n3. Fill in:\n   - **Command**: `/archon` (or `/archon-workflow`)\n   - **Request URL**: leave blank -- Socket Mode handles routing\n   - **Short Description**: e.g. \"Talk to Archon\" or \"Archon workflow control\"\n   - **Usage Hint**: e.g. `<message>` or `<subcommand>`\n4. Save\n\nReinstall the app (Step 5) after adding scopes or commands so Slack issues a\nfresh token with the new permissions.\n\n## Step 5: Install to Workspace\n\n1. In the left sidebar, click **Install App**\n2. Click **Install to Workspace**\n3. Review the permissions and click **Allow**\n4. **Copy the Bot User OAuth Token** (starts with `xoxb-`) -- this is your `SLACK_BOT_TOKEN`\n\n## Step 6: Set Environment Variables\n\nAdd both tokens to the server's env file (`~/.archon/.env`, or the compose `.env` in Docker):\n\n```ini\nSLACK_BOT_TOKEN=xoxb-your-bot-token\nSLACK_APP_TOKEN=xapp-your-app-token\n```\n\nIn Docker, a nonempty `SLACK_BOT_TOKEN` declares installation at startup, including tokens in `/.archon/.env`. Both tokens are required for a connection.\n\nThe plugin reads them from the environment the server starts it with. If either is missing, the plugin fails to start and the server does not retry it.\n\n## Step 7: Install the Slack Plugin\n\nWith the release binary or Homebrew:\n\n```bash\narchon plugin add coleam00/Archon/plugins/chat-slack\n```\n\nIn a source checkout, run `bun run cli plugin add coleam00/Archon/plugins/chat-slack`.\n\nIn Docker, restart the container with its tokens configured. The image-provided release installs offline before the server starts, writes a verified receipt to `/.archon/plugins`, and logs why it was provisioned. No manual install is needed. A valid installed Slack plugin is retained at its current version. See [Building the image](/deployment/docker/#building-the-image) for the release pin and build args.\n\nWithout a tag, manual `plugin add` installs the latest release. Restart the server; Slack appears in `/api/health` once the plugin connects. Keep the plugin current with `archon plugin update coleam00/Archon/plugins/chat-slack`, and remove it with `archon plugin remove coleam00/Archon/plugins/chat-slack`. In Docker, remove Slack tokens from every effective env source as well, or the image plugin is reinstalled on restart. Outside Docker, removal followed by a restart keeps Slack off. See [`archon plugin`](/reference/cli/#plugin).\n\n## Step 8: Invite Bot to Channel\n\n1. Go to the Slack channel where you want to use the bot\n2. Type `/invite @your-bot` (your bot's display name)\n3. The bot should now respond to @mentions in that channel\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users:\n1. In Slack, go to a user's profile > click \"...\" > \"Copy member ID\"\n2. Add to environment:\n\n```ini\nSLACK_ALLOWED_USER_IDS=U01ABC123,U02DEF456\n```\n\nWhen set, only listed user IDs can interact with the bot. When empty/unset, the bot responds to all users. The server enforces this list, for messages and for button clicks alike, before anything reaches Archon.\n\n## Configure Streaming Mode (Optional)\n\n```ini\nSLACK_STREAMING_MODE=batch  # batch (default) | stream\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Usage\n\nSelect a registered project by posting `@your-bot /setproject <name>` in the target thread. To register a local directory first, post `@your-bot /register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nSlack registers `/archon` and `/archon-workflow`, not raw `/setproject` or `/register-project` slash commands. `/archon <message>` creates a new thread; use an @mention in the existing thread to scope that conversation.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\n### @Mention in Channels\n\n```\n@your-bot /clone https://github.com/user/repo\n```\n\n### Continue Work in Thread\n\nReply in the thread created by the initial message:\n\n```\n@your-bot /status\n```\n\n### Start Parallel Work (Worktree)\n\n```\n@your-bot /worktree feature-branch\n```\n\n### Direct Messages\n\nYou can also DM the bot directly -- no @mention needed:\n\n```\n/help\n```\n\n## In-Thread UX\n\nWhen a workflow runs in a Slack thread, Archon now:\n\n- Adds 🔄 to your triggering message when the run starts, and swaps it for ✅\n  on execution completion or ❌ on execution failure / cancellation. If a declared\n  authored outcome disagrees, it also adds the outcome reaction, so both facts remain visible\n- Posts a single status message in the thread that's edited in place as DAG\n  nodes start, complete, fail, or get skipped\n- When approval details are available, renders the gate's declared decisions as\n  buttons, using labels when supplied and sending the exact decision ID.\n  Legacy gates keep **Approve** / **Reject**.\n  The fallback text lists `/archon-workflow respond <id> <decision> [text]` commands;\n  use these to include optional text with a decision. For a parent blocked on a\n  child run, these parent controls redirect to the child instead of recording a\n  parent decision. Answer the child run using the displayed child command\n- If approval details cannot be loaded, posts an **Approval details unavailable**\n  notice naming the run instead of decision buttons or response commands. Check\n  the run status before responding\n- Adds a **Cancel** button on the status message while the run is\n  non-terminal, so the run's starter or an admin can abandon a runaway run\n  with a click\n- Appends a small italic cost / token footer after direct-chat replies\n  (e.g. `_cost: $0.0234 · 12.4k tokens · stop: end_turn_`) and on the\n  final status message when a workflow completes\n- Labels execution status and a non-null authored outcome separately in the final status message;\n  workflows without an authored outcome retain the existing status-only message\n- Annotates long responses split across multiple Slack messages with\n  `_part i/n_` footers so it's clear they belong together\n\nAll gate-decision and Cancel clicks run through the same\n`SLACK_ALLOWED_USER_IDS` whitelist as inbound messages -- clicks from users\noutside it are dropped. A member who is not the run's starter or an admin sees\na refusal only they can see.\n\n`/archon connect github` replies with a pointer to the Web UI or\n`archon auth github`; it does not run the GitHub device flow in Slack.\n\n## Upgrading From the Bundled Slack Adapter\n\nEarlier Archon versions ran Slack inside the server whenever `SLACK_BOT_TOKEN` and\n`SLACK_APP_TOKEN` were set. The server no longer contains that adapter: without the\nplugin, Slack stays off. When `SLACK_BOT_TOKEN` is set and no Slack plugin is installed,\nstartup logs a `chat.bundled_adapter_retired` warning with Docker provisioning guidance and the manual install command.\n\nIn Docker, existing token configuration provisions the image-provided plugin offline on restart; no manual step is needed. The boot log names the release and why it was installed. Outside Docker, install the plugin (Step 7) and restart. Nothing else changes: the tokens, the Slack app\nconfiguration, `SLACK_ALLOWED_USER_IDS` and `SLACK_STREAMING_MODE` stay as they are, and\nexisting conversations, linked identities and workspaces carry over because the plugin\nkeeps the platform id `slack`.\n\n## Troubleshooting\n\n### Bot Doesn't Respond\n\n1. Check that the plugin is installed: `archon plugin list` shows `coleam00/Archon/plugins/chat-slack`\n2. Check that Socket Mode is enabled\n3. Verify both tokens are correct in `.env`, then check the server log for `chat.bundled_adapter_retired` or a plugin start failure\n4. Ensure the bot is invited to the channel\n5. Make sure you're @mentioning the bot (not just typing)\n\n### \"channel_not_found\" Error\n\nThe bot needs to be invited to the channel:\n\n```\n/invite @your-bot\n```\n\n### \"missing_scope\" Error\n\nAdd the required scope in **OAuth & Permissions** and reinstall the app.\n\n### Thread Context Not Working\n\nEnsure these scopes are added:\n\n- `channels:history` (public channels)\n- `groups:history` (private channels)\n\n## Security Recommendations\n\n1. **Use User Whitelist**: Set `SLACK_ALLOWED_USER_IDS` to restrict bot access\n2. **Private Channels**: Invite the bot only to channels where it's needed\n3. **Token Security**: Never commit tokens to version control\n\n## Reference Links\n\n- [Slack API Documentation](https://api.slack.com/docs)\n- [Bolt for JavaScript](https://tools.slack.dev/bolt-js/)\n- [Socket Mode Guide](https://api.slack.com/apis/connections/socket)\n- [Permission Scopes](https://api.slack.com/scopes)\n","packages/docs-web/src/content/docs/adapters/telegram.md":"---\ntitle: Telegram\ndescription: Connect Archon to Telegram using the Bot API for mobile and desktop access.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 3\n---\n\nConnect Archon to Telegram so you can interact with your AI coding assistant from any Telegram client.\n\n:::caution[Deprecated]\nThe Telegram adapter is deprecated and will be removed in the next minor release, 0.13.0. No Archon maintainer owns it. It keeps working until then, and the server logs a deprecation notice when it starts the adapter. If you need Telegram after that, build and own it as a [chat plugin](/guides/publishing-plugins/#chat-plugins). [#4641](https://github.com/coleam00/Archon/issues/4641) tracks the removal.\n:::\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Telegram account\n\n## Create Telegram Bot\n\n1. Message [@BotFather](https://t.me/BotFather) on Telegram\n2. Send `/newbot` and follow the prompts\n3. Copy the bot token (format: `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`)\n\n## Set Environment Variable\n\n```ini\nTELEGRAM_BOT_TOKEN=123456789:ABCdefGHI...\n```\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users:\n1. Message [@userinfobot](https://t.me/userinfobot) on Telegram to get your user ID\n2. Add to environment:\n\n```ini\nTELEGRAM_ALLOWED_USER_IDS=123456789,987654321\n```\n\nWhen set, only listed user IDs can interact with the bot. When empty/unset, the bot responds to all users.\n\n## Configure Streaming Mode (Optional)\n\n```ini\nTELEGRAM_STREAMING_MODE=stream  # stream (default) | batch\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Project selection\n\nSelect a registered project by submitting `/setproject <name>` in the conversation or thread where you want to work. To register a local directory first, submit `/register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","packages/docs-web/src/content/docs/adapters/web.md":"---\ntitle: Web UI\ndescription: Built-in console for operating Archon from a browser.\ncategory: adapters\narea: adapters\naudience: [user]\nstatus: current\nsidebar:\n  order: 1\n---\n\nThe Web UI is Archon's built-in management console. It uses Archon's public API\nand does not require a separate hosted service.\n\n## Start the console\n\nFor local development, start the API server and Vite frontend together:\n\n```bash\nbun run dev\n```\n\nThe console is available at `http://localhost:5173`; the API server listens on\n`http://localhost:3090`.\n\nYou can also run them separately:\n\n```bash\nbun run dev:server\nbun run dev:web\n```\n\nFor a production build, the API server serves the compiled console on port 3090:\n\n```bash\nbun run build\nbun run start\n```\n\nThe backend binds to `0.0.0.0` by default. The Vite development server binds to\nlocalhost; pass `--host 0.0.0.0` only when you intentionally want to expose it:\n\n```bash\nbun run dev:web -- --host 0.0.0.0\n```\n\n## Authentication\n\nAuthentication is optional. A solo installation with Web authentication disabled\nopens the console directly. When authentication is enabled, the same console\nrequires a session and provides sign-in, signup when allowed by server policy,\nand sign-out.\n\n## Console navigation\n\nThe project rail is the main navigation surface:\n\n- **Runs** at `/console` shows runs across projects. Selecting a\n  project opens its scoped runs at `/console/p/:projectId`.\n- **Chat** at `/console/p/:projectId/chat` opens the operator conversation for\n  that project.\n- **Settings** at `/console/settings` manages assistant defaults, model tiers,\n  aliases, provider credentials, system status, GitHub identity, and alerts.\n- **Workflow builder** at `/console/builder` opens the experimental visual\n  authoring surface. Select a project before opening or creating a workflow.\n\nRegister a project with the add button in the rail. Remote Git URLs are cloned;\nlocal paths are registered in place. Removing a project removes its registration,\nnot its files.\n\n## Workflows and runs\n\nChoose a workflow from a project's run or chat view, supply its inputs, and\nlaunch it. The console follows execution through live events and exposes the\ngovernance actions that apply to the current state, including approval, rejection,\nresume, cancel, and abandon.\n\nA run can be opened from a project at\n`/console/p/:projectId/r/:runId` or directly by ID at\n`/console/r/:runId`. Direct links also work for runs that have no registered\nproject. Run details include the event log and artifacts; a graph is shown when\nthe run has the project context needed to load its workflow definition.\n\nTurn on **Alerts** in Settings to hear a sound, and see a browser notification\nwhere the browser allows it, when a run stops at an approval gate or an\nattention wait, or when it completes, fails, or is cancelled. The setting\nis saved in the current browser and is off by default. Runs that were already\nwaiting or finished when the console loaded do not alert. Settings shows when\nthe browser blocks the sound or notifications.\n\nOld settings, workflow-list, workflow-builder, and run bookmarks redirect to the\nclosest console route. Old chat and other retired pages land on the console\noverview because the conversation browser is no longer shipped.\n\n## Project chat\n\nProject chat streams assistant text and tool activity. When Web authentication\nis enabled, the console requests the signed-in user's project conversation and\nsends the active identity with each turn. On a solo installation, chat works\nwithout a user identity.\n\nAttachments are limited to five files of at most 10 MB each. The first message in\na new conversation cannot include attachments; create the conversation with a\ntext message, then attach files on the next turn.\n\n## Experimental workflow builder\n\nThe builder is retained as a bounded experiment inside the console. Use its\ncanvas, inspector, validation, and YAML preview to draft new workflows with\nprompt, command, bash, script, loop, approval, wait, and cancel nodes. Editing an\nexisting workflow and relying on a load/edit/save round trip is not supported\nuntil [#3378](https://github.com/coleam00/Archon/pull/3378) and\n[#3379](https://github.com/coleam00/Archon/pull/3379) land. Edit existing\nworkflows as YAML. The builder also does not represent include directives, loop\ngroups, or workflow sub-run nodes. Workflow YAML remains the authoritative\nartifact.\n\n## Further reading\n\n- [Quickstart](/getting-started/quick-start/)\n- [Configuration](/getting-started/configuration/)\n- [Workflow language](/reference/workflow-language/)\n- [API Reference](/reference/api/)\n","packages/docs-web/src/content/docs/brand/index.md":"---\ntitle: Brand\ndescription: Archon brand foundation — colors, logo, and usage guidelines.\n---\n\n## Quick reference\n\nThe essentials at a glance:\n\n- **Primary gradient** — <code style=\"color:#ED10EC\">#ED10EC</code> → <code style=\"color:#8E40C8\">#8E40C8</code> → <code style=\"color:#06CE94\">#06CE94</code>\n- **Surface** — <code style=\"color:#0F1115;background:#fff;padding:0 6px;border-radius:3px\">#0F1115</code>\n- **Logo** — the shield mark with the abstract Archon glyph, stroked with the primary gradient\n\nUse the embedded foundation below for the canonical version. If you need a vector asset or have a question about an unusual usage, open a discussion on [GitHub](https://github.com/coleam00/Archon/discussions).\n\n## Full brand sheet\n\nThe Archon brand foundation lives below. It's a single self-contained page covering the logo, color system, typography, and approved usage.\n\n<iframe\n  src=\"/brand/foundation.html\"\n  title=\"Archon Brand Foundation\"\n  style=\"width:100%;height:80vh;border:1px solid var(--sl-color-gray-5);border-radius:8px;background:#0F1115;\"\n  loading=\"lazy\"\n></iframe>\n\nPrefer a full-window view? [Open the brand sheet in a new tab](/brand/foundation.html).\n","packages/docs-web/src/content/docs/contributing/cli-internals.md":"---\ntitle: CLI Internals\ndescription: Technical reference for the Archon CLI package — entry point flow, command routing, worktree logic, and adapter details.\ncategory: contributing\narea: cli\naudience: [developer]\nstatus: current\nsidebar:\n  order: 2\n---\n\nTechnical reference for understanding CLI internals.\n\n## Package Structure\n\n```\npackages/cli/\n├── src/\n│   ├── cli.ts              # Entry point, argument parsing, routing\n│   ├── commands/\n│   │   ├── workflow.ts     # workflow route module: re-exports every subcommand; reset-sessions/cleanup/event\n│   │   ├── workflow-control.ts # workflow resume/abandon/cancel/answer/approve/reject/respond (delegate to @archon/core/operations)\n│   │   ├── workflow-query.ts # workflow list/test/status/get/runs/logs/wait\n│   │   ├── workflow-run-*.ts # workflow run: select, admit, launch, execute\n│   │   ├── isolation.ts    # isolation list/cleanup (list/merged-cleanup delegate to @archon/core/operations)\n│   │   ├── setup.ts        # setup command implementation\n│   │   ├── chat.ts         # chat command implementation\n│   │   ├── validate.ts     # validate command implementation\n│   │   └── version.ts      # version command\n│   └── adapters/\n│       └── cli-adapter.ts  # IPlatformAdapter for stdout\n└── package.json            # Defines \"archon\" binary\n```\n\n## Entry Point Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon <command> [subcommand] [options] [arguments]             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ strip-cwd-env-boot  (first import, side-effect)                 │\n│   stripCwdEnv(): deletes Bun-auto-loaded <cwd>/.env* keys from  │\n│   process.env + CLAUDE_CODE_* session markers. Emits            │\n│   [archon] stripped N keys from <cwd> (...) when N > 0.         │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ loadCliEnv(cwd) — wraps loadArchonEnv with identity protection  │\n│   1. ~/.archon/.env        (home scope)                         │\n│   2. <cwd>/.archon/.env    (repo scope, exceptions below)       │\n│   Emits one [archon] loaded N keys from <path> line per file    │\n│   when N > 0 and ARCHON_VERBOSE_BOOT=1 or LOG_LEVEL=debug/trace.│\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Parse arguments                                         │\n│                 --cwd, --branch, --no-worktree, --help          │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Project resolution                                      │\n│                 Honor folder selection and registered folders;  │\n│                 otherwise resolve the nearest Git checkout      │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Route to command handler                                │\n│                 switch(command) → workflow | isolation | version│\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Exit with code, always closeDatabase()                  │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/cli.ts`\n\nRepository env normally overrides home env. The CLI preserves the user-scope `GH_CONFIG_DIR` (including its absence), so repository env cannot redirect the stored gh login. Inside a workflow, inherited GitHub identity values take precedence over both env files, including empty values that disable host credentials. See [forge plugin configuration](/reference/forge/#plugin-configuration).\n\n**Project resolution:**\n\n- `workflow run --folder` uses the supplied canonical directory, even inside Git, and explicitly registers it as a folder project.\n- A registered folder and its ordinary descendants resolve to the folder directory. Archon runs folder workflows in place without managing a worktree, branch or PR.\n- A Git repository nested inside a registered folder retains its own repository identity. Other Git paths resolve to the nearest repository root, preserving linked-checkout ownership.\n- Unregistered non-Git execution requires folder registration; first-use CLI execution uses `workflow run --folder`. Without registration or that flag, execution exits with code 1.\n- File-only dry runs and fixture tests can inspect local files without registration. On a readable registry they honor folder ownership; if lookup fails they use the proven Git root or supplied directory. `workflow list` and `validate` retain their Git-root fallback when the registry cannot open.\n- `version`, `help`, `setup`, and `chat` bypass project resolution.\n\n---\n\n## `workflow list` Flow\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ archon workflow list [name] [--full] [--json]                    │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ workflow-query.ts  workflowListCommand(cwd, { name, full, json })│\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ @archon/workflows/workflow-discovery                              │\n│ discoverWorkflowsWithConfig(cwd, loadConfig, providerRegistry)    │\n│ - Loads bundled defaults                                         │\n│ - Searches .archon/workflows/ recursively                        │\n│ - Merges (repo overrides defaults by name)                       │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ Optional name resolution; project errors remain in the result    │\n│ full=false: bounded description + structured truncation state    │\n│ full=true: exact authored description                            │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n               ┌───────────────┴───────────────┐\n               │ json=true                     │ json=false\n               ▼                               ▼\n┌──────────────────────────┐   ┌───────────────────────────────────┐\n│ JSON output to stdout    │   │ Human-readable list to stdout     │\n│ descriptionTruncated     │   │ shortened descriptions carry     │\n│ { workflows, errors }    │   │ the ` [truncated]` marker         │\n└──────────────────────────┘   └───────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/commands/workflow-query.ts`\n\n---\n\n## `workflow run` Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon workflow run <name> [message] [--branch X] [--detach] …  │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ selectRunWorkflow()                                             │\n│ - Locate the run a continuation resumes                         │\n│ - Capture the workflow source, then discover from the capture   │\n│ - Find the workflow by name (error lists available)             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ admitRunExecution()   (--dry-run stops before this)             │\n│ - Flag, worktree policy, input and capability gates             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                    ┌─────────────┴─────────────┐\n                    │                           │\n                --detach                    (default)\n                    │                           │\n                    ▼                           ▼\n┌───────────────────────────┐   ┌───────────────────────────────────┐\n│ launchDetachedOwnedRun()  │   │ prepareForegroundLaunch()         │\n│ Pre-flight, write the run │   │ Destination, project, adoption    │\n│ row, fork the child       │   │ lane, --resume binding, AI config │\n└───────────────────────────┘   └───────────────┬───────────────────┘\n                                                │\n                                                ▼\n                                ┌───────────────────────────────────┐\n                                │ provisionWorkspace()              │\n                                │ Folder seam, reused --branch      │\n                                │ worktree, or a new worktree       │\n                                └───────────────┬───────────────────┘\n                                                │\n                                                ▼\n                                ┌───────────────────────────────────┐\n                                │ executeOwnedRun()                 │\n                                │ Signal guard, progress, engine    │\n                                │ submit or resume, wait, teardown, │\n                                │ result                            │\n                                └───────────────────────────────────┘\n```\n\n**Code:** `workflowRunCommand` in `packages/cli/src/commands/workflow-run-owned.ts`, which owns the captured source and the durable-wait loop. The phases live in `workflow-run-admission.ts`, `workflow-run-launch.ts` and `workflow-run-execution.ts` beside it.\n\n**Worktree Provider:** `packages/isolation/src/providers/worktree.ts`\n\n---\n\n## `workflow event emit` Flow\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ archon workflow event emit --run-id <run-id> --type <type> [...] │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ cli.ts  Validate --run-id, --type (required)                     │\n│         Validate --type against WORKFLOW_EVENT_TYPES              │\n│         Parse --data as JSON (warn + skip if invalid)            │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ workflow.ts  workflowEventEmitCommand(..., cwd)                   │\n│              Resolve an unambiguous run-id prefix                 │\n│              Node state: persistWorkflowEvent(...)               │\n│              Observability: createWorkflowEvent(...)             │\n│              Run-ID resolution may fail                           │\n└──────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/cli.ts` (case 'event'), `packages/cli/src/commands/workflow.ts:workflowEventEmitCommand`\n\n**Contract:** The shared `isNodeStateEventType` predicate routes node-state events through\n`persistWorkflowEvent`, which propagates storage failures. The CLI prints `Event persisted`\nonly after that write succeeds. Other events use `createWorkflowEvent` and retain best-effort\npersistence: `Event submitted (best-effort)` does not guarantee storage. Run-ID resolution\ncan fail before either write.\n\n---\n\n## `isolation list` Flow\n\n```text\narchon isolation list → isolationListCommand()\n  │\n  ├─ @archon/core listEnvironments()\n  │    ├─ isolationDb.listAllActiveWithCodebase()\n  │    ├─ For each codebase: isolationDb.listByCodebaseWithAge()\n  │    │    └─ Reconcile missing worktrees, preserving live-run ownership\n  │    └─ Return environments grouped by codebase and reconciliation totals\n  │\n  ├─ Print worktree details and totals (return early if no codebases)\n  │\n  ├─ @archon/core listContainerEnvironments()\n  │    ├─ isolationDb.listActiveContainerEnvironments()\n  │    ├─ getRunByIsolationEnvId() for each container\n  │    │    └─ Return matched run or null; throw on query/status errors\n  │    └─ Build summaries; catch lookup errors and set runStatus: lookup-failed\n  │\n  └─ Print container details and totals: run status, no run (orphan),\n     or run lookup failed\n```\n\n**Code:** `packages/cli/src/commands/isolation.ts` (`isolationListCommand`),\n`packages/core/src/operations/isolation-operations.ts`\n(`listEnvironments`), `packages/core/src/services/cleanup/containers.ts`\n(`listContainerEnvironments`), `packages/core/src/db/workflow-run-reads.ts`\n(`getRunByIsolationEnvId`).\n\nThe container listing matches `metadata.isolation_env_id`; no match does not\nestablish that cleanup can remove the container. Cleanup checks wider live\nownership. A failed lookup is logged and displayed as `run lookup failed`.\n\n---\n\n## `isolation cleanup` Flow\n\n```text\narchon isolation cleanup [days] (default: 7)\n  │\n  ├─ Reconcile missing worktrees (preserve container rows)\n  │\n  ├─ findStaleEnvironments(days)\n  │    └─ Filter provider = 'worktree'\n  │         └─ For each stale worktree:\n  │              ├─ Skip environments owned by resumable runs\n  │              ├─ Skip any with a commit on no remote (getCleanupRunLock)\n  │              └─ provider.destroy(path, options)\n  │                   └─ Mark destroyed only after complete removal\n  │\n  └─ cleanupContainerEnvironments(days), even when no worktrees are stale\n       ├─ Skip containers owned by resumable runs\n       └─ Container backend removes container and volume\n            └─ Mark destroyed only after successful removal\n```\n\n**Code:** `isolationCleanupCommand` in `packages/cli/src/commands/isolation.ts`\n\n---\n\n## `isolation cleanup --merged` Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon isolation cleanup --merged [--include-closed]            │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ isolation.ts  isolationCleanupMergedCommand({ includeClosed })  │\n│ For each codebase → cleanupMergedWorktrees(codebaseId, path)    │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                     For each active environment\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ judgeBranchForRemoval() — three-signal union                    │\n│  localBranchExists()     gate: (a) and (b) need the local ref   │\n│  (a) isBranchMerged()    git ancestry (fast-forward/merge)      │\n│  (b) isPatchEquivalent() git cherry  (1-commit squash-merge)    │\n│  (c) getPrState()        gh CLI      (MERGED/CLOSED/OPEN/NONE/  │\n│                                      UNAVAILABLE)               │\n│                                                                 │\n│  any git signal          → 'merged' if the worktree HEAD        │\n│                            also passes `git cherry <base> HEAD` │\n│  MERGED                  → 'merged' if the worktree HEAD        │\n│                            and the branch ref (where each       │\n│                            exists) are at or behind the PR head │\n│  CLOSED                  → 'pr-closed' the same way, only if    │\n│                            includeClosed                        │\n│  OPEN                    → 'open-pr', always kept               │\n│  UNAVAILABLE             → 'pr-unavailable', kept and reported  │\n│  NONE and ref present    → 'unmerged', not reclaimed            │\n│  NONE and ref gone       → 'unjudgeable', kept and reported     │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │ 'merged' or 'pr-closed'\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ Guard checks: no uncommitted changes, no run lock (checkRemoval │\n│ → getCleanupRunLock; a failed run's lock is released by the     │\n│ verdict, running/pending/paused runs always lock)               │\n│ provider.destroy() → remove worktree and local branch           │\n│ → commit registration retirement; retain remote branch         │\n└─────────────────────────────────────────────────────────────────┘\n```\n\nCompletion and merged cleanup retain remote branches, including with force,\nbecause registration ownership cannot be proved after retirement. Retention is\nlogged as a warning and displayed in both completion and merged-cleanup reports.\nEnable GitHub's **Automatically delete head branches** setting for\nmerged pull requests, or follow [manual remote branch recovery](/reference/cli/#recovering-remote-branch-cleanup).\n\nSignals are evaluated in order — the first positive match short-circuits to avoid\nunnecessary `gh` API calls. The `gh` CLI is a soft dependency: when it is not installed or\nthe remote is not GitHub, only git signals are used (`NONE`). When `gh` is there but fails\n(auth, rate limit, unreadable output) the result is `UNAVAILABLE`, and the environment is\nkept and reported rather than passed to the staleness sweep.\n\nA multi-commit squash merge is invisible to both git signals, so PR state is the only\none that sees it. Both git signals also need the local branch ref: once it is gone, the\nPR is the only judge, and a branch with no PR is reported as unverifiable instead of\nfailing the check. A MERGED or CLOSED PR only covers the commits it carried. Run\nbranch names are reused, and removal deletes both the worktree and the branch, so each\nlocal tip that still exists, the worktree's HEAD and the branch ref, must be the PR's head\ncommit or an ancestor of it (`isRevCoveredBy`). A detached HEAD or a reused branch with\ncommits past the PR head is unmerged work. The same holds when git proves the merge: the worktree's\nHEAD must also pass `git cherry <base> HEAD`, which answers for both git signals (an\nancestor of the base lists nothing, a squash-merged commit lists as `-`). A HEAD it\ncannot answer for is reported as a failed merge check. A PR head pushed from elsewhere and never fetched here is\nfetched by SHA from the branch's remote first; if that fetch fails, the check fails and\nthe worktree is kept, with the fetch error in the reason. The scheduled sweep (`runScheduledCleanup`) makes the same call, so\nboth paths agree on what counts as merged.\n\n**Code:** `packages/core/src/services/cleanup/` — `judgeBranchForRemoval()` in `merge-verdict.ts`, `cleanupMergedWorktrees()` in `merged-sweep.ts`, `getCleanupRunLock()` in `run-lock.ts`; `runScheduledCleanup()` in `packages/core/src/services/cleanup-service.ts`\n**Code:** `packages/isolation/src/pr-state.ts` — `getPrState()`\n**Code:** `packages/git/src/branch.ts` — `isPatchEquivalent()`, `localBranchExists()`\n\n---\n\n## CLI Adapter\n\nImplements `IPlatformAdapter` for terminal output.\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ CLIAdapter                                                      │\n├─────────────────────────────────────────────────────────────────┤\n│ sendMessage(convId, msg) → Output to stdout                     │\n│ getStreamingMode()       → 'batch'                              │\n│ getPlatformType()        → 'cli'                                │\n│ ensureThread()           → passthrough                          │\n│ start() / stop()         → no-op                                │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/adapters/cli-adapter.ts:13-47`\n\n---\n\n## Key Dependencies\n\n| Function | Package | Location | Purpose |\n|----------|---------|----------|---------|\n| `discoverWorkflowsWithConfig(cwd, loadConfig, providerRegistry)` | `@archon/workflows/workflow-discovery` | `workflows/src/workflow-discovery.ts` | Find and parse workflow YAML |\n| `executeWorkflow(...)` | `@archon/workflows/executor` | `workflows/src/executor.ts` | Run workflow steps |\n| `getIsolationProvider()` | `@archon/isolation` | `isolation/src/factory.ts` | Get WorktreeProvider singleton |\n| `conversationDb.*` | `@archon/core` | `core/src/db/conversations.ts` | Conversation CRUD |\n| `codebaseDb.*` | `@archon/core` | `core/src/db/codebases.ts` | Codebase CRUD |\n| `isolationDb.*` | `@archon/core` | `core/src/db/isolation-environments.ts` | Worktree tracking |\n| `git.*` | `@archon/git` | `packages/git/src/` | Git operations |\n| `closeDatabase()` | `@archon/core` | `core/src/db/connection.ts` | Clean shutdown |\n\n---\n\n## Conversation ID Format\n\nCLI conversations use ID format: `cli-{timestamp}-{random}`\n\nThe random suffix contains 16 cryptographically random bytes encoded as 32 hex characters.\n\nGenerated at: `packages/cli/src/utils/conversation-id.ts`\n\n---\n\n## Worktree Reuse Logic\n\nWhen `--branch` is provided:\n\n1. **Lookup:** `isolationDb.findActiveByWorkflow(codebaseId, 'task', branchName)`\n2. **Health check:** `provider.healthCheck(path)` on existing\n3. **Reuse:** If found and healthy (warns if `--from` was specified but not applied)\n4. **Create:** If not found or unhealthy -- passes a `taskBranch: { kind: 'new', fromBranch }`\n   selection to the provider when `--from` is specified. Adoption instead passes\n   `taskBranch: { kind: 'existing', branch }`, which checks out that exact local branch\n   without creating a child branch or syncing it to a remote.\n\nWorktrees stored at: `~/.archon/workspaces/<owner>/<repo>/worktrees/<branch-slug>/`\n\n**Code:** `provisionWorktree` in `packages/cli/src/commands/workflow-run-launch.ts`\n\n---\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| 0 | Success, except for the JSON control refusals noted below. |\n| 1 | Error. Answer commands (`answer`, `respond`, `approve`, `reject`) and `resume --detach` also return 1 on refusal. Human-mode diagnostics go to stderr; JSON refusals emit `ok: false` with the reason on stdout. |\n\nNon-detached JSON `resume`, `abandon`, and `cancel` can return 0 with `ok: false`.\nCheck `ok` when consuming those acknowledgments.\n\n---\n\n## Database Connection\n\n- Connection opened on first database call\n- Always closed in `finally` block after command completes\n- **Default: SQLite** at `~/.archon/archon.db` (zero setup, auto-initialized)\n- **Optional: PostgreSQL** when `DATABASE_URL` is set (for cloud/advanced deployments)\n\n**Code:** `packages/cli/src/cli.ts`\n","packages/docs-web/src/content/docs/contributing/dx-quirks.md":"---\ntitle: DX Quirks\ndescription: Known development experience quirks and workarounds when working on the Archon codebase.\ncategory: contributing\naudience: [developer]\nstatus: current\nsidebar:\n  order: 6\n---\n\nDevelopment experience notes and workarounds.\n\n## Bun Log Elision\n\nWhen running `bun dev` from the repo root, Bun's `--filter` truncates logs:\n\n```\n@archon/server dev $ bun --watch src/index.ts\n│ [129 lines elided]\n│ [Hono] Server listening on port 3090\n└─ Running...\n```\n\n**To see full logs**, run directly from the server package:\n\n```bash\ncd packages/server && bun --watch src/index.ts\n```\n\nOr:\n\n```bash\nbun --cwd packages/server run dev\n```\n\nNote: The root `bun dev` uses `--filter` to fix hot reload path issues, but this comes with log condensing.\n\n## `mock.module()` merges over the real module\n\nBun merges a `mock.module()` factory over the real module: an export the factory leaves out keeps its real implementation. When a mocked module gains an export, every factory that does not list it starts running real code (a database, the network, the filesystem) while its tests still pass, often until a 5-second timeout.\n\n`bun run check:mock-modules` (validate id `mock-modules`, run in CI) fails on every factory that leaves out a value export of the module it mocks, naming the file, line, specifier and missing names. Mock each missing export, or spread the real module to keep the rest real on purpose:\n\n```ts\nmock.module('../db/workflows', async () => ({\n  ...(await import('../db/workflows')),\n  getWorkflowRun: mockGetWorkflowRun,\n}));\n```\n\nA spreading factory provides every export, including ones the module gains later; that is the author's declaration. Where test files share a process (packages other than `@archon/core`, unless the file has its own group), the `import()` can return a module an earlier file already mocked.\n\nFactories that were incomplete when the check landed are grandfathered in `scripts/mock-module-baseline.json`. A grandfathered site may omit only the exports its module's grandfathered sites omitted then, so a new export still fails at every one of them. The baseline only shrinks: after completing a factory, run `bun run check:mock-modules --write`. `bun run check:ratchet` refuses entries the baseline gains unless a commit in the change carries `Ratchet-Allow: scripts/mock-module-baseline.json`.\n\nWhat the check does not prove:\n\n- It reads factory keys from the factory's type, so an `as` cast can misstate them.\n- It checks only literal specifiers, and refuses a non-literal one.\n- It refuses a module TypeScript cannot read (no type declarations, or a path that does not resolve) unless the site is grandfathered.\n- A grandfathered factory can drop a key that another grandfathered site of the same module already omits.\n- It says nothing about a client a test never stubbed, or about I/O outside `mock.module()`. The adapters package preloads `no-network` (it traps only `globalThis.fetch`) and `no-archon-home-writes` guards for part of that; Bun reads `bunfig.toml` only from the working directory, so those guards apply only when the tests run from `packages/adapters`. Their headers list the limits.\n- It runs in validate and CI, not inside `bun test`.\n\n## `mock.module()` Pollution\n\nBun's `mock.module()` is process-global and irreversible — `mock.restore()` does NOT undo it.\n\n- Never add `afterAll(() => mock.restore())` for `mock.module()` cleanup — it has no effect\n- Use `spyOn()` for internal modules that other test files import directly (e.g., `spyOn(git, 'checkout')`) — `spy.mockRestore()` DOES work for spies\n- Never `mock.module()` a module path that another test file also `mock.module()`s with a different implementation\n- When adding a new test file with `mock.module()` to `packages/core`, make its first line exactly `// @archon-test-isolated` so it runs in its own process. Core discovers its tests, and discovery fails on a file that calls `mock.module()` without the marker. A file that mocks through a helper needs the marker too; discovery cannot see that call\n- In the other packages, give the file its own entry in `testGroups` in the package's `package.json`, or its own `bun test` step for a package still using a `&&` chain\n- A package's `testConcurrency` in `package.json` runs that many `testGroups` at once, each still in its own process; leave it out to run groups one at a time\n- Groups start slowest first, by the times in `scripts/test-weights.json`. CI splits slow shards into parts with `ARCHON_TEST_PART=<index>/<count>`, using the same times; `ARCHON_TEST_PART=1/2 bun run test` in a package runs that part locally. A new group with no recorded time counts as one second, so refresh the file with `bun scripts/update-test-weights.ts <run-id>` when one part of a shard finishes well after its sibling\n\n## Worktree Port Allocation\n\nWorktrees auto-allocate ports (3190–4089 range, hash-based on path). Same worktree always gets same port.\n\n- Main repo defaults to 3090\n- Override: `PORT=4000 bun dev`\n- Same worktree always gets same port (deterministic)\n- If that port is already taken — another worktree hashed to it, or any unrelated\n  process holds it — the server takes the next free port in the range and logs\n  `worktree_port_reallocated` with both the port it wanted and the one it got.\n  The root `bun dev` launcher passes that selected port to both the server and Web\n  client, so the Vite proxy and SSE streams use the endpoint the server binds. It\n  returns to the hashed port once that frees up.\n- `PORT` is never moved: an explicit override binds where you said, or fails.\n\n## `bun run test` vs `bun test`\n\n**NEVER run `bun test` from the repo root** — it discovers all test files across all packages and runs them in one process, causing ~135 mock pollution failures.\n\nAlways use `bun run test`, which runs each package's own grouped suite for isolation.\n\n### Running one test\n\n`bun run test <path>` runs that path.\n\n```bash\nbun run test packages/paths/src/skills.test.ts   # from the repo root\ncd packages/paths && bun run test src/skills.test.ts\n```\n\nFrom the repo root, an argument naming a path under `packages/<name>/`, `scripts/` or\n`.archon/scripts/` decides where `bun test` runs; flags and substring filters are\nforwarded unchanged. Paths naming several packages run one package at a time and stop at\nthe first failure. An argument that names no owner fails instead of running the suite.\n\nA package whose `scripts.test` is still a `bun test ... && bun test ...` chain rather than\n`bun run ../../scripts/package-tests.ts` ignores the argument when you run it from inside\nthe package: `bun run` appends arguments to the script, so the whole chain runs and a wrong\npath still reports a pass. Run those from the repo root, which routes the path itself.\n","packages/docs-web/src/content/docs/contributing/index.md":"---\ntitle: Contributing\ndescription: Developer guides for contributing to Archon — internals, testing, releasing.\ncategory: contributing\naudience: [developer]\nsidebar:\n  order: 0\n---\n\nGuides for developers contributing to the Archon codebase.\n\n## Developer Guides\n\n- **[New Developer Guide](/contributing/new-developer-guide/)** — Codebase orientation, architecture overview, and first steps\n- **[CLI Internals](/contributing/cli-internals/)** — CLI package internals, command routing, and worktree logic\n- **[Releasing](/contributing/releasing/)** — Version management and release process\n- **[DX Quirks](/contributing/dx-quirks/)** — Known development experience issues and workarounds\n\n","packages/docs-web/src/content/docs/contributing/new-developer-guide.md":"---\ntitle: New Developer Guide\ndescription: Codebase orientation for new Archon developers — architecture overview, workflows, platforms, and first steps.\ncategory: contributing\naudience: [developer]\nstatus: current\nsidebar:\n  order: 1\n---\n\n> **TL;DR**: Archon lets you control AI coding assistants (Claude Code, Codex) from your phone via Telegram, Slack, Discord, or GitHub. Think of it as a remote control for AI pair programming.\n\n---\n\n## The Problem We Solve\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│                        WITHOUT ARCHON                               │\n│                                                                     │\n│   You're on the train, phone in hand...                            │\n│                                                                     │\n│   ┌──────────┐     ❌ Can't SSH      ┌──────────────────┐          │\n│   │  Phone   │ ──────────────────────│  Dev Machine     │          │\n│   │          │     ❌ No terminal    │  (Claude Code)   │          │\n│   └──────────┘     ❌ No IDE         └──────────────────┘          │\n│                                                                     │\n│   \"I wish I could just message Claude to fix that bug...\"          │\n└─────────────────────────────────────────────────────────────────────┘\n\n┌─────────────────────────────────────────────────────────────────────┐\n│                         WITH ARCHON                                 │\n│                                                                     │\n│   ┌──────────┐                       ┌──────────────────┐          │\n│   │  Phone   │ ─────Telegram────────▶│  Archon Server   │          │\n│   │          │     \"fix issue #42\"   │                  │          │\n│   └──────────┘                       │  ┌────────────┐  │          │\n│        │                             │  │Claude Code │  │          │\n│        │                             │  │   SDK      │  │          │\n│        │                             │  └─────┬──────┘  │          │\n│        │                             │        │         │          │\n│        │                             │  ┌─────▼──────┐  │          │\n│        │◀────\"PR created #127\"───────│  │ Git Repo   │  │          │\n│        │                             │  │ (worktree) │  │          │\n│                                      │  └────────────┘  │          │\n│                                      └──────────────────┘          │\n│                                                                     │\n│   You just fixed a bug from your phone.                            │\n└─────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Core Concept: Message → AI → Code → Response\n\n```\n┌──────────────────────────────────────────────────────────────────────────┐\n│                                                                          │\n│   USER                    ARCHON                         CODEBASE        │\n│                                                                          │\n│   ┌─────────┐            ┌─────────────────┐            ┌──────────┐    │\n│   │Telegram │            │                 │            │          │    │\n│   │  Slack  │───Message─▶│   Orchestrator  │───Claude──▶│ Git Repo │    │\n│   │ Discord │            │                 │   Code     │          │    │\n│   │ GitHub  │◀──Response─│   (routes to    │◀──────────│ (files)  │    │\n│   └─────────┘            │    AI client)   │            └──────────┘    │\n│                          └─────────────────┘                             │\n│                                                                          │\n│   That's it. You message, AI works on code, you get results.            │\n│                                                                          │\n└──────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## The Four Ways to Use Archon\n\n### 1. Command Line (Local Execution)\n\nRun workflows directly from your terminal without needing the server:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ TERMINAL                                                        │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ $ bun run cli workflow list                                     │\n│                                                                 │\n│ Available workflows in .archon/workflows/:                     │\n│   - archon-ship                  Issue to reviewed PR           │\n│   - archon-investigate           Prove a root cause             │\n│   - archon-review                Parallel PR review             │\n│                                                                 │\n│ $ bun run cli workflow run archon-investigate \"What does the    │\n│   orchestrator do?\"                                             │\n│                                                                 │\n│ 🔧 READ                                                         │\n│ Reading: packages/core/src/orchestrator/orchestrator.ts                │\n│                                                                 │\n│ The orchestrator is the main entry point that routes incoming  │\n│ messages. It checks if it's a slash command, loads conversation│\n│ context from the database, and routes to the appropriate AI     │\n│ client for processing...                                        │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Good for:** Running workflows locally, testing, automation scripts, CI/CD\n\n### 2. Direct Chat (Simple Questions)\n\nJust talk to the AI like you would in Claude Code terminal:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ TELEGRAM CHAT                                                   │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ You: What does the handleMessage function do?                   │\n│                                                                 │\n│ Archon: Looking at packages/core/src/orchestrator/orchestrator.ts...          │\n│                                                                 │\n│         The handleMessage function is the main entry point      │\n│         that routes incoming messages. It:                      │\n│         1. Checks if it's a slash command                       │\n│         2. Loads conversation context from database             │\n│         3. Routes to AI client for processing                   │\n│         4. Streams responses back to platform                   │\n│                                                                 │\n│         See: packages/core/src/orchestrator/orchestrator.ts            │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### 3. Slash Commands (Specific Operations)\n\nDeterministic commands that don't involve AI:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ SLASH COMMANDS                                                  │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ /clone https://github.com/user/repo    Clone a repository      │\n│ /status                                 Show current state      │\n│ /repos                                  List available repos    │\n│ /setcwd /path/to/dir                   Change working dir      │\n│ /reset                                  Clear AI session        │\n│ /help                                   Show all commands       │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### 4. Workflows (Multi-Step Automation)\n\nThis is where Archon shines - automated multi-step AI workflows:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ GITHUB ISSUE #42                                                │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ Title: Login button doesn't work on mobile                      │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @user commented:                                                │\n│   @archon fix this issue                                        │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @archon commented:                                              │\n│   🔍 Investigation Complete                                     │\n│                                                                 │\n│   Root Cause: Touch event handler missing on mobile             │\n│   File: packages/server/src/components/LoginButton.tsx:45                       │\n│   Fix: Add onTouchEnd handler alongside onClick                 │\n│                                                                 │\n│   Creating PR...                                                │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @archon commented:                                              │\n│   ✅ Fix implemented: PR #127                                   │\n│   - Added touch event handling                                  │\n│   - Added mobile viewport tests                                 │\n│   - All tests passing                                           │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## How Workflows Work (The Magic)\n\nA workflow is a YAML file that chains AI prompts together:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   .archon/workflows/fix-github-issue.yaml                              │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ name: fix-github-issue                                          │  │\n│   │ description: Investigate and fix a GitHub issue                 │  │\n│   │                                                                 │  │\n│   │ nodes:                                                          │  │\n│   │   - id: investigate                                             │  │\n│   │     command: investigate-issue    ◀── Node 1: Research         │  │\n│   │   - id: implement                                               │  │\n│   │     command: implement-issue      ◀── Node 2: Fix              │  │\n│   │     depends_on: [investigate]                                   │  │\n│   │     context: fresh                                              │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│                              │                                          │\n│                              ▼                                          │\n│                                                                         │\n│   EXECUTION FLOW:                                                       │\n│                                                                         │\n│   ┌──────────────────┐      ┌──────────────────┐      ┌────────────┐  │\n│   │  investigate-    │      │   implement-     │      │            │  │\n│   │  issue.md        │─────▶│   issue.md       │─────▶│  PR #127   │  │\n│   │                  │      │                  │      │            │  │\n│   │  - Read issue    │      │  - Read artifact │      │  Created!  │  │\n│   │  - Explore code  │      │  - Make changes  │      │            │  │\n│   │  - Find root     │      │  - Run tests     │      │            │  │\n│   │    cause         │      │  - Commit        │      │            │  │\n│   │  - Save artifact │      │  - Create PR     │      │            │  │\n│   └──────────────────┘      └──────────────────┘      └────────────┘  │\n│                                                                         │\n│   Each \"command\" is a markdown file with AI instructions.              │\n│   The workflow executor runs nodes in dependency order.                │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## The Router: How Archon Picks Workflows\n\nWhen you send a message, an AI \"router\" decides what to do:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   USER MESSAGE                           ROUTER DECISION                │\n│                                                                         │\n│   \"fix this issue\"          ───────▶     archon-ship                   │\n│   \"review this PR\"          ───────▶     archon-review                 │\n│   \"what does X do?\"         ───────▶     answered directly, no workflow│\n│   \"update sharp\"            ───────▶     archon-upkeep                 │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   HOW IT WORKS:                                                         │\n│                                                                         │\n│   ┌──────────┐     ┌─────────────────────────────────────┐             │\n│   │ Message  │────▶│ Router AI reads workflow descriptions│             │\n│   │          │     │ and picks the best match             │             │\n│   └──────────┘     └──────────────────┬──────────────────┘             │\n│                                       │                                 │\n│                                       ▼                                 │\n│                    ┌─────────────────────────────────────┐             │\n│                    │ invoke_workflow: archon-ship        │             │\n│                    └─────────────────────────────────────┘             │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Available Workflows\n\nThe table below lists the key workflows in the bundled `sdlc` pack. All bundled workflows are prefixed with `archon-`. Run `bun run cli workflow list` to see the full current list.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   WORKFLOW                              TRIGGER PHRASES    WHAT IT DOES │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-ship               \"fix this issue\"        Triage +       │  │\n│   │                           \"implement #42\"         deliver a PR   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-review             \"review this PR\"        Parallel       │  │\n│   │                           \"is this ready\"         review lenses  │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-deliver            \"deliver this plan\"     Implement +    │  │\n│   │                           \"take this to a PR\"     PR + review    │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-upkeep             \"update sharp\"          One dependency │  │\n│   │                           \"bump X to latest\"      bump + PR      │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Parallel Agents: The PR Review Example\n\nA quick route agent picks which specialist lenses the change earns, and `archon-review` runs them in fresh sessions as shown below. The code lens uses the configured `large` model tier.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   USER: \"review this PR\"                                               │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 1: scope + route          Resolve the PR, its diff, the    │  │\n│   │                                contract, and which lenses run   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 2: PARALLEL LENSES (each in a fresh session)               │  │\n│   │                                                                 │  │\n│   │   ┌──────────────┐  ┌──────────────┐                           │  │\n│   │   │ code         │  │ seams        │                           │  │\n│   │   │              │  │              │                           │  │\n│   │   │ Bugs,        │  │ Contracts    │                           │  │\n│   │   │ correctness  │  │ that drift   │                           │  │\n│   │   └──────────────┘  └──────────────┘                           │  │\n│   │                                                                 │  │\n│   │   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐         │  │\n│   │   │ tests        │  │ errors       │  │ docs         │         │  │\n│   │   │              │  │              │  │ (when docs   │         │  │\n│   │   │ Missing      │  │ Visibility & │  │  change)     │         │  │\n│   │   │ coverage     │  │ containment  │  │ Stale docs   │         │  │\n│   │   └──────────────┘  └──────────────┘  └──────────────┘         │  │\n│   │                                                                 │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 3: synthesize             Combine findings into one verdict│  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 4: publish                Post one review comment on the PR│  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\nReview never edits code. Fixing findings belongs to `archon-implement`, or to `archon-deliver`, which runs review and corrections in a loop.\n\n---\n\n## Platform Integration\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   CLI                               HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Direct command execution         │\n│   │  Terminal        │              - Real-time streaming to stdout    │\n│   │                  │              - No server needed                 │\n│   │  bun run cli     │              - Good for local workflows         │\n│   │  workflow run    │              - Perfect for CI/CD                │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   TELEGRAM                          HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Bot polls for messages           │\n│   │  @archon_bot     │              - Real-time streaming (default)    │\n│   │                  │              - DM the bot directly              │\n│   │  \"fix issue #42\" │              - Good for mobile use              │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   SLACK                             HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Socket Mode (no webhooks)        │\n│   │  #dev-channel    │              - @mention in threads              │\n│   │                  │              - DM the bot                       │\n│   │  @archon review  │              - Good for team visibility         │\n│   │  this PR         │                                                  │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   DISCORD                           HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - WebSocket connection             │\n│   │  #coding-help    │              - @mention to activate             │\n│   │                  │              - Thread support                   │\n│   │  @Archon what    │              - Good for communities             │\n│   │  does this do?   │                                                  │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   GITHUB                            HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Webhook on issues/PRs            │\n│   │  Issue #42       │              - @archon in comments              │\n│   │                  │              - Batch mode (assembled reply)     │\n│   │  @archon fix     │              - Auto-creates PRs                 │\n│   │  this issue      │              - Good for automation              │\n│   └──────────────────┘                                                  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\nBatch mode assembles the reply before delivery. Adapters may split long replies into multiple messages or comments.\n\n## Isolation: Git Worktrees\n\nEach conversation gets its own isolated copy of the repo:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   ~/.archon/workspaces/owner/repo/worktrees/                           │\n│   │                                                                     │\n│   ├── issue-42/              ◀── Conversation about issue #42         │\n│   │   └── (full repo)            Working on fix for mobile bug         │\n│   │                                                                     │\n│   ├── pr-127/                ◀── Conversation about PR #127           │\n│   │   └── (full repo)            Reviewing code changes                │\n│   │                                                                     │\n│   └── task-dark-mode/        ◀── Manual feature work                  │\n│       └── (full repo)            Adding dark mode feature              │\n│                                                                         │\n│   WHY WORKTREES?                                                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   - Multiple conversations can work simultaneously                     │\n│   - No branch conflicts between parallel work                          │\n│   - Each gets isolated file changes                                    │\n│   - Cleaned up when issue/PR closes                                    │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Configuration Hierarchy\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   CONFIGURATION LAYERS (later overrides earlier)                       │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 1. DEFAULTS (hardcoded)                                         │  │\n│   │    assistant: claude                                            │  │\n│   │    streaming.telegram: stream                                   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 2. GLOBAL CONFIG (~/.archon/config.yaml)                        │  │\n│   │    botName: MyBot                                               │  │\n│   │    defaultAssistant: claude                                     │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 3. REPO CONFIG (.archon/config.yaml)                            │  │\n│   │    assistant: codex          # This repo prefers Codex          │  │\n│   │    commands:                                                    │  │\n│   │      folder: .claude/commands/custom                            │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 4. ENVIRONMENT VARIABLES (highest priority)                     │  │\n│   │    TELEGRAM_STREAMING_MODE=batch                                │  │\n│   │    DEFAULT_AI_ASSISTANT=claude                                  │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Directory Structure\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   YOUR REPO                         ARCHON SERVER                       │\n│                                                                         │\n│   my-app/                           ~/.archon/                          │\n│   ├── .archon/                      ├── config.yaml      (global cfg)  │\n│   │   ├── config.yaml               ├── workspaces/      (cloned repos)│\n│   │   ├── commands/                 │   └── user/repo/                 │\n│   │   │   ├── investigate-issue.md  │       ├── source/    (clone)      │\n│   │   │   ├── implement-issue.md   │       └── worktrees/ (isolation)  │\n│   │   │   └── assist.md            │           ├── issue-42/           │\n│   │   ├── workflows/               │           └── pr-127/             │\n│   │   │   ├── fix-github-issue.yaml                                    │\n│   │   │   └── assist.yaml                                              │\n│   │   └── artifacts/                                                   │\n│   │       └── issues/                                                  │\n│   │           └── issue-42.md                                          │\n│   ├── packages/                                                             │\n│   └── ...                                                              │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Quick Reference: Common Interactions\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   WHAT YOU WANT                     WHAT TO SAY (Platform/CLI)          │\n│                                                                         │\n│   Run workflow locally              bun run cli workflow run <name>     │\n│   List CLI workflows                bun run cli workflow list           │\n│   Fix a GitHub issue                \"@archon fix this issue\"            │\n│   Review a PR                       \"@archon review this PR\"            │\n│   Ask a question                    \"What does handleMessage do?\"       │\n│   Resolve conflicts                 \"@archon resolve the conflicts\"     │\n│   See current state                 \"/status\"                           │\n│   Clone a repo                      \"/clone https://github.com/u/r\"     │\n│   Switch repos                      \"/repos\" then pick one              │\n│   List available workflows          \"/workflow list\"                    │\n│   Reload workflow definitions       \"/workflow reload\"                  │\n│   Approve paused workflow           \"/workflow approve <id> [comment]\"  │\n│   Reject paused workflow           \"/workflow reject <id> [reason]\"   │\n│   Cancel stuck workflow             \"/workflow cancel\"                  │\n│   Start fresh                       \"/reset\"                            │\n│   Get help                          \"/help\"                             │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Summary\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   ARCHON = Remote Control for AI Coding Assistants                     │\n│                                                                         │\n│   ┌────────────────────────────────────────────────────────────────┐   │\n│   │                                                                │   │\n│   │   Phone/Slack/GitHub ──▶ Archon Server ──▶ AI (Claude/Codex)  │   │\n│   │                              │                    │            │   │\n│   │                              ▼                    ▼            │   │\n│   │                         Workflows           Git Worktrees      │   │\n│   │                        (automation)         (isolation)        │   │\n│   │                                                                │   │\n│   └────────────────────────────────────────────────────────────────┘   │\n│                                                                         │\n│   KEY CAPABILITIES:                                                    │\n│   ─────────────────                                                    │\n│   ✓ Message from anywhere (phone, tablet, desktop)                    │\n│   ✓ Automated multi-step workflows                                    │\n│   ✓ Parallel AI agents for complex tasks                              │\n│   ✓ Isolated environments per conversation                            │\n│   ✓ Custom prompts versioned in Git                                   │\n│   ✓ GitHub integration (issues/PRs/comments)                          │\n│                                                                         │\n│   WHEN TO USE:                                                         │\n│   ─────────────                                                        │\n│   ✓ You want to fix bugs from your phone                              │\n│   ✓ You want automated PR reviews                                     │\n│   ✓ You want GitHub issue automation                                  │\n│   ✓ You want parallel development without conflicts                   │\n│   ✓ You want custom AI workflows for your team                        │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Next Steps\n\n1. **Read**: [Quickstart](/getting-started/quick-start/) - Set up your first instance\n2. **Explore**: `.archon/workflows/` - See example workflows\n3. **Customize**: `.archon/commands/` - Create your own prompts\n4. **Configure**: `.archon/config.yaml` - Tweak settings\n\nWelcome to remote agentic coding!\n","packages/docs-web/src/content/docs/contributing/releasing.md":"---\ntitle: Releasing\ndescription: How to create a new release of the Archon CLI — version management, release process, and troubleshooting.\ncategory: contributing\narea: infra\naudience: [developer]\nstatus: current\nsidebar:\n  order: 3\n---\n\nThis guide covers how to create a new release of the Archon CLI.\n\n## Version Management\n\nVersions follow [Semantic Versioning](https://semver.org/):\n- **Major** (1.0.0): Breaking changes to CLI interface or workflow format\n- **Minor** (0.1.0): New features, new workflows, new commands\n- **Patch** (0.0.1): Bug fixes, documentation updates\n\nVersion is stored in the root `package.json` only -- this is the single source of truth.\n\n## Release Process\n\nReleases are created by merging `dev` into `main`. Never commit directly to `main`.\n\n### 1. Prepare the Release\n\nUse the `/release` skill (or follow these manual steps):\n\n```bash\n# Ensure dev is up to date\ngit checkout dev\ngit pull origin dev\n\n# Run default local validation\nbun run validate\nbun run validate --only format,workflow-fixtures\n```\n\nCI remains the full validation gate, including service and binary checks that local validation does not run.\n\nThe `/release` skill automates the following:\n1. Compares `dev` to `main` to generate changelog entries\n2. Bumps the version in root `package.json` (patch by default; use `/release minor` or `/release major` for other increments)\n3. Updates `CHANGELOG.md` following Keep a Changelog format\n4. Creates a PR from `dev` to `main`\n\n### 2. Merge and Tag\n\nOnce the release PR is reviewed and merged:\n\n```bash\n# Create and push the tag from main\ngit checkout main\ngit pull origin main\ngit tag vX.Y.Z\ngit push origin vX.Y.Z\n```\n\nThis triggers the GitHub Actions release workflow which:\n1. Builds binaries for all platforms (macOS arm64/x64, Linux arm64/x64, Windows x64)\n2. Generates checksums\n3. Creates a GitHub Release with all artifacts\n\n### 3. Update Homebrew Formula (Optional)\n\nAfter the release workflow completes:\n\n```bash\n# Update checksums in the Homebrew formula\n./scripts/update-homebrew.sh vX.Y.Z\n\n# Review and commit\ngit diff homebrew/archon.rb\ngit add homebrew/archon.rb\ngit commit -m \"chore: update Homebrew formula for vX.Y.Z\"\ngit push origin main\n```\n\nIf you maintain a Homebrew tap (`homebrew-archon`), copy the updated formula there.\n\n### 4. Verify the Release\n\n```bash\n# Test the install script (only works if repo is public)\ncurl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash\n\n# Verify version\narchon version\n```\n\n> **Note: Private Repository Installation**\n>\n> If the repository is private, the curl install script won't work for anonymous users.\n> Use the GitHub CLI instead:\n>\n> ```bash\n> # Download and install using gh (requires GitHub authentication)\n> gh release download v0.2.0 --repo coleam00/Archon \\\n>   --pattern \"archon-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')\" \\\n>   --dir /tmp/archon-install\n>\n> # Install the binary\n> chmod +x /tmp/archon-install/archon-*\n> sudo mv /tmp/archon-install/archon-* /usr/local/bin/archon\n>\n> # Verify\n> archon version\n> ```\n\n## Manual Release (When GitHub Actions Unavailable)\n\nIf GitHub Actions can't run (billing issues, private repo limits), create the release manually:\n\n```bash\n# 1. Build binaries locally (every target in the script's local TARGETS list).\n#    TARGET or OUTFILE left set in the shell switches the script to single-target mode.\nunset TARGET OUTFILE\n./scripts/build-binaries.sh\n\n# 2. Generate checksums.txt, which the install scripts verify against\n./scripts/checksums.sh\n\n# 3. Create the release with binaries and checksums\ngh release create vX.Y.Z dist/binaries/* \\\n  --title \"Archon CLI vX.Y.Z\" \\\n  --generate-notes\n\n# 4. Verify the release\ngh release view vX.Y.Z\n```\n\n> **Note:** With `TARGET` and `OUTFILE` unset, `scripts/build-binaries.sh` cross-compiles\n> the CLI and server executables for every target in its local `TARGETS` list into\n> `dist/binaries/`. That list has no Windows target.\n>\n> A manual release is therefore incomplete. It lacks the assets only the CI release\n> builds: the Windows binaries, the plugin executables (`archon plugin add`\n> downloads these), and `archon-web.tar.gz` (`archon serve` downloads it). The `files:`\n> list of the release step in `.github/workflows/release.yml` is the full set CI publishes.\n\n## Manual Build (for Testing)\n\nTo build binaries locally without creating a release:\n\n```bash\n# Build every target in the script's local TARGETS list (no Windows)\nunset TARGET OUTFILE\n./scripts/build-binaries.sh\n\n# Binaries are in dist/binaries/\nls -la dist/binaries/\n\n# Generate checksums\n./scripts/checksums.sh\n```\n\n## Release Workflow Details\n\nThe `.github/workflows/release.yml` workflow:\n\n1. **Triggers on**:\n   - Push of tags matching `v*`\n   - Manual workflow dispatch with version input\n\n2. **Build job** (runs in parallel for each platform):\n   - Sets up Bun\n   - Installs dependencies\n   - Compiles binary with `bun build --compile`\n   - Uploads as artifact\n\n3. **Release job** (runs after all builds complete):\n   - Downloads all artifacts\n   - Generates SHA256 checksums\n   - Creates GitHub Release with:\n     - All binaries attached\n     - checksums.txt\n     - Auto-generated release notes\n     - Installation instructions\n\n## Troubleshooting\n\n### Build Fails on GitHub Actions\n\nCheck the Actions tab for specific errors. Common issues:\n- Dependency installation failure: Check `bun.lock` is committed\n- Type errors: Run `bun run type-check` locally first\n\n### Install Script Fails\n\nThe install script requires:\n- `curl` for downloading\n- `sha256sum` or `shasum` for verification\n- Write access to `/usr/local/bin` (or custom `INSTALL_DIR`)\n\n### Checksums Don't Match\n\nIf users report checksum failures:\n1. Check the release artifacts are complete\n2. Verify checksums.txt was generated correctly\n3. Ensure binaries weren't modified after checksum generation\n\n## Pre-release Versions\n\nFor testing releases before public announcement:\n\n```bash\n# Create a pre-release tag\ngit tag v0.3.0-beta.1\ngit push origin v0.3.0-beta.1\n```\n\nPre-releases (tags containing `-`) are marked as such on GitHub.\n\n## Hotfix Process\n\nFor urgent fixes to a released version:\n\n```bash\n# Create hotfix branch from tag\ngit checkout -b hotfix/0.2.1 v0.2.0\n\n# Make fixes, then tag\ngit tag v0.2.1\ngit push origin v0.2.1\n\n# Merge fixes back to dev\ngit checkout dev\ngit merge hotfix/0.2.1\ngit push origin dev\n```\n","packages/docs-web/src/content/docs/deployment/cloud.md":"---\ntitle: Cloud Deployment\ndescription: Prepare a cloud VPS for Archon, then run it with Docker Compose and automatic HTTPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 3\n---\n\nThis page prepares a VPS: a user, SSH, a firewall and DNS. The [Docker guide](/deployment/docker/) then installs and runs Archon on it with Docker Compose and Caddy for HTTPS. Using your own proxy instead of Caddy? See [Running Archon behind your own reverse proxy](/deployment/reverse-proxy/).\n\nFor the fastest route, use the [cloud-init script](/deployment/docker/#cloud-init-fastest-setup) instead. It creates a user, opens the firewall and installs Docker when the VPS first boots. You still create the [DNS record](#point-your-domain-at-the-server).\n\n## What you need\n\n- A VPS from any provider (DigitalOcean, Linode, AWS EC2, Vultr and so on): 1 to 2 vCPUs, 2 GB RAM minimum (4 GB recommended), 20 GB SSD, Ubuntu 22.04 LTS.\n- A domain or subdomain, such as `archon.yourdomain.com`.\n- An SSH key. Create one before the VPS, and add the public key when you create it:\n\n  ```bash\n  ssh-keygen -t ed25519 -C \"archon\"\n  cat ~/.ssh/id_ed25519.pub\n  ```\n\nWhen you create the VPS, choose the Ubuntu 22.04 image and your SSH key, and allow inbound SSH (22), HTTP (80) and HTTPS (443) if the provider has its own firewall or security group. Note the public IP.\n\n## Secure the server\n\nConnect with the image's default account: `root` on most VPS providers, `ubuntu` on AWS EC2, which disables root login. Create a deploy user with your key, and switch to it:\n\n```bash\nssh root@your-server-ip            # or: ssh ubuntu@your-server-ip on EC2\n\nsudo adduser deploy\nsudo usermod -aG sudo deploy\nsudo mkdir -p /home/deploy/.ssh\nsudo cp ~/.ssh/authorized_keys /home/deploy/.ssh/\nsudo chown -R deploy:deploy /home/deploy/.ssh\nsudo chmod 700 /home/deploy/.ssh\nsudo chmod 600 /home/deploy/.ssh/authorized_keys\n```\n\nTest `ssh deploy@your-server-ip` in a second terminal before you go on. Then turn off password logins by setting `PasswordAuthentication no` in `/etc/ssh/sshd_config`, and restart SSH:\n\n```bash\nsudo systemctl restart ssh\nsudo su - deploy\n```\n\nOpen the firewall:\n\n```bash\nsudo ufw allow 22/tcp\nsudo ufw allow 80/tcp\nsudo ufw allow 443\nsudo ufw --force enable\n```\n\n## Point your domain at the server\n\nAt your DNS provider, create an A record: name `archon` (or `@` for the root domain), value the server's public IP. With Cloudflare, set the record to DNS only, not proxied, so Caddy can obtain its certificate. Check it with `dig archon.yourdomain.com`.\n\n## Install and run Archon\n\nFollow the Docker guide's [manual server setup](/deployment/docker/#manual-server-setup). It installs Docker, clones the repository, sets up `.env` and the `Caddyfile`, and starts the stack. Before you expose the Web UI, choose a login in [Protect the Web UI](/deployment/docker/#protect-the-web-ui).\n\nCheck the deployment from your machine:\n\n```bash\ncurl https://archon.yourdomain.com/api/health\n```\n","packages/docs-web/src/content/docs/deployment/docker.md":"---\ntitle: Docker Guide\ndescription: Run Archon with Docker Compose, locally or on a server with PostgreSQL and automatic HTTPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 2\n---\n\nThis page installs and runs Archon with Docker Compose: on your own machine, or on a server with PostgreSQL and Caddy for automatic HTTPS. To prepare a VPS first (a user, SSH, the firewall and DNS), see [Cloud Deployment](/deployment/cloud/).\n\nThe image includes the Claude Code binary from the Claude Agent SDK. On start, the entrypoint points `CLAUDE_BIN_PATH` at it unless you set `CLAUDE_BIN_PATH` yourself.\n\n## Cloud-init (fastest setup)\n\n`deploy/cloud-init.yml` prepares a new VPS when it first boots. Paste its contents into the provider's User Data field when you create the server (Ubuntu 22.04+ or Debian 12+), add your SSH key, and point your domain's A record at the server's IP.\n\n| Provider | Where to paste it |\n|---|---|\n| DigitalOcean | Create Droplet, Advanced Options, User Data |\n| AWS EC2 | Launch Instance, Advanced Details, User Data |\n| Linode | Create Linode, Add Tags, Metadata (User Data) |\n| Hetzner | Create Server, Cloud config, User Data |\n| Vultr | Deploy, Additional Features, Cloud-Init User-Data |\n\nThe script installs Docker, opens ports 22, 80 and 443 (TCP, plus UDP 443 for HTTP/3), adds a 2 GB swapfile, clones the repository to `/opt/archon`, copies `.env.example` to `.env` and `Caddyfile.example` to `Caddyfile`, and builds the image. It also creates an `archon` user with Docker access but no sudo, and copies your SSH key to it. Use the default cloud user or root for system maintenance.\n\nSetup takes 5 to 8 minutes and writes `/opt/archon/SETUP_COMPLETE` when it finishes. Then:\n\n```bash\nssh archon@your-server-ip\nnano /opt/archon/.env          # see \"Configure .env\" below\ncd /opt/archon\ndocker compose --profile with-db --profile cloud up -d\n```\n\n## Local Docker Desktop (Windows and macOS)\n\nRun Archon on your own machine with SQLite and the Web UI. No domain or VPS is needed.\n\n```bash\ngit clone https://github.com/coleam00/Archon.git\ncd Archon\ncp .env.example .env           # set a Claude or Codex credential\ndocker compose up -d\n```\n\nOpen `http://localhost:3000`. Add `--profile with-db` to use the PostgreSQL container instead of SQLite (see [Profiles](#profiles)).\n\nOn Windows, build from a WSL terminal (for example in `/mnt/c/Users/YourName/path/to/Archon`), not PowerShell. Docker Desktop cannot follow Bun workspace symlinks from a Windows build context and fails with `The file cannot be accessed by the system`. If the container fails with `exec docker-entrypoint.sh: no such file or directory`, the checkout has CRLF line endings. `.gitattributes` forces LF for shell scripts, so re-clone the repository.\n\n## Manual server setup\n\nUse this instead of cloud-init when you want each step under your control. Prepare the server with [Cloud Deployment](/deployment/cloud/) first.\n\n1. Install Docker, then log out and back in so the group change applies:\n\n   ```bash\n   curl -fsSL https://get.docker.com | sh\n   sudo usermod -aG docker $USER\n   ```\n\n2. Clone the repository and create the config files:\n\n   ```bash\n   git clone https://github.com/coleam00/Archon.git\n   cd Archon\n   cp .env.example .env\n   cp Caddyfile.example Caddyfile\n   ```\n\n3. Edit `.env` as described in [Configure .env](#configure-env).\n\n4. Start the app, PostgreSQL and Caddy:\n\n   ```bash\n   docker compose --profile with-db --profile cloud up -d\n   ```\n\n5. Check it:\n\n   ```bash\n   docker compose --profile with-db --profile cloud ps\n   docker compose logs -f app\n   curl https://archon.example.com/api/health    # from your machine\n   ```\n\n## Pre-built image\n\nTo run `ghcr.io/coleam00/archon:latest` without building from source:\n\n```bash\nmkdir archon && cd archon\ncurl -O https://raw.githubusercontent.com/coleam00/Archon/main/deploy/docker-compose.yml\ncurl -O https://raw.githubusercontent.com/coleam00/Archon/main/.env.example\ncp .env.example .env           # see \"Configure .env\" below\ndocker compose up -d\n```\n\nThis compose file has only the app service. To add PostgreSQL, uncomment the `postgres` service and the `postgres_data` volume, and set `DATABASE_URL`. It has no Caddy service.\n\n### Verify image attestations\n\nPublished images carry BuildKit provenance and SPDX SBOM attestations. Resolve the tag to a digest, then inspect that digest so the image cannot change between inspection and deployment:\n\n```bash\ndocker buildx imagetools inspect ghcr.io/coleam00/archon:latest\n# Copy the top-level Digest, not a platform manifest's digest.\nimage='ghcr.io/coleam00/archon@sha256:<digest>'\ndocker buildx imagetools inspect \"$image\" --format '{{ json .Provenance }}'\ndocker buildx imagetools inspect \"$image\" --format '{{ json .SBOM }}'\n```\n\nBoth results must be non-empty: each platform has a `SLSA` provenance object and an `SPDX` SBOM object. In the provenance, check three things:\n\n- The source is `https://github.com/coleam00/Archon` and the revision is the release commit you intend to run. Current releases record them in `buildDefinition.externalParameters.request.args` as `label:org.opencontainers.image.source` and `label:org.opencontainers.image.revision`. Older BuildKit versions use `invocation.configSource` and `invocation.environment`.\n- The workflow ref in `buildDefinition.internalParameters.github_workflow_ref` names `.github/workflows/publish.yml` at the expected ref.\n- The builder ID (`builder.id` or `runDetails.builder.id`) links to the GitHub Actions run.\n\nThe package inventory is under `SPDX.packages`. The `unknown/unknown` entries in the manifest list are the attestation manifests. The attestations are unsigned metadata, so they do not authenticate the publisher by themselves. See [Docker's provenance documentation](https://docs.docker.com/build/metadata/attestations/slsa-provenance/).\n\n## Configure .env\n\nDocker Compose reads the repository's `.env`. Edit it directly. Don't run `archon setup` for a Docker install: the wizard writes Archon's CLI env files, not this one. `.env.example` documents every variable.\n\n### AI credentials\n\nSet at least one provider:\n\n```ini\n# Claude: an OAuth token from `claude setup-token` on your own machine\nCLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxx\n# or an API key from console.anthropic.com\n# CLAUDE_API_KEY=sk-ant-xxxxx\n\n# Codex: values from ~/.codex/auth.json after `codex login`\n# ARCHON_CODEX_ID_TOKEN=eyJhbGc...\n# ARCHON_CODEX_ACCESS_TOKEN=eyJhbGc...\n# ARCHON_CODEX_REFRESH_TOKEN=rt_...\n# ARCHON_CODEX_ACCOUNT_ID=6a6a7ba6-...\n```\n\n`.env.example` sets `CLAUDE_USE_GLOBAL_AUTH=true`. That flag only satisfies the server's startup check: it tells Archon to rely on a `claude /login` session, and a fresh container has none. Set a token or key. When one is set, Archon uses it whatever the flag says. See [AI Assistants](/getting-started/ai-assistants/) for every provider.\n\n### Server, domain and database\n\n| Variable | Default in Docker | Purpose |\n|---|---|---|\n| `PORT` | `3000` | App port, published on the host. Local development defaults to 3090. |\n| `DOMAIN` | none | Public hostname. Required for `--profile cloud`. |\n| `DATABASE_URL` | unset (SQLite) | PostgreSQL connection string. |\n| `POSTGRES_PORT` | `5432` | Host port for the PostgreSQL container, bound to `127.0.0.1`. |\n| `LOG_LEVEL` | `info` | `fatal`, `error`, `warn`, `info`, `debug` or `trace`. |\n\nFor the PostgreSQL container, use the service name as the host:\n\n```ini\nDATABASE_URL=postgresql://postgres:postgres@postgres:5432/remote_coding_agent\n```\n\nPlatform tokens (`TELEGRAM_BOT_TOKEN`, `SLACK_BOT_TOKEN`, `GH_TOKEN`, `WEBHOOK_SECRET` and so on) are covered in each adapter's page under [Adapters](/adapters/). For a GitHub webhook, the payload URL is `https://archon.example.com/webhooks/github`; see [GitHub](/adapters/github/).\n\n## Profiles\n\nCompose profiles add PostgreSQL, HTTPS or form login to the base app service:\n\n| Command | What runs |\n|---|---|\n| `docker compose up -d` | App with SQLite |\n| `docker compose --profile with-db up -d` | App and PostgreSQL 17 |\n| `docker compose --profile cloud up -d` | App and Caddy |\n| `docker compose --profile with-db --profile cloud up -d` | App, PostgreSQL and Caddy |\n\nAdd `--profile auth` for the [form login sidecar](#form-login-sidecar). For an external database such as Supabase or Neon, set `DATABASE_URL` and use no database profile. There is no `external-db` profile.\n\n- **No profile:** SQLite, stored in the data volume at `/.archon/archon.db`.\n- **`with-db`:** a PostgreSQL 17 container. The app creates and upgrades the schema on every start. If you start this profile without setting `DATABASE_URL`, the app logs a warning and uses SQLite, and the container sits unused.\n- **`cloud`:** [Caddy](https://caddyserver.com/) in front of the app, with Let's Encrypt certificates, HTTP to HTTPS redirects, HTTP/3 and unbuffered `/api/stream/*` for live updates. It needs a `Caddyfile` (copied from `Caddyfile.example`), `DOMAIN` set, the DNS A record in place, and ports 80 and 443 open. Caddy waits for the app's health check before it starts.\n\n## Protect the Web UI\n\nA Web UI on a public domain needs a login. Choose one of these:\n\n- **Better Auth** (PostgreSQL only, recommended): per-user accounts, and the server rejects every `/api/*` request without a session. Set `BETTER_AUTH_SECRET`; see [Web UI login](/reference/configuration/#web-ui-login-better-auth-optional). Signup is closed by default: invite people with `ARCHON_AUTH_ALLOWED_EMAILS`, or open it with `ARCHON_AUTH_OPEN_SIGNUP=true`. With Better Auth on, you don't need either Caddy option below; the [Security reference](/reference/security/#adapter-authorization) explains how to retire them.\n- **Caddy basic auth:** the browser's own login prompt, with no extra container.\n- **Form login sidecar:** a styled login page and a session cookie, served by the `auth-service` container.\n\nUse one Caddy option at a time, not both. Caddy always leaves `/webhooks/*` and `/api/health` open, and webhooks verify their own signatures. Caddy answers `/internal/*` with a 404 and never forwards it.\n\n### Caddy basic auth\n\n1. Generate a bcrypt hash. Never put a plaintext password in `.env`.\n\n   ```bash\n   docker run --rm caddy caddy hash-password --plaintext 'YOUR_PASSWORD'\n   ```\n\n2. Set `CADDY_BASIC_AUTH` in `.env`. Replace `admin` with your username and paste the complete hash unchanged:\n\n   ```ini\n   CADDY_BASIC_AUTH='basic_auth @protected {\n     admin REPLACE_WITH_COMPLETE_HASH_OUTPUT\n   }'\n   ```\n\n   This is a single-quoted multiline Compose value, so newlines and `$` characters survive as written. Don't double the dollar signs, and don't source the file in a shell. See [Compose interpolation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/) and [Caddy's basic_auth](https://caddyserver.com/docs/caddyfile/directives/basic_auth).\n\n3. Recreate Caddy so it reads the new value:\n\n   ```bash\n   docker compose --profile cloud up -d --force-recreate caddy\n   ```\n\nTo turn it off, leave `CADDY_BASIC_AUTH` empty or unset.\n\n### Form login sidecar\n\n1. Generate a bcrypt hash. The first run builds the `auth-service` image.\n\n   ```bash\n   docker compose --profile auth run --rm auth-service \\\n     node -e \"require('bcryptjs').hash('YOUR_PASSWORD', 12).then(h => console.log(h))\"\n   ```\n\n2. Generate a cookie signing secret:\n\n   ```bash\n   docker run --rm node:22-alpine \\\n     node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\n   ```\n\n3. Set them in `.env`, writing every `$` in the hash as `$$` so Compose does not interpolate it:\n\n   ```ini\n   AUTH_USERNAME=admin\n   AUTH_PASSWORD_HASH=$$2b$$12$$REPLACE_WITH_YOUR_HASH\n   COOKIE_SECRET=REPLACE_WITH_64_HEX_CHARS\n   # COOKIE_MAX_AGE=86400   # session length in seconds\n   ```\n\n4. In `Caddyfile`, uncomment the Option A block (`handle /login`, `handle /logout` and the `forward_auth` handle), and comment out the \"No auth\" `handle` block below it.\n\n5. Start with the `auth` profile:\n\n   ```bash\n   docker compose --profile with-db --profile cloud --profile auth up -d\n   ```\n\nYour domain now redirects to `/login`. Visit `/logout` to end the session.\n\n## Data and home volumes\n\nThe container keeps Archon's data in `/.archon`: workspaces, worktrees, artifacts, logs, plugins and the SQLite database. The user's home, `/home/appuser`, holds provider and tool state:\n\n| Path | What it holds |\n|---|---|\n| `~/.claude/` | Claude Code skills, commands, agents, hooks, MCP config, sessions, memory and OAuth state |\n| `~/.codex/` | Codex `auth.json` from `codex login`. With the `ARCHON_CODEX_*` variables set, startup overwrites it each time. |\n| `~/.pi/agent/` | Pi `auth.json` from `pi /login`, `models.json`, `settings.json` and sessions |\n| `~/.gitconfig` | Git identity, signing config, aliases and `safe.directory` entries |\n| `~/.config/gh/` | GitHub CLI login from `gh auth login` |\n| `~/.bash_history` | Shell history from `docker compose exec app bash` |\n\nBoth are Docker named volumes by default (`archon_data` and `archon_user_home`), so they survive restarts and rebuilds. To keep them at a host path instead, set `ARCHON_DATA` or `ARCHON_USER_HOME` in `.env`, and make the directory writable by UID 1001, the container user:\n\n```bash\nsudo mkdir -p /opt/archon-data /opt/archon-user-home\nsudo chown -R 1001:1001 /opt/archon-data /opt/archon-user-home\n```\n\n```ini\nARCHON_DATA=/opt/archon-data\nARCHON_USER_HOME=/opt/archon-user-home\n```\n\nOn every start the entrypoint fixes ownership of both paths, touching only files with the wrong owner.\n\n- `ARCHON_HOME` has no effect inside the container, which always uses `/.archon`. Use `ARCHON_DATA` to choose where that lives on the host.\n- A bind-mounted home starts with an empty `~/.gitconfig`, because Docker copies image content into new named volumes but never into bind mounts. The entrypoint still adds the `safe.directory` entries Archon needs. Set your Git identity with `git config --global` inside the container.\n- `safe.directory` entries added at runtime stay in the home volume, even after the checkout is gone. Archon never removes them, because concurrent setup may rely on the same entry. See [Git directory trust](/reference/security/#git-directory-trust).\n- To keep Pi's data on the data volume, set `PI_CODING_AGENT_DIR=/.archon/pi` before the container starts.\n- To wipe the home volume: `docker compose down && docker volume rm archon_archon_user_home`.\n\n### Root fallback for macOS bind mounts\n\nOn a macOS bind mount (VirtioFS), the ownership fix always fails: the host will not remap files to UID 1001. The entrypoint prints the `chown` error and exits 1, so the container restarts in a loop. Read-only mounts and SELinux or AppArmor denials on Linux fail the same way.\n\n`ARCHON_ALLOW_ROOT_FALLBACK=1` in `.env` makes the entrypoint print a warning and keep running as root instead. Any other value, or none, keeps the exit. The variable does nothing when the ownership fix succeeds.\n\n:::caution\nRunning as root also sets `IS_SANDBOX=1`, which disables the Claude provider's refusal to run `bypassPermissions` as root, so AI subprocesses run as root in the container. That is acceptable on a single-operator Mac where the bind mount already limits what the container can reach. On Linux the failure means the volume ownership is wrong: run `sudo chown -R 1001:1001 <path>` on the host instead.\n:::\n\n## GitHub credentials\n\nThe container uses `GH_TOKEN` from `.env`. You can also log in inside the container, and the login lands on the home volume:\n\n```bash\ndocker compose exec -u appuser -e HOME=/home/appuser app gh auth login\n```\n\n- With `GH_TOKEN` set and GitHub App mode off, every start resets Git's `https://github.com` credential helper to one that uses `GH_TOKEN`. A helper written by `gh auth login` applies only without `GH_TOKEN`.\n- In [GitHub App mode](/adapters/github-app-setup/) (`GITHUB_APP_ID`, a private key and `WEBHOOK_SECRET` set, `GITHUB_TOKEN` unset), startup removes only the PAT helper that the entrypoint wrote earlier. An incomplete App configuration leaves it in place. Other helpers are yours and stay.\n- The entrypoint reads these variables from the container environment, not from `/.archon/.env`. Keep `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY` or `GITHUB_APP_PRIVATE_KEY_PATH`, `WEBHOOK_SECRET`, `GITHUB_TOKEN` and `GH_TOKEN` in the compose `.env`. If they are only in `/.archon/.env`, the server runs in App mode while clones keep using the old PAT.\n- With `ARCHON_SDLC_FORGE=gh`, delete an empty `GH_TOKEN=` line. The SDLC workflows refuse an empty `GH_TOKEN` when `GITHUB_TOKEN` is unset, rather than let `gh` fall back to the stored login.\n\n## Plugins\n\nInstall plugins inside the running container, as `appuser` so the server owns the files:\n\n```bash\ndocker compose exec -u appuser app bun run cli plugin add coleam00/Archon/plugins/forge-github\ndocker compose exec -u appuser app bun run cli plugin add Wirasm/archon-video\n```\n\n`plugin update`, `plugin remove` and `plugin list` work the same way. Plugins live in `/.archon/plugins` on the data volume, so they survive restarts and rebuilds. See [Forge operations](/reference/forge/#install-the-github-plugin) and [Installed workflow packs](/guides/global-workflows/#installed-workflow-packs).\n\nThe bundled SDLC workflows use `gh` by default. To use the forge plugin instead, set `ARCHON_SDLC_FORGE=forge` and install forge-github v0.12.1 or later; some pack steps still need `gh`. The plugin looks for `GH_TOKEN`, then `GITHUB_TOKEN`, then the container's `gh` login. Your host's `gh` login is not visible inside the container. See [Use the forge path in the SDLC pack](/reference/forge/#use-the-forge-path-in-the-sdlc-pack).\n\nThe image includes a verified Slack plugin outside the data volume. When `SLACK_BOT_TOKEN` is set, in the container environment or in `/.archon/.env`, the entrypoint installs it offline into `/.archon/plugins` before the server starts, and logs the release and commit. You don't run `plugin add` for Slack. Slack still needs both `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` to connect; see [Slack](/adapters/slack/).\n\n- An installed, verified Slack plugin stays as it is, including a release you pinned from another source. The image does not update it.\n- Missing or changed Slack executables, ownership conflicts, or a broken image receipt when a new install is needed stop provisioning with an error in the boot logs. Repair the install before restarting.\n- To turn Slack off, remove its tokens from every env source, run `docker compose exec -u appuser app bun run cli plugin remove coleam00/Archon/plugins/chat-slack`, and restart. Removing only the plugin reinstalls it on the next start. Removing only the tokens leaves it installed and reporting missing credentials.\n\nThe image has `git` and Bun, but not `uv`, Python, or tools a pack's scripts call, such as ffmpeg. Add them with [Dockerfile.user](#customizing-the-image).\n\n## Container isolation is unavailable\n\n`archon workflow run ... --container` starts a sibling Docker container for each run. Inside this compose stack it fails at preflight with \"Cannot connect to the Docker daemon\": the image has no `docker` CLI, and the stack does not mount `/var/run/docker.sock`. Worktree isolation and in-place folder runs work normally.\n\n:::caution\nMounting the Docker socket into the app container is not part of the supported stack. The socket is root-equivalent: anything that can reach it can start a privileged container and take over the host. If you accept that on a single-operator host, read `packages/isolation/docker/SECURITY.md` first. To use `--container` without that risk, run Archon directly on a host with a local Docker daemon.\n:::\n\n## Building the image\n\n```bash\ndocker build -t archon .\ndocker run --env-file .env -e ARCHON_DOCKER=true -p 3000:3000 archon\n```\n\nThe Dockerfile builds in these stages:\n\n1. `manifests` and `sources` derive the workspace layout from `packages/`, so a new package needs no Dockerfile edit.\n2. `deps` installs all dependencies, including the dev dependencies the web build needs.\n3. `image-plugins` downloads the pinned Slack release, checks its commit and checksum, and records a verified receipt.\n4. `web-build` builds the Web UI with Vite.\n5. `production` keeps production dependencies and the built Web UI.\n\nThe production image runs Bun as the non-root `appuser` (UID 1001), which Claude Code requires. It includes `git`, `curl`, `gh`, `postgresql-client`, `ripgrep`, `jq`, Chromium and [agent-browser](https://github.com/vercel-labs/agent-browser), which drives the system Chromium for browser testing. It ships the source of every workspace package except `docs-web`, `web` (only the built UI ships) and `chat-slack` (shipped as the verified release under `/opt/archon/image-plugins`).\n\nThe Slack release is pinned separately from Archon's version by the build args `SLACK_PLUGIN_TAG` (default `v0.12.1`) and `SLACK_PLUGIN_COMMIT` (default `5f2b4841aaca38180c0abbd32bc6345593292019`). Override both together; the build rejects a tag that resolves to a different commit. Downloads happen only at build time.\n\n### Customizing the image\n\nTo add tools without editing the tracked Dockerfile:\n\n1. Copy `Dockerfile.user.example` to `Dockerfile.user` (use `deploy/Dockerfile.user.example` for a server deploy) and edit it.\n2. Copy `docker-compose.override.example.yml` to `docker-compose.override.yml` (or `deploy/docker-compose.override.example.yml`).\n3. Run `docker compose up -d`. Compose merges the override file automatically.\n\nBoth files are gitignored, so your changes stay local.\n\n## Maintenance\n\n```bash\ndocker compose logs -f app            # follow app logs\ndocker compose logs --tail=100 app    # last 100 lines\ndocker compose restart app\ndocker compose down                   # stop, keep data\ndocker compose down -v                # stop and delete volumes, including data\ndocker system df                      # Docker disk usage\n```\n\n### Update\n\n```bash\ngit pull\ndocker compose --profile with-db --profile cloud up -d --build\n```\n\nUse the same profiles you deployed with. The app applies schema changes on startup: it runs the idempotent `migrations/000_combined.sql` under a PostgreSQL advisory lock, so there is no manual migration step.\n\n## Troubleshooting\n\n### App exits with `no_ai_credentials`\n\nThe server found no Claude credential and no Codex credential. Set `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_API_KEY`, or `ARCHON_CODEX_ID_TOKEN` with `ARCHON_CODEX_ACCESS_TOKEN`. See [AI credentials](#ai-credentials).\n\n### Caddy fails with \"not a directory\"\n\n```\nerror mounting \"Caddyfile\": not a directory\n```\n\n`Caddyfile` did not exist, so Docker created a directory in its place:\n\n```bash\nrm -rf Caddyfile\ncp Caddyfile.example Caddyfile\ndocker compose --profile cloud up -d\n```\n\n### Caddy gets no certificate\n\nCheck that `dig archon.example.com` returns the server's IP, that `sudo ufw status` shows 80 and 443 open, and that `DOMAIN` in `.env` is spelled right. Then read `docker compose logs caddy`. DNS changes can take up to an hour to propagate.\n\n### Health check fails\n\nThe container health check calls `/api/health` on `PORT`, not `/health`:\n\n```bash\ncurl http://localhost:3000/api/health\n```\n\n### PostgreSQL connection refused\n\nWith `--profile with-db`, `DATABASE_URL` must use the service name `postgres` as the host, not `localhost`. Check the container with `docker compose ps postgres`.\n\n### Permission errors in `/.archon`\n\nThe entrypoint exits 1 with the `chown` error when it cannot give `/.archon` or `/home/appuser` to UID 1001. On Linux with a bind mount, run `sudo chown -R 1001:1001 /path/to/archon-data` on the host. On a macOS bind mount, `chown` on the host does not help; see [Root fallback for macOS bind mounts](#root-fallback-for-macos-bind-mounts).\n\n### Port already in use\n\nSet another `PORT` in `.env`, such as `PORT=3001`.\n\n### Container keeps restarting\n\nRun `docker compose ps` and `docker compose logs --tail=50 app`. The usual causes are a missing `.env`, an invalid credential or an unreachable database.\n","packages/docs-web/src/content/docs/deployment/e2e-testing-wsl.md":"---\ntitle: E2E Testing on WSL\ndescription: Run agent-browser inside WSL for end-to-end testing when dev servers run on Windows.\ncategory: deployment\narea: infra\naudience: [developer]\nstatus: current\nsidebar:\n  order: 6\n---\n\n`agent-browser` (Vercel) has a [known Windows bug](https://github.com/vercel-labs/agent-browser/issues/56) where the daemon fails to start due to Unix domain socket incompatibility. The workaround is to run agent-browser inside WSL while the dev servers run on Windows.\n\n> **General setup:** For non-WSL platforms (macOS, Linux, Docker), see the [E2E Testing Guide](/deployment/e2e-testing/) instead.\n\n## Prerequisites\n\n- WSL2 with Ubuntu installed (`wsl --list --verbose`)\n- agent-browser installed in WSL: `npm install -g agent-browser`\n- Playwright chromium installed: `agent-browser install --with-deps` (needs sudo)\n\n## Setup\n\n### 1. Find the Windows host IP accessible from WSL\n\n```bash\nipconfig | findstr \"IPv4\" | findstr \"WSL\"\n# Example output: IPv4 Address. . . . . . . . . . . : 172.18.64.1\n```\n\nOr from inside WSL:\n```bash\nwsl -d Ubuntu -- bash -c \"cat /etc/resolv.conf | grep nameserver\"\n```\n\nThe Windows host IP for this system is `172.18.64.1`.\n\n### 2. Start dev servers on Windows (bound to all interfaces)\n\n```bash\n# Backend (Hono on port 3090) - already binds to 0.0.0.0 by default\nbun run dev:server &\n\n# Frontend (Vite on port 5173) - needs --host flag\ncd packages/web && bun x vite --host 0.0.0.0 &\n```\n\n### 3. Verify WSL can reach the servers\n\n```bash\nwsl -d Ubuntu -- curl -s http://172.18.64.1:3090/api/health\nwsl -d Ubuntu -- curl -s -o /dev/null -w \"%{http_code}\" http://172.18.64.1:5173\n```\n\n## Running agent-browser Commands\n\nAll commands are run from the Windows terminal, prefixed with `wsl -d Ubuntu --`:\n\n```bash\n# Open a page\nwsl -d Ubuntu -- agent-browser open http://172.18.64.1:5173\n\n# Take interactive snapshot (get element refs like @e1, @e2)\nwsl -d Ubuntu -- agent-browser snapshot -i\n\n# Click, fill, press\nwsl -d Ubuntu -- agent-browser click @e1\nwsl -d Ubuntu -- agent-browser fill @e2 \"some text\"\nwsl -d Ubuntu -- agent-browser press Enter\n\n# Wait for content to load\nwsl -d Ubuntu -- agent-browser wait 3000\n\n# Reload page (hard refresh)\nwsl -d Ubuntu -- agent-browser reload\n\n# Close browser\nwsl -d Ubuntu -- agent-browser close\n```\n\n## Taking Screenshots\n\nScreenshots must be saved to a WSL-native path first, then copied to the Windows filesystem via the `/mnt/c/` mount:\n\n```bash\n# Save to WSL home, then copy to project\nwsl -d Ubuntu -- bash -c '\n  agent-browser screenshot /home/user/screenshot.png 2>&1 &&\n  cp /home/user/screenshot.png /path/to/archon/e2e-screenshots/my-test.png\n'\n```\n\n**Why not save directly to `/mnt/c/...`?** agent-browser resolves paths through its Node.js process, which on some setups mangles `/mnt/c/` paths (e.g., prepending `C:/Program Files/Git/`). Saving to a WSL-native path and copying avoids this.\n\n## Gotchas\n\n- **`localhost` doesn't work from WSL2** - must use the Windows host IP (`172.18.64.1`)\n- **Vite must bind to `0.0.0.0`** - default `localhost` isn't reachable from WSL\n- **Git Bash path expansion** - `/status` gets expanded to `C:/Program Files/Git/status` when passed through Git Bash. Not an agent-browser issue; it's the shell expanding `/` paths\n- **SSE `Connected` indicator** - only shows for `web` platform conversations; Telegram/Slack conversations show `Disconnected` (expected)\n- **Daemon startup** - if `agent-browser open` fails with \"Daemon failed to start\", kill stale daemons: `wsl -d Ubuntu -- pkill -f daemon.js` and retry\n","packages/docs-web/src/content/docs/deployment/e2e-testing.md":"---\ntitle: E2E Testing\ndescription: Set up agent-browser for end-to-end browser testing in Archon workflows.\ncategory: deployment\narea: infra\naudience: [developer, operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\nArchon uses [agent-browser](https://github.com/vercel-labs/agent-browser) (by Vercel Labs) for end-to-end browser testing in workflows. It is an **optional** external dependency — core Archon functionality works without it.\n\n## Installation\n\n```bash\n# Install globally\nnpm install -g agent-browser\n\n# Download browser engine (Chrome for Testing)\nagent-browser install\n```\n\n## Verify Installation\n\n```bash\nagent-browser --version\n# Expected: prints version number (e.g., 0.x.x)\n\n# Quick smoke test — opens a page and closes\nagent-browser open https://example.com\nagent-browser close\n```\n\n## Where It's Used\n\nNo bundled workflow requires agent-browser. Your own workflows can call it from a `bash:` node or from an AI node's instructions, for example to reproduce a UI bug in a real browser or to check a change against a running dev server.\n\n## Platform-Specific Notes\n\n### Docker\n\nagent-browser is **pre-installed** in the Archon Docker image. No action needed.\n\n### macOS / Linux\n\nWorks natively after running the install commands above. If the daemon fails to start:\n\n```bash\n# Kill stale daemons and retry\npkill -f daemon.js\nagent-browser open http://localhost:3090\n```\n\n### Windows\n\nagent-browser has a [known bug](https://github.com/vercel-labs/agent-browser/issues/56) where the daemon fails to start due to Unix domain socket incompatibility on Windows.\n\n**Workaround:** Run agent-browser inside WSL while dev servers run on Windows. See the [E2E Testing on WSL](/deployment/e2e-testing-wsl/) guide for detailed setup instructions.\n\n## Running Without agent-browser\n\nIf agent-browser is not installed, the E2E workflow nodes will fail when the agent tries to invoke `agent-browser`. The AI agent is instructed (via prompt) to stop after 2 failed connection attempts and produce a code-review-only report — but this is a prompt-level instruction, not automated workflow logic. Results may vary depending on the AI model's adherence to the instruction.\n\nYou can safely run all non-E2E workflows without agent-browser installed.\n","packages/docs-web/src/content/docs/deployment/index.md":"---\ntitle: Deployment Overview\ndescription: Overview of deployment options for running Archon locally, with Docker, or on a cloud VPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 0\n---\n\nArchon can run locally for development or be deployed to a server for always-on operation.\n\n## Deployment Options\n\n| Method | Best For | Guide |\n|--------|----------|-------|\n| **Local** | Development, personal use | [Local Development](/deployment/local/) |\n| **Docker** | Self-hosted servers, CI environments | [Docker](/deployment/docker/) |\n| **Cloud VPS** | 24/7 operation with automatic HTTPS | [Cloud Deployment](/deployment/cloud/) |\n| **Windows** | Native Windows or WSL2 | [Windows](/deployment/windows/) |\n\nUsing an existing proxy? See [Running Archon behind your own reverse proxy](/deployment/reverse-proxy/) for routing, authentication, webhook, and streaming requirements.\n\n## Database Options\n\n| Option | Setup | Best For |\n|--------|-------|----------|\n| **SQLite** (default) | Zero config, just omit `DATABASE_URL` | Single-user, CLI usage, local development |\n| **Remote PostgreSQL** | Set `DATABASE_URL` to hosted DB | Cloud deployments, shared access |\n| **Local PostgreSQL** | Docker `--profile with-db` | Self-hosted, Docker-based setups |\n\nSQLite stores data at `~/.archon/archon.db` (or `/.archon/archon.db` in Docker). It is auto-initialized on first run.\n\n## Testing\n\n| Guide | Audience |\n|-------|----------|\n| [E2E Testing](/deployment/e2e-testing/) | Developers and operators |\n| [E2E Testing on WSL](/deployment/e2e-testing-wsl/) | Developers on Windows |\n","packages/docs-web/src/content/docs/deployment/local.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Flocal.md -->","packages/docs-web/src/content/docs/deployment/reverse-proxy.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Freverse-proxy.md -->","packages/docs-web/src/content/docs/deployment/windows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Fwindows.md -->","packages/docs-web/src/content/docs/getting-started/ai-assistants.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fai-assistants.md -->","packages/docs-web/src/content/docs/getting-started/configuration.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fconfiguration.md -->","packages/docs-web/src/content/docs/getting-started/first-workflow.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Ffirst-workflow.md -->","packages/docs-web/src/content/docs/getting-started/installation.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Finstallation.md -->","packages/docs-web/src/content/docs/getting-started/operate.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Foperate.md -->","packages/docs-web/src/content/docs/getting-started/quick-start.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fquick-start.md -->","packages/docs-web/src/content/docs/getting-started/sdlc-factory.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fsdlc-factory.md -->","packages/docs-web/src/content/docs/getting-started/updating.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fupdating.md -->","packages/docs-web/src/content/docs/getting-started/why-archon.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fwhy-archon.md -->","packages/docs-web/src/content/docs/guides/approval-nodes.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fapproval-nodes.md -->","packages/docs-web/src/content/docs/guides/authoring-commands.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fauthoring-commands.md -->","packages/docs-web/src/content/docs/guides/container-isolation.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fcontainer-isolation.md -->","packages/docs-web/src/content/docs/guides/global-workflows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fglobal-workflows.md -->","packages/docs-web/src/content/docs/guides/hooks.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fhooks.md -->","packages/docs-web/src/content/docs/guides/index.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Findex.md -->","packages/docs-web/src/content/docs/guides/loop-nodes.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Floop-nodes.md -->","packages/docs-web/src/content/docs/guides/mcp-servers.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fmcp-servers.md -->","packages/docs-web/src/content/docs/guides/multi-repo-projects.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fmulti-repo-projects.md -->","packages/docs-web/src/content/docs/guides/proactive-workflows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fproactive-workflows.md -->","packages/docs-web/src/content/docs/guides/publishing-plugins.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fpublishing-plugins.md -->","packages/docs-web/src/content/docs/guides/script-nodes.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fscript-nodes.md -->","packages/docs-web/src/content/docs/guides/skills.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fskills.md -->","packages/docs-web/src/content/docs/guides/workflow-triggers.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fworkflow-triggers.md -->","packages/docs-web/src/content/docs/guides/worktrees.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fworktrees.md -->","packages/docs-web/src/content/docs/reference/api.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fapi.md -->","packages/docs-web/src/content/docs/reference/architecture.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Farchitecture.md -->","packages/docs-web/src/content/docs/reference/archon-directories.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Farchon-directories.md -->","packages/docs-web/src/content/docs/reference/cli.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fcli.md -->","packages/docs-web/src/content/docs/reference/commands.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fcommands.md -->","packages/docs-web/src/content/docs/reference/configuration.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fconfiguration.md -->","packages/docs-web/src/content/docs/reference/database.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fdatabase.md -->","packages/docs-web/src/content/docs/reference/forge.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fforge.md -->","packages/docs-web/src/content/docs/reference/index.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Findex.md -->","packages/docs-web/src/content/docs/reference/node-execution.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fnode-execution.md -->","packages/docs-web/src/content/docs/reference/provider-capabilities.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fprovider-capabilities.md -->","packages/docs-web/src/content/docs/reference/sdlc-workflows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fsdlc-workflows.md -->","packages/docs-web/src/content/docs/reference/security.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fsecurity.md -->","packages/docs-web/src/content/docs/reference/troubleshooting.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Ftroubleshooting.md -->","packages/docs-web/src/content/docs/reference/variables.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fvariables.md -->","packages/docs-web/src/content/docs/reference/workflow-language.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fworkflow-language.md -->","packages/forge/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fforge%2FREADME.md -->","packages/provider-contract/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fprovider-contract%2FREADME.md -->","packages/providers/src/codex/protocol/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fproviders%2Fsrc%2Fcodex%2Fprotocol%2FREADME.md -->","packages/web/src/experiments/console/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fweb%2Fsrc%2Fexperiments%2Fconsole%2FREADME.md -->","packages/web/src/experiments/console/builder/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fweb%2Fsrc%2Fexperiments%2Fconsole%2Fbuilder%2FREADME.md -->","plugins/chat-slack/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fchat-slack%2Farchon-plugin.json -->","plugins/forge-github/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fforge-github%2Farchon-plugin.json -->","plugins/provider-claude/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-claude%2Farchon-plugin.json -->","plugins/provider-codex/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-codex%2Farchon-plugin.json -->","plugins/provider-pi/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-pi%2Farchon-plugin.json -->"},"files":{".claude/agents/code-reviewer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcode-reviewer.md -->",".claude/agents/code-simplifier.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcode-simplifier.md -->",".claude/agents/codebase-analyst.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcodebase-analyst.md -->",".claude/agents/codebase-explorer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcodebase-explorer.md -->",".claude/agents/comment-analyzer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcomment-analyzer.md -->",".claude/agents/docs-impact.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fdocs-impact.md -->",".claude/agents/pr-test-analyzer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fpr-test-analyzer.md -->",".claude/agents/rulecheck-agent.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Frulecheck-agent.md -->",".claude/agents/sdk-verifier.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fsdk-verifier.md -->",".claude/agents/silent-failure-hunter.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fsilent-failure-hunter.md -->",".claude/agents/triage-agent.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Ftriage-agent.md -->",".claude/agents/type-design-analyzer.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Ftype-design-analyzer.md -->",".claude/agents/web-researcher.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fweb-researcher.md -->",".claude/commands/commit.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fcommit.md -->",".claude/commands/create-command.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fcreate-command.md -->",".claude/commands/execute.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fexecute.md -->",".claude/commands/github_bug_fix/implement-fix.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fgithub_bug_fix%2Fimplement-fix.md -->",".claude/commands/github_bug_fix/rca.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fgithub_bug_fix%2Frca.md -->",".claude/commands/handoff.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fhandoff.md -->",".claude/commands/plan-feature.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fplan-feature.md -->",".claude/commands/prime-backend.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-backend.md -->",".claude/commands/prime-frontend.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-frontend.md -->",".claude/commands/prime-isolation.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-isolation.md -->",".claude/commands/prime-workflows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-workflows.md -->",".claude/commands/prime.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime.md -->",".claude/commands/review-doc.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Freview-doc.md -->",".claude/commands/validate.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidate.md -->",".claude/commands/validation/code-review-fix.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fcode-review-fix.md -->",".claude/commands/validation/code-review.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fcode-review.md -->",".claude/commands/validation/execution-report.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fexecution-report.md -->",".claude/commands/validation/system-review.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fsystem-review.md -->",".claude/commands/validation/validate-2.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fvalidate-2.md -->",".claude/skills/archon-cli/SKILL.md":"---\nname: archon-cli\ndescription: |\n  Drive Archon through its CLI: run AI workflows on a repo, watch and answer the runs\n  that need a decision, drive the SDLC pack's queues (intake, build queue, merge queue,\n  discoveries), set up Archon or change its config, and author and validate workflows.\n  Use when the user says \"use archon\", \"run archon\", \"archon workflow\", \"fix issue #N\n  with archon\", \"have archon review this PR\", \"what's running / check run <id>\",\n  \"approve/reject/answer/cancel/resume that run\", \"run the build queue\", \"drain\n  discoveries\", \"set up archon\", \"configure archon\", \"change my archon config\",\n  \"create a workflow\", \"author a workflow\", or asks how to write a better Archon prompt.\n  NOT for: doing the coding work yourself. Archon delegates it to isolated runs.\nargument-hint: \"[workflow | run-id | intent]\"\n---\n\n# Archon CLI\n\nArchon runs multi-step AI workflows through the `archon` CLI. Git projects run in\nisolated worktrees by default; registered folder projects run in place. You sit on\ntop of the runs: you write the brief, launch, watch one stream, answer the decisions\nthat reach you, and read what each run left behind.\n\nRoute by intent:\n\n| The user wants to... | Read |\n|---|---|\n| Run a workflow on real work | `operate/running.md` |\n| Inspect, answer, cancel or resume runs | `operate/managing-runs.md` |\n| Drive the SDLC pack's queues | `operate/queues.md` |\n| Find out why a run failed or hangs | `operate/troubleshooting.md` |\n| Set up Archon or change config | `setup/setup-and-config.md` |\n| Author or change a workflow | `authoring/authoring-workflows.md` (then `node-reference.md` and `variables.md` in the same folder) |\n| Improve a node prompt or a run message | `authoring/prompting-mistakes.md` |\n\n- When the intent is clear (\"build me a workflow for X\", \"run archon-ship on issue\n  #42\"), go straight to the reference. Do not ask.\n- When it is ambiguous, ask which of these the user wants.\n- On a machine where `archon doctor` fails, start with `setup/setup-and-config.md`.\n\n## The operating loop\n\n1. **Discover.** `archon workflow list --json` gives compact descriptions. For each\n   plausible candidate, read the full description with `archon workflow list <name>\n   --full`. Choose from full descriptions, never from names you remember.\n2. **Check the brief.** The message, issue body or document the run reads is the\n   contract the run is measured against. It must state the problem, why it is worth\n   solving, why now, the desired outcome, the invariants and the acceptance. If any\n   is missing, say which, propose wording, and get the user's agreement before\n   launching. `operate/running.md` has the full check.\n3. **Launch detached.** Pass the work as a message, the workflow's declared inputs\n   with `--input`, or both:\n\n   ```bash\n   archon workflow run <workflow> --branch <branch-name> \"<the work>\" --detach\n   archon workflow run <workflow> --input <name>=<value> --input <name>=<value> --detach\n   ```\n\n4. **Watch once.** Run one stream as a background task of your harness, however many\n   runs you launched:\n\n   ```bash\n   archon workflow attention --watch --json        # this project; add --all for the install\n   ```\n\n   Each line is JSON. `attention` means a run needs a decision, `resolved` means it no\n   longer does, and `terminal` means a run ended. Act on the line, then keep reading.\n5. **Answer deliberately.** Read the item's question, its recommendation and the\n   evidence it links. Answer with a reason, detached:\n   `archon workflow respond <run-id> <decision> \"<reason>\" --detach`.\n   `operate/managing-runs.md` covers every form.\n6. **Read the result.** A completed run is not a verdict. `archon workflow get\n   <run-id> --json` gives the normalized `outcome` and `leave_behind.artifactFiles`.\n   Read the report before telling the user what the run concluded.\n\n## Hard rules\n\n- **Never launch on a thin brief.** A weak input does not produce a weak result. It\n  produces a confident, well-formed answer to the wrong question, at full price.\n- **Launch and answer with `--detach`.** A detached run starts its own session and\n  survives your shell. A foreground `respond` or `resume` continues the run inside\n  your shell, and closing the shell stops it.\n- **One watcher, not one per run.** One `workflow attention --watch` stream covers\n  every run in scope, including runs that triggers or other sessions start. A\n  `workflow wait <run-id>` per run is fine for one or two runs; with dozens it\n  contends for the database.\n- **Stop work only when you mean to end it.** `archon workflow cancel` stops the run\n  mid-node, and that node's provider session and unsaved work are lost. Let a run reach\n  its next pause or end when you only want to stop launching more. When one turn is\n  stuck on a tool or background command that never finishes, `archon workflow\n  end-turn <run-id>` ends that turn and keeps the run (`operate/managing-runs.md`,\n  \"A stalled turn\").\n- **Merge only what was reviewed.** When you merge a pull request by hand, pin the\n  merge to the reviewed head (`gh pr merge <n> --match-head-commit <sha>`), read the\n  open review threads first, and sync your checkout afterwards.\n- **Author agent-first.** In a workflow you write, an agent node with typed output\n  judges, scripts read facts and act at boundaries, and YAML only wires them.\n  `authoring/authoring-workflows.md` (\"Agent-first\") has the rule and an example.\n\n## Gotchas\n\n- The current directory selects the project for discovery, launches and project\n  listings. `workflow status`, `workflow runs` and `workflow attention` default to\n  that project; add `--all` for the whole install. For a git project, run from the\n  repo root. Register a non-git folder with `workflow run --folder`. A full run ID\n  works from any directory.\n- Attention items are paused runs. A running run stuck on a stalled tool call is\n  not an item: `workflow wait <run-id>` and `workflow status` show it.\n- `workflow status` and `workflow runs` set `scopeFallback: true` in JSON when an\n  unregistered project produced an install-wide result.\n- Prefer `--json` whenever you will parse output.\n- `archon --help` is authoritative. If a flag this skill names is rejected, trust the\n  CLI's help and tell the user about the drift.\n",".claude/skills/archon-cli/authoring/authoring-workflows.md":"---\nname: archon-cli-authoring\ndescription: Design and write an Archon workflow: outcome-first decomposition, gate design, node types, loop channels, packaging, fixtures. Read when creating or editing workflow YAML.\n---\n\n# Authoring Workflows\n\nDesign and write an Archon workflow — for code work, business operations, or any\ngoverned process. The method is outcome-first; the mechanics follow from it.\nExemplars: the bundled sdlc pack in any Archon checkout under\n`.archon/workflows/sdlc/<primitive>/` — read a pack YAML before writing your own.\nArchon is not code-only: triage an inbox of tickets, reconcile accounts,\nscreen applicants, keep CRM records honest — the same node types, gates, and\ndiscipline apply. What changes per domain is where the gates come from (below).\n\n## The design sequence\n\nWork through these questions in order, with the user where marked:\n\n1. **Desired outcome.** What artifact or world-state exists when this succeeds?\n   One sentence. If it cannot be stated as an outcome, it is not ready to build.\n2. **Specificity (with the user).** Is this workflow written against ONE project\n   or outcome, or meant to be generally reusable? This changes everything\n   downstream: a project-specific workflow may hardcode that repo's real gates,\n   real commands, real rules, and speak in its vocabulary — which makes it far\n   more powerful than any generic equivalent. Ask before designing.\n3. **Reuse check.** Which of the USER'S OWN workflows already do part of this?\n   (`archon workflow list`, then read their YAML.) Compose those via `include:`.\n\n   **The bundled sdlc pack is NOT a component library.** Its workflows exist to\n   teach how good workflows are written — read them as worked examples of gate\n   placement, prompt contracts, and fixture discipline, never `include:` them\n   into a user workflow by default. Their prompts and gates are deliberately\n   general so they fit any project; a user workflow composed out of them inherits\n   that generality and loses the specificity that made it valuable. If the user\n   asks for \"something like archon-ship but for my repo\", write a new workflow in\n   their project's terms, using the pack only as the craft reference.\n4. **Primitives.** Thinking in primitives is right: name the small jobs that\n   compose into the outcome — investigate, decide, implement, verify, publish;\n   or intake, enrich, judge, route, notify for an ops queue.\n   Package each coherent reusable primitive as a workflow folder with declared\n   `inputs:` and `returns:`. It can then run alone or compose into a parent with\n   `include:` and `with:`. Nodes implement a primitive's internals and the small\n   one-off glue in a parent: use an AI node for judgment and code for a checkable\n   fact. The primitive set comes from THIS outcome, not from copying another\n   workflow's node list.\n5. **Gate placement (with the user).** Where must the run stop before\n   continuing, and what decides? Three gate kinds, all first-class:\n\n   - **Deterministic gates** — a `bash:`/`script:` node owning an exit code or a\n     `when:` on a declared field. Use wherever a checkable fact settles it —\n     in code work: tests pass, file exists; in ops: totals reconcile, every\n     row has an owner, the export count matches the source. Cheapest and least\n     arguable.\n   - **Prompt command gates** — an independent verifier judging another agent's\n     output against an evidence bar. Start the verifier with `context: fresh`,\n     have it read the diff/artifact/entry, check claims and criteria, and return a\n     structured boolean that drives the loop or branch. When gate design is\n     unknown, START HERE: an agent checking agents is the most powerful\n     general-purpose gate and catches what no fixed check anticipated. Keep the\n     verdict structured (`output_format` boolean) so a machine consumes it; keep\n     the judgment prose in the prompt. Grounded on an **authored sidecar** — a\n     versioned rubric/directions file in the repo — an agent judgment gate stands\n     in for a typical human reviewer: same criteria, same questions, recorded\n     answers, every run.\n   - **Attention pauses** (`wait: { attention: ..., form: approval }`) — only\n     two cases justify them: an **interactive** workflow where someone is present\n     at the console and reads each iteration, or a **super load-bearing** action — irreversible,\n     public-facing, or expensive beyond easy rollback (deploy, post publicly,\n     delete data, send customer-facing communication). Everything else should be\n     decidable by code or by another agent. The pause holds the run durably in the\n     attention queue, whether it started in the foreground, detached, or from a\n     trigger. Container-isolated workflows cannot hold one: Archon refuses a\n     workflow with any `wait:` node under container isolation before creating the\n     run. Reject is an answer, not a cancel: add a `cancel:` node behind\n     `when: \"$<id>.output.decision == 'reject'\"` where rejecting should end the run.\n     The older `approval:` node is deprecated.\n\n   On a project-specific workflow the user often already knows some of these —\n   \"never push before I see the diff\", \"stop if the migration touches billing\",\n   \"no applicant advances without both scorecards agreeing\" — collect them\n   verbatim; they become attention pauses, cancel nodes, or `when:` conditions.\n   Everything else defaults to a prompt command gate until a real run proves\n   which part can be made deterministic.\n\n   **Gates are latent in every domain.** Any process humans govern already\n   invented its controls: double-entry, reconciliation, four-eyes review,\n   checklists, read-backs. Elicitation is the authoring work — have a strong\n   model propose candidate gates from domain knowledge, then have the user (the\n   domain expert) validate which ones actually bind. A proposed gate earns trust\n   only after it has demonstrably refused a planted bad outcome — prove gates red\n   the way you prove fixtures red.\n6. **Data flow.** What does each node hand the next? Declared values\n   (`$node.output`, `output_format` fields, workflow `inputs:`/`returns:`) carry\n   JSON values; artifacts (`$ARTIFACTS_DIR`) carry files. The producing node owns\n   the schema, including for script results and composed workflows. Read\n   `node-reference.md` → Result contracts before declaring a result, and\n   `variables.md` → Artifact pointers when returning a file reference.\n7. **Prompting** per node → see `prompting-mistakes.md` in this folder before writing prose.\n8. **Wiring** into YAML (below), then **fixtures**: every authored workflow ships\n   a dry-run fixture under `<pack>/<workflow>/fixtures/*.stubs.yaml` declaring its\n   expected outcome. Prove red paths red before trusting them. Fixtures prove the\n   paths that dry-run can execute. A reachable `workflow:` or `include:` +\n   `fan_out:` node fails dry-run by design, so prove that path with a small real\n   acceptance run while keeping fixtures for the surrounding wiring.\n9. **Validate** before any real run. None of these steps spends on a model:\n\n   ```bash\n   archon validate workflows <name>                    # schema, references, command files\n   archon workflow run <name> --dry-run --stubs-init stubs.yaml   # scratch stub scaffold\n   archon workflow run <name> --dry-run --stubs stubs.yaml\n   archon workflow test <name>                         # every declared fixture\n   ```\n\n   `--stubs-init` writes a block-style YAML stub scaffold to fill in. Keep it outside\n   `fixtures/`: a dry-run stub file is not a fixture. A fixture file declares a `fixture:` block, and\n   `--stubs` refuses it (\"this is a fixture file; run it with 'workflow test'\"). Copy\n   the stub values you need into a fixture and run it with `workflow test`. A dry run\n   walks the graph with stubbed node outputs and prints each node's resolved provider\n   and model; add `--exec-code` only for trusted `bash:` and `script:` nodes. Then make a planted\n   bad case fail: a fixture that never went red proves nothing. Finish with one real\n   run on a genuine task.\n\n## Agent-first: who decides what\n\nArchon's rule is **YAML coordinates. Code computes. Agents judge.** Give each kind\nof work to the one that does it well:\n\n- **Agents judge.** Reading a comment, a diff, a ticket or a CI log and deciding what\n  it means is a prompt node that returns a small typed result (`output_format`).\n  A script that tries the same judgment freezes one guess into code.\n- **Scripts give facts and act at boundaries.** A script reads the forge, git, files\n  or the CLI and hands the agent facts, or performs the write the agent decided.\n  It never re-judges what the agent already judged. Compute in a `bash:` or\n  `script:` node only when the rule is genuinely fixed (parsing JSON, counting,\n  comparing heads) or a typed invariant must hold before an irreversible action.\n- **YAML coordinates.** It holds topology, gates on typed fields, joins, sessions and\n  waits. Do not grow `when:` into logic; compute the decision in a node and gate on\n  its field.\n- **Natural language is not a wire format.** Never regex or keyword-match prose,\n  whether a user's message, an agent's output or a vendor's error text, to decide\n  what happens next. Let an agent read it and return a typed value.\n\nBad: a script classifies a review comment with a pattern, and the pattern is the\ndecision.\n\n```yaml\n- id: classify\n  bash: |\n    if printf '%s' \"$COMMENT\" | grep -qiE 'lgtm|approve|ship it'; then echo ready; else echo changes; fi\n- id: merge\n  depends_on: [classify]\n  when: \"$classify.output == 'ready'\"\n  script: merge\n  runtime: bun\n```\n\nGood: an agent reads the comment and returns a typed verdict, and the script only\nacts on it.\n\n```yaml\n- id: verdict\n  prompt: |\n    Read the review comment in $ARTIFACTS_DIR/comment.md. Decide whether the\n    reviewer approves merging this change as it stands.\n  output_format:\n    type: object\n    properties:\n      ready: { type: boolean }\n      reason: { type: string }\n    required: [ready, reason]\n- id: merge\n  depends_on: [verdict]\n  when: \"$verdict.output.ready == true\"\n  script: merge\n  runtime: bun\n```\n\nTest at the same seams. A script gets a test at its tool boundary: it writes an issue\nonce, reads back a pushed head, merges only what was approved. Agent judgment is\nchecked by outcome, through dry-run fixtures (`archon workflow test`) and real runs,\nnever by a test that pins a prompt's wording, the YAML's shape or the route a run\ntakes. Do not retest what the engine owns (`when:`, includes, loops, output\nvalidation). In an Archon checkout, `.archon/workflow-language-constitution.md` and\n`.archon/workflows/sdlc/CONSTITUTION.md` (sections 1 to 5 and 13) state these rules\nin full.\n\n## Start simple, then adapt to what runs teach\n\nThe first version is deliberately under-engineered:\n\n- Use few nodes, short prompts, and key gates. Do not add determinism without evidence;\n  before a real run, it is only a guess about where things fail.\n- Dogfood it immediately on something real — a genuine task in the actual repo,\n  not a demo. The run itself is the test.\n- Adapt from evidence, not anticipation: a failure shows which gate was missing;\n  a wasted review shows which check earned its place. Add machinery only where a\n  logged occurrence demands it.\n- Iterate in place: edit the folder, rerun the same kind of task, compare the\n  artifacts. The workflow converges on the project instead of being designed\n  up front.\n\nAll mechanical detail lives in `node-reference.md` (every node type and field,\nwhen:/trigger_rule wiring, fixtures) with the full substitution semantics in\n`variables.md` — read both when writing YAML.\n\n## File layout — one folder per workflow\n\nKeep each workflow in its own folder. If its scripts use pack-level shared\nmodules, copy the pack when moving it between repos. To start a workflow:\n\n```bash\nmkdir -p .archon/workflows/my-pack/my-workflow/{commands,scripts,fixtures}\n```\n\n```text\n.archon/workflows/<pack>/<workflow>/\n├── <workflow>.yaml      # name, description, inputs:, returns:, nodes:\n├── commands/*.md        # prompt files referenced by command: nodes\n├── scripts/*.py|.ts     # deterministic logic (Python/uv preferred, TS/bun fine)\n└── fixtures/*.stubs.yaml\n```\n\nSubstantive prompts live in command files, not inline. Inline `bash:` only for\none-or-two-line glue; anything with branching is a script file. Never shell as a\nscript language for logic. Ask the user their script-language preference if they\nwill maintain it. Declare Python dependencies inline (`deps:` for `uv`); keep\n`node_modules` and virtual environments out of the packaged script tree.\n\nPut shared `.ts`, `.js`, or `.py` modules and `.md` documents in\n`<pack>/.shared/` (subfolders are supported). This reserved directory supplies\nmodules and documents, never workflow definitions or `script:` targets. Documents\nship beside the modules in every build, so a script finds one relative to its own\nlocation and hands its path to a prompt node through `with:`. Missing packaged script targets fail at load time. From `<workflow>/scripts/publish.ts`, Bun can import\n`../../.shared/result.ts`. Python files can add\n`str(Path(__file__).resolve().parents[2] / \".shared\")` to `sys.path` using\n`pathlib.Path`, then import normally; Archon does not inject `PYTHONPATH`.\nAdjust the relative depth for grouped scripts.\n\nThe same paths work in project/global source, bundled binaries, and frozen\ncaptures. Keep imports inside the pack and require no npm packages or target\nproject configuration. Use regular module files: the binary generator rejects\nsymlinks under `.shared`. Write outputs to `ARTIFACTS_DIR` or `STATE_DIR`, never\nbeside a script; Python bytecode caching is disabled in runs and executable fixtures.\n\n## Node types — one per node\n\n`command` (prompt file) · `prompt` (inline) · `bash` (shell, no AI) · `script`\n(py/ts, no AI) · `loop` (iterate one AI node) · `loop_group` (repeat a sub-DAG) ·\n`approval` (deprecated; use `wait: attention`) · `cancel` (terminate with reason, usually behind `when:`) ·\n`wait` (durable suspension, or an `attention:` question answered in a form) · `workflow` (governed child run) · `include`\n(plain load-time composition or runtime-width in-run composition with `fan_out:`).\n\nSelection rules of thumb:\n\n- A machine-verifiable check → `bash:`/`script:` node, never an AI asked to \"check\".\n- Model judgment that gates downstream spend → AI node with `output_format` boolean\n  + a downstream gate or loop channel reading that field.\n- Repeated act→verify cycle → `loop_group` with the verify node's structured\n  boolean driving completion.\n\n## Loop completion channels — pick by tier\n\n- Completion externally checkable → `until_bash: \"<exit-0 check>\"`.\n- Completion is the model's judgment → `output_format` + `until_field: <bool-field>`.\n- Prose sentinel (`until: TOKEN`) is deprecated; prefer the channels above.\n  Declare at least one channel per loop.\n\n## Structured output: only where a machine decides\n\nDeclare `output_format` when a `when:`, `until_field`, or script consumes the\nfield. Otherwise let output stay whole-string prose — schemas are a reliability\ntax on non-enforcing providers. Provider-agnostic YAML always: tiers\n(`small|medium|large`) never literal model ids, no `provider:` keys in bundled work.\n\n## Isolation and interactivity\n\n- At workflow level, `worktree.enabled: true` pins worktree isolation. Omission\n  leaves the choice to the caller. On a `workflow:` child node,\n  `isolation: worktree` explicitly gives the child its own worktree. A worktree\n  bounds git files; it does NOT isolate `~/.archon`, env, database, or sessions.\n- A `wait: attention` node pauses the run in the attention queue until it is answered. It\n  needs no workflow-level declaration and does not stop a detached or triggered launch.\n- Composition (`include:`) runs inside the parent's single run/isolation/audit\n  trail. Use `workflow:` sub-runs only when a separately governed launch is the\n  actual requirement.\n\n## Common mistakes\n\nRead before writing YAML:\n\n- **Prose between nodes as a wire format.** Emitting a token so the next node can\n  grep it. Fix: structured output + typed reads, or a script exit code.\n- **AI doing what code should.** \"Check if tests pass\" as a prompt. Fix: bash/script\n  node owning the exit code.\n- **Parsing model prose to decide.** Regex over `$node.output`. Same fix.\n- **Hardcoding the project's toolchain** (`tsc`, `pytest`) in default-workflow\n  gates. Deterministic checks verify only what Archon owns (git state, artifacts,\n  exit codes of agent-discovered commands).\n- **Silent fallbacks.** An optional input that degrades quietly instead of failing.\n  Fail loud with the missing thing named.\n- **Trusting exit codes / run status as verdicts.** A declined task exits 0;\n  gate on artifacts existing and validating, and on declared outcome fields.\n- **Skipping fixtures.** Unfalsified wiring is unknown wiring. Expected-red paths\n  especially: seen red, or they prove nothing.\n- **Inline everything.** Long prompts inline in YAML, logic inline in bash.\n  Files age better, diff better, and are reusable across nodes.\n",".claude/skills/archon-cli/authoring/node-reference.md":"# Node Reference\n\nMechanical reference: every node type, its fields, how nodes wire together, and\nhow to prove wiring with fixtures. For design judgment (gates, primitives,\nprompting) read `authoring-workflows.md` in this folder first.\n\n## Workflow-level fields\n\n```yaml\nname: my-workflow            # unique; user workflows may shadow bundled names\ndescription: |\n  What this does and when to use it. Shown by `archon workflow list`.\nmodel: large                 # tier keyword (small|medium|large) or @alias — never a literal model id\ninputs:                      # declared signature; callers pass via --input name=value\n  work:\n    default: \"\"\n    description: Optional work override; prompts read $ARGUMENTS explicitly for fallback\nreturns: implement           # which node's output is the workflow's terminal output\noutcome_field: green         # persist one authored boolean beside run status (e.g. green/ready/rooted)\nworktree:\n  enabled: true              # pin worktree isolation. Bounds git files ONLY —\n                             # not ~/.archon, env vars, database, or sessions\nmutates_checkout: false      # declare when the workflow never touches the working tree\n```\n\nAn input default does not fall back to the invocation message automatically.\nWhen both are valid sources, give the model both values and state the precedence:\n\n```yaml\n- id: implement\n  prompt: |\n    Optional work override: $INPUTS.work\n    Original invocation: $ARGUMENTS\n    Use the work override when it is non-empty; otherwise use the original invocation.\n```\n\n## The eleven node types\n\nExactly one of these appears per node: `command`, `prompt`, `bash`, `script`,\n`loop`, `loop_group`, `approval`, `cancel`, `wait`, `workflow`, `include`.\n`approval` is deprecated; new pauses use `wait: { attention: ..., form: ... }`.\n\n### command / prompt — AI nodes\n\n```yaml\n- id: plan\n  command: plan              # loads <workflow-folder>/commands/plan.md — prefer files over inline prose\n  depends_on: [investigate]\n```\n```yaml\n- id: classify\n  prompt: \"Classify $ARGUMENTS. Set type to BUG or FEATURE.\"\n  allowed_tools: []          # no tools needed for pure judgment\n  output_format:             # declare ONLY when a machine consumes a field (gate/until_field/script)\n    type: object\n    properties:\n      type: { type: string, enum: [BUG, FEATURE] }\n    required: [type]\n```\n\nKey fields on AI nodes: `model`/`provider` (tiers), `output_format` (+ `output_type`\nfor typed artifact sidecars), `allowed_tools`/`denied_tools`, `effort`/`thinking`,\n`retry` (default 2x on transient), `persist_session` (continues across runs only on a\nprovider that supports session forks; others start fresh each run), `idle_timeout`.\n\nSession context is explicit when it matters:\n\n- Omitted context inherits the prior compatible provider session in a sequential\n  layer and starts fresh in a parallel layer.\n- `context: fresh` always starts clean. Use it for independent verification.\n- `context: shared` explicitly inherits the ambient session in a sequential\n  layer. It is invalid where parallel structure makes that ancestry ambiguous.\n- `context: { resume: plan }` forks the named completed upstream node's session.\n  Use it when one exact ancestry matters; the provider must support session forks.\n  Inside a `loop_group` body the source can be an enclosing node, whose session the\n  body continues across iterations, or an upstream node in the same body, whose\n  session of that iteration the consumer forks. The same-body form suits a follow-up that asks the agent to\n  finish a step it skipped.\n\n### bash — deterministic shell, no AI\n\n```yaml\n- id: test-gate\n  bash: bun run test\n  timeout: 300000            # ms; stdout captured as $test-gate.output\n```\nExit code decides pass/fail. On `timeout`, Archon signals a host node's process tree,\nso work behind `bun run` or another wrapper stops too. A process that leaves the node's\nprocess group (`setsid`) escapes. In a container node, a timeout ends only the local\n`docker exec` client, and the command inside the container may keep running. In a detached run on macOS or Linux, a timeout whose\ncleanup cannot be confirmed fails the node, even with `on_timeout: skip`.\n\nThe same node shape verifies non-code work:\n`bash: python3 scripts/reconcile.py --period 2026-08` (totals must balance),\nor a script that checks every imported row has an owner. One-or-two-line glue\nonly; anything with branching is a script node.\n\n### script — TypeScript (bun) or Python (uv), no AI\n\n```yaml\n- id: verify-report\n  script: verify-report      # loads <workflow-folder>/scripts/verify-report.py\n  runtime: uv\n  depends_on: [implement, review]\n  with:                      # typed value transport into INPUTS_<UPPER_SNAKE> env vars\n    green: \"$implement.output.green\"\n    prior: {from: \"$review.output\", if_skipped: \"none\"}\n```\n\nPrefer named script files over inline bodies. Python (`runtime: uv`) preferred,\nTypeScript (`runtime: bun`) fine. `deps:` adds uv dependencies.\n\n### Result contracts — the producer owns the schema\n\nUse the producing node's `output_format` for a JSON result. `returns:` selects\nthat node; it does not declare another schema. No value-shape declaration belongs\non `inputs:` or `returns:`.\n\n```yaml\nname: certified-result\ndescription: Certify a deterministic result and bind its field downstream\nreturns: build\nnodes:\n  - id: build\n    bash: printf '%s' '{\"ready\":true}'\n    output_format:\n      type: object\n      properties:\n        ready: {type: boolean}\n      required: [ready]\n  - id: consume\n    depends_on: [build]\n    with:\n      ready: \"$build.output.ready\"\n    runtime: uv\n    script: |\n      import os\n      print(os.environ[\"INPUTS_READY\"])\n```\n\nA `bash:` or `script:` node with `output_format` must print one strict JSON\ndocument satisfying that schema. Send diagnostics to stderr. There is no JSON\nrepair or reask; a contract violation fails the node and is not retried, even\nwith `retry:`. A schema that cannot compile is a load error. Without\n`output_format`, exec stdout remains raw text. Named scripts use the same rule;\nthe small bash example above keeps the handoff visible.\n\nAn `include:` alias exposes the selected producer's contract through flattening.\nA `workflow:` caller receives the child's selected result and declared field\nnames, persisted for cold resume. Do not repeat the schema on the caller:\n`output_format` on a `workflow:` node is a load error; on `include:` it is ignored\nwith a warning. A schemaless child stays schemaless. Fan-out returns an ordered\narray of item values; consume the whole aggregate, not a field on the array.\n\nFor field-access and file-pointer rules, read `variables.md` → Node Output Details and\nArtifact pointers. The docs' `reference/workflow-language.md` → Result contracts\ngives the full composition examples.\n\n### loop — iterate ONE AI job\n\n```yaml\n- id: implement\n  loop:\n    command: implement\n    until_field: done        # boolean in this node's output_format\n    max_iterations: 5\n  depends_on: [plan]\n  output_format:\n    type: object\n    properties:\n      done: {type: boolean}\n    required: [done]\n```\n\nCompletion channels — declare at least one, pick by tier:\n\n- `until_bash: \"<exit-0 check>\"` — completion externally checkable (deterministic)\n- `until_field: <bool>` — completion is the model's judgment, via `output_format`\n- `until: TOKEN` — deprecated prose sentinel; use one of the channels above\n\nAlso: `fresh_context: true` (each iteration starts clean) and `$LOOP_PREV_OUTPUT`\nfor previous-iteration state.\nA failed iteration fails the loop at `max_iterations` — bound exhaustion fails\ntruthfully rather than blessing a bad last attempt.\n\n### loop_group — repeat a sub-DAG\n\n```yaml\n- id: fix-cycle\n  depends_on: [review-findings]\n  loop_group:\n    nodes:\n      - id: fix\n        prompt: \"Address findings: $review-findings.output\"\n      - id: recheck\n        prompt: \"Verify each finding is fixed with evidence.\"\n        depends_on: [fix]\n        context: fresh\n        output_format: {type: object, properties: {ready: {type: boolean}}, required: [ready]}\n    until_bash: 'ready=$recheck.output.ready; test \"$ready\" = \"true\"'\n    max_iterations: 5\n```\n\nBody is sealed (no external `depends_on`); outer outputs reach bodies via\n`$nodeId.output`. In a `loop_group` body, `$LOOP_PREV.<body-id>.output` reads the\nprevious iteration: prompts text-substitute it, while `when:` conditions resolve it\nas a typed condition reference. A failed body node fails the group immediately.\n\n### approval — deprecated\n\n`approval:` still loads and runs, but the loader warns once per workflow file, naming\neach node and its replacement. Do not write new ones. Replace:\n\n- `approval: { message }` → `wait: { attention: <message>, form: approval }`. A legacy\n  reject cancels the run; the attention form continues with `{decision: 'reject'}`, so\n  add a `cancel:` node with `when: \"$<id>.output.decision == 'reject'\"` where rejecting\n  should end the run.\n- `approval.decisions` → `wait: { attention, form: select, options }` with the same ids\n  and labels; same `{decision, text}` output.\n- `capture_response` → read the answer's `$<id>.output.text`; every form records text.\n- `on_reject` / `$REJECTION_REASON` → a rework node with\n  `when: \"$<id>.output.decision == 'reject'\"` that reads `$<id>.output.text`, inside a\n  `loop_group` if it should repeat.\n\n### cancel — terminate deliberately\n\n```yaml\n- id: refuse\n  cancel: \"This workflow handles bugs only.\"\n  when: \"$classify.output.type != 'BUG'\"\n```\n\n### workflow / include — reuse another workflow\n\nUse `workflow:` to start a governed child run with its own artifacts, gates, cost,\nand audit trail. Use `include:` to flatten a reusable workflow block into the\ncurrent run at load time.\n\n```yaml\n- id: review-child\n  workflow: archon-review\n  with:\n    scope: \"$INPUTS.branch\"\n  isolation: worktree\n\n- id: review-inline\n  include: archon-review\n  with:\n    scope: \"$INPUTS.branch\"\n```\n\nThe `workflow:` target name is static YAML but resolves when the node executes,\nso a prior node may author the child workflow. `workflow:` also accepts `input:`\ninstead of `with:` and can declare `fan_out:`. A plain `include:` resolves and\nexpands fully at load time; the directive carries only structural graph fields\nplus `with:` and has no execution surface of its own. The include's `when:` gates the\nwhole block. Inside a `loop_group` it reads `$LOOP_PREV` from the loop that contains\nthe include.\n\nOn a `workflow:` node, `fan_out.max_parallel` limits unfinished children (default\n`5`). `max_parallel: all` starts every item at once: use it when each child asks an\nindependent attention question, because a paused child keeps its slot and a numeric\nwindow leaves later questions unstarted. Under `join: all_done` the node completes once\nevery child is terminal, even when some failed, never started or were cancelled; the run\nrecords each slot's outcome for operators (`workflow get`), but a downstream node that\njudges the results reads the `archon_failed` markers in `$<id>.output`.\n\nPut `fan_out:` on `include:` when runtime data determines how many copies of a\nstatic block should run inside the parent run:\n\n```yaml\n- id: list-scopes\n  bash: printf '[\"packages/cli\",\"packages/workflows\"]'\n\n- id: review-each\n  include: archon-review\n  depends_on: [list-scopes]\n  fan_out:\n    items: \"$list-scopes.output\"\n    as: scope               # required; each instance receives $INPUTS.scope\n    max_parallel: 1         # serial escape when the included block may write\n    join: all_done\n```\n\nThis form creates no child runs. It returns an item-ordered JSON array under\n`$review-each.output`. With `join: all_done`, a failed instance contributes an\n`archon_failed` marker; `all_success` fails the node. To run instances\nconcurrently, the complete included block must declare `mutates_checkout: false`.\nApproval gates, durable waits, and other suspension paths are not supported\ninside composed fan-out; put them before or after the fan-out.\n\n### wait — suspend durably\n\n```yaml\n- id: pause-for-ci\n  wait:\n    duration_ms: 300000\n```\n\nA wait declares exactly one condition: `duration_ms`, an ISO timestamp under\n`until`, `event` plus `deadline_ms`, or `attention`. Archon persists the suspension and\nreleases the worker slot. A time or event wait's fixed structured output includes\n`status` (`satisfied` or `expired`) and `waited_ms`; event waits may also return `event`\nand `payload`.\n\n`attention` asks a question that pauses the run until someone answers it in its `form`:\n\n```yaml\n- id: ask\n  depends_on: [plan]\n  wait:\n    attention: \"$plan.output.question\"\n    form: select              # message (default) | approval | select | multi_select\n    options:                  # select and multi_select only; or one whole \"$plan.output.options\"\n      - { id: use-main, label: \"Base on main\" }\n      - { id: use-dev }\n    labels:                   # optional; plain strings, runtime refs resolved at pause\n      about: \"$plan.output.topic\"\n```\n\nThe next node reads the answer: `{text}` for `message`, `{decision, text}` for `approval`\nand `select`, `{decisions, text}` for `multi_select`. In the `approval` form `reject` is\nan answer and never cancels the run; branch on `$ask.output.decision` with `when:`:\n\n```yaml\n- id: review\n  depends_on: [plan]\n  wait:\n    attention: \"Approve this plan? $plan.output\"\n    form: approval\n- id: stop\n  depends_on: [review]\n  when: \"$review.output.decision == 'reject'\"\n  cancel: \"Plan rejected.\"\n- id: implement\n  depends_on: [review]\n  when: \"$review.output.decision == 'approve'\"\n  command: implement\n```\n\nPause only for an interactive session or a super load-bearing action. The run pauses\nin the attention queue until answered, however it was started. To ask after each pass\nof a repeated body, end a `loop_group` body in the wait (it must be the body's sole\nterminal sink) and read the answer next iteration as `$LOOP_PREV.<wait-id>.output.text`.\n\n`labels` mean nothing to the engine; they let the user's handler workflows pick the\npause out of `archon workflow attention --label k=v`.\n\n## Wiring nodes together\n\n**`depends_on`** creates edges; independent nodes in one topological layer run\nconcurrently.\n\n**Data flow** — declared values carry scalars, artifacts carry documents:\n\n- `$nodeId.output` — whole text of an upstream node; unknown/skipped producer → `''`\n- `$nodeId.output.field` — strict JSON access: missing key **fails the consumer**\n  (never silent-empty); declared-optional field resolves to `''`\n- `with:` bindings (#2637) — prompt/command files read bound values as\n  `$INPUTS.<name>`; bash/script processes receive `INPUTS_<UPPER_SNAKE>` env vars.\n  `{from, if_skipped}` discriminates skipped producers from ran ones; a failed\n  producer fails the binding\n- `$INPUTS.<name>` — declared workflow inputs, also delivered as\n  `INPUTS_<UPPER_SNAKE>` to bash/script\n- `$ARTIFACTS_DIR` — pre-created directory for reports/evidence humans read.\n  Throwaway checkouts and dependency installs go under `$ARTIFACTS_DIR/.archon/scratch/`\n  (create it with `mkdir -p`), which Archon deletes once the run ends and its nodes have\n  stopped (a container run's when its container is removed); the rest of\n  `$ARTIFACTS_DIR` is kept\n- In `bash:` and `script:` sources, user-controlled variables (`$ARGUMENTS`,\n  `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`,\n  `$CONTEXT`) are **not** spliced as raw text (injection guard); they arrive as\n  environment variables (`ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`,\n  `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT`, `EXTERNAL_CONTEXT`,\n  `ISSUE_CONTEXT`). Read an environment-delivered value in `bash:` as `\"$ARGUMENTS\"`.\n  In `script:` read it via `process.env.ARGUMENTS` (bun) or `os.environ['ARGUMENTS']`\n  (uv/python).\n  A `loop_group` body's `bash:` node is the exception: the loop scope splices the\n  reply (`$LOOP_USER_INPUT`) into its source, shell-quoted, so the reply arrives\n  already quoted. Read the spliced reply as an already-quoted value — assign it\n  and quote the variable (`value=$LOOP_USER_INPUT; printf '%s' \"$value\"`) — rather\n  than as `\"$LOOP_USER_INPUT\"`.\n  `$nodeId.output` refs ARE substituted; give prose producers an `output_format`\n  before assigning their output directly in a script.\n\n**`when:` conditions** skip a node when false (skipped propagates to dependants):\n\n```yaml\nwhen: \"$classify.output.type == 'BUG'\"\nwhen: \"$score.output.confidence >= '0.9' && $gate.output.proceed == true\"\n```\n\nOperators: `==`, `!=`, `<`, `>`, `<=`, `>=` (numbers auto-parse, fail-closed on\nnon-numeric), compound with `&&`/`||` (`&&` binds tighter, no parentheses).\nMalformed expression → node skipped + warning. Dot-access contract violation →\nnode FAILS loudly. Guard optional paths with `when:` or `trigger_rule`.\n\n**`trigger_rule`** — join semantics over dependencies:\n\n| Value | Behavior |\n|-------|----------|\n| `all_success` | all deps succeeded (default) |\n| `one_success` | at least one dep succeeded |\n| `none_failed_min_one_success` | none failed AND ≥1 succeeded (skips OK) |\n| `all_done` | all deps terminal (completed, failed, OR skipped) |\n\n## Fixtures — prove wiring without AI spend\n\nEvery authored workflow ships dry-run fixtures in\n`<workflow-folder>/fixtures/<name>.stubs.yaml`:\n\n```yaml\n# Declared expectations + node stubs. Every non-reserved key is a node output.\nfixture:\n  expect: completed          # or failed / paused / cancelled\n  fail-node: assert-changed  # required when expect: failed — must match exactly\n  inputs:\n    branch: task-42\nimplement:\n  done: true\n  green: true\n  summary: \"stub summary\"\nexec-code: false             # true executes bash/script nodes for real (requires a git checkout)\n```\n\nRun them:\n\n```bash\narchon workflow test                    # everything discovered\narchon workflow test my-workflow        # one workflow's fixtures\n```\n\nA reachable attention wait pauses the simulation unless the fixture stubs it; the stub\nstands in for the answer and must match the form's output, e.g.\n`review: { decision: approve, text: \"\" }`. Deprecated `approval:` gates auto-approve.\n\nExpected-red fixtures are the important ones: a guard that has never been seen\nfailing proves nothing. Prove reds red before trusting them. `workflow test`\nmirrors what a real run does with its two directories: named scripts and command\nfiles come from one frozen capture of your working tree taken per invocation —\nuncommitted edits included, so authoring works as normal — while exec-code nodes\nexecute against a scratch worktree of HEAD. So a script that reaches outside\n`.archon/` with a `__file__`-relative path fails the fixture exactly as it would\nfail the run, and nothing a node executes can touch your checkout. Exec-code\nfixtures require a git checkout and fail explicitly outside one. Validate\nload-time errors with `archon workflow list`.\n",".claude/skills/archon-cli/authoring/prompting-mistakes.md":"---\nname: archon-cli-prompting-mistakes\ndescription: Common prompt mistakes when invoking Archon workflows or writing node prompts. Short, negative-form guidance. Read before dispatching work or authoring prompts.\n---\n\n# Prompting Basics\n\n## General\n\n1. Agents are smart — take advantage of that. Each command node runs a real\n   coding-agent harness under the hood, with all its capabilities.\n2. Agents inside Archon do not necessarily know about Archon itself, this run,\n   or past failures. Do not reference context the agent has no awareness of.\n\n# Prompting Mistakes\n\nShort list. Applies in two places: the **message you pass when invoking a\nworkflow** (it becomes `$ARGUMENTS` for the whole run) and the **prompts inside\ncommand/prompt nodes** you author.\n\n## The mistakes\n\n1. **Vague outcome, implied method.** \"Look into the auth thing.\" Say what done\n   looks like and where context lives: \"Fix issue #42. Use the issue, comments,\n   and current source as evidence. Preserve the documented login behavior.\"\n2. **Smuggling your diagnosis.** If you want the run's investigation tested,\n   ask for the fix in ordinary language — do not hand it your suspected root\n   cause as fact unless you have proven it.\n3. **Narrating to nobody.** Unattended nodes produce artifacts, commits, and\n   declared fields — not chat. Prompts that say \"report to me below\" produce\n   output nothing ever reads. Point every instruction at the artifact it serves.\n4. **No evidence bar.** A judging prompt without \"cite the causal line / drop\n   anything that rests on 'might'\" collects plausible filler. State what counts\n   as proof, and demand silence otherwise.\n5. **Trusting exit codes as verdicts.** A declined task exits 0. Gates read\n   artifacts and declared fields, not success bits.\n6. **Prose wire formats.** Asking a node to end with a magic token the next node\n   greps. Use structured output fields or script exit codes instead.\n7. **Everything-but-the-kitchen-sink scope.** One node, one job, stated non-goals.\n   \"Also update docs and tests and the changelog\" dilutes all three. Negative\n   scope (\"do not touch the lockfile\") is as load-bearing as positive.\n8. **Assuming memory.** Each node may be a fresh session with no prior context.\n   Restate the input path, artifact path, and constraint inside each prompt.\n9. **Stop rules left unstated.** Tell the agent when to stop and surface:\n   ambiguity → present options; missing context → name it. Never \"use judgment\"\n   on decisions with real cost.\n10. **Soft failure language.** \"Try to...\" invites theater. Put checkable failure\n    in a `bash:`/`script:` node whose exit code fails the run, or have a judging\n    node return a structured boolean and gate on it deterministically.\n\n## The one-line test\n\nBefore sending any prompt — invocation or node — ask: *could a competent engineer\nwho has never seen this conversation act on exactly this text?* If the answer\nneeds context that lives only in your head, the prompt is not done.\n",".claude/skills/archon-cli/authoring/variables.md":"# Variable Substitution Reference\n\nVariables are placeholders in command files and workflow prompts that get replaced at execution time.\n\n## Variable Table\n\n| Variable | Scope | Description |\n|----------|-------|-------------|\n| `$ARGUMENTS` | All modes | The user's original message passed to the workflow |\n| `$USER_MESSAGE` | All modes | Same as `$ARGUMENTS` — both resolve to the user's message |\n| `$WORKFLOW_ID` | All modes | Unique workflow run ID (for tracking and logging) |\n| `$ARTIFACTS_DIR` | All modes | Pre-created directory for this workflow run's artifacts. Write outputs here. Scratch work (a throwaway checkout, a dependency install) goes under `$ARTIFACTS_DIR/.archon/scratch/`, which Archon deletes once the run ends and its nodes have stopped (a container run's when its container is removed) |\n| `$BASE_BRANCH` | All modes | The run's resolved or recorded base branch. Throws if referenced but unresolved. See the [Variable Reference](https://archon.diy/reference/variables/#workflow-variables) for resolution and child inheritance rules |\n| `$DOCS_DIR` | All modes | Documentation directory (config `docs.path`, default `docs/`). Never throws |\n| `$CONTEXT` | All modes | GitHub issue/PR context (if available from platform). Empty string if unavailable |\n| `$EXTERNAL_CONTEXT` | All modes | Alias for `$CONTEXT` |\n| `$ISSUE_CONTEXT` | All modes | Alias for `$CONTEXT` |\n| `$LOOP_USER_INPUT` | Loop / loop_group prompts | **Deprecated** with interactive loop gates; new loops end a `loop_group` body in a `wait: attention` node and read `$LOOP_PREV.<wait-id>.output.text`. Feedback from `/workflow approve <id> <text>` at an interactive loop gate. Populated ONLY on the first iteration after a resume; empty string everywhere else |\n| `$LOOP_PREV_OUTPUT` | Loop prompts | Previous iteration's cleaned output (completion tags stripped). Empty on iteration 1. Key tool for `fresh_context: true` loops that need to know what the last pass did |\n| `$LOOP_PREV.<nodeId>.output[.field]` | loop_group body prompts and `when:` conditions | Previous iteration's output of a specific body node. Empty on iteration 1. Field access follows the strict contract below (except genuinely-absent prior output → `''`). In a body `when:`, it is a typed condition reference rather than text substitution |\n| `$REJECTION_REASON` | Legacy `approval.on_reject` prompts | **Deprecated.** The reason from `/workflow reject <id> <reason>`. Empty string everywhere else. New workflows read an attention wait's `$<wait-id>.output.text` from a node behind `when: \"$<wait-id>.output.decision == 'reject'\"` |\n| `$nodeId.output` | DAG only | Full text output of a completed upstream node. Unknown/skipped producer → `''` |\n| `$nodeId.output.field` | DAG only | JSON field access — **strict**: an unresolvable field FAILS the consuming node (see below) |\n\n## Where Variables Are Substituted\n\n- **Command files** (`.archon/commands/*.md`) — the core set (`$ARGUMENTS`/`$USER_MESSAGE`, `$WORKFLOW_ID`, `$ARTIFACTS_DIR`, `$BASE_BRANCH`, `$DOCS_DIR`, `$CONTEXT` family), plus `$nodeId.output[.field]` when the command runs as a DAG node. A command file referenced by `loop.command` or a `loop_group` body receives the same populated loop variables as an inline loop prompt. Ordinary DAG command nodes receive `''` for loop/rejection variables. `$REJECTION_REASON` is populated only in an `approval.on_reject` prompt\n- **Inline `prompt:` fields** — in DAG prompt nodes, loop prompts, and loop_group body prompts\n- **`bash:` scripts** — SPECIAL: user-controlled variables (`$ARGUMENTS`, `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`, `$CONTEXT`) are **NOT spliced as raw text** into the script (shell-injection guard). A `loop_group` body's `bash:` node is the exception: the loop scope splices the reply (`$LOOP_USER_INPUT`) into its source, shell-quoted, so the reply arrives already quoted. Read the spliced reply as an already-quoted value — assign it and quote the variable (`value=$LOOP_USER_INPUT; printf '%s' \"$value\"`) — rather than as `\"$LOOP_USER_INPUT\"`. In `bash:`, they arrive as **environment variables** instead: `ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`, `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT` (+ `EXTERNAL_CONTEXT`/`ISSUE_CONTEXT`), plus `ARTIFACTS_DIR`, `LOG_DIR`, `BASE_BRANCH` — read an environment-delivered value with `\"$ARGUMENTS\"` as normal shell env access. `$nodeId.output` refs ARE substituted: small string values are single-quoted inline; values exceeding 32,768 UTF-16 code units (JavaScript `string.length`) spill to a file and substitute as unquoted `$(cat '<path>')`, which drops trailing newlines; the command substitution splits words and expands globs when used as a bare argument. Assign first (`value=$nodeId.output`), then quote the shell variable (`printf '%s' \"$value\"`) to handle both forms. See [Shell quoting guidance](https://archon.diy/reference/variables/#shell-quoting-in-bash-vs-script).\n- **`script:` bodies** — like `bash:`, user-controlled variables (`$ARGUMENTS`, `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`, `$CONTEXT`) are **NOT spliced as raw text** into the source (injection guard, #2115). They arrive as **environment variables** — `ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`, `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT` (+ `EXTERNAL_CONTEXT`/`ISSUE_CONTEXT`), plus `ARTIFACTS_DIR`, `LOG_DIR`, `BASE_BRANCH` and managed per-project env — read via `process.env.ARGUMENTS` (bun) / `os.environ['ARGUMENTS']` (uv/python) — env vars are the general injection-safe channel for untrusted values. A literal `$ARGUMENTS`/`$USER_MESSAGE`/`$CONTEXT` left in the source is not substituted: the text stays in the source. `$nodeId.output` values are still substituted **raw** (not shell-quoted). Direct assignment (`const data = $nodeId.output;`) is safe **only with `runtime: bun` when the producer uses `output_format`**. JSON booleans and nulls are not Python literals, so a `runtime: uv` script should bind the value with `with: { data: \"$nodeId.output\" }` and parse `os.environ['INPUTS_DATA']` with `json.loads`. For arbitrary-text producers (bash/script stdout or prose), use the same environment binding instead of embedding the raw value in source, then parse defensively only if the text is expected to contain JSON. **Avoid wrapping `$nodeId.output` in a `String.raw` template literal** — it silently breaks when the output contains a backtick (common in AI-generated markdown and `output_format` payloads)\n\n## Substitution Order\n\nA template is substituted in one pass. Each reference written in the template resolves once, and the value is inserted exactly as written: a message, issue context, loop reply, rejection reason, previous output, or input value that contains `$nodeId.output`, `$INPUTS.<name>`, a variable name, or `$&` reaches the node unchanged. Where two grammars match at the same `$`, the earlier group wins:\n\n1. Loop references inside a `loop_group` body (`$LOOP_PREV.<nodeId>.output`, the group's `$LOOP_USER_INPUT`)\n2. Workflow variables (`$WORKFLOW_ID`, `$ARGUMENTS`, `$ARTIFACTS_DIR`, `$BASE_BRANCH`, `$DOCS_DIR`, `$INPUTS.<name>`, `$CONTEXT`, loop/rejection vars)\n3. Node output references (`$nodeId.output`, `$nodeId.output.field`) — DAG mode only\n\n## Context Auto-Append\n\nIf `$CONTEXT` / `$EXTERNAL_CONTEXT` / `$ISSUE_CONTEXT` is NOT present anywhere in the prompt template but context exists (e.g., from a GitHub issue), it is automatically appended at the end after a `---` separator.\n\n## NOT Supported: `$1` … `$9` Positional Arguments\n\nDespite older docs suggesting otherwise, positional `$1`…`$9` are **not substituted** by the workflow engine — command files and prompts receive the whole message only, via `$ARGUMENTS`/`$USER_MESSAGE`. (A legacy positional-substitution helper exists in the codebase but is not wired into the execution path.) Parse arguments inside the prompt or with a `bash:`/`script:` node instead.\n\n## Node Output Details (DAG Only)\n\n`$nodeId.output` resolves to the full text output of the upstream node. If the node used `output_format:` (structured output), the output is the JSON-stringified validated result. Bash/script output without `output_format` is stdout with the trailing newline trimmed; with it, stdout is certified as JSON under the same result contract (see `node-reference.md` → Result contracts). Loop/loop_group output is the final iteration's output with completion-signal tags stripped. An attention wait outputs its answer by form: `{text}` for `message`, `{decision, text}` for `approval` and `select`, `{decisions, text}` for `multi_select`; read fields as `$ask.output.decision` and `$ask.output.text`. A deprecated gate with authored `approval.decisions` also outputs JSON `{decision, text}`. A deprecated gate without authored decisions keeps the old behavior: its approval comment is output only when `capture_response: true`, otherwise `''`. Unknown or skipped producers resolve to an empty string (with a warning logged).\n\n`$nodeId.output.field` and nested paths such as `$nodeId.output.proposal.action` are **strict** (no-silent-drop) — they either resolve or **fail the consuming node**. Paths are object field names only: no array indexing, wildcards or expressions. The same paths work in templates, `when:` (which needs a scalar), whole-value `with:` bindings and `$LOOP_PREV.<id>.output.a.b`. Do not flatten a producer's schema to reach a nested field.\n\n- Producer has `output_format`: every segment must be a declared `properties` entry; a declared field or parent that is absent or null resolves to `''` (declared-optional). An undeclared segment, at any depth, fails `archon validate workflows` when the producer's schema is known at load, and fails the consumer otherwise (typo protection).\n- Schemaless producer (no `output_format` on the producing node): the output must be a JSON object containing every key on the path, with an object at each intermediate step — anything else (non-JSON output, missing key, a scalar or array mid-path) fails the consumer.\n- Producer skipped or pending: fails the consumer — guard the reference with `when:` or a permissive `trigger_rule`.\n\nValues: strings pass through; numbers/booleans stringify; objects/arrays are JSON-stringified.\n\nFor `workflow:` results, these field rules use the child's `returns:` node's\nschema, whose declared paths travel with the value and survive cold resume. The\ncaller cannot add or narrow the contract. An `include:` alias uses its selected\nproducer directly after flattening.\n\n### Artifact pointers\n\nReturn a small JSON value containing a pointer when a machine consumer needs a\nfile result. The reserved shape is:\n\n```json\n{\"type\":\"archon_artifact\",\"run_id\":\"<producing run id>\",\"path\":\"review/report.md\"}\n```\n\nUse the actual `WORKFLOW_ID` value for `run_id`, not the literal variable name.\nThe producer must first write the file under its `$ARTIFACTS_DIR`. Sibling keys\non the pointer are allowed. Before persisting the result, the engine validates\ntagged pointers against the producing run: its own run id, a nonempty relative\npath with no `..` segment or NUL, lexical containment, and an existing regular\nfile. Absolute/escaping paths, directories, missing files, and another run's id\nfail the producer.\n\nA `workflow:` parent and fan-out aggregate relay pointers unchanged without\nrevalidating them against the parent. Events and resume retain the run id and\nrelative path. The engine does not expand the pointer into an absolute path,\nread its contents into a prompt, or provide an in-workflow resolver. For prompt\nhandoffs inside the producing run, keep using `$ARTIFACTS_DIR/<path>`.\n\nThe reader owns authorization, run reachability, and realpath containment at\nread time. Producer validation is not a read-side symlink guarantee; the artifact\nAPI's outstanding realpath check is tracked in #3160. Full semantics live in\nthe repository's `reference/variables.md` → Artifact pointers in a result.\n",".claude/skills/archon-cli/operate/managing-runs.md":"---\nname: archon-cli-manage-run\ndescription: Inspect, control, and resolve Archon workflow runs: list/get/status, approve, reject, respond, cancel vs abandon, resume. Read when a run is paused, failed, or the user asks about one.\n---\n\n# Managing runs\n\nEvery run-control verb, what it actually does, and the gate semantics that are\neasy to get wrong. Project listings scope by cwd; full run IDs remain globally\naddressable.\n\n## Output contract\n\n- `--json` → one clean JSON object on stdout; logs suppressed. Use when parsing.\n- No flag → human-readable text; diagnostics on stderr.\n\n## Overriding models for one run\n\nWhen the user asks to run on different models for a specific run, rebind the\ntier keywords or aliases at launch — the persistent config stays untouched:\n\n```bash\n# Rebind one or more tiers / @aliases for this run only; repeat per binding\narchon workflow run archon-ship --branch fix/x \"...\" \\\n  --model large=pi/minimax/minimax-m3 \\\n  --model small=openai/gpt-5-mini\n```\n\nSpec format: `<tier|@alias>=<provider>/<model-ref>` where `<model-ref>` is what\nthe named provider expects (for Pi backends that is itself a `<vendor>/<model>`\nref, e.g. `pi/minimax/minimax-m3`). An unknown tier/alias errors with the list\nof defined names.\n\nVerify before dispatching real work:\n\n```bash\n# dry-run prints each node's resolved provider/model without AI spend\narchon workflow run <workflow> \"test\" --dry-run --model large=...   # see 'runs on:' lines\n```\n\nFor reusable alternate setups, prefer a config layer instead of long flag lists\n(see `../setup/setup-and-config.md`, \"Alternate config layers\").\n\n## Verbs\n\n| Goal | Command |\n|---|---|\n| List recent runs (this project) | `archon workflow runs --json` |\n| Across all projects | `archon workflow runs --all --json` |\n| Filter by status / cap rows | `archon workflow runs --status running --limit 50 --json` |\n| One run's status/error | `archon workflow get <run-id> --json` |\n| One run with per-node detail | `archon workflow get <run-id> --verbose --json` |\n| One run with raw event rows | `archon workflow get <run-id> --verbose --events --json` |\n| List a run's artifact files | `archon workflow artifacts <run-id> --json` |\n| Read one artifact | `archon workflow artifacts <run-id> <path>` |\n| List registered projects (and their `--project` keys) | `archon project list --json` |\n| Active runs (this project) | `archon workflow status --json` |\n| Active runs (all projects) | `archon workflow status --all --json` |\n| Block until the run ends or needs attention | `archon workflow wait <run-id> --json` |\n| Every run that needs someone (this project / all) | `archon workflow attention --json [--all] [--label k=v]` |\n| The queue item one run stands for | `archon workflow attention <run-id> --json` |\n| Every change in the queue, as it happens | `archon workflow attention --watch --json [--all] [--label k=v] [--after <cursor>]` |\n| Classify an item without answering it | `archon workflow attention label <run-id> --occurrence <id> --set k=v --json` |\n| Resolve a gate with any declared decision | `archon workflow respond <run-id> <decision> [text]` |\n| Answer an attention wait in its form | `archon workflow answer <run-id> --occurrence <id> [--choose <id>]... [--text <t>]` |\n| Approve (default vocabulary) | `archon workflow approve <run-id> [text]` |\n| Reject (default vocabulary) | `archon workflow reject <run-id> \"<reason>\"` |\n| End a stalled turn on a running run, keep the run | `archon workflow end-turn <run-id>` |\n| Stop a running run (its live owner first) | `archon workflow cancel <run-id>` |\n| Mark paused/failed/orphaned run cancelled | `archon workflow abandon <run-id>` |\n| Resume failed/paused from completed nodes | `archon workflow resume <run-id>` |\n\n## Answering a decision well\n\nAn attention item is a question a run cannot settle. Most are written to be answered\nby whoever is on top of the runs, which is often you, not the user. Answer the ones\nthe user's standing direction already decides; bring the user only the ones that are\nunclear, risky, or outside what they have told you.\n\n1. Read the whole item: what is being decided, why it matters, the recommendation\n   with its evidence and what would change it, the choices and what each does, and\n   what happens if nobody answers. Open the evidence links before agreeing.\n2. Choose one of the declared choices. When none fits and the item offers `other`,\n   choose it and say what to do instead.\n3. Give a reason in the text. Workflows record the answer's text with the decision,\n   and some write it down as a ruling: the SDLC build queue and merge items owned by\n   a build add it to the issue body as a dated \"Current scope\" section that later\n   rounds work from. Enrolled own PRs keep the answer on the work item's merge\n   decision entry and write no issue section. A bare choice teaches\n   the next round nothing.\n4. Answer detached, so the continuation does not run in your shell:\n\n   ```bash\n   archon workflow respond <run-id> <choice> \"<reason>\" --detach\n   ```\n\n5. Pass `--occurrence <id>` when you answer from an item you read earlier: the\n   `occurrence` on its stream line, or `attention.occurrence` from `workflow wait\n   --json`. If the run has moved to a different pause, the answer is refused and the\n   error names the current occurrence, instead of landing on a question you never\n   read.\n\n## The approve/resume two-step\n\nWith `--json`, `approve`/`reject`/`respond` **record the decision and stop** —\nthis command does not execute the run (streaming output would corrupt the JSON).\nThe answered gate is a due continuation: a running `archon serve`, or\n`archon workflow wake [--watch]`, continues it without anyone resuming by hand.\nContainer runs (including a container write-back gate) are the exception: the\nhosts cannot continue them, so run `archon workflow resume <run-id>` yourself.\nTo record AND continue in this process:\n\n```bash\narchon workflow approve <run-id> \"ship it\"   # no --json: records + auto-resumes; background task!\n```\n\nA `wait: attention` pause (`attention.kind: \"attention\"`) is answered in the `form` it\nreports, with one of the `options` ids it lists:\n\n```bash\narchon workflow answer <run-id> --occurrence <id> --text \"done\" --json            # message\narchon workflow answer <run-id> --occurrence <id> --choose use-dev --json         # approval/select\narchon workflow answer <run-id> --occurrence <id> --choose a --choose b --json    # multi_select\n```\n\n`approve`/`reject`/`respond <option>` are shorthand for the same answer. In the\n`approval` form `reject` is an answer and does not cancel the run. Like a gate answer,\nthe answered wait is a due continuation. Answer only what the user actually decided; an\nundeclared option is refused. A `legacy:` occurrence is a wait paused before answers\nexisted: it takes no answer, so `workflow resume` it once its action is done.\n\nTo work the whole queue instead of one run, `archon workflow attention --json` lists\nevery item that needs someone, each with its `kind`, `occurrence`, `form`, `options`,\n`labels`, typed `project` and pointers to its transcript and artifacts. Answer an item\nby its `runId` and `occurrence` exactly as above. Labels are the user's routing data:\n`--label ci=other` selects one class, and `attention label` writes a classification\nwithout answering. Archon never routes by them.\n\nAn item of kind `usage_limit` asks nothing. Its run hit a provider usage limit that\nreported a reset time, and waits until then. A node that failed only downstream of the\nlimited one, such as an `all_done` node reading its output, does not turn the wait into a\nfailure. At the reset, a continuation host (`archon serve`, `archon workflow wake`, or\nthe CLI process that started the run) runs the failed nodes again. Cancel or abandon the\nrun to stop it.\n\nA workflow can ask several independent questions at once by fanning out one child run\nper question. Each child is its own item, with its own `runId` and `occurrence`, and\nits `fanOut.parentRunId` names the parent. `attention <parent-run-id>` returns only the\nfirst open child, so list the queue to find them all, and answer each child by its own\n`runId`. Answers can arrive in any order; the parent stays paused until every child\nfinishes.\n\n`workflow wait` follows one run whose id you hold. To follow every run in scope,\nincluding runs started later by triggers, schedules or other sessions, run\n`archon workflow attention --watch --json --all` as a background task. It prints one\nJSON line per change: each `attention` line is an item that needs attention, carrying\nthe same item the list returns, so treat it as a wake and answer by its `runId` and\n`occurrence`. `resolved` means it no longer does, and `terminal` means a run ended. The\nlines before `ready` are the whole open set. After a restart, `--after <cursor>`\nreplays what came after that cursor. A cursor names a feed event, not a line, and one\nevent can emit two lines with the same cursor (a `resolved` and the `attention` that\nreplaces it, or a `resolved` and a `terminal`). `--after` skips the whole event, so\ncheckpoint on a `bookmark` line you handled, or on the cursor before an event you\nhandled only partly, and accept duplicate lines on replay. Exit `4` means the store\ncannot read the stream's cursor: rerun without `--after`.\n\nOr deliberately in two steps:\n\n```bash\narchon workflow approve <run-id> \"ship it\" --json   # recorded, resumable: true\narchon workflow resume <run-id>                     # executes; background task\n```\n\nTo let the continuation own a background process, add `--detach` to\n`approve`/`reject`/`respond`/`resume`. The parent validates the run and returns an\nack with `continues: true`; the detached child records the decision and continues\nwithout `--json`. Detached `resume` acknowledges after engine acceptance clears the\nold pause, so immediately follow it with `workflow wait`. A new pause is still\nreported. If acceptance is unconfirmed after 60 seconds, inspect the run before\nretrying; the child may still continue.\n\n## Deprecated interactive-loop gates: comment or no comment is a decision\n\nRead the gate state first:\n\n```bash\narchon workflow get <run-id> --json | jq .metadata.approval.completionSignaled\n```\n\n- `true` and you approve **without** a comment → accept & complete: the node\n  finalizes from its already-computed output, no re-run.\n- You approve **with** a comment → another full iteration runs using your text\n  as feedback.\n- Reject always needs a reason. Gates with authored `decisions:` expose it as\n  `$gate.output.text`; only deprecated `approval.on_reject` prompts receive\n  `$REJECTION_REASON`. At an attention wait, reject is an answer\n  (`decision: reject`) and the run continues.\n\nChoose deliberately after reading what the gate produced — not by reflex.\n\n## Cancel vs abandon\n\n- `cancel` stops a running run. When another process owns it, that process is\n  stopped and its tree confirmed gone before `cancelled` is recorded. A run the\n  cancelling server executes itself, and a `workflow:` sub-run with no owner of\n  its own (it runs inside its root), is marked `cancelled` and stops at its\n  executor's next status check. Otherwise it refuses and changes nothing when no\n  owner answers. Use this to kill real work. Cancel still ends the run, and prints a\n  warning (`cleanupWarnings` in `--json`), when a process outside the run's process\n  group still holds a node's output, and on macOS and Linux when a host `bash:` or\n  `script:` command started or completed, because it may have left background work\n  running. The warning confirms neither a survivor nor cleanup, and Archon does not\n  signal or name such a process. Find it with `lsof` on the run's checkout, check whose\n  it is, and stop it before you reuse or remove the checkout.\n- `abandon` is for paused or failed runs, or a \"running\" row whose owner is gone\n  (crashed host). If an owner still answers on this host, it stops it first, like\n  `cancel`. If none answers, it records `cancelled` and prints the host and pid the\n  run recorded; when that host is another machine, check there before abandoning.\n  It also removes the worktree the run created, uncommitted files included, and keeps\n  the branch. Commit or copy anything you need from the worktree first. When stopping\n  the owner produced one of cancel's warnings, abandon keeps the checkout for you to\n  inspect instead.\n\n## A stalled turn: end-turn, cancel or resume\n\nAn agent or loop node can sit open on a provider tool call or reported background\ncommand that never finishes, such as a `while true; do ...; sleep 30; done` poll. The\nidle watchdog does not fire while that work is open. After\n`workflows.toolCallAttentionMs` without progress (30 minutes by default), the run raises\n`stalled_tool_calls` attention. The run stays `running`, so it is not an attention-queue\nitem and the `--watch` stream does not report it. `workflow wait <run-id>`\nreturns it as `attention.kind: \"stalled_tool_calls\"`, and `workflow status` prints it\nunder the run.\n\n- **`archon workflow end-turn <run-id>`** when the stalled work is done or useless and\n  the rest of the run is worth keeping. It records a request; the live owner checks\n  about every ten seconds and ends that turn if the same stall is still there. The\n  attempt fails as `timeout`, and the node's retry policy decides what happens next.\n  By default that is a fresh attempt, which can stall the same way. Exit `0` means the\n  request was recorded, not acted on: follow with `workflow wait <run-id>`. It refuses\n  a run that is not `running` or has nothing stalled.\n- **`cancel`** when the whole run should stop.\n- **`resume`** refuses a running run. Use it once the run has failed or paused, for\n  example after an ended turn exhausts its retries.\n\nA silent turn with no open tool or background work is a different case: its node's\n`idle_timeout` fails it as before, with no request needed.\n\n## Respond: gates beyond approve/reject\n\nSome gates declare decisions beyond the default pair (`respond <run-id> <decision>\n[text]`). Read the run's metadata to see which decisions its gates declare before\nguessing one.\n\n## Judging a finished run\n\nTerminal ≠ good. The three JSON modes expose different data:\n\n| Command | Read |\n|---|---|\n| `get <id> --json` | top-level normalized `outcome` (`succeeded`/`failed`) and `leave_behind.artifactFiles` |\n| `get <id> --verbose --json` | ordered `nodes` summaries and parse warnings |\n| `get <id> --verbose --events --json` | raw `events` rows |\n\n`outcome_field` selects a boolean from the return node, but the run stores only\nthe normalized top-level outcome. It does not expose the authored field name\n(`green`, `ready`, or `rooted`) as another top-level property. Locate the report\nthrough `leave_behind.artifactFiles`, then read the artifact before reporting to\nthe user.\n\nA completed `workflow:` fan-out node is not completed child work: under the default\n`join: all_done` it completes once every child is terminal, whatever each child's\nresult. When some slots did not complete, `workflow get <run-id>` prints a\n`Fan-out work:` line with the tally and those slots' outcomes. The full per-slot record (`completed`, `failed`,\n`not_started`, or a `cancelled_*` outcome naming who cancelled it) is\n`terminal_record.nodes[].fan_out_outcome` with `--json`.\n\nReport outcomes with their evidence: \"completed, review verdict ready:false —\n2 findings remain, report at <path>\" beats \"done\".\n\n## Discoveries: the SDLC pack's out-of-scope findings\n\nA convention of the bundled SDLC workflows (`archon-ship`, `archon-deliver`,\n`archon-review`, `archon-upkeep`), not of workflows in general. Their review lenses\nrecord proven findings that fall outside the run's accepted scope as discovery records\ninstead of blocking findings: raw per-producer files at\n`<artifacts>/discoveries/*.json` (records of `title`, `claim`, `evidence`, `relation:\nunrelated | scope_conflict | note`, `source_node`), consolidated by the review into\nthe verdict's `discoveries` records and a readable `discoveries.md`.\n\nThe run that found them files nothing. Its terminal report lists them, and\n`archon-discoveries` later decides every recorded discovery across finished runs:\nfile an issue with a full brief, track it on the open issue that already covers it,\ndrop it with a reason, or ask a maintainer. Run that drain rather than filing\ndiscoveries by hand (see `queues.md`, \"Discoveries drain\").\n\nWhen the user asks about one run's discoveries before a drain has run:\n\n1. Read the report's Discoveries section and, when it names any, open\n   `discoveries.md` (find it through `leave_behind.artifactFiles`).\n2. On a failed run, also check the raw `discoveries/*.json` records: consolidation\n   runs late, so a run that died mid-flight can hold records no report mentions.\n3. Tell the user what the run found. Do not re-litigate the verdict from them:\n   `unrelated` and `note` records never affected the run's readiness.\n\n## When a run looks wrong\n\n- Paused unexpectedly → `get --verbose`; look at the failing node's error and the\n  gate metadata.\n- Failed mid-flight → fix the cause if you can name it, then `resume <run-id>`\n  (background task). Resume skips completed nodes.\n- A \"running\" row with nothing alive → verify the process is truly gone, then\n  `abandon`.\n- Logs live under `~/.archon/workspaces/<project>/logs/`; artifacts under\n  `.../artifacts/runs/<run-id>/`. `archon doctor` checks the installation itself.\n",".claude/skills/archon-cli/operate/queues.md":"---\nname: archon-cli-queues\ndescription: Drive the SDLC pack's queues by hand: intake, the build queue, the merge queue and the discoveries drain. Their inputs, what each run does, the decisions they raise and how to answer them. Read when launching or tending any of them.\n---\n\n# Driving the SDLC queues\n\nThe bundled SDLC pack turns issues into merged pull requests through four queue\nworkflows. Each run does one step and stops only for a decision it cannot make:\n\n| Workflow | One run |\n|---|---|\n| `archon-intake` | Records an issue event and triages the best waiting issue. The verdict becomes the item's stage and a route label. |\n| `archon-build-queue` | Claims the best waiting triaged item, or the one `number` names, and takes it to a pull request whose review says Ready, one round per run. |\n| `archon-merge-queue` | Claims the items the build queue finished and merges what policy allows, one at a time, through a gate. |\n| `archon-discoveries` | Decides the out-of-scope findings finished runs recorded: file, track, drop or ask. |\n\nThe pack's README (`.archon/workflows/sdlc/README.md`) is the full contract. This\nreference covers launching and tending the queues by hand.\n\n## Before the first run\n\n- `archon workflow run archon-forge-check` must pass in the checkout. It proves the\n  pack can read the repository through its forge source (`gh` by default).\n- `.archon/sdlc/policy/maintainers.json` on the default branch lists the forge logins\n  whose issues are triaged and whose review comments count. An entry can be an object,\n  `{ \"login\": \"<login>\", \"merge_queue\": \"own-prs\" }`, which also sends that\n  maintainer's own open pull requests through the merge queue, except one a work item\n  already names, such as a pull request the build queue delivered. Without the file\n  every author is an outsider: intake labels their issues `archon-needs-maintainer`\n  and runs no agent.\n- The forge account the pack's review runs post as must also be on\n  `maintainers.json`. The merge queue counts only a review comment a listed\n  maintainer wrote; with no trusted review, no verdict is Ready and nothing merges.\n- The merge gate requires green CI at the pull request's head. A repository with no\n  CI checks reads as `none`, not green, so nothing merges there.\n- Policy documents at `.archon/sdlc/policy/<topic>.md` add to the pack's defaults\n  rather than replacing them. Where a line in the project's file conflicts with the\n  default, the project's line wins.\n- The pack installs no trigger bindings. Nothing runs on its own until the operator\n  adds one. The README shows a binding per queue; until then, launch by hand.\n\n## Inputs\n\nEvery queue takes the repository as two inputs: `repo_host` (such as `github.com`)\nand `repo_path` (`<owner>/<repo>`). Launch from the repository's checkout, detached.\n\n```bash\nR=\"--input repo_host=github.com --input repo_path=<owner>/<repo>\"\n\narchon workflow run archon-intake $R --input number=<issue> --detach\narchon workflow run archon-build-queue $R --detach\narchon workflow run archon-merge-queue $R --detach\narchon workflow run archon-discoveries $R --detach\n```\n\nOptional inputs, with their defaults in each workflow's YAML:\n\n- `archon-intake`: `action` (`opened` by default; `edited`, `reopened`, or `added`\n  for a label), `label` and `label_actor` for a label event.\n- `archon-build-queue`: `number` (the issue to build; empty claims the first in\n  order), `retriage_as` (a maintainer login; set, a run that names no item also\n  re-triages blocked items that no longer look blocked) and `round_limit` (default\n  `3`), the rounds after which a run that wants another also judges whether the rounds\n  converge.\n- `archon-merge-queue`: `limit` (default `10` items claimed), `merge_method`\n  (`squash` or `merge`) and `retriage_as` (a maintainer login; set, an issue still\n  open after its pull request merged goes back to intake).\n- `archon-discoveries`: `since` and `until` (ISO-8601) and `limit` (default `20` runs\n  per pass).\n\nIntake, the merge queue and the discoveries drain never change their checkout. They\ncreate no worktree and run in the launch checkout as it stands, so launch them from a\ncheckout that tracks the default branch. The build queue changes its checkout, so each\nrun gets its own worktree on a run-named branch. A completed round removes that\nworktree when it is clean and every commit is on a remote, and keeps the branch;\notherwise the run names the path and what removing it would lose.\n\n## Intake\n\nRun intake once per issue event. For a new issue, `--input number=<issue>` is enough.\n\nAn edit or a plain rerun does not re-triage a maintainer's issue that was already\ntriaged. To re-triage one, add the `archon-triage` label on the forge and launch the\nlabel event, naming who added it:\n\n```bash\narchon workflow run archon-intake $R --input number=<issue> \\\n  --input action=added --input label=archon-triage --input label_actor=<maintainer-login> --detach\n```\n\n`label_actor` must be on `maintainers.json`. The same event with a listed maintainer\npromotes an outside author's issue.\n\nRead the result from the issue's labels:\n\n- `archon-ready`, `archon-plan` or `archon-investigate`: routed to the build queue.\n- `archon-needs-contract`: the body misses one of the six brief elements. Fix the\n  body, then re-triage.\n- `archon-blocked`: a prerequisite or a decision must land first. Re-triage once it\n  has.\n- `archon-close`: a duplicate or already delivered. Intake closes it when the triage\n  policy allows.\n- `archon-needs-maintainer`: an outside author's issue, waiting for promotion.\n\n## Build queue\n\nA run claims the waiting item that sorts first and works it for one round. To build\na chosen issue, name it:\n\n```bash\narchon workflow run archon-build-queue $R --input number=<issue> --detach\n```\n\nA named issue that cannot be claimed refuses the run before any agent, with the\nreason: it has no work item, another run holds it, or it does not wait for the build\nqueue. Launch one run per item you want worked at the same time; the launch's\n`--config` decides which provider builds it.\n\nA round ends one of three ways, recorded on the item:\n\n- `done`: the pull request's latest review says Ready at its head. The merge queue\n  takes it from there. A pull request already reviewed Ready at its head is marked\n  ready without another round.\n- `continue`: the issue already answers what stopped the round. The run starts the\n  item's next round at once, as a detached `archon-build-queue --input number=<n>`\n  run on the same run config and the same `retriage_as` and `round_limit`.\n  If that launch fails, the item waits for the next queue run.\n- `ask`: the run pauses as one attention item labelled `build: route`, `build: stop`\n  or `build: scope`.\n\nAn item the merge queue returns waits for a new build round, with the merge queue's\nbrief. A re-triage after the queue's last decision starts the item over.\n\nDo not resume a failed build-queue run once a later build-queue run has released its\nclaim. A run that names no item releases every finished run's claim, whichever item it\nthen picks. A run named for the same `number` releases it too; one named for a\ndifferent `number` leaves it alone. A resume skips the completed `facts` node that\nclaimed the item, so the resumed run works the whole round, then its `apply` refuses\nbecause the item is no longer claimed by it. Launch a fresh run with\n`--input number=<issue>` instead.\n\n## Merge queue\n\nA sweep claims every waiting item up to `limit` and merges what policy allows, one\nat a time, through a gate that requires a Ready verdict for the head, green CI there\nand a default branch whose CI is not red. A pull request that merges cleanly into the\ndefault branch merges, even when it is behind, so one sweep can land many. The queue\nnever pushes or waits for CI: a conflict goes back to the build queue, and the default\nbranch's CI on a merge is the next sweep's first fact. Claims keep two sweeps off the\nsame item.\n\nA verdict counts only at the head it reviewed: after a push, the pull request goes\nback to the build queue for a new round. A maintainer's own pull request\n(`merge_queue: \"own-prs\"`) is an own-PR work item with no build record to return to,\nso where another would go back, the sweep asks instead and offers no `return`.\n\nA pull request whose read fails twice in a row is parked with the read error until\nthe build queue readies the item again. A maintainer's own pull request has no build\nrecord, so nothing readies it again: once parked, it stays parked, and you merge it by\nhand.\n\nA sweep merges a clear pull request on its own. An unclear one, such as a very large\nchange, pauses as an attention item labelled `merge: pr` with the choices `merge`,\n`return`, `park` and `other`. A red default branch pauses as `merge: base` with the\none choice `hold`, and the gate refuses every merge until it is green. A run raises at\nmost one attention item: the red default branch first, otherwise the first pull\nrequest the sweep asked about. A later sweep decides the rest again.\n\nA `merge` answer approves the pull request at the head it asked about, and a sweep\nthe answer starts, on the answered run's `limit`, `merge_method` and\n`retriage_as`, merges it once the gate passes. The approval carries to a later head only when the sweep\njudges that everything since is merges of the default branch and their conflict\nresolutions, and the verdict is Ready at the later head. A `return` hands the item\nback to the build queue, whose next round starts at once on the build queue's own\ninput defaults. A `park` stops work on the item until the build queue readies it again.\n\nFor an own pull request, `park` and `other` stop queue work until a later sweep\nobserves a head different from the head asked about. At the same head it stays\nparked, even after green CI or a new review. The `other` text is recorded as the\nruling on the work item; it starts no build round.\n\n## Discoveries drain\n\nEach pass decides the records of up to `limit` finished runs, oldest first. Repeat\npasses until one finds nothing queued: its `decide` node is skipped\n(`archon workflow get <run-id> --verbose --json`). Stop early when a pass pauses with a\n`discoveries: question` item, and answer it first.\n\nIssues a pass files carry no labels. Run intake on each one to triage it.\n\n## Answering queue decisions\n\nQueue items follow one shape: what is being decided, why it matters, the\nrecommendation with its evidence and what would change it, the choices and what each\ndoes, and what happens if nobody answers. Answer them as `managing-runs.md`\n(\"Answering a decision well\") describes:\n\n```bash\narchon workflow respond <run-id> <choice> \"<reason>\" --detach\n```\n\nThe build queue and merge items owned by a build write the ruling into the issue\nbody as a dated \"Current scope\" section, and later rounds work from it. An enrolled\nown PR keeps the answer on its work item's merge decision entry; it writes no issue\nsection. Write the reason as a ruling a later round can act on: what is in scope,\nwhat is not, and why. An answer that\ncontinues an item (a build `continue`, a merge `merge` or `return`) starts the follow-up\nrun at once, on the run config the answered run started with, so you don't relaunch. A\nfollow-up in the same queue also keeps the answered run's input selections.\n\n- When the recommendation fits the user's standing direction, answer it yourself.\n- When it is unclear, risky or not decided by anything the user has said, bring it to\n  the user with the item's own summary.\n- `other` exists on every pull-request and build item. Use it when no declared choice\n  fits, and say exactly what to do instead.\n\n## Merging by hand\n\nA pull request the queues did not deliver has no work item, so the merge queue does\nnot take it, unless its author's `maintainers.json` entry sets\n`merge_queue: \"own-prs\"`. To merge one yourself:\n\n1. Read the review verdict and the open review threads, including automated\n   reviewers' inline comments.\n2. Merge at the head you read: `gh pr merge <n> --squash --match-head-commit <sha>`.\n   A push since your read makes the merge fail instead of landing unreviewed code.\n3. Update your checkout to the new default branch.\n",".claude/skills/archon-cli/operate/running.md":"# Running workflows\n\nHow to discover, select and launch Archon workflows against real work, and how to watch\nwhat you launched.\n\n## Discover what exists\n\n```bash\narchon workflow list                 # human-readable compact descriptions and errors\narchon workflow list --json          # compact descriptions + descriptionTruncated state\narchon workflow list <name> --full   # exact description for one candidate\narchon plugin add owner/repo     # install a workflow pack published on GitHub\n```\n\nThe live list is authoritative. Bundled workflows ship under the `archon-` prefix\n(e.g. `archon-ship`, `archon-review`, `archon-deliver`, `archon-investigate`,\n`archon-triage`, `archon-upkeep`); user-authored workflows may\nclaim any name and override a bundled one in that repo.\n\nIn JSON output, `descriptionTruncated: true` means `description` is a discovery preview,\nnot the complete routing contract. Human output marks the same state with `[truncated]`.\nUse the previews to identify plausible candidates, then run `archon workflow list <name>\n--full` for each candidate before choosing or launching. Map intent from those full\ndescriptions — never from memory of names you have seen elsewhere. If two names plausibly\nmatch, read both full descriptions, then say which you picked and why in one line.\n\n## The input is the contract\n\nEverything the run does is measured against its input — the message, the issue body, or the\ndocument it reads. Archon governs how the work happens; it cannot supply what the user\nactually wanted. This is the highest-leverage moment in the whole flow, and it is before any\nmoney is spent.\n\nCheck the input names all six. Solution steering is a seventh, optional, and last:\n\n1. **Problem to solve** — what is wrong today, concretely.\n2. **Why it is worth solving** — the cost of leaving it.\n3. **Why now** — what makes this the moment.\n4. **Desired outcome** — what is observably different afterwards.\n5. **Invariants** — what must stay true across any acceptable implementation.\n6. **Acceptance** — how completion is checked.\n\n**When something is missing, do not fix it silently and do not launch anyway.** Tell the user\nwhich of the six is absent, propose concrete wording, and let them decide. They own the\ncontract; your job is to notice it is thin before it costs them a run.\n\nA useful shape:\n\n> Before I launch: this brief states the problem and the outcome, but not the invariants or\n> acceptance. Without acceptance the run decides for itself when it is done. Suggest adding:\n> \"Acceptance: X passes, Y is covered by a test, Z is unchanged.\" Want me to run it with that,\n> or would you rather word it yourself?\n\nTwo failure modes worth naming, because they look like diligence:\n\n- **Naming a solution before the problem is settled.** It narrows the run to the first guess\n  and hides better answers. If the user supplied one, keep it, but make sure the problem is\n  stated too — a run that only knows the proposed fix cannot tell you it was the wrong fix.\n- **Dropping steering the user actually holds.** Optional does not mean unwanted. If they have\n  said anywhere in the conversation how they want this done — reuse a primitive, avoid a\n  dependency, migrate rather than rewrite — carry it into the input as explicit steering. An\n  unstated preference cannot be honoured, and the user discovers it only in the diff.\n- **Silence about uncertainty.** An assumption stated in the brief is something the run can\n  contradict. An assumption left out is one it will quietly inherit.\n\nIf the input is a GitHub issue, read the actual body before launching rather than trusting the\ntitle. Recommend edits to the issue itself when it is thin — the issue is the durable contract,\nand the next run against it inherits whatever you leave there.\n\n## Invocation\n\n```bash\n# Default: isolated worktree, detached, non-blocking\narchon workflow run <workflow> --branch <branch-name> \"<message>\" --detach\n\n# Start from a specific base instead of the current branch\narchon workflow run <workflow> --branch <name> --from <base> \"<message>\" --detach\n\n# Foreground (blocks the shell)\narchon workflow run <workflow> --branch <name> \"<message>\"\n\n# No isolation — only when the user explicitly asks to work on the checkout directly\narchon workflow run <workflow> --no-worktree \"<message>\"\n\n# First run for a registered non-git folder project (runs in place; no branch flags)\narchon workflow run <workflow> --folder \"<message>\"\n\n# From another clone of a registered repo: the run belongs to that project\narchon workflow run <workflow> --project <owner/repo> --branch <name> \"<message>\" --detach\n```\n\n`--project <id|name>` takes a name or ID from `archon project list`. The run uses the\nproject's identity, env, artifacts and logs, and never registers the clone you launch\nfrom. Workflow source and config come from the launch directory. A worktree is cut from\nthe registered checkout, so a branch that exists only in your clone is not there; add\n`--no-worktree` to execute in the launch directory instead. It cannot be combined with\n`--folder`, `--base-branch`, `--resume` or `--dry-run`.\n\nRules:\n\n1. **Detached is the default posture.** Workflows are long-running; `--detach`\n   returns immediately and the run appears in `archon workflow runs`. In your\n   harness, even foreground invocations belong in a background task.\n2. **For git projects, pass `--branch`** unless the user chose otherwise. Derive the name\n   from the domain of work — code: `fix/<topic-or-issue-N>`, `feat/<name>`,\n   `review/pr-N`; ops/process runs: e.g. `ops/<queue>-<item>`,\n   `upkeep/<dependency>`, `triage/<source>`. To run on\n   different models for one run, see `managing-runs.md` (\"Overriding models for one\n   run\").\n   A worktree created for `--branch` stays when the run ends; `archon isolation\n   cleanup --merged` removes it after its pull request merges. Without `--branch`, a\n   completed run removes the worktree it created on its own generated branch when the\n   checkout is clean and every commit is on a remote, keeping the branch. A kept\n   checkout is named in the run's output with what removing it would lose.\n   Folder projects run in place and reject `--branch`, `--from`, and `--base`.\n3. **One workflow per shell.** Multiple pieces of work = separate invocations with\n   separate branches; they cannot conflict because each gets its own worktree.\n4. **The message is the work order.** Write it as you would brief a competent\n   engineer: what outcome is wanted, where the context lives (issue number, plan\n   path), and any constraint the user actually stated. See\n   `../authoring/prompting-mistakes.md` before writing it.\n5. **Interactive workflows need a human at their gates.** Launch them in the\n   foreground. When they pause, read the rendered gate message and declared\n   decisions from `archon workflow get <run-id> --json` under\n   `metadata.approval`; assistant JSONL rows are AI transcripts, not the gate\n   contract. Resolve the gate per `managing-runs.md`. Continuation\n   actions on the paused run may use `--detach`.\n\n## Inputs\n\nA workflow that declares `inputs:` takes them as `--input <name>=<value>`, one flag\nper input. `archon workflow list <name> --full` shows the description; the workflow's\nYAML lists each input with its default and meaning. An input without a default is\nrequired, and an undeclared name is refused.\n\n```bash\narchon workflow run archon-intake \\\n  --input repo_host=github.com --input repo_path=<owner>/<repo> --input number=42 --detach\n```\n\nInputs and a message can be combined. Queue workflows such as the SDLC pack's take\ninputs only; `queues.md` lists them.\n\n## Monitoring\n\n```bash\narchon workflow attention --watch --json    # one stream: decisions and run ends, this project\narchon workflow attention --watch --json --all   # the same across the install\narchon workflow wait <run-id> --json        # block on one run until it ends or needs attention\narchon workflow runs --json                 # recent runs for this project\narchon workflow status --json               # active runs (running or paused) for this project\narchon workflow get <run-id> --json         # one run: status, outcome, artifacts\narchon workflow get <run-id> --verbose --json  # plus per-node detail\n```\n\nTerminal statuses: `completed`, `failed`, `cancelled`.\n\n**Watch with one stream.** Right after your first launch, run `archon workflow\nattention --watch --json` as a background task of your harness. It follows every run\nin scope, including runs started later by triggers, schedules or other sessions, and\nprints one JSON line per change:\n\n- `attention`: a run needs a decision. The line carries the whole item; answer it by\n  its `runId` and `occurrence` (see `managing-runs.md`).\n- `resolved`: the item no longer needs attention.\n- `terminal`: a run ended, with its `status` and `outcome`. Read its result.\n- `owner_lost`: a run says it is running but its process is gone.\n\nThe lines before `ready` are the current open set. Keep the `cursor` of the last line\nyou handled; after a restart, `--after <cursor>` replays what you missed.\n\n`workflow wait <run-id>` blocks on one run and suits one or two runs. Do not arm one\nwait per run for a batch: dozens of waiting processes contend for the database, and a\nsingle stream already wakes you per run. Never chain waits in one shell (`wait A &&\nwait B` sleeps on A while B pauses unattended).\n\n**Status is not verdict.** A `completed` run can carry a negative `outcome`. Read it\nwith `get --json`, find the report under `leave_behind.artifactFiles`, and use a\nseparate `get --verbose --json` call for node summaries. Read the report before\ntelling the user what the run concluded. For the bundled SDLC workflows the report\nmay list discoveries: findings outside the run's scope. The run records them but\nfiles nothing; `archon-discoveries` decides and files them (see `queues.md`).\n\n## Continuing finished work\n\n```bash\narchon workflow runs --open                                 # find the prior run id\narchon workflow run docs --adopt <run-id> \"Now also update the docs\"  # new run on the adopted worktree/branch\narchon complete fix/issue-42                                # remove worktree + local branch; keeps the remote branch\narchon isolation cleanup                                    # prune stale environments (7d)\narchon isolation cleanup --merged                           # prune merged ones now\n```\n\n## Resuming after failure\n\nA failed or paused run resumes from completed nodes:\n\n```bash\narchon workflow resume <run-id>\narchon workflow resume <run-id> --detach\n```\n\nDetached resume returns after engine acceptance, before completion. Your attention\nstream reports the run's next pause or end. If the\n60-second acceptance confirmation fails, inspect the run before retrying: the\nchild may still continue.\n\nThe ambient sequential session cursor is not reconstructed by a cold resume.\nExplicit `context: { resume: node-id }` ancestry is restored from the completed\nsource's saved handle, and a paused loop with `fresh_context: false` continues its\npre-pause session when the provider supports it. Use durable artifacts for every\nhandoff that must survive regardless of provider session availability.\n",".claude/skills/archon-cli/operate/troubleshooting.md":"# Troubleshooting Workflows\n\nWhere to look when a workflow fails, hangs, or does the wrong thing.\n\n## Log Locations\n\nWorkflow run logs are written as JSONL per run:\n\n```text\n~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl\n```\n\nEach line is a structured event. The discriminator is the `type` field. Values (see `packages/workflows/src/logger.ts` for the canonical list):\n\n| `type` | Meaning |\n|--------|---------|\n| `workflow_start` / `workflow_complete` / `workflow_error` | Run lifecycle |\n| `workflow_resume` | A resumed execution picked the run back up here. The first execution writes `workflow_start`; every resume writes one `workflow_resume` instead, so the rows after the n-th one belong to the run's (n+1)-th segment. Transcripts written before this row existed repeat `workflow_start` on resume |\n| `node_start` / `node_complete` / `node_error` / `node_skipped` | Node lifecycle |\n| `node_suspended` | A node paused the run; `content` names why (a gate kind such as `approval`, or `wait`) |\n| `gate_decision` | How a gate was resolved — `step` (the gate node), `decision` (`approved`, `rejected`, or an author-declared decision), and `content` (the operator's comment or rejection reason, as the run's `approval_received` event stores it: an approve without a comment records `Approved`, and the field is absent when the event stores neither). No actor is recorded. Written by the process that approved or rejected, which is often not the one running the workflow |\n| `assistant` | AI assistant message — has `content` field with the full AI output |\n| `tool` | SDK tool invocation — has `tool_name` and `tool_input` |\n| `exec_output` | What a `bash`/`script` node or `until_bash` probe printed — `stdout_tail`, `stderr_tail`, `exit_code` |\n| `watchdog_reset` | A streamed chunk renewed a node's idle watchdog — `chunk_type` only, never content. Renewals closer together than `WATCHDOG_RESET_BURST_GAP_MS` form a burst, recorded as its first and last renewal; `chunk_count` is the renewals since the node's previous record (absent on older transcripts, which wrote one row per renewal). A burst's last record can land after later rows, so order by `ts` |\n| `validation` | Historical compatibility only; current runs do not emit this type |\n\n> **Loop iteration lifecycle events are NOT in the JSONL file.** `loop_iteration_started` / `loop_iteration_completed` go through the workflow event emitter (WebSocket / `workflow_events` DB table) — query the DB or the Web UI dashboard for those. Per-iteration rows that DO reach the JSONL are keyed `<node-id>-iteration-<n>`: a loop's `node_complete` row, its `watchdog_reset` rows, and the `exec_output` row for each `until_bash` probe.\n\nFind the run ID from `archon workflow runs --status failed` (or `archon workflow runs` for the most recent run of any status; `workflow status` only shows active runs). Then:\n\n```bash\n# Last assistant message (what the AI said before failure)\njq -sr 'map(select(.type == \"assistant\") | .content) | last // empty' <log-file>\n\n# All error events (node failures + workflow-level failures)\njq 'select(.type == \"node_error\" or .type == \"workflow_error\")' <log-file>\n\n# What a specific node actually printed (evidence for \"did this really do X?\")\njq 'select(.type == \"exec_output\" and .step == \"<node-id>\")' <log-file>\n\n# Where the run was resumed, and how each gate was decided\njq 'select(.type == \"workflow_resume\" or .type == \"gate_decision\")' <log-file>\n\n# When a node's stream last renewed its watchdog, and with what chunk type\njq -s 'map(select(.type == \"watchdog_reset\" and .step == \"<node-id>\")) | sort_by(.ts) | last' <log-file>\n\n# Full event stream\ncat <log-file> | jq .\n```\n\nAdapter logs (Slack / Telegram / Web / GitHub) are emitted to stderr when `LOG_LEVEL=debug` is set on the server.\n\n## Artifact Locations\n\n```text\n~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/\n```\n\nInspect artifacts when a multi-node workflow produces wrong output. The failing node's upstream artifact is usually where the problem originated.\n\nNodes with `output_type:` also get engine-written sidecars under `runs/<run-id>/nodes/` (`<node-id>.md` + `<node-id>.meta.json`), and — for session-persisted workflows — mirrored copies under `artifacts/scopes/<workflow>/<scope>/nodes/` that survive across runs.\n\n```bash\nls ~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/\ncat ~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/issues/issue-42.md\n```\n\nArtifacts are **external** to the repo on purpose — they don't pollute git. They are kept until you remove them, except `.archon/scratch/` inside the run's directory, which holds throwaway checkouts and installs and is deleted once the run is completed, failed or cancelled and its nodes have stopped (for a container run, when its container is removed). A run whose nodes Archon cannot confirm stopped keeps its scratch.\n\n## Common Failure Modes\n\n### \"No base branch could be resolved\"\n\nA node references `$BASE_BRANCH`, but the run has no resolved base. Causes:\n- A git run where no `--base`, `worktree.baseBranch`, or codebase default was set and git detection failed. A shared-checkout child inherits this from its parent.\n- A folder project, which has no git base.\n- An isolated child that reused an existing branch without its original child run record. Its historical base is unknown, so config and parent flags cannot recover it.\n\n**Fix:**\n1. For a top-level git run, pass `--base <branch>` on `archon workflow run`, or set `worktree.baseBranch: main` (or `dev`, or whatever) in `.archon/config.yaml`. `--from` picks the starting point and does not set `$BASE_BRANCH`.\n2. For the reused-branch child, start a new parent run so the child cuts a new isolated branch with a known base.\n3. Or remove the `$BASE_BRANCH` reference if the node doesn't need it.\n\nA resumed run keeps its recorded base unless it is retargeted with `--base`. See [`$BASE_BRANCH` fail-fast](https://archon.diy/reference/variables/#base_branch-fail-fast).\n\n### \"Claude Code not found\" / \"Codex CLI binary not found\"\n\nCompiled-binary builds of Archon no longer embed Claude Code / Codex — you install them separately and Archon resolves the binary via env var or config.\n\n**Fix (Claude):**\n- Install: `curl -fsSL https://claude.ai/install.sh | bash` (or `npm install -g @anthropic-ai/claude-code`)\n- Set `CLAUDE_BIN_PATH=/path/to/claude` in `~/.archon/.env`, OR\n- Set `assistants.claude.claudeBinaryPath: /absolute/path` in `.archon/config.yaml`\n- Autodetect covers `$HOME/.local/bin/claude` (native installer) — no config needed if you used that path\n\n**Fix (Codex):**\n- Install: `npm install -g @openai/codex` (or platform-specific instructions)\n- Set `CODEX_BIN_PATH=/path/to/codex` or `assistants.codex.codexBinaryPath` in config\n- Autodetect covers the standard npm / Homebrew locations per platform\n\nSee [archon.diy/getting-started/installation/](https://archon.diy/getting-started/installation/) for full platform-specific install paths.\n\n### Workflow shows `running` for a long time but nothing happens\n\nFour possibilities:\n\n1. **The AI is actually working.** Check `~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl` — if you see recent `tool` or `assistant` events in the tail, it's fine. Wait.\n2. **The server crashed and left an orphan row.** Server startup no longer auto-fails orphaned `running` rows (per the \"Do not guess lifecycle ownership\" rule — `AGENTS.md`). Transition it manually:\n   - Any cancel (Web UI Cancel, `archon workflow cancel <run-id>`, chat `/workflow cancel [id]`) stops a live owner process before marking the row cancelled, and refuses when no owner answers — an orphan row gets that refusal, with the host and pid it recorded\n   - Verified orphan: `archon workflow abandon <run-id>` (or Abandon in the Web UI after the refused Cancel, or `/workflow abandon <id>` in chat) — marks the row cancelled\n3. **A node is past its `idle_timeout`.** The default is 30 minutes of complete silence (the timer resets on every streamed message — it's a deadlock detector, not a work limiter). Override with per-node `idle_timeout` (ms) if a node legitimately goes quiet for longer.\n4. **A tool call or background command never finished.** While the provider reports open work, `idle_timeout` does not fire. `archon workflow wait <run-id>` and `archon workflow status` show `stalled_tool_calls` attention once that work has made no progress for `workflows.toolCallAttentionMs` (30 minutes by default). `archon workflow end-turn <run-id>` ends that turn without cancelling the run; see `managing-runs.md`, \"A stalled turn\".\n\n### Workflow fails mid-way; how do I resume?\n\nFind the failed run, then resume it explicitly:\n\n```bash\narchon workflow runs\narchon workflow get <run-id>\narchon workflow resume <run-id>\n```\n\n`archon workflow resume <run-id>` targets that exact run and reuses its recorded working path/worktree. As a convenience, `archon workflow run my-workflow --resume` resumes the most recent resumable run for that workflow at the invocation cwd. A bare `archon workflow run my-workflow` starts a fresh run.\n\n**Session caveat:** A cold resume does not reconstruct the ambient sequential\n`context: shared` cursor. Explicit `context: { resume: node-id }` ancestry is\nrestored from saved run-scoped handles, and a deprecated interactive loop with\n`fresh_context: false` resumes its pre-pause session when the provider supports\nit. Artifact-based handoff remains the provider-independent path.\n\n### Paused run with no chat message\n\nA run is paused at a gate or an attention wait, but no message reached the conversation that started it.\n\n**Cause:** the platform could not deliver the pause notice. The run still waits. The failure is logged as `platform_message_send_failed` and recorded on the run as a `pause_notice_undelivered` event, which the console's run view shows. Answer the run by id: `archon workflow status`, then `approve`, `reject`, or `respond`.\n\n### `MCP server connection failed: <server>`\n\nArchon reports only the MCP servers a node declares with `mcp:`. Status for any other server goes to the debug log as `dag.mcp_server_status`. A declared server is required: on Claude and Codex the node fails as `misconfigured` before its first model call, with an error naming the server and its cause. Check the config JSON path, the server's `command` and `args`, and any env vars it references.\n\n### A node was skipped and I don't know why\n\nSkips carry a **reason** in the persisted `node_skipped` event (`archon workflow get <run-id> --verbose --json`, or the `workflow_events` table):\n\n| Reason | Meaning | Fix |\n|--------|---------|-----|\n| `trigger_rule` | Upstream states didn't satisfy the join rule (default `all_success` — a skipped upstream counts as not-success and cascades) | Use `one_success` / `none_failed_min_one_success` / `all_done` on merge nodes after `when:`-gated branches |\n| `when_condition` | The `when:` expression evaluated false | Expected behavior — check the upstream output it compared against |\n| `when_condition_parse_error` | The `when:` expression has invalid SYNTAX — fail-closed skip | Fix the expression (six operators, `&&`/`\\|\\|`, no parentheses) |\n| `prior_success` | Resume: the node completed in a prior run | Expected; use `always_run: true` to force re-execution |\n\n### Node FAILED with an `OutputRefError`-style message about `$node.output.field`\n\nNot a skip — strict field access failed loudly: the field isn't in the producer's `output_format` schema (typo), the schemaless producer's output isn't JSON / lacks the key, or the producer never ran. Fix the reference, add the field to the schema, or guard with `when:`/`trigger_rule`. (Only whole-text `$node.output` degrades silently to `''`.)\n\n### Node output is empty / a declared-optional field resolves to empty string\n\nCommon causes:\n\n1. Upstream node was **skipped** — whole-text `$node.output` is `''` for skipped producers.\n2. The field is declared in the producer's `output_format` but was absent/null in the actual output (declared-optional → `''` by design).\n3. Bash/script node printed to stderr, not stdout. Only stdout is captured.\n4. For script nodes, non-zero exit on a non-existent file / missing import fails the node. Check the run log for `node_error` entries.\n\n### A workflow-level field seems to have no effect\n\nInvalid values for optional workflow-level fields (`effort`, `sandbox`, `fallbackModel`, `betas`, `tags`, `worktree.enabled`, `mutates_checkout`) are **warn-and-drop**: the workflow still loads and runs, the field is discarded, and a warn-level `*_ignored` loader log is written (stderr for CLI commands, the server log for `archon serve`). AI-only fields on bash/script nodes, unknown keys, and deprecations are parse warnings instead. A key that belongs to another node type, such as `timeout:` on a prompt node, is a load error that names the node, the key and the node types that accept it. Run `archon validate workflows` or `archon workflow list` to see the warnings, or set `LOG_LEVEL=debug` to see them in the logs. Check both before assuming the field is broken.\n\n### Persisted session didn't restore (cold resume)\n\nWhen a node resumes a session that no longer exists, what happens depends on the provider. Pi starts a fresh session with a warning. Claude rejects the resume; when Archon confirms on the host that the transcript is gone and the rejection came before any output or tool call, it runs the node once more on a fresh session with the same warning. A second rejection, a rejection after output, a container run, or a named resume (`context: { resume }`) fails the node. Codex fails the node every time, so clear its saved session with `archon workflow reset-sessions <workflow>`. The fresh-session warning includes pointers to prior typed artifacts (`output_type` sidecars in the cross-run scope dir) so the prompt/agent can re-read lost context. If sessions are repeatedly cold, check server logs; clear broken state with `archon workflow reset-sessions <workflow>`.\n\n### `cannot fork a session` warning\n\nThe node's provider cannot fork a session (no `sessionFork` in the [provider capabilities](https://archon.diy/reference/provider-capabilities/)), so a `persist_session` node on it never continues an earlier run's session: every run starts fresh. `archon validate workflows` reports this when the provider is written in the workflow file. Once an earlier run has saved a session for this node and provider, each later run also posts this warning in chat and records a `node_session_not_continued` event naming the session it skipped. This is expected, not broken state, and `reset-sessions` does not change it. To carry context across runs, move the node to a provider that forks sessions, or have it write what the next run needs as an artifact and read that instead. If neither applies, remove `persist_session` from the node.\n\n## Useful Diagnostic Commands\n\n```bash\n# Environment sanity first: providers, DB, adapters (forge access: archon workflow run archon-forge-check)\narchon doctor\n\n# One run, any status, with raw events — the fastest \"why did node X skip/fail\"\narchon workflow get <run-id> --verbose --events --json | jq '.events[]'\n\n# Recent runs for this project, all statuses (\"did the review pass?\")\narchon workflow runs --json | jq '.runs[]'\n\n# Recent runs of any status (find a failed run's ID)\narchon workflow runs --status failed --limit 10\n\n# Active runs for this project (running / paused only — finished runs never appear here)\narchon workflow status --json | jq '.runs[]'\n\n# Active runs across all projects\narchon workflow status --all --json | jq '.runs[]'\n\n# Human-readable status of any active runs\narchon workflow status\n\n# Active worktrees and their last activity\narchon isolation list\n\n# Validate a specific workflow before running\narchon validate workflows my-workflow\n\n# Validate a specific command\narchon validate commands my-command\n\n# Dump the last 50 lines of a workflow's log\ntail -n 50 ~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl | jq .\n\n# Increase log verbosity (workflow run)\narchon workflow run my-workflow --verbose \"...\"\n\n# Increase server log verbosity\nLOG_LEVEL=debug bun run start\n```\n\n## Escalation: when nothing makes sense\n\n1. Run `archon version` and note the version.\n2. Run `archon validate workflows <name>` and capture the output.\n3. Grab the last ~50 lines of the run's JSONL log.\n4. Check the `CHANGELOG.md` for known issues / recent changes to the subsystem you're hitting.\n5. File an issue at https://github.com/coleam00/Archon/issues with version, validate output, log tail, and the YAML.\n",".claude/skills/archon-cli/setup/setup-and-config.md":"---\nname: archon-cli-setup-config\ndescription: Install Archon, verify with doctor, connect provider credentials, and change config.yaml (tiers, aliases, adapters). Read when setting up Archon or changing its configuration.\n---\n\n# Setup and config\n\nInstall Archon, verify it, and change its configuration. Run from the repo you\nwant to work on unless setting up globally.\n\n## First: is it already working?\n\n```bash\narchon --version      # CLI present?\narchon doctor         # installed providers, database, adapters\narchon doctor --full  # also probes the OpenCode runtime SDK\narchon workflow run archon-forge-check  # can the SDLC workflows reach the forge?\n```\n\nIf `doctor` is green, skip to Configuration. Only set up what is actually broken.\nFor the SDLC pack's queues, also read `../operate/queues.md` (\"Before the first run\").\n\n## Install\n\nPrerequisites: `git`, and Bun for source installs (a compiled binary needs only\nitself).\n\n```bash\n# Compiled binary; asks which providers to install\ncurl -fsSL https://archon.diy/install | bash\ncurl -fsSL https://archon.diy/install | bash -s -- --providers codex   # no question\n\n# Source install\ngit clone <archon-repo> && cd Archon\nbun install\ncd packages/cli && bun link && cd ../..\n\n# or run from a checkout without linking\nbun --cwd packages/cli src/cli.ts --help\n```\n\n## Providers (compiled binaries)\n\nA compiled binary ships without Claude, Codex and Pi; it runs only the ones\ninstalled. No provider is ever picked silently: a run needing a missing one\nfails before it starts and names the command.\n\n```bash\narchon provider add codex            # or: claude codex pi\narchon setup --providers codex           # same, then auth prompts\n```\n\nAn installed provider must match the CLI version: after upgrading Archon, rerun\n`archon provider add <ids>`. `archon doctor` shows what is installed and any\nstale provider. Source checkouts and Docker run all three from source; there\n`provider add` refuses.\n\n## Credentials — the setup wizard and its pieces\n\n```bash\narchon setup          # interactive wizard: providers + credentials + config + bundled skill\n```\n\nThe wizard composes these, which also work standalone:\n\n```bash\narchon ai list                      # which providers are connected\narchon ai key set anthropic        # API key (prompt/stdin) — anthropic, openai, ...\narchon ai login anthropic          # Claude Pro/Max subscription (Claude provider only)\narchon ai login openai             # ChatGPT/Codex subscription\narchon ai login github-copilot     # GitHub Copilot subscription\narchon auth github                  # GitHub identity via device flow\n```\n\nNotes:\n\n- Subscription logins cover Claude Pro/Max, ChatGPT/Codex, and GitHub Copilot.\n  API keys remain available through `ai key set`.\n- On multi-user installs each human connects their own credentials; runs execute\n  on the starter's keys. Solo installs just need one working provider.\n- `gh` must be authenticated (`gh auth status`) for any GitHub-touching workflow.\n\n## Config: two scopes, one schema\n\n| Scope | File | Wins |\n|---|---|---|\n| Repo | `<repo>/.archon/config.yaml` | over global |\n| Global/user | `~/.archon/config.yaml` | defaults |\n\nRead both before changing anything; change the narrowest scope that fits the ask.\n\nKey sections:\n\n```yaml\nassistants:\n  claude:\n    model: <default model for claude nodes>\n    # provider-specific options live here too\ntiers:                     # how much model a step deserves; keywords small/medium/large\n  large:\n    provider: claude\n    model: claude-<...>\n    effort: high\naliases:                   # @name shortcuts resolvable in workflows\n  \"@fast\":\n    provider: ...\n    model: ...\ndefaults:\n  loadDefaultWorkflows: true   # bundled archon-* pack\n  loadDefaultCommands: true\ndocs:\n  path: docs/                  # where $DOCS_DIR points\nenv:                           # per-project env vars injected into execution\nworktree:                      # repo scope only — see \"Files agents need in worktrees\"\n  baseBranch: main\n  copyFiles:\n    - .env\n```\n\n### Files agents need in worktrees\n\n`git worktree add` carries **tracked** files only. Everything gitignored is absent\nfrom a fresh worktree — `.env` above all — so an agent cannot start the project's\nserver, run integration tests, or reproduce anything reading local credentials\nfrom the checkout it was handed. Nothing errors; the agent just finds no config.\n\nIf a user asks why something works in their checkout but not in a run, check this\nfirst. The fix is one entry:\n\n```yaml\n# <repo>/.archon/config.yaml\nworktree:\n  copyFiles:\n    - .env\n```\n\nRules worth knowing before suggesting entries:\n\n- **Repo scope only.** `worktree` is read from `<repo>/.archon/config.yaml`.\n  Putting it in `~/.archon/config.yaml` parses and does nothing.\n- **Copy the real gitignored file, never a tracked template.** Suggesting\n  `.env.example` is wrong twice: a worktree already has it, and materialising a\n  placeholder `.env` makes a server start against fake credentials instead of\n  failing.\n- **Entries are plain paths.** Source and destination are always identical; there\n  is no rename syntax.\n- **Missing entries are skipped silently**, so listing an optional path is safe\n  and a contributor without the file is unaffected.\n- Nothing is copied unless listed. Workflows, commands, and scripts do **not**\n  need an entry — a run freezes its own source.\n\nOther common entries: `.vscode/`, `.claude/`, and local-only fixture or plans\ndirectories. Full semantics, including path-traversal rejection and the\n`worktree.path` interaction, are in `/reference/configuration/`.\n\nPrefer the typed commands over hand-editing when they exist — they validate:\n\n```bash\narchon ai tier set large claude claude-<model> --effort high\narchon ai tier list --json     # configured vs built-in defaults\narchon ai alias set @fast pi minimax/MiniMax-M3\narchon ai key set openai                         # masked prompt, or key via stdin\n```\n\nTier, alias, and default commands write install-wide settings unless you pass\n`--scope user`. Use the user scope for one person's preferences on a shared\ninstall; use the install scope for shared defaults.\n\nAfter edits, confirm with `archon doctor` and one cheap probe:\n`archon workflow list` should load with no errors.\n\n## Alternate config layers (per-run model setups)\n\nA user who wants a second model setup — say, a cheap free-model combo or a\npremium one — does not need to edit their main config. Create a **sparse YAML\nlayer** holding only the keys it overrides, and load it per run with `--config`:\n\n```yaml\n# e.g. .archon/config.free.yaml (operator-local; `.archon/config.*.yaml` is gitignored,\n# and `.archon/config.example.yaml` is the committed template to copy)\ntiers:\n  small:\n    provider: pi\n    model: minimax/MiniMax-M3\n  large:\n    provider: pi\n    model: minimax/MiniMax-M3\naliases:\n  \"@fast\":\n    provider: pi\n    model: minimax/MiniMax-M3\n```\n\nValid layer keys: `assistant` or `defaultAssistant`, `assistants`, `tiers`,\n`aliases`, `workflows`, `docs` (with `path`), and `env`. A tier or alias entry\nstores the Archon provider separately from the model. For `provider: pi`, the\nmodel is Pi's inner `<vendor>/<model>` reference, such as\n`minimax/MiniMax-M3`. The outer `pi/` appears only in a single-string CLI\nbinding such as `--model large=pi/minimax/MiniMax-M3`.\n\n```bash\narchon workflow run archon-ship --branch fix/x \"...\" --config .config.free.yaml\n```\n\nNotes:\n- The layer applies to that fresh run and its descendants only; nothing persists.\n- Mutually exclusive with resuming an existing run.\n- One-off, ad-hoc rebinding without a file: use `--model tier=provider/model`\n  instead — see `../operate/managing-runs.md`.\n\nTo help a user decide scope: persistent default → edit config.yaml (below);\na named alternate setup they will reuse → a config layer;\na one-time experiment → `--model` flags.\n\n## Adapters (Slack / Telegram / GitHub / Discord / Web)\n\nAdapters let platforms drive Archon remotely. Each configures via env vars and is\nverified by `doctor`. The web UI is the zero-config surface:\n\n```bash\narchon serve            # web console; downloads the server and UI on first run (default port 3090)\n```\n\nSet up a chat adapter only when the user actually asks to drive Archon from that\nplatform — do not proactively configure Slack/Telegram/Discord.\n\n## Project initialization for workflow authoring\n\nCreating `.archon/` content (workflows, commands, scripts) belongs to the authoring\ncapability: see `../authoring/authoring-workflows.md`. Setup only ensures the CLI works.\n",".claude/skills/docker-extend/SKILL.md":"---\nname: docker-extend\ndescription: |\n  Use when: User wants to extend Docker with custom tools, personalize the Docker environment,\n  or set up user-specific Docker customization.\n  Triggers: 'extend docker', 'docker-extend', 'add tools to docker', 'customize docker',\n            'add my tools to the container', 'personalize docker setup', 'docker user setup',\n            'install tools in docker'.\n  Does: Interactively sets up Dockerfile.user and docker-compose.override.yml so users can\n  add personal tools to their Docker environment without affecting maintainer files or\n  committing user-specific config to git.\nargument-hint: '[optional: tools to install, e.g. \"vim ripgrep jq\"]'\n---\n\n# Docker Extend\n\nSets up personal Docker tool customization using the project's gitignored override pattern.\nYour `Dockerfile.user` and `docker-compose.override.yml` are yours — never committed to git.\n\n## Step 1: Detect Context\n\nDetermine if the user is in dev (source build) or deploy (GHCR) mode:\n\n!`if [ -f docker-compose.yml ] && grep -q 'build: \\.' docker-compose.yml 2>/dev/null; then echo \"DEV_MODE: Building from source (root docker-compose.yml)\"; elif [ -f deploy/docker-compose.yml ]; then echo \"DEPLOY_MODE: Using GHCR image (deploy/docker-compose.yml)\"; else echo \"UNKNOWN_MODE: No docker-compose.yml found in root or deploy/\"; fi`\n\n**Decision:**\n- `DEV_MODE` → work with `Dockerfile.user` + `docker-compose.override.yml` (repo root)\n- `DEPLOY_MODE` → work with `deploy/Dockerfile.user` + `deploy/docker-compose.override.yml`\n- `UNKNOWN_MODE` → ask the user which directory their `docker-compose.yml` lives in before proceeding\n\n## Step 2: Check Current State\n\n**DEV_MODE — check root files:**\n\n!`echo \"=== Dockerfile.user ===\" && (cat Dockerfile.user 2>/dev/null || echo \"(not found — will create from example)\") && echo \"\" && echo \"=== docker-compose.override.yml ===\" && (cat docker-compose.override.yml 2>/dev/null || echo \"(not found — will create from example)\")`\n\n**DEPLOY_MODE — check deploy/ files:**\n\n!`echo \"=== deploy/Dockerfile.user ===\" && (cat deploy/Dockerfile.user 2>/dev/null || echo \"(not found — will create from example)\") && echo \"\" && echo \"=== deploy/docker-compose.override.yml ===\" && (cat deploy/docker-compose.override.yml 2>/dev/null || echo \"(not found — will create from example)\")`\n\n**If both files already exist:** Skip to Step 4 (add tools).\n**If files are missing:** Proceed to Step 3 (copy from examples).\n**If example files are missing too:** Tell the user the project hasn't added the example files yet and to check with the maintainers or the docs.\n\n## Step 3: Copy Example Files\n\nRun only the copy commands for whichever files are missing. Do NOT overwrite existing files.\n\n**DEV_MODE:**\n\n```bash\n# Only if Dockerfile.user does not exist:\ncp Dockerfile.user.example Dockerfile.user\n\n# Only if docker-compose.override.yml does not exist:\ncp docker-compose.override.example.yml docker-compose.override.yml\n```\n\n**DEPLOY_MODE:**\n\n```bash\n# Only if deploy/Dockerfile.user does not exist:\ncp deploy/Dockerfile.user.example deploy/Dockerfile.user\n\n# Only if deploy/docker-compose.override.yml does not exist:\ncp deploy/docker-compose.override.example.yml deploy/docker-compose.override.yml\n```\n\nAfter running the copy commands, confirm to the user which files were created.\n\n## Step 4: Add Tools\n\nParse `$ARGUMENTS` for tool names (space-separated, e.g., `vim ripgrep jq`).\n\n**If tools were specified:** Edit the appropriate `Dockerfile.user` to add (or uncomment) the `RUN apt-get install` block with the requested packages. Use the Edit tool — do not rewrite the entire file.\n\nThe block to add/modify:\n```dockerfile\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    <tool1> \\\n    <tool2> \\\n && rm -rf /var/lib/apt/lists/*\n```\n\n**If no tools were specified:** Show the user the file and ask which tools they want to add. Then edit the file accordingly.\n\n## Step 5: Rebuild\n\nShow the user the rebuild command for their context. Do NOT run it automatically.\n\n**DEV_MODE:**\n```bash\ndocker compose build && docker compose up -d\n```\n\n**DEPLOY_MODE:**\n```bash\ncd deploy && docker compose build && docker compose up -d\n```\n\n## Summary\n\nTell the user:\n1. Which files were created (if any)\n2. Which tools were added to `Dockerfile.user` (if any)\n3. The rebuild command to run when ready\n4. A reminder that `Dockerfile.user` and `docker-compose.override.yml` are gitignored — they're personal and won't be committed\n",".claude/skills/playwright-cli/SKILL.md":"---\nname: playwright-cli\ndescription: Automate browser interactions, test web pages and work with Playwright tests.\nallowed-tools: Bash(playwright-cli:*) Bash(npx:*) Bash(npm:*)\n---\n\n# Browser Automation with playwright-cli\n\n## Quick start\n\n```bash\n# open new browser\nplaywright-cli open\n# navigate to a page\nplaywright-cli goto https://playwright.dev\n# interact with the page using refs from the snapshot\nplaywright-cli click e15\nplaywright-cli type \"page.click\"\nplaywright-cli press Enter\n# take a screenshot (rarely used, as snapshot is more common)\nplaywright-cli screenshot\n# close the browser\nplaywright-cli close\n```\n\n## Commands\n\n### Core\n\n```bash\nplaywright-cli open\n# open and navigate right away\nplaywright-cli open https://example.com/\nplaywright-cli goto https://playwright.dev\nplaywright-cli type \"search query\"\nplaywright-cli click e3\nplaywright-cli dblclick e7\n# --submit presses Enter after filling the element\nplaywright-cli fill e5 \"user@example.com\"  --submit\nplaywright-cli drag e2 e8\n# drop files or data onto an element (from outside the page)\nplaywright-cli drop e4 --path=./image.png\nplaywright-cli drop e4 --data=\"text/plain=hello world\"\nplaywright-cli hover e4\nplaywright-cli select e9 \"option-value\"\nplaywright-cli upload ./document.pdf\nplaywright-cli check e12\nplaywright-cli uncheck e12\nplaywright-cli snapshot\n# search the snapshot for text or a regexp, returns matching nodes with surrounding context\nplaywright-cli find \"Sign in\"\nplaywright-cli find --regex \"Sign (in|up)\"\n# wrap the regexp in slashes to add flags, e.g. /i for case-insensitive\nplaywright-cli find --regex \"/sign (in|up)/i\"\nplaywright-cli eval \"document.title\"\nplaywright-cli eval \"el => el.textContent\" e5\n# get element id, class, or any attribute not visible in the snapshot\nplaywright-cli eval \"el => el.id\" e5\nplaywright-cli eval \"el => el.getAttribute('data-testid')\" e5\nplaywright-cli dialog-accept\nplaywright-cli dialog-accept \"confirmation text\"\nplaywright-cli dialog-dismiss\nplaywright-cli resize 1920 1080\nplaywright-cli close\n```\n\n### Navigation\n\n```bash\nplaywright-cli go-back\nplaywright-cli go-forward\nplaywright-cli reload\n```\n\n### Keyboard\n\n```bash\nplaywright-cli press Enter\nplaywright-cli press ArrowDown\nplaywright-cli keydown Shift\nplaywright-cli keyup Shift\n```\n\n### Mouse\n\n```bash\nplaywright-cli mousemove 150 300\nplaywright-cli mousedown\nplaywright-cli mousedown right\nplaywright-cli mouseup\nplaywright-cli mouseup right\nplaywright-cli mousewheel 0 100\n```\n\n### Save as\n\n```bash\nplaywright-cli screenshot\nplaywright-cli screenshot e5\nplaywright-cli screenshot --filename=page.png\nplaywright-cli screenshot --hires\nplaywright-cli pdf --filename=page.pdf\n```\n\n### Tabs\n\n```bash\nplaywright-cli tab-list\nplaywright-cli tab-new\nplaywright-cli tab-new https://example.com/page\nplaywright-cli tab-close\nplaywright-cli tab-close 2\nplaywright-cli tab-select 0\n```\n\n### Storage\n\n```bash\nplaywright-cli state-save\nplaywright-cli state-save auth.json\nplaywright-cli state-load auth.json\n\n# Cookies\nplaywright-cli cookie-list\nplaywright-cli cookie-list --domain=example.com\nplaywright-cli cookie-get session_id\nplaywright-cli cookie-set session_id abc123\nplaywright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure\nplaywright-cli cookie-delete session_id\nplaywright-cli cookie-clear\n\n# LocalStorage\nplaywright-cli localstorage-list\nplaywright-cli localstorage-get theme\nplaywright-cli localstorage-set theme dark\nplaywright-cli localstorage-delete theme\nplaywright-cli localstorage-clear\n\n# SessionStorage\nplaywright-cli sessionstorage-list\nplaywright-cli sessionstorage-get step\nplaywright-cli sessionstorage-set step 3\nplaywright-cli sessionstorage-delete step\nplaywright-cli sessionstorage-clear\n```\n\n### Network\n\n```bash\nplaywright-cli route \"**/*.jpg\" --status=404\nplaywright-cli route \"https://api.example.com/**\" --body='{\"mock\": true}'\nplaywright-cli route-list\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n### DevTools\n\n```bash\nplaywright-cli console\nplaywright-cli console warning\nplaywright-cli requests\nplaywright-cli request 5\nplaywright-cli run-code \"async page => await page.context().grantPermissions(['geolocation'])\"\nplaywright-cli run-code --filename=script.js\nplaywright-cli tracing-start\nplaywright-cli tracing-stop\nplaywright-cli video-start video.webm\nplaywright-cli video-chapter \"Chapter Title\" --description=\"Details\" --duration=2000\nplaywright-cli video-stop\n\n# annotate each subsequent action (click, type, ...) with a callout naming the action and highlighting the target\nplaywright-cli video-show-actions --duration=600 --position=top-right\nplaywright-cli video-hide-actions\n\n# launch the dashboard for UI review / design feedback — user annotates the page, you receive the annotated screenshot, snapshot, and notes\nplaywright-cli show --annotate\n\n# generate a Playwright locator for an element from its ref or selector\nplaywright-cli generate-locator e5 --raw\n\n# show a persistent highlight overlay for an element, optionally with a custom style\nplaywright-cli highlight e5\nplaywright-cli highlight e5 --style=\"outline: 3px dashed red\"\n# hide a single element highlight, or all page highlights when no target is given\nplaywright-cli highlight e5 --hide\nplaywright-cli highlight --hide\n```\n\n## Raw output\n\nThe global `--raw` option strips page status, generated code, and snapshot sections from the output, returning only the result value. Use it to pipe command output into other tools. Commands that don't produce output return nothing.\n\n```bash\nplaywright-cli --raw eval \"JSON.stringify(performance.timing)\" | jq '.loadEventEnd - .navigationStart'\nplaywright-cli --raw eval \"JSON.stringify([...document.querySelectorAll('a')].map(a => a.href))\" > links.json\nplaywright-cli --raw snapshot > before.yml\nplaywright-cli click e5\nplaywright-cli --raw snapshot > after.yml\ndiff before.yml after.yml\nTOKEN=$(playwright-cli --raw cookie-get session_id)\nplaywright-cli --raw localstorage-get theme\n```\n\nFor structured output wrapping every reply as JSON, pass --json\n```bash\nplaywright-cli list --json\n```\n\n## Open parameters\n```bash\n# Use specific browser when creating session\nplaywright-cli open --browser=chrome\nplaywright-cli open --browser=firefox\nplaywright-cli open --browser=webkit\nplaywright-cli open --browser=msedge\n\n# Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit).\n# Prefer this when a mobile layout is acceptable: mobile pages are usually\n# lighter, so snapshots are smaller and cheaper.\nplaywright-cli open --mobile\nplaywright-cli open --device=\"iPhone 15\"\n\n# Use persistent profile (by default profile is in-memory)\nplaywright-cli open --persistent\n# Use persistent profile with custom directory\nplaywright-cli open --profile=/path/to/profile\n\n# Connect to browser via Playwright Extension\nplaywright-cli attach --extension=chrome\n\n# Connect to a running Chrome or Edge by channel name\nplaywright-cli attach --cdp=chrome\nplaywright-cli attach --cdp=msedge\n\n# Connect to a running browser via CDP endpoint\nplaywright-cli attach --cdp=http://localhost:9222\n\n# Start with config file\nplaywright-cli open --config=my-config.json\n\n# Close the browser\nplaywright-cli close\n# Detach from an attached browser (leaves the external browser running)\nplaywright-cli -s=msedge detach\n# Delete user data for the default session\nplaywright-cli delete-data\n```\n\n## URLs with `&` on Windows\n\nOn Windows, `cmd.exe` and PowerShell treat `&` as a command separator, so URLs with multiple query parameters get truncated before `playwright-cli` runs. Escape `&` with `^&` in `cmd.exe`, or use `--%` in PowerShell:\n\n```batch\nplaywright-cli goto \"https://example.com/?a=1^&b=2\"\n```\n\n```powershell\nplaywright-cli --% goto \"https://example.com/?a=1&b=2\"\n```\n\n## Snapshots\n\nAfter each command, playwright-cli provides a snapshot of the current browser state.\n\n```bash\n> playwright-cli goto https://example.com\n### Page\n- Page URL: https://example.com/\n- Page Title: Example Domain\n### Snapshot\n[Snapshot](.playwright-cli/page-2026-02-14T19-22-42-679Z.yml)\n```\n\nYou can also take a snapshot on demand using `playwright-cli snapshot` command. All the options below can be combined as needed.\n\n```bash\n# default - save to a file with timestamp-based name\nplaywright-cli snapshot\n\n# save to file, use when snapshot is a part of the workflow result\nplaywright-cli snapshot --filename=after-click.yaml\n\n# snapshot an element instead of the whole page\nplaywright-cli snapshot \"#main\"\n\n# limit snapshot depth for efficiency, take a partial snapshot afterwards\nplaywright-cli snapshot --depth=4\nplaywright-cli snapshot e34\n\n# include each element's bounding box as [box=x,y,width,height]\nplaywright-cli snapshot --boxes\n\n# search a large snapshot instead of capturing it all — returns matching nodes\n# with 3 lines of context around each match (like grep -C)\nplaywright-cli find \"Add to cart\"\nplaywright-cli find --regex \"\\\\$[0-9]+\\\\.[0-9]{2}\"\n```\n\n## Targeting elements\n\nBy default, use refs from the snapshot to interact with page elements.\n\n```bash\n# get snapshot with refs\nplaywright-cli snapshot\n\n# interact using a ref\nplaywright-cli click e15\n```\n\nYou can also use css selectors or Playwright locators.\n\n```bash\n# css selector\nplaywright-cli click \"#main > button.submit\"\n\n# role locator\nplaywright-cli click \"getByRole('button', { name: 'Submit' })\"\n\n# test id\nplaywright-cli click \"getByTestId('submit-button')\"\n```\n\n## Browser Sessions\n\n```bash\n# create new browser session named \"mysession\" with persistent profile\nplaywright-cli -s=mysession open example.com --persistent\n# same with manually specified profile directory (use when requested explicitly)\nplaywright-cli -s=mysession open example.com --profile=/path/to/profile\nplaywright-cli -s=mysession click e6\nplaywright-cli -s=mysession close  # stop a named browser\nplaywright-cli -s=mysession delete-data  # delete user data for persistent session\n\nplaywright-cli list\n# Close all browsers\nplaywright-cli close-all\n# Forcefully kill all browser processes\nplaywright-cli kill-all\n```\n\n## Installation\n\nIf global `playwright-cli` command is not available, try a local version via `npx playwright cli`:\n\n```bash\nnpx --no-install playwright --version\n```\n\nWhen local version is available, use `npx playwright cli` in all commands. Otherwise, install `playwright-cli` as a global command:\n\n```bash\nnpm install -g @playwright/cli@latest\n```\n\n## Example: Form submission\n\n```bash\nplaywright-cli open https://example.com/form\nplaywright-cli snapshot\n\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Multi-tab workflow\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tab-new https://example.com/other\nplaywright-cli tab-list\nplaywright-cli tab-select 0\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Debugging with DevTools\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli console\nplaywright-cli requests\nplaywright-cli close\n```\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tracing-start\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli tracing-stop\nplaywright-cli close\n```\n\n## Example: Interactive session\n\nAsk the user for UI review or design feedback. The user draws boxes on the live page and types comments; you receive the annotated screenshot, the snapshot of the marked region, and the user's notes. Use this whenever the user asks for \"UI review\", \"design feedback\", or to \"ask the user what they think / want / mean\":\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli show --annotate\n```\n\n## Specific tasks\n\n* **Running and Debugging Playwright tests** [references/playwright-tests.md](references/playwright-tests.md)\n* **Request mocking** [references/request-mocking.md](references/request-mocking.md)\n* **Running Playwright code** [references/running-code.md](references/running-code.md)\n* **Browser session management** [references/session-management.md](references/session-management.md)\n* **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)\n* **Test generation (plan / generate / heal)** [references/test-generation.md](references/test-generation.md)\n* **Tracing** [references/tracing.md](references/tracing.md)\n* **Video recording** [references/video-recording.md](references/video-recording.md)\n* **Inspecting element attributes** [references/element-attributes.md](references/element-attributes.md)\n",".claude/skills/playwright-cli/references/element-attributes.md":"# Inspecting Element Attributes\n\nWhen the snapshot doesn't show an element's `id`, `class`, `data-*` attributes, or other DOM properties, use `eval` to inspect them.\n\n## Examples\n\n```bash\nplaywright-cli snapshot\n# snapshot shows a button as e7 but doesn't reveal its id or data attributes\n\n# get the element's id\nplaywright-cli eval \"el => el.id\" e7\n\n# get all CSS classes\nplaywright-cli eval \"el => el.className\" e7\n\n# get a specific attribute\nplaywright-cli eval \"el => el.getAttribute('data-testid')\" e7\nplaywright-cli eval \"el => el.getAttribute('aria-label')\" e7\n\n# get a computed style property\nplaywright-cli eval \"el => getComputedStyle(el).display\" e7\n```\n",".claude/skills/playwright-cli/references/playwright-tests.md":"# Running Playwright Tests\n\nTo run Playwright tests, use the `npx playwright test` command, or a package manager script. To avoid opening the interactive html report, use `PLAYWRIGHT_HTML_OPEN=never` environment variable.\n\n```bash\n# Run all tests\nPLAYWRIGHT_HTML_OPEN=never npx playwright test\n\n# Run all tests through a custom npm script\nPLAYWRIGHT_HTML_OPEN=never npm run special-test-command\n```\n\n# Debugging Playwright Tests\n\nTo debug a failing Playwright test, run it with `--debug=cli` option. This command will pause the test at the start and print the debugging instructions.\n\n**IMPORTANT**: run the command in the background and check the output until \"Debugging Instructions\" is printed. Make sure to stop the command after you have finished.\n\nOnce instructions containing a session name are printed, use `playwright-cli` to attach the session and explore the page.\n\n```bash\n# Run the test\nPLAYWRIGHT_HTML_OPEN=never npx playwright test --debug=cli\n# ...\n# ... debugging instructions for \"tw-abcdef\" session ...\n# ...\n\n# Attach to the test\nplaywright-cli attach tw-abcdef\n```\n\nKeep the test running in the background while you explore and look for a fix.\nThe test is paused at the start, so you should step over or pause at a particular location\nwhere the problem is most likely to be.\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code.\nThis code appears in the output and can be copied directly into the test. Most of the time, a specific locator or an expectation should be updated, but it could also be a bug in the app. Use your judgement.\n\nAfter fixing the test, stop the background test run. Rerun to check that test passes.\n",".claude/skills/playwright-cli/references/request-mocking.md":"# Request Mocking\n\nIntercept, mock, modify, and block network requests.\n\n## CLI Route Commands\n\n```bash\n# Mock with custom status\nplaywright-cli route \"**/*.jpg\" --status=404\n\n# Mock with JSON body\nplaywright-cli route \"**/api/users\" --body='[{\"id\":1,\"name\":\"Alice\"}]' --content-type=application/json\n\n# Mock with custom headers\nplaywright-cli route \"**/api/data\" --body='{\"ok\":true}' --header=\"X-Custom: value\"\n\n# Remove headers from requests\nplaywright-cli route \"**/*\" --remove-header=cookie,authorization\n\n# List active routes\nplaywright-cli route-list\n\n# Remove a route or all routes\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n## URL Patterns\n\n```\n**/api/users           - Exact path match\n**/api/*/details       - Wildcard in path\n**/*.{png,jpg,jpeg}    - Match file extensions\n**/search?q=*          - Match query parameters\n```\n\n## Advanced Mocking with run-code\n\nFor conditional responses, request body inspection, response modification, or delays:\n\n### Conditional Response Based on Request\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/login', route => {\n    const body = route.request().postDataJSON();\n    if (body.username === 'admin') {\n      route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) });\n    } else {\n      route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) });\n    }\n  });\n}\"\n```\n\n### Modify Real Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/user', async route => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.isPremium = true;\n    await route.fulfill({ response, json });\n  });\n}\"\n```\n\n### Simulate Network Failures\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/offline', route => route.abort('internetdisconnected'));\n}\"\n# Options: connectionrefused, timedout, connectionreset, internetdisconnected\n```\n\n### Delayed Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/slow', async route => {\n    await new Promise(r => setTimeout(r, 3000));\n    route.fulfill({ body: JSON.stringify({ data: 'loaded' }) });\n  });\n}\"\n```\n",".claude/skills/playwright-cli/references/running-code.md":"# Running Custom Playwright Code\n\nUse `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands.\n\n## Syntax\n\n```bash\nplaywright-cli run-code \"async page => {\n  // Your Playwright code here\n  // Access page.context() for browser context operations\n}\"\n```\n\nYou can also load the function from a file:\n\n```bash\nplaywright-cli run-code --filename=./my-script.js\n```\n\n\nThe code must be a single function expression, it is wrapped in `(...)` and evaluated.\nimport/export/require syntax is not supported.\n\n## Geolocation\n\n```bash\n# Grant geolocation permission and set location\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });\n}\"\n\n# Set location to London\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 });\n}\"\n\n# Clear geolocation override\nplaywright-cli run-code \"async page => {\n  await page.context().clearPermissions();\n}\"\n```\n\n## Permissions\n\n```bash\n# Grant multiple permissions\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions([\n    'geolocation',\n    'notifications',\n    'camera',\n    'microphone'\n  ]);\n}\"\n\n# Grant permissions for specific origin\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read'], {\n    origin: 'https://example.com'\n  });\n}\"\n```\n\n## Media Emulation\n\n```bash\n# Emulate dark color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'dark' });\n}\"\n\n# Emulate light color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'light' });\n}\"\n\n# Emulate reduced motion\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ reducedMotion: 'reduce' });\n}\"\n\n# Emulate print media\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ media: 'print' });\n}\"\n```\n\n## Wait Strategies\n\n```bash\n# Wait for network idle\nplaywright-cli run-code \"async page => {\n  await page.waitForLoadState('networkidle');\n}\"\n\n# Wait for specific element\nplaywright-cli run-code \"async page => {\n  await page.locator('.loading').waitFor({ state: 'hidden' });\n}\"\n\n# Wait for function to return true\nplaywright-cli run-code \"async page => {\n  await page.waitForFunction(() => window.appReady === true);\n}\"\n\n# Wait with timeout\nplaywright-cli run-code \"async page => {\n  await page.locator('.result').waitFor({ timeout: 10000 });\n}\"\n```\n\n## Frames and Iframes\n\n```bash\n# Work with iframe\nplaywright-cli run-code \"async page => {\n  const frame = page.locator('iframe#my-iframe').contentFrame();\n  await frame.locator('button').click();\n}\"\n\n# Get all frames\nplaywright-cli run-code \"async page => {\n  const frames = page.frames();\n  return frames.map(f => f.url());\n}\"\n```\n\n## File Downloads\n\n```bash\n# Handle file download\nplaywright-cli run-code \"async page => {\n  const downloadPromise = page.waitForEvent('download');\n  await page.getByRole('link', { name: 'Download' }).click();\n  const download = await downloadPromise;\n  await download.saveAs('./downloaded-file.pdf');\n  return download.suggestedFilename();\n}\"\n```\n\n## Clipboard\n\n```bash\n# Read clipboard (requires permission)\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read']);\n  return await page.evaluate(() => navigator.clipboard.readText());\n}\"\n\n# Write to clipboard\nplaywright-cli run-code \"async page => {\n  await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!');\n}\"\n```\n\n## Page Information\n\n```bash\n# Get page title\nplaywright-cli run-code \"async page => {\n  return await page.title();\n}\"\n\n# Get current URL\nplaywright-cli run-code \"async page => {\n  return page.url();\n}\"\n\n# Get page content\nplaywright-cli run-code \"async page => {\n  return await page.content();\n}\"\n\n# Get viewport size\nplaywright-cli run-code \"async page => {\n  return page.viewportSize();\n}\"\n```\n\n## JavaScript Execution\n\n```bash\n# Execute JavaScript and return result\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(() => {\n    return {\n      userAgent: navigator.userAgent,\n      language: navigator.language,\n      cookiesEnabled: navigator.cookieEnabled\n    };\n  });\n}\"\n\n# Pass arguments to evaluate\nplaywright-cli run-code \"async page => {\n  const multiplier = 5;\n  return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier);\n}\"\n```\n\n## Error Handling\n\n```bash\n# Try-catch in run-code\nplaywright-cli run-code \"async page => {\n  try {\n    await page.getByRole('button', { name: 'Submit' }).click({ timeout: 1000 });\n    return 'clicked';\n  } catch (e) {\n    return 'element not found';\n  }\n}\"\n```\n\n## Complex Workflows\n\n```bash\n# Login and save state\nplaywright-cli run-code \"async page => {\n  await page.goto('https://example.com/login');\n  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n  await page.getByRole('textbox', { name: 'Password' }).fill('secret');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await page.waitForURL('**/dashboard');\n  await page.context().storageState({ path: 'auth.json' });\n  return 'Login successful';\n}\"\n\n# Scrape data from multiple pages\nplaywright-cli run-code \"async page => {\n  const results = [];\n  for (let i = 1; i <= 3; i++) {\n    await page.goto(\\`https://example.com/page/\\${i}\\`);\n    const items = await page.locator('.item').allTextContents();\n    results.push(...items);\n  }\n  return results;\n}\"\n```\n",".claude/skills/playwright-cli/references/session-management.md":"# Browser Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Browser Sessions\n\nUse `-s` flag to isolate browser contexts:\n\n```bash\n# Browser 1: Authentication flow\nplaywright-cli -s=auth open https://app.example.com/login\n\n# Browser 2: Public browsing (separate cookies, storage)\nplaywright-cli -s=public open https://example.com\n\n# Commands are isolated by browser session\nplaywright-cli -s=auth fill e1 \"user@example.com\"\nplaywright-cli -s=public snapshot\n```\n\n## Browser Session Isolation Properties\n\nEach browser session has independent:\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Browser Session Commands\n\n```bash\n# List all browser sessions\nplaywright-cli list\n\n# Stop a browser session (close the browser)\nplaywright-cli close                # stop the default browser\nplaywright-cli -s=mysession close   # stop a named browser\n\n# Stop all browser sessions\nplaywright-cli close-all\n\n# Forcefully kill all daemon processes (for stale/zombie processes)\nplaywright-cli kill-all\n\n# Delete browser session user data (profile directory)\nplaywright-cli delete-data                # delete default browser data\nplaywright-cli -s=mysession delete-data   # delete named browser data\n```\n\n## Environment Variable\n\nSet a default browser session name via environment variable:\n\n```bash\nexport PLAYWRIGHT_CLI_SESSION=\"mysession\"\nplaywright-cli open example.com  # Uses \"mysession\" automatically\n```\n\n## Common Patterns\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all browsers\nplaywright-cli -s=site1 open https://site1.com &\nplaywright-cli -s=site2 open https://site2.com &\nplaywright-cli -s=site3 open https://site3.com &\nwait\n\n# Take snapshots from each\nplaywright-cli -s=site1 snapshot\nplaywright-cli -s=site2 snapshot\nplaywright-cli -s=site3 snapshot\n\n# Cleanup\nplaywright-cli close-all\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nplaywright-cli -s=variant-a open \"https://app.com?variant=a\"\nplaywright-cli -s=variant-b open \"https://app.com?variant=b\"\n\n# Compare\nplaywright-cli -s=variant-a screenshot\nplaywright-cli -s=variant-b screenshot\n```\n\n### Persistent Profile\n\nBy default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk:\n\n```bash\n# Use persistent profile (auto-generated location)\nplaywright-cli open https://example.com --persistent\n\n# Use persistent profile with custom directory\nplaywright-cli open https://example.com --profile=/path/to/profile\n```\n\n## Attaching to a Running Browser\n\nUse `attach` to connect to a browser that is already running, instead of launching a new one.\n\n### Attach by channel name\n\nConnect to a running Chrome or Edge instance by its channel name. The browser must have remote debugging enabled — navigate to `chrome://inspect/#remote-debugging` in the target browser and check \"Allow remote debugging for this browser instance\".\n\n```bash\n# Attach to Chrome\nplaywright-cli attach --cdp=chrome\n\n# Attach to Chrome Canary\nplaywright-cli attach --cdp=chrome-canary\n\n# Attach to Microsoft Edge\nplaywright-cli attach --cdp=msedge\n\n# Attach to Edge Dev\nplaywright-cli attach --cdp=msedge-dev\n```\n\nSupported channels: `chrome`, `chrome-beta`, `chrome-dev`, `chrome-canary`, `msedge`, `msedge-beta`, `msedge-dev`, `msedge-canary`.\n\nWhen `--session` is not provided, the session is named after the channel (e.g. `--cdp=msedge` creates a session called `msedge`), so parallel attaches to Chrome and Edge don't collide on `default`. Pass `--session=<name>` to override.\n\n### Attach via CDP endpoint\n\nConnect to a browser that exposes a Chrome DevTools Protocol endpoint:\n\n```bash\nplaywright-cli attach --cdp=http://localhost:9222\n```\n\n### Attach via browser extension\n\nConnect to a browser with the Playwright extension installed:\n\n```bash\nplaywright-cli attach --extension\n```\n\n### Detach\n\nTear down an attached session without affecting the external browser:\n\n```bash\n# Detach the default attached session\nplaywright-cli detach\n\n# Detach a specific attached session\nplaywright-cli -s=msedge detach\n```\n\n`detach` only works on sessions created via `attach`. For sessions created via `open`, use `close`.\n\n## Default Browser Session\n\nWhen `-s` is omitted, commands use the default browser session:\n\n```bash\n# These use the same default browser session\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli close  # Stops default browser\n```\n\n## Browser Session Configuration\n\nConfigure a browser session with specific settings when opening:\n\n```bash\n# Open with config file\nplaywright-cli open https://example.com --config=.playwright/my-cli.json\n\n# Open with specific browser\nplaywright-cli open https://example.com --browser=firefox\n\n# Open in headed mode\nplaywright-cli open https://example.com --headed\n\n# Open with persistent profile\nplaywright-cli open https://example.com --persistent\n```\n\n## Best Practices\n\n### 1. Name Browser Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nplaywright-cli -s=github-auth open https://github.com\nplaywright-cli -s=docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nplaywright-cli -s=s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Stop browsers when done\nplaywright-cli -s=auth close\nplaywright-cli -s=scrape close\n\n# Or stop all at once\nplaywright-cli close-all\n\n# If browsers become unresponsive or zombie processes remain\nplaywright-cli kill-all\n```\n\n### 3. Delete Stale Browser Data\n\n```bash\n# Remove old browser data to free disk space\nplaywright-cli -s=oldsession delete-data\n```\n",".claude/skills/playwright-cli/references/storage-state.md":"# Storage Management\n\nManage cookies, localStorage, sessionStorage, and browser storage state.\n\n## Storage State\n\nSave and restore complete browser state including cookies and storage.\n\n### Save Storage State\n\n```bash\n# Save to auto-generated filename (storage-state-{timestamp}.json)\nplaywright-cli state-save\n\n# Save to specific filename\nplaywright-cli state-save my-auth-state.json\n```\n\n### Restore Storage State\n\n```bash\n# Load storage state from file\nplaywright-cli state-load my-auth-state.json\n\n# Reload page to apply cookies\nplaywright-cli open https://example.com\n```\n\n### Storage State File Format\n\nThe saved file contains:\n\n```json\n{\n  \"cookies\": [\n    {\n      \"name\": \"session_id\",\n      \"value\": \"abc123\",\n      \"domain\": \"example.com\",\n      \"path\": \"/\",\n      \"expires\": 1893456000,\n      \"httpOnly\": true,\n      \"secure\": true,\n      \"sameSite\": \"Lax\"\n    }\n  ],\n  \"origins\": [\n    {\n      \"origin\": \"https://example.com\",\n      \"localStorage\": [\n        { \"name\": \"theme\", \"value\": \"dark\" },\n        { \"name\": \"user_id\", \"value\": \"12345\" }\n      ]\n    }\n  ]\n}\n```\n\n## Cookies\n\n### List All Cookies\n\n```bash\nplaywright-cli cookie-list\n```\n\n### Filter Cookies by Domain\n\n```bash\nplaywright-cli cookie-list --domain=example.com\n```\n\n### Filter Cookies by Path\n\n```bash\nplaywright-cli cookie-list --path=/api\n```\n\n### Get Specific Cookie\n\n```bash\nplaywright-cli cookie-get session_id\n```\n\n### Set a Cookie\n\n```bash\n# Basic cookie\nplaywright-cli cookie-set session abc123\n\n# Cookie with options\nplaywright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax\n\n# Cookie with expiration (Unix timestamp)\nplaywright-cli cookie-set remember_me token123 --expires=1893456000\n```\n\n### Delete a Cookie\n\n```bash\nplaywright-cli cookie-delete session_id\n```\n\n### Clear All Cookies\n\n```bash\nplaywright-cli cookie-clear\n```\n\n### Advanced: Multiple Cookies or Custom Options\n\nFor complex scenarios like adding multiple cookies at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.context().addCookies([\n    { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true },\n    { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' }\n  ]);\n}\"\n```\n\n## Local Storage\n\n### List All localStorage Items\n\n```bash\nplaywright-cli localstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli localstorage-get token\n```\n\n### Set Value\n\n```bash\nplaywright-cli localstorage-set theme dark\n```\n\n### Set JSON Value\n\n```bash\nplaywright-cli localstorage-set user_settings '{\"theme\":\"dark\",\"language\":\"en\"}'\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli localstorage-delete token\n```\n\n### Clear All localStorage\n\n```bash\nplaywright-cli localstorage-clear\n```\n\n### Advanced: Multiple Operations\n\nFor complex scenarios like setting multiple values at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    localStorage.setItem('token', 'jwt_abc123');\n    localStorage.setItem('user_id', '12345');\n    localStorage.setItem('expires_at', Date.now() + 3600000);\n  });\n}\"\n```\n\n## Session Storage\n\n### List All sessionStorage Items\n\n```bash\nplaywright-cli sessionstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli sessionstorage-get form_data\n```\n\n### Set Value\n\n```bash\nplaywright-cli sessionstorage-set step 3\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli sessionstorage-delete step\n```\n\n### Clear sessionStorage\n\n```bash\nplaywright-cli sessionstorage-clear\n```\n\n## IndexedDB\n\n### List Databases\n\n```bash\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(async () => {\n    const databases = await indexedDB.databases();\n    return databases;\n  });\n}\"\n```\n\n### Delete Database\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    indexedDB.deleteDatabase('myDatabase');\n  });\n}\"\n```\n\n## Common Patterns\n\n### Authentication State Reuse\n\n```bash\n# Step 1: Login and save state\nplaywright-cli open https://app.example.com/login\nplaywright-cli snapshot\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\n\n# Save the authenticated state\nplaywright-cli state-save auth.json\n\n# Step 2: Later, restore state and skip login\nplaywright-cli state-load auth.json\nplaywright-cli open https://app.example.com/dashboard\n# Already logged in!\n```\n\n### Save and Restore Roundtrip\n\n```bash\n# Set up authentication state\nplaywright-cli open https://example.com\nplaywright-cli eval \"() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }\"\n\n# Save state to file\nplaywright-cli state-save my-session.json\n\n# ... later, in a new session ...\n\n# Restore state\nplaywright-cli state-load my-session.json\nplaywright-cli open https://example.com\n# Cookies and localStorage are restored!\n```\n\n## Security Notes\n\n- Never commit storage state files containing auth tokens\n- Add `*.auth-state.json` to `.gitignore`\n- Delete state files after automation completes\n- Use environment variables for sensitive data\n- By default, sessions run in-memory mode which is safer for sensitive operations\n",".claude/skills/playwright-cli/references/test-generation.md":"# Test generation (plan → generate → heal)\n\nEnd-to-end workflow for authoring and maintaining Playwright tests with `playwright-cli`. Every `playwright-cli` action emits the equivalent Playwright TypeScript, and that generated code is the raw material for every test. The sections below can be used independently:\n\n- **How generation works** — the core mechanic everything else relies on: actions become TypeScript, plus how to add assertions.\n- **Plan** — explore the app, produce a spec file describing what to test.\n- **Generate** — turn a spec into Playwright test files. Update the spec if it's vague or stale.\n- **Heal** — diagnose failing tests, fix the code, reconcile the spec with reality.\n\nPlan / generate / heal lean on the same mechanic: run `npx playwright test --debug=cli` in the background, then `playwright-cli attach tw-XXXX` to drive the paused page interactively. See [playwright-tests.md](playwright-tests.md) for the debug/attach mechanics.\n\n---\n\n## 0. How generation works\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. This code appears in the output and can be copied directly into your test files.\n\n```bash\n# Start a session\nplaywright-cli open https://example.com/login\n\n# Take a snapshot to see elements\nplaywright-cli snapshot\n# Output shows: e1 [textbox \"Email\"], e2 [textbox \"Password\"], e3 [button \"Sign In\"]\n\n# Fill form fields - generates code automatically\nplaywright-cli fill e1 \"user@example.com\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n\nplaywright-cli fill e2 \"password123\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n\nplaywright-cli click e3\n# Ran Playwright code:\n# await page.getByRole('button', { name: 'Sign In' }).click();\n```\n\n### Building a test file\n\nCollect the generated code into a Playwright test:\n\n```typescript\nimport { test, expect } from '@playwright/test';\n\ntest('login flow', async ({ page }) => {\n  // Generated code from playwright-cli session:\n  await page.goto('https://example.com/login');\n  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n  await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n  await page.getByRole('button', { name: 'Sign In' }).click();\n\n  // Add assertions\n  await expect(page).toHaveURL(/.*dashboard/);\n});\n```\n\n### Use semantic locators\n\nThe generated code uses role-based locators when possible, which are more resilient:\n\n```typescript\n// Generated (good - semantic)\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Avoid (fragile - CSS selectors)\nawait page.locator('#submit-btn').click();\n```\n\n### Explore before recording\n\nTake snapshots to understand the page structure before recording actions:\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli snapshot\n# Review the element structure\nplaywright-cli click e5\n```\n\n### Add assertions manually\n\nGenerated code captures actions but not assertions. Add expectations in your test using one of the recommended matchers:\n\n- `toBeVisible()` — element is rendered and visible\n- `toHaveText(text)` — element text content matches\n- `toHaveValue(value) / toBeEmpty()` — input/select value matches\n- `toBeChecked() / toBeUnchecked()` — checkbox state matches\n- `toMatchAriaSnapshot(snapshot)` — page (or locator) matches a partial accessibility snapshot\n\nUse `playwright-cli generate-locator <target>` to produce the locator expression for the assertion, and the snapshot/eval commands to capture the expected value.\n\nWhen asserting text content, make sure that generated locator does not contain text from the element itself. `getByTestId()` or `getByLabel()` usually work well with asserting text. When locator is text-based, prefer `toBeVisible()` instead.\n\nSnapshot to be matched does not have to contain all the information - only capture what's necessary for the assertion. You can use regular expressions for unstable values.\n\n```bash\n# Get a stable locator for an element ref to use in the assertion\nplaywright-cli --raw generate-locator e5\n# getByRole('button', { name: 'Submit' })\n\n# Capture expected text content for toHaveText\nplaywright-cli --raw eval \"el => el.textContent\" e5\n\n# Capture expected input value for toHaveValue/toBeEmpty\nplaywright-cli --raw eval \"el => el.value\" e5\n\n# Capture expected aria snapshot for toMatchAriaSnapshot/toBeChecked\n# (whole page, or use a ref to scope to a region)\nplaywright-cli --raw snapshot\nplaywright-cli --raw snapshot e5\n```\n\n```typescript\n// Generated action\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Manual assertions using the outputs above:\nawait expect(page.getByRole('alert', { name: 'Success' })).toBeVisible();\nawait expect(page.getByTestId('main-header')).toHaveText('Welcome, user');\nawait expect(page.getByRole('textbox', { name: 'Email' })).toHaveValue('user@example.com');\nawait expect(page.getByRole('checkbox', { name: 'Enable notifications' })).toBeChecked();\n\n// toMatchAriaSnapshot on the whole page, finds a matching region\nawait expect(page).toMatchAriaSnapshot(`\n  - heading \"Welcome, user\"\n  - link /\\\\d+ new messages?/\n  - button \"Sign out\"\n`);\n\n// toMatchAriaSnapshot scoped to a region\nawait expect(page.getByRole('navigation')).toMatchAriaSnapshot(`\n  - link \"Home\"\n  - link /\\\\d+ new messages?/\n  - link \"Profile\"\n`);\n```\n\n---\n\n## 1. Planning\n\nGoal: produce a spec file (e.g. `specs/<feature>.plan.md`) that enumerates the scenarios to test. **Always** write the spec to a file.\n\n### 1.1 Prerequisite: workspace\n\nCheck the workspace has Playwright installed before anything else:\n\n```bash\n# Either of these confirms a workspace:\ntest -f playwright.config.ts || test -f playwright.config.js\nnpx --no-install playwright --version\n```\n\nIf there is no Playwright install, bootstrap one and let the user pick the defaults:\n\n```bash\nnpm init playwright@latest\n```\n\n### 1.2 Prerequisite: seed test\n\nA **seed test** is a minimal test that lands the page in the state every scenario starts from: navigation to the app, any required login, feature flags, etc. Scenarios assume a fresh start *after* the seed. `--debug=cli` pauses *inside* this test, so the seed is where every planning and generation session begins.\n\nMinimum viable seed:\n\n```ts\n// tests/seed.spec.ts\nimport { test } from '@playwright/test';\n\ntest('seed', async ({ page }) => {\n  await page.goto('https://example.com/');\n});\n```\n\nPreferred — push navigation into a fixture so scenario tests reuse it:\n\n```ts\n// tests/fixtures.ts\nimport { test as baseTest } from '@playwright/test';\nexport { expect } from '@playwright/test';\n\nexport const test = baseTest.extend({\n  page: async ({ page }, use) => {\n    await page.goto('https://example.com/');\n    await use(page);\n  },\n});\n```\n\n```ts\n// tests/seed.spec.ts\nimport { test } from './fixtures';\n\ntest('seed', async ({ page }) => {\n  // Fixture already navigates. This empty body tells agents where to start.\n});\n```\n\nIf no seed exists, create one that at least navigates to the app.\n\n### 1.3 Explore the app\n\nLaunch the app via the seed in the background and attach:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/seed.spec.ts --debug=cli\n# wait for \"Debugging Instructions\" and the session name tw-XXXX\nplaywright-cli attach tw-XXXX\n```\n\nResume so the seed runs, then probe the app:\n\n```bash\nplaywright-cli resume                   # resume so that seed test runs fully\nplaywright-cli snapshot                 # inventory of interactive elements\nplaywright-cli click e5                 # follow a flow\nplaywright-cli eval \"location.href\"     # read URL / state\nplaywright-cli show --annotate          # ask the user to point at something\n```\n\nMap out:\n\n- Interactive surfaces (forms, buttons, lists, filters, modals).\n- Primary user journeys end-to-end.\n- Edge cases: empty states, validation errors, very long input, boundary values.\n- Persistence: reload, local/session storage, URL fragments.\n- Navigation: which controls change the URL, back/forward behaviour.\n\n**Important**: Do not just open the app url with playwright-cli, always go through the test to capture any custom setup done there.\n**Important**: Stop the background test when done exploring.\n\n### 1.4 Write the spec file\n\nSave under `specs/<feature>.plan.md`. Use this structure:\n\n```markdown\n# <Feature> Test Plan\n\n## Application Overview\n\n<One paragraph describing what the feature does and why it matters.>\n\n## Test Scenarios\n\n### 1. <Group Name>\n\n**Seed:** `tests/seed.spec.ts`\n\n#### 1.1. <kebab-case-scenario-name>\n\n**File:** `tests/<group>/<kebab-case-scenario-name>.spec.ts`\n\n**Steps:**\n  1. <Concrete user step>\n    - expect: <observable outcome>\n    - expect: <another observable outcome>\n  2. <Next step>\n    - expect: <outcome>\n\n#### 1.2. <next-scenario>\n...\n\n### 2. <Next Group>\n\n**Seed:** `tests/seed.spec.ts`\n...\n```\n\nGuidelines:\n\n- Each scenario is independent and starts from the seed's fresh state — never chain scenarios.\n- Scenario names are kebab-case and match the test file name (`should-add-single-todo` → `should-add-single-todo.spec.ts`).\n- Cover happy path, edge cases, validation, negative flows, persistence.\n- Write steps at the user level (\"Type 'Buy milk' into the input\"), not the API level (\"call `fill`\").\n- Put observable outcomes in `- expect:` bullets; each becomes an assertion during generation.\n\n---\n\n## 2. Generate\n\nGoal: take a spec file and produce Playwright test files. Optionally update the spec if it has drifted.\n\n### 2.1 Inputs\n\n- **Spec file**, e.g. `specs/basic-operations.plan.md`.\n- **Target**: either a single scenario (e.g. `1.2`), a whole group (`1`), or all.\n- **Seed file**, read from the `**Seed:**` line of the scenario's group.\n\n### 2.2 Generate one scenario\n\nFor each target scenario, in sequence (never in parallel — scenarios share the seed session):\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test <seed-file> --debug=cli   # background\nplaywright-cli attach tw-XXXX\n# resume\n```\n\n**Do not** just open the app url with playwright-cli, always go through the test to capture any custom setup done there.\n\nWalk the scenario's `Steps:` one by one with `playwright-cli`, treating the spec as the plan and the live app as the source of truth. If a step is vague (\"click the button\" — which button?), references an element that no longer exists, or contradicts the app's actual behaviour, use your judgement: update the spec to match what the app really does, then keep going. Editing the spec mid-generation is expected.\n\nEvery action prints the equivalent Playwright TypeScript (see [How generation works](#0-how-generation-works)):\n\n```bash\nplaywright-cli snapshot                         # find refs\nplaywright-cli fill e3 \"John Doe\"               # -> page.getByRole('textbox', {...}).fill(...)\nplaywright-cli press Enter\nplaywright-cli click e7\n```\n\nFor each `- expect:` bullet, add an explicit assertion. See [How generation works](#0-how-generation-works) for details.\n\nCollect the generated code and write the test file at the path given in the spec:\n\n```ts\n// spec: specs/basic-operations.plan.md\n// seed: tests/seed.spec.ts\nimport { test, expect } from './fixtures';   // or '@playwright/test' if no fixtures file\n\ntest.describe('Signing in and out', () => {\n  test('should sign in', async ({ page }) => {\n    // 1. Navigate to the application\n    // (handled by the seed fixture)\n\n    // 2. Type 'John Doe' into the username field\n    await page.getByRole('textbox', { name: 'username' }).fill('John Doe');\n\n    // 3. Type password\n    await page.getByRole('textbox', { name: 'password' }).fill('TestPassword');\n\n    // 4. Press Enter to submit\n    await page.getByRole('textbox', { name: 'password' }).press('Enter');\n\n    await expect(page.getByRole('heading')).toContainText('Welcome, John Doe!');\n  });\n});\n```\n\nRules:\n\n- **One test per file.** File path, describe name, and test name come verbatim from the spec (minus the ordinal).\n- Prefix each numbered step with a `// N. <step text>` comment before its actions.\n- Use the describe group name verbatim from the spec (no `1.` ordinal).\n- Import from `./fixtures` if the project has one; otherwise `@playwright/test`.\n- **Important**: close the CLI session and stop the background test before moving to the next scenario.\n\n### 2.3 Generate multiple scenarios\n\nLoop 2.2 over the targeted scenarios one at a time, restarting the seed between each so every test starts from a clean page. This is safe to parallelise due to unique generated session names - just make sure each test run is stopped.\n\n### 2.4 Run generated tests\n\nAfter generation, run the new tests once:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/<group>/<scenario>.spec.ts\n```\n\nAny failure goes to Section 3.\n\n---\n\n## 3. Heal\n\nGoal: fix failing tests, and update the spec if the app's intended behaviour changed.\n\n### 3.1 Find failing tests\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test\n```\n\nRecord the list of failing `<file>:<line>` entries and process them one at a time. Do not attempt parallel fixes — shared state and the single CLI session make that fragile.\n\n### 3.2 Debug one failure\n\nRun the single failing test in debug mode in the background, then attach:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/<group>/<scenario>.spec.ts:<line> --debug=cli\n# wait for \"Debugging Instructions\" and the tw-XXXX session name\nplaywright-cli attach tw-XXXX\n```\n\nThe test is paused at the start. Step forward or run to until just before the failing action or assertion, then diagnose:\n\n```bash\nplaywright-cli snapshot                # did the element change / move / rename?\nplaywright-cli console                 # app-side errors?\nplaywright-cli requests                # failed request? wrong payload?\nplaywright-cli show --annotate         # ask the user to point somewhere\n```\n\nCommon causes: selector drift, new wrapper element, label/ARIA rename, timing (transition, async load), assertion text updated in the app, test data leaking between runs.\n\nRehearse the corrected interaction with `playwright-cli` — the generated code in the output is what you paste back into the test.\n\n### 3.3 Apply the fix\n\nEdit the test file: update the locator, assertion, step order, or inputs to match the corrected behaviour. Stop the background debug run. Rerun the single test to confirm green.\n\nNever skip hooks or add sleeps as a fix. Never use `networkidle`.\n\n### 3.4 Reconcile with the spec\n\nOpen the spec referenced by the `// spec:` header in the test file and locate the scenario that matches the test.\n\n- **Fix was purely technical** (locator drift, better assertion shape) and the spec's user-level behaviour still matches the app → leave the spec alone.\n- **Fix changed user-visible steps, inputs, order, or expected outcomes** that the spec describes → update the spec to match reality. Keep the scenario id and file path stable; only the step / expect lines change.\n- **Unclear whether the app change is intentional** (spec is stale) **or a regression** (test was right, app is wrong) → **stop and ask the user**. Provide:\n  - the scenario id (e.g. `2.3`),\n  - the spec lines that no longer match,\n  - the observed app behaviour (quote a snapshot excerpt or a concrete outcome).\n\nOnly after the user answers, either update the spec (intentional change) or file/flag the test as covering a bug (regression).\n\n### 3.5 Iteration and giving up\n\n- Fix failures one at a time; rerun after each.\n- If after thorough investigation you are confident the test is correct but the app is wrong *and* the user has confirmed it's a bug: mark the test `test.fixme(...)` with a comment pointing at the user's decision or issue link. Never silently skip.\n\n---\n\n## Cross-references\n\n| For... | See |\n|---|---|\n| `--debug=cli` / attach mechanics | [playwright-tests.md](playwright-tests.md) |\n| Mocking requests during exploration/generation | [request-mocking.md](request-mocking.md) |\n| Managing the CLI browser session | [session-management.md](session-management.md) |\n",".claude/skills/playwright-cli/references/tracing.md":"# Tracing\n\nCapture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.\n\n## Basic Usage\n\n```bash\n# Start trace recording\nplaywright-cli tracing-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli click e1\nplaywright-cli fill e2 \"test\"\n\n# Stop trace recording\nplaywright-cli tracing-stop\n```\n\n## Trace Output Files\n\nWhen you start tracing, Playwright creates a `traces/` directory with several files:\n\n### `trace-{timestamp}.trace`\n\n**Action log** - The main trace file containing:\n- Every action performed (clicks, fills, navigations)\n- DOM snapshots before and after each action\n- Screenshots at each step\n- Timing information\n- Console messages\n- Source locations\n\n### `trace-{timestamp}.network`\n\n**Network log** - Complete network activity:\n- All HTTP requests and responses\n- Request headers and bodies\n- Response headers and bodies\n- Timing (DNS, connect, TLS, TTFB, download)\n- Resource sizes\n- Failed requests and errors\n\n### `resources/`\n\n**Resources directory** - Cached resources:\n- Images, fonts, stylesheets, scripts\n- Response bodies for replay\n- Assets needed to reconstruct page state\n\n## What Traces Capture\n\n| Category | Details |\n|----------|---------|\n| **Actions** | Clicks, fills, hovers, keyboard input, navigations |\n| **DOM** | Full DOM snapshot before/after each action |\n| **Screenshots** | Visual state at each step |\n| **Network** | All requests, responses, headers, bodies, timing |\n| **Console** | All console.log, warn, error messages |\n| **Timing** | Precise timing for each operation |\n\n## Use Cases\n\n### Debugging Failed Actions\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://app.example.com\n\n# This click fails - why?\nplaywright-cli click e5\n\nplaywright-cli tracing-stop\n# Open trace to see DOM state when click was attempted\n```\n\n### Analyzing Performance\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://slow-site.com\nplaywright-cli tracing-stop\n\n# View network waterfall to identify slow resources\n```\n\n### Capturing Evidence\n\n```bash\n# Record a complete user flow for documentation\nplaywright-cli tracing-start\n\nplaywright-cli open https://app.example.com/checkout\nplaywright-cli fill e1 \"4111111111111111\"\nplaywright-cli fill e2 \"12/25\"\nplaywright-cli fill e3 \"123\"\nplaywright-cli click e4\n\nplaywright-cli tracing-stop\n# Trace shows exact sequence of events\n```\n\n## Trace vs Video vs Screenshot\n\n| Feature | Trace | Video | Screenshot |\n|---------|-------|-------|------------|\n| **Format** | .trace file | .webm video | .png/.jpeg image |\n| **DOM inspection** | Yes | No | No |\n| **Network details** | Yes | No | No |\n| **Step-by-step replay** | Yes | Continuous | Single frame |\n| **File size** | Medium | Large | Small |\n| **Best for** | Debugging | Demos | Quick capture |\n\n## Best Practices\n\n### 1. Start Tracing Before the Problem\n\n```bash\n# Trace the entire flow, not just the failing step\nplaywright-cli tracing-start\nplaywright-cli open https://example.com\n# ... all steps leading to the issue ...\nplaywright-cli tracing-stop\n```\n\n### 2. Clean Up Old Traces\n\nTraces can consume significant disk space:\n\n```bash\n# Remove traces older than 7 days\nfind .playwright-cli/traces -mtime +7 -delete\n```\n\n## Limitations\n\n- Traces add overhead to automation\n- Large traces can consume significant disk space\n- Some dynamic content may not replay perfectly\n",".claude/skills/playwright-cli/references/video-recording.md":"# Video Recording\n\nCapture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).\n\n## Basic Recording\n\n```bash\n# Open browser first\nplaywright-cli open\n\n# Start recording\nplaywright-cli video-start demo.webm\n\n# Add a chapter marker for section transitions\nplaywright-cli video-chapter \"Getting Started\" --description=\"Opening the homepage\" --duration=2000\n\n# Navigate and perform actions\nplaywright-cli goto https://example.com\nplaywright-cli snapshot\nplaywright-cli click e1\n\n# Add another chapter\nplaywright-cli video-chapter \"Filling Form\" --description=\"Entering test data\" --duration=2000\nplaywright-cli fill e2 \"test input\"\n\n# Stop and save\nplaywright-cli video-stop\n```\n\n## Best Practices\n\n### 1. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nplaywright-cli video-start recordings/login-flow-2024-01-15.webm\nplaywright-cli video-start recordings/checkout-test-run-42.webm\n```\n\n### 2. Record entire hero scripts.\n\nWhen recording a video for the user or as a proof of work, it is best to create a code snippet and execute it with run-code.\nIt allows inserting appropriate pauses between the actions and annotating the video. There are new Playwright APIs for that.\n\n1) Perform scenario using CLI and take note of all locators and actions. You'll need those locators to request their bounding boxes for highlight.\n2) Create a file with the intended script for video (below). Use pressSequentially w/ delay for nice typing, make reasonable pauses.\n3) Use playwright-cli run-code --filename your-script.js\n\n**Important**: Overlays are `pointer-events: none` — they do not interfere with page interactions. You can safely keep sticky overlays visible while clicking, filling, or performing any actions on the page.\n\n```js\nasync page => {\n  await page.screencast.start({ path: 'video.webm', size: { width: 1280, height: 800 } });\n  await page.goto('https://demo.playwright.dev/todomvc');\n\n  // Show a chapter card — blurs the page and shows a dialog.\n  // Blocks until duration expires, then auto-removes.\n  // Use this for simple use cases, but always feel free to hand-craft your own beautiful\n  // overlay via await page.screencast.showOverlay().\n  await page.screencast.showChapter('Adding Todo Items', {\n    description: 'We will add several items to the todo list.',\n    duration: 2000,\n  });\n\n  // Perform action\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Walk the dog', { delay: 60 });\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');\n  await page.waitForTimeout(1000);\n\n  // Show next chapter\n  await page.screencast.showChapter('Verifying Results', {\n    description: 'Checking the item appeared in the list.',\n    duration: 2000,\n  });\n\n  // Add a sticky annotation that stays while you perform actions.\n  // Overlays are pointer-events: none, so they won't block clicks.\n  const annotation = await page.screencast.showOverlay(`\n    <div style=\"position: absolute; top: 8px; right: 8px;\n      padding: 6px 12px; background: rgba(0,0,0,0.7);\n      border-radius: 8px; font-size: 13px; color: white;\">\n      ✓ Item added successfully\n    </div>\n  `);\n\n  // Perform more actions while the annotation is visible\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Buy groceries', { delay: 60 });\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');\n  await page.waitForTimeout(1500);\n\n  // Remove the annotation when done\n  await annotation.dispose();\n\n  // You can also highlight relevant locators and provide contextual annotations.\n  const bounds = await page.getByText('Walk the dog').boundingBox();\n  await page.screencast.showOverlay(`\n    <div style=\"position: absolute;\n      top: ${bounds.y}px;\n      left: ${bounds.x}px;\n      width: ${bounds.width}px;\n      height: ${bounds.height}px;\n      border: 1px solid red;\">\n    </div>\n    <div style=\"position: absolute;\n      top: ${bounds.y + bounds.height + 5}px;\n      left: ${bounds.x + bounds.width / 2}px;\n      transform: translateX(-50%);\n      padding: 6px;\n      background: #808080;\n      border-radius: 10px;\n      font-size: 14px;\n      color: white;\">Check it out, it is right above this text\n    </div>\n  `, { duration: 2000 });\n\n  await page.screencast.stop();\n}\n```\n\nEmbrace creativity, overlays are powerful.\n\n### Overlay API Summary\n\n| Method | Use Case |\n|--------|----------|\n| `page.screencast.showChapter(title, { description?, duration?, styleSheet? })` | Full-screen chapter card with blurred backdrop — ideal for section transitions |\n| `page.screencast.showOverlay(html, { duration? })` | Custom HTML overlay — use for callouts, labels, highlights |\n| `disposable.dispose()` | Remove a sticky overlay added without duration |\n| `page.screencast.hideOverlays()` / `page.screencast.showOverlays()` | Temporarily hide/show all overlays |\n\n## Tracing vs Video\n\n| Feature | Video | Tracing |\n|---------|-------|---------|\n| Output | WebM file | Trace file (viewable in Trace Viewer) |\n| Shows | Visual recording | DOM snapshots, network, console, actions |\n| Use case | Demos, documentation | Debugging, analysis |\n| Size | Larger | Smaller |\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n",".claude/skills/release/SKILL.md":"---\nname: release\ndescription: |\n  Create a release from dev branch. Generates changelog entries from commits,\n  bumps version, and creates a PR to main.\n\n  TRIGGERS - Use this skill when user says:\n  - \"/release\" - create a patch release (default)\n  - \"/release minor\" - create a minor release\n  - \"/release major\" - create a major release\n  - \"make a release\", \"cut a release\", \"ship it\", \"release to main\"\n---\n\n# Release Skill\n\nCreates a release by comparing dev to main, generating changelog entries from commits, bumping the version, and creating a PR. After the tag is pushed and the release workflow finishes building binaries, updates the Homebrew formula with the real SHA256 values from the published `checksums.txt`, syncs the `coleam00/homebrew-archon` tap, and verifies the end-to-end install path via `/test-release`.\n\n> **⚠️ CRITICAL — Homebrew formula SHAs cannot be known until after the release workflow builds binaries.**\n>\n> The `version` field in `homebrew/archon.rb` and the `sha256` fields must be updated **atomically**. Never update one without the other.\n>\n> The correct sequence is:\n> 1. Tag is pushed → release workflow fires → binaries built → `checksums.txt` uploaded\n> 2. Fetch `checksums.txt` from the published release\n> 3. Parse the SHA256 per platform\n> 4. Update `homebrew/archon.rb` with the new version AND the new SHAs in a single commit\n> 5. Sync to the `coleam00/homebrew-archon/Formula/archon.rb` tap repo\n>\n> Updating the formula's `version` field without also updating the `sha256` values creates a stale, misleading formula that looks valid but produces checksum mismatches on install. This has happened before (v0.3.0: version updated to 0.3.0 but SHAs were still from v0.2.13). Always do both or neither.\n\n## Process\n\n### Step 1: Validate State\n\n```bash\n# Must be on dev branch with clean working tree\ngit checkout dev\ngit pull origin dev --no-rebase\ngit status --porcelain  # must be empty\ngit fetch origin main\n```\n\nIf not on dev or working tree is dirty, abort with a clear message.\n\n**Then check that dev actually contains main.** Anything committed directly to\nmain — a docs typo fix, a hotfix, the previous release's formula commit — stays\nstranded there until someone merges it back. The release PR would then propose\n*reverting* it, and the next release inherits the drift.\n\n```bash\nif git merge-base --is-ancestor origin/main origin/dev; then\n  echo \"dev contains main — OK\"\nelse\n  echo \"DRIFT: commits exist on main that dev does not have:\"\n  git log origin/dev..origin/main --oneline\n  echo \"\"\n  echo \"Resync before releasing:\"\n  echo \"  git checkout dev && git pull origin main --no-rebase && git push origin dev\"\n  exit 1\nfi\n```\n\nObserved on the 0.7.1 release: `ae704a73 Update docs.mdx (#2155)` had been\ncommitted straight to main after 0.7.0 and never merged back. Someone had\nnoticed the *content* gap and hand-forward-ported it via a separate PR (#2403),\nso dev had the change but not the commit — and `main` was still not an ancestor\nof `dev`. Resolve this before Step 2; do not carry it into the release PR.\n\n### Step 1.5: Pre-flight compiled-binary smoke test (MANDATORY before any other step)\n\n> **Why this is first**: releases have ended up with zero working binaries because a module-init crash or bundler bug only surfaces in `bun build --compile` output, not in `bun run`. CI catches it — but only AFTER the tag is pushed and a GitHub Release is created. By then the damage (empty release, broken `releases/latest`, broken `install.sh`) is already live. Failing here, before any user-visible change, keeps the blast radius at \"no release was cut.\"\n\nRun locally on the native target. This takes ~15-30s and is cheaper than discovering the problem after tag+release.\n\n```bash\n# Guard: only run this for Node/Bun projects with a CLI entry point + build-binaries script.\nif [ -f scripts/build-binaries.sh ] && [ -f packages/cli/src/cli.ts ]; then\n  TMP_BINARY=$(mktemp)\n  trap \"rm -f $TMP_BINARY\" EXIT\n\n  # Compile for the native target only (not full cross-compile — that's CI's job).\n  # Match the real release flags so any bundler quirk reproduces locally.\n  bun build \\\n    --compile \\\n    --minify \\\n    --target=bun \\\n    --outfile=\"$TMP_BINARY\" \\\n    packages/cli/src/cli.ts\n\n  # Smoke test: the binary must start and exit 0 on a safe, non-interactive command.\n  # Use `--help` (NOT `version`). The `version` command's compiled-binary code\n  # path depends on BUNDLED_IS_BINARY=true, which is set by scripts/build-binaries.sh\n  # — but we're doing a bare `bun build --compile` here to keep the smoke fast,\n  # so BUNDLED_IS_BINARY is still `false`. That sends `version` down the dev\n  # branch of version.ts which tries to read package.json from a path that only\n  # exists in node_modules, producing a false-positive ENOENT. `--help` has no\n  # such dev/binary branch and exercises the same module-init graph we're\n  # actually testing. Must NOT touch network, database, or require env vars.\n  if ! \"$TMP_BINARY\" --help > /tmp/archon-preflight.log 2>&1; then\n    echo \"ERROR: compiled binary crashed at startup\"\n    cat /tmp/archon-preflight.log\n    echo \"\"\n    echo \"This usually means a dependency has a module-init-time side effect that\"\n    echo \"fails in a compiled binary context (readFileSync of a path that only\"\n    echo \"exists in node_modules, etc.). Fix before cutting the release — do NOT\"\n    echo \"proceed to version bump.\"\n    exit 1\n  fi\n\n  # Also grep for known crash markers that exit 0 but print a fatal error\n  # (some module-init errors are caught by top-level try/catch but still log).\n  if grep -qE \"Expected CommonJS module|TypeError:|ReferenceError:|SyntaxError:\" /tmp/archon-preflight.log; then\n    echo \"ERROR: compiled binary emitted a runtime error despite exit 0\"\n    cat /tmp/archon-preflight.log\n    exit 1\n  fi\n\n  echo \"Pre-flight binary smoke: PASSED\"\nfi\n```\n\nIf this fails, **abort the release entirely** — do not bump version, do not modify CHANGELOG, do not create a PR. Surface the error to the user, point at the failing output, and stop. Recovery is: fix the bundler / dependency issue on a feature branch, merge to dev, re-run `/release`.\n\n**Common failure modes this catches:**\n- Bun `--bytecode` flag producing broken bytecode for the current module graph\n- A dependency (e.g. an SDK) reading `package.json` or other files at module top level via paths that resolve fine in `node_modules/` but not next to a compiled binary\n- Circular imports that break under minification but work under plain `bun run`\n- A newly added package that ships CJS with an unusual wrapper shape\n\n### Step 2: Detect Stack and Current Version\n\nDetect the project's package manager and version file:\n\n1. **Check for `pyproject.toml`** — Python project, version in `version = \"x.y.z\"`\n2. **Check for `package.json`** — Node/Bun project, version in `\"version\": \"x.y.z\"`\n3. **Check for `Cargo.toml`** — Rust project, version in `version = \"x.y.z\"`\n4. **Check for `go.mod`** — Go project (version from git tags only, no file to bump)\n\nIf none found, abort: \"Could not detect project stack — no version file found.\"\n\nRead the current version from the detected file.\n\n### Step 3: Determine Version Bump\n\n**Bump rules based on argument:**\n- No argument or `patch` (default): `0.1.0 -> 0.1.1`\n- `minor`: `0.1.3 -> 0.2.0`\n- `major`: `0.3.5 -> 1.0.0`\n\n### Step 4: Collect Commits\n\n> **Do NOT use `git log main..dev`.** This repo **squash**-merges the release PR\n> into main, so main receives one new commit per release and never contains dev's\n> individual commits. `main..dev` therefore accumulates *every commit ever\n> squashed* and grows release over release. On 0.7.1 it returned **61** commits\n> when only **25** were new — the other 36 were already shipped in 0.7.0 and\n> already written into its changelog. Followed literally, it re-logs most of the\n> previous release.\n\nThe real boundary is dev's own last `Release x.y.z` commit:\n\n```bash\n# The previous release commit ON DEV (not the squashed one on main).\n#\n# The `$` anchor is load-bearing. Both commits exist on dev after Step 9's sync:\n#   6c6945ce  Release 0.7.1           <- dev's own version-bump commit  (WANT)\n#   c71f7f52  Release 0.7.1 (#2435)   <- main's squash, pulled back     (WRONG)\n# Picking the squash commit is catastrophic: its ancestry does not include dev's\n# individual history, so the range balloons to the whole repo (383 commits when\n# tested). Match the bare subject only.\nLAST_RELEASE=$(git log origin/dev \\\n  --grep='^Release [0-9]+\\.[0-9]+\\.[0-9]+$' --extended-regexp \\\n  --format='%H' -n 1)\n\n# First-ever release: fall back to the root commit. Without this the range\n# becomes `..origin/dev`, which git resolves against HEAD and which returns\n# ZERO commits — the skill would report \"nothing to release\" and stop.\nif [ -z \"$LAST_RELEASE\" ]; then\n  LAST_RELEASE=$(git rev-list --max-parents=0 origin/dev | tail -n 1)\nfi\necho \"Previous release commit: $(git log -1 --oneline \"$LAST_RELEASE\")\"\n\n# Everything after it is genuinely new, minus the release-plumbing commits\n# described below.\ngit log \"$LAST_RELEASE\"..origin/dev --oneline --no-merges \\\n  --grep='^Release [0-9]+\\.[0-9]+\\.[0-9]+' \\\n  --grep='^chore: update Homebrew formula for v' \\\n  --grep='^chore\\(homebrew\\):' \\\n  --extended-regexp --invert-grep\n```\n\n> The backslashes in `^chore\\(homebrew\\):` are required. Under `--extended-regexp`\n> bare `(` and `)` are grouping operators, so the unescaped form matches the\n> literal text `chorehomebrew:` and silently fails to filter anything.\n\nTwo commit kinds in that range are **release plumbing, not changelog material**,\nand the `--invert-grep` above drops them:\n\n- `Release x.y.z (#NNNN)` — main's squash commit, pulled back by Step 9's sync\n- `chore: update Homebrew formula for vx.y.z` / `chore(homebrew): …` — the CI job\n  and the Step 10 commit; note these appear as *two different SHAs* with the same\n  content, one per branch\n\n> The `Release` pattern is anchored to a full `x.y.z` version deliberately. A bare\n> `^Release ` would also swallow a legitimate feature commit whose subject starts\n> with that word. If a real commit is ever dropped, widen the range by hand rather\n> than loosening the pattern.\n\n**Sanity-check the boundary before drafting.** Cross-reference against what the\nprevious version already documented — any overlap means the range is wrong.\nDerive both headings from the current version so this does not rot:\n\n```bash\n# CURRENT_VERSION is the value read in Step 2, before the bump (e.g. 0.7.1)\nPREV_MAJOR_MINOR_PATCH=\"$CURRENT_VERSION\"\n# The heading immediately above the one we are about to write:\nawk -v prev=\"## [$PREV_MAJOR_MINOR_PATCH]\" '\n  index($0, prev) == 1 { on = 1; next }\n  on && /^## \\[/       { exit }\n  on                   { print }\n' CHANGELOG.md | grep -oE '#[0-9]{3,5}' | sort -u\n```\n\nIf a PR number in your commit range appears in that list, stop and re-derive the\nboundary — do not write it twice.\n\n**Also read the `[Unreleased]` section.** Contributors may write entries there by\nhand. They are input to this release, not a boundary error:\n\n```bash\nawk '\n  /^## \\[Unreleased\\]/ { on = 1; next }\n  on && /^## \\[/      { exit }\n  on                  { print }\n' CHANGELOG.md\n```\n\nIf no new commits, abort: \"Nothing to release — dev is up to date with main.\"\n\n### Step 5: Draft Changelog Entries\n\nRead the commit messages and the actual diffs (`git diff \"$LAST_RELEASE\"..origin/dev`) to understand what changed.\n\nPrefer the PR title and its `## Summary` section over the raw commit subject —\nthey state the user-visible problem, which is what a changelog entry needs:\n\n```bash\ngh pr view <N> --repo coleam00/Archon --json number,title,body,closingIssuesReferences\n```\n\nDraft entries only for changes the `[Unreleased]` entries from Step 4 do not\nalready describe, so each change appears once. Hand-written entries often cite\nthe issue a PR closes (`closingIssuesReferences`) rather than the PR, or cite\nnothing; judge by what the entry says, not by its number. Keep the substance of\nhand-written entries (migration tables, exact details) and tidy only their wording\nand placement. If you can't tell whether an entry covers a PR, ask in Step 7.\n\n**Categorize into Keep a Changelog sections, in this order (omit empty ones):**\n1. **Breaking** — changes that break existing behavior users may rely on\n2. **Security** — vulnerability fixes and security-relevant hardening\n3. **Added** — new features, new files, new capabilities\n4. **Changed** — modifications to existing behavior\n5. **Deprecated** — features still available but discouraged or planned for removal\n6. **Removed** — deleted features or code\n7. **Fixed** — bug fixes\n\n**Writing rules:**\n- Write entries as a human would — clear, concise, user-facing language\n- Do NOT just copy commit messages verbatim — rewrite them into proper changelog entries\n- Group related commits into single entries where it makes sense\n- Include PR numbers in parentheses when available: `(#12)`\n- Each entry should start with a noun or gerund describing WHAT changed\n- Skip internal-only changes (CI tweaks, typo fixes) unless they affect behavior\n- One blank line between sections\n\n### Step 6: Update Files\n\n1. **Version file** — update version to new value:\n   - `package.json`: update `\"version\": \"x.y.z\"`\n   - `pyproject.toml`: update `version = \"x.y.z\"`\n   - `Cargo.toml`: update `version = \"x.y.z\"`\n\n2. **Workspace version sync** (monorepo only):\n   - If `scripts/sync-versions.sh` exists, run `bash scripts/sync-versions.sh` to sync all `packages/*/package.json` versions to match the root version. The script also refreshes `bun.lock` itself, so the workspace versions land in the lockfile too.\n\n3. **Lockfile refresh** (stack-dependent) — skip the matching line if step 2's script already refreshed the lockfile:\n   - `package.json` + `bun.lock`: run `bun install`\n   - `package.json` + `package-lock.json`: run `npm install --package-lock-only`\n   - `pyproject.toml` + `uv.lock`: run `uv lock --quiet`\n   - `Cargo.toml`: run `cargo update --workspace`\n\n4. **`CHANGELOG.md`** — write one new version section below `[Unreleased]` from\n   the `[Unreleased]` entries and the Step 5 drafts, one `### <Category>` heading\n   per non-empty category in the Step 5 order:\n\n```markdown\n## [x.y.z] - YYYY-MM-DD\n\nOne-line summary of the release.\n\n### Breaking\n\n- Entry one (#PR)\n\n### Added\n\n- Entry one (#PR)\n- Entry two (#PR)\n\n### Fixed\n\n- Entry one (#PR)\n```\n\n**`### Breaking` must come first**, immediately after the one-line summary. Step 9's\nrelease workflow reads this section verbatim into the GitHub release body, so this\nfile's own section order is what makes breaking changes appear first on the release page.\n\nLeave the `[Unreleased]` header with nothing under it.\n\n### Step 7: Present for Review\n\nShow the user:\n1. The detected stack and version file\n2. The version bump (old -> new)\n3. The full changelog section that will be added\n4. The list of commits being included\n5. Any `[Unreleased]` entry you were unsure covers a PR, next to that PR\n\nAsk: \"Does this look good? I'll commit and create the PR.\"\n\n### Step 8: Commit and PR\n\nOnly after user approval:\n\n```bash\n# Stage version file, workspace packages, lockfile, and changelog\ngit add <version-file> packages/*/package.json <lockfile> CHANGELOG.md\ngit commit -m \"Release x.y.z\"\n\n# Push dev\ngit push origin dev\n\n# Create PR: dev -> main\ngh pr create --base main --head dev \\\n  --title \"Release x.y.z\" \\\n  --body \"$(cat <<'EOF'\n## Release x.y.z\n\n{changelog section content}\n\n---\n\nMerging this PR releases x.y.z to main.\nEOF\n)\"\n```\n\nReturn the PR URL to the user.\n\n### Step 9: Tag, Release, and Sync After Merge\n\nAfter the PR is merged (either by the user or via `gh pr merge`):\n\n```bash\n# Fetch the merge commit on main\ngit fetch origin main\n\n# Tag the merge commit\ngit tag vx.y.z origin/main\ngit push origin vx.y.z\n\n# Create a GitHub Release from the tag. --generate-notes is a placeholder: the\n# push above already triggered .github/workflows/release.yml, and once its\n# `release` job finishes building binaries (5-10 min), its \"Build release\n# notes\" step overwrites this release's body with CHANGELOG.md's own\n# `## [x.y.z]` section (Breaking first, per Step 6) plus install instructions.\n# That job is the single source of truth for the body, so it does not matter\n# that this step's placeholder is throwaway, or which of the two finishes\n# first — do NOT hand-craft curated notes here, they would just be discarded.\ngh release create vx.y.z --title \"vx.y.z\" --generate-notes\n\n# Sync dev with main so both branches are identical\ngit checkout dev\ngit pull origin main --no-rebase\ngit push origin dev\n\n# Verify the sync actually converged — do not assume it did. FAIL CLOSED:\n# continuing past a failed sync publishes a formula and tap from a tree that\n# does not match what was released.\ngit fetch origin\nif git merge-base --is-ancestor origin/main origin/dev; then\n  echo \"dev contains main — OK\"\nelse\n  echo \"STILL DIVERGED — stranded on main:\"\n  git log origin/dev..origin/main --oneline\n  exit 1\nfi\n```\n\n> **`--no-rebase` is required, not optional.** Without it, git aborts with\n> `fatal: Need to specify how to reconcile divergent branches` on any machine\n> that has not set `pull.rebase`. It also pins the behaviour to the merge this\n> step actually wants — a `pull.rebase=true` config would otherwise rewrite dev's\n> history, which is exactly what the warning below forbids.\n\n> **Expect to run this twice.** The CI `update-homebrew` job pushes its own\n> formula commit to dev while you are working, so the `git push origin dev` here\n> (and again in Step 10) can be rejected with `Updates were rejected`. That is\n> normal: `git pull origin dev --no-rebase`, then push again. On 0.7.1 both this\n> step and Step 10 required a second pass.\n\n> **Important**: This sync ensures dev has the merge commit from main. Without it,\n> dev and main diverge. The CI `update-homebrew` job only pushes the formula\n> commit to dev — it does not bring the PR merge commit onto dev. This manual\n> `git pull origin main --no-rebase` is what ensures dev has the merge commit.\n\n> **Do NOT** use `git pull origin main --ff-only` or `git reset --hard origin/main`\n> for this sync. Fast-forward is impossible across a squash merge — main's squash\n> commit has a different SHA than dev's release commit, so dev is never\n> fast-forwardable to main. And resetting dev to main rewrites dev's history,\n> which severs every open PR's merge-base from its original commit and balloons\n> their diffs to thousands of lines (confirmed against v0.3.10's release: PRs\n> went from `+80/-1` to `+6626/-300` after a `git reset --hard origin/main` on\n> dev). The plain `git pull origin main --no-rebase` above creates a regular merge commit on\n> dev. The merge bubble in dev's `git log` is the right cost for preserving\n> open-PR sanity. If the merge produces a `homebrew/archon.rb` conflict during a\n> recovery flow, resolve with `git checkout origin/main -- homebrew/archon.rb`\n> (note: `origin/main`, NOT `main` — local main is often stale because the\n> release pushes via `git push origin dev:main` without fast-forwarding the local\n> branch).\n\nThe GitHub Release is distinct from the git tag — without it, the release won't appear on the repository's Releases page. Always create it.\n\nIf the user merges the PR themselves and comes back, still offer to tag, release, and sync.\n\n### Step 9.5: Add the new tag's SQLite vintage fixture\n\nA release tag is a released schema shape. When `vx.y.z` changes the SQLite schema, `check:sqlite-vintages` (the `schema-upgrade` CI job) fails on every PR until the tag's fixture is committed. A tag that keeps the previous schema gets no fixture and needs nothing. Generate it on a branch off dev and commit whatever it writes:\n\n```bash\ngit fetch --tags origin\nbun run generate:sqlite-vintages   # writes packages/core/src/db/fixtures/sqlite-vintages/vx.y.z.sql\nbun run check:sqlite-vintages\n```\n\n### Step 10: Wait for Release Workflow and Update Homebrew Formula\n\n> **Note**: The `update-homebrew` CI job in `.github/workflows/release.yml` runs automatically\n> after the release job and handles the formula update + push to dev (part of Step 10).\n> Step 11 (tap sync to `coleam00/homebrew-archon`) is always manual. Check the Actions tab\n> before running Step 10 manually.\n\nAfter the tag is pushed, `.github/workflows/release.yml` builds platform binaries and uploads them to the GitHub release. This takes 5-10 minutes. The Homebrew formula SHA256 values cannot be known until these binaries exist.\n\n**Wait for all assets to appear on the release:**\n\n```bash\necho \"Waiting for release workflow to finish uploading binaries...\"\nWORKFLOW_FAILED=0\nfor i in {1..30}; do\n  ASSET_COUNT=$(gh release view \"vx.y.z\" --repo coleam00/Archon --json assets --jq '.assets | length')\n  # Expect 7 assets: 5 binaries (darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64.exe) + archon-web.tar.gz + checksums.txt\n  if [ \"$ASSET_COUNT\" -ge 7 ]; then\n    echo \"All $ASSET_COUNT assets uploaded\"\n    break\n  fi\n\n  # Short-circuit: if the release workflow itself has failed, stop waiting.\n  # Hanging for 15 min when CI already crashed just delays the recovery path.\n  WORKFLOW_STATUS=$(gh run list --workflow release.yml --event push --limit 1 --json conclusion,status --jq '.[0] | \"\\(.status)|\\(.conclusion)\"')\n  if [[ \"$WORKFLOW_STATUS\" == \"completed|failure\" ]]; then\n    echo \"Release workflow FAILED — no point waiting longer\"\n    WORKFLOW_FAILED=1\n    break\n  fi\n\n  echo \"  Assets so far: $ASSET_COUNT/7 — waiting 30s (attempt $i/30)...\"\n  sleep 30\ndone\n\nif [ \"$WORKFLOW_FAILED\" -eq 1 ] || [ \"$ASSET_COUNT\" -lt 7 ]; then\n  # Triage: rerun once in case it's transient, then check again.\n  RUN_ID=$(gh run list --workflow release.yml --event push --limit 1 --json databaseId --jq '.[0].databaseId')\n  echo \"Release workflow failed on run $RUN_ID. Rerunning failed jobs once to confirm...\"\n  gh run rerun \"$RUN_ID\" --failed\n  gh run watch \"$RUN_ID\" --exit-status --interval 30 || true\n\n  # Re-check asset count + run status after rerun.\n  ASSET_COUNT=$(gh release view \"vx.y.z\" --repo coleam00/Archon --json assets --jq '.assets | length')\n  if [ \"$ASSET_COUNT\" -ge 7 ]; then\n    echo \"Rerun succeeded — all assets now present\"\n  else\n    echo \"\"\n    echo \"===== DETERMINISTIC CI FAILURE =====\"\n    echo \"The release workflow failed on two consecutive runs. This is NOT a flake.\"\n    echo \"The tag and release exist but have no (or incomplete) assets.\"\n    echo \"\"\n    echo \"install.sh and similar 'releases/latest' paths are now 404-ing.\"\n    echo \"Proceeding with Homebrew/tap sync would publish a formula pointing at\"\n    echo \"missing or inconsistent binaries.\"\n    echo \"\"\n    echo \"Jump to the 'Recovery: deterministic release CI failure' section at the\"\n    echo \"bottom of this skill and execute it. Do NOT continue past this point.\"\n    exit 1\n  fi\nfi\n```\n\n**Fetch checksums.txt and extract SHA256 values:**\n\n```bash\nTMP_DIR=$(mktemp -d)\ngh release download \"vx.y.z\" --repo coleam00/Archon --pattern \"checksums.txt\" --dir \"$TMP_DIR\"\n\nDARWIN_ARM64_SHA=$(awk '/archon-darwin-arm64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nDARWIN_X64_SHA=$(awk '/archon-darwin-x64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nLINUX_ARM64_SHA=$(awk '/archon-linux-arm64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nLINUX_X64_SHA=$(awk '/archon-linux-x64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\n\n# Sanity check — all four must be present and non-empty\nfor var in DARWIN_ARM64_SHA DARWIN_X64_SHA LINUX_ARM64_SHA LINUX_X64_SHA; do\n  if [ -z \"${!var}\" ]; then\n    echo \"ERROR: $var is empty — checksums.txt may be malformed\"\n    cat \"$TMP_DIR/checksums.txt\"\n    exit 1\n  fi\ndone\n\nrm -rf \"$TMP_DIR\"\n```\n\n**Update `homebrew/archon.rb` in the main repo atomically with version AND SHAs:**\n\nRewrite the formula file using the exact template below. Before using it, diff it against the current `homebrew/archon.rb`: if the current formula has a block the template lacks, update the template first (v0.12.0's first regeneration dropped `caveats` this way). Do NOT edit in place with sed — the whole file should be regenerated from this template so there is zero risk of partial updates.\n\n```bash\ncat > homebrew/archon.rb << EOF\n# Homebrew formula for Archon CLI\n# To install: brew install coleam00/archon/archon\n#\n# This formula downloads pre-built binaries from GitHub releases.\n# For development, see: https://github.com/coleam00/Archon\n\nclass Archon < Formula\n  desc \"Governed agentic automation engine\"\n  homepage \"https://github.com/coleam00/Archon\"\n  version \"x.y.z\"\n  license \"MIT\"\n\n  on_macos do\n    on_arm do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-arm64\"\n      sha256 \"${DARWIN_ARM64_SHA}\"\n    end\n    on_intel do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-x64\"\n      sha256 \"${DARWIN_X64_SHA}\"\n    end\n  end\n\n  on_linux do\n    on_arm do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-arm64\"\n      sha256 \"${LINUX_ARM64_SHA}\"\n    end\n    on_intel do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-x64\"\n      sha256 \"${LINUX_X64_SHA}\"\n    end\n  end\n\n  def install\n    binary_name = case\n    when OS.mac? && Hardware::CPU.arm?\n      \"archon-darwin-arm64\"\n    when OS.mac? && Hardware::CPU.intel?\n      \"archon-darwin-x64\"\n    when OS.linux? && Hardware::CPU.arm?\n      \"archon-linux-arm64\"\n    when OS.linux? && Hardware::CPU.intel?\n      \"archon-linux-x64\"\n    end\n\n    bin.install binary_name => \"archon\"\n  end\n\n  def caveats\n    <<~EOS\n      Archon ships without AI providers. Add the ones you use:\n        archon setup\n      or directly:\n        archon provider add <claude|codex|pi>\n\n      Adding a provider connects Archon to your agent CLI or SDK; it does not install that CLI.\n\n      After `brew upgrade archon`, run `archon provider add` again for each\n      provider you use; installed providers must match the CLI version.\n    EOS\n  end\n\n  test do\n    # Basic version check - archon version should exit with 0 on success\n    assert_match version.to_s, shell_output(\"#{bin}/archon version\")\n  end\nend\nEOF\n```\n\n**Commit the formula update to main, then sync back to dev:**\n\n```bash\ngit checkout main\ngit pull origin main --no-rebase\ngit add homebrew/archon.rb\ngit commit -m \"chore(homebrew): update formula to vx.y.z\"\ngit push origin main\n\n# Sync dev with main so the formula update is on both branches\ngit checkout dev\ngit pull origin main --no-rebase\ngit push origin dev   # may be rejected — see below\n```\n\n**The CI `update-homebrew` job races you here.** It writes its own formula commit\ndirectly to dev (e.g. `chore: update Homebrew formula for vx.y.z`) while you are\ncommitting the same change to main. Both touch the same four `sha256` lines. The\npush above then fails with `Updates were rejected`:\n\n```bash\ngit pull origin dev --no-rebase   # merges CI's commit; usually resolves clean\ngit push origin dev\n```\n\n**A clean merge is not proof the SHAs are right.** Two commits editing the same\nlines can merge without conflict and still leave the wrong values. Verify against\nthe published checksums before pushing — this is the one file where a wrong value\nbreaks every user's install:\n\n```bash\nVERSION=x.y.z   # without the leading v\ngh release download \"v$VERSION\" --repo coleam00/Archon --pattern checksums.txt --dir /tmp/rel\n\nfail=0\n\n# Assert the formula version matches the release. A stale version points every\n# URL at the wrong release while carrying the new digests.\nformula_version=$(awk -F'\"' '/^[[:space:]]*version \"/ {print $2; exit}' homebrew/archon.rb)\nif [ \"$formula_version\" != \"$VERSION\" ]; then\n  echo \"  BAD version: formula says '$formula_version', release is '$VERSION'\"\n  fail=1\nfi\n\n# Compare each digest against the sha256 that FOLLOWS ITS OWN url line. A naive\n# `grep -q \"$digest\" formula` only asks whether the value appears anywhere, so\n# two platforms with swapped hashes both pass — and an empty digest degenerates\n# to `grep -q \"\"`, which matches every file.\nfor p in archon-darwin-arm64 archon-darwin-x64 archon-linux-arm64 archon-linux-x64; do\n  real=$(awk -v p=\"$p\" '$2 ~ \"(^|/)\" p \"$\" {print $1}' /tmp/rel/checksums.txt | head -1)\n  if ! printf '%s' \"$real\" | grep -qE '^[0-9a-f]{64}$'; then\n    echo \"  BAD $p: no 64-char digest in checksums.txt (got '$real')\"\n    fail=1\n    continue\n  fi\n  in_formula=$(awk -v p=\"$p\" '\n    index($0, \"/\" p \"\\\"\") { seen = 1; next }\n    seen && /sha256 \"/    { gsub(/.*sha256 \"|\".*/, \"\"); print; exit }\n  ' homebrew/archon.rb)\n  if [ \"$in_formula\" = \"$real\" ]; then\n    echo \"  OK   $p\"\n  else\n    echo \"  BAD  $p: formula has '$in_formula', release has '$real'\"\n    fail=1\n  fi\ndone\n\n[ \"$fail\" -eq 0 ] || { echo \"formula verification FAILED — do not push\"; exit 1; }\necho \"formula verified against v$VERSION checksums\"\n```\n\nEvery line must print `OK` and the version must match. On any `BAD`, regenerate\nthe formula from the Step 10 template rather than hand-editing — a hand edit is\nhow a digest ends up under the wrong platform in the first place.\n\nFinally, confirm convergence. **Fail closed** — Step 11 publishes to the tap that\nusers install from, so do not proceed on an unconverged tree:\n\n```bash\ngit fetch origin\nconverged=1\ngit merge-base --is-ancestor origin/main origin/dev \\\n  || { echo \"DIVERGED: dev does not contain main\"; converged=0; }\nstranded=$(git log origin/dev..origin/main --oneline | wc -l | tr -d ' ')\n[ \"$stranded\" -eq 0 ] || { echo \"STRANDED: $stranded commit(s) on main not on dev:\"; \\\n  git log origin/dev..origin/main --oneline; converged=0; }\n[ \"$converged\" -eq 1 ] || { echo \"sync incomplete — resolve before Step 11\"; exit 1; }\necho \"branches converged — OK\"\n```\n\n### Step 11: Sync the Homebrew Tap Repo\n\nThe `coleam00/homebrew-archon` repository hosts the actual tap formula that Homebrew reads when users run `brew tap coleam00/archon && brew install coleam00/archon/archon`. The file `coleam00/Archon/homebrew/archon.rb` is the source-of-truth template; the file `coleam00/homebrew-archon/Formula/archon.rb` is what users actually install from. These must be kept in sync.\n\n```bash\nTAP_DIR=$(mktemp -d)\ngit clone git@github.com:coleam00/homebrew-archon.git \"$TAP_DIR\"\ncp homebrew/archon.rb \"$TAP_DIR/Formula/archon.rb\"\n\ncd \"$TAP_DIR\"\nif git diff --quiet; then\n  echo \"Tap formula already matches — no sync needed\"\nelse\n  git add Formula/archon.rb\n  git commit -m \"chore: sync formula to vx.y.z\"\n  git push origin main\nfi\ncd -\nrm -rf \"$TAP_DIR\"\n```\n\nIf the `git clone` fails with a permissions error, the user running the release skill does not have push access to `coleam00/homebrew-archon`. Ask them to request push access from the repo owner, or to perform the sync manually via the GitHub web UI. Do not skip this step silently — the release is not complete until the tap is synced.\n\n### Step 12: Verify the Release End-to-End\n\nAfter the formula is synced, the final verification step is to actually install the released binary via Homebrew and run smoke tests. Use the `test-release` skill:\n\n```\n/test-release brew x.y.z\n```\n\nThis will:\n- Install via `brew tap coleam00/archon && brew install coleam00/archon/archon`\n- Verify the binary reports the correct version and `Build: binary`\n- Verify bundled workflows load\n- Verify the SDK spawn path works (a minimal archon-investigate run)\n- Verify the env-leak guard strips sensitive keys from a leaky .env (Test 4)\n- Uninstall cleanly\n- Produce a PASS/FAIL report\n\n**If `/test-release brew` fails, the release is not ready to announce.** File a hotfix issue for whatever broke, cut `x.y.z+1` with the fix, and re-run this skill. Do NOT advertise a release that fails `test-release`.\n\nAlso run `/test-release curl-mac x.y.z` to cover the curl install path. The two install paths test slightly different things (Homebrew tests the tap formula, curl tests `install.sh` and checksums from the release) and both need to work for users to have a reliable install experience.\n\nIf you have a VPS available, also run `/test-release curl-vps x.y.z <vps-target>` to verify the Linux binary.\n\n## Recovery: deterministic release CI failure\n\nReached here because Step 10 detected two consecutive workflow failures. The tag `vx.y.z` is pushed, the GitHub release exists, but assets are missing or incomplete. Every `install.sh` run currently resolves `releases/latest` to this broken release and 404s on download. Homebrew users are safe because Step 10's atomic formula update was blocked.\n\n**Do not re-run the release workflow a third time hoping it succeeds.** If the failure was reproducible twice, it's a code bug — you need to ship code to fix it.\n\n### Immediate mitigation (restore `install.sh`)\n\nDelete the GitHub Release so `releases/latest` falls back to the previous version. Keep the git tag — tag immutability matters and there are no shipped artifacts pointing at it anyway.\n\n```bash\ngh release delete \"vx.y.z\" --yes\n# Do NOT delete the tag:\n#   git push --delete origin vx.y.z   ← do not run\n# Tag stays so git history records the attempt; no release means no assets\n# means releases/latest resolves to the prior working release.\n```\n\nVerify:\n\n```bash\ngh api repos/coleam00/Archon/releases/latest --jq '.tag_name'\n# should now print the prior version (e.g. v0.3.6), not vx.y.z\n```\n\n### Diagnose\n\nThe release workflow logs tell you which target failed and at what stage (compile vs. smoke-test vs. upload):\n\n```bash\ngh run list --workflow release.yml --limit 2 --json databaseId,conclusion\ngh run view <RUN_ID> --log-failed\n```\n\nCommon causes:\n- **Bundler/bytecode bug** — Bun `--bytecode` produces invalid output for the current module graph. Symptom: `TypeError: Expected CommonJS module to have a function wrapper` at binary startup. Historically caused by a new dependency's CJS/ESM shape interacting with `--bytecode` — dropping the flag or lazy-importing the offending module has been the fix.\n- **Module-init crash** — a dependency does `readFileSync('package.json')` or similar at module top level via a path that exists in `node_modules/` but not next to a compiled binary. Symptom: every binary subcommand crashes immediately; error typically mentions a missing file adjacent to `process.execPath`. Fix by lazy-importing the dependency behind the code path that actually uses it.\n- **Smoke-test timeout on Windows** — not actually a bug in the code; the Windows runner is slow. Rerun once; if it recurs, bump the test timeout.\n\nStep 1.5 now runs a local compiled-binary smoke test before any user-visible step. If the failure mode above reproduces locally, you've found it. If it doesn't, the bug is platform-specific (Windows cross-compile, Linux glibc, etc.) and you need the CI logs.\n\n### Fix and re-release as the NEXT patch\n\n**Do not reuse `vx.y.z`.** Cut `vx.y.(z+1)` (or next-minor if warranted) with the fix. Rationale:\n- Tag immutability: `vx.y.z` is already recorded in git history and release cache\n- Semver clarity: users and tooling should see a new version number when the bits change\n- Audit trail: \"v0.3.7 was cut but had no shipped binaries; v0.3.8 is the first release with <fix>\" is cleaner than rewriting v0.3.7\n\nSteps:\n\n1. Cut a fix branch off dev, implement the fix, PR to dev, merge.\n2. Re-run `/release` (it will bump to the next patch — e.g. `0.3.8` — automatically).\n3. Step 1.5's pre-flight smoke will catch the same bug locally if the fix didn't actually fix it. Iterate until it passes before tagging.\n\n### CHANGELOG note for the hotfix release\n\nInclude a line in the new release's CHANGELOG that references the broken prior version so users understand why there's no binary artifact under that tag:\n\n```markdown\n### Fixed\n\n- **First release with working compiled binaries after vx.y.z's <bug>.** vx.y.z was tagged but its binary smoke test failed deterministically (see RUN_ID in CI history). The tag is preserved for history; this release (vx.y.(z+1)) is the first with shipped binaries. `install.sh` and Homebrew were never updated to vx.y.z, so users were not exposed to the broken state.\n```\n\n### What NOT to do\n\n- **Do not force-push or rewrite the tag.** Once a tag exists, it's a public promise of that SHA. Deleting and re-creating to a different SHA is tag-spoofing and breaks any downstream that cached the original.\n- **Do not skip this recovery path to \"just push more binaries to the broken release\".** The release exists with a specific commit SHA; uploading binaries built from a newer SHA creates binary/source drift that is hard to diagnose later.\n- **Do not update the Homebrew formula before v0.3.(z+1) is fully shipped.** The formula should always point at a version with all 7 assets uploaded and `/test-release brew` passing.\n\n## Important Rules\n\n- NEVER force push\n- **NEVER derive the changelog from `git log main..dev`.** main squash-merges, so\n  that range accumulates every previously-released commit and grows each release\n  (61 vs 25 actual on 0.7.1). Use dev's last `Release x.y.z` commit as the\n  boundary, and cross-check the result against the previous version's changelog\n  entries — any PR number appearing in both means the boundary is wrong.\n- **ALWAYS pass `--no-rebase` to `git pull`.** The bare form aborts with\n  `Need to specify how to reconcile divergent branches` unless `pull.rebase` is\n  configured, and a `pull.rebase=true` config would rewrite dev's history.\n- **ALWAYS verify sync convergence rather than assuming it.**\n  `git merge-base --is-ancestor origin/main origin/dev` after every sync step.\n  A commit made directly to main stays stranded silently otherwise.\n- **NEVER trust a clean `homebrew/archon.rb` merge.** The CI `update-homebrew`\n  job edits the same `sha256` lines on dev that you edit on main; the merge can\n  succeed and still be wrong. Diff the four values against the published\n  `checksums.txt` before pushing.\n- **Flag a version bump that contradicts the commit range.** If `patch` was\n  requested but the range contains `feat:` commits or new user-facing CLI\n  surface, say so at the Step 7 review and let the user decide — do not silently\n  ship features as a patch.\n- **NEVER skip Step 1.5 (pre-flight compiled-binary smoke).** If the stack is a Bun/Node project with a build-binaries script, the `bun build --compile` smoke test runs before version bump, PR, or tag. Skipping it means every bundler regression or module-init crash only surfaces after the tag is pushed — by which point `releases/latest` is already 404-ing for every user. The ~30s cost is paid to keep the failure mode local.\n- If Step 1.5 fails, **abort the release** and fix the underlying issue on a feature branch. Do not \"just skip it\" and hope CI doesn't repro the problem.\n- NEVER skip the review step — always show the changelog before committing\n- NEVER include \"Co-Authored-By: Claude\" or any AI attribution in the commit\n- NEVER add emoji to changelog entries unless the user asks\n- If the user says \"ship it\" without specifying bump type, default to patch\n- The commit message is just `Release x.y.z` — clean and simple\n- **NEVER update `homebrew/archon.rb` version field without also updating the `sha256` values**. They must move together atomically. The correct SHAs only exist after the release workflow finishes building binaries — see Step 10. Updating the version field alone produces a stale formula that looks valid but causes checksum mismatches on install.\n- **NEVER skip Step 11 (tap sync).** The `coleam00/Archon/homebrew/archon.rb` file is only a template; users install from `coleam00/homebrew-archon/Formula/archon.rb`. If you update one without the other, users get stale or wrong data.\n- **NEVER announce a release that failed `/test-release brew`.** A release that installs but crashes on first invocation is worse than no release — it burns user trust. If the release verification fails, cut a hotfix before telling anyone the release exists.\n",".claude/skills/rulecheck/SKILL.md":"---\nname: rulecheck\ndescription: |\n  Autonomous rule adherence checker. Scans the codebase for rule violations,\n  fixes the highest-impact ones in an isolated worktree, runs full validation,\n  creates a PR. Uses memory to track progress across runs.\ndisable-model-invocation: true\nagent: rulecheck-agent\nargument-hint: \"[focus area]\"\n---\n\n# Rulecheck\n\nLaunch the rulecheck-agent to autonomously scan and fix AGENTS.md rule violations.\n\n## Your Job (Main Agent)\n\nYou are the orchestrator. Your ONLY job is to launch the rulecheck-agent and\nreport its results when it completes. You do NOT do the scanning or fixing yourself.\n\n1. **Launch the rulecheck-agent** with the focus area (if any): `$ARGUMENTS`\n2. **Wait for it to complete** — do NOT poll, tail, or check on it. You will be\n   notified automatically when it finishes.\n3. **Report ONLY what the agent told you** — relay its final message verbatim\n   or summarize it. Do NOT search for PRs, read files, or fabricate results.\n   If the agent hit its context limit, say so. If it didn't produce a PR URL\n   in its output, don't go looking for one.\n\n## Rules for You\n\n- **Do NOT scan the codebase yourself** — that's the agent's job\n- **Do NOT grep, read source files, or run linters** — the agent handles all of that\n- **Do NOT edit any files** — you are the orchestrator, not the fixer\n- **Do NOT try to resume or check on the agent** while it's running — just wait\n- **Do NOT do the agent's work if it fails or hits context limits** — report the failure to the user and stop. NEVER pick up where the agent left off. You are not in a worktree and would be editing main directly.\n- **Do NOT update project memory** — the agent maintains its own memory at `.claude/agent-memory/rulecheck-agent/`. Do not duplicate run results into your project memory.\n- Trust the agent. It runs in an isolated worktree and will create a PR when done.\n",".claude/skills/rulecheck/rules-guide.md":"# Rules Guide — Where to Find Project Rules\n\nReference for the rulecheck agent. Focus on rules that **linters can't enforce** —\narchitectural principles, patterns, and conventions from AGENTS.md.\n\n## Primary Source: `AGENTS.md` (Root)\n\nRead this file thoroughly, but derive checks only from its normative rules —\nsections like `Project Overview` and `Product Direction` are descriptive\ncontext, not something to enforce. Root `CLAUDE.md` is only a one-line pointer\nto this file — reading that instead gets you nothing.\n\n### Engineering Principles (each is a concrete rule, not a slogan)\n\nRead `AGENTS.md` for the repository's engineering rules.\n\nAlso read `.archon/engineering.md` for the aspirational engineering standard.\n",".claude/skills/rulecheck/workshop-guide.md":"# Rulecheck — Workshop Guide\n\n## What This Skill Demonstrates\n\nAn autonomous code quality agent that combines **17 Claude Code features** into one\ngenuinely useful tool: custom agents, safety hooks, all four hook types (command,\nprompt, http, agent), LLM-as-judge, persistent memory, Slack notifications, and\nworktree isolation.\n\nReplaces the existing advisory-only `code-rulecheck` agent with a fully autonomous\nversion that finds violations, fixes them, validates, creates PRs, and learns.\n\n**Full demo steps**: See [part2-guide.md](../../workshop/part2-guide.md) — Feature 8.\n\n## Architecture\n\n```\nUser invokes /rulecheck [focus area]\n        │\n        ▼\n┌──────────────────────────┐\n│  SKILL.md                │  agent: rulecheck-agent\n│  (orchestrator)          │  disable-model-invocation: true\n│                          │  argument-hint: \"[focus area]\"\n│  Launches agent,         │\n│  reports results         │  NO context: fork\n└────────┬─────────────────┘\n         │ delegates to agent\n         ▼\n┌──────────────────────────┐\n│  rulecheck-agent.md     │  isolation: worktree\n│  (.claude/agents/)       │  background: true\n│                          │  memory: project\n│  model: sonnet           │  permissionMode: acceptEdits\n│  maxTurns: 500           │\n│                          │\n│  hooks:                  │\n│  ├─ PreToolUse [Bash]    │──→ block-dangerous.sh (safety gate)\n│  ├─ PostToolUse [Edit]   │──→ bun run lint:fix (auto-fix)\n│  └─ Stop                 │\n│     ├─ command           │──→ slack-notify.sh (reads summary JSON)\n│     ├─ http              │──→ POST to Slack webhook URL\n│     └─ agent             │──→ meta-judge (LLM evaluation)\n└────────┬─────────────────┘\n         │ works in worktree\n         ▼\n┌──────────────────────────┐\n│  Worktree                │\n│  1. Read memory          │\n│  2. Read AGENTS.md rules │\n│  3. Deep scan source     │\n│  4. Group violations     │\n│  5. Fix one group        │\n│  6. Check own changes    │\n│  7. Write summary JSON   │\n│  8. gh pr create         │\n│  9. Update memory        │\n└──────────────────────────┘\n         │\n         ▼\n   Results reported to main conversation\n   + PR created + Slack notified + memory updated\n```\n\n## Features to Walk Through\n\n### 1. `agent: rulecheck-agent` — Custom Agent Delegation (no fork)\n\n**What is it?** The `agent:` field delegates to a custom agent with its own\nsystem prompt, model, hooks, and memory configuration.\n\n**In this skill**: The skill acts as an **orchestrator** — it launches the agent\nand reports results. There is no `context: fork`. The skill stays active in the\nmain conversation while the agent runs in the background in its own worktree.\n\n**Why no fork?** The rulecheck agent uses `background: true` and\n`isolation: worktree`, so it already runs in a completely separate context.\nForking would add an unnecessary layer. The skill just launches and waits.\n\n### 2. `disable-model-invocation: true` — User-Only Trigger\n\n**What is it?** Prevents Claude from auto-invoking this skill. Since it creates\nPRs and pushes branches (side effects), only the user should trigger it.\n\n### 3. `argument-hint: \"[focus area]\"` — Usage Hint\n\n**What is it?** Shows in `/help` and tab completion to tell users what arguments\nthe skill accepts. Optional — the agent works without arguments too.\n\n### 4. `!`command`` — Dynamic Context Injection\n\n**What is it?** Shell commands that execute at skill load time (before Claude sees\nthe prompt). Stdout replaces the placeholder.\n\n**In this skill**: Injects current branch, recent git log, and lint status so the\nagent starts with real context instead of having to fetch it.\n\n### 5. Supporting Files — On-Demand Loading\n\n**What is it?** Markdown files linked from the skill body with `[name](file.md)`.\nClaude loads them only when needed, keeping the initial prompt small.\n\n**In this skill**: `rules-guide.md` documents where to find rules and how to rank\nviolations. The agent loads it during the scan phase.\n\n### 6. `isolation: worktree` — Git Worktree Isolation\n\n**What is it?** The agent works in a temporary git worktree — a separate working\ndirectory with its own branch. Changes are isolated from the main repo.\n\n**In this skill**: The agent edits files and creates commits without affecting the\nuser's working directory. If something goes wrong, the worktree is disposable.\n\n### 7. `background: true` — Concurrent Execution\n\n**What is it?** The agent runs in the background. The user can continue working\nin the main conversation while the agent scans, fixes, and creates a PR.\n\n### 8. `memory: project` — Persistent Memory\n\n**What is it?** The agent has a persistent memory directory at\n`.claude/agent-memory/rulecheck-agent/`. It reads from and writes to `MEMORY.md`\nacross runs.\n\n**In this skill**: The agent remembers what it fixed last time, what's in the\nbacklog, and meta-judge feedback — so each run builds on the previous one.\n\n### 9. `permissionMode: acceptEdits` — Auto-Accept Edits\n\n**What is it?** File edits (Edit/Write tools) are automatically approved without\nuser confirmation. Other tools still require approval per the normal flow.\n\n**In this skill**: The agent needs to edit many files to fix violations. Prompting\nfor each edit would defeat the purpose of autonomous execution.\n\n### 10. `maxTurns: 500` — Safety Cap\n\n**What is it?** Limits the agent to 500 API round-trips. Prevents runaway agents\nthat loop endlessly. Set high here because the agent does a lot of work (scan,\nfix, validate, commit, push, PR, memory update).\n\n### 11. `hooks:` in Agent Frontmatter\n\n**What is it?** Hooks defined directly in the agent's YAML frontmatter. They\nactivate whenever this agent runs, regardless of which skill invokes it.\n\nThis skill demonstrates all four hook types:\n\n#### 11a. `type: \"command\"` — Shell Script Hooks\n\n**PreToolUse [Bash]**: `block-dangerous.sh` reads the command from stdin, checks\nagainst a blocklist (force push, git clean, hard reset, rm -rf), and exits 2 to\nblock dangerous commands.\n\n**PostToolUse [Edit|Write]**: Runs `bun run lint:fix` after every file edit to\nauto-correct formatting issues before they accumulate.\n\n**Stop**: `slack-notify.sh` reads the agent's summary file and sends a formatted\nSlack message with what was fixed, PR link, and remaining opportunities. This\ndemonstrates **inter-hook communication** — the agent writes a JSON file, the\nhook reads it.\n\n#### 11b. `type: \"http\"` — HTTP Webhook\n\n**Stop**: POSTs the event directly to a Slack webhook URL. No script needed —\njust a URL in the frontmatter. This is the simplest way to notify external\nservices. Compare with 11a above: the command hook gives you full control over\nthe message format, while the HTTP hook sends the raw event with zero code.\n\n#### 11c. `type: \"agent\"` — LLM Meta-Judge\n\n**Stop**: A third Stop hook spawns a subagent that evaluates the rulecheck's\nexecution. It reviews what was fixed, assesses prioritization quality, and writes\nstructured feedback to memory for the next run.\n\n### 12. `statusMessage` — Hook Progress Indicators\n\n**What is it?** A string shown in the Claude Code spinner while a hook runs.\nGives users visibility into what's happening during hook execution.\n\n**In this skill**: \"Checking command safety...\", \"Auto-fixing lint issues...\",\n\"Notifying Slack...\", \"Posting run event to Slack...\", \"Running meta-judge evaluation...\"\n\n### 13. `$ARGUMENTS` — Skill Arguments\n\n**What is it?** The text after the skill name (e.g., `/rulecheck error handling`)\nis available as `$ARGUMENTS` in the skill body and passed through to the agent.\n\n### 14. `${CLAUDE_SESSION_ID}` — Session Identifier\n\n**What is it?** Environment variable with the current session ID. Used in the\nmeta-judge prompt to tag feedback with the session that produced it.\n\n### 15. Summary File Pattern — Inter-Hook Communication\n\n**What is it?** The agent writes `.claude/archon/rulecheck-last-run.json` as a\nstructured summary. The Slack hook reads this file to format its notification.\n\nThis is a practical pattern for passing data between the agent and its hooks\nwhen the hook needs more than what's in the event JSON.\n\n## Live Demo Steps\n\n1. **Show the architecture** — open all files:\n   - `.claude/skills/rulecheck/SKILL.md` (skill entry point)\n   - `.claude/agents/rulecheck-agent.md` (the autonomous agent)\n   - `.claude/skills/rulecheck/hooks/block-dangerous.sh` (safety gate)\n   - `.claude/skills/rulecheck/hooks/slack-notify.sh` (Slack notification)\n   - `.claude/skills/rulecheck/rules-guide.md` (supporting reference)\n\n2. **Trace the delegation chain**:\n   skill → `agent: rulecheck-agent` → `isolation: worktree` + `background: true`\n\n3. **Show the hooks**: PreToolUse safety gate, PostToolUse lint auto-fix,\n   Stop Slack (command + http) + meta-judge (agent)\n\n4. **Test the safety hook**:\n   ```bash\n   echo '{\"tool_input\":{\"command\":\"git push --force\"}}' | .claude/skills/rulecheck/hooks/block-dangerous.sh\n   # Should exit 2 with error message\n   echo '{\"tool_input\":{\"command\":\"bun run lint\"}}' | .claude/skills/rulecheck/hooks/block-dangerous.sh\n   # Should exit 0\n   ```\n\n5. **Invoke**: `/rulecheck type safety`\n   - Show: background execution, user can keep working\n   - Show: the agent scanning, fixing, validating in the worktree\n   - Show: PR creation and Slack notification\n\n6. **Show the outputs**:\n   - PR on GitHub\n   - Slack notification\n   - Memory file (what was learned)\n   - Meta-judge feedback\n\n## Comparison: Before vs After\n\n| | Advisory Code-Rulecheck | Autonomous Rulecheck |\n|---|---|---|\n| Format | `.claude/agents/code-rulecheck.md` | Skill + agent + hooks |\n| Execution | Inline, blocks conversation | Background, worktree-isolated |\n| Output | Advisory report (no changes) | Actual fixes + PR |\n| Isolation | None (reads in-place) | Git worktree |\n| Safety | None | PreToolUse command blocklist |\n| Validation | None | Tests, lint, type-check on changes; CI is the full gate |\n| Notifications | None | Slack (command hook + HTTP hook) |\n| Learning | None | Persistent memory + meta-judge |\n| Autonomy | Reports findings only | Finds, fixes, validates, PRs |\n\n## Talking Points\n\n- \"This is 17 features in one skill. Each one is simple — the power is in composition.\"\n- \"The safety hook is a shell script. It reads JSON, checks a blocklist, exits 2 to block. No framework needed.\"\n- \"Memory makes the agent better over time. Each run builds on the last — it remembers what's in the backlog.\"\n- \"The meta-judge is an LLM evaluating another LLM. It writes structured feedback the agent reads next run.\"\n- \"Worktree isolation means the agent can break things safely. Your working directory is untouched.\"\n- \"Background execution means you keep working. The agent runs, creates a PR, notifies Slack — you review when ready.\"\n- \"The old code-rulecheck was advisory. This one actually fixes things. Same domain, fundamentally different capability.\"\n",".claude/skills/test-release/SKILL.md":"---\nname: test-release\ndescription: |\n  Verify a released archon binary works end-to-end via a specific install path.\n  Use when: cutting a new release, reproducing a user bug report on the released\n  version, or validating that a hotfix binary actually works after a re-tag.\n  Triggers: \"test the release\", \"test 0.3.1 via brew\", \"verify the curl install\",\n            \"smoke test the binary\", \"did the release binary work\",\n            \"run /test-release\", \"verify the release\".\n  NOT for: testing dev work (use bun link directly), testing unreleased changes\n  (build locally via scripts/build-binaries.sh first), or running the full\n  validate suite (bun run validate is separate).\nargument-hint: \"[brew|curl-mac|curl-vps] [optional: version to verify] [optional: vps-target]\"\n---\n\n# Test Release\n\nAutomated smoke test for a released archon binary. Covers three install paths:\n\n- `brew` — Homebrew tap on macOS (tests the formula and checksums)\n- `curl-mac` — `curl install.sh` on macOS (tests the install script, sandboxed to a temp dir)\n- `curl-vps` — `curl install.sh` on a remote Linux VPS (tests the Linux binary and full install path)\n\nEvery path installs the binary, runs a fixed smoke test suite, and cleans up. The dev `bun link` binary is never touched and remains the default `archon` on PATH throughout.\n\n**When NOT to use this skill:**\n\n- There is no release yet — build a local binary via `bash scripts/build-binaries.sh` and run it from `dist/binaries/` directly\n- You want to test the dev clone — use `bun run validate` or invoke source directly via `bun packages/cli/src/cli.ts`\n- You want to test the full server + web UI deploy flow — use the cloud-init from `deploy/cloud-init.yml` on a real VPS\n\n## Local build for pre-release QA\n\nTo build a binary locally with the exact same flags and constants that CI uses,\ninvoke `scripts/build-binaries.sh` directly. The script supports two modes:\n\n```bash\n# Multi-target mode (builds all 4 local platforms into dist/binaries/)\nVERSION=0.3.1 GIT_COMMIT=abc12345 bash scripts/build-binaries.sh\n\n# Single-target mode (matches one CI matrix job)\nVERSION=0.3.1 \\\nGIT_COMMIT=abc12345 \\\nTARGET=bun-darwin-arm64 \\\nOUTFILE=dist/test-archon-darwin-arm64 \\\nbash scripts/build-binaries.sh\n\n# Verify the binary — use the path from the mode you built:\n#   multi-target → ./dist/binaries/archon-darwin-arm64\n#   single-target → the OUTFILE you passed above\n./dist/test-archon-darwin-arm64 version\n# Expected: Archon CLI v0.3.1, Build: binary, Git commit: abc12345\n```\n\nRun this **before tagging a release** to catch build-time-constant issues\nlocally. The script is the canonical entry point — both local dev and the\nrelease workflow call it the same way, so a green local build means the CI\nbuild will exercise the same code path.\n\n## Phase 1 — Determine scope\n\nParse the arguments. The skill takes up to three:\n\n1. **Install path** (`brew` | `curl-mac` | `curl-vps`): which install flow to exercise\n2. **Expected version** (optional): the version tag the release should report, e.g. `0.3.1`. If not provided, fetch it:\n\n```bash\ngh release list --repo coleam00/Archon --limit 1 --json tagName --jq '.[0].tagName'\n```\n\n3. **VPS target** (only for `curl-vps`): SSH target in the form `user@host` or `host` (uses default SSH config)\n\nIf any argument is missing, ask the user for clarification BEFORE doing anything. Never guess the install path or the expected version.\n\nConfirm the plan with the user before proceeding to Phase 2. Output should look like:\n\n```\nAbout to test:\n  Path:     brew (Homebrew tap on macOS)\n  Version:  0.3.1 (expected)\n  Cleanup:  will uninstall after tests (brew uninstall + untap)\n            If `archon-stable` symlink is detected in Phase 2, it will be\n            restored at the end of Phase 5 by reinstalling the tap formula.\n\nProceed? (y/N)\n```\n\nDo not continue without explicit confirmation. Release testing touches install state and the user should be aware.\n\n## Phase 2 — Pre-flight\n\nBefore touching anything:\n\n1. Capture the current dev binary state for reference:\n\n```bash\nwhich -a archon\narchon version 2>&1 | head -5\n```\n\nRecord the path and version of the dev binary so the final report can show \"dev binary was untouched\".\n\n2. Verify prerequisites for the chosen path:\n\n   - **brew**: `brew --version` must succeed. If not, abort with \"Homebrew not installed — see https://brew.sh/\"\n   - **curl-mac**: `curl --version` must succeed (effectively always true on macOS)\n   - **curl-vps**: `ssh <target> 'uname -a'` must succeed. If not, abort with \"Cannot SSH to <target>\". Also verify `ssh <target> 'command -v curl'` returns a path.\n\n3. Confirm the release exists on GitHub:\n\n```bash\ngh release view v<version> --repo coleam00/Archon --json tagName,assets --jq '{tag: .tagName, assetCount: (.assets | length)}'\n```\n\nIf the release does not exist or has no assets, abort with a clear message. Do not proceed to install a non-existent release.\n\n4. **Detect persistent `archon-stable` install (brew path only).** If the user has renamed a prior brew install to `archon-stable` (the dual-homebrew pattern — see `~/.config/fish/functions/brew-upgrade-archon.fish`), Phase 5's `brew uninstall` will wipe it. Capture the state so Phase 5b can restore it:\n\n```bash\nARCHON_STABLE_WAS_INSTALLED=\"\"\nif [ -L /opt/homebrew/bin/archon-stable ] || [ -L /usr/local/bin/archon-stable ]; then\n  ARCHON_STABLE_WAS_INSTALLED=\"yes\"\n  echo \"Detected persistent archon-stable — will restore after Phase 5 uninstall.\"\nfi\n```\n\nExport `ARCHON_STABLE_WAS_INSTALLED` into the environment used by Phase 5b. Only applies to the `brew` path — `curl-mac` and `curl-vps` don't go through brew and don't disturb `archon-stable`.\n\n## Phase 3 — Install\n\n### Path: brew\n\n```bash\nbrew tap coleam00/archon\nbrew install coleam00/archon/archon\nBINARY=\"$(brew --prefix coleam00/archon/archon)/bin/archon\"\n```\n\nCapture `$BINARY` for Phase 4. Verify the file exists and is executable.\n\n### Path: curl-mac\n\nInstall to a dedicated tmp directory so the dev `bun link` binary stays on PATH unchanged:\n\n```bash\nINSTALL_DIR=/tmp/archon-test-release-$(date +%s)\nmkdir -p \"$INSTALL_DIR\"\ncurl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | INSTALL_DIR=\"$INSTALL_DIR\" bash\nBINARY=\"$INSTALL_DIR/archon\"\n```\n\n> **The env var must prefix `bash`, not `curl`.** In `VAR=x cmd1 | cmd2` the\n> assignment applies only to `cmd1`, so `INSTALL_DIR=… curl … | bash` sets it for\n> the *download* and leaves the *installer* on its `/usr/local/bin` default. The\n> failure is confusing rather than obvious: the script downloads and verifies the\n> checksum, then demands sudo and dies with\n> `sudo: a terminal is required to read the password`. Observed on the 0.7.1 test.\n>\n> `scripts/install.sh` carries the same broken form in its own header example\n> (`INSTALL_DIR=~/.local/bin curl -fsSL ... | bash`), so users copying it hit this\n> too. Fix both together; see issue #2436.\n\nVerify `$BINARY` exists and is executable. Capture the install directory for cleanup.\n\n### Path: curl-vps\n\nRun the install script on the VPS:\n\n```bash\nssh <target> 'curl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash'\n```\n\nDetermine where the binary landed — `install.sh` uses `/usr/local/bin/archon` by default, or falls back to `$HOME/.local/bin/archon` if `/usr/local/bin` is not writable:\n\n```bash\nssh <target> 'command -v archon'\n```\n\nCapture the remote path as `$REMOTE_BINARY`. For the rest of Phase 4, wrap every command as `ssh <target> '<cmd>'`.\n\n### Capture SHA256 and version\n\nImmediately after install, capture:\n\n```bash\n# Local paths (brew / curl-mac)\nshasum -a 256 \"$BINARY\" | awk '{print $1}'\n\"$BINARY\" version 2>&1\n\n# Remote path (curl-vps)\nssh <target> \"shasum -a 256 $REMOTE_BINARY || sha256sum $REMOTE_BINARY\" | awk '{print $1}'\nssh <target> \"$REMOTE_BINARY version\" 2>&1\n```\n\nRecord both for the report. The SHA256 lets us confirm later that a user reporting a bug is running the exact same artifact we tested.\n\n## Phase 4 — Smoke tests\n\nRun these in order against `$BINARY` (or `ssh <target> $REMOTE_BINARY` for curl-vps). **Always use the full binary path, never the `archon` on PATH**, so there is no ambiguity about which binary is under test.\n\nEach test should capture the full command output for the final report. If a test fails, continue to the next test (so the report is complete) but mark the overall result as FAIL.\n\n### Test 1 — Version reports correctly\n\n```bash\n\"$BINARY\" version\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- Output contains `Archon CLI v<expected-version>`\n- Output contains `Build: binary` (not `Build: source (bun)`)\n- Output contains a non-`unknown` git commit (i.e., `Git commit: <sha>`)\n\n**Common failures:**\n\n- Exit code non-zero → pino-pretty crash (#960) or similar startup failure\n- Wrong version reported → binary is stale or the build script failed to update `bundled-build.ts`\n- `Build: source (bun)` → `BUNDLED_IS_BINARY` was not set to `true` during the build (regression of #979)\n- `Git commit: unknown` → build script did not capture the commit\n\n### Test 2 — Bundled workflows load\n\nFrom 0.12.0, release binaries ship without providers, and a config that names one fails to load until it's installed. Run `\"$BINARY\" provider add claude codex pi` (the ones the config uses) before this test. That is the documented upgrade step, and it is part of what this test checks.\n\nCreate a temporary git repository so the CLI has something to operate on:\n\n```bash\nTESTREPO=/tmp/archon-test-repo-$(date +%s)\nmkdir -p \"$TESTREPO\"\ncd \"$TESTREPO\"\ngit init -q\ngit commit -q --allow-empty -m init\n\"$BINARY\" workflow list\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- Output lists the bundled `sdlc` pack workflows: archon-triage, archon-investigate, archon-plan, archon-implement, archon-pr, archon-review, archon-validate, archon-deliver, archon-upkeep, archon-ship\n- No errors about missing workflow files or JSON parse failures\n\n**Common failures:**\n\n- Empty list → bundled defaults were not embedded in the binary (regression of the `isBinaryBuild` detection path)\n- `Not in a git repository` → working directory handling bug\n- Parse errors → the embedded JSON is corrupt or stale\n\n### Test 3 — SDK path works (archon-investigate workflow)\n\n**Prerequisite.** Compiled binaries require Claude Code installed on the host and a configured binary path. Before running this test, ensure one of:\n\n```bash\n# Option A — env var (easy for ad-hoc testing)\n# After the native installer (Anthropic's default):\nexport CLAUDE_BIN_PATH=\"$HOME/.local/bin/claude\"\n# Or after npm global install:\nexport CLAUDE_BIN_PATH=\"$(npm root -g)/@anthropic-ai/claude-code/cli.js\"\n\n# Option B — config file (persistent)\n#   Add to ~/.archon/config.yaml:\n#   assistants:\n#     claude:\n#       claudeBinaryPath: /absolute/path/to/claude\n```\n\nThen in the same `$TESTREPO`:\n\n```bash\n\"$BINARY\" workflow run archon-investigate --no-worktree \"say hello and nothing else\" 2>&1 | tee /tmp/archon-test-investigate.log\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- The Claude subprocess spawns successfully (no `spawn EACCES`, `ENOENT`, or `process exited with code 1` in the early output)\n- No `Claude Code CLI not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)\n- A response is produced (any response — even just \"hello\" — proves the SDK round-trip works)\n\n**Common failures:**\n\n- `Claude Code not found` → `CLAUDE_BIN_PATH` / `claudeBinaryPath` is unset or points at a non-existent file. Fix the path and re-run.\n- `Module not found \"/Users/runner/...\"` → regression of #1210: the resolver was bypassed and the SDK's `import.meta.url` fallback leaked a build-host path. Investigate `packages/providers/src/claude/provider.ts` and the resolver.\n- `Credit balance is too low` → auth is pointing at an exhausted API key (check `CLAUDE_USE_GLOBAL_AUTH` and `~/.archon/.env`)\n- `unable to determine transport target for \"pino-pretty\"` → #960 regression, binary crashes on TTY\n- `package.json not found (bad installation?)` → #961 regression, `isBinaryBuild` detection broken\n- Process exits before producing output → generic spawn failure, capture stderr\n\n### Test 3b — Resolver error path (run without `CLAUDE_BIN_PATH`)\n\nQuickly verify the resolver fails loud when nothing is configured:\n\n```bash\n(unset CLAUDE_BIN_PATH; \"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" 2>&1 | tee /tmp/archon-test-no-path.log)\n```\n\n**Pass criteria (when no `~/.archon/config.yaml` configures `claudeBinaryPath`):**\n\n- Error message contains `Claude Code not found`\n- Error message mentions both `CLAUDE_BIN_PATH` and `claudeBinaryPath` as remediation options\n- No `Module not found` stack traces referencing the CI filesystem\n\nIf you *do* have `claudeBinaryPath` set globally, skip this test or temporarily rename `~/.archon/config.yaml`.\n\n### Test 4 — Env-leak guard strips sensitive keys from .env\n\nCreate a second throwaway repo with a fake sensitive key:\n\n```bash\nLEAKREPO=/tmp/archon-test-leak-$(date +%s)\nmkdir -p \"$LEAKREPO\"\ncd \"$LEAKREPO\"\ngit init -q\ngit commit -q --allow-empty -m init\nprintf 'ANTHROPIC_API_KEY=sk-ant-test-fake\\n' > .env\n\"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" 2>&1 | tee /tmp/archon-test-leak.log\n```\n\n**Pass criteria** (current behaviour — the guard **strips**, it does not refuse):\n\n- Output contains a strip line naming the repo and the source file, of the form:\n  `[archon] stripped N keys from <repo> (.env) to prevent target repo env from leaking into Archon processes`\n- `N` equals the number of keys planted (1 for the `.env` above) — a count of `0`\n  means the file was not read at all, which is a real regression\n- The workflow is then allowed to proceed and complete normally\n\nAssert on the strip line — pinned to **this** repo and **this** key count, so a\nstrip from some other directory cannot produce a false pass. Capture the exit\nstatus too: under the strip design the workflow is expected to succeed, so a\nnon-zero exit is its own failure.\n\n```bash\n\"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" > /tmp/archon-test-leak.log 2>&1\nleak_exit=$?\n\n# $LEAKREPO may be a symlinked path (/tmp -> /private/tmp on macOS); the binary\n# logs the resolved form, so compare against that.\nresolved_repo=$(cd \"$LEAKREPO\" && pwd -P)\nexpected=\"[archon] stripped 1 keys from ${resolved_repo} (.env) to prevent target repo env from leaking into Archon processes\"\n\nif [ \"$leak_exit\" -ne 0 ]; then\n  echo \"FAIL: workflow exited $leak_exit — the guard strips and proceeds, it should not abort\"\nelif grep -qxF \"$expected\" /tmp/archon-test-leak.log; then\n  echo \"PASS: env-leak guard stripped exactly the planted key from this repo\"\nelse\n  echo \"FAIL: expected strip line absent. Got:\"\n  grep -F '[archon] stripped' /tmp/archon-test-leak.log || echo \"  (no strip line at all — guard inactive)\"\nfi\n```\n\nAn exact-match assertion is deliberate here. A looser pattern such as\n`stripped [1-9][0-9]* keys .*\\.env` passes on **any** positive count from **any**\npath, so a strip belonging to a different repository — or a count inflated by an\nunrelated `.env` — reads as success while the regression it is meant to catch\ngoes unnoticed.\n\n> **History — do not re-assert the old criteria.** This test previously expected a\n> *refusal* (`Cannot add codebase` / `Cannot run workflow`, non-zero exit), which\n> was the #1036/#1038/#983 behaviour. The current design is the two-layer strip\n> guard: the target repo's `.env` is read, dangerous keys are removed from the\n> environment handed to Archon subprocesses, and the run continues. On the 0.7.1\n> test the old assertions reported FAIL against a binary whose guard was working\n> correctly — it stripped exactly the 1 planted key. Verify the security property\n> (the key never reaches the subprocess), not the obsolete remediation text.\n\n**Common failures:**\n\n- No strip line at all → the guard is not active; the `.env` is reaching subprocesses\n- `stripped 0 keys` → the file was located but not parsed\n- Strip line names the wrong file or repo → path resolution regression\n\nClean up the leak test repo:\n\n```bash\nrm -rf \"$LEAKREPO\"\n```\n\n### Test 5 — Isolation list works (sanity check)\n\nIn the same `$TESTREPO`:\n\n```bash\n\"$BINARY\" isolation list\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- No errors (the list may be empty if no worktrees have been created, which is fine)\n\nThis catches regressions in the isolation subsystem that would not surface from the other tests.\n\n### Test 6 — Cleanup test repos\n\n```bash\nrm -rf \"$TESTREPO\"\n```\n\nFor `curl-vps` path, also clean up any remote test repos created via SSH.\n\n## Phase 5 — Uninstall\n\n**Always run uninstall, even if Phase 4 failed.** The goal is to leave the system in the same state as before the test.\n\n### Path: brew\n\n```bash\nbrew uninstall coleam00/archon/archon\nbrew untap coleam00/archon\n```\n\nVerify the dev binary is still the default:\n\n```bash\nwhich -a archon\n# should show only the ~/.bun/bin/archon path, not a brew path\n\narchon version | head -1\n# should match the dev version captured in Phase 2\n```\n\n**Restore `archon-stable` if it existed before the test** (dual-homebrew pattern — see Phase 2 item 4):\n\n```bash\nif [ -n \"$ARCHON_STABLE_WAS_INSTALLED\" ]; then\n  echo \"Restoring archon-stable (detected before test)...\"\n  brew tap coleam00/archon\n  brew install coleam00/archon/archon\n  BREW_BIN=\"$(brew --prefix)/bin\"\n  if [ -e \"$BREW_BIN/archon\" ]; then\n    mv \"$BREW_BIN/archon\" \"$BREW_BIN/archon-stable\"\n    echo \"archon-stable restored: $(archon-stable version 2>/dev/null | head -1)\"\n  else\n    echo \"WARNING: brew install succeeded but $BREW_BIN/archon missing — check formula\"\n  fi\nfi\n```\n\n> **Note on the restored version**: this reinstalls from whatever the tap currently ships, which is typically the release you just tested (so `archon-stable` ends up at the newly-tested version). That's usually what the operator wants — you just verified the new release works, and you want `archon-stable` pointed at it. If you were testing an older version for back-version QA, the restored `archon-stable` will be the *current* tap formula, not the pre-test version. For that rare case, the operator should re-run `brew-upgrade-archon` manually after the test.\n\n### Path: curl-mac\n\n```bash\nrm -rf \"$INSTALL_DIR\"\n```\n\n### Path: curl-vps\n\n```bash\nssh <target> \"sudo rm -f /usr/local/bin/archon || rm -f \\$HOME/.local/bin/archon\"\n```\n\nOptional: the user may want to LEAVE the VPS binary installed for ongoing QA. Ask before removing.\n\n## Phase 6 — Report\n\nProduce a structured report with:\n\n- **Header**: release version tested, install path, timestamp, SHA256 of the tested binary\n- **Environment**: dev binary path + version (proof the dev install was not disturbed)\n- **Test results table**: one row per test with PASS / FAIL / SKIP\n- **Captured output**: for any FAIL, include the exact command, exit code, and last 20 lines of stderr/stdout\n- **Overall verdict**: PASS if all tests passed, FAIL if any test failed\n- **Next steps**: if FAIL, suggest concrete actions (file a hotfix issue, re-tag, check the build workflow, etc.)\n\nExample PASS report:\n\n```\nTest Release Report — archon v0.3.1 via brew\n────────────────────────────────────────────\nTested at:    2026-04-08 15:42 UTC\nBinary SHA:   e62eb73547b3740d56f242859b434a91d3830360a0d18f14de383da0fd7a0be6\nBinary path:  /opt/homebrew/Cellar/archon/0.3.1/bin/archon\nDev binary:   /Users/rasmus/.bun/bin/archon → ../install/.../cli.ts (unchanged)\n\n  [PASS]  Test 1  version reports 0.3.1, Build: binary, commit abc1234\n  [PASS]  Test 2  workflow list returned 21 bundled workflows\n  [PASS]  Test 3  workflow run archon-investigate produced output\n  [PASS]  Test 4  env-leak guard stripped exactly the planted key\n  [PASS]  Test 5  isolation list executed without errors\n  [PASS]  Cleanup brew uninstall + untap clean, dev binary unchanged\n\nOverall: PASS\n\nThis release is safe to announce. Next steps:\n  - Update release notes on GitHub if not done already\n  - Announce on whatever channels you use\n```\n\nExample FAIL report:\n\n```\nTest Release Report — archon v0.3.1 via curl-vps\n────────────────────────────────────────────────\nTested at:    2026-04-08 15:42 UTC\nBinary SHA:   0cf83e15e6af228e3c3473467ca30fa7525b6d7069818d85f97a115ea703d708\nBinary path:  user@vps:/usr/local/bin/archon\nDev binary:   /Users/rasmus/.bun/bin/archon (unchanged)\n\n  [PASS]  Test 1  version reports 0.3.1, Build: binary\n  [FAIL]  Test 2  workflow list returned 0 workflows\n\n    Command:  archon workflow list\n    Exit:     0\n    Output:\n      Discovering workflows in: /tmp/archon-test-repo-1712590923\n      Found 0 workflow(s):\n\n  [SKIP]  Test 3  SDK test skipped because Test 2 failed\n  [SKIP]  Test 4  env-leak guard test skipped because Test 2 failed\n  [PASS]  Test 5  isolation list executed without errors\n  [PASS]  Cleanup VPS binary removed\n\nOverall: FAIL\n\nLikely cause: bundled workflows were not embedded in the binary.\nCheck the build workflow for missing asset embedding, or verify that\nBUNDLED_WORKFLOWS in packages/workflows/src/defaults/bundled-defaults.ts\nwas populated at build time.\n\nNext steps:\n  1. File a P0 hotfix issue with the captured output\n  2. Do NOT announce v0.3.1 until the hotfix ships as v0.3.2\n  3. Consider adding a CI guard that blocks releases if BUNDLED_WORKFLOWS is empty\n```\n\n## Key behaviors\n\n- **Never touch the dev `bun link` binary.** Always use the installed binary path for Phase 4 tests. Verify before and after.\n- **Clean up on failure.** If Phase 4 fails mid-way, still run Phase 5 so the next run starts clean.\n- **Capture SHA256 immediately after install.** This lets bug reports reference the exact artifact under test.\n- **Explicit confirmation before install.** Never surprise the user by installing a second binary.\n- **Report the dev binary state in both preamble and postamble.** Proof that the test did not disturb the dev environment.\n- **Exit non-zero if any test failed.** The skill should propagate failure so automated wrappers (CI, scripts) can detect it.\n\n## Related\n\n- `scripts/build-binaries.sh` — builds the binary artifacts that end up in releases\n- `.github/workflows/release.yml` — builds and publishes the binary on tag push\n- `homebrew/archon.rb` — Homebrew tap formula (updated per release)\n- `scripts/install.sh` — the curl install script\n- `scripts/install-local.sh` / `install-local.ps1` — local-file install harnesses (for pre-release QA of binaries built from a branch, not from GitHub releases)\n- `/release` skill — the release procedure itself (opposite side of the flow)\n",".claude/skills/triage/SKILL.md":"---\nname: triage\ndescription: |\n  Triage GitHub issues by applying type, effort, priority, and area labels.\n  Runs in an isolated context to avoid polluting the main conversation with\n  issue details. Delegates to a specialized triage agent with label validation hooks.\nargument-hint: \"[unlabeled|all|N|N-M]\"\ndisable-model-invocation: true\ncontext: fork\nagent: triage-agent\nallowed-tools: Bash(gh *), Read, Glob, Grep\n---\n\n# Triage GitHub Issues\n\nTriage issues for this repository by applying appropriate labels.\n\n## Repository Context\n\n- **Current repo**: !`gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo \"unknown\"`\n- **Open issues**: !`gh issue list --state open --json number --jq 'length' 2>/dev/null || echo \"?\"`\n- **Existing labels**: !`gh label list --json name -q '.[].name' 2>/dev/null | head -20 || echo \"none found\"`\n\n---\n\n## Scope\n\nDetermine which issues to triage based on the arguments: **$ARGUMENTS**\n\n| Argument | Behavior |\n|----------|----------|\n| (empty) | Only unlabeled issues (default) |\n| `unlabeled` | Only issues without any labels |\n| `all` | All open issues |\n| `N` | Specific issue (e.g., `67`) |\n| `N-M` | Range of issues inclusive (e.g., `60-67`) |\n\n## Process\n\n1. **Fetch available labels** — run `gh label list --json name,description` to understand\n   the label taxonomy. Labels are organized into type, effort, priority, and area categories.\n\n2. **Fetch target issues** — based on the scope above. For each issue, fetch the full body:\n   ```bash\n   gh issue view {number} --json number,title,body,labels\n   ```\n\n3. **For each issue**:\n   - Read the title and full body carefully\n   - If needed, explore the codebase (`Glob`, `Grep`, `Read`) to understand the affected code\n   - Classify: one type, one effort, one priority, one or more areas\n   - Track relationships with other issues (duplicates, related, blocking)\n   - Apply labels:\n     ```bash\n     gh issue edit {number} --add-label \"type,effort/level,P#,area.domain\"\n     ```\n   - Skip issues that already have complete labeling (type + effort + priority + area)\n   - For partially labeled issues, only add missing label categories\n\n4. **Output a triage summary**:\n\n   ```\n   ## Triage Summary\n\n   | Issue | Title | Labels Applied | Reasoning |\n   |-------|-------|----------------|-----------|\n   | #67 | ... | bug, effort/low, P1, core.config | ... |\n\n   **Totals:**\n   - Issues triaged: X\n   - Already labeled (skipped): Y\n   - By priority: P0(n), P1(n), P2(n), P3(n)\n\n   ## Relationships Discovered\n\n   | Issues | Relationship | Notes |\n   |--------|--------------|-------|\n   | #61, #62 | Related | Both involve config/logging UX |\n   ```\n\n## Rules\n\n- **Don't hardcode labels** — always fetch current labels first, they may change\n- **Respect existing labels** — don't remove labels, only add missing ones\n- **Check issue body** — titles alone aren't enough context\n- **Use the codebase** — if understanding a relationship requires seeing how modules connect, look\n- **When uncertain, note it** — flag ambiguous issues in the summary rather than guessing\n",".claude/skills/triage/workshop-guide.md":"# Triage — Workshop Guide\n\n## What This Skill Demonstrates\n\nA command upgraded to a skill + custom agent pair, showcasing **context forking**,\n**custom agent delegation**, **prompt hooks as guardrails**, and **restricted toolsets**.\n\n**Full demo steps**: See [part2-guide.md](../../workshop/part2-guide.md) — Feature 7.\n\n## Architecture\n\n```\nUser invokes /triage 42\n        │\n        ▼\n┌─────────────────────┐\n│  SKILL.md           │  context: fork + agent: triage-agent\n│  (entry point)      │  Injects repo context via !`command`\n│  allowed-tools:     │  Restricts tools to gh CLI + read-only\n│    Bash(gh *)       │\n│    Read, Glob, Grep │\n└────────┬────────────┘\n         │ forks into isolated context\n         ▼\n┌─────────────────────┐\n│  triage-agent.md    │  Custom agent with PostToolUse hook\n│  (.claude/agents/)  │  Hook: type \"prompt\" validates every\n│                     │  gh issue edit command to ensure\n│  model: sonnet      │  all 4 label categories are present\n│  hooks: PostToolUse │\n└─────────────────────┘\n         │\n         ▼\n   Summary returned to main conversation\n   (intermediate tool calls discarded)\n```\n\n## Features to Walk Through\n\n### 1. `context: fork` — Isolated Execution\n\n**What is it?** A skill frontmatter field that runs the skill in a **new isolated\nsubagent context** instead of inline in the main conversation. The skill body\nbecomes the task prompt for the subagent. When the subagent finishes, only its\nfinal response returns to the main conversation — all intermediate tool calls,\nfile reads, and reasoning are discarded.\n\n**Key properties:**\n- The forked context has **no access** to the main conversation history\n- Only the **final summary** flows back — intermediate work is discarded\n- `CLAUDE.md` files are still loaded in the forked context; this project's root file is only a pointer to `AGENTS.md`, which holds the project rules\n- The fork cannot spawn further subagents (no nesting)\n- Forked skills must contain **concrete tasks**, not just reference material\n\n**In this skill**: Triaging 20 issues could consume 50K+ tokens of context\n(issue bodies, label lists, grep results). Without forking, all of that pollutes\nthe main conversation. With forking, only the structured triage summary returns.\n\n### 2. `agent: triage-agent` — Custom Agent Delegation\n\n**What is it?** When a skill has `context: fork`, the `agent:` field controls\nwhich agent type runs the forked context. Options:\n\n| Agent | Model | Tools | Use case |\n|-------|-------|-------|----------|\n| `Explore` | Haiku (fast, cheap) | Read-only | File search, codebase exploration |\n| `Plan` | Inherits from main | Read-only | Planning and research |\n| `general-purpose` | Inherits from main | All tools | Complex multi-step tasks |\n| Any custom agent | Whatever agent defines | Whatever agent defines | Domain-specific work |\n\nCustom agents are `.md` files in `.claude/agents/` with their own frontmatter\n(model, tools, hooks, memory, permissions) and a markdown body that serves as\nthe system prompt.\n\n**In this skill**: Instead of `agent: general-purpose`, this skill delegates to\n`triage-agent` — a custom agent defined in `.claude/agents/triage-agent.md` with:\n- A triage specialist system prompt (label taxonomy, classification rules)\n- Its own PostToolUse hook for label validation\n- Restricted tool access\n\n**Teaching moment**: Skills define *what* to do (scope, context, arguments).\nAgents define *how* to do it (persona, tools, guardrails). Separating them\nmakes both composable — the same agent could be used by multiple skills.\n\n### 3. `type: \"prompt\"` Hook — LLM as Guardrail\n\n**What is it?** One of four hook handler types. A prompt hook sends the event\ncontext to an LLM (Haiku by default) for a single-turn yes/no evaluation.\nThe LLM must return `{\"ok\": true}` to allow the action or\n`{\"ok\": false, \"reason\": \"...\"}` to block it.\n\n**How it differs from the other hook types:**\n- `type: \"command\"` — runs a shell script, blocks via exit code 2\n- `type: \"http\"` — POSTs JSON to a URL, blocks via JSON response\n- `type: \"prompt\"` — asks an LLM to evaluate, blocks via `{\"ok\": false}`\n- `type: \"agent\"` — spawns a subagent with Read/Grep/Glob to verify conditions\n\nPrompt hooks are ideal for **semantic validation** — cases where the decision\nrequires understanding intent, not just pattern matching.\n\n**In this skill**: The triage agent has a PostToolUse hook on Bash. When it\ndetects a `gh issue edit --add-label` command, the LLM verifies all four label\ncategories (type, effort, priority, area) are present.\n\n**What happens if validation fails**: The LLM returns\n`{\"ok\": false, \"reason\": \"Missing effort label\"}` and Claude receives that\nfeedback. It can fix the label application before moving to the next issue.\n\n**Smart filtering**: The prompt instructs the LLM to return `{\"ok\": true}`\nfor non-label commands (`gh issue list`, `gh label list`) — no false positives.\n\n### 4. `allowed-tools` — Restricted Toolset\n\n**What is it?** A skill frontmatter field that controls which tools Claude can\nuse **without per-use permission prompts** when the skill is active. It acts as\nboth an allowlist (pre-approves listed tools) and a restriction (only these\ntools are available in the forked context).\n\n**Wildcard patterns** are supported:\n- `Bash(gh *)` — only `gh` subcommands, no arbitrary shell\n- `Bash(npm *)` — only npm commands\n- `Read, Glob, Grep` — read-only codebase access\n\n**In this skill**: `allowed-tools: Bash(gh *), Read, Glob, Grep` means the\nagent can interact with GitHub and read the codebase, but can't run arbitrary\nshell commands, write files, or edit code. This is a security boundary.\n\n### 5. `disable-model-invocation: true`\n\n**What is it?** Prevents Claude from auto-invoking this skill. By default, Claude\nreads skill descriptions and can invoke them when it determines they're relevant.\nSetting this to `true` means only the user can trigger it via `/triage`.\n\n**In this skill**: Triage modifies GitHub issues (side effects). Only the user\nshould decide when to apply labels.\n\n### 6. Dynamic Context Injection (`!`command``)\n\n**What is it?** Shell commands in the skill body using `!`command`` syntax that\nexecute as preprocessing — before Claude sees the prompt. The command's stdout\nreplaces the placeholder. This is not tool use; it happens at skill load time.\n\n**In this skill**: Three commands inject live repo data:\n- Repository name (`gh repo view`)\n- Open issue count (`gh issue list | length`)\n- Existing label taxonomy (`gh label list`)\n\nThe agent starts with real context instead of instructions to go fetch it.\n\n## Live Demo Steps\n\n1. **Show the architecture** — open both files side by side:\n   - `.claude/skills/triage/SKILL.md` (the skill entry point)\n   - `.claude/agents/triage-agent.md` (the specialist agent)\n\n2. **Point out the delegation chain**: skill → `context: fork` → `agent: triage-agent`\n\n3. **Invoke on a single issue**: `/triage 42`\n   - Show the `statusMessage` spinner: \"Validating label application...\"\n   - Point out: the main conversation stays clean while triage happens in the fork\n\n4. **Show the summary** that returns to the main conversation\n   - All the intermediate work (fetching issues, reading code, applying labels) is gone\n   - Only the structured summary survives\n\n5. **Show the hook validation** — if a label application was incomplete, the prompt\n   hook would have caught it and Claude would have self-corrected\n\n## Comparison: Before vs After\n\n| | Old Command | New Skill + Agent |\n|---|---|---|\n| Format | `.claude/commands/archon/triage.md` | Skill + `.claude/agents/triage-agent.md` |\n| Execution | Inline (pollutes main context) | Forked (isolated context window) |\n| Validation | None | Prompt hook validates every label application |\n| Tool access | All Bash commands | `Bash(gh *)` only — no arbitrary shell |\n| Agent persona | Generic | Specialized triage agent with domain knowledge |\n| Context cost | All issue data stays in conversation | Only summary returns |\n\n## Talking Points\n\n- \"Context forking is about information hygiene — the triage work happens in a disposable context.\"\n- \"Custom agents let you embed domain expertise. The triage rules live in the agent, the scope lives in the skill.\"\n- \"The prompt hook is an LLM guardrail — it catches incomplete label applications before they reach GitHub.\"\n- \"`Bash(gh *)` is a security boundary — the agent can interact with GitHub but can't run arbitrary commands.\"\n- \"This pattern — skill as entry point, custom agent as specialist — is how you build composable workflows.\"\n","AGENTS.md":"# Working on Archon\n\nArchon is a self-hostable, governed agentic automation engine. It runs workflows that mix deterministic steps, AI agents, human gates, and audit trails. Coding automation is its most mature surface, but the engine is intended for general business operations too.\n\nThis file is the canonical project guidance for coding agents. Keep it short, durable, and about judgment. Put changing product direction in [`.archon/direction.md`](.archon/direction.md), workflow-language design in [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md), aspirational engineering craft and review values in [`.archon/engineering.md`](.archon/engineering.md), user documentation in the docs site, and machine-checkable rules in code, types, tests, lint, or CI.\n\n## Read before changing code\n\n- Treat the source, its call sites, and its tests as the current evidence. The request defines the goal and constraints, but a prompt may describe old code.\n- Read every relevant consumer before changing a producer, contract, path, identifier, schema, or generated artifact.\n- Reproduce bugs when practical. If reproduction is impossible, establish another concrete causal chain before editing.\n- Do not trust a passing test by itself. Check that it exercises the behavior it claims to protect.\n- A question is read-only. Explain first; change code only when the user asks for a change.\n- Do not stop at editing. Finish the requested change and verify the outcome.\n- If the implementation keeps getting more complicated, stop and reconsider the model. Solve the underlying problem.\n\n## Product boundaries\n\n- Archon is a governed automation engine, not a replacement for the coding agents it orchestrates.\n- One install serves one operator or client. Client isolation belongs at the deployment layer. Multiple users within one install are supported and are not multi-tenancy.\n- Keep the conversation layer platform-agnostic. Platform adapters translate transport details; core behavior should not depend on Slack, Telegram, GitHub, Discord, CLI, or Web UI semantics.\n- Keep the core self-hostable. Do not make a proprietary hosted service a runtime dependency.\n- Keep the core small and flexible. Agentic engineering changes too quickly for one monolith to encode every useful workflow or integration; give users stable seams to assemble and extend the rest.\n- Treat bundled workflows as reference implementations, not a frozen compatibility surface.\n- A git worktree separates checkouts; it is not an execution or security sandbox.\n- Consult [`.archon/direction.md`](.archon/direction.md) before making product-scope or architecture-direction decisions.\n\n## Engineering taste\n\n### Prefer the smallest truthful system\n\n- Apply KISS and YAGNI. Do not add configuration, interfaces, lifecycle states, compatibility paths, or guards without a current requirement or concrete failure mode.\n- Before adding machinery, look for dead, redundant, or superseded machinery on the same path that can disappear.\n- Fix drift on the path you touch rather than preserving it for a cleaner diff. Two sites that should agree and do not are a defect, not a diff-size decision.\n- Duplicate small local logic when it keeps ownership clear. Extract only after a pattern has repeated and stabilized.\n- Prefer explicit control flow and narrow typed interfaces over meta-programming, shared mutable state, or hidden coupling.\n- Keep policy, transport, persistence, and execution concerns separate.\n- Consider a focused rewrite when it produces a smaller, clearer result without widening risk.\n\n### Let types carry invariants\n\n- Keep TypeScript strict. Avoid `any` and broad assertions when a sound type can express the state.\n- Two declarations that must agree to stay correct, kept in agreement by discipline rather than by a mechanism, are a present defect and not a future risk. Treat a hand-synced pair as a bug the moment it exists, and as a live one once the sides already disagree. Derive from the owner instead: an import, a derived or mapped type, a generated artifact with an owning script, or an enforced conformance test. A \"keep in sync\" comment is none of those; it records the defect rather than clearing it.\n- Import external SDK types rather than restating them. Where a boundary genuinely cannot share one definition, the copy owes a conformance test.\n- Use discriminated unions and constructors to make invalid states hard to represent.\n- Keep interfaces narrow. Extend an existing interface only when the new method belongs to the same responsibility.\n\n### Fail clearly\n\n- Fail early on unsupported, ambiguous, or unsafe states. Silent fallback in an agent runtime can waste money or broaden capabilities.\n- Never silently swallow an error or broaden permissions.\n- Translate errors at the boundary that can explain them, while preserving the original evidence for logs and diagnostics.\n- A fallback must be intentional, safe, documented near the branch, and observable enough to debug.\n- Never log credentials, secret values, user-message contents, or unnecessary personal data.\n\n### Write for the next maintainer\n\n- Comments should explain a non-obvious invariant, ownership decision, or reason a tempting alternative is wrong. Do not narrate the code.\n- Delete stale comments and documentation when behavior changes.\n- Use one precise name for each concept. Prefer direct prose, active voice, and concrete mechanisms over slogans or vague summaries.\n- Avoid hardcoded examples that will become false as files, counts, providers, or routes change.\n\n## Agentic-system boundaries\n\n### Natural language is not a wire format\n\n- Let the agent interpret the full natural-language message. Do not reconstruct intent with regexes, keyword lists, exact prose tokens, or hand-written parsers.\n- Put determinism after interpretation: give the agent typed tools, then validate resolved identifiers, arguments, permissions, and invariants at the tool boundary.\n- If exact syntax is required, expose a structured interface such as a CLI flag, typed tool, button action, or schema.\n- In a workflow, prose inside a node is model reasoning. Prose passed between nodes as a token to parse is an invented protocol; use structured output, typed inputs, exit status, or another explicit engine channel.\n- Vendor output is prose too. Classifying git, SDK, or vendor error text with a pattern is a last resort, not a carve-out: prefer the structured channel (exit code, typed error class, `--json` or porcelain output), then agent classification into a typed value, then an honest unclassified failure carrying the original evidence. A match that survives anchors to a machine token embedded in the message (an errno, an error code) and may enrich a report — never gate retry, fallback, or suppression, where a vendor rewording silently changes behavior.\n\n### Do not guess lifecycle ownership\n\n- A process must not mark non-terminal work failed, cancelled, or abandoned when it cannot distinguish a live owner from an orphaned process.\n- Surface ambiguous ownership and give the operator an explicit action.\n- Timers remain appropriate for recoverable operations such as retry backoff, subprocess timeouts, and cleanup of already-terminal data.\n- Terminal state transitions and completion signals belong to the engine, not to workflow-specific scripts that reinterpret run state.\n\n### Project guidance should be available, not sprayed everywhere\n\n- This `AGENTS.md` is the project-guidance channel for agent nodes. Provider integrations should preserve that contract through the provider's native context mechanism.\n- `AGENTS.md` may point to focused project context such as direction or language-design documents.\n- Do not inject every project document into every node. A node should receive only the context its responsibility requires.\n- The engine and reusable workflow packs must not hardcode a project's context paths.\n\n### Preserve provider configuration; scope capabilities\n\n- Provider integrations must preserve the provider's native user and project configuration, including its global and project guidance files. The user's provider configuration owns whether those sources load; Archon must not replace a provider home directory or add a parallel context switch.\n- Workflow AI nodes start without ambient skills, plugins, or MCP servers. A node receives only the skills, plugins, and MCP servers it explicitly names. Enforce that boundary without hiding unrelated provider settings.\n- Pi extensions are user-owned provider configuration, not Archon-managed plugins. Load them as Pi configures them; any tools or MCP clients an extension exposes are the user's responsibility.\n- Leave native prompt templates and commands under provider control unless the provider automatically advertises them to the model as a skill-like capability. In that case they follow the same explicit node-scoping rule as skills and plugins.\n- Preserve the selected agent provider's native authentication contract. A Claude Code subscription runs through the Claude Agent SDK; using an Anthropic model through another provider is not Claude Code subscription support. Do not reroute or relabel provider identity to reuse credentials.\n- Provider-boundary tests must prove both halves of the contract: native settings and guidance remain visible, while undeclared skills, plugins, and MCP servers remain unavailable and explicitly named capabilities load.\n\n## Workflow-language boundary\n\nThe governing rule is: **YAML coordinates. Code computes. Agents judge.** Read [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md) before changing the YAML surface. Read [`.archon/workflows/sdlc/CONSTITUTION.md`](.archon/workflows/sdlc/CONSTITUTION.md) before changing the SDLC pack.\n\n- YAML should expose only what the engine must see to govern a run: topology, gates, joins, retries, sessions, artifacts, durable waits, and reusable structure.\n- Computation belongs inside a `bash:`, `script:`, or `prompt:` node. Choosing a script instead of a prompt is an ordinary reliability and maintainability decision, not a constitutional rule.\n- A new YAML field or expression feature must add governance value that existing node bodies and wiring cannot provide.\n- Do not grow `when:` one operator at a time. Compute a complex decision in a node and gate on structured output.\n- Composition must preserve a graph the engine can validate and govern. Runtime-created independent work belongs in a child run, not hidden dynamic YAML structure.\n- Provider capabilities normally belong in provider config, tiers, or aliases. A new node field must earn genuine per-node variance.\n- Implicit behavior must be documented, defeatable where appropriate, visible, and fail-safe.\n\n## Architecture and ownership\n\n- Preserve package boundaries. Inspect package manifests and current imports before adding a dependency; do not rely on a copied package inventory.\n- Provider SDK dependencies and SDK-specific option translation belong in `@archon/providers`.\n- Workflow execution depends on injected contracts, not on core database or adapter implementations.\n- Platform-specific parsing and authorization belong inside the owning adapter. Core receives normalized intent and identity.\n- The Web client consumes generated API types through its local API layer; it does not import server or workflow runtime packages.\n- The engine (`packages/`) and the bundled SDLC pack (`.archon/workflows`, `.archon/scripts`) don't import each other's code, in source or in tests. They meet through the workflow language, the CLI and the provider and forge contracts.\n- Shared schemas are the source of truth for persisted and API shapes. Keep OpenAPI generation and runtime validation on the same schema path.\n- Use the type system, schema validation, and package boundaries to enforce architecture before adding prose rules.\n\n## Data and compatibility\n\n- Database schema evolution is additive-only. There is no migration ledger or version gate, and older binaries may open the same database.\n- Never rename, retype, or drop a shipped table or column. A new `NOT NULL` column needs a default that remains valid for older writers.\n- Keep SQLite and PostgreSQL schemas aligned unless a narrow, documented exception is already enforced.\n- Test upgrades from shipped schema shapes, not only fresh database creation.\n- Keep generated or mirrored artifacts synchronized through their owning scripts. Find those scripts in `package.json` and adjacent tests instead of copying a list here.\n- Persisted identifiers, paths, and wire values are contracts. Trace them through every reader before changing their meaning.\n\n## Git and workspace safety\n\n- `main` is the release branch and `dev` is the working base. Never commit directly to either for feature work, and never force-push `main` or `master`.\n- Preserve user changes, including untracked files. Do not use `git clean -fd`, destructive resets, or broad cleanup commands on user-owned workspaces.\n- Let git report conflicts and dirty state. Do not hide or automatically resolve a state the operator owns.\n- Hard reset is allowed only for an explicit reset mode on an Archon-owned checkout.\n- Run artifacts, logs, and state belong outside the repository under the Archon workspace. Do not stage them.\n- When stopping a process, target the recorded PID or exact bound port. Never kill by a broad process-name match.\n- Keep commits and pull requests focused. Do not mix unrelated cleanup into the requested outcome.\n\n## Running Archon workflows\n\nThe input is the contract. A run is only as accurate as the brief it starts from, and a\nworkflow cannot recover from a premise that was never stated — it will produce a confident,\nwell-formed answer to the wrong question. Treat writing the input as the work, not the\npreamble to it.\n\nBefore launching a run, the input — an issue body, a message, a document, whatever the run\nreads — must state:\n\n- the problem to solve;\n- why it is worth solving;\n- why now;\n- the desired outcome;\n- the invariants that must hold;\n- what acceptance looks like.\n\nSolution steering is optional and belongs last, but omitting it is a decision: the run then\nchooses its own approach. State a constraint you actually hold — reuse this primitive, no new\ndependency, migrate rather than rewrite — because an unstated preference cannot be honoured.\nNaming an implementation *before* the problem is settled narrows the run to your first guess.\n\nDo not launch a run against an input missing any of the six. Fix the input first, and say\nwhat you changed — a thin brief is cheaper to correct before a run than after one.\n\n## Tests and validation\n\n- Add tests for meaningful behavior and failure modes. Do not preserve deleted features through regression-test clutter.\n- The engine tests what it owns (the workflow language, script nodes, state, waits, resume) with its own minimal fixtures, never with a pack workflow. The pack tests only its own scripts at a tool boundary and never retests the engine's behavior.\n- Prefer deterministic, cheap tests. Remove accidental I/O and unnecessary subprocesses before increasing timeouts.\n- Remove test temp trees with `removeTempTree` or `trackTempRoots`: from `@archon/paths/test-utils` in engine and `scripts/` tests, and from the pack's own `.archon/scripts/__tests__/temp-roots.ts` in pack tests.\n- Spawn a subprocess only when the subprocess is the subject under test. To suppress an inherited env key for a Bun child, pass it as `''`; deleting it allows `.env` loading to restore it.\n- Bun's module mocks pollute the process cache. Use the package test scripts that preserve isolation; do not run `bun test` from the repository root.\n- Run lint through `bun run lint`. Besides Biome, it runs the repository checks Biome cannot express, such as the workflow persistence boundary.\n- Check what you changed: run the tests that cover it, lint and type-check the packages you touched, and run `bun run check:ratchet` before you push. CI refuses a change the ratchet refuses, and it takes seconds locally against a fetched `origin/dev`.\n- CI is the full validation. Don't run `bun run validate` to finish a change. `scripts/validate.ts` owns the check list and CI selects from it; workflow fixtures run locally only through `--only`.\n- Schema changes also need the PostgreSQL upgrade check. [`CONTRIBUTING.md`](CONTRIBUTING.md) lists it with its command.\n- For visual or runtime behavior, add direct evidence when static tests cannot prove the outcome.\n- Destructive verification, including DDL, migrations, and data writes, runs only against a scratch database you create and drop. A configured live DSN is read-only at most; when only a live resource exists, stop and surface it to the operator.\n\n## Pull requests\n\n- Branch from `dev` and open the pull request back to `dev`.\n- Use [`.github/pull_request_template.md`](.github/pull_request_template.md). Keep its required sections, remove instructional comments and irrelevant conditional sections, and describe the problem before the implementation.\n- Review the complete diff against the actual base branch before opening the pull request.\n- Link the source issue with a closing keyword when the work has one.\n- Keep titles concise and outcome-focused. Do not add AI attribution to commits or pull requests.\n\n## Find current facts\n\nDo not expand this file with copied reference material. Use the owning source:\n\n- Product direction: [`.archon/direction.md`](.archon/direction.md)\n- Code structure, flows, extension points and enforced boundaries: [`.archon/architecture.md`](.archon/architecture.md)\n- Workflow-language design: [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md)\n- Engineering craft and review values: [`.archon/engineering.md`](.archon/engineering.md)\n- CLI: [`packages/docs-web/src/content/docs/reference/cli.md`](packages/docs-web/src/content/docs/reference/cli.md) and `archon --help`\n- Workflow authoring: [`packages/docs-web/src/content/docs/reference/workflow-language.md`](packages/docs-web/src/content/docs/reference/workflow-language.md) and workflow schemas\n- Database behavior: [`packages/docs-web/src/content/docs/reference/database.md`](packages/docs-web/src/content/docs/reference/database.md), migrations, and adapter tests\n- Available commands and generated-file checks: root and package `package.json` scripts\n","CLAUDE.md":"Agent rules: read @AGENTS.md\n","packages/adapters/src/community/chat/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fadapters%2Fsrc%2Fcommunity%2Fchat%2FREADME.md -->","packages/adapters/src/community/forge/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fadapters%2Fsrc%2Fcommunity%2Fforge%2FREADME.md -->","packages/chat-contract/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fchat-contract%2FREADME.md -->","packages/docs-web/public/brand/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fpublic%2Fbrand%2FREADME.md -->","packages/docs-web/src/content/docs/adapters/community/discord.md":"---\ntitle: Discord\ndescription: Connect Archon to Discord for AI coding assistance in servers and DMs.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\n:::note\nDiscord is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to Discord so you can interact with your AI coding assistant from any Discord server or DM.\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Discord account\n- \"Manage Server\" permission on the Discord server you want to add the bot to\n\n## Create Discord Bot\n\n1. Visit [Discord Developer Portal](https://discord.com/developers/applications)\n2. Click \"New Application\" > Enter a name > Click \"Create\"\n3. Go to the \"Bot\" tab in the left sidebar\n4. Click \"Add Bot\" > Confirm\n\n## Get Bot Token\n\n1. Under the Bot tab, click \"Reset Token\"\n2. Copy the token (starts with a long alphanumeric string)\n3. **Save it securely** -- you won't be able to see it again\n\n## Enable Message Content Intent (Required)\n\n1. Scroll down to \"Privileged Gateway Intents\"\n2. Enable **\"Message Content Intent\"** (required for the bot to read messages)\n3. Save changes\n\n:::caution\nSkipping this step causes Discord to reject the bot's connection with\n`Used disallowed intents`. Archon will log\n`discord.start_failed_continuing_without_adapter` and keep the rest of\nthe server running, but the Discord adapter will be unavailable until\nthe intent is enabled and the server is restarted.\n:::\n\n## Invite Bot to Your Server\n\n1. Go to \"OAuth2\" > \"URL Generator\" in the left sidebar\n2. Under \"Scopes\", select:\n   - `bot`\n3. Under \"Bot Permissions\", select:\n   - Send Messages\n   - Read Message History\n   - Create Public Threads (optional, for thread support)\n   - Send Messages in Threads (optional, for thread support)\n4. Copy the generated URL at the bottom\n5. Paste it in your browser and select your server\n6. Click \"Authorize\"\n\n**Note:** You need \"Manage Server\" permission to add bots.\n\n## Set Environment Variable\n\n```ini\nDISCORD_BOT_TOKEN=your_bot_token_here\n```\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users, enable Developer Mode in Discord:\n1. User Settings > Advanced > Enable \"Developer Mode\"\n2. Right-click on users > \"Copy User ID\"\n3. Add to environment:\n\n```ini\nDISCORD_ALLOWED_USER_IDS=123456789012345678,987654321098765432\n```\n\n## Configure Streaming Mode (Optional)\n\n```ini\nDISCORD_STREAMING_MODE=batch  # batch (default) | stream\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Configure Mention Requirement (Optional)\n\nBy default the bot only activates in servers when @mentioned (DMs are exempt). On single-user or private servers you can opt out so the bot responds to any authorized message:\n\n```ini\nDISCORD_REQUIRE_MENTION=false  # true (default) | false\n```\n\nOnly the literal value `false` disables the mention requirement. Mentions that are present are still stripped from the message.\n\n## Usage\n\nSelect a registered project by submitting `/setproject <name>` in the conversation or thread where you want to work. To register a local directory first, submit `/register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\nThe bot responds to:\n- **Direct Messages**: Just send messages directly\n- **Server Channels**: @mention the bot (e.g., `@YourBotName help me with this code`) — or any authorized message when `DISCORD_REQUIRE_MENTION=false`\n- **Threads**: Bot maintains context in thread conversations\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","packages/docs-web/src/content/docs/adapters/community/gitea.md":"---\ntitle: Gitea\ndescription: Connect Archon to a Gitea instance for issue and PR automation.\ncategory: adapters\narea: adapters\naudience: [operator]\nsidebar:\n  order: 6\n---\n\n:::note\nGitea is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to a self-hosted Gitea instance so you can interact with your AI coding assistant from Gitea issues and pull requests.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Gitea instance with API access enabled\n- A Gitea personal access token (or dedicated bot account token)\n- Public endpoint for webhooks (or a tunnel for local development)\n\n:::tip\n`GITEA_TOKEN` also enables authenticated cloning of private Gitea and Forgejo repositories. If you set it for the adapter, clone authentication works automatically — no extra configuration needed.\n:::\n\n## Step 1: Create a Gitea Token\n\n1. Log in to your Gitea instance\n2. Go to **Settings > Applications > Manage Access Tokens**\n3. Create a new token with repository read/write permissions\n4. Copy the token -- you will need it for Step 3\n\n## Step 2: Generate a Webhook Secret\n\nOn Linux/Mac:\n```bash\nopenssl rand -hex 32\n```\n\nOn Windows (PowerShell):\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret for Steps 3 and 4.\n\n## Step 3: Set Environment Variables\n\n```ini\nGITEA_URL=https://gitea.example.com\nGITEA_TOKEN=your_personal_access_token\nGITEA_WEBHOOK_SECRET=your_secret_from_step_2\n```\n\nAll three variables are required. The adapter starts automatically when all three are set.\n\n**Optional variables:**\n\n```ini\n# Restrict who can trigger the bot (comma-separated usernames, case-insensitive)\nGITEA_ALLOWED_USERS=alice,bob\n\n# Custom @mention name (defaults to BOT_DISPLAY_NAME, then \"Archon\")\nGITEA_BOT_MENTION=archon\n```\n\n## Step 4: Configure Gitea Webhook\n\nGo to your repository settings in Gitea:\n- Navigate to **Settings > Webhooks > Add Webhook > Gitea**\n\n**Webhook Configuration:**\n\n| Field | Value |\n|-------|-------|\n| **Target URL** | `https://your-domain.com/webhooks/gitea` |\n| **HTTP Method** | `POST` |\n| **Content Type** | `application/json` |\n| **Secret** | Paste the secret from Step 2 |\n| **Events** | Issues, Issue Comments, Pull Requests, Pull Request Comments |\n\nClick **Add Webhook** and use the **Test Delivery** button to verify connectivity.\n\nWebhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** and are not processed.\n\n## Usage\n\nInteract by @mentioning the bot in issue or PR **comments**:\n\n```\n@archon can you analyze this bug?\n@archon review this implementation\n@archon /workflow run investigate \"why does the auth flow reject valid tokens?\"\n```\n\n**First mention behavior:**\n- Reuses an existing local registration at its saved checkout path. Otherwise, registers and clones the repository to `~/.archon/workspaces/`\n- Detects and loads commands from `.archon/commands/` if present\n- Injects full issue/PR context (title, description, labels) for the AI assistant\n\n**Subsequent mentions:**\n- Resumes the existing conversation\n- Maintains full context across comments\n\n:::note\nOnly comments trigger the bot. @mentions in issue or PR descriptions are ignored -- descriptions often contain example commands or documentation that are not intended as bot invocations.\n:::\n\n## How It Works\n\nThe Gitea adapter is a webhook-based forge adapter, similar to the GitHub adapter:\n\n- **Transport**: Receives HTTP POST webhooks from Gitea\n- **Signature verification**: HMAC SHA-256 over the exact received bytes, using the `X-Gitea-Signature` header\n- **Streaming mode**: Always batch (assembles the assistant reply after processing; long replies may span multiple comments; see [batch mode](/reference/configuration/#batch-mode) for filtering and fallback behavior)\n- **Conversation ID format**: `owner/repo#number` for issues, `owner/repo!number` for PRs\n- **Self-loop prevention**: Bot comments include a hidden HTML marker (`<!-- archon-bot-response -->`) to avoid re-triggering on its own messages\n- **Retry logic**: Transient network errors (timeouts, connection resets) are retried up to 3 times with exponential backoff\n\n### Close/Merge Cleanup\n\nWhen an issue is closed or a PR is merged/closed, the adapter automatically cleans up any associated worktree isolation environment.\n\n## Adding More Repositories\n\nAdd a webhook with the same secret to each repository you want the bot to monitor. The webhook secret must be identical across all repos pointing to the same Archon instance.\n\n## Further Reading\n\n- [Configuration](/reference/configuration/) -- Full environment variable reference\n- [Security](/reference/security/) -- Webhook verification and authorization details\n","packages/docs-web/src/content/docs/adapters/community/gitlab.md":"---\ntitle: GitLab\ndescription: Connect Archon to GitLab for AI coding assistance in issues and merge requests.\ncategory: adapters\narea: adapters\naudience: [operator]\nsidebar:\n  order: 7\n---\n\n:::note\nGitLab is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to a GitLab instance (gitlab.com or self-hosted) so you can interact with your AI coding assistant from issues and merge requests.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- GitLab project with issues and merge requests enabled\n- GitLab Personal Access Token or Project Access Token with `api` scope\n- Public endpoint for webhooks (see ngrok setup below for local development)\n\n:::tip\n`GITLAB_TOKEN` also enables authenticated cloning of private GitLab repositories. If you set it for the adapter, clone authentication works automatically — no extra configuration needed.\n:::\n\n## Step 1: Create a GitLab Access Token\n\n### Personal Access Token (recommended for getting started)\n\n1. Go to **GitLab → User Settings → Access Tokens**\n2. Create a token with:\n   - **Name**: `archon`\n   - **Scopes**: `api`\n   - **Expiration**: Set as needed\n3. Copy the token (starts with `glpat-`)\n\n### Project Access Token (recommended for production)\n\n1. Go to **Project → Settings → Access Tokens**\n2. Create a token with:\n   - **Role**: Developer or Maintainer\n   - **Scopes**: `api`\n3. This creates a bot user scoped to the project\n\n## Step 2: Generate Webhook Secret\n\n```bash\nopenssl rand -hex 32\n```\n\nWindows (PowerShell):\n\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret — you'll need it for steps 3 and 4.\n\n## Step 3: Expose Local Server (Development Only)\n\n```bash\nngrok http 3090\n# Copy the HTTPS URL (e.g., https://abc123.ngrok-free.app)\n```\n\nFor production, use your deployed server URL directly.\n\n## Step 4: Configure GitLab Webhook\n\nNavigate to **Project → Settings → Webhooks → Add new webhook**:\n\n| Field | Value |\n|-------|-------|\n| **URL** | `https://your-domain.com/webhooks/gitlab` |\n| **Secret token** | The secret from Step 2 |\n| **Triggers** | Enable: `Comments`, `Issues events`, `Merge request events` |\n| **SSL verification** | Enable (recommended) |\n\nClick \"Add webhook\" and use **Test → Note events** to verify.\n\n:::note\nGitLab uses a plain secret token in the `X-Gitlab-Token` header (not HMAC like GitHub). The token must match your `GITLAB_WEBHOOK_SECRET` exactly.\n:::\n\n## Step 5: Set Environment Variables\n\n```ini\nGITLAB_URL=https://gitlab.com\nGITLAB_TOKEN=glpat-your-token-here\nGITLAB_WEBHOOK_SECRET=your-secret-here\n```\n\nOptional:\n\n```ini\nGITLAB_ALLOWED_USERS=alice,bob\nGITLAB_BOT_MENTION=archon\n```\n\nSee the [full environment variable reference](/reference/configuration/) for details.\n\n## Usage\n\nMention your bot in issue or MR comments:\n\n```\n@archon can you analyze this bug?\n@archon /status\n@archon review this implementation\n```\n\n**First mention** reuses an existing local registration at its saved checkout path; otherwise it registers and clones the repository. It detects `.archon/commands/` if present and injects full issue/MR context. For a one-level namespace, the checkout is `~/.archon/workspaces/<group>/<project>/source`. Nested namespaces are flattened after the top-level group: `org/team/api` uses `~/.archon/workspaces/org/team-api/source`. If another project owns that storage name, registration allocates a suffix such as `org/team-api-2`, with its checkout under `~/.archon/workspaces/org/team-api-2/source`. See the [directory reference](/reference/archon-directories/) for the full workspace layout.\n\n**Subsequent mentions** resume the existing conversation with full context.\n\n## Conversation ID Format\n\n| Type | Format | Example |\n|------|--------|---------|\n| Issue | `group/project#iid` | `myteam/api#42` |\n| Merge Request | `group/project!iid` | `myteam/api!15` |\n| Nested group | `group/subgroup/project#iid` | `org/team/api#7` |\n\n## Supported Events\n\n| GitLab Event | Action |\n|-------------|--------|\n| **Note Hook** (comment with @mention) | Triggers AI conversation |\n| **Issue Hook** (close) | Cleans up isolation environment |\n| **MR Hook** (close/merge) | Cleans up isolation environment |\n| Issue/MR opened | Ignored (descriptions are not commands) |\n\n## Adding Additional Projects\n\nAdd the same webhook to other projects:\n\n```bash\nglab api projects/<PROJECT_ID>/hooks \\\n  --method POST \\\n  -f url=\"https://YOUR_DOMAIN/webhooks/gitlab\" \\\n  -f token=\"YOUR_WEBHOOK_SECRET\" \\\n  -f note_events=true \\\n  -f issues_events=true \\\n  -f merge_requests_events=true\n```\n\nOr via GitLab UI with the same secret.\n\n## Troubleshooting\n\n| Issue | Cause | Fix |\n|-------|-------|-----|\n| `gitlab.invalid_webhook_token` | Secret mismatch | Ensure `GITLAB_WEBHOOK_SECRET` matches the webhook config exactly |\n| Clone hangs | macOS Keychain credential helper | The adapter disables it automatically |\n| `404 Project Not Found` | Token lacks access | Ensure token has `api` scope and project access |\n| `403 You are not allowed` | Insufficient permissions | Use a token with Developer role or higher |\n| No webhook delivery | ngrok URL changed | Update the webhook URL after restarting ngrok |\n| Webhook auto-disabled | 4+ consecutive failures | Fix the issue, then send a test event to re-enable |\n\n## glab CLI Reference\n\nThe AI agent uses `glab` CLI commands. Install and authenticate:\n\n```bash\nbrew install glab\nglab auth login\n```\n\n| Command | Purpose |\n|---------|---------|\n| `glab issue view <IID>` | View issue details |\n| `glab issue note <IID> -m \"...\"` | Comment on issue |\n| `glab mr view <IID>` | View merge request |\n| `glab mr diff <IID>` | View MR diff |\n| `glab mr note <IID> -m \"...\"` | Comment on MR |\n| `glab mr create --title \"...\" --description \"...\"` | Create MR |\n","packages/docs-web/src/content/docs/adapters/github-app-setup.md":"---\ntitle: GitHub App Setup\ndescription: Run Archon's bot identity as a registered GitHub App with multi-installation routing.\ncategory: adapters\narea: adapters\naudience: [operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\nThis is the recommended GitHub auth mode for teams sharing one Archon instance. It replaces the shared `GITHUB_TOKEN` PAT with a registered GitHub App so that:\n\n- Bot comments appear as `archon[bot]` with the App badge, not under an operator's personal account.\n- Installation access tokens rotate automatically every ~1h (smaller blast radius if leaked).\n- Webhooks centralise — one URL per App covers every installation.\n- A team's repos can span multiple GitHub orgs (or a mix of orgs and personal accounts) — Archon routes per-(owner, repo) to the right installation transparently.\n\nSolo installs that only need the PAT model can ignore this page; see [GitHub](/adapters/github/) for the legacy setup.\n\n## When to use App mode vs. PAT mode\n\n| Situation                                                           | Recommended mode |\n| ------------------------------------------------------------------- | ---------------- |\n| Solo developer, single GitHub account                               | PAT              |\n| Team of 2+ sharing one Archon instance                              | App              |\n| Repos across multiple orgs                                          | App              |\n| You want bot comments to attribute as `<slug>[bot]` with App badge  | App              |\n| You want short-lived (1h) tokens instead of long-lived PAT          | App              |\n\nArchon refuses to start with **both** modes configured. Pick one set of env vars.\n\n## Step 1: Register the GitHub App\n\n1. Go to <https://github.com/settings/apps/new> (or `https://github.com/organizations/<org>/settings/apps/new` for an org-owned App).\n2. Fill in:\n   - **GitHub App name** — e.g. `Archon Bot`. The slug derived from this (visible in the App URL) is what you'll later set as `GITHUB_APP_SLUG`. Self-filter compares against `<slug>[bot]`.\n   - **Homepage URL** — your team's Archon URL, e.g. `https://archon.example.com/`.\n   - **Webhook URL** — `https://archon.example.com/webhooks/github`.\n   - **Webhook secret** — same value as your `WEBHOOK_SECRET` env var.\n\n   Webhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** before the signature is checked; Archon stops reading them and creates no receipt or workflow start. This limit applies only to webhook routes.\n3. Uncheck **Active** on the user authorisation callback URL — Archon doesn't use OAuth in PR-B.\n\n## Step 2: Permissions (fine-grained)\n\n**Repository permissions:**\n\n| Permission       | Access       | Used for                                          |\n| ---------------- | ------------ | ------------------------------------------------- |\n| Actions          | Read & Write | Observing GitHub Actions workflow runs and rerunning failed CI |\n| Checks           | Read         | Waking workflow CI waits when a check concludes   |\n| Contents         | Read & Write | Cloning, reading repo metadata, pushing branches, and merging pull requests |\n| Workflows        | Read & Write | Required only when runs create or change files under `.github/workflows/` |\n| Issues           | Read & Write | `createComment` + `listComments`                  |\n| Pull requests    | Read & Write | `pulls.get` + comment posting                     |\n| Metadata         | Read         | Mandatory (auto-included)                         |\n\nGitHub refuses pushes that create or change files under `.github/workflows/` unless the App has **Workflows: Read & Write**, even when **Contents: Read & Write** is granted. See [GitHub's Git access permissions](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app#choosing-permissions-for-git-access).\n\n**Account permissions:** none.\n\n## Step 3: Subscribe to webhook events\n\nSubscribe to:\n\n- Issue comments\n- Check runs\n- Pull request review comments\n- Pull request\n- Issues (used for `closed` cleanup)\n\nExisting installations must accept the added Checks permission before check-run wake-ups work. If a check-run webhook is unavailable, delivery still progresses after the wait's five-minute deadline and probes CI again.\n\n## Step 4: Generate a private key\n\n1. After saving the App, scroll to **Private keys** and click **Generate a private key**.\n2. Save the downloaded `.pem` file in a location only readable by the Archon process — e.g. `/etc/archon/github-app.pem`.\n\n## Step 5: Install the App\n\nInstall the App on every org or personal account that holds repos your team operates on:\n\n1. From the App settings page, click **Install App**.\n2. Pick the org → grant access to all repos (or selected repos).\n3. Repeat for every org/account.\n\n> **Multi-installation:** Archon resolves `owner/repo → installation_id` via `GET /repos/{owner}/{repo}/installation` automatically. No per-install config is needed. `GITHUB_APP_INSTALLATION_ID` only pins the installation used for adapter API operations — see below.\n\n## Step 6: Configure Archon\n\nAdd the following to `~/.archon/.env` for the install or `<cwd>/.archon/.env` for the project:\n\n```dotenv\nGITHUB_APP_ID=123456                 # numeric App ID, visible on the App settings page\nGITHUB_APP_PRIVATE_KEY_PATH=/etc/archon/github-app.pem\nWEBHOOK_SECRET=<same value as on the GitHub side>\n\n# Optional:\n# GITHUB_APP_SLUG=archon-bot         # defaults to 'archon'; set this if you named your App\n#                                    # differently. The bot's posted-comment login is `<slug>[bot]`.\n# GITHUB_APP_INSTALLATION_ID=98765   # use a fixed installation for adapter API operations.\n#                                    # Repository tokens still verify the repository installation.\n```\n\nCLI workflow run, `--detach`, resume, wake, and signal use `GITHUB_APP_ID` plus either private-key setting. They do not require `WEBHOOK_SECRET`; that setting is required for the server's webhook adapter. Each detached child initializes its own provider from the inherited configuration.\n\nIncomplete App settings, malformed App or installation IDs, an unusable RSA private key, and a malformed per-user encryption key fail before workflow execution. App/key correspondence and installation access are checked remotely when resolving the repository token. A resolution failure fails the run before any node executes; it cannot fall back to another credential. With no App settings, CLI behavior is unchanged. CLI runs follow the same per-user policy as server runs: connected user credentials override the bot, and unconnected users have GitHub credentials scrubbed unless org fallback is explicitly enabled.\n\nEach workflow execution, including a resumed segment, resolves the codebase repository's installation token. The provider caches fresh tokens in memory and refreshes them near expiry; tokens are not cached on disk. A node's environment stays fixed during one execution, so this does not rotate tokens inside a workflow that runs longer than the token lifetime.\n\n### Inline private key (alternative)\n\nIf you can't write a file (e.g. a managed PaaS), set the PEM contents inline:\n\n```dotenv\nGITHUB_APP_PRIVATE_KEY=\"-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----\"\n```\n\nArchon normalises the literal `\\n` sequence in `.env`-quoted values into real newlines.\n\n### Unset the PAT\n\nArchon refuses to start if both modes are configured. Remove `GITHUB_TOKEN` from your env when switching to App mode.\n\n## Step 7: Restart Archon and verify\n\n1. Restart the server.\n2. Confirm the startup log shows `github.adapter_mode_app` with your slug.\n3. Trigger a webhook from a repo where the App is installed (e.g. comment `@archon ping` on an issue).\n4. Confirm the bot's reply appears as `<slug>[bot]` with the App badge in the GitHub UI.\n\n## Step 8 (optional): Enable per-user GitHub identity\n\nBy default GitHub API requests and pushes authenticate as the **bot** (`<slug>[bot]`), while commit authorship uses the checkout's ambient git identity. On a multi-user install teammates can connect their own GitHub identity for authentication and commit authorship.\n\nEnable the feature by adding two env vars on top of App mode:\n\n```bash\n# The App's Client ID (App settings → \"Client ID\", starts with Iv1./Iv23…).\n# Distinct from the numeric GITHUB_APP_ID.\nGITHUB_APP_CLIENT_ID=Iv23xxxxxxxxxxxx\n# 32-byte key used to encrypt stored per-user tokens at rest (AES-256-GCM).\nTOKEN_ENCRYPTION_KEY=$(openssl rand -hex 32)\n```\n\nThen, on the GitHub App settings page, enable **Device Flow** (under \"Identifying and authorizing users\"). The feature gate activates when `GITHUB_APP_ID` and `TOKEN_ENCRYPTION_KEY` are both set; `GITHUB_APP_CLIENT_ID` is required for the connect flow itself.\n\nTeammates connect once via any surface:\n\n- **CLI:** `archon auth github`\n- **Web UI:** Settings → **Connect GitHub**\n\nOnce enabled:\n\n- Run commits use the initiating connected user's GitHub login as author name and `<numeric_id>+<login>@users.noreply.github.com` as author email. The committer remains the ambient git identity. This applies to new and adopted worktrees, in-place runs, and folder projects without changing repository git config. Unconnected users and runs without an initiating user keep ambient authorship.\n- Workflows declaring `requires: [github]` **hard-block** unconnected users before any worktree/clone/AI cost.\n- An unconnected user's workflow `gh`/`git` has its GitHub token **scrubbed** by default (rather than silently using the shared org/bot token). The host's `GH_ENTERPRISE_TOKEN`/`GITHUB_ENTERPRISE_TOKEN` are cleared for every per-user run. The scrub also hides the host's stored logins: `gh` gets an empty config directory, so a `gh auth login` on the server is not used, and git's credential helpers (keychain, credential store, `gh auth git-credential`) are cleared. GitHub commands in such a run fail with an authentication error until the user connects. SSH remotes still use the host's SSH keys. Opt back into the shared token with `ARCHON_ALLOW_ORG_GITHUB_TOKEN_FALLBACK=true`.\n- Rotating `TOKEN_ENCRYPTION_KEY` invalidates all stored user tokens — everyone must reconnect.\n\nThis is fully backward compatible: leave the two vars unset and the App keeps working as the bot only. See the [configuration reference](/reference/configuration/#per-user-github-identity-app-mode-optional) for the full env var table.\n\n## Operational notes\n\n### Token rotation is invisible\n\nInstallation tokens are valid for 1h. Archon caches each `installation_id → token` pair and refreshes ~5 minutes before expiry on the next access. No background timer; no leaked handles.\n\n### `event.installation.id` short-circuits the lookup\n\nEvery webhook delivery from a GitHub App carries `installation.id`. Archon primes its `owner/repo → installation_id` cache from the payload, so the next outbound call to that repo skips the `GET /repos/{owner}/{repo}/installation` round trip.\n\n### 401 forces a single retry\n\nA 401 from any installation Octokit evicts the cached token and the call is retried once with a fresh token. Persistent 401s propagate as the original error.\n\n### Long-running workflow `git push`\n\nWorkflows that span >1h need a fresh token to push from the cloned worktree. In App mode, Archon installs a git credential helper after cloning and refreshes it whenever it sets up an existing repository, including webhook handling and adding a codebase by URL: the worktree's `.git/config` points at `~/.archon/bin/git-credential-archon`, which talks back to Archon's internal endpoint for a fresh installation token on each operation. Git skips credential helpers when a remote URL carries credentials, so the same setup removes any token embedded in the repository's `https://github.com` remote URLs, such as one left by an older PAT-mode clone. The helper reads the server's port from `ARCHON_PORT`, which Archon sets for the processes it launches. A `git` command run from an independent shell on the Archon host gets no credential unless it exports `ARCHON_PORT` with the server's port.\n\nThe helper is embedded in compiled binaries. Each setup replaces an outdated managed script and registers the helper and GitHub path forwarding in the repository's local Git config. Existing repositories are refreshed on their next setup, not swept at server startup. If helper installation or registration fails, repository setup stops before URL registration, sync, or workflow dispatch; a later setup retries registration and repairs partial config.\n\n### Long-running workflow `gh`\n\nA run's `GH_TOKEN` and `GITHUB_TOKEN` hold the installation token resolved when the run, or a resumed segment, starts. That token can have as little as 5 minutes left. When the run's process has `ARCHON_PORT` (every run the server dispatches, and a CLI run that exports the server's port), Archon also puts `~/.archon/bin/gh-shim` first on the run's `PATH`. On each call the shim asks the same internal endpoint for a current token for the run's repository and runs the real `gh` with it in the environment. If it gets no token, it exits 1 with a `gh (archon shim):` error instead of running `gh` with a stale token. A `GH_TOKEN` that is not an installation token, such as one a step sets itself, passes through unchanged. A per-user token or scrubbed token from [per-user GitHub identity](#step-8-optional-enable-per-user-github-identity) turns the shim off for that run.\n\n`gh` keeps the start-time token, and fails once it expires, in these cases:\n\n- **A CLI run without `ARCHON_PORT`.** No endpoint is reachable, so Archon logs `workflow.gh_token_refresh_unavailable` each time it resolves the run's GitHub environment. That happens when the run starts, when it runs worktree setup commands, and when it resumes, so one run can log it several times. The CLI mints its token at start, so it lasts about an hour. Export `ARCHON_PORT` with an App-mode server's port to cover longer runs.\n- **`gh` called by a Codex agent.** Codex's shell snapshot restores a fixed `PATH`, so its shell never finds the shim. Archon does not change that Codex setting. Bash, script and loop nodes, and Claude and Pi agents, use the shim.\n- **Windows.** The shim is a POSIX shell script, so a run on Windows logs the same warning and keeps the start-time token.\n\nRun output redacts the start-time token only. A token the shim fetches later lives in the `gh` process's environment, so it is not redacted if a step prints it, for example with `gh auth token`.\n\n### Internal endpoint security — REQUIRED\n\nThe credential-helper backend is exposed at `POST /internal/git-credential` and hands out live installation access tokens. **It MUST NOT be reachable from outside the Archon host.**\n\nArchon enforces this at startup: with App mode active and the server bound to a non-loopback interface (e.g. `0.0.0.0`), the process **refuses to start** and exits with `github_app.internal_endpoint_public_bind_rejected`. Two correct configurations:\n\n1. **Recommended (bare-metal / systemd) — bind Archon to `127.0.0.1`** (`HOST=127.0.0.1`) and put a reverse proxy in front. Configure the proxy to drop `/internal/*` paths.\n2. **Opt-in escape hatch — `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`** combined with `HOST=0.0.0.0` (or unset). Use ONLY when your reverse proxy already drops `/internal/*` AND your deployment topology genuinely requires the upstream to bind non-loopback (e.g. a container network where loopback isn't reachable from the proxy). **Docker / docker-compose deployments fall here** — the app container *must* bind `0.0.0.0` inside the container for docker-proxy to forward traffic, so option 1 isn't directly usable. See the [Canonical Docker setup](#canonical-docker-setup) below. Startup logs `github_app.internal_endpoint_exposed_acknowledged` so the choice is auditable.\n\nExample Caddy snippet that drops `/internal/*` (bare-metal):\n\n```txt\nexample.com {\n  @internal path /internal/*\n  respond @internal 404\n  reverse_proxy 127.0.0.1:3090\n}\n```\n\n### Canonical Docker setup\n\nThe canonical Archon Docker stack (`docker-compose.yml` + `docker-compose.override.yml` + Caddy) publishes the app container's port `3000` and runs Caddy as a reverse proxy. Because the app container can't bind `127.0.0.1` and still have docker-proxy forward traffic to it, option 1 above isn't directly usable. Take option 2 with three layers of defense:\n\n**1. In `docker-compose.override.yml` — bind the published host port to loopback only.**\n\n```yaml\nservices:\n  app:\n    ports: !override\n      - \"127.0.0.1:${PORT:-3000}:${PORT:-3000}\"\n```\n\nThe `!override` tag (compose-spec) replaces the base file's `ports` list instead of merging. After this, port `3000` is reachable only via the Docker network (where Caddy lives) or from the host itself — not from the public internet.\n\n**2. In `Caddyfile` — drop `/internal/*` requests.**\n\nThe maintained `Caddyfile.example` includes this denial. Keep it in your deployed `Caddyfile`; if you use an older or custom configuration, insert it before the fallthrough `handle { }` block:\n\n```txt\nhandle /internal/* {\n  respond \"Not Found\" 404\n}\n```\n\nCaddy evaluates `handle` blocks by path specificity, so `/internal/*` (more specific) wins over the generic fallthrough regardless of order.\n\n**3. In `.env` — opt out of the loopback-bind guard.**\n\n```ini\n# Safe because:\n#  1. docker-compose binds port 3000 to 127.0.0.1 only (override above)\n#  2. Caddy drops /internal/* (handle block above)\nARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1\n```\n\nWithout this flag, Archon refuses to start in App mode and exits with `github_app.internal_endpoint_public_bind_rejected`.\n\n**Apply + verify:**\n\n```sh\n# Apply the override + Caddyfile + env changes, then restart app + caddy.\ndocker compose up -d --force-recreate app caddy\n\n# From outside the host — must be 404/403 from the proxy.\n# Use POST (the endpoint's actual method) — a GET probe can false-pass when\n# the proxy 404s unmatched GETs while still forwarding the POST upstream.\ncurl -i -X POST https://your-archon/internal/git-credential \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"host\":\"github.com\",\"path\":\"any/repo\"}'\n\n# On the host — port should bind to loopback only\nss -tlnp | grep :3000\n# Expected: 127.0.0.1:3000   (NOT 0.0.0.0:3000)\n\n# Startup log should contain:\ndocker compose logs app --tail 200 | grep \"internal_endpoint_exposed_acknowledged\"\n# This event confirms the opt-out path was taken (audit trail).\n```\n\nIf the external POST probe returns anything other than `404` or `403` from the proxy — i.e. anything that suggests the request reached the Archon process — **stop and fix the proxy config** before going live. The endpoint hands out live installation tokens to whoever can reach it.\n\n## Migration from PAT mode\n\n1. Register the App and install on your orgs (Steps 1–5 above).\n2. Add `GITHUB_APP_*` env vars (Step 6). In Docker, put them in the container environment (the compose `.env`), not `/.archon/.env`; see [GitHub CLI authentication](/deployment/docker/#github-credentials).\n3. **Remove** `GITHUB_TOKEN` from your env (or comment it out). Archon refuses to start if both are set.\n4. **Remove** `GH_TOKEN` if you set it to the same PAT. Archon starts with it set, but the GitHub CLI in the container keeps using the old PAT.\n5. Restart Archon.\n6. Webhook URLs configured per-repo against the PAT-mode setup can stay or be removed — the App's single webhook URL covers everything once it's installed. New repos auto-join via App installation. While both webhooks are active, a comment delivered by each is deduplicated at ingest, so the bot still responds once.\n\nIn App mode, adding a private GitHub codebase by URL and inspecting its base branch use the repository's installation token; no PAT is needed. The clone keeps a credential-free remote URL and receives the credential helper before registration completes. If the App is not installed, Archon reports installation guidance. Adding an already-registered URL refreshes the helper without cloning again when the checkout exists. A missing Archon-managed checkout is cloned again at its registered path. A missing operator-owned checkout must be restored or repaired with `/update-project`.\n\nRun `archon doctor` to validate local App configuration after migration. A passing App check confirms the ID and RSA key are valid locally; it does not confirm GitHub accepts them or that the App has repository access.\n\n## Troubleshooting\n\n### `AppPrivateKeyError: Provided value is not a valid PEM-encoded private key`\n\n- Check the file content includes `-----BEGIN ... PRIVATE KEY-----` and `-----END ... PRIVATE KEY-----`.\n- For inline keys, ensure the `.env` value preserves newlines (either literal newlines in a multi-line value or the `\\n` escape inside double quotes).\n\n### `AppNotInstalledError: The Archon GitHub App is not installed on \"<owner>\"`\n\n- The App is not installed on that owner's org/account. Use the install link in the error message to add it.\n\n### 401 loop on GitHub API calls\n\nRepeated 401s on outbound API calls (`createComment`, `listComments`, `repos.get`, `pulls.get`) point at installation / token issues, not webhook config. Walk through:\n\n- Verify the App is installed on the target `owner` and has not been suspended or uninstalled. Visit `https://github.com/settings/installations` (or the org equivalent) to confirm.\n- Verify the private key in your env (`GITHUB_APP_PRIVATE_KEY` or `_PATH`) matches the same App that `GITHUB_APP_ID` points at. Mismatched key+ID is a common cause of \"401 from JWT\" errors at token-issuance time.\n- Verify `GITHUB_APP_INSTALLATION_ID` (if set) still corresponds to a live installation — uninstall + reinstall assigns a new ID.\n\n### GitHub refuses a branch push\n\nAn installation token needs Contents write access to push branches. If the branch creates or changes files under `.github/workflows/`, GitHub also requires Workflows write access. Check the App and installation permissions:\n\nVerify the App's permissions against the [permissions table](#step-2-permissions-fine-grained). Permission changes require operators to **review and accept** the new permissions on every installation; until accepted, that installation keeps its old permissions.\n\n### Webhook deliveries fail signature verification\n\nA webhook-secret mismatch causes `github.signature_mismatch` / `github.signature_length_mismatch` errors at the `POST /webhooks/github` endpoint — distinct from outbound API 401s. If GitHub's webhook delivery page shows red ❌ next to the delivery (rather than your bot just silently not responding), check:\n\n- `WEBHOOK_SECRET` in Archon's env matches the value entered in the GitHub App's webhook configuration page exactly.\n\n### Bot comments still appear under your personal account\n\n- You're still in PAT mode. Check `process.env.GITHUB_TOKEN` is unset and `GITHUB_APP_ID` is set; restart.\n\n### Server refused to start: `github_app.internal_endpoint_public_bind_rejected`\n\n- App mode is active but the server is bound to a non-loopback interface. This is a fail-fast guard — the `/internal/git-credential` endpoint hands out live installation access tokens and would leak credentials to the network. Either set `HOST=127.0.0.1` (bare-metal / systemd), or, if you're on Docker, follow the [Canonical Docker setup](#canonical-docker-setup) above (port→127.0.0.1 binding + Caddy `/internal/*` drop + `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`).\n\n### Server log shows `github_app.internal_endpoint_exposed_acknowledged`\n\n- You set `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`. Double-check that your reverse proxy actually drops `/internal/*` — a `curl https://your-archon/internal/git-credential -d '{\"host\":\"github.com\",\"path\":\"any/repo\"}'` from outside the host must return 404 or 403 from the proxy (NOT a token from Archon). On the canonical Docker stack, also confirm `ss -tlnp | grep :3000` shows `127.0.0.1:3000` (not `0.0.0.0:3000`).\n","packages/docs-web/src/content/docs/adapters/github.md":"---\ntitle: GitHub\ndescription: Connect Archon to GitHub via webhooks to interact from issues and pull requests.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 4\n---\n\nConnect Archon to GitHub so you can interact with your AI coding assistant from issues and pull requests.\n\n> **Teams sharing one Archon instance:** prefer the [GitHub App setup](./github-app-setup.md). The PAT-mode setup on this page is the legacy path — it still works for solo installs, but bot comments all post under the PAT owner's avatar and tokens never auto-rotate. App mode gives you `<slug>[bot]` attribution, 1h token rotation, multi-org support, and one webhook URL.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- GitHub repository with issues enabled\n- A GitHub personal access token set as `GITHUB_TOKEN` in `~/.archon/.env`\n- Public endpoint for webhooks (see ngrok setup below for local development)\n\n## Step 1: Generate Webhook Secret\n\nOn Linux/Mac:\n```bash\nopenssl rand -hex 32\n```\n\nOn Windows (PowerShell):\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret -- you'll need it for steps 3 and 4.\n\n## Step 2: Expose Local Server (Development Only)\n\n### Using ngrok (Free Tier)\n\n```bash\n# Install ngrok: https://ngrok.com/download\n# Or: choco install ngrok (Windows)\n# Or: brew install ngrok (Mac)\n\n# Start tunnel\nngrok http 3090\n\n# Copy the HTTPS URL (e.g., https://abc123.ngrok-free.app)\n# Free tier URLs change on restart\n```\n\nKeep this terminal open while testing.\n\n### Using Cloudflare Tunnel (Persistent URLs)\n\n```bash\n# Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/\ncloudflared tunnel --url http://localhost:3090\n\n# Get persistent URL from Cloudflare dashboard\n```\n\nPersistent URLs survive restarts.\n\n**For production deployments**, use your deployed server URL (no tunnel needed).\n\n## Step 3: Configure GitHub Webhook\n\nGo to your repository settings:\n- Navigate to: `https://github.com/owner/repo/settings/hooks`\n- Click \"Add webhook\"\n- **Note**: For multiple repositories, you'll need to add the webhook to each one individually\n\n**Webhook Configuration:**\n\n| Field | Value |\n|-------|-------|\n| **Payload URL** | Local: `https://abc123.ngrok-free.app/webhooks/github`<br>Production: `https://your-domain.com/webhooks/github` |\n| **Content type** | `application/json` |\n| **Secret** | Paste the secret from Step 1 |\n| **SSL verification** | Enable SSL verification (recommended) |\n| **Events** | Select \"Let me select individual events\":<br>- Check runs<br>- Issues<br>- Issue comments<br>- Pull requests |\n\nClick \"Add webhook\" and verify it shows a green checkmark after delivery.\n\nWebhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** before the signature is checked; Archon stops reading them and creates no receipt or workflow start. This limit applies only to webhook routes.\n\n## Step 4: Set Environment Variables\n\n```ini\nWEBHOOK_SECRET=your_secret_from_step_1\n```\n\n**Important**: The `WEBHOOK_SECRET` must match exactly what you entered in GitHub's webhook configuration.\n\n## Step 5: Configure Streaming (Optional)\n\nThe GitHub adapter always uses `batch` mode (hardcoded). It assembles the reply before delivery and splits long replies into multiple comments.\n\n## Usage\n\nInteract by @mentioning your bot in issue or PR **comments**:\n\n```\n@archon can you analyze this bug?\n@archon prime the codebase\n@archon review this implementation\n```\n\n**First mention behavior:**\n- Reuses an existing local registration at its saved checkout path, including a subdirectory of a Git checkout. Otherwise, registers and clones the repository to `~/.archon/workspaces/owner/repo/source/`, or to a suffixed path such as `owner/repo-2/source/` when that project name is taken.\n- For an existing registration, readiness skips syncing the checkout to preserve local changes.\n- Detects and loads commands from `.archon/commands/` if present\n- Injects full issue/PR context for the AI assistant\n\n**Subsequent mentions:**\n- Resumes existing conversation\n- Maintains full context across comments\n\n:::note\nOnly comments trigger the bot. @mentions in issue or PR descriptions are ignored -- descriptions often contain example commands or documentation that are not intended as bot invocations.\n:::\n\n## Adding Additional Repositories\n\nOnce your server is running, add more repos by creating a webhook with the same secret.\n\n**Via GitHub UI:** Repo Settings > Webhooks > Add webhook\n- **Payload URL**: Your server URL + `/webhooks/github`\n- **Content type**: `application/json`\n- **Secret**: Same `WEBHOOK_SECRET` from your `.env`\n- **Events**: Check runs, Issues, Issue comments, Pull requests\n\n**Via CLI:**\n\n```bash\n# Get your existing webhook secret\nWEBHOOK_SECRET=$(grep WEBHOOK_SECRET .env | cut -d= -f2)\n\n# Add webhook to new repo (replace OWNER/REPO)\ngh api repos/OWNER/REPO/hooks --method POST \\\n  -f \"config[url]=https://YOUR_DOMAIN/webhooks/github\" \\\n  -f \"config[content_type]=json\" \\\n  -f \"config[secret]=$WEBHOOK_SECRET\" \\\n  -f \"events[]=check_run\" \\\n  -f \"events[]=issues\" \\\n  -f \"events[]=issue_comment\" \\\n  -f \"events[]=pull_request\"\n```\n\n**Important**: The webhook secret must be identical across all repos.\n\nCheck-run deliveries wake CI waits quickly. They are not required for correctness: when GitHub does not deliver one, the workflow reaches its deadline and probes CI again.\n\n## GitHub Enterprise Server\n\nTo run the adapter against a self-hosted GitHub Enterprise Server, set its base URL:\n\n```ini\nGITHUB_URL=https://github.example.com\n```\n\nWith `GITHUB_URL` set, the adapter calls `https://github.example.com/api/v3`, clones from `https://github.example.com/<owner>/<repo>.git`, and `GH_TOKEN`/`GITHUB_TOKEN` authenticate HTTPS clones of that exact scheme, host and port. Leave it unset for github.com. A value with a path, query or credentials is rejected: the adapter and the GitHub App fail startup, and clones to the Enterprise host get no GitHub token. Clones from other forges are unaffected.\n\n- **Webhook:** create it on the Enterprise repository or organization, pointing at the same `/webhooks/github` endpoint with the same secret (Step 3).\n- **GitHub App mode:** register the App on the Enterprise instance (**Settings → Developer settings → GitHub Apps**) with the permissions listed in [GitHub App Setup](/adapters/github-app-setup/). The App's API calls and installation tokens go to the Enterprise instance.\n- **SDLC workflows:** the forge plugin needs a `forge.hosts` entry for the Enterprise host; see [forge plugin configuration](/reference/forge/#plugin-configuration).\n\nCurrent limits: in App mode, git operations after the first clone (sync, workflow pushes) and per-user identity (`archon auth github`, the device flow) still target github.com. Use PAT mode on Enterprise Server for now.\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","packages/docs-web/src/content/docs/adapters/index.md":"---\ntitle: Platform Adapters\ndescription: Overview of all platform adapters available for connecting to Archon.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 0\n---\n\nArchon supports multiple platform adapters. Each adapter connects Archon to a different communication channel, letting you trigger workflows and interact with AI agents from wherever you work.\n\n## Core Adapters\n\n| Adapter | Transport | Auth Required | Setup |\n|---------|-----------|---------------|-------|\n| [**Web UI**](/adapters/web/) | SSE streaming | None | Built-in |\n| [**CLI**](/reference/cli/) | stdout | None | Built-in |\n| [**Slack**](/adapters/slack/) | Socket Mode, as a chat plugin | Plugin + Bot and App tokens | [Setup guide](/adapters/slack/) |\n| [**Telegram**](/adapters/telegram/) (deprecated) | Bot API polling | Bot token | [Setup guide](/adapters/telegram/) |\n| [**GitHub**](/adapters/github/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/github/) |\n\n## Community Adapters\n\nCommunity adapters follow the same `IPlatformAdapter` interface but target platforms outside the core set.\n\n| Adapter | Transport | Auth Required | Setup |\n|---------|-----------|---------------|-------|\n| [**Discord**](/adapters/community/discord/) | WebSocket | Bot token | [Setup guide](/adapters/community/discord/) |\n| [**Gitea**](/adapters/community/gitea/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/community/gitea/) |\n| [**GitLab**](/adapters/community/gitlab/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/community/gitlab/) |\n\n## How Adapters Work\n\nSlack runs as a chat plugin: a separate process the server starts from an installed plugin, speaking the chat plugin protocol. The other adapters are built into the server. All bundled adapters implement the `IPlatformAdapter` interface. They handle:\n\n- **Message ingestion** -- receiving messages from the platform and forwarding them to Archon's orchestrator\n- **Response delivery** -- streaming or batching AI responses back to the platform\n- **Authorization** -- optional user whitelists to restrict access\n- **Conversation tracking** -- mapping platform-specific identifiers (thread IDs, chat IDs, issue numbers) to Archon conversations\n\n## Choosing an Adapter\n\n- **Web UI** is the fastest way to get started -- no tokens or external services needed.\n- **Slack** is ideal for mobile access and team collaboration.\n- **GitHub** integrates directly into your issue and PR workflow.\n- **Discord** works well for community or team servers.\n\nYou can run multiple adapters simultaneously. Any bundled adapter with the required environment variables set will start automatically when you launch the server. Slack also needs its plugin installed; see the [Slack guide](/adapters/slack/).\n","packages/docs-web/src/content/docs/adapters/slack.md":"---\ntitle: Slack\ndescription: Install the Slack chat plugin and connect Archon to Slack over Socket Mode -- works behind firewalls with no public URL needed.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 2\n---\n\nConnect Archon to Slack so you can interact with your AI coding assistant from any Slack workspace.\n\nSlack runs as a chat plugin, `chat-slack`, released with Archon. The server starts the plugin as its own process and talks to it over the chat plugin protocol; the server itself contains no Slack code. You create a Slack app, set its two tokens in the server environment, and install the plugin. Docker provisions the image-provided plugin automatically when `SLACK_BOT_TOKEN` is configured.\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Slack workspace where you have permission to install apps\n- An Archon release that publishes the Slack plugin asset (`archon-chat-slack-<os>-<arch>`)\n\n## Overview\n\nArchon uses **Socket Mode** for Slack integration, which means:\n\n- No public HTTP endpoints needed\n- Works behind firewalls\n- Simpler local development\n- Not suitable for Slack App Directory (fine for personal/team use)\n\n## Step 1: Create a Slack App\n\n1. Go to [Slack API Apps](https://api.slack.com/apps)\n2. Log in if prompted\n3. Choose the workspace for your app\n4. Click **Create New App**\n5. Choose **From scratch**\n6. Enter:\n   - **App Name**: Any name (this is what you will use to @mention the bot)\n   - **Workspace**: Select your workspace\n7. Click **Create App**\n\n## Step 2: Enable Socket Mode\n\n1. In the left sidebar, click **Socket Mode**\n2. Toggle **Enable Socket Mode** to ON\n3. When prompted, create an App-Level Token:\n   - **Token Name**: `socket-mode`\n   - **Scopes**: Add `connections:write`\n   - Click **Generate**\n4. **Copy the token** (starts with `xapp-`) -- this is your `SLACK_APP_TOKEN`\n\n## Step 3: Configure Bot Scopes\n\n1. In the left sidebar, click **OAuth & Permissions**\n2. Scroll down to **Scopes** > **Bot Token Scopes**\n3. Add these scopes to bot token scopes:\n   - `app_mentions:read` -- Receive @mention events\n   - `chat:write` -- Send and edit messages (covers `chat.update` on the bot's own messages)\n   - `channels:history` -- Read messages in public channels (for thread context)\n   - `channels:join` -- Allow bot to join public channels\n   - `groups:history` -- Read messages in private channels (optional)\n   - `im:history` -- Read DM history (for DM support)\n   - `im:write` -- Send DMs\n   - `im:read` -- Read DM history (for DM support)\n   - `mpim:history` -- Read group DM history (optional)\n   - `mpim:write` -- Send group DMs\n   - `reactions:write` -- Add lifecycle reactions (🔄 / ✅ / ❌) to the triggering message\n   - `commands` -- Required for the `/archon` and `/archon-workflow` slash commands\n   - `users:read` -- Look up real names via `users.info` for user attribution. Without this scope messages still flow, but real names do not appear in the Archon database.\n\n## Step 4: Subscribe to Events\n\n1. In the left sidebar, click **Event Subscriptions**\n2. Toggle **Enable Events** to ON\n3. Under **Subscribe to bot events**, add:\n   - `app_mention` -- When someone @mentions your bot\n   - `message.im` -- Direct messages to your bot\n   - `message.channels` -- Messages in public channels (optional, for broader context)\n   - `message.groups` -- Messages in private channels (optional)\n4. Click **Save Changes**\n\n## Step 4b: Enable Interactivity\n\nInteractive buttons (gate decisions and Cancel on workflow runs) ride the same\nSocket Mode connection -- no public URL needed.\n\n1. In the left sidebar, click **Interactivity & Shortcuts**\n2. Toggle **Interactivity** to ON\n3. Leave the **Request URL** field blank -- Socket Mode handles routing\n4. Click **Save Changes**\n\n## Step 4c: Register Slash Commands\n\nTwo slash commands give the team an alternative to @mention:\n\n| Command | What it does |\n| --- | --- |\n| `/archon <message>` | Starts a new thread in the current channel. To continue an existing thread, post `@archon <message>` in that thread. |\n| `/archon-workflow <subcommand>` | Direct workflow control. Supports `list`, `status`, `get <id>`, `run <name> <args>`, `approve <id> [comment]`, `reject <id> [reason]`, `respond <id> <decision> [text]`, `abandon <id>`, `resume <id>`. |\n\nFor each command:\n\n1. In the left sidebar, click **Slash Commands**\n2. Click **Create New Command**\n3. Fill in:\n   - **Command**: `/archon` (or `/archon-workflow`)\n   - **Request URL**: leave blank -- Socket Mode handles routing\n   - **Short Description**: e.g. \"Talk to Archon\" or \"Archon workflow control\"\n   - **Usage Hint**: e.g. `<message>` or `<subcommand>`\n4. Save\n\nReinstall the app (Step 5) after adding scopes or commands so Slack issues a\nfresh token with the new permissions.\n\n## Step 5: Install to Workspace\n\n1. In the left sidebar, click **Install App**\n2. Click **Install to Workspace**\n3. Review the permissions and click **Allow**\n4. **Copy the Bot User OAuth Token** (starts with `xoxb-`) -- this is your `SLACK_BOT_TOKEN`\n\n## Step 6: Set Environment Variables\n\nAdd both tokens to the server's env file (`~/.archon/.env`, or the compose `.env` in Docker):\n\n```ini\nSLACK_BOT_TOKEN=xoxb-your-bot-token\nSLACK_APP_TOKEN=xapp-your-app-token\n```\n\nIn Docker, a nonempty `SLACK_BOT_TOKEN` declares installation at startup, including tokens in `/.archon/.env`. Both tokens are required for a connection.\n\nThe plugin reads them from the environment the server starts it with. If either is missing, the plugin fails to start and the server does not retry it.\n\n## Step 7: Install the Slack Plugin\n\nWith the release binary or Homebrew:\n\n```bash\narchon plugin add coleam00/Archon/plugins/chat-slack\n```\n\nIn a source checkout, run `bun run cli plugin add coleam00/Archon/plugins/chat-slack`.\n\nIn Docker, restart the container with its tokens configured. The image-provided release installs offline before the server starts, writes a verified receipt to `/.archon/plugins`, and logs why it was provisioned. No manual install is needed. A valid installed Slack plugin is retained at its current version. See [Building the image](/deployment/docker/#building-the-image) for the release pin and build args.\n\nWithout a tag, manual `plugin add` installs the latest release. Restart the server; Slack appears in `/api/health` once the plugin connects. Keep the plugin current with `archon plugin update coleam00/Archon/plugins/chat-slack`, and remove it with `archon plugin remove coleam00/Archon/plugins/chat-slack`. In Docker, remove Slack tokens from every effective env source as well, or the image plugin is reinstalled on restart. Outside Docker, removal followed by a restart keeps Slack off. See [`archon plugin`](/reference/cli/#plugin).\n\n## Step 8: Invite Bot to Channel\n\n1. Go to the Slack channel where you want to use the bot\n2. Type `/invite @your-bot` (your bot's display name)\n3. The bot should now respond to @mentions in that channel\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users:\n1. In Slack, go to a user's profile > click \"...\" > \"Copy member ID\"\n2. Add to environment:\n\n```ini\nSLACK_ALLOWED_USER_IDS=U01ABC123,U02DEF456\n```\n\nWhen set, only listed user IDs can interact with the bot. When empty/unset, the bot responds to all users. The server enforces this list, for messages and for button clicks alike, before anything reaches Archon.\n\n## Configure Streaming Mode (Optional)\n\n```ini\nSLACK_STREAMING_MODE=batch  # batch (default) | stream\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Usage\n\nSelect a registered project by posting `@your-bot /setproject <name>` in the target thread. To register a local directory first, post `@your-bot /register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nSlack registers `/archon` and `/archon-workflow`, not raw `/setproject` or `/register-project` slash commands. `/archon <message>` creates a new thread; use an @mention in the existing thread to scope that conversation.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\n### @Mention in Channels\n\n```\n@your-bot /clone https://github.com/user/repo\n```\n\n### Continue Work in Thread\n\nReply in the thread created by the initial message:\n\n```\n@your-bot /status\n```\n\n### Start Parallel Work (Worktree)\n\n```\n@your-bot /worktree feature-branch\n```\n\n### Direct Messages\n\nYou can also DM the bot directly -- no @mention needed:\n\n```\n/help\n```\n\n## In-Thread UX\n\nWhen a workflow runs in a Slack thread, Archon now:\n\n- Adds 🔄 to your triggering message when the run starts, and swaps it for ✅\n  on execution completion or ❌ on execution failure / cancellation. If a declared\n  authored outcome disagrees, it also adds the outcome reaction, so both facts remain visible\n- Posts a single status message in the thread that's edited in place as DAG\n  nodes start, complete, fail, or get skipped\n- When approval details are available, renders the gate's declared decisions as\n  buttons, using labels when supplied and sending the exact decision ID.\n  Legacy gates keep **Approve** / **Reject**.\n  The fallback text lists `/archon-workflow respond <id> <decision> [text]` commands;\n  use these to include optional text with a decision. For a parent blocked on a\n  child run, these parent controls redirect to the child instead of recording a\n  parent decision. Answer the child run using the displayed child command\n- If approval details cannot be loaded, posts an **Approval details unavailable**\n  notice naming the run instead of decision buttons or response commands. Check\n  the run status before responding\n- Adds a **Cancel** button on the status message while the run is\n  non-terminal, so the run's starter or an admin can abandon a runaway run\n  with a click\n- Appends a small italic cost / token footer after direct-chat replies\n  (e.g. `_cost: $0.0234 · 12.4k tokens · stop: end_turn_`) and on the\n  final status message when a workflow completes\n- Labels execution status and a non-null authored outcome separately in the final status message;\n  workflows without an authored outcome retain the existing status-only message\n- Annotates long responses split across multiple Slack messages with\n  `_part i/n_` footers so it's clear they belong together\n\nAll gate-decision and Cancel clicks run through the same\n`SLACK_ALLOWED_USER_IDS` whitelist as inbound messages -- clicks from users\noutside it are dropped. A member who is not the run's starter or an admin sees\na refusal only they can see.\n\n`/archon connect github` replies with a pointer to the Web UI or\n`archon auth github`; it does not run the GitHub device flow in Slack.\n\n## Upgrading From the Bundled Slack Adapter\n\nEarlier Archon versions ran Slack inside the server whenever `SLACK_BOT_TOKEN` and\n`SLACK_APP_TOKEN` were set. The server no longer contains that adapter: without the\nplugin, Slack stays off. When `SLACK_BOT_TOKEN` is set and no Slack plugin is installed,\nstartup logs a `chat.bundled_adapter_retired` warning with Docker provisioning guidance and the manual install command.\n\nIn Docker, existing token configuration provisions the image-provided plugin offline on restart; no manual step is needed. The boot log names the release and why it was installed. Outside Docker, install the plugin (Step 7) and restart. Nothing else changes: the tokens, the Slack app\nconfiguration, `SLACK_ALLOWED_USER_IDS` and `SLACK_STREAMING_MODE` stay as they are, and\nexisting conversations, linked identities and workspaces carry over because the plugin\nkeeps the platform id `slack`.\n\n## Troubleshooting\n\n### Bot Doesn't Respond\n\n1. Check that the plugin is installed: `archon plugin list` shows `coleam00/Archon/plugins/chat-slack`\n2. Check that Socket Mode is enabled\n3. Verify both tokens are correct in `.env`, then check the server log for `chat.bundled_adapter_retired` or a plugin start failure\n4. Ensure the bot is invited to the channel\n5. Make sure you're @mentioning the bot (not just typing)\n\n### \"channel_not_found\" Error\n\nThe bot needs to be invited to the channel:\n\n```\n/invite @your-bot\n```\n\n### \"missing_scope\" Error\n\nAdd the required scope in **OAuth & Permissions** and reinstall the app.\n\n### Thread Context Not Working\n\nEnsure these scopes are added:\n\n- `channels:history` (public channels)\n- `groups:history` (private channels)\n\n## Security Recommendations\n\n1. **Use User Whitelist**: Set `SLACK_ALLOWED_USER_IDS` to restrict bot access\n2. **Private Channels**: Invite the bot only to channels where it's needed\n3. **Token Security**: Never commit tokens to version control\n\n## Reference Links\n\n- [Slack API Documentation](https://api.slack.com/docs)\n- [Bolt for JavaScript](https://tools.slack.dev/bolt-js/)\n- [Socket Mode Guide](https://api.slack.com/apis/connections/socket)\n- [Permission Scopes](https://api.slack.com/scopes)\n","packages/docs-web/src/content/docs/adapters/telegram.md":"---\ntitle: Telegram\ndescription: Connect Archon to Telegram using the Bot API for mobile and desktop access.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 3\n---\n\nConnect Archon to Telegram so you can interact with your AI coding assistant from any Telegram client.\n\n:::caution[Deprecated]\nThe Telegram adapter is deprecated and will be removed in the next minor release, 0.13.0. No Archon maintainer owns it. It keeps working until then, and the server logs a deprecation notice when it starts the adapter. If you need Telegram after that, build and own it as a [chat plugin](/guides/publishing-plugins/#chat-plugins). [#4641](https://github.com/coleam00/Archon/issues/4641) tracks the removal.\n:::\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Telegram account\n\n## Create Telegram Bot\n\n1. Message [@BotFather](https://t.me/BotFather) on Telegram\n2. Send `/newbot` and follow the prompts\n3. Copy the bot token (format: `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`)\n\n## Set Environment Variable\n\n```ini\nTELEGRAM_BOT_TOKEN=123456789:ABCdefGHI...\n```\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users:\n1. Message [@userinfobot](https://t.me/userinfobot) on Telegram to get your user ID\n2. Add to environment:\n\n```ini\nTELEGRAM_ALLOWED_USER_IDS=123456789,987654321\n```\n\nWhen set, only listed user IDs can interact with the bot. When empty/unset, the bot responds to all users.\n\n## Configure Streaming Mode (Optional)\n\n```ini\nTELEGRAM_STREAMING_MODE=stream  # stream (default) | batch\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Project selection\n\nSelect a registered project by submitting `/setproject <name>` in the conversation or thread where you want to work. To register a local directory first, submit `/register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","packages/docs-web/src/content/docs/adapters/web.md":"---\ntitle: Web UI\ndescription: Built-in console for operating Archon from a browser.\ncategory: adapters\narea: adapters\naudience: [user]\nstatus: current\nsidebar:\n  order: 1\n---\n\nThe Web UI is Archon's built-in management console. It uses Archon's public API\nand does not require a separate hosted service.\n\n## Start the console\n\nFor local development, start the API server and Vite frontend together:\n\n```bash\nbun run dev\n```\n\nThe console is available at `http://localhost:5173`; the API server listens on\n`http://localhost:3090`.\n\nYou can also run them separately:\n\n```bash\nbun run dev:server\nbun run dev:web\n```\n\nFor a production build, the API server serves the compiled console on port 3090:\n\n```bash\nbun run build\nbun run start\n```\n\nThe backend binds to `0.0.0.0` by default. The Vite development server binds to\nlocalhost; pass `--host 0.0.0.0` only when you intentionally want to expose it:\n\n```bash\nbun run dev:web -- --host 0.0.0.0\n```\n\n## Authentication\n\nAuthentication is optional. A solo installation with Web authentication disabled\nopens the console directly. When authentication is enabled, the same console\nrequires a session and provides sign-in, signup when allowed by server policy,\nand sign-out.\n\n## Console navigation\n\nThe project rail is the main navigation surface:\n\n- **Runs** at `/console` shows runs across projects. Selecting a\n  project opens its scoped runs at `/console/p/:projectId`.\n- **Chat** at `/console/p/:projectId/chat` opens the operator conversation for\n  that project.\n- **Settings** at `/console/settings` manages assistant defaults, model tiers,\n  aliases, provider credentials, system status, GitHub identity, and alerts.\n- **Workflow builder** at `/console/builder` opens the experimental visual\n  authoring surface. Select a project before opening or creating a workflow.\n\nRegister a project with the add button in the rail. Remote Git URLs are cloned;\nlocal paths are registered in place. Removing a project removes its registration,\nnot its files.\n\n## Workflows and runs\n\nChoose a workflow from a project's run or chat view, supply its inputs, and\nlaunch it. The console follows execution through live events and exposes the\ngovernance actions that apply to the current state, including approval, rejection,\nresume, cancel, and abandon.\n\nA run can be opened from a project at\n`/console/p/:projectId/r/:runId` or directly by ID at\n`/console/r/:runId`. Direct links also work for runs that have no registered\nproject. Run details include the event log and artifacts; a graph is shown when\nthe run has the project context needed to load its workflow definition.\n\nTurn on **Alerts** in Settings to hear a sound, and see a browser notification\nwhere the browser allows it, when a run stops at an approval gate or an\nattention wait, or when it completes, fails, or is cancelled. The setting\nis saved in the current browser and is off by default. Runs that were already\nwaiting or finished when the console loaded do not alert. Settings shows when\nthe browser blocks the sound or notifications.\n\nOld settings, workflow-list, workflow-builder, and run bookmarks redirect to the\nclosest console route. Old chat and other retired pages land on the console\noverview because the conversation browser is no longer shipped.\n\n## Project chat\n\nProject chat streams assistant text and tool activity. When Web authentication\nis enabled, the console requests the signed-in user's project conversation and\nsends the active identity with each turn. On a solo installation, chat works\nwithout a user identity.\n\nAttachments are limited to five files of at most 10 MB each. The first message in\na new conversation cannot include attachments; create the conversation with a\ntext message, then attach files on the next turn.\n\n## Experimental workflow builder\n\nThe builder is retained as a bounded experiment inside the console. Use its\ncanvas, inspector, validation, and YAML preview to draft new workflows with\nprompt, command, bash, script, loop, approval, wait, and cancel nodes. Editing an\nexisting workflow and relying on a load/edit/save round trip is not supported\nuntil [#3378](https://github.com/coleam00/Archon/pull/3378) and\n[#3379](https://github.com/coleam00/Archon/pull/3379) land. Edit existing\nworkflows as YAML. The builder also does not represent include directives, loop\ngroups, or workflow sub-run nodes. Workflow YAML remains the authoritative\nartifact.\n\n## Further reading\n\n- [Quickstart](/getting-started/quick-start/)\n- [Configuration](/getting-started/configuration/)\n- [Workflow language](/reference/workflow-language/)\n- [API Reference](/reference/api/)\n","packages/docs-web/src/content/docs/brand/index.md":"---\ntitle: Brand\ndescription: Archon brand foundation — colors, logo, and usage guidelines.\n---\n\n## Quick reference\n\nThe essentials at a glance:\n\n- **Primary gradient** — <code style=\"color:#ED10EC\">#ED10EC</code> → <code style=\"color:#8E40C8\">#8E40C8</code> → <code style=\"color:#06CE94\">#06CE94</code>\n- **Surface** — <code style=\"color:#0F1115;background:#fff;padding:0 6px;border-radius:3px\">#0F1115</code>\n- **Logo** — the shield mark with the abstract Archon glyph, stroked with the primary gradient\n\nUse the embedded foundation below for the canonical version. If you need a vector asset or have a question about an unusual usage, open a discussion on [GitHub](https://github.com/coleam00/Archon/discussions).\n\n## Full brand sheet\n\nThe Archon brand foundation lives below. It's a single self-contained page covering the logo, color system, typography, and approved usage.\n\n<iframe\n  src=\"/brand/foundation.html\"\n  title=\"Archon Brand Foundation\"\n  style=\"width:100%;height:80vh;border:1px solid var(--sl-color-gray-5);border-radius:8px;background:#0F1115;\"\n  loading=\"lazy\"\n></iframe>\n\nPrefer a full-window view? [Open the brand sheet in a new tab](/brand/foundation.html).\n","packages/docs-web/src/content/docs/contributing/cli-internals.md":"---\ntitle: CLI Internals\ndescription: Technical reference for the Archon CLI package — entry point flow, command routing, worktree logic, and adapter details.\ncategory: contributing\narea: cli\naudience: [developer]\nstatus: current\nsidebar:\n  order: 2\n---\n\nTechnical reference for understanding CLI internals.\n\n## Package Structure\n\n```\npackages/cli/\n├── src/\n│   ├── cli.ts              # Entry point, argument parsing, routing\n│   ├── commands/\n│   │   ├── workflow.ts     # workflow route module: re-exports every subcommand; reset-sessions/cleanup/event\n│   │   ├── workflow-control.ts # workflow resume/abandon/cancel/answer/approve/reject/respond (delegate to @archon/core/operations)\n│   │   ├── workflow-query.ts # workflow list/test/status/get/runs/logs/wait\n│   │   ├── workflow-run-*.ts # workflow run: select, admit, launch, execute\n│   │   ├── isolation.ts    # isolation list/cleanup (list/merged-cleanup delegate to @archon/core/operations)\n│   │   ├── setup.ts        # setup command implementation\n│   │   ├── chat.ts         # chat command implementation\n│   │   ├── validate.ts     # validate command implementation\n│   │   └── version.ts      # version command\n│   └── adapters/\n│       └── cli-adapter.ts  # IPlatformAdapter for stdout\n└── package.json            # Defines \"archon\" binary\n```\n\n## Entry Point Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon <command> [subcommand] [options] [arguments]             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ strip-cwd-env-boot  (first import, side-effect)                 │\n│   stripCwdEnv(): deletes Bun-auto-loaded <cwd>/.env* keys from  │\n│   process.env + CLAUDE_CODE_* session markers. Emits            │\n│   [archon] stripped N keys from <cwd> (...) when N > 0.         │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ loadCliEnv(cwd) — wraps loadArchonEnv with identity protection  │\n│   1. ~/.archon/.env        (home scope)                         │\n│   2. <cwd>/.archon/.env    (repo scope, exceptions below)       │\n│   Emits one [archon] loaded N keys from <path> line per file    │\n│   when N > 0 and ARCHON_VERBOSE_BOOT=1 or LOG_LEVEL=debug/trace.│\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Parse arguments                                         │\n│                 --cwd, --branch, --no-worktree, --help          │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Project resolution                                      │\n│                 Honor folder selection and registered folders;  │\n│                 otherwise resolve the nearest Git checkout      │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Route to command handler                                │\n│                 switch(command) → workflow | isolation | version│\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Exit with code, always closeDatabase()                  │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/cli.ts`\n\nRepository env normally overrides home env. The CLI preserves the user-scope `GH_CONFIG_DIR` (including its absence), so repository env cannot redirect the stored gh login. Inside a workflow, inherited GitHub identity values take precedence over both env files, including empty values that disable host credentials. See [forge plugin configuration](/reference/forge/#plugin-configuration).\n\n**Project resolution:**\n\n- `workflow run --folder` uses the supplied canonical directory, even inside Git, and explicitly registers it as a folder project.\n- A registered folder and its ordinary descendants resolve to the folder directory. Archon runs folder workflows in place without managing a worktree, branch or PR.\n- A Git repository nested inside a registered folder retains its own repository identity. Other Git paths resolve to the nearest repository root, preserving linked-checkout ownership.\n- Unregistered non-Git execution requires folder registration; first-use CLI execution uses `workflow run --folder`. Without registration or that flag, execution exits with code 1.\n- File-only dry runs and fixture tests can inspect local files without registration. On a readable registry they honor folder ownership; if lookup fails they use the proven Git root or supplied directory. `workflow list` and `validate` retain their Git-root fallback when the registry cannot open.\n- `version`, `help`, `setup`, and `chat` bypass project resolution.\n\n---\n\n## `workflow list` Flow\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ archon workflow list [name] [--full] [--json]                    │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ workflow-query.ts  workflowListCommand(cwd, { name, full, json })│\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ @archon/workflows/workflow-discovery                              │\n│ discoverWorkflowsWithConfig(cwd, loadConfig, providerRegistry)    │\n│ - Loads bundled defaults                                         │\n│ - Searches .archon/workflows/ recursively                        │\n│ - Merges (repo overrides defaults by name)                       │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ Optional name resolution; project errors remain in the result    │\n│ full=false: bounded description + structured truncation state    │\n│ full=true: exact authored description                            │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n               ┌───────────────┴───────────────┐\n               │ json=true                     │ json=false\n               ▼                               ▼\n┌──────────────────────────┐   ┌───────────────────────────────────┐\n│ JSON output to stdout    │   │ Human-readable list to stdout     │\n│ descriptionTruncated     │   │ shortened descriptions carry     │\n│ { workflows, errors }    │   │ the ` [truncated]` marker         │\n└──────────────────────────┘   └───────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/commands/workflow-query.ts`\n\n---\n\n## `workflow run` Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon workflow run <name> [message] [--branch X] [--detach] …  │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ selectRunWorkflow()                                             │\n│ - Locate the run a continuation resumes                         │\n│ - Capture the workflow source, then discover from the capture   │\n│ - Find the workflow by name (error lists available)             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ admitRunExecution()   (--dry-run stops before this)             │\n│ - Flag, worktree policy, input and capability gates             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                    ┌─────────────┴─────────────┐\n                    │                           │\n                --detach                    (default)\n                    │                           │\n                    ▼                           ▼\n┌───────────────────────────┐   ┌───────────────────────────────────┐\n│ launchDetachedOwnedRun()  │   │ prepareForegroundLaunch()         │\n│ Pre-flight, write the run │   │ Destination, project, adoption    │\n│ row, fork the child       │   │ lane, --resume binding, AI config │\n└───────────────────────────┘   └───────────────┬───────────────────┘\n                                                │\n                                                ▼\n                                ┌───────────────────────────────────┐\n                                │ provisionWorkspace()              │\n                                │ Folder seam, reused --branch      │\n                                │ worktree, or a new worktree       │\n                                └───────────────┬───────────────────┘\n                                                │\n                                                ▼\n                                ┌───────────────────────────────────┐\n                                │ executeOwnedRun()                 │\n                                │ Signal guard, progress, engine    │\n                                │ submit or resume, wait, teardown, │\n                                │ result                            │\n                                └───────────────────────────────────┘\n```\n\n**Code:** `workflowRunCommand` in `packages/cli/src/commands/workflow-run-owned.ts`, which owns the captured source and the durable-wait loop. The phases live in `workflow-run-admission.ts`, `workflow-run-launch.ts` and `workflow-run-execution.ts` beside it.\n\n**Worktree Provider:** `packages/isolation/src/providers/worktree.ts`\n\n---\n\n## `workflow event emit` Flow\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ archon workflow event emit --run-id <run-id> --type <type> [...] │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ cli.ts  Validate --run-id, --type (required)                     │\n│         Validate --type against WORKFLOW_EVENT_TYPES              │\n│         Parse --data as JSON (warn + skip if invalid)            │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ workflow.ts  workflowEventEmitCommand(..., cwd)                   │\n│              Resolve an unambiguous run-id prefix                 │\n│              Node state: persistWorkflowEvent(...)               │\n│              Observability: createWorkflowEvent(...)             │\n│              Run-ID resolution may fail                           │\n└──────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/cli.ts` (case 'event'), `packages/cli/src/commands/workflow.ts:workflowEventEmitCommand`\n\n**Contract:** The shared `isNodeStateEventType` predicate routes node-state events through\n`persistWorkflowEvent`, which propagates storage failures. The CLI prints `Event persisted`\nonly after that write succeeds. Other events use `createWorkflowEvent` and retain best-effort\npersistence: `Event submitted (best-effort)` does not guarantee storage. Run-ID resolution\ncan fail before either write.\n\n---\n\n## `isolation list` Flow\n\n```text\narchon isolation list → isolationListCommand()\n  │\n  ├─ @archon/core listEnvironments()\n  │    ├─ isolationDb.listAllActiveWithCodebase()\n  │    ├─ For each codebase: isolationDb.listByCodebaseWithAge()\n  │    │    └─ Reconcile missing worktrees, preserving live-run ownership\n  │    └─ Return environments grouped by codebase and reconciliation totals\n  │\n  ├─ Print worktree details and totals (return early if no codebases)\n  │\n  ├─ @archon/core listContainerEnvironments()\n  │    ├─ isolationDb.listActiveContainerEnvironments()\n  │    ├─ getRunByIsolationEnvId() for each container\n  │    │    └─ Return matched run or null; throw on query/status errors\n  │    └─ Build summaries; catch lookup errors and set runStatus: lookup-failed\n  │\n  └─ Print container details and totals: run status, no run (orphan),\n     or run lookup failed\n```\n\n**Code:** `packages/cli/src/commands/isolation.ts` (`isolationListCommand`),\n`packages/core/src/operations/isolation-operations.ts`\n(`listEnvironments`), `packages/core/src/services/cleanup/containers.ts`\n(`listContainerEnvironments`), `packages/core/src/db/workflow-run-reads.ts`\n(`getRunByIsolationEnvId`).\n\nThe container listing matches `metadata.isolation_env_id`; no match does not\nestablish that cleanup can remove the container. Cleanup checks wider live\nownership. A failed lookup is logged and displayed as `run lookup failed`.\n\n---\n\n## `isolation cleanup` Flow\n\n```text\narchon isolation cleanup [days] (default: 7)\n  │\n  ├─ Reconcile missing worktrees (preserve container rows)\n  │\n  ├─ findStaleEnvironments(days)\n  │    └─ Filter provider = 'worktree'\n  │         └─ For each stale worktree:\n  │              ├─ Skip environments owned by resumable runs\n  │              ├─ Skip any with a commit on no remote (getCleanupRunLock)\n  │              └─ provider.destroy(path, options)\n  │                   └─ Mark destroyed only after complete removal\n  │\n  └─ cleanupContainerEnvironments(days), even when no worktrees are stale\n       ├─ Skip containers owned by resumable runs\n       └─ Container backend removes container and volume\n            └─ Mark destroyed only after successful removal\n```\n\n**Code:** `isolationCleanupCommand` in `packages/cli/src/commands/isolation.ts`\n\n---\n\n## `isolation cleanup --merged` Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon isolation cleanup --merged [--include-closed]            │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ isolation.ts  isolationCleanupMergedCommand({ includeClosed })  │\n│ For each codebase → cleanupMergedWorktrees(codebaseId, path)    │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                     For each active environment\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ judgeBranchForRemoval() — three-signal union                    │\n│  localBranchExists()     gate: (a) and (b) need the local ref   │\n│  (a) isBranchMerged()    git ancestry (fast-forward/merge)      │\n│  (b) isPatchEquivalent() git cherry  (1-commit squash-merge)    │\n│  (c) getPrState()        gh CLI      (MERGED/CLOSED/OPEN/NONE/  │\n│                                      UNAVAILABLE)               │\n│                                                                 │\n│  any git signal          → 'merged' if the worktree HEAD        │\n│                            also passes `git cherry <base> HEAD` │\n│  MERGED                  → 'merged' if the worktree HEAD        │\n│                            and the branch ref (where each       │\n│                            exists) are at or behind the PR head │\n│  CLOSED                  → 'pr-closed' the same way, only if    │\n│                            includeClosed                        │\n│  OPEN                    → 'open-pr', always kept               │\n│  UNAVAILABLE             → 'pr-unavailable', kept and reported  │\n│  NONE and ref present    → 'unmerged', not reclaimed            │\n│  NONE and ref gone       → 'unjudgeable', kept and reported     │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │ 'merged' or 'pr-closed'\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ Guard checks: no uncommitted changes, no run lock (checkRemoval │\n│ → getCleanupRunLock; a failed run's lock is released by the     │\n│ verdict, running/pending/paused runs always lock)               │\n│ provider.destroy() → remove worktree and local branch           │\n│ → commit registration retirement; retain remote branch         │\n└─────────────────────────────────────────────────────────────────┘\n```\n\nCompletion and merged cleanup retain remote branches, including with force,\nbecause registration ownership cannot be proved after retirement. Retention is\nlogged as a warning and displayed in both completion and merged-cleanup reports.\nEnable GitHub's **Automatically delete head branches** setting for\nmerged pull requests, or follow [manual remote branch recovery](/reference/cli/#recovering-remote-branch-cleanup).\n\nSignals are evaluated in order — the first positive match short-circuits to avoid\nunnecessary `gh` API calls. The `gh` CLI is a soft dependency: when it is not installed or\nthe remote is not GitHub, only git signals are used (`NONE`). When `gh` is there but fails\n(auth, rate limit, unreadable output) the result is `UNAVAILABLE`, and the environment is\nkept and reported rather than passed to the staleness sweep.\n\nA multi-commit squash merge is invisible to both git signals, so PR state is the only\none that sees it. Both git signals also need the local branch ref: once it is gone, the\nPR is the only judge, and a branch with no PR is reported as unverifiable instead of\nfailing the check. A MERGED or CLOSED PR only covers the commits it carried. Run\nbranch names are reused, and removal deletes both the worktree and the branch, so each\nlocal tip that still exists, the worktree's HEAD and the branch ref, must be the PR's head\ncommit or an ancestor of it (`isRevCoveredBy`). A detached HEAD or a reused branch with\ncommits past the PR head is unmerged work. The same holds when git proves the merge: the worktree's\nHEAD must also pass `git cherry <base> HEAD`, which answers for both git signals (an\nancestor of the base lists nothing, a squash-merged commit lists as `-`). A HEAD it\ncannot answer for is reported as a failed merge check. A PR head pushed from elsewhere and never fetched here is\nfetched by SHA from the branch's remote first; if that fetch fails, the check fails and\nthe worktree is kept, with the fetch error in the reason. The scheduled sweep (`runScheduledCleanup`) makes the same call, so\nboth paths agree on what counts as merged.\n\n**Code:** `packages/core/src/services/cleanup/` — `judgeBranchForRemoval()` in `merge-verdict.ts`, `cleanupMergedWorktrees()` in `merged-sweep.ts`, `getCleanupRunLock()` in `run-lock.ts`; `runScheduledCleanup()` in `packages/core/src/services/cleanup-service.ts`\n**Code:** `packages/isolation/src/pr-state.ts` — `getPrState()`\n**Code:** `packages/git/src/branch.ts` — `isPatchEquivalent()`, `localBranchExists()`\n\n---\n\n## CLI Adapter\n\nImplements `IPlatformAdapter` for terminal output.\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ CLIAdapter                                                      │\n├─────────────────────────────────────────────────────────────────┤\n│ sendMessage(convId, msg) → Output to stdout                     │\n│ getStreamingMode()       → 'batch'                              │\n│ getPlatformType()        → 'cli'                                │\n│ ensureThread()           → passthrough                          │\n│ start() / stop()         → no-op                                │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/adapters/cli-adapter.ts:13-47`\n\n---\n\n## Key Dependencies\n\n| Function | Package | Location | Purpose |\n|----------|---------|----------|---------|\n| `discoverWorkflowsWithConfig(cwd, loadConfig, providerRegistry)` | `@archon/workflows/workflow-discovery` | `workflows/src/workflow-discovery.ts` | Find and parse workflow YAML |\n| `executeWorkflow(...)` | `@archon/workflows/executor` | `workflows/src/executor.ts` | Run workflow steps |\n| `getIsolationProvider()` | `@archon/isolation` | `isolation/src/factory.ts` | Get WorktreeProvider singleton |\n| `conversationDb.*` | `@archon/core` | `core/src/db/conversations.ts` | Conversation CRUD |\n| `codebaseDb.*` | `@archon/core` | `core/src/db/codebases.ts` | Codebase CRUD |\n| `isolationDb.*` | `@archon/core` | `core/src/db/isolation-environments.ts` | Worktree tracking |\n| `git.*` | `@archon/git` | `packages/git/src/` | Git operations |\n| `closeDatabase()` | `@archon/core` | `core/src/db/connection.ts` | Clean shutdown |\n\n---\n\n## Conversation ID Format\n\nCLI conversations use ID format: `cli-{timestamp}-{random}`\n\nThe random suffix contains 16 cryptographically random bytes encoded as 32 hex characters.\n\nGenerated at: `packages/cli/src/utils/conversation-id.ts`\n\n---\n\n## Worktree Reuse Logic\n\nWhen `--branch` is provided:\n\n1. **Lookup:** `isolationDb.findActiveByWorkflow(codebaseId, 'task', branchName)`\n2. **Health check:** `provider.healthCheck(path)` on existing\n3. **Reuse:** If found and healthy (warns if `--from` was specified but not applied)\n4. **Create:** If not found or unhealthy -- passes a `taskBranch: { kind: 'new', fromBranch }`\n   selection to the provider when `--from` is specified. Adoption instead passes\n   `taskBranch: { kind: 'existing', branch }`, which checks out that exact local branch\n   without creating a child branch or syncing it to a remote.\n\nWorktrees stored at: `~/.archon/workspaces/<owner>/<repo>/worktrees/<branch-slug>/`\n\n**Code:** `provisionWorktree` in `packages/cli/src/commands/workflow-run-launch.ts`\n\n---\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| 0 | Success, except for the JSON control refusals noted below. |\n| 1 | Error. Answer commands (`answer`, `respond`, `approve`, `reject`) and `resume --detach` also return 1 on refusal. Human-mode diagnostics go to stderr; JSON refusals emit `ok: false` with the reason on stdout. |\n\nNon-detached JSON `resume`, `abandon`, and `cancel` can return 0 with `ok: false`.\nCheck `ok` when consuming those acknowledgments.\n\n---\n\n## Database Connection\n\n- Connection opened on first database call\n- Always closed in `finally` block after command completes\n- **Default: SQLite** at `~/.archon/archon.db` (zero setup, auto-initialized)\n- **Optional: PostgreSQL** when `DATABASE_URL` is set (for cloud/advanced deployments)\n\n**Code:** `packages/cli/src/cli.ts`\n","packages/docs-web/src/content/docs/contributing/dx-quirks.md":"---\ntitle: DX Quirks\ndescription: Known development experience quirks and workarounds when working on the Archon codebase.\ncategory: contributing\naudience: [developer]\nstatus: current\nsidebar:\n  order: 6\n---\n\nDevelopment experience notes and workarounds.\n\n## Bun Log Elision\n\nWhen running `bun dev` from the repo root, Bun's `--filter` truncates logs:\n\n```\n@archon/server dev $ bun --watch src/index.ts\n│ [129 lines elided]\n│ [Hono] Server listening on port 3090\n└─ Running...\n```\n\n**To see full logs**, run directly from the server package:\n\n```bash\ncd packages/server && bun --watch src/index.ts\n```\n\nOr:\n\n```bash\nbun --cwd packages/server run dev\n```\n\nNote: The root `bun dev` uses `--filter` to fix hot reload path issues, but this comes with log condensing.\n\n## `mock.module()` merges over the real module\n\nBun merges a `mock.module()` factory over the real module: an export the factory leaves out keeps its real implementation. When a mocked module gains an export, every factory that does not list it starts running real code (a database, the network, the filesystem) while its tests still pass, often until a 5-second timeout.\n\n`bun run check:mock-modules` (validate id `mock-modules`, run in CI) fails on every factory that leaves out a value export of the module it mocks, naming the file, line, specifier and missing names. Mock each missing export, or spread the real module to keep the rest real on purpose:\n\n```ts\nmock.module('../db/workflows', async () => ({\n  ...(await import('../db/workflows')),\n  getWorkflowRun: mockGetWorkflowRun,\n}));\n```\n\nA spreading factory provides every export, including ones the module gains later; that is the author's declaration. Where test files share a process (packages other than `@archon/core`, unless the file has its own group), the `import()` can return a module an earlier file already mocked.\n\nFactories that were incomplete when the check landed are grandfathered in `scripts/mock-module-baseline.json`. A grandfathered site may omit only the exports its module's grandfathered sites omitted then, so a new export still fails at every one of them. The baseline only shrinks: after completing a factory, run `bun run check:mock-modules --write`. `bun run check:ratchet` refuses entries the baseline gains unless a commit in the change carries `Ratchet-Allow: scripts/mock-module-baseline.json`.\n\nWhat the check does not prove:\n\n- It reads factory keys from the factory's type, so an `as` cast can misstate them.\n- It checks only literal specifiers, and refuses a non-literal one.\n- It refuses a module TypeScript cannot read (no type declarations, or a path that does not resolve) unless the site is grandfathered.\n- A grandfathered factory can drop a key that another grandfathered site of the same module already omits.\n- It says nothing about a client a test never stubbed, or about I/O outside `mock.module()`. The adapters package preloads `no-network` (it traps only `globalThis.fetch`) and `no-archon-home-writes` guards for part of that; Bun reads `bunfig.toml` only from the working directory, so those guards apply only when the tests run from `packages/adapters`. Their headers list the limits.\n- It runs in validate and CI, not inside `bun test`.\n\n## `mock.module()` Pollution\n\nBun's `mock.module()` is process-global and irreversible — `mock.restore()` does NOT undo it.\n\n- Never add `afterAll(() => mock.restore())` for `mock.module()` cleanup — it has no effect\n- Use `spyOn()` for internal modules that other test files import directly (e.g., `spyOn(git, 'checkout')`) — `spy.mockRestore()` DOES work for spies\n- Never `mock.module()` a module path that another test file also `mock.module()`s with a different implementation\n- When adding a new test file with `mock.module()` to `packages/core`, make its first line exactly `// @archon-test-isolated` so it runs in its own process. Core discovers its tests, and discovery fails on a file that calls `mock.module()` without the marker. A file that mocks through a helper needs the marker too; discovery cannot see that call\n- In the other packages, give the file its own entry in `testGroups` in the package's `package.json`, or its own `bun test` step for a package still using a `&&` chain\n- A package's `testConcurrency` in `package.json` runs that many `testGroups` at once, each still in its own process; leave it out to run groups one at a time\n- Groups start slowest first, by the times in `scripts/test-weights.json`. CI splits slow shards into parts with `ARCHON_TEST_PART=<index>/<count>`, using the same times; `ARCHON_TEST_PART=1/2 bun run test` in a package runs that part locally. A new group with no recorded time counts as one second, so refresh the file with `bun scripts/update-test-weights.ts <run-id>` when one part of a shard finishes well after its sibling\n\n## Worktree Port Allocation\n\nWorktrees auto-allocate ports (3190–4089 range, hash-based on path). Same worktree always gets same port.\n\n- Main repo defaults to 3090\n- Override: `PORT=4000 bun dev`\n- Same worktree always gets same port (deterministic)\n- If that port is already taken — another worktree hashed to it, or any unrelated\n  process holds it — the server takes the next free port in the range and logs\n  `worktree_port_reallocated` with both the port it wanted and the one it got.\n  The root `bun dev` launcher passes that selected port to both the server and Web\n  client, so the Vite proxy and SSE streams use the endpoint the server binds. It\n  returns to the hashed port once that frees up.\n- `PORT` is never moved: an explicit override binds where you said, or fails.\n\n## `bun run test` vs `bun test`\n\n**NEVER run `bun test` from the repo root** — it discovers all test files across all packages and runs them in one process, causing ~135 mock pollution failures.\n\nAlways use `bun run test`, which runs each package's own grouped suite for isolation.\n\n### Running one test\n\n`bun run test <path>` runs that path.\n\n```bash\nbun run test packages/paths/src/skills.test.ts   # from the repo root\ncd packages/paths && bun run test src/skills.test.ts\n```\n\nFrom the repo root, an argument naming a path under `packages/<name>/`, `scripts/` or\n`.archon/scripts/` decides where `bun test` runs; flags and substring filters are\nforwarded unchanged. Paths naming several packages run one package at a time and stop at\nthe first failure. An argument that names no owner fails instead of running the suite.\n\nA package whose `scripts.test` is still a `bun test ... && bun test ...` chain rather than\n`bun run ../../scripts/package-tests.ts` ignores the argument when you run it from inside\nthe package: `bun run` appends arguments to the script, so the whole chain runs and a wrong\npath still reports a pass. Run those from the repo root, which routes the path itself.\n","packages/docs-web/src/content/docs/contributing/index.md":"---\ntitle: Contributing\ndescription: Developer guides for contributing to Archon — internals, testing, releasing.\ncategory: contributing\naudience: [developer]\nsidebar:\n  order: 0\n---\n\nGuides for developers contributing to the Archon codebase.\n\n## Developer Guides\n\n- **[New Developer Guide](/contributing/new-developer-guide/)** — Codebase orientation, architecture overview, and first steps\n- **[CLI Internals](/contributing/cli-internals/)** — CLI package internals, command routing, and worktree logic\n- **[Releasing](/contributing/releasing/)** — Version management and release process\n- **[DX Quirks](/contributing/dx-quirks/)** — Known development experience issues and workarounds\n\n","packages/docs-web/src/content/docs/contributing/new-developer-guide.md":"---\ntitle: New Developer Guide\ndescription: Codebase orientation for new Archon developers — architecture overview, workflows, platforms, and first steps.\ncategory: contributing\naudience: [developer]\nstatus: current\nsidebar:\n  order: 1\n---\n\n> **TL;DR**: Archon lets you control AI coding assistants (Claude Code, Codex) from your phone via Telegram, Slack, Discord, or GitHub. Think of it as a remote control for AI pair programming.\n\n---\n\n## The Problem We Solve\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│                        WITHOUT ARCHON                               │\n│                                                                     │\n│   You're on the train, phone in hand...                            │\n│                                                                     │\n│   ┌──────────┐     ❌ Can't SSH      ┌──────────────────┐          │\n│   │  Phone   │ ──────────────────────│  Dev Machine     │          │\n│   │          │     ❌ No terminal    │  (Claude Code)   │          │\n│   └──────────┘     ❌ No IDE         └──────────────────┘          │\n│                                                                     │\n│   \"I wish I could just message Claude to fix that bug...\"          │\n└─────────────────────────────────────────────────────────────────────┘\n\n┌─────────────────────────────────────────────────────────────────────┐\n│                         WITH ARCHON                                 │\n│                                                                     │\n│   ┌──────────┐                       ┌──────────────────┐          │\n│   │  Phone   │ ─────Telegram────────▶│  Archon Server   │          │\n│   │          │     \"fix issue #42\"   │                  │          │\n│   └──────────┘                       │  ┌────────────┐  │          │\n│        │                             │  │Claude Code │  │          │\n│        │                             │  │   SDK      │  │          │\n│        │                             │  └─────┬──────┘  │          │\n│        │                             │        │         │          │\n│        │                             │  ┌─────▼──────┐  │          │\n│        │◀────\"PR created #127\"───────│  │ Git Repo   │  │          │\n│        │                             │  │ (worktree) │  │          │\n│                                      │  └────────────┘  │          │\n│                                      └──────────────────┘          │\n│                                                                     │\n│   You just fixed a bug from your phone.                            │\n└─────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Core Concept: Message → AI → Code → Response\n\n```\n┌──────────────────────────────────────────────────────────────────────────┐\n│                                                                          │\n│   USER                    ARCHON                         CODEBASE        │\n│                                                                          │\n│   ┌─────────┐            ┌─────────────────┐            ┌──────────┐    │\n│   │Telegram │            │                 │            │          │    │\n│   │  Slack  │───Message─▶│   Orchestrator  │───Claude──▶│ Git Repo │    │\n│   │ Discord │            │                 │   Code     │          │    │\n│   │ GitHub  │◀──Response─│   (routes to    │◀──────────│ (files)  │    │\n│   └─────────┘            │    AI client)   │            └──────────┘    │\n│                          └─────────────────┘                             │\n│                                                                          │\n│   That's it. You message, AI works on code, you get results.            │\n│                                                                          │\n└──────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## The Four Ways to Use Archon\n\n### 1. Command Line (Local Execution)\n\nRun workflows directly from your terminal without needing the server:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ TERMINAL                                                        │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ $ bun run cli workflow list                                     │\n│                                                                 │\n│ Available workflows in .archon/workflows/:                     │\n│   - archon-ship                  Issue to reviewed PR           │\n│   - archon-investigate           Prove a root cause             │\n│   - archon-review                Parallel PR review             │\n│                                                                 │\n│ $ bun run cli workflow run archon-investigate \"What does the    │\n│   orchestrator do?\"                                             │\n│                                                                 │\n│ 🔧 READ                                                         │\n│ Reading: packages/core/src/orchestrator/orchestrator.ts                │\n│                                                                 │\n│ The orchestrator is the main entry point that routes incoming  │\n│ messages. It checks if it's a slash command, loads conversation│\n│ context from the database, and routes to the appropriate AI     │\n│ client for processing...                                        │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Good for:** Running workflows locally, testing, automation scripts, CI/CD\n\n### 2. Direct Chat (Simple Questions)\n\nJust talk to the AI like you would in Claude Code terminal:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ TELEGRAM CHAT                                                   │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ You: What does the handleMessage function do?                   │\n│                                                                 │\n│ Archon: Looking at packages/core/src/orchestrator/orchestrator.ts...          │\n│                                                                 │\n│         The handleMessage function is the main entry point      │\n│         that routes incoming messages. It:                      │\n│         1. Checks if it's a slash command                       │\n│         2. Loads conversation context from database             │\n│         3. Routes to AI client for processing                   │\n│         4. Streams responses back to platform                   │\n│                                                                 │\n│         See: packages/core/src/orchestrator/orchestrator.ts            │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### 3. Slash Commands (Specific Operations)\n\nDeterministic commands that don't involve AI:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ SLASH COMMANDS                                                  │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ /clone https://github.com/user/repo    Clone a repository      │\n│ /status                                 Show current state      │\n│ /repos                                  List available repos    │\n│ /setcwd /path/to/dir                   Change working dir      │\n│ /reset                                  Clear AI session        │\n│ /help                                   Show all commands       │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### 4. Workflows (Multi-Step Automation)\n\nThis is where Archon shines - automated multi-step AI workflows:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ GITHUB ISSUE #42                                                │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ Title: Login button doesn't work on mobile                      │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @user commented:                                                │\n│   @archon fix this issue                                        │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @archon commented:                                              │\n│   🔍 Investigation Complete                                     │\n│                                                                 │\n│   Root Cause: Touch event handler missing on mobile             │\n│   File: packages/server/src/components/LoginButton.tsx:45                       │\n│   Fix: Add onTouchEnd handler alongside onClick                 │\n│                                                                 │\n│   Creating PR...                                                │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @archon commented:                                              │\n│   ✅ Fix implemented: PR #127                                   │\n│   - Added touch event handling                                  │\n│   - Added mobile viewport tests                                 │\n│   - All tests passing                                           │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## How Workflows Work (The Magic)\n\nA workflow is a YAML file that chains AI prompts together:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   .archon/workflows/fix-github-issue.yaml                              │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ name: fix-github-issue                                          │  │\n│   │ description: Investigate and fix a GitHub issue                 │  │\n│   │                                                                 │  │\n│   │ nodes:                                                          │  │\n│   │   - id: investigate                                             │  │\n│   │     command: investigate-issue    ◀── Node 1: Research         │  │\n│   │   - id: implement                                               │  │\n│   │     command: implement-issue      ◀── Node 2: Fix              │  │\n│   │     depends_on: [investigate]                                   │  │\n│   │     context: fresh                                              │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│                              │                                          │\n│                              ▼                                          │\n│                                                                         │\n│   EXECUTION FLOW:                                                       │\n│                                                                         │\n│   ┌──────────────────┐      ┌──────────────────┐      ┌────────────┐  │\n│   │  investigate-    │      │   implement-     │      │            │  │\n│   │  issue.md        │─────▶│   issue.md       │─────▶│  PR #127   │  │\n│   │                  │      │                  │      │            │  │\n│   │  - Read issue    │      │  - Read artifact │      │  Created!  │  │\n│   │  - Explore code  │      │  - Make changes  │      │            │  │\n│   │  - Find root     │      │  - Run tests     │      │            │  │\n│   │    cause         │      │  - Commit        │      │            │  │\n│   │  - Save artifact │      │  - Create PR     │      │            │  │\n│   └──────────────────┘      └──────────────────┘      └────────────┘  │\n│                                                                         │\n│   Each \"command\" is a markdown file with AI instructions.              │\n│   The workflow executor runs nodes in dependency order.                │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## The Router: How Archon Picks Workflows\n\nWhen you send a message, an AI \"router\" decides what to do:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   USER MESSAGE                           ROUTER DECISION                │\n│                                                                         │\n│   \"fix this issue\"          ───────▶     archon-ship                   │\n│   \"review this PR\"          ───────▶     archon-review                 │\n│   \"what does X do?\"         ───────▶     answered directly, no workflow│\n│   \"update sharp\"            ───────▶     archon-upkeep                 │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   HOW IT WORKS:                                                         │\n│                                                                         │\n│   ┌──────────┐     ┌─────────────────────────────────────┐             │\n│   │ Message  │────▶│ Router AI reads workflow descriptions│             │\n│   │          │     │ and picks the best match             │             │\n│   └──────────┘     └──────────────────┬──────────────────┘             │\n│                                       │                                 │\n│                                       ▼                                 │\n│                    ┌─────────────────────────────────────┐             │\n│                    │ invoke_workflow: archon-ship        │             │\n│                    └─────────────────────────────────────┘             │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Available Workflows\n\nThe table below lists the key workflows in the bundled `sdlc` pack. All bundled workflows are prefixed with `archon-`. Run `bun run cli workflow list` to see the full current list.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   WORKFLOW                              TRIGGER PHRASES    WHAT IT DOES │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-ship               \"fix this issue\"        Triage +       │  │\n│   │                           \"implement #42\"         deliver a PR   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-review             \"review this PR\"        Parallel       │  │\n│   │                           \"is this ready\"         review lenses  │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-deliver            \"deliver this plan\"     Implement +    │  │\n│   │                           \"take this to a PR\"     PR + review    │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-upkeep             \"update sharp\"          One dependency │  │\n│   │                           \"bump X to latest\"      bump + PR      │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Parallel Agents: The PR Review Example\n\nA quick route agent picks which specialist lenses the change earns, and `archon-review` runs them in fresh sessions as shown below. The code lens uses the configured `large` model tier.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   USER: \"review this PR\"                                               │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 1: scope + route          Resolve the PR, its diff, the    │  │\n│   │                                contract, and which lenses run   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 2: PARALLEL LENSES (each in a fresh session)               │  │\n│   │                                                                 │  │\n│   │   ┌──────────────┐  ┌──────────────┐                           │  │\n│   │   │ code         │  │ seams        │                           │  │\n│   │   │              │  │              │                           │  │\n│   │   │ Bugs,        │  │ Contracts    │                           │  │\n│   │   │ correctness  │  │ that drift   │                           │  │\n│   │   └──────────────┘  └──────────────┘                           │  │\n│   │                                                                 │  │\n│   │   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐         │  │\n│   │   │ tests        │  │ errors       │  │ docs         │         │  │\n│   │   │              │  │              │  │ (when docs   │         │  │\n│   │   │ Missing      │  │ Visibility & │  │  change)     │         │  │\n│   │   │ coverage     │  │ containment  │  │ Stale docs   │         │  │\n│   │   └──────────────┘  └──────────────┘  └──────────────┘         │  │\n│   │                                                                 │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 3: synthesize             Combine findings into one verdict│  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 4: publish                Post one review comment on the PR│  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\nReview never edits code. Fixing findings belongs to `archon-implement`, or to `archon-deliver`, which runs review and corrections in a loop.\n\n---\n\n## Platform Integration\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   CLI                               HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Direct command execution         │\n│   │  Terminal        │              - Real-time streaming to stdout    │\n│   │                  │              - No server needed                 │\n│   │  bun run cli     │              - Good for local workflows         │\n│   │  workflow run    │              - Perfect for CI/CD                │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   TELEGRAM                          HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Bot polls for messages           │\n│   │  @archon_bot     │              - Real-time streaming (default)    │\n│   │                  │              - DM the bot directly              │\n│   │  \"fix issue #42\" │              - Good for mobile use              │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   SLACK                             HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Socket Mode (no webhooks)        │\n│   │  #dev-channel    │              - @mention in threads              │\n│   │                  │              - DM the bot                       │\n│   │  @archon review  │              - Good for team visibility         │\n│   │  this PR         │                                                  │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   DISCORD                           HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - WebSocket connection             │\n│   │  #coding-help    │              - @mention to activate             │\n│   │                  │              - Thread support                   │\n│   │  @Archon what    │              - Good for communities             │\n│   │  does this do?   │                                                  │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   GITHUB                            HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Webhook on issues/PRs            │\n│   │  Issue #42       │              - @archon in comments              │\n│   │                  │              - Batch mode (assembled reply)     │\n│   │  @archon fix     │              - Auto-creates PRs                 │\n│   │  this issue      │              - Good for automation              │\n│   └──────────────────┘                                                  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\nBatch mode assembles the reply before delivery. Adapters may split long replies into multiple messages or comments.\n\n## Isolation: Git Worktrees\n\nEach conversation gets its own isolated copy of the repo:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   ~/.archon/workspaces/owner/repo/worktrees/                           │\n│   │                                                                     │\n│   ├── issue-42/              ◀── Conversation about issue #42         │\n│   │   └── (full repo)            Working on fix for mobile bug         │\n│   │                                                                     │\n│   ├── pr-127/                ◀── Conversation about PR #127           │\n│   │   └── (full repo)            Reviewing code changes                │\n│   │                                                                     │\n│   └── task-dark-mode/        ◀── Manual feature work                  │\n│       └── (full repo)            Adding dark mode feature              │\n│                                                                         │\n│   WHY WORKTREES?                                                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   - Multiple conversations can work simultaneously                     │\n│   - No branch conflicts between parallel work                          │\n│   - Each gets isolated file changes                                    │\n│   - Cleaned up when issue/PR closes                                    │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Configuration Hierarchy\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   CONFIGURATION LAYERS (later overrides earlier)                       │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 1. DEFAULTS (hardcoded)                                         │  │\n│   │    assistant: claude                                            │  │\n│   │    streaming.telegram: stream                                   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 2. GLOBAL CONFIG (~/.archon/config.yaml)                        │  │\n│   │    botName: MyBot                                               │  │\n│   │    defaultAssistant: claude                                     │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 3. REPO CONFIG (.archon/config.yaml)                            │  │\n│   │    assistant: codex          # This repo prefers Codex          │  │\n│   │    commands:                                                    │  │\n│   │      folder: .claude/commands/custom                            │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 4. ENVIRONMENT VARIABLES (highest priority)                     │  │\n│   │    TELEGRAM_STREAMING_MODE=batch                                │  │\n│   │    DEFAULT_AI_ASSISTANT=claude                                  │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Directory Structure\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   YOUR REPO                         ARCHON SERVER                       │\n│                                                                         │\n│   my-app/                           ~/.archon/                          │\n│   ├── .archon/                      ├── config.yaml      (global cfg)  │\n│   │   ├── config.yaml               ├── workspaces/      (cloned repos)│\n│   │   ├── commands/                 │   └── user/repo/                 │\n│   │   │   ├── investigate-issue.md  │       ├── source/    (clone)      │\n│   │   │   ├── implement-issue.md   │       └── worktrees/ (isolation)  │\n│   │   │   └── assist.md            │           ├── issue-42/           │\n│   │   ├── workflows/               │           └── pr-127/             │\n│   │   │   ├── fix-github-issue.yaml                                    │\n│   │   │   └── assist.yaml                                              │\n│   │   └── artifacts/                                                   │\n│   │       └── issues/                                                  │\n│   │           └── issue-42.md                                          │\n│   ├── packages/                                                             │\n│   └── ...                                                              │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Quick Reference: Common Interactions\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   WHAT YOU WANT                     WHAT TO SAY (Platform/CLI)          │\n│                                                                         │\n│   Run workflow locally              bun run cli workflow run <name>     │\n│   List CLI workflows                bun run cli workflow list           │\n│   Fix a GitHub issue                \"@archon fix this issue\"            │\n│   Review a PR                       \"@archon review this PR\"            │\n│   Ask a question                    \"What does handleMessage do?\"       │\n│   Resolve conflicts                 \"@archon resolve the conflicts\"     │\n│   See current state                 \"/status\"                           │\n│   Clone a repo                      \"/clone https://github.com/u/r\"     │\n│   Switch repos                      \"/repos\" then pick one              │\n│   List available workflows          \"/workflow list\"                    │\n│   Reload workflow definitions       \"/workflow reload\"                  │\n│   Approve paused workflow           \"/workflow approve <id> [comment]\"  │\n│   Reject paused workflow           \"/workflow reject <id> [reason]\"   │\n│   Cancel stuck workflow             \"/workflow cancel\"                  │\n│   Start fresh                       \"/reset\"                            │\n│   Get help                          \"/help\"                             │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Summary\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   ARCHON = Remote Control for AI Coding Assistants                     │\n│                                                                         │\n│   ┌────────────────────────────────────────────────────────────────┐   │\n│   │                                                                │   │\n│   │   Phone/Slack/GitHub ──▶ Archon Server ──▶ AI (Claude/Codex)  │   │\n│   │                              │                    │            │   │\n│   │                              ▼                    ▼            │   │\n│   │                         Workflows           Git Worktrees      │   │\n│   │                        (automation)         (isolation)        │   │\n│   │                                                                │   │\n│   └────────────────────────────────────────────────────────────────┘   │\n│                                                                         │\n│   KEY CAPABILITIES:                                                    │\n│   ─────────────────                                                    │\n│   ✓ Message from anywhere (phone, tablet, desktop)                    │\n│   ✓ Automated multi-step workflows                                    │\n│   ✓ Parallel AI agents for complex tasks                              │\n│   ✓ Isolated environments per conversation                            │\n│   ✓ Custom prompts versioned in Git                                   │\n│   ✓ GitHub integration (issues/PRs/comments)                          │\n│                                                                         │\n│   WHEN TO USE:                                                         │\n│   ─────────────                                                        │\n│   ✓ You want to fix bugs from your phone                              │\n│   ✓ You want automated PR reviews                                     │\n│   ✓ You want GitHub issue automation                                  │\n│   ✓ You want parallel development without conflicts                   │\n│   ✓ You want custom AI workflows for your team                        │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Next Steps\n\n1. **Read**: [Quickstart](/getting-started/quick-start/) - Set up your first instance\n2. **Explore**: `.archon/workflows/` - See example workflows\n3. **Customize**: `.archon/commands/` - Create your own prompts\n4. **Configure**: `.archon/config.yaml` - Tweak settings\n\nWelcome to remote agentic coding!\n","packages/docs-web/src/content/docs/contributing/releasing.md":"---\ntitle: Releasing\ndescription: How to create a new release of the Archon CLI — version management, release process, and troubleshooting.\ncategory: contributing\narea: infra\naudience: [developer]\nstatus: current\nsidebar:\n  order: 3\n---\n\nThis guide covers how to create a new release of the Archon CLI.\n\n## Version Management\n\nVersions follow [Semantic Versioning](https://semver.org/):\n- **Major** (1.0.0): Breaking changes to CLI interface or workflow format\n- **Minor** (0.1.0): New features, new workflows, new commands\n- **Patch** (0.0.1): Bug fixes, documentation updates\n\nVersion is stored in the root `package.json` only -- this is the single source of truth.\n\n## Release Process\n\nReleases are created by merging `dev` into `main`. Never commit directly to `main`.\n\n### 1. Prepare the Release\n\nUse the `/release` skill (or follow these manual steps):\n\n```bash\n# Ensure dev is up to date\ngit checkout dev\ngit pull origin dev\n\n# Run default local validation\nbun run validate\nbun run validate --only format,workflow-fixtures\n```\n\nCI remains the full validation gate, including service and binary checks that local validation does not run.\n\nThe `/release` skill automates the following:\n1. Compares `dev` to `main` to generate changelog entries\n2. Bumps the version in root `package.json` (patch by default; use `/release minor` or `/release major` for other increments)\n3. Updates `CHANGELOG.md` following Keep a Changelog format\n4. Creates a PR from `dev` to `main`\n\n### 2. Merge and Tag\n\nOnce the release PR is reviewed and merged:\n\n```bash\n# Create and push the tag from main\ngit checkout main\ngit pull origin main\ngit tag vX.Y.Z\ngit push origin vX.Y.Z\n```\n\nThis triggers the GitHub Actions release workflow which:\n1. Builds binaries for all platforms (macOS arm64/x64, Linux arm64/x64, Windows x64)\n2. Generates checksums\n3. Creates a GitHub Release with all artifacts\n\n### 3. Update Homebrew Formula (Optional)\n\nAfter the release workflow completes:\n\n```bash\n# Update checksums in the Homebrew formula\n./scripts/update-homebrew.sh vX.Y.Z\n\n# Review and commit\ngit diff homebrew/archon.rb\ngit add homebrew/archon.rb\ngit commit -m \"chore: update Homebrew formula for vX.Y.Z\"\ngit push origin main\n```\n\nIf you maintain a Homebrew tap (`homebrew-archon`), copy the updated formula there.\n\n### 4. Verify the Release\n\n```bash\n# Test the install script (only works if repo is public)\ncurl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash\n\n# Verify version\narchon version\n```\n\n> **Note: Private Repository Installation**\n>\n> If the repository is private, the curl install script won't work for anonymous users.\n> Use the GitHub CLI instead:\n>\n> ```bash\n> # Download and install using gh (requires GitHub authentication)\n> gh release download v0.2.0 --repo coleam00/Archon \\\n>   --pattern \"archon-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')\" \\\n>   --dir /tmp/archon-install\n>\n> # Install the binary\n> chmod +x /tmp/archon-install/archon-*\n> sudo mv /tmp/archon-install/archon-* /usr/local/bin/archon\n>\n> # Verify\n> archon version\n> ```\n\n## Manual Release (When GitHub Actions Unavailable)\n\nIf GitHub Actions can't run (billing issues, private repo limits), create the release manually:\n\n```bash\n# 1. Build binaries locally (every target in the script's local TARGETS list).\n#    TARGET or OUTFILE left set in the shell switches the script to single-target mode.\nunset TARGET OUTFILE\n./scripts/build-binaries.sh\n\n# 2. Generate checksums.txt, which the install scripts verify against\n./scripts/checksums.sh\n\n# 3. Create the release with binaries and checksums\ngh release create vX.Y.Z dist/binaries/* \\\n  --title \"Archon CLI vX.Y.Z\" \\\n  --generate-notes\n\n# 4. Verify the release\ngh release view vX.Y.Z\n```\n\n> **Note:** With `TARGET` and `OUTFILE` unset, `scripts/build-binaries.sh` cross-compiles\n> the CLI and server executables for every target in its local `TARGETS` list into\n> `dist/binaries/`. That list has no Windows target.\n>\n> A manual release is therefore incomplete. It lacks the assets only the CI release\n> builds: the Windows binaries, the plugin executables (`archon plugin add`\n> downloads these), and `archon-web.tar.gz` (`archon serve` downloads it). The `files:`\n> list of the release step in `.github/workflows/release.yml` is the full set CI publishes.\n\n## Manual Build (for Testing)\n\nTo build binaries locally without creating a release:\n\n```bash\n# Build every target in the script's local TARGETS list (no Windows)\nunset TARGET OUTFILE\n./scripts/build-binaries.sh\n\n# Binaries are in dist/binaries/\nls -la dist/binaries/\n\n# Generate checksums\n./scripts/checksums.sh\n```\n\n## Release Workflow Details\n\nThe `.github/workflows/release.yml` workflow:\n\n1. **Triggers on**:\n   - Push of tags matching `v*`\n   - Manual workflow dispatch with version input\n\n2. **Build job** (runs in parallel for each platform):\n   - Sets up Bun\n   - Installs dependencies\n   - Compiles binary with `bun build --compile`\n   - Uploads as artifact\n\n3. **Release job** (runs after all builds complete):\n   - Downloads all artifacts\n   - Generates SHA256 checksums\n   - Creates GitHub Release with:\n     - All binaries attached\n     - checksums.txt\n     - Auto-generated release notes\n     - Installation instructions\n\n## Troubleshooting\n\n### Build Fails on GitHub Actions\n\nCheck the Actions tab for specific errors. Common issues:\n- Dependency installation failure: Check `bun.lock` is committed\n- Type errors: Run `bun run type-check` locally first\n\n### Install Script Fails\n\nThe install script requires:\n- `curl` for downloading\n- `sha256sum` or `shasum` for verification\n- Write access to `/usr/local/bin` (or custom `INSTALL_DIR`)\n\n### Checksums Don't Match\n\nIf users report checksum failures:\n1. Check the release artifacts are complete\n2. Verify checksums.txt was generated correctly\n3. Ensure binaries weren't modified after checksum generation\n\n## Pre-release Versions\n\nFor testing releases before public announcement:\n\n```bash\n# Create a pre-release tag\ngit tag v0.3.0-beta.1\ngit push origin v0.3.0-beta.1\n```\n\nPre-releases (tags containing `-`) are marked as such on GitHub.\n\n## Hotfix Process\n\nFor urgent fixes to a released version:\n\n```bash\n# Create hotfix branch from tag\ngit checkout -b hotfix/0.2.1 v0.2.0\n\n# Make fixes, then tag\ngit tag v0.2.1\ngit push origin v0.2.1\n\n# Merge fixes back to dev\ngit checkout dev\ngit merge hotfix/0.2.1\ngit push origin dev\n```\n","packages/docs-web/src/content/docs/deployment/cloud.md":"---\ntitle: Cloud Deployment\ndescription: Prepare a cloud VPS for Archon, then run it with Docker Compose and automatic HTTPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 3\n---\n\nThis page prepares a VPS: a user, SSH, a firewall and DNS. The [Docker guide](/deployment/docker/) then installs and runs Archon on it with Docker Compose and Caddy for HTTPS. Using your own proxy instead of Caddy? See [Running Archon behind your own reverse proxy](/deployment/reverse-proxy/).\n\nFor the fastest route, use the [cloud-init script](/deployment/docker/#cloud-init-fastest-setup) instead. It creates a user, opens the firewall and installs Docker when the VPS first boots. You still create the [DNS record](#point-your-domain-at-the-server).\n\n## What you need\n\n- A VPS from any provider (DigitalOcean, Linode, AWS EC2, Vultr and so on): 1 to 2 vCPUs, 2 GB RAM minimum (4 GB recommended), 20 GB SSD, Ubuntu 22.04 LTS.\n- A domain or subdomain, such as `archon.yourdomain.com`.\n- An SSH key. Create one before the VPS, and add the public key when you create it:\n\n  ```bash\n  ssh-keygen -t ed25519 -C \"archon\"\n  cat ~/.ssh/id_ed25519.pub\n  ```\n\nWhen you create the VPS, choose the Ubuntu 22.04 image and your SSH key, and allow inbound SSH (22), HTTP (80) and HTTPS (443) if the provider has its own firewall or security group. Note the public IP.\n\n## Secure the server\n\nConnect with the image's default account: `root` on most VPS providers, `ubuntu` on AWS EC2, which disables root login. Create a deploy user with your key, and switch to it:\n\n```bash\nssh root@your-server-ip            # or: ssh ubuntu@your-server-ip on EC2\n\nsudo adduser deploy\nsudo usermod -aG sudo deploy\nsudo mkdir -p /home/deploy/.ssh\nsudo cp ~/.ssh/authorized_keys /home/deploy/.ssh/\nsudo chown -R deploy:deploy /home/deploy/.ssh\nsudo chmod 700 /home/deploy/.ssh\nsudo chmod 600 /home/deploy/.ssh/authorized_keys\n```\n\nTest `ssh deploy@your-server-ip` in a second terminal before you go on. Then turn off password logins by setting `PasswordAuthentication no` in `/etc/ssh/sshd_config`, and restart SSH:\n\n```bash\nsudo systemctl restart ssh\nsudo su - deploy\n```\n\nOpen the firewall:\n\n```bash\nsudo ufw allow 22/tcp\nsudo ufw allow 80/tcp\nsudo ufw allow 443\nsudo ufw --force enable\n```\n\n## Point your domain at the server\n\nAt your DNS provider, create an A record: name `archon` (or `@` for the root domain), value the server's public IP. With Cloudflare, set the record to DNS only, not proxied, so Caddy can obtain its certificate. Check it with `dig archon.yourdomain.com`.\n\n## Install and run Archon\n\nFollow the Docker guide's [manual server setup](/deployment/docker/#manual-server-setup). It installs Docker, clones the repository, sets up `.env` and the `Caddyfile`, and starts the stack. Before you expose the Web UI, choose a login in [Protect the Web UI](/deployment/docker/#protect-the-web-ui).\n\nCheck the deployment from your machine:\n\n```bash\ncurl https://archon.yourdomain.com/api/health\n```\n","packages/docs-web/src/content/docs/deployment/docker.md":"---\ntitle: Docker Guide\ndescription: Run Archon with Docker Compose, locally or on a server with PostgreSQL and automatic HTTPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 2\n---\n\nThis page installs and runs Archon with Docker Compose: on your own machine, or on a server with PostgreSQL and Caddy for automatic HTTPS. To prepare a VPS first (a user, SSH, the firewall and DNS), see [Cloud Deployment](/deployment/cloud/).\n\nThe image includes the Claude Code binary from the Claude Agent SDK. On start, the entrypoint points `CLAUDE_BIN_PATH` at it unless you set `CLAUDE_BIN_PATH` yourself.\n\n## Cloud-init (fastest setup)\n\n`deploy/cloud-init.yml` prepares a new VPS when it first boots. Paste its contents into the provider's User Data field when you create the server (Ubuntu 22.04+ or Debian 12+), add your SSH key, and point your domain's A record at the server's IP.\n\n| Provider | Where to paste it |\n|---|---|\n| DigitalOcean | Create Droplet, Advanced Options, User Data |\n| AWS EC2 | Launch Instance, Advanced Details, User Data |\n| Linode | Create Linode, Add Tags, Metadata (User Data) |\n| Hetzner | Create Server, Cloud config, User Data |\n| Vultr | Deploy, Additional Features, Cloud-Init User-Data |\n\nThe script installs Docker, opens ports 22, 80 and 443 (TCP, plus UDP 443 for HTTP/3), adds a 2 GB swapfile, clones the repository to `/opt/archon`, copies `.env.example` to `.env` and `Caddyfile.example` to `Caddyfile`, and builds the image. It also creates an `archon` user with Docker access but no sudo, and copies your SSH key to it. Use the default cloud user or root for system maintenance.\n\nSetup takes 5 to 8 minutes and writes `/opt/archon/SETUP_COMPLETE` when it finishes. Then:\n\n```bash\nssh archon@your-server-ip\nnano /opt/archon/.env          # see \"Configure .env\" below\ncd /opt/archon\ndocker compose --profile with-db --profile cloud up -d\n```\n\n## Local Docker Desktop (Windows and macOS)\n\nRun Archon on your own machine with SQLite and the Web UI. No domain or VPS is needed.\n\n```bash\ngit clone https://github.com/coleam00/Archon.git\ncd Archon\ncp .env.example .env           # set a Claude or Codex credential\ndocker compose up -d\n```\n\nOpen `http://localhost:3000`. Add `--profile with-db` to use the PostgreSQL container instead of SQLite (see [Profiles](#profiles)).\n\nOn Windows, build from a WSL terminal (for example in `/mnt/c/Users/YourName/path/to/Archon`), not PowerShell. Docker Desktop cannot follow Bun workspace symlinks from a Windows build context and fails with `The file cannot be accessed by the system`. If the container fails with `exec docker-entrypoint.sh: no such file or directory`, the checkout has CRLF line endings. `.gitattributes` forces LF for shell scripts, so re-clone the repository.\n\n## Manual server setup\n\nUse this instead of cloud-init when you want each step under your control. Prepare the server with [Cloud Deployment](/deployment/cloud/) first.\n\n1. Install Docker, then log out and back in so the group change applies:\n\n   ```bash\n   curl -fsSL https://get.docker.com | sh\n   sudo usermod -aG docker $USER\n   ```\n\n2. Clone the repository and create the config files:\n\n   ```bash\n   git clone https://github.com/coleam00/Archon.git\n   cd Archon\n   cp .env.example .env\n   cp Caddyfile.example Caddyfile\n   ```\n\n3. Edit `.env` as described in [Configure .env](#configure-env).\n\n4. Start the app, PostgreSQL and Caddy:\n\n   ```bash\n   docker compose --profile with-db --profile cloud up -d\n   ```\n\n5. Check it:\n\n   ```bash\n   docker compose --profile with-db --profile cloud ps\n   docker compose logs -f app\n   curl https://archon.example.com/api/health    # from your machine\n   ```\n\n## Pre-built image\n\nTo run `ghcr.io/coleam00/archon:latest` without building from source:\n\n```bash\nmkdir archon && cd archon\ncurl -O https://raw.githubusercontent.com/coleam00/Archon/main/deploy/docker-compose.yml\ncurl -O https://raw.githubusercontent.com/coleam00/Archon/main/.env.example\ncp .env.example .env           # see \"Configure .env\" below\ndocker compose up -d\n```\n\nThis compose file has only the app service. To add PostgreSQL, uncomment the `postgres` service and the `postgres_data` volume, and set `DATABASE_URL`. It has no Caddy service.\n\n### Verify image attestations\n\nPublished images carry BuildKit provenance and SPDX SBOM attestations. Resolve the tag to a digest, then inspect that digest so the image cannot change between inspection and deployment:\n\n```bash\ndocker buildx imagetools inspect ghcr.io/coleam00/archon:latest\n# Copy the top-level Digest, not a platform manifest's digest.\nimage='ghcr.io/coleam00/archon@sha256:<digest>'\ndocker buildx imagetools inspect \"$image\" --format '{{ json .Provenance }}'\ndocker buildx imagetools inspect \"$image\" --format '{{ json .SBOM }}'\n```\n\nBoth results must be non-empty: each platform has a `SLSA` provenance object and an `SPDX` SBOM object. In the provenance, check three things:\n\n- The source is `https://github.com/coleam00/Archon` and the revision is the release commit you intend to run. Current releases record them in `buildDefinition.externalParameters.request.args` as `label:org.opencontainers.image.source` and `label:org.opencontainers.image.revision`. Older BuildKit versions use `invocation.configSource` and `invocation.environment`.\n- The workflow ref in `buildDefinition.internalParameters.github_workflow_ref` names `.github/workflows/publish.yml` at the expected ref.\n- The builder ID (`builder.id` or `runDetails.builder.id`) links to the GitHub Actions run.\n\nThe package inventory is under `SPDX.packages`. The `unknown/unknown` entries in the manifest list are the attestation manifests. The attestations are unsigned metadata, so they do not authenticate the publisher by themselves. See [Docker's provenance documentation](https://docs.docker.com/build/metadata/attestations/slsa-provenance/).\n\n## Configure .env\n\nDocker Compose reads the repository's `.env`. Edit it directly. Don't run `archon setup` for a Docker install: the wizard writes Archon's CLI env files, not this one. `.env.example` documents every variable.\n\n### AI credentials\n\nSet at least one provider:\n\n```ini\n# Claude: an OAuth token from `claude setup-token` on your own machine\nCLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxx\n# or an API key from console.anthropic.com\n# CLAUDE_API_KEY=sk-ant-xxxxx\n\n# Codex: values from ~/.codex/auth.json after `codex login`\n# ARCHON_CODEX_ID_TOKEN=eyJhbGc...\n# ARCHON_CODEX_ACCESS_TOKEN=eyJhbGc...\n# ARCHON_CODEX_REFRESH_TOKEN=rt_...\n# ARCHON_CODEX_ACCOUNT_ID=6a6a7ba6-...\n```\n\n`.env.example` sets `CLAUDE_USE_GLOBAL_AUTH=true`. That flag only satisfies the server's startup check: it tells Archon to rely on a `claude /login` session, and a fresh container has none. Set a token or key. When one is set, Archon uses it whatever the flag says. See [AI Assistants](/getting-started/ai-assistants/) for every provider.\n\n### Server, domain and database\n\n| Variable | Default in Docker | Purpose |\n|---|---|---|\n| `PORT` | `3000` | App port, published on the host. Local development defaults to 3090. |\n| `DOMAIN` | none | Public hostname. Required for `--profile cloud`. |\n| `DATABASE_URL` | unset (SQLite) | PostgreSQL connection string. |\n| `POSTGRES_PORT` | `5432` | Host port for the PostgreSQL container, bound to `127.0.0.1`. |\n| `LOG_LEVEL` | `info` | `fatal`, `error`, `warn`, `info`, `debug` or `trace`. |\n\nFor the PostgreSQL container, use the service name as the host:\n\n```ini\nDATABASE_URL=postgresql://postgres:postgres@postgres:5432/remote_coding_agent\n```\n\nPlatform tokens (`TELEGRAM_BOT_TOKEN`, `SLACK_BOT_TOKEN`, `GH_TOKEN`, `WEBHOOK_SECRET` and so on) are covered in each adapter's page under [Adapters](/adapters/). For a GitHub webhook, the payload URL is `https://archon.example.com/webhooks/github`; see [GitHub](/adapters/github/).\n\n## Profiles\n\nCompose profiles add PostgreSQL, HTTPS or form login to the base app service:\n\n| Command | What runs |\n|---|---|\n| `docker compose up -d` | App with SQLite |\n| `docker compose --profile with-db up -d` | App and PostgreSQL 17 |\n| `docker compose --profile cloud up -d` | App and Caddy |\n| `docker compose --profile with-db --profile cloud up -d` | App, PostgreSQL and Caddy |\n\nAdd `--profile auth` for the [form login sidecar](#form-login-sidecar). For an external database such as Supabase or Neon, set `DATABASE_URL` and use no database profile. There is no `external-db` profile.\n\n- **No profile:** SQLite, stored in the data volume at `/.archon/archon.db`.\n- **`with-db`:** a PostgreSQL 17 container. The app creates and upgrades the schema on every start. If you start this profile without setting `DATABASE_URL`, the app logs a warning and uses SQLite, and the container sits unused.\n- **`cloud`:** [Caddy](https://caddyserver.com/) in front of the app, with Let's Encrypt certificates, HTTP to HTTPS redirects, HTTP/3 and unbuffered `/api/stream/*` for live updates. It needs a `Caddyfile` (copied from `Caddyfile.example`), `DOMAIN` set, the DNS A record in place, and ports 80 and 443 open. Caddy waits for the app's health check before it starts.\n\n## Protect the Web UI\n\nA Web UI on a public domain needs a login. Choose one of these:\n\n- **Better Auth** (PostgreSQL only, recommended): per-user accounts, and the server rejects every `/api/*` request without a session. Set `BETTER_AUTH_SECRET`; see [Web UI login](/reference/configuration/#web-ui-login-better-auth-optional). Signup is closed by default: invite people with `ARCHON_AUTH_ALLOWED_EMAILS`, or open it with `ARCHON_AUTH_OPEN_SIGNUP=true`. With Better Auth on, you don't need either Caddy option below; the [Security reference](/reference/security/#adapter-authorization) explains how to retire them.\n- **Caddy basic auth:** the browser's own login prompt, with no extra container.\n- **Form login sidecar:** a styled login page and a session cookie, served by the `auth-service` container.\n\nUse one Caddy option at a time, not both. Caddy always leaves `/webhooks/*` and `/api/health` open, and webhooks verify their own signatures. Caddy answers `/internal/*` with a 404 and never forwards it.\n\n### Caddy basic auth\n\n1. Generate a bcrypt hash. Never put a plaintext password in `.env`.\n\n   ```bash\n   docker run --rm caddy caddy hash-password --plaintext 'YOUR_PASSWORD'\n   ```\n\n2. Set `CADDY_BASIC_AUTH` in `.env`. Replace `admin` with your username and paste the complete hash unchanged:\n\n   ```ini\n   CADDY_BASIC_AUTH='basic_auth @protected {\n     admin REPLACE_WITH_COMPLETE_HASH_OUTPUT\n   }'\n   ```\n\n   This is a single-quoted multiline Compose value, so newlines and `$` characters survive as written. Don't double the dollar signs, and don't source the file in a shell. See [Compose interpolation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/) and [Caddy's basic_auth](https://caddyserver.com/docs/caddyfile/directives/basic_auth).\n\n3. Recreate Caddy so it reads the new value:\n\n   ```bash\n   docker compose --profile cloud up -d --force-recreate caddy\n   ```\n\nTo turn it off, leave `CADDY_BASIC_AUTH` empty or unset.\n\n### Form login sidecar\n\n1. Generate a bcrypt hash. The first run builds the `auth-service` image.\n\n   ```bash\n   docker compose --profile auth run --rm auth-service \\\n     node -e \"require('bcryptjs').hash('YOUR_PASSWORD', 12).then(h => console.log(h))\"\n   ```\n\n2. Generate a cookie signing secret:\n\n   ```bash\n   docker run --rm node:22-alpine \\\n     node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\n   ```\n\n3. Set them in `.env`, writing every `$` in the hash as `$$` so Compose does not interpolate it:\n\n   ```ini\n   AUTH_USERNAME=admin\n   AUTH_PASSWORD_HASH=$$2b$$12$$REPLACE_WITH_YOUR_HASH\n   COOKIE_SECRET=REPLACE_WITH_64_HEX_CHARS\n   # COOKIE_MAX_AGE=86400   # session length in seconds\n   ```\n\n4. In `Caddyfile`, uncomment the Option A block (`handle /login`, `handle /logout` and the `forward_auth` handle), and comment out the \"No auth\" `handle` block below it.\n\n5. Start with the `auth` profile:\n\n   ```bash\n   docker compose --profile with-db --profile cloud --profile auth up -d\n   ```\n\nYour domain now redirects to `/login`. Visit `/logout` to end the session.\n\n## Data and home volumes\n\nThe container keeps Archon's data in `/.archon`: workspaces, worktrees, artifacts, logs, plugins and the SQLite database. The user's home, `/home/appuser`, holds provider and tool state:\n\n| Path | What it holds |\n|---|---|\n| `~/.claude/` | Claude Code skills, commands, agents, hooks, MCP config, sessions, memory and OAuth state |\n| `~/.codex/` | Codex `auth.json` from `codex login`. With the `ARCHON_CODEX_*` variables set, startup overwrites it each time. |\n| `~/.pi/agent/` | Pi `auth.json` from `pi /login`, `models.json`, `settings.json` and sessions |\n| `~/.gitconfig` | Git identity, signing config, aliases and `safe.directory` entries |\n| `~/.config/gh/` | GitHub CLI login from `gh auth login` |\n| `~/.bash_history` | Shell history from `docker compose exec app bash` |\n\nBoth are Docker named volumes by default (`archon_data` and `archon_user_home`), so they survive restarts and rebuilds. To keep them at a host path instead, set `ARCHON_DATA` or `ARCHON_USER_HOME` in `.env`, and make the directory writable by UID 1001, the container user:\n\n```bash\nsudo mkdir -p /opt/archon-data /opt/archon-user-home\nsudo chown -R 1001:1001 /opt/archon-data /opt/archon-user-home\n```\n\n```ini\nARCHON_DATA=/opt/archon-data\nARCHON_USER_HOME=/opt/archon-user-home\n```\n\nOn every start the entrypoint fixes ownership of both paths, touching only files with the wrong owner.\n\n- `ARCHON_HOME` has no effect inside the container, which always uses `/.archon`. Use `ARCHON_DATA` to choose where that lives on the host.\n- A bind-mounted home starts with an empty `~/.gitconfig`, because Docker copies image content into new named volumes but never into bind mounts. The entrypoint still adds the `safe.directory` entries Archon needs. Set your Git identity with `git config --global` inside the container.\n- `safe.directory` entries added at runtime stay in the home volume, even after the checkout is gone. Archon never removes them, because concurrent setup may rely on the same entry. See [Git directory trust](/reference/security/#git-directory-trust).\n- To keep Pi's data on the data volume, set `PI_CODING_AGENT_DIR=/.archon/pi` before the container starts.\n- To wipe the home volume: `docker compose down && docker volume rm archon_archon_user_home`.\n\n### Root fallback for macOS bind mounts\n\nOn a macOS bind mount (VirtioFS), the ownership fix always fails: the host will not remap files to UID 1001. The entrypoint prints the `chown` error and exits 1, so the container restarts in a loop. Read-only mounts and SELinux or AppArmor denials on Linux fail the same way.\n\n`ARCHON_ALLOW_ROOT_FALLBACK=1` in `.env` makes the entrypoint print a warning and keep running as root instead. Any other value, or none, keeps the exit. The variable does nothing when the ownership fix succeeds.\n\n:::caution\nRunning as root also sets `IS_SANDBOX=1`, which disables the Claude provider's refusal to run `bypassPermissions` as root, so AI subprocesses run as root in the container. That is acceptable on a single-operator Mac where the bind mount already limits what the container can reach. On Linux the failure means the volume ownership is wrong: run `sudo chown -R 1001:1001 <path>` on the host instead.\n:::\n\n## GitHub credentials\n\nThe container uses `GH_TOKEN` from `.env`. You can also log in inside the container, and the login lands on the home volume:\n\n```bash\ndocker compose exec -u appuser -e HOME=/home/appuser app gh auth login\n```\n\n- With `GH_TOKEN` set and GitHub App mode off, every start resets Git's `https://github.com` credential helper to one that uses `GH_TOKEN`. A helper written by `gh auth login` applies only without `GH_TOKEN`.\n- In [GitHub App mode](/adapters/github-app-setup/) (`GITHUB_APP_ID`, a private key and `WEBHOOK_SECRET` set, `GITHUB_TOKEN` unset), startup removes only the PAT helper that the entrypoint wrote earlier. An incomplete App configuration leaves it in place. Other helpers are yours and stay.\n- The entrypoint reads these variables from the container environment, not from `/.archon/.env`. Keep `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY` or `GITHUB_APP_PRIVATE_KEY_PATH`, `WEBHOOK_SECRET`, `GITHUB_TOKEN` and `GH_TOKEN` in the compose `.env`. If they are only in `/.archon/.env`, the server runs in App mode while clones keep using the old PAT.\n- With `ARCHON_SDLC_FORGE=gh`, delete an empty `GH_TOKEN=` line. The SDLC workflows refuse an empty `GH_TOKEN` when `GITHUB_TOKEN` is unset, rather than let `gh` fall back to the stored login.\n\n## Plugins\n\nInstall plugins inside the running container, as `appuser` so the server owns the files:\n\n```bash\ndocker compose exec -u appuser app bun run cli plugin add coleam00/Archon/plugins/forge-github\ndocker compose exec -u appuser app bun run cli plugin add Wirasm/archon-video\n```\n\n`plugin update`, `plugin remove` and `plugin list` work the same way. Plugins live in `/.archon/plugins` on the data volume, so they survive restarts and rebuilds. See [Forge operations](/reference/forge/#install-the-github-plugin) and [Installed workflow packs](/guides/global-workflows/#installed-workflow-packs).\n\nThe bundled SDLC workflows use `gh` by default. To use the forge plugin instead, set `ARCHON_SDLC_FORGE=forge` and install forge-github v0.12.1 or later; some pack steps still need `gh`. The plugin looks for `GH_TOKEN`, then `GITHUB_TOKEN`, then the container's `gh` login. Your host's `gh` login is not visible inside the container. See [Use the forge path in the SDLC pack](/reference/forge/#use-the-forge-path-in-the-sdlc-pack).\n\nThe image includes a verified Slack plugin outside the data volume. When `SLACK_BOT_TOKEN` is set, in the container environment or in `/.archon/.env`, the entrypoint installs it offline into `/.archon/plugins` before the server starts, and logs the release and commit. You don't run `plugin add` for Slack. Slack still needs both `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` to connect; see [Slack](/adapters/slack/).\n\n- An installed, verified Slack plugin stays as it is, including a release you pinned from another source. The image does not update it.\n- Missing or changed Slack executables, ownership conflicts, or a broken image receipt when a new install is needed stop provisioning with an error in the boot logs. Repair the install before restarting.\n- To turn Slack off, remove its tokens from every env source, run `docker compose exec -u appuser app bun run cli plugin remove coleam00/Archon/plugins/chat-slack`, and restart. Removing only the plugin reinstalls it on the next start. Removing only the tokens leaves it installed and reporting missing credentials.\n\nThe image has `git` and Bun, but not `uv`, Python, or tools a pack's scripts call, such as ffmpeg. Add them with [Dockerfile.user](#customizing-the-image).\n\n## Container isolation is unavailable\n\n`archon workflow run ... --container` starts a sibling Docker container for each run. Inside this compose stack it fails at preflight with \"Cannot connect to the Docker daemon\": the image has no `docker` CLI, and the stack does not mount `/var/run/docker.sock`. Worktree isolation and in-place folder runs work normally.\n\n:::caution\nMounting the Docker socket into the app container is not part of the supported stack. The socket is root-equivalent: anything that can reach it can start a privileged container and take over the host. If you accept that on a single-operator host, read `packages/isolation/docker/SECURITY.md` first. To use `--container` without that risk, run Archon directly on a host with a local Docker daemon.\n:::\n\n## Building the image\n\n```bash\ndocker build -t archon .\ndocker run --env-file .env -e ARCHON_DOCKER=true -p 3000:3000 archon\n```\n\nThe Dockerfile builds in these stages:\n\n1. `manifests` and `sources` derive the workspace layout from `packages/`, so a new package needs no Dockerfile edit.\n2. `deps` installs all dependencies, including the dev dependencies the web build needs.\n3. `image-plugins` downloads the pinned Slack release, checks its commit and checksum, and records a verified receipt.\n4. `web-build` builds the Web UI with Vite.\n5. `production` keeps production dependencies and the built Web UI.\n\nThe production image runs Bun as the non-root `appuser` (UID 1001), which Claude Code requires. It includes `git`, `curl`, `gh`, `postgresql-client`, `ripgrep`, `jq`, Chromium and [agent-browser](https://github.com/vercel-labs/agent-browser), which drives the system Chromium for browser testing. It ships the source of every workspace package except `docs-web`, `web` (only the built UI ships) and `chat-slack` (shipped as the verified release under `/opt/archon/image-plugins`).\n\nThe Slack release is pinned separately from Archon's version by the build args `SLACK_PLUGIN_TAG` (default `v0.12.1`) and `SLACK_PLUGIN_COMMIT` (default `5f2b4841aaca38180c0abbd32bc6345593292019`). Override both together; the build rejects a tag that resolves to a different commit. Downloads happen only at build time.\n\n### Customizing the image\n\nTo add tools without editing the tracked Dockerfile:\n\n1. Copy `Dockerfile.user.example` to `Dockerfile.user` (use `deploy/Dockerfile.user.example` for a server deploy) and edit it.\n2. Copy `docker-compose.override.example.yml` to `docker-compose.override.yml` (or `deploy/docker-compose.override.example.yml`).\n3. Run `docker compose up -d`. Compose merges the override file automatically.\n\nBoth files are gitignored, so your changes stay local.\n\n## Maintenance\n\n```bash\ndocker compose logs -f app            # follow app logs\ndocker compose logs --tail=100 app    # last 100 lines\ndocker compose restart app\ndocker compose down                   # stop, keep data\ndocker compose down -v                # stop and delete volumes, including data\ndocker system df                      # Docker disk usage\n```\n\n### Update\n\n```bash\ngit pull\ndocker compose --profile with-db --profile cloud up -d --build\n```\n\nUse the same profiles you deployed with. The app applies schema changes on startup: it runs the idempotent `migrations/000_combined.sql` under a PostgreSQL advisory lock, so there is no manual migration step.\n\n## Troubleshooting\n\n### App exits with `no_ai_credentials`\n\nThe server found no Claude credential and no Codex credential. Set `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_API_KEY`, or `ARCHON_CODEX_ID_TOKEN` with `ARCHON_CODEX_ACCESS_TOKEN`. See [AI credentials](#ai-credentials).\n\n### Caddy fails with \"not a directory\"\n\n```\nerror mounting \"Caddyfile\": not a directory\n```\n\n`Caddyfile` did not exist, so Docker created a directory in its place:\n\n```bash\nrm -rf Caddyfile\ncp Caddyfile.example Caddyfile\ndocker compose --profile cloud up -d\n```\n\n### Caddy gets no certificate\n\nCheck that `dig archon.example.com` returns the server's IP, that `sudo ufw status` shows 80 and 443 open, and that `DOMAIN` in `.env` is spelled right. Then read `docker compose logs caddy`. DNS changes can take up to an hour to propagate.\n\n### Health check fails\n\nThe container health check calls `/api/health` on `PORT`, not `/health`:\n\n```bash\ncurl http://localhost:3000/api/health\n```\n\n### PostgreSQL connection refused\n\nWith `--profile with-db`, `DATABASE_URL` must use the service name `postgres` as the host, not `localhost`. Check the container with `docker compose ps postgres`.\n\n### Permission errors in `/.archon`\n\nThe entrypoint exits 1 with the `chown` error when it cannot give `/.archon` or `/home/appuser` to UID 1001. On Linux with a bind mount, run `sudo chown -R 1001:1001 /path/to/archon-data` on the host. On a macOS bind mount, `chown` on the host does not help; see [Root fallback for macOS bind mounts](#root-fallback-for-macos-bind-mounts).\n\n### Port already in use\n\nSet another `PORT` in `.env`, such as `PORT=3001`.\n\n### Container keeps restarting\n\nRun `docker compose ps` and `docker compose logs --tail=50 app`. The usual causes are a missing `.env`, an invalid credential or an unreachable database.\n","packages/docs-web/src/content/docs/deployment/e2e-testing-wsl.md":"---\ntitle: E2E Testing on WSL\ndescription: Run agent-browser inside WSL for end-to-end testing when dev servers run on Windows.\ncategory: deployment\narea: infra\naudience: [developer]\nstatus: current\nsidebar:\n  order: 6\n---\n\n`agent-browser` (Vercel) has a [known Windows bug](https://github.com/vercel-labs/agent-browser/issues/56) where the daemon fails to start due to Unix domain socket incompatibility. The workaround is to run agent-browser inside WSL while the dev servers run on Windows.\n\n> **General setup:** For non-WSL platforms (macOS, Linux, Docker), see the [E2E Testing Guide](/deployment/e2e-testing/) instead.\n\n## Prerequisites\n\n- WSL2 with Ubuntu installed (`wsl --list --verbose`)\n- agent-browser installed in WSL: `npm install -g agent-browser`\n- Playwright chromium installed: `agent-browser install --with-deps` (needs sudo)\n\n## Setup\n\n### 1. Find the Windows host IP accessible from WSL\n\n```bash\nipconfig | findstr \"IPv4\" | findstr \"WSL\"\n# Example output: IPv4 Address. . . . . . . . . . . : 172.18.64.1\n```\n\nOr from inside WSL:\n```bash\nwsl -d Ubuntu -- bash -c \"cat /etc/resolv.conf | grep nameserver\"\n```\n\nThe Windows host IP for this system is `172.18.64.1`.\n\n### 2. Start dev servers on Windows (bound to all interfaces)\n\n```bash\n# Backend (Hono on port 3090) - already binds to 0.0.0.0 by default\nbun run dev:server &\n\n# Frontend (Vite on port 5173) - needs --host flag\ncd packages/web && bun x vite --host 0.0.0.0 &\n```\n\n### 3. Verify WSL can reach the servers\n\n```bash\nwsl -d Ubuntu -- curl -s http://172.18.64.1:3090/api/health\nwsl -d Ubuntu -- curl -s -o /dev/null -w \"%{http_code}\" http://172.18.64.1:5173\n```\n\n## Running agent-browser Commands\n\nAll commands are run from the Windows terminal, prefixed with `wsl -d Ubuntu --`:\n\n```bash\n# Open a page\nwsl -d Ubuntu -- agent-browser open http://172.18.64.1:5173\n\n# Take interactive snapshot (get element refs like @e1, @e2)\nwsl -d Ubuntu -- agent-browser snapshot -i\n\n# Click, fill, press\nwsl -d Ubuntu -- agent-browser click @e1\nwsl -d Ubuntu -- agent-browser fill @e2 \"some text\"\nwsl -d Ubuntu -- agent-browser press Enter\n\n# Wait for content to load\nwsl -d Ubuntu -- agent-browser wait 3000\n\n# Reload page (hard refresh)\nwsl -d Ubuntu -- agent-browser reload\n\n# Close browser\nwsl -d Ubuntu -- agent-browser close\n```\n\n## Taking Screenshots\n\nScreenshots must be saved to a WSL-native path first, then copied to the Windows filesystem via the `/mnt/c/` mount:\n\n```bash\n# Save to WSL home, then copy to project\nwsl -d Ubuntu -- bash -c '\n  agent-browser screenshot /home/user/screenshot.png 2>&1 &&\n  cp /home/user/screenshot.png /path/to/archon/e2e-screenshots/my-test.png\n'\n```\n\n**Why not save directly to `/mnt/c/...`?** agent-browser resolves paths through its Node.js process, which on some setups mangles `/mnt/c/` paths (e.g., prepending `C:/Program Files/Git/`). Saving to a WSL-native path and copying avoids this.\n\n## Gotchas\n\n- **`localhost` doesn't work from WSL2** - must use the Windows host IP (`172.18.64.1`)\n- **Vite must bind to `0.0.0.0`** - default `localhost` isn't reachable from WSL\n- **Git Bash path expansion** - `/status` gets expanded to `C:/Program Files/Git/status` when passed through Git Bash. Not an agent-browser issue; it's the shell expanding `/` paths\n- **SSE `Connected` indicator** - only shows for `web` platform conversations; Telegram/Slack conversations show `Disconnected` (expected)\n- **Daemon startup** - if `agent-browser open` fails with \"Daemon failed to start\", kill stale daemons: `wsl -d Ubuntu -- pkill -f daemon.js` and retry\n","packages/docs-web/src/content/docs/deployment/e2e-testing.md":"---\ntitle: E2E Testing\ndescription: Set up agent-browser for end-to-end browser testing in Archon workflows.\ncategory: deployment\narea: infra\naudience: [developer, operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\nArchon uses [agent-browser](https://github.com/vercel-labs/agent-browser) (by Vercel Labs) for end-to-end browser testing in workflows. It is an **optional** external dependency — core Archon functionality works without it.\n\n## Installation\n\n```bash\n# Install globally\nnpm install -g agent-browser\n\n# Download browser engine (Chrome for Testing)\nagent-browser install\n```\n\n## Verify Installation\n\n```bash\nagent-browser --version\n# Expected: prints version number (e.g., 0.x.x)\n\n# Quick smoke test — opens a page and closes\nagent-browser open https://example.com\nagent-browser close\n```\n\n## Where It's Used\n\nNo bundled workflow requires agent-browser. Your own workflows can call it from a `bash:` node or from an AI node's instructions, for example to reproduce a UI bug in a real browser or to check a change against a running dev server.\n\n## Platform-Specific Notes\n\n### Docker\n\nagent-browser is **pre-installed** in the Archon Docker image. No action needed.\n\n### macOS / Linux\n\nWorks natively after running the install commands above. If the daemon fails to start:\n\n```bash\n# Kill stale daemons and retry\npkill -f daemon.js\nagent-browser open http://localhost:3090\n```\n\n### Windows\n\nagent-browser has a [known bug](https://github.com/vercel-labs/agent-browser/issues/56) where the daemon fails to start due to Unix domain socket incompatibility on Windows.\n\n**Workaround:** Run agent-browser inside WSL while dev servers run on Windows. See the [E2E Testing on WSL](/deployment/e2e-testing-wsl/) guide for detailed setup instructions.\n\n## Running Without agent-browser\n\nIf agent-browser is not installed, the E2E workflow nodes will fail when the agent tries to invoke `agent-browser`. The AI agent is instructed (via prompt) to stop after 2 failed connection attempts and produce a code-review-only report — but this is a prompt-level instruction, not automated workflow logic. Results may vary depending on the AI model's adherence to the instruction.\n\nYou can safely run all non-E2E workflows without agent-browser installed.\n","packages/docs-web/src/content/docs/deployment/index.md":"---\ntitle: Deployment Overview\ndescription: Overview of deployment options for running Archon locally, with Docker, or on a cloud VPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 0\n---\n\nArchon can run locally for development or be deployed to a server for always-on operation.\n\n## Deployment Options\n\n| Method | Best For | Guide |\n|--------|----------|-------|\n| **Local** | Development, personal use | [Local Development](/deployment/local/) |\n| **Docker** | Self-hosted servers, CI environments | [Docker](/deployment/docker/) |\n| **Cloud VPS** | 24/7 operation with automatic HTTPS | [Cloud Deployment](/deployment/cloud/) |\n| **Windows** | Native Windows or WSL2 | [Windows](/deployment/windows/) |\n\nUsing an existing proxy? See [Running Archon behind your own reverse proxy](/deployment/reverse-proxy/) for routing, authentication, webhook, and streaming requirements.\n\n## Database Options\n\n| Option | Setup | Best For |\n|--------|-------|----------|\n| **SQLite** (default) | Zero config, just omit `DATABASE_URL` | Single-user, CLI usage, local development |\n| **Remote PostgreSQL** | Set `DATABASE_URL` to hosted DB | Cloud deployments, shared access |\n| **Local PostgreSQL** | Docker `--profile with-db` | Self-hosted, Docker-based setups |\n\nSQLite stores data at `~/.archon/archon.db` (or `/.archon/archon.db` in Docker). It is auto-initialized on first run.\n\n## Testing\n\n| Guide | Audience |\n|-------|----------|\n| [E2E Testing](/deployment/e2e-testing/) | Developers and operators |\n| [E2E Testing on WSL](/deployment/e2e-testing-wsl/) | Developers on Windows |\n","packages/docs-web/src/content/docs/deployment/local.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Flocal.md -->","packages/docs-web/src/content/docs/deployment/reverse-proxy.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Freverse-proxy.md -->","packages/docs-web/src/content/docs/deployment/windows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Fwindows.md -->","packages/docs-web/src/content/docs/getting-started/ai-assistants.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fai-assistants.md -->","packages/docs-web/src/content/docs/getting-started/configuration.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fconfiguration.md -->","packages/docs-web/src/content/docs/getting-started/first-workflow.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Ffirst-workflow.md -->","packages/docs-web/src/content/docs/getting-started/installation.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Finstallation.md -->","packages/docs-web/src/content/docs/getting-started/operate.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Foperate.md -->","packages/docs-web/src/content/docs/getting-started/quick-start.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fquick-start.md -->","packages/docs-web/src/content/docs/getting-started/sdlc-factory.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fsdlc-factory.md -->","packages/docs-web/src/content/docs/getting-started/updating.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fupdating.md -->","packages/docs-web/src/content/docs/getting-started/why-archon.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fwhy-archon.md -->","packages/docs-web/src/content/docs/guides/approval-nodes.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fapproval-nodes.md -->","packages/docs-web/src/content/docs/guides/authoring-commands.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fauthoring-commands.md -->","packages/docs-web/src/content/docs/guides/container-isolation.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fcontainer-isolation.md -->","packages/docs-web/src/content/docs/guides/global-workflows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fglobal-workflows.md -->","packages/docs-web/src/content/docs/guides/hooks.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fhooks.md -->","packages/docs-web/src/content/docs/guides/index.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Findex.md -->","packages/docs-web/src/content/docs/guides/loop-nodes.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Floop-nodes.md -->","packages/docs-web/src/content/docs/guides/mcp-servers.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fmcp-servers.md -->","packages/docs-web/src/content/docs/guides/multi-repo-projects.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fmulti-repo-projects.md -->","packages/docs-web/src/content/docs/guides/proactive-workflows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fproactive-workflows.md -->","packages/docs-web/src/content/docs/guides/publishing-plugins.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fpublishing-plugins.md -->","packages/docs-web/src/content/docs/guides/script-nodes.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fscript-nodes.md -->","packages/docs-web/src/content/docs/guides/skills.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fskills.md -->","packages/docs-web/src/content/docs/guides/workflow-triggers.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fworkflow-triggers.md -->","packages/docs-web/src/content/docs/guides/worktrees.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fworktrees.md -->","packages/docs-web/src/content/docs/reference/api.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fapi.md -->","packages/docs-web/src/content/docs/reference/architecture.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Farchitecture.md -->","packages/docs-web/src/content/docs/reference/archon-directories.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Farchon-directories.md -->","packages/docs-web/src/content/docs/reference/cli.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fcli.md -->","packages/docs-web/src/content/docs/reference/commands.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fcommands.md -->","packages/docs-web/src/content/docs/reference/configuration.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fconfiguration.md -->","packages/docs-web/src/content/docs/reference/database.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fdatabase.md -->","packages/docs-web/src/content/docs/reference/forge.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fforge.md -->","packages/docs-web/src/content/docs/reference/index.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Findex.md -->","packages/docs-web/src/content/docs/reference/node-execution.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fnode-execution.md -->","packages/docs-web/src/content/docs/reference/provider-capabilities.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fprovider-capabilities.md -->","packages/docs-web/src/content/docs/reference/sdlc-workflows.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fsdlc-workflows.md -->","packages/docs-web/src/content/docs/reference/security.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fsecurity.md -->","packages/docs-web/src/content/docs/reference/troubleshooting.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Ftroubleshooting.md -->","packages/docs-web/src/content/docs/reference/variables.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fvariables.md -->","packages/docs-web/src/content/docs/reference/workflow-language.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fworkflow-language.md -->","packages/forge/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fforge%2FREADME.md -->","packages/provider-contract/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fprovider-contract%2FREADME.md -->","packages/providers/src/codex/protocol/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fproviders%2Fsrc%2Fcodex%2Fprotocol%2FREADME.md -->","packages/web/src/experiments/console/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fweb%2Fsrc%2Fexperiments%2Fconsole%2FREADME.md -->","packages/web/src/experiments/console/builder/README.md":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fweb%2Fsrc%2Fexperiments%2Fconsole%2Fbuilder%2FREADME.md -->","plugins/chat-slack/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fchat-slack%2Farchon-plugin.json -->","plugins/forge-github/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fforge-github%2Farchon-plugin.json -->","plugins/provider-claude/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-claude%2Farchon-plugin.json -->","plugins/provider-codex/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-codex%2Farchon-plugin.json -->","plugins/provider-pi/archon-plugin.json":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-pi%2Farchon-plugin.json -->"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Working on Archon\n\nArchon is a self-hostable, governed agentic automation engine. It runs workflows that mix deterministic steps, AI agents, human gates, and audit trails. Coding automation is its most mature surface, but the engine is intended for general business operations too.\n\nThis file is the canonical project guidance for coding agents. Keep it short, durable, and about judgment. Put changing product direction in [`.archon/direction.md`](.archon/direction.md), workflow-language design in [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md), aspirational engineering craft and review values in [`.archon/engineering.md`](.archon/engineering.md), user documentation in the docs site, and machine-checkable rules in code, types, tests, lint, or CI.\n\n## Read before changing code\n\n- Treat the source, its call sites, and its tests as the current evidence. The request defines the goal and constraints, but a prompt may describe old code.\n- Read every relevant consumer before changing a producer, contract, path, identifier, schema, or generated artifact.\n- Reproduce bugs when practical. If reproduction is impossible, establish another concrete causal chain before editing.\n- Do not trust a passing test by itself. Check that it exercises the behavior it claims to protect.\n- A question is read-only. Explain first; change code only when the user asks for a change.\n- Do not stop at editing. Finish the requested change and verify the outcome.\n- If the implementation keeps getting more complicated, stop and reconsider the model. Solve the underlying problem.\n\n## Product boundaries\n\n- Archon is a governed automation engine, not a replacement for the coding agents it orchestrates.\n- One install serves one operator or client. Client isolation belongs at the deployment layer. Multiple users within one install are supported and are not multi-tenancy.\n- Keep the conversation layer platform-agnostic. Platform adapters translate transport details; core behavior should not depend on Slack, Telegram, GitHub, Discord, CLI, or Web UI semantics.\n- Keep the core self-hostable. Do not make a proprietary hosted service a runtime dependency.\n- Keep the core small and flexible. Agentic engineering changes too quickly for one monolith to encode every useful workflow or integration; give users stable seams to assemble and extend the rest.\n- Treat bundled workflows as reference implementations, not a frozen compatibility surface.\n- A git worktree separates checkouts; it is not an execution or security sandbox.\n- Consult [`.archon/direction.md`](.archon/direction.md) before making product-scope or architecture-direction decisions.\n\n## Engineering taste\n\n### Prefer the smallest truthful system\n\n- Apply KISS and YAGNI. Do not add configuration, interfaces, lifecycle states, compatibility paths, or guards without a current requirement or concrete failure mode.\n- Before adding machinery, look for dead, redundant, or superseded machinery on the same path that can disappear.\n- Fix drift on the path you touch rather than preserving it for a cleaner diff. Two sites that should agree and do not are a defect, not a diff-size decision.\n- Duplicate small local logic when it keeps ownership clear. Extract only after a pattern has repeated and stabilized.\n- Prefer explicit control flow and narrow typed interfaces over meta-programming, shared mutable state, or hidden coupling.\n- Keep policy, transport, persistence, and execution concerns separate.\n- Consider a focused rewrite when it produces a smaller, clearer result without widening risk.\n\n### Let types carry invariants\n\n- Keep TypeScript strict. Avoid `any` and broad assertions when a sound type can express the state.\n- Two declarations that must agree to stay correct, kept in agreement by discipline rather than by a mechanism, are a present defect and not a future risk. Treat a hand-synced pair as a bug the moment it exists, and as a live one once the sides already disagree. Derive from the owner instead: an import, a derived or mapped type, a generated artifact with an owning script, or an enforced conformance test. A \"keep in sync\" comment is none of those; it records the defect rather than clearing it.\n- Import external SDK types rather than restating them. Where a boundary genuinely cannot share one definition, the copy owes a conformance test.\n- Use discriminated unions and constructors to make invalid states hard to represent.\n- Keep interfaces narrow. Extend an existing interface only when the new method belongs to the same responsibility.\n\n### Fail clearly\n\n- Fail early on unsupported, ambiguous, or unsafe states. Silent fallback in an agent runtime can waste money or broaden capabilities.\n- Never silently swallow an error or broaden permissions.\n- Translate errors at the boundary that can explain them, while preserving the original evidence for logs and diagnostics.\n- A fallback must be intentional, safe, documented near the branch, and observable enough to debug.\n- Never log credentials, secret values, user-message contents, or unnecessary personal data.\n\n### Write for the next maintainer\n\n- Comments should explain a non-obvious invariant, ownership decision, or reason a tempting alternative is wrong. Do not narrate the code.\n- Delete stale comments and documentation when behavior changes.\n- Use one precise name for each concept. Prefer direct prose, active voice, and concrete mechanisms over slogans or vague summaries.\n- Avoid hardcoded examples that will become false as files, counts, providers, or routes change.\n\n## Agentic-system boundaries\n\n### Natural language is not a wire format\n\n- Let the agent interpret the full natural-language message. Do not reconstruct intent with regexes, keyword lists, exact prose tokens, or hand-written parsers.\n- Put determinism after interpretation: give the agent typed tools, then validate resolved identifiers, arguments, permissions, and invariants at the tool boundary.\n- If exact syntax is required, expose a structured interface such as a CLI flag, typed tool, button action, or schema.\n- In a workflow, prose inside a node is model reasoning. Prose passed between nodes as a token to parse is an invented protocol; use structured output, typed inputs, exit status, or another explicit engine channel.\n- Vendor output is prose too. Classifying git, SDK, or vendor error text with a pattern is a last resort, not a carve-out: prefer the structured channel (exit code, typed error class, `--json` or porcelain output), then agent classification into a typed value, then an honest unclassified failure carrying the original evidence. A match that survives anchors to a machine token embedded in the message (an errno, an error code) and may enrich a report — never gate retry, fallback, or suppression, where a vendor rewording silently changes behavior.\n\n### Do not guess lifecycle ownership\n\n- A process must not mark non-terminal work failed, cancelled, or abandoned when it cannot distinguish a live owner from an orphaned process.\n- Surface ambiguous ownership and give the operator an explicit action.\n- Timers remain appropriate for recoverable operations such as retry backoff, subprocess timeouts, and cleanup of already-terminal data.\n- Terminal state transitions and completion signals belong to the engine, not to workflow-specific scripts that reinterpret run state.\n\n### Project guidance should be available, not sprayed everywhere\n\n- This `AGENTS.md` is the project-guidance channel for agent nodes. Provider integrations should preserve that contract through the provider's native context mechanism.\n- `AGENTS.md` may point to focused project context such as direction or language-design documents.\n- Do not inject every project document into every node. A node should receive only the context its responsibility requires.\n- The engine and reusable workflow packs must not hardcode a project's context paths.\n\n### Preserve provider configuration; scope capabilities\n\n- Provider integrations must preserve the provider's native user and project configuration, including its global and project guidance files. The user's provider configuration owns whether those sources load; Archon must not replace a provider home directory or add a parallel context switch.\n- Workflow AI nodes start without ambient skills, plugins, or MCP servers. A node receives only the skills, plugins, and MCP servers it explicitly names. Enforce that boundary without hiding unrelated provider settings.\n- Pi extensions are user-owned provider configuration, not Archon-managed plugins. Load them as Pi configures them; any tools or MCP clients an extension exposes are the user's responsibility.\n- Leave native prompt templates and commands under provider control unless the provider automatically advertises them to the model as a skill-like capability. In that case they follow the same explicit node-scoping rule as skills and plugins.\n- Preserve the selected agent provider's native authentication contract. A Claude Code subscription runs through the Claude Agent SDK; using an Anthropic model through another provider is not Claude Code subscription support. Do not reroute or relabel provider identity to reuse credentials.\n- Provider-boundary tests must prove both halves of the contract: native settings and guidance remain visible, while undeclared skills, plugins, and MCP servers remain unavailable and explicitly named capabilities load.\n\n## Workflow-language boundary\n\nThe governing rule is: **YAML coordinates. Code computes. Agents judge.** Read [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md) before changing the YAML surface. Read [`.archon/workflows/sdlc/CONSTITUTION.md`](.archon/workflows/sdlc/CONSTITUTION.md) before changing the SDLC pack.\n\n- YAML should expose only what the engine must see to govern a run: topology, gates, joins, retries, sessions, artifacts, durable waits, and reusable structure.\n- Computation belongs inside a `bash:`, `script:`, or `prompt:` node. Choosing a script instead of a prompt is an ordinary reliability and maintainability decision, not a constitutional rule.\n- A new YAML field or expression feature must add governance value that existing node bodies and wiring cannot provide.\n- Do not grow `when:` one operator at a time. Compute a complex decision in a node and gate on structured output.\n- Composition must preserve a graph the engine can validate and govern. Runtime-created independent work belongs in a child run, not hidden dynamic YAML structure.\n- Provider capabilities normally belong in provider config, tiers, or aliases. A new node field must earn genuine per-node variance.\n- Implicit behavior must be documented, defeatable where appropriate, visible, and fail-safe.\n\n## Architecture and ownership\n\n- Preserve package boundaries. Inspect package manifests and current imports before adding a dependency; do not rely on a copied package inventory.\n- Provider SDK dependencies and SDK-specific option translation belong in `@archon/providers`.\n- Workflow execution depends on injected contracts, not on core database or adapter implementations.\n- Platform-specific parsing and authorization belong inside the owning adapter. Core receives normalized intent and identity.\n- The Web client consumes generated API types through its local API layer; it does not import server or workflow runtime packages.\n- The engine (`packages/`) and the bundled SDLC pack (`.archon/workflows`, `.archon/scripts`) don't import each other's code, in source or in tests. They meet through the workflow language, the CLI and the provider and forge contracts.\n- Shared schemas are the source of truth for persisted and API shapes. Keep OpenAPI generation and runtime validation on the same schema path.\n- Use the type system, schema validation, and package boundaries to enforce architecture before adding prose rules.\n\n## Data and compatibility\n\n- Database schema evolution is additive-only. There is no migration ledger or version gate, and older binaries may open the same database.\n- Never rename, retype, or drop a shipped table or column. A new `NOT NULL` column needs a default that remains valid for older writers.\n- Keep SQLite and PostgreSQL schemas aligned unless a narrow, documented exception is already enforced.\n- Test upgrades from shipped schema shapes, not only fresh database creation.\n- Keep generated or mirrored artifacts synchronized through their owning scripts. Find those scripts in `package.json` and adjacent tests instead of copying a list here.\n- Persisted identifiers, paths, and wire values are contracts. Trace them through every reader before changing their meaning.\n\n## Git and workspace safety\n\n- `main` is the release branch and `dev` is the working base. Never commit directly to either for feature work, and never force-push `main` or `master`.\n- Preserve user changes, including untracked files. Do not use `git clean -fd`, destructive resets, or broad cleanup commands on user-owned workspaces.\n- Let git report conflicts and dirty state. Do not hide or automatically resolve a state the operator owns.\n- Hard reset is allowed only for an explicit reset mode on an Archon-owned checkout.\n- Run artifacts, logs, and state belong outside the repository under the Archon workspace. Do not stage them.\n- When stopping a process, target the recorded PID or exact bound port. Never kill by a broad process-name match.\n- Keep commits and pull requests focused. Do not mix unrelated cleanup into the requested outcome.\n\n## Running Archon workflows\n\nThe input is the contract. A run is only as accurate as the brief it starts from, and a\nworkflow cannot recover from a premise that was never stated — it will produce a confident,\nwell-formed answer to the wrong question. Treat writing the input as the work, not the\npreamble to it.\n\nBefore launching a run, the input — an issue body, a message, a document, whatever the run\nreads — must state:\n\n- the problem to solve;\n- why it is worth solving;\n- why now;\n- the desired outcome;\n- the invariants that must hold;\n- what acceptance looks like.\n\nSolution steering is optional and belongs last, but omitting it is a decision: the run then\nchooses its own approach. State a constraint you actually hold — reuse this primitive, no new\ndependency, migrate rather than rewrite — because an unstated preference cannot be honoured.\nNaming an implementation *before* the problem is settled narrows the run to your first guess.\n\nDo not launch a run against an input missing any of the six. Fix the input first, and say\nwhat you changed — a thin brief is cheaper to correct before a run than after one.\n\n## Tests and validation\n\n- Add tests for meaningful behavior and failure modes. Do not preserve deleted features through regression-test clutter.\n- The engine tests what it owns (the workflow language, script nodes, state, waits, resume) with its own minimal fixtures, never with a pack workflow. The pack tests only its own scripts at a tool boundary and never retests the engine's behavior.\n- Prefer deterministic, cheap tests. Remove accidental I/O and unnecessary subprocesses before increasing timeouts.\n- Remove test temp trees with `removeTempTree` or `trackTempRoots`: from `@archon/paths/test-utils` in engine and `scripts/` tests, and from the pack's own `.archon/scripts/__tests__/temp-roots.ts` in pack tests.\n- Spawn a subprocess only when the subprocess is the subject under test. To suppress an inherited env key for a Bun child, pass it as `''`; deleting it allows `.env` loading to restore it.\n- Bun's module mocks pollute the process cache. Use the package test scripts that preserve isolation; do not run `bun test` from the repository root.\n- Run lint through `bun run lint`. Besides Biome, it runs the repository checks Biome cannot express, such as the workflow persistence boundary.\n- Check what you changed: run the tests that cover it, lint and type-check the packages you touched, and run `bun run check:ratchet` before you push. CI refuses a change the ratchet refuses, and it takes seconds locally against a fetched `origin/dev`.\n- CI is the full validation. Don't run `bun run validate` to finish a change. `scripts/validate.ts` owns the check list and CI selects from it; workflow fixtures run locally only through `--only`.\n- Schema changes also need the PostgreSQL upgrade check. [`CONTRIBUTING.md`](CONTRIBUTING.md) lists it with its command.\n- For visual or runtime behavior, add direct evidence when static tests cannot prove the outcome.\n- Destructive verification, including DDL, migrations, and data writes, runs only against a scratch database you create and drop. A configured live DSN is read-only at most; when only a live resource exists, stop and surface it to the operator.\n\n## Pull requests\n\n- Branch from `dev` and open the pull request back to `dev`.\n- Use [`.github/pull_request_template.md`](.github/pull_request_template.md). Keep its required sections, remove instructional comments and irrelevant conditional sections, and describe the problem before the implementation.\n- Review the complete diff against the actual base branch before opening the pull request.\n- Link the source issue with a closing keyword when the work has one.\n- Keep titles concise and outcome-focused. Do not add AI attribution to commits or pull requests.\n\n## Find current facts\n\nDo not expand this file with copied reference material. Use the owning source:\n\n- Product direction: [`.archon/direction.md`](.archon/direction.md)\n- Code structure, flows, extension points and enforced boundaries: [`.archon/architecture.md`](.archon/architecture.md)\n- Workflow-language design: [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md)\n- Engineering craft and review values: [`.archon/engineering.md`](.archon/engineering.md)\n- CLI: [`packages/docs-web/src/content/docs/reference/cli.md`](packages/docs-web/src/content/docs/reference/cli.md) and `archon --help`\n- Workflow authoring: [`packages/docs-web/src/content/docs/reference/workflow-language.md`](packages/docs-web/src/content/docs/reference/workflow-language.md) and workflow schemas\n- Database behavior: [`packages/docs-web/src/content/docs/reference/database.md`](packages/docs-web/src/content/docs/reference/database.md), migrations, and adapter tests\n- Available commands and generated-file checks: root and package `package.json` scripts\n","isInternal":false,"tokens":3688,"sizeBytes":18432},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"Agent rules: read @AGENTS.md\n","isInternal":false,"tokens":9,"sizeBytes":29},{"name":"authoring-workflows.md","path":".claude/skills/archon-cli/authoring/authoring-workflows.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/authoring/authoring-workflows.md","title":"Authoring Skill","category":"anthropic-skill","format":"markdown","content":"---\nname: archon-cli-authoring\ndescription: Design and write an Archon workflow: outcome-first decomposition, gate design, node types, loop channels, packaging, fixtures. Read when creating or editing workflow YAML.\n---\n\n# Authoring Workflows\n\nDesign and write an Archon workflow — for code work, business operations, or any\ngoverned process. The method is outcome-first; the mechanics follow from it.\nExemplars: the bundled sdlc pack in any Archon checkout under\n`.archon/workflows/sdlc/<primitive>/` — read a pack YAML before writing your own.\nArchon is not code-only: triage an inbox of tickets, reconcile accounts,\nscreen applicants, keep CRM records honest — the same node types, gates, and\ndiscipline apply. What changes per domain is where the gates come from (below).\n\n## The design sequence\n\nWork through these questions in order, with the user where marked:\n\n1. **Desired outcome.** What artifact or world-state exists when this succeeds?\n   One sentence. If it cannot be stated as an outcome, it is not ready to build.\n2. **Specificity (with the user).** Is this workflow written against ONE project\n   or outcome, or meant to be generally reusable? This changes everything\n   downstream: a project-specific workflow may hardcode that repo's real gates,\n   real commands, real rules, and speak in its vocabulary — which makes it far\n   more powerful than any generic equivalent. Ask before designing.\n3. **Reuse check.** Which of the USER'S OWN workflows already do part of this?\n   (`archon workflow list`, then read their YAML.) Compose those via `include:`.\n\n   **The bundled sdlc pack is NOT a component library.** Its workflows exist to\n   teach how good workflows are written — read them as worked examples of gate\n   placement, prompt contracts, and fixture discipline, never `include:` them\n   into a user workflow by default. Their prompts and gates are deliberately\n   general so they fit any project; a user workflow composed out of them inherits\n   that generality and loses the specificity that made it valuable. If the user\n   asks for \"something like archon-ship but for my repo\", write a new workflow in\n   their project's terms, using the pack only as the craft reference.\n4. **Primitives.** Thinking in primitives is right: name the small jobs that\n   compose into the outcome — investigate, decide, implement, verify, publish;\n   or intake, enrich, judge, route, notify for an ops queue.\n   Package each coherent reusable primitive as a workflow folder with declared\n   `inputs:` and `returns:`. It can then run alone or compose into a parent with\n   `include:` and `with:`. Nodes implement a primitive's internals and the small\n   one-off glue in a parent: use an AI node for judgment and code for a checkable\n   fact. The primitive set comes from THIS outcome, not from copying another\n   workflow's node list.\n5. **Gate placement (with the user).** Where must the run stop before\n   continuing, and what decides? Three gate kinds, all first-class:\n\n   - **Deterministic gates** — a `bash:`/`script:` node owning an exit code or a\n     `when:` on a declared field. Use wherever a checkable fact settles it —\n     in code work: tests pass, file exists; in ops: totals reconcile, every\n     row has an owner, the export count matches the source. Cheapest and least\n     arguable.\n   - **Prompt command gates** — an independent verifier judging another agent's\n     output against an evidence bar. Start the verifier with `context: fresh`,\n     have it read the diff/artifact/entry, check claims and criteria, and return a\n     structured boolean that drives the loop or branch. When gate design is\n     unknown, START HERE: an agent checking agents is the most powerful\n     general-purpose gate and catches what no fixed check anticipated. Keep the\n     verdict structured (`output_format` boolean) so a machine consumes it; keep\n     the judgment prose in the prompt. Grounded on an **authored sidecar** — a\n     versioned rubric/directions file in the repo — an agent judgment gate stands\n     in for a typical human reviewer: same criteria, same questions, recorded\n     answers, every run.\n   - **Attention pauses** (`wait: { attention: ..., form: approval }`) — only\n     two cases justify them: an **interactive** workflow where someone is present\n     at the console and reads each iteration, or a **super load-bearing** action — irreversible,\n     public-facing, or expensive beyond easy rollback (deploy, post publicly,\n     delete data, send customer-facing communication). Everything else should be\n     decidable by code or by another agent. The pause holds the run durably in the\n     attention queue, whether it started in the foreground, detached, or from a\n     trigger. Container-isolated workflows cannot hold one: Archon refuses a\n     workflow with any `wait:` node under container isolation before creating the\n     run. Reject is an answer, not a cancel: add a `cancel:` node behind\n     `when: \"$<id>.output.decision == 'reject'\"` where rejecting should end the run.\n     The older `approval:` node is deprecated.\n\n   On a project-specific workflow the user often already knows some of these —\n   \"never push before I see the diff\", \"stop if the migration touches billing\",\n   \"no applicant advances without both scorecards agreeing\" — collect them\n   verbatim; they become attention pauses, cancel nodes, or `when:` conditions.\n   Everything else defaults to a prompt command gate until a real run proves\n   which part can be made deterministic.\n\n   **Gates are latent in every domain.** Any process humans govern already\n   invented its controls: double-entry, reconciliation, four-eyes review,\n   checklists, read-backs. Elicitation is the authoring work — have a strong\n   model propose candidate gates from domain knowledge, then have the user (the\n   domain expert) validate which ones actually bind. A proposed gate earns trust\n   only after it has demonstrably refused a planted bad outcome — prove gates red\n   the way you prove fixtures red.\n6. **Data flow.** What does each node hand the next? Declared values\n   (`$node.output`, `output_format` fields, workflow `inputs:`/`returns:`) carry\n   JSON values; artifacts (`$ARTIFACTS_DIR`) carry files. The producing node owns\n   the schema, including for script results and composed workflows. Read\n   `node-reference.md` → Result contracts before declaring a result, and\n   `variables.md` → Artifact pointers when returning a file reference.\n7. **Prompting** per node → see `prompting-mistakes.md` in this folder before writing prose.\n8. **Wiring** into YAML (below), then **fixtures**: every authored workflow ships\n   a dry-run fixture under `<pack>/<workflow>/fixtures/*.stubs.yaml` declaring its\n   expected outcome. Prove red paths red before trusting them. Fixtures prove the\n   paths that dry-run can execute. A reachable `workflow:` or `include:` +\n   `fan_out:` node fails dry-run by design, so prove that path with a small real\n   acceptance run while keeping fixtures for the surrounding wiring.\n9. **Validate** before any real run. None of these steps spends on a model:\n\n   ```bash\n   archon validate workflows <name>                    # schema, references, command files\n   archon workflow run <name> --dry-run --stubs-init stubs.yaml   # scratch stub scaffold\n   archon workflow run <name> --dry-run --stubs stubs.yaml\n   archon workflow test <name>                         # every declared fixture\n   ```\n\n   `--stubs-init` writes a block-style YAML stub scaffold to fill in. Keep it outside\n   `fixtures/`: a dry-run stub file is not a fixture. A fixture file declares a `fixture:` block, and\n   `--stubs` refuses it (\"this is a fixture file; run it with 'workflow test'\"). Copy\n   the stub values you need into a fixture and run it with `workflow test`. A dry run\n   walks the graph with stubbed node outputs and prints each node's resolved provider\n   and model; add `--exec-code` only for trusted `bash:` and `script:` nodes. Then make a planted\n   bad case fail: a fixture that never went red proves nothing. Finish with one real\n   run on a genuine task.\n\n## Agent-first: who decides what\n\nArchon's rule is **YAML coordinates. Code computes. Agents judge.** Give each kind\nof work to the one that does it well:\n\n- **Agents judge.** Reading a comment, a diff, a ticket or a CI log and deciding what\n  it means is a prompt node that returns a small typed result (`output_format`).\n  A script that tries the same judgment freezes one guess into code.\n- **Scripts give facts and act at boundaries.** A script reads the forge, git, files\n  or the CLI and hands the agent facts, or performs the write the agent decided.\n  It never re-judges what the agent already judged. Compute in a `bash:` or\n  `script:` node only when the rule is genuinely fixed (parsing JSON, counting,\n  comparing heads) or a typed invariant must hold before an irreversible action.\n- **YAML coordinates.** It holds topology, gates on typed fields, joins, sessions and\n  waits. Do not grow `when:` into logic; compute the decision in a node and gate on\n  its field.\n- **Natural language is not a wire format.** Never regex or keyword-match prose,\n  whether a user's message, an agent's output or a vendor's error text, to decide\n  what happens next. Let an agent read it and return a typed value.\n\nBad: a script classifies a review comment with a pattern, and the pattern is the\ndecision.\n\n```yaml\n- id: classify\n  bash: |\n    if printf '%s' \"$COMMENT\" | grep -qiE 'lgtm|approve|ship it'; then echo ready; else echo changes; fi\n- id: merge\n  depends_on: [classify]\n  when: \"$classify.output == 'ready'\"\n  script: merge\n  runtime: bun\n```\n\nGood: an agent reads the comment and returns a typed verdict, and the script only\nacts on it.\n\n```yaml\n- id: verdict\n  prompt: |\n    Read the review comment in $ARTIFACTS_DIR/comment.md. Decide whether the\n    reviewer approves merging this change as it stands.\n  output_format:\n    type: object\n    properties:\n      ready: { type: boolean }\n      reason: { type: string }\n    required: [ready, reason]\n- id: merge\n  depends_on: [verdict]\n  when: \"$verdict.output.ready == true\"\n  script: merge\n  runtime: bun\n```\n\nTest at the same seams. A script gets a test at its tool boundary: it writes an issue\nonce, reads back a pushed head, merges only what was approved. Agent judgment is\nchecked by outcome, through dry-run fixtures (`archon workflow test`) and real runs,\nnever by a test that pins a prompt's wording, the YAML's shape or the route a run\ntakes. Do not retest what the engine owns (`when:`, includes, loops, output\nvalidation). In an Archon checkout, `.archon/workflow-language-constitution.md` and\n`.archon/workflows/sdlc/CONSTITUTION.md` (sections 1 to 5 and 13) state these rules\nin full.\n\n## Start simple, then adapt to what runs teach\n\nThe first version is deliberately under-engineered:\n\n- Use few nodes, short prompts, and key gates. Do not add determinism without evidence;\n  before a real run, it is only a guess about where things fail.\n- Dogfood it immediately on something real — a genuine task in the actual repo,\n  not a demo. The run itself is the test.\n- Adapt from evidence, not anticipation: a failure shows which gate was missing;\n  a wasted review shows which check earned its place. Add machinery only where a\n  logged occurrence demands it.\n- Iterate in place: edit the folder, rerun the same kind of task, compare the\n  artifacts. The workflow converges on the project instead of being designed\n  up front.\n\nAll mechanical detail lives in `node-reference.md` (every node type and field,\nwhen:/trigger_rule wiring, fixtures) with the full substitution semantics in\n`variables.md` — read both when writing YAML.\n\n## File layout — one folder per workflow\n\nKeep each workflow in its own folder. If its scripts use pack-level shared\nmodules, copy the pack when moving it between repos. To start a workflow:\n\n```bash\nmkdir -p .archon/workflows/my-pack/my-workflow/{commands,scripts,fixtures}\n```\n\n```text\n.archon/workflows/<pack>/<workflow>/\n├── <workflow>.yaml      # name, description, inputs:, returns:, nodes:\n├── commands/*.md        # prompt files referenced by command: nodes\n├── scripts/*.py|.ts     # deterministic logic (Python/uv preferred, TS/bun fine)\n└── fixtures/*.stubs.yaml\n```\n\nSubstantive prompts live in command files, not inline. Inline `bash:` only for\none-or-two-line glue; anything with branching is a script file. Never shell as a\nscript language for logic. Ask the user their script-language preference if they\nwill maintain it. Declare Python dependencies inline (`deps:` for `uv`); keep\n`node_modules` and virtual environments out of the packaged script tree.\n\nPut shared `.ts`, `.js`, or `.py` modules and `.md` documents in\n`<pack>/.shared/` (subfolders are supported). This reserved directory supplies\nmodules and documents, never workflow definitions or `script:` targets. Documents\nship beside the modules in every build, so a script finds one relative to its own\nlocation and hands its path to a prompt node through `with:`. Missing packaged script targets fail at load time. From `<workflow>/scripts/publish.ts`, Bun can import\n`../../.shared/result.ts`. Python files can add\n`str(Path(__file__).resolve().parents[2] / \".shared\")` to `sys.path` using\n`pathlib.Path`, then import normally; Archon does not inject `PYTHONPATH`.\nAdjust the relative depth for grouped scripts.\n\nThe same paths work in project/global source, bundled binaries, and frozen\ncaptures. Keep imports inside the pack and require no npm packages or target\nproject configuration. Use regular module files: the binary generator rejects\nsymlinks under `.shared`. Write outputs to `ARTIFACTS_DIR` or `STATE_DIR`, never\nbeside a script; Python bytecode caching is disabled in runs and executable fixtures.\n\n## Node types — one per node\n\n`command` (prompt file) · `prompt` (inline) · `bash` (shell, no AI) · `script`\n(py/ts, no AI) · `loop` (iterate one AI node) · `loop_group` (repeat a sub-DAG) ·\n`approval` (deprecated; use `wait: attention`) · `cancel` (terminate with reason, usually behind `when:`) ·\n`wait` (durable suspension, or an `attention:` question answered in a form) · `workflow` (governed child run) · `include`\n(plain load-time composition or runtime-width in-run composition with `fan_out:`).\n\nSelection rules of thumb:\n\n- A machine-verifiable check → `bash:`/`script:` node, never an AI asked to \"check\".\n- Model judgment that gates downstream spend → AI node with `output_format` boolean\n  + a downstream gate or loop channel reading that field.\n- Repeated act→verify cycle → `loop_group` with the verify node's structured\n  boolean driving completion.\n\n## Loop completion channels — pick by tier\n\n- Completion externally checkable → `until_bash: \"<exit-0 check>\"`.\n- Completion is the model's judgment → `output_format` + `until_field: <bool-field>`.\n- Prose sentinel (`until: TOKEN`) is deprecated; prefer the channels above.\n  Declare at least one channel per loop.\n\n## Structured output: only where a machine decides\n\nDeclare `output_format` when a `when:`, `until_field`, or script consumes the\nfield. Otherwise let output stay whole-string prose — schemas are a reliability\ntax on non-enforcing providers. Provider-agnostic YAML always: tiers\n(`small|medium|large`) never literal model ids, no `provider:` keys in bundled work.\n\n## Isolation and interactivity\n\n- At workflow level, `worktree.enabled: true` pins worktree isolation. Omission\n  leaves the choice to the caller. On a `workflow:` child node,\n  `isolation: worktree` explicitly gives the child its own worktree. A worktree\n  bounds git files; it does NOT isolate `~/.archon`, env, database, or sessions.\n- A `wait: attention` node pauses the run in the attention queue until it is answered. It\n  needs no workflow-level declaration and does not stop a detached or triggered launch.\n- Composition (`include:`) runs inside the parent's single run/isolation/audit\n  trail. Use `workflow:` sub-runs only when a separately governed launch is the\n  actual requirement.\n\n## Common mistakes\n\nRead before writing YAML:\n\n- **Prose between nodes as a wire format.** Emitting a token so the next node can\n  grep it. Fix: structured output + typed reads, or a script exit code.\n- **AI doing what code should.** \"Check if tests pass\" as a prompt. Fix: bash/script\n  node owning the exit code.\n- **Parsing model prose to decide.** Regex over `$node.output`. Same fix.\n- **Hardcoding the project's toolchain** (`tsc`, `pytest`) in default-workflow\n  gates. Deterministic checks verify only what Archon owns (git state, artifacts,\n  exit codes of agent-discovered commands).\n- **Silent fallbacks.** An optional input that degrades quietly instead of failing.\n  Fail loud with the missing thing named.\n- **Trusting exit codes / run status as verdicts.** A declined task exits 0;\n  gate on artifacts existing and validating, and on declared outcome fields.\n- **Skipping fixtures.** Unfalsified wiring is unknown wiring. Expected-red paths\n  especially: seen red, or they prove nothing.\n- **Inline everything.** Long prompts inline in YAML, logic inline in bash.\n  Files age better, diff better, and are reusable across nodes.\n","isInternal":false,"tokens":4027,"sizeBytes":17264},{"name":"node-reference.md","path":".claude/skills/archon-cli/authoring/node-reference.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/authoring/node-reference.md","title":"Authoring Skill","category":"anthropic-skill","format":"markdown","content":"# Node Reference\n\nMechanical reference: every node type, its fields, how nodes wire together, and\nhow to prove wiring with fixtures. For design judgment (gates, primitives,\nprompting) read `authoring-workflows.md` in this folder first.\n\n## Workflow-level fields\n\n```yaml\nname: my-workflow            # unique; user workflows may shadow bundled names\ndescription: |\n  What this does and when to use it. Shown by `archon workflow list`.\nmodel: large                 # tier keyword (small|medium|large) or @alias — never a literal model id\ninputs:                      # declared signature; callers pass via --input name=value\n  work:\n    default: \"\"\n    description: Optional work override; prompts read $ARGUMENTS explicitly for fallback\nreturns: implement           # which node's output is the workflow's terminal output\noutcome_field: green         # persist one authored boolean beside run status (e.g. green/ready/rooted)\nworktree:\n  enabled: true              # pin worktree isolation. Bounds git files ONLY —\n                             # not ~/.archon, env vars, database, or sessions\nmutates_checkout: false      # declare when the workflow never touches the working tree\n```\n\nAn input default does not fall back to the invocation message automatically.\nWhen both are valid sources, give the model both values and state the precedence:\n\n```yaml\n- id: implement\n  prompt: |\n    Optional work override: $INPUTS.work\n    Original invocation: $ARGUMENTS\n    Use the work override when it is non-empty; otherwise use the original invocation.\n```\n\n## The eleven node types\n\nExactly one of these appears per node: `command`, `prompt`, `bash`, `script`,\n`loop`, `loop_group`, `approval`, `cancel`, `wait`, `workflow`, `include`.\n`approval` is deprecated; new pauses use `wait: { attention: ..., form: ... }`.\n\n### command / prompt — AI nodes\n\n```yaml\n- id: plan\n  command: plan              # loads <workflow-folder>/commands/plan.md — prefer files over inline prose\n  depends_on: [investigate]\n```\n```yaml\n- id: classify\n  prompt: \"Classify $ARGUMENTS. Set type to BUG or FEATURE.\"\n  allowed_tools: []          # no tools needed for pure judgment\n  output_format:             # declare ONLY when a machine consumes a field (gate/until_field/script)\n    type: object\n    properties:\n      type: { type: string, enum: [BUG, FEATURE] }\n    required: [type]\n```\n\nKey fields on AI nodes: `model`/`provider` (tiers), `output_format` (+ `output_type`\nfor typed artifact sidecars), `allowed_tools`/`denied_tools`, `effort`/`thinking`,\n`retry` (default 2x on transient), `persist_session` (continues across runs only on a\nprovider that supports session forks; others start fresh each run), `idle_timeout`.\n\nSession context is explicit when it matters:\n\n- Omitted context inherits the prior compatible provider session in a sequential\n  layer and starts fresh in a parallel layer.\n- `context: fresh` always starts clean. Use it for independent verification.\n- `context: shared` explicitly inherits the ambient session in a sequential\n  layer. It is invalid where parallel structure makes that ancestry ambiguous.\n- `context: { resume: plan }` forks the named completed upstream node's session.\n  Use it when one exact ancestry matters; the provider must support session forks.\n  Inside a `loop_group` body the source can be an enclosing node, whose session the\n  body continues across iterations, or an upstream node in the same body, whose\n  session of that iteration the consumer forks. The same-body form suits a follow-up that asks the agent to\n  finish a step it skipped.\n\n### bash — deterministic shell, no AI\n\n```yaml\n- id: test-gate\n  bash: bun run test\n  timeout: 300000            # ms; stdout captured as $test-gate.output\n```\nExit code decides pass/fail. On `timeout`, Archon signals a host node's process tree,\nso work behind `bun run` or another wrapper stops too. A process that leaves the node's\nprocess group (`setsid`) escapes. In a container node, a timeout ends only the local\n`docker exec` client, and the command inside the container may keep running. In a detached run on macOS or Linux, a timeout whose\ncleanup cannot be confirmed fails the node, even with `on_timeout: skip`.\n\nThe same node shape verifies non-code work:\n`bash: python3 scripts/reconcile.py --period 2026-08` (totals must balance),\nor a script that checks every imported row has an owner. One-or-two-line glue\nonly; anything with branching is a script node.\n\n### script — TypeScript (bun) or Python (uv), no AI\n\n```yaml\n- id: verify-report\n  script: verify-report      # loads <workflow-folder>/scripts/verify-report.py\n  runtime: uv\n  depends_on: [implement, review]\n  with:                      # typed value transport into INPUTS_<UPPER_SNAKE> env vars\n    green: \"$implement.output.green\"\n    prior: {from: \"$review.output\", if_skipped: \"none\"}\n```\n\nPrefer named script files over inline bodies. Python (`runtime: uv`) preferred,\nTypeScript (`runtime: bun`) fine. `deps:` adds uv dependencies.\n\n### Result contracts — the producer owns the schema\n\nUse the producing node's `output_format` for a JSON result. `returns:` selects\nthat node; it does not declare another schema. No value-shape declaration belongs\non `inputs:` or `returns:`.\n\n```yaml\nname: certified-result\ndescription: Certify a deterministic result and bind its field downstream\nreturns: build\nnodes:\n  - id: build\n    bash: printf '%s' '{\"ready\":true}'\n    output_format:\n      type: object\n      properties:\n        ready: {type: boolean}\n      required: [ready]\n  - id: consume\n    depends_on: [build]\n    with:\n      ready: \"$build.output.ready\"\n    runtime: uv\n    script: |\n      import os\n      print(os.environ[\"INPUTS_READY\"])\n```\n\nA `bash:` or `script:` node with `output_format` must print one strict JSON\ndocument satisfying that schema. Send diagnostics to stderr. There is no JSON\nrepair or reask; a contract violation fails the node and is not retried, even\nwith `retry:`. A schema that cannot compile is a load error. Without\n`output_format`, exec stdout remains raw text. Named scripts use the same rule;\nthe small bash example above keeps the handoff visible.\n\nAn `include:` alias exposes the selected producer's contract through flattening.\nA `workflow:` caller receives the child's selected result and declared field\nnames, persisted for cold resume. Do not repeat the schema on the caller:\n`output_format` on a `workflow:` node is a load error; on `include:` it is ignored\nwith a warning. A schemaless child stays schemaless. Fan-out returns an ordered\narray of item values; consume the whole aggregate, not a field on the array.\n\nFor field-access and file-pointer rules, read `variables.md` → Node Output Details and\nArtifact pointers. The docs' `reference/workflow-language.md` → Result contracts\ngives the full composition examples.\n\n### loop — iterate ONE AI job\n\n```yaml\n- id: implement\n  loop:\n    command: implement\n    until_field: done        # boolean in this node's output_format\n    max_iterations: 5\n  depends_on: [plan]\n  output_format:\n    type: object\n    properties:\n      done: {type: boolean}\n    required: [done]\n```\n\nCompletion channels — declare at least one, pick by tier:\n\n- `until_bash: \"<exit-0 check>\"` — completion externally checkable (deterministic)\n- `until_field: <bool>` — completion is the model's judgment, via `output_format`\n- `until: TOKEN` — deprecated prose sentinel; use one of the channels above\n\nAlso: `fresh_context: true` (each iteration starts clean) and `$LOOP_PREV_OUTPUT`\nfor previous-iteration state.\nA failed iteration fails the loop at `max_iterations` — bound exhaustion fails\ntruthfully rather than blessing a bad last attempt.\n\n### loop_group — repeat a sub-DAG\n\n```yaml\n- id: fix-cycle\n  depends_on: [review-findings]\n  loop_group:\n    nodes:\n      - id: fix\n        prompt: \"Address findings: $review-findings.output\"\n      - id: recheck\n        prompt: \"Verify each finding is fixed with evidence.\"\n        depends_on: [fix]\n        context: fresh\n        output_format: {type: object, properties: {ready: {type: boolean}}, required: [ready]}\n    until_bash: 'ready=$recheck.output.ready; test \"$ready\" = \"true\"'\n    max_iterations: 5\n```\n\nBody is sealed (no external `depends_on`); outer outputs reach bodies via\n`$nodeId.output`. In a `loop_group` body, `$LOOP_PREV.<body-id>.output` reads the\nprevious iteration: prompts text-substitute it, while `when:` conditions resolve it\nas a typed condition reference. A failed body node fails the group immediately.\n\n### approval — deprecated\n\n`approval:` still loads and runs, but the loader warns once per workflow file, naming\neach node and its replacement. Do not write new ones. Replace:\n\n- `approval: { message }` → `wait: { attention: <message>, form: approval }`. A legacy\n  reject cancels the run; the attention form continues with `{decision: 'reject'}`, so\n  add a `cancel:` node with `when: \"$<id>.output.decision == 'reject'\"` where rejecting\n  should end the run.\n- `approval.decisions` → `wait: { attention, form: select, options }` with the same ids\n  and labels; same `{decision, text}` output.\n- `capture_response` → read the answer's `$<id>.output.text`; every form records text.\n- `on_reject` / `$REJECTION_REASON` → a rework node with\n  `when: \"$<id>.output.decision == 'reject'\"` that reads `$<id>.output.text`, inside a\n  `loop_group` if it should repeat.\n\n### cancel — terminate deliberately\n\n```yaml\n- id: refuse\n  cancel: \"This workflow handles bugs only.\"\n  when: \"$classify.output.type != 'BUG'\"\n```\n\n### workflow / include — reuse another workflow\n\nUse `workflow:` to start a governed child run with its own artifacts, gates, cost,\nand audit trail. Use `include:` to flatten a reusable workflow block into the\ncurrent run at load time.\n\n```yaml\n- id: review-child\n  workflow: archon-review\n  with:\n    scope: \"$INPUTS.branch\"\n  isolation: worktree\n\n- id: review-inline\n  include: archon-review\n  with:\n    scope: \"$INPUTS.branch\"\n```\n\nThe `workflow:` target name is static YAML but resolves when the node executes,\nso a prior node may author the child workflow. `workflow:` also accepts `input:`\ninstead of `with:` and can declare `fan_out:`. A plain `include:` resolves and\nexpands fully at load time; the directive carries only structural graph fields\nplus `with:` and has no execution surface of its own. The include's `when:` gates the\nwhole block. Inside a `loop_group` it reads `$LOOP_PREV` from the loop that contains\nthe include.\n\nOn a `workflow:` node, `fan_out.max_parallel` limits unfinished children (default\n`5`). `max_parallel: all` starts every item at once: use it when each child asks an\nindependent attention question, because a paused child keeps its slot and a numeric\nwindow leaves later questions unstarted. Under `join: all_done` the node completes once\nevery child is terminal, even when some failed, never started or were cancelled; the run\nrecords each slot's outcome for operators (`workflow get`), but a downstream node that\njudges the results reads the `archon_failed` markers in `$<id>.output`.\n\nPut `fan_out:` on `include:` when runtime data determines how many copies of a\nstatic block should run inside the parent run:\n\n```yaml\n- id: list-scopes\n  bash: printf '[\"packages/cli\",\"packages/workflows\"]'\n\n- id: review-each\n  include: archon-review\n  depends_on: [list-scopes]\n  fan_out:\n    items: \"$list-scopes.output\"\n    as: scope               # required; each instance receives $INPUTS.scope\n    max_parallel: 1         # serial escape when the included block may write\n    join: all_done\n```\n\nThis form creates no child runs. It returns an item-ordered JSON array under\n`$review-each.output`. With `join: all_done`, a failed instance contributes an\n`archon_failed` marker; `all_success` fails the node. To run instances\nconcurrently, the complete included block must declare `mutates_checkout: false`.\nApproval gates, durable waits, and other suspension paths are not supported\ninside composed fan-out; put them before or after the fan-out.\n\n### wait — suspend durably\n\n```yaml\n- id: pause-for-ci\n  wait:\n    duration_ms: 300000\n```\n\nA wait declares exactly one condition: `duration_ms`, an ISO timestamp under\n`until`, `event` plus `deadline_ms`, or `attention`. Archon persists the suspension and\nreleases the worker slot. A time or event wait's fixed structured output includes\n`status` (`satisfied` or `expired`) and `waited_ms`; event waits may also return `event`\nand `payload`.\n\n`attention` asks a question that pauses the run until someone answers it in its `form`:\n\n```yaml\n- id: ask\n  depends_on: [plan]\n  wait:\n    attention: \"$plan.output.question\"\n    form: select              # message (default) | approval | select | multi_select\n    options:                  # select and multi_select only; or one whole \"$plan.output.options\"\n      - { id: use-main, label: \"Base on main\" }\n      - { id: use-dev }\n    labels:                   # optional; plain strings, runtime refs resolved at pause\n      about: \"$plan.output.topic\"\n```\n\nThe next node reads the answer: `{text}` for `message`, `{decision, text}` for `approval`\nand `select`, `{decisions, text}` for `multi_select`. In the `approval` form `reject` is\nan answer and never cancels the run; branch on `$ask.output.decision` with `when:`:\n\n```yaml\n- id: review\n  depends_on: [plan]\n  wait:\n    attention: \"Approve this plan? $plan.output\"\n    form: approval\n- id: stop\n  depends_on: [review]\n  when: \"$review.output.decision == 'reject'\"\n  cancel: \"Plan rejected.\"\n- id: implement\n  depends_on: [review]\n  when: \"$review.output.decision == 'approve'\"\n  command: implement\n```\n\nPause only for an interactive session or a super load-bearing action. The run pauses\nin the attention queue until answered, however it was started. To ask after each pass\nof a repeated body, end a `loop_group` body in the wait (it must be the body's sole\nterminal sink) and read the answer next iteration as `$LOOP_PREV.<wait-id>.output.text`.\n\n`labels` mean nothing to the engine; they let the user's handler workflows pick the\npause out of `archon workflow attention --label k=v`.\n\n## Wiring nodes together\n\n**`depends_on`** creates edges; independent nodes in one topological layer run\nconcurrently.\n\n**Data flow** — declared values carry scalars, artifacts carry documents:\n\n- `$nodeId.output` — whole text of an upstream node; unknown/skipped producer → `''`\n- `$nodeId.output.field` — strict JSON access: missing key **fails the consumer**\n  (never silent-empty); declared-optional field resolves to `''`\n- `with:` bindings (#2637) — prompt/command files read bound values as\n  `$INPUTS.<name>`; bash/script processes receive `INPUTS_<UPPER_SNAKE>` env vars.\n  `{from, if_skipped}` discriminates skipped producers from ran ones; a failed\n  producer fails the binding\n- `$INPUTS.<name>` — declared workflow inputs, also delivered as\n  `INPUTS_<UPPER_SNAKE>` to bash/script\n- `$ARTIFACTS_DIR` — pre-created directory for reports/evidence humans read.\n  Throwaway checkouts and dependency installs go under `$ARTIFACTS_DIR/.archon/scratch/`\n  (create it with `mkdir -p`), which Archon deletes once the run ends and its nodes have\n  stopped (a container run's when its container is removed); the rest of\n  `$ARTIFACTS_DIR` is kept\n- In `bash:` and `script:` sources, user-controlled variables (`$ARGUMENTS`,\n  `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`,\n  `$CONTEXT`) are **not** spliced as raw text (injection guard); they arrive as\n  environment variables (`ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`,\n  `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT`, `EXTERNAL_CONTEXT`,\n  `ISSUE_CONTEXT`). Read an environment-delivered value in `bash:` as `\"$ARGUMENTS\"`.\n  In `script:` read it via `process.env.ARGUMENTS` (bun) or `os.environ['ARGUMENTS']`\n  (uv/python).\n  A `loop_group` body's `bash:` node is the exception: the loop scope splices the\n  reply (`$LOOP_USER_INPUT`) into its source, shell-quoted, so the reply arrives\n  already quoted. Read the spliced reply as an already-quoted value — assign it\n  and quote the variable (`value=$LOOP_USER_INPUT; printf '%s' \"$value\"`) — rather\n  than as `\"$LOOP_USER_INPUT\"`.\n  `$nodeId.output` refs ARE substituted; give prose producers an `output_format`\n  before assigning their output directly in a script.\n\n**`when:` conditions** skip a node when false (skipped propagates to dependants):\n\n```yaml\nwhen: \"$classify.output.type == 'BUG'\"\nwhen: \"$score.output.confidence >= '0.9' && $gate.output.proceed == true\"\n```\n\nOperators: `==`, `!=`, `<`, `>`, `<=`, `>=` (numbers auto-parse, fail-closed on\nnon-numeric), compound with `&&`/`||` (`&&` binds tighter, no parentheses).\nMalformed expression → node skipped + warning. Dot-access contract violation →\nnode FAILS loudly. Guard optional paths with `when:` or `trigger_rule`.\n\n**`trigger_rule`** — join semantics over dependencies:\n\n| Value | Behavior |\n|-------|----------|\n| `all_success` | all deps succeeded (default) |\n| `one_success` | at least one dep succeeded |\n| `none_failed_min_one_success` | none failed AND ≥1 succeeded (skips OK) |\n| `all_done` | all deps terminal (completed, failed, OR skipped) |\n\n## Fixtures — prove wiring without AI spend\n\nEvery authored workflow ships dry-run fixtures in\n`<workflow-folder>/fixtures/<name>.stubs.yaml`:\n\n```yaml\n# Declared expectations + node stubs. Every non-reserved key is a node output.\nfixture:\n  expect: completed          # or failed / paused / cancelled\n  fail-node: assert-changed  # required when expect: failed — must match exactly\n  inputs:\n    branch: task-42\nimplement:\n  done: true\n  green: true\n  summary: \"stub summary\"\nexec-code: false             # true executes bash/script nodes for real (requires a git checkout)\n```\n\nRun them:\n\n```bash\narchon workflow test                    # everything discovered\narchon workflow test my-workflow        # one workflow's fixtures\n```\n\nA reachable attention wait pauses the simulation unless the fixture stubs it; the stub\nstands in for the answer and must match the form's output, e.g.\n`review: { decision: approve, text: \"\" }`. Deprecated `approval:` gates auto-approve.\n\nExpected-red fixtures are the important ones: a guard that has never been seen\nfailing proves nothing. Prove reds red before trusting them. `workflow test`\nmirrors what a real run does with its two directories: named scripts and command\nfiles come from one frozen capture of your working tree taken per invocation —\nuncommitted edits included, so authoring works as normal — while exec-code nodes\nexecute against a scratch worktree of HEAD. So a script that reaches outside\n`.archon/` with a `__file__`-relative path fails the fixture exactly as it would\nfail the run, and nothing a node executes can touch your checkout. Exec-code\nfixtures require a git checkout and fail explicitly outside one. Validate\nload-time errors with `archon workflow list`.\n","isInternal":false,"tokens":4584,"sizeBytes":18942},{"name":"prompting-mistakes.md","path":".claude/skills/archon-cli/authoring/prompting-mistakes.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/authoring/prompting-mistakes.md","title":"archon-cli-prompting-mistakes","category":"anthropic-skill","format":"markdown","content":"---\nname: archon-cli-prompting-mistakes\ndescription: Common prompt mistakes when invoking Archon workflows or writing node prompts. Short, negative-form guidance. Read before dispatching work or authoring prompts.\n---\n\n# Prompting Basics\n\n## General\n\n1. Agents are smart — take advantage of that. Each command node runs a real\n   coding-agent harness under the hood, with all its capabilities.\n2. Agents inside Archon do not necessarily know about Archon itself, this run,\n   or past failures. Do not reference context the agent has no awareness of.\n\n# Prompting Mistakes\n\nShort list. Applies in two places: the **message you pass when invoking a\nworkflow** (it becomes `$ARGUMENTS` for the whole run) and the **prompts inside\ncommand/prompt nodes** you author.\n\n## The mistakes\n\n1. **Vague outcome, implied method.** \"Look into the auth thing.\" Say what done\n   looks like and where context lives: \"Fix issue #42. Use the issue, comments,\n   and current source as evidence. Preserve the documented login behavior.\"\n2. **Smuggling your diagnosis.** If you want the run's investigation tested,\n   ask for the fix in ordinary language — do not hand it your suspected root\n   cause as fact unless you have proven it.\n3. **Narrating to nobody.** Unattended nodes produce artifacts, commits, and\n   declared fields — not chat. Prompts that say \"report to me below\" produce\n   output nothing ever reads. Point every instruction at the artifact it serves.\n4. **No evidence bar.** A judging prompt without \"cite the causal line / drop\n   anything that rests on 'might'\" collects plausible filler. State what counts\n   as proof, and demand silence otherwise.\n5. **Trusting exit codes as verdicts.** A declined task exits 0. Gates read\n   artifacts and declared fields, not success bits.\n6. **Prose wire formats.** Asking a node to end with a magic token the next node\n   greps. Use structured output fields or script exit codes instead.\n7. **Everything-but-the-kitchen-sink scope.** One node, one job, stated non-goals.\n   \"Also update docs and tests and the changelog\" dilutes all three. Negative\n   scope (\"do not touch the lockfile\") is as load-bearing as positive.\n8. **Assuming memory.** Each node may be a fresh session with no prior context.\n   Restate the input path, artifact path, and constraint inside each prompt.\n9. **Stop rules left unstated.** Tell the agent when to stop and surface:\n   ambiguity → present options; missing context → name it. Never \"use judgment\"\n   on decisions with real cost.\n10. **Soft failure language.** \"Try to...\" invites theater. Put checkable failure\n    in a `bash:`/`script:` node whose exit code fails the run, or have a judging\n    node return a structured boolean and gate on it deterministically.\n\n## The one-line test\n\nBefore sending any prompt — invocation or node — ask: *could a competent engineer\nwho has never seen this conversation act on exactly this text?* If the answer\nneeds context that lives only in your head, the prompt is not done.\n","frontmatter":{"name":"archon-cli-prompting-mistakes","description":"Common prompt mistakes when invoking Archon workflows or writing node prompts. Short, negative-form guidance. Read before dispatching work or authoring prompts."},"isInternal":false,"tokens":691,"sizeBytes":3002},{"name":"variables.md","path":".claude/skills/archon-cli/authoring/variables.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/authoring/variables.md","title":"Authoring Skill","category":"anthropic-skill","format":"markdown","content":"# Variable Substitution Reference\n\nVariables are placeholders in command files and workflow prompts that get replaced at execution time.\n\n## Variable Table\n\n| Variable | Scope | Description |\n|----------|-------|-------------|\n| `$ARGUMENTS` | All modes | The user's original message passed to the workflow |\n| `$USER_MESSAGE` | All modes | Same as `$ARGUMENTS` — both resolve to the user's message |\n| `$WORKFLOW_ID` | All modes | Unique workflow run ID (for tracking and logging) |\n| `$ARTIFACTS_DIR` | All modes | Pre-created directory for this workflow run's artifacts. Write outputs here. Scratch work (a throwaway checkout, a dependency install) goes under `$ARTIFACTS_DIR/.archon/scratch/`, which Archon deletes once the run ends and its nodes have stopped (a container run's when its container is removed) |\n| `$BASE_BRANCH` | All modes | The run's resolved or recorded base branch. Throws if referenced but unresolved. See the [Variable Reference](https://archon.diy/reference/variables/#workflow-variables) for resolution and child inheritance rules |\n| `$DOCS_DIR` | All modes | Documentation directory (config `docs.path`, default `docs/`). Never throws |\n| `$CONTEXT` | All modes | GitHub issue/PR context (if available from platform). Empty string if unavailable |\n| `$EXTERNAL_CONTEXT` | All modes | Alias for `$CONTEXT` |\n| `$ISSUE_CONTEXT` | All modes | Alias for `$CONTEXT` |\n| `$LOOP_USER_INPUT` | Loop / loop_group prompts | **Deprecated** with interactive loop gates; new loops end a `loop_group` body in a `wait: attention` node and read `$LOOP_PREV.<wait-id>.output.text`. Feedback from `/workflow approve <id> <text>` at an interactive loop gate. Populated ONLY on the first iteration after a resume; empty string everywhere else |\n| `$LOOP_PREV_OUTPUT` | Loop prompts | Previous iteration's cleaned output (completion tags stripped). Empty on iteration 1. Key tool for `fresh_context: true` loops that need to know what the last pass did |\n| `$LOOP_PREV.<nodeId>.output[.field]` | loop_group body prompts and `when:` conditions | Previous iteration's output of a specific body node. Empty on iteration 1. Field access follows the strict contract below (except genuinely-absent prior output → `''`). In a body `when:`, it is a typed condition reference rather than text substitution |\n| `$REJECTION_REASON` | Legacy `approval.on_reject` prompts | **Deprecated.** The reason from `/workflow reject <id> <reason>`. Empty string everywhere else. New workflows read an attention wait's `$<wait-id>.output.text` from a node behind `when: \"$<wait-id>.output.decision == 'reject'\"` |\n| `$nodeId.output` | DAG only | Full text output of a completed upstream node. Unknown/skipped producer → `''` |\n| `$nodeId.output.field` | DAG only | JSON field access — **strict**: an unresolvable field FAILS the consuming node (see below) |\n\n## Where Variables Are Substituted\n\n- **Command files** (`.archon/commands/*.md`) — the core set (`$ARGUMENTS`/`$USER_MESSAGE`, `$WORKFLOW_ID`, `$ARTIFACTS_DIR`, `$BASE_BRANCH`, `$DOCS_DIR`, `$CONTEXT` family), plus `$nodeId.output[.field]` when the command runs as a DAG node. A command file referenced by `loop.command` or a `loop_group` body receives the same populated loop variables as an inline loop prompt. Ordinary DAG command nodes receive `''` for loop/rejection variables. `$REJECTION_REASON` is populated only in an `approval.on_reject` prompt\n- **Inline `prompt:` fields** — in DAG prompt nodes, loop prompts, and loop_group body prompts\n- **`bash:` scripts** — SPECIAL: user-controlled variables (`$ARGUMENTS`, `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`, `$CONTEXT`) are **NOT spliced as raw text** into the script (shell-injection guard). A `loop_group` body's `bash:` node is the exception: the loop scope splices the reply (`$LOOP_USER_INPUT`) into its source, shell-quoted, so the reply arrives already quoted. Read the spliced reply as an already-quoted value — assign it and quote the variable (`value=$LOOP_USER_INPUT; printf '%s' \"$value\"`) — rather than as `\"$LOOP_USER_INPUT\"`. In `bash:`, they arrive as **environment variables** instead: `ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`, `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT` (+ `EXTERNAL_CONTEXT`/`ISSUE_CONTEXT`), plus `ARTIFACTS_DIR`, `LOG_DIR`, `BASE_BRANCH` — read an environment-delivered value with `\"$ARGUMENTS\"` as normal shell env access. `$nodeId.output` refs ARE substituted: small string values are single-quoted inline; values exceeding 32,768 UTF-16 code units (JavaScript `string.length`) spill to a file and substitute as unquoted `$(cat '<path>')`, which drops trailing newlines; the command substitution splits words and expands globs when used as a bare argument. Assign first (`value=$nodeId.output`), then quote the shell variable (`printf '%s' \"$value\"`) to handle both forms. See [Shell quoting guidance](https://archon.diy/reference/variables/#shell-quoting-in-bash-vs-script).\n- **`script:` bodies** — like `bash:`, user-controlled variables (`$ARGUMENTS`, `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`, `$CONTEXT`) are **NOT spliced as raw text** into the source (injection guard, #2115). They arrive as **environment variables** — `ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`, `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT` (+ `EXTERNAL_CONTEXT`/`ISSUE_CONTEXT`), plus `ARTIFACTS_DIR`, `LOG_DIR`, `BASE_BRANCH` and managed per-project env — read via `process.env.ARGUMENTS` (bun) / `os.environ['ARGUMENTS']` (uv/python) — env vars are the general injection-safe channel for untrusted values. A literal `$ARGUMENTS`/`$USER_MESSAGE`/`$CONTEXT` left in the source is not substituted: the text stays in the source. `$nodeId.output` values are still substituted **raw** (not shell-quoted). Direct assignment (`const data = $nodeId.output;`) is safe **only with `runtime: bun` when the producer uses `output_format`**. JSON booleans and nulls are not Python literals, so a `runtime: uv` script should bind the value with `with: { data: \"$nodeId.output\" }` and parse `os.environ['INPUTS_DATA']` with `json.loads`. For arbitrary-text producers (bash/script stdout or prose), use the same environment binding instead of embedding the raw value in source, then parse defensively only if the text is expected to contain JSON. **Avoid wrapping `$nodeId.output` in a `String.raw` template literal** — it silently breaks when the output contains a backtick (common in AI-generated markdown and `output_format` payloads)\n\n## Substitution Order\n\nA template is substituted in one pass. Each reference written in the template resolves once, and the value is inserted exactly as written: a message, issue context, loop reply, rejection reason, previous output, or input value that contains `$nodeId.output`, `$INPUTS.<name>`, a variable name, or `$&` reaches the node unchanged. Where two grammars match at the same `$`, the earlier group wins:\n\n1. Loop references inside a `loop_group` body (`$LOOP_PREV.<nodeId>.output`, the group's `$LOOP_USER_INPUT`)\n2. Workflow variables (`$WORKFLOW_ID`, `$ARGUMENTS`, `$ARTIFACTS_DIR`, `$BASE_BRANCH`, `$DOCS_DIR`, `$INPUTS.<name>`, `$CONTEXT`, loop/rejection vars)\n3. Node output references (`$nodeId.output`, `$nodeId.output.field`) — DAG mode only\n\n## Context Auto-Append\n\nIf `$CONTEXT` / `$EXTERNAL_CONTEXT` / `$ISSUE_CONTEXT` is NOT present anywhere in the prompt template but context exists (e.g., from a GitHub issue), it is automatically appended at the end after a `---` separator.\n\n## NOT Supported: `$1` … `$9` Positional Arguments\n\nDespite older docs suggesting otherwise, positional `$1`…`$9` are **not substituted** by the workflow engine — command files and prompts receive the whole message only, via `$ARGUMENTS`/`$USER_MESSAGE`. (A legacy positional-substitution helper exists in the codebase but is not wired into the execution path.) Parse arguments inside the prompt or with a `bash:`/`script:` node instead.\n\n## Node Output Details (DAG Only)\n\n`$nodeId.output` resolves to the full text output of the upstream node. If the node used `output_format:` (structured output), the output is the JSON-stringified validated result. Bash/script output without `output_format` is stdout with the trailing newline trimmed; with it, stdout is certified as JSON under the same result contract (see `node-reference.md` → Result contracts). Loop/loop_group output is the final iteration's output with completion-signal tags stripped. An attention wait outputs its answer by form: `{text}` for `message`, `{decision, text}` for `approval` and `select`, `{decisions, text}` for `multi_select`; read fields as `$ask.output.decision` and `$ask.output.text`. A deprecated gate with authored `approval.decisions` also outputs JSON `{decision, text}`. A deprecated gate without authored decisions keeps the old behavior: its approval comment is output only when `capture_response: true`, otherwise `''`. Unknown or skipped producers resolve to an empty string (with a warning logged).\n\n`$nodeId.output.field` and nested paths such as `$nodeId.output.proposal.action` are **strict** (no-silent-drop) — they either resolve or **fail the consuming node**. Paths are object field names only: no array indexing, wildcards or expressions. The same paths work in templates, `when:` (which needs a scalar), whole-value `with:` bindings and `$LOOP_PREV.<id>.output.a.b`. Do not flatten a producer's schema to reach a nested field.\n\n- Producer has `output_format`: every segment must be a declared `properties` entry; a declared field or parent that is absent or null resolves to `''` (declared-optional). An undeclared segment, at any depth, fails `archon validate workflows` when the producer's schema is known at load, and fails the consumer otherwise (typo protection).\n- Schemaless producer (no `output_format` on the producing node): the output must be a JSON object containing every key on the path, with an object at each intermediate step — anything else (non-JSON output, missing key, a scalar or array mid-path) fails the consumer.\n- Producer skipped or pending: fails the consumer — guard the reference with `when:` or a permissive `trigger_rule`.\n\nValues: strings pass through; numbers/booleans stringify; objects/arrays are JSON-stringified.\n\nFor `workflow:` results, these field rules use the child's `returns:` node's\nschema, whose declared paths travel with the value and survive cold resume. The\ncaller cannot add or narrow the contract. An `include:` alias uses its selected\nproducer directly after flattening.\n\n### Artifact pointers\n\nReturn a small JSON value containing a pointer when a machine consumer needs a\nfile result. The reserved shape is:\n\n```json\n{\"type\":\"archon_artifact\",\"run_id\":\"<producing run id>\",\"path\":\"review/report.md\"}\n```\n\nUse the actual `WORKFLOW_ID` value for `run_id`, not the literal variable name.\nThe producer must first write the file under its `$ARTIFACTS_DIR`. Sibling keys\non the pointer are allowed. Before persisting the result, the engine validates\ntagged pointers against the producing run: its own run id, a nonempty relative\npath with no `..` segment or NUL, lexical containment, and an existing regular\nfile. Absolute/escaping paths, directories, missing files, and another run's id\nfail the producer.\n\nA `workflow:` parent and fan-out aggregate relay pointers unchanged without\nrevalidating them against the parent. Events and resume retain the run id and\nrelative path. The engine does not expand the pointer into an absolute path,\nread its contents into a prompt, or provide an in-workflow resolver. For prompt\nhandoffs inside the producing run, keep using `$ARTIFACTS_DIR/<path>`.\n\nThe reader owns authorization, run reachability, and realpath containment at\nread time. Producer validation is not a read-side symlink guarantee; the artifact\nAPI's outstanding realpath check is tracked in #3160. Full semantics live in\nthe repository's `reference/variables.md` → Artifact pointers in a result.\n","isInternal":false,"tokens":2811,"sizeBytes":12048},{"name":"managing-runs.md","path":".claude/skills/archon-cli/operate/managing-runs.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/operate/managing-runs.md","title":"Operate Skill","category":"anthropic-skill","format":"markdown","content":"---\nname: archon-cli-manage-run\ndescription: Inspect, control, and resolve Archon workflow runs: list/get/status, approve, reject, respond, cancel vs abandon, resume. Read when a run is paused, failed, or the user asks about one.\n---\n\n# Managing runs\n\nEvery run-control verb, what it actually does, and the gate semantics that are\neasy to get wrong. Project listings scope by cwd; full run IDs remain globally\naddressable.\n\n## Output contract\n\n- `--json` → one clean JSON object on stdout; logs suppressed. Use when parsing.\n- No flag → human-readable text; diagnostics on stderr.\n\n## Overriding models for one run\n\nWhen the user asks to run on different models for a specific run, rebind the\ntier keywords or aliases at launch — the persistent config stays untouched:\n\n```bash\n# Rebind one or more tiers / @aliases for this run only; repeat per binding\narchon workflow run archon-ship --branch fix/x \"...\" \\\n  --model large=pi/minimax/minimax-m3 \\\n  --model small=openai/gpt-5-mini\n```\n\nSpec format: `<tier|@alias>=<provider>/<model-ref>` where `<model-ref>` is what\nthe named provider expects (for Pi backends that is itself a `<vendor>/<model>`\nref, e.g. `pi/minimax/minimax-m3`). An unknown tier/alias errors with the list\nof defined names.\n\nVerify before dispatching real work:\n\n```bash\n# dry-run prints each node's resolved provider/model without AI spend\narchon workflow run <workflow> \"test\" --dry-run --model large=...   # see 'runs on:' lines\n```\n\nFor reusable alternate setups, prefer a config layer instead of long flag lists\n(see `../setup/setup-and-config.md`, \"Alternate config layers\").\n\n## Verbs\n\n| Goal | Command |\n|---|---|\n| List recent runs (this project) | `archon workflow runs --json` |\n| Across all projects | `archon workflow runs --all --json` |\n| Filter by status / cap rows | `archon workflow runs --status running --limit 50 --json` |\n| One run's status/error | `archon workflow get <run-id> --json` |\n| One run with per-node detail | `archon workflow get <run-id> --verbose --json` |\n| One run with raw event rows | `archon workflow get <run-id> --verbose --events --json` |\n| List a run's artifact files | `archon workflow artifacts <run-id> --json` |\n| Read one artifact | `archon workflow artifacts <run-id> <path>` |\n| List registered projects (and their `--project` keys) | `archon project list --json` |\n| Active runs (this project) | `archon workflow status --json` |\n| Active runs (all projects) | `archon workflow status --all --json` |\n| Block until the run ends or needs attention | `archon workflow wait <run-id> --json` |\n| Every run that needs someone (this project / all) | `archon workflow attention --json [--all] [--label k=v]` |\n| The queue item one run stands for | `archon workflow attention <run-id> --json` |\n| Every change in the queue, as it happens | `archon workflow attention --watch --json [--all] [--label k=v] [--after <cursor>]` |\n| Classify an item without answering it | `archon workflow attention label <run-id> --occurrence <id> --set k=v --json` |\n| Resolve a gate with any declared decision | `archon workflow respond <run-id> <decision> [text]` |\n| Answer an attention wait in its form | `archon workflow answer <run-id> --occurrence <id> [--choose <id>]... [--text <t>]` |\n| Approve (default vocabulary) | `archon workflow approve <run-id> [text]` |\n| Reject (default vocabulary) | `archon workflow reject <run-id> \"<reason>\"` |\n| End a stalled turn on a running run, keep the run | `archon workflow end-turn <run-id>` |\n| Stop a running run (its live owner first) | `archon workflow cancel <run-id>` |\n| Mark paused/failed/orphaned run cancelled | `archon workflow abandon <run-id>` |\n| Resume failed/paused from completed nodes | `archon workflow resume <run-id>` |\n\n## Answering a decision well\n\nAn attention item is a question a run cannot settle. Most are written to be answered\nby whoever is on top of the runs, which is often you, not the user. Answer the ones\nthe user's standing direction already decides; bring the user only the ones that are\nunclear, risky, or outside what they have told you.\n\n1. Read the whole item: what is being decided, why it matters, the recommendation\n   with its evidence and what would change it, the choices and what each does, and\n   what happens if nobody answers. Open the evidence links before agreeing.\n2. Choose one of the declared choices. When none fits and the item offers `other`,\n   choose it and say what to do instead.\n3. Give a reason in the text. Workflows record the answer's text with the decision,\n   and some write it down as a ruling: the SDLC build queue and merge items owned by\n   a build add it to the issue body as a dated \"Current scope\" section that later\n   rounds work from. Enrolled own PRs keep the answer on the work item's merge\n   decision entry and write no issue section. A bare choice teaches\n   the next round nothing.\n4. Answer detached, so the continuation does not run in your shell:\n\n   ```bash\n   archon workflow respond <run-id> <choice> \"<reason>\" --detach\n   ```\n\n5. Pass `--occurrence <id>` when you answer from an item you read earlier: the\n   `occurrence` on its stream line, or `attention.occurrence` from `workflow wait\n   --json`. If the run has moved to a different pause, the answer is refused and the\n   error names the current occurrence, instead of landing on a question you never\n   read.\n\n## The approve/resume two-step\n\nWith `--json`, `approve`/`reject`/`respond` **record the decision and stop** —\nthis command does not execute the run (streaming output would corrupt the JSON).\nThe answered gate is a due continuation: a running `archon serve`, or\n`archon workflow wake [--watch]`, continues it without anyone resuming by hand.\nContainer runs (including a container write-back gate) are the exception: the\nhosts cannot continue them, so run `archon workflow resume <run-id>` yourself.\nTo record AND continue in this process:\n\n```bash\narchon workflow approve <run-id> \"ship it\"   # no --json: records + auto-resumes; background task!\n```\n\nA `wait: attention` pause (`attention.kind: \"attention\"`) is answered in the `form` it\nreports, with one of the `options` ids it lists:\n\n```bash\narchon workflow answer <run-id> --occurrence <id> --text \"done\" --json            # message\narchon workflow answer <run-id> --occurrence <id> --choose use-dev --json         # approval/select\narchon workflow answer <run-id> --occurrence <id> --choose a --choose b --json    # multi_select\n```\n\n`approve`/`reject`/`respond <option>` are shorthand for the same answer. In the\n`approval` form `reject` is an answer and does not cancel the run. Like a gate answer,\nthe answered wait is a due continuation. Answer only what the user actually decided; an\nundeclared option is refused. A `legacy:` occurrence is a wait paused before answers\nexisted: it takes no answer, so `workflow resume` it once its action is done.\n\nTo work the whole queue instead of one run, `archon workflow attention --json` lists\nevery item that needs someone, each with its `kind`, `occurrence`, `form`, `options`,\n`labels`, typed `project` and pointers to its transcript and artifacts. Answer an item\nby its `runId` and `occurrence` exactly as above. Labels are the user's routing data:\n`--label ci=other` selects one class, and `attention label` writes a classification\nwithout answering. Archon never routes by them.\n\nAn item of kind `usage_limit` asks nothing. Its run hit a provider usage limit that\nreported a reset time, and waits until then. A node that failed only downstream of the\nlimited one, such as an `all_done` node reading its output, does not turn the wait into a\nfailure. At the reset, a continuation host (`archon serve`, `archon workflow wake`, or\nthe CLI process that started the run) runs the failed nodes again. Cancel or abandon the\nrun to stop it.\n\nA workflow can ask several independent questions at once by fanning out one child run\nper question. Each child is its own item, with its own `runId` and `occurrence`, and\nits `fanOut.parentRunId` names the parent. `attention <parent-run-id>` returns only the\nfirst open child, so list the queue to find them all, and answer each child by its own\n`runId`. Answers can arrive in any order; the parent stays paused until every child\nfinishes.\n\n`workflow wait` follows one run whose id you hold. To follow every run in scope,\nincluding runs started later by triggers, schedules or other sessions, run\n`archon workflow attention --watch --json --all` as a background task. It prints one\nJSON line per change: each `attention` line is an item that needs attention, carrying\nthe same item the list returns, so treat it as a wake and answer by its `runId` and\n`occurrence`. `resolved` means it no longer does, and `terminal` means a run ended. The\nlines before `ready` are the whole open set. After a restart, `--after <cursor>`\nreplays what came after that cursor. A cursor names a feed event, not a line, and one\nevent can emit two lines with the same cursor (a `resolved` and the `attention` that\nreplaces it, or a `resolved` and a `terminal`). `--after` skips the whole event, so\ncheckpoint on a `bookmark` line you handled, or on the cursor before an event you\nhandled only partly, and accept duplicate lines on replay. Exit `4` means the store\ncannot read the stream's cursor: rerun without `--after`.\n\nOr deliberately in two steps:\n\n```bash\narchon workflow approve <run-id> \"ship it\" --json   # recorded, resumable: true\narchon workflow resume <run-id>                     # executes; background task\n```\n\nTo let the continuation own a background process, add `--detach` to\n`approve`/`reject`/`respond`/`resume`. The parent validates the run and returns an\nack with `continues: true`; the detached child records the decision and continues\nwithout `--json`. Detached `resume` acknowledges after engine acceptance clears the\nold pause, so immediately follow it with `workflow wait`. A new pause is still\nreported. If acceptance is unconfirmed after 60 seconds, inspect the run before\nretrying; the child may still continue.\n\n## Deprecated interactive-loop gates: comment or no comment is a decision\n\nRead the gate state first:\n\n```bash\narchon workflow get <run-id> --json | jq .metadata.approval.completionSignaled\n```\n\n- `true` and you approve **without** a comment → accept & complete: the node\n  finalizes from its already-computed output, no re-run.\n- You approve **with** a comment → another full iteration runs using your text\n  as feedback.\n- Reject always needs a reason. Gates with authored `decisions:` expose it as\n  `$gate.output.text`; only deprecated `approval.on_reject` prompts receive\n  `$REJECTION_REASON`. At an attention wait, reject is an answer\n  (`decision: reject`) and the run continues.\n\nChoose deliberately after reading what the gate produced — not by reflex.\n\n## Cancel vs abandon\n\n- `cancel` stops a running run. When another process owns it, that process is\n  stopped and its tree confirmed gone before `cancelled` is recorded. A run the\n  cancelling server executes itself, and a `workflow:` sub-run with no owner of\n  its own (it runs inside its root), is marked `cancelled` and stops at its\n  executor's next status check. Otherwise it refuses and changes nothing when no\n  owner answers. Use this to kill real work. Cancel still ends the run, and prints a\n  warning (`cleanupWarnings` in `--json`), when a process outside the run's process\n  group still holds a node's output, and on macOS and Linux when a host `bash:` or\n  `script:` command started or completed, because it may have left background work\n  running. The warning confirms neither a survivor nor cleanup, and Archon does not\n  signal or name such a process. Find it with `lsof` on the run's checkout, check whose\n  it is, and stop it before you reuse or remove the checkout.\n- `abandon` is for paused or failed runs, or a \"running\" row whose owner is gone\n  (crashed host). If an owner still answers on this host, it stops it first, like\n  `cancel`. If none answers, it records `cancelled` and prints the host and pid the\n  run recorded; when that host is another machine, check there before abandoning.\n  It also removes the worktree the run created, uncommitted files included, and keeps\n  the branch. Commit or copy anything you need from the worktree first. When stopping\n  the owner produced one of cancel's warnings, abandon keeps the checkout for you to\n  inspect instead.\n\n## A stalled turn: end-turn, cancel or resume\n\nAn agent or loop node can sit open on a provider tool call or reported background\ncommand that never finishes, such as a `while true; do ...; sleep 30; done` poll. The\nidle watchdog does not fire while that work is open. After\n`workflows.toolCallAttentionMs` without progress (30 minutes by default), the run raises\n`stalled_tool_calls` attention. The run stays `running`, so it is not an attention-queue\nitem and the `--watch` stream does not report it. `workflow wait <run-id>`\nreturns it as `attention.kind: \"stalled_tool_calls\"`, and `workflow status` prints it\nunder the run.\n\n- **`archon workflow end-turn <run-id>`** when the stalled work is done or useless and\n  the rest of the run is worth keeping. It records a request; the live owner checks\n  about every ten seconds and ends that turn if the same stall is still there. The\n  attempt fails as `timeout`, and the node's retry policy decides what happens next.\n  By default that is a fresh attempt, which can stall the same way. Exit `0` means the\n  request was recorded, not acted on: follow with `workflow wait <run-id>`. It refuses\n  a run that is not `running` or has nothing stalled.\n- **`cancel`** when the whole run should stop.\n- **`resume`** refuses a running run. Use it once the run has failed or paused, for\n  example after an ended turn exhausts its retries.\n\nA silent turn with no open tool or background work is a different case: its node's\n`idle_timeout` fails it as before, with no request needed.\n\n## Respond: gates beyond approve/reject\n\nSome gates declare decisions beyond the default pair (`respond <run-id> <decision>\n[text]`). Read the run's metadata to see which decisions its gates declare before\nguessing one.\n\n## Judging a finished run\n\nTerminal ≠ good. The three JSON modes expose different data:\n\n| Command | Read |\n|---|---|\n| `get <id> --json` | top-level normalized `outcome` (`succeeded`/`failed`) and `leave_behind.artifactFiles` |\n| `get <id> --verbose --json` | ordered `nodes` summaries and parse warnings |\n| `get <id> --verbose --events --json` | raw `events` rows |\n\n`outcome_field` selects a boolean from the return node, but the run stores only\nthe normalized top-level outcome. It does not expose the authored field name\n(`green`, `ready`, or `rooted`) as another top-level property. Locate the report\nthrough `leave_behind.artifactFiles`, then read the artifact before reporting to\nthe user.\n\nA completed `workflow:` fan-out node is not completed child work: under the default\n`join: all_done` it completes once every child is terminal, whatever each child's\nresult. When some slots did not complete, `workflow get <run-id>` prints a\n`Fan-out work:` line with the tally and those slots' outcomes. The full per-slot record (`completed`, `failed`,\n`not_started`, or a `cancelled_*` outcome naming who cancelled it) is\n`terminal_record.nodes[].fan_out_outcome` with `--json`.\n\nReport outcomes with their evidence: \"completed, review verdict ready:false —\n2 findings remain, report at <path>\" beats \"done\".\n\n## Discoveries: the SDLC pack's out-of-scope findings\n\nA convention of the bundled SDLC workflows (`archon-ship`, `archon-deliver`,\n`archon-review`, `archon-upkeep`), not of workflows in general. Their review lenses\nrecord proven findings that fall outside the run's accepted scope as discovery records\ninstead of blocking findings: raw per-producer files at\n`<artifacts>/discoveries/*.json` (records of `title`, `claim`, `evidence`, `relation:\nunrelated | scope_conflict | note`, `source_node`), consolidated by the review into\nthe verdict's `discoveries` records and a readable `discoveries.md`.\n\nThe run that found them files nothing. Its terminal report lists them, and\n`archon-discoveries` later decides every recorded discovery across finished runs:\nfile an issue with a full brief, track it on the open issue that already covers it,\ndrop it with a reason, or ask a maintainer. Run that drain rather than filing\ndiscoveries by hand (see `queues.md`, \"Discoveries drain\").\n\nWhen the user asks about one run's discoveries before a drain has run:\n\n1. Read the report's Discoveries section and, when it names any, open\n   `discoveries.md` (find it through `leave_behind.artifactFiles`).\n2. On a failed run, also check the raw `discoveries/*.json` records: consolidation\n   runs late, so a run that died mid-flight can hold records no report mentions.\n3. Tell the user what the run found. Do not re-litigate the verdict from them:\n   `unrelated` and `note` records never affected the run's readiness.\n\n## When a run looks wrong\n\n- Paused unexpectedly → `get --verbose`; look at the failing node's error and the\n  gate metadata.\n- Failed mid-flight → fix the cause if you can name it, then `resume <run-id>`\n  (background task). Resume skips completed nodes.\n- A \"running\" row with nothing alive → verify the process is truly gone, then\n  `abandon`.\n- Logs live under `~/.archon/workspaces/<project>/logs/`; artifacts under\n  `.../artifacts/runs/<run-id>/`. `archon doctor` checks the installation itself.\n","isInternal":false,"tokens":4269,"sizeBytes":17462},{"name":"queues.md","path":".claude/skills/archon-cli/operate/queues.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/operate/queues.md","title":"Operate Skill","category":"anthropic-skill","format":"markdown","content":"---\nname: archon-cli-queues\ndescription: Drive the SDLC pack's queues by hand: intake, the build queue, the merge queue and the discoveries drain. Their inputs, what each run does, the decisions they raise and how to answer them. Read when launching or tending any of them.\n---\n\n# Driving the SDLC queues\n\nThe bundled SDLC pack turns issues into merged pull requests through four queue\nworkflows. Each run does one step and stops only for a decision it cannot make:\n\n| Workflow | One run |\n|---|---|\n| `archon-intake` | Records an issue event and triages the best waiting issue. The verdict becomes the item's stage and a route label. |\n| `archon-build-queue` | Claims the best waiting triaged item, or the one `number` names, and takes it to a pull request whose review says Ready, one round per run. |\n| `archon-merge-queue` | Claims the items the build queue finished and merges what policy allows, one at a time, through a gate. |\n| `archon-discoveries` | Decides the out-of-scope findings finished runs recorded: file, track, drop or ask. |\n\nThe pack's README (`.archon/workflows/sdlc/README.md`) is the full contract. This\nreference covers launching and tending the queues by hand.\n\n## Before the first run\n\n- `archon workflow run archon-forge-check` must pass in the checkout. It proves the\n  pack can read the repository through its forge source (`gh` by default).\n- `.archon/sdlc/policy/maintainers.json` on the default branch lists the forge logins\n  whose issues are triaged and whose review comments count. An entry can be an object,\n  `{ \"login\": \"<login>\", \"merge_queue\": \"own-prs\" }`, which also sends that\n  maintainer's own open pull requests through the merge queue, except one a work item\n  already names, such as a pull request the build queue delivered. Without the file\n  every author is an outsider: intake labels their issues `archon-needs-maintainer`\n  and runs no agent.\n- The forge account the pack's review runs post as must also be on\n  `maintainers.json`. The merge queue counts only a review comment a listed\n  maintainer wrote; with no trusted review, no verdict is Ready and nothing merges.\n- The merge gate requires green CI at the pull request's head. A repository with no\n  CI checks reads as `none`, not green, so nothing merges there.\n- Policy documents at `.archon/sdlc/policy/<topic>.md` add to the pack's defaults\n  rather than replacing them. Where a line in the project's file conflicts with the\n  default, the project's line wins.\n- The pack installs no trigger bindings. Nothing runs on its own until the operator\n  adds one. The README shows a binding per queue; until then, launch by hand.\n\n## Inputs\n\nEvery queue takes the repository as two inputs: `repo_host` (such as `github.com`)\nand `repo_path` (`<owner>/<repo>`). Launch from the repository's checkout, detached.\n\n```bash\nR=\"--input repo_host=github.com --input repo_path=<owner>/<repo>\"\n\narchon workflow run archon-intake $R --input number=<issue> --detach\narchon workflow run archon-build-queue $R --detach\narchon workflow run archon-merge-queue $R --detach\narchon workflow run archon-discoveries $R --detach\n```\n\nOptional inputs, with their defaults in each workflow's YAML:\n\n- `archon-intake`: `action` (`opened` by default; `edited`, `reopened`, or `added`\n  for a label), `label` and `label_actor` for a label event.\n- `archon-build-queue`: `number` (the issue to build; empty claims the first in\n  order), `retriage_as` (a maintainer login; set, a run that names no item also\n  re-triages blocked items that no longer look blocked) and `round_limit` (default\n  `3`), the rounds after which a run that wants another also judges whether the rounds\n  converge.\n- `archon-merge-queue`: `limit` (default `10` items claimed), `merge_method`\n  (`squash` or `merge`) and `retriage_as` (a maintainer login; set, an issue still\n  open after its pull request merged goes back to intake).\n- `archon-discoveries`: `since` and `until` (ISO-8601) and `limit` (default `20` runs\n  per pass).\n\nIntake, the merge queue and the discoveries drain never change their checkout. They\ncreate no worktree and run in the launch checkout as it stands, so launch them from a\ncheckout that tracks the default branch. The build queue changes its checkout, so each\nrun gets its own worktree on a run-named branch. A completed round removes that\nworktree when it is clean and every commit is on a remote, and keeps the branch;\notherwise the run names the path and what removing it would lose.\n\n## Intake\n\nRun intake once per issue event. For a new issue, `--input number=<issue>` is enough.\n\nAn edit or a plain rerun does not re-triage a maintainer's issue that was already\ntriaged. To re-triage one, add the `archon-triage` label on the forge and launch the\nlabel event, naming who added it:\n\n```bash\narchon workflow run archon-intake $R --input number=<issue> \\\n  --input action=added --input label=archon-triage --input label_actor=<maintainer-login> --detach\n```\n\n`label_actor` must be on `maintainers.json`. The same event with a listed maintainer\npromotes an outside author's issue.\n\nRead the result from the issue's labels:\n\n- `archon-ready`, `archon-plan` or `archon-investigate`: routed to the build queue.\n- `archon-needs-contract`: the body misses one of the six brief elements. Fix the\n  body, then re-triage.\n- `archon-blocked`: a prerequisite or a decision must land first. Re-triage once it\n  has.\n- `archon-close`: a duplicate or already delivered. Intake closes it when the triage\n  policy allows.\n- `archon-needs-maintainer`: an outside author's issue, waiting for promotion.\n\n## Build queue\n\nA run claims the waiting item that sorts first and works it for one round. To build\na chosen issue, name it:\n\n```bash\narchon workflow run archon-build-queue $R --input number=<issue> --detach\n```\n\nA named issue that cannot be claimed refuses the run before any agent, with the\nreason: it has no work item, another run holds it, or it does not wait for the build\nqueue. Launch one run per item you want worked at the same time; the launch's\n`--config` decides which provider builds it.\n\nA round ends one of three ways, recorded on the item:\n\n- `done`: the pull request's latest review says Ready at its head. The merge queue\n  takes it from there. A pull request already reviewed Ready at its head is marked\n  ready without another round.\n- `continue`: the issue already answers what stopped the round. The run starts the\n  item's next round at once, as a detached `archon-build-queue --input number=<n>`\n  run on the same run config and the same `retriage_as` and `round_limit`.\n  If that launch fails, the item waits for the next queue run.\n- `ask`: the run pauses as one attention item labelled `build: route`, `build: stop`\n  or `build: scope`.\n\nAn item the merge queue returns waits for a new build round, with the merge queue's\nbrief. A re-triage after the queue's last decision starts the item over.\n\nDo not resume a failed build-queue run once a later build-queue run has released its\nclaim. A run that names no item releases every finished run's claim, whichever item it\nthen picks. A run named for the same `number` releases it too; one named for a\ndifferent `number` leaves it alone. A resume skips the completed `facts` node that\nclaimed the item, so the resumed run works the whole round, then its `apply` refuses\nbecause the item is no longer claimed by it. Launch a fresh run with\n`--input number=<issue>` instead.\n\n## Merge queue\n\nA sweep claims every waiting item up to `limit` and merges what policy allows, one\nat a time, through a gate that requires a Ready verdict for the head, green CI there\nand a default branch whose CI is not red. A pull request that merges cleanly into the\ndefault branch merges, even when it is behind, so one sweep can land many. The queue\nnever pushes or waits for CI: a conflict goes back to the build queue, and the default\nbranch's CI on a merge is the next sweep's first fact. Claims keep two sweeps off the\nsame item.\n\nA verdict counts only at the head it reviewed: after a push, the pull request goes\nback to the build queue for a new round. A maintainer's own pull request\n(`merge_queue: \"own-prs\"`) is an own-PR work item with no build record to return to,\nso where another would go back, the sweep asks instead and offers no `return`.\n\nA pull request whose read fails twice in a row is parked with the read error until\nthe build queue readies the item again. A maintainer's own pull request has no build\nrecord, so nothing readies it again: once parked, it stays parked, and you merge it by\nhand.\n\nA sweep merges a clear pull request on its own. An unclear one, such as a very large\nchange, pauses as an attention item labelled `merge: pr` with the choices `merge`,\n`return`, `park` and `other`. A red default branch pauses as `merge: base` with the\none choice `hold`, and the gate refuses every merge until it is green. A run raises at\nmost one attention item: the red default branch first, otherwise the first pull\nrequest the sweep asked about. A later sweep decides the rest again.\n\nA `merge` answer approves the pull request at the head it asked about, and a sweep\nthe answer starts, on the answered run's `limit`, `merge_method` and\n`retriage_as`, merges it once the gate passes. The approval carries to a later head only when the sweep\njudges that everything since is merges of the default branch and their conflict\nresolutions, and the verdict is Ready at the later head. A `return` hands the item\nback to the build queue, whose next round starts at once on the build queue's own\ninput defaults. A `park` stops work on the item until the build queue readies it again.\n\nFor an own pull request, `park` and `other` stop queue work until a later sweep\nobserves a head different from the head asked about. At the same head it stays\nparked, even after green CI or a new review. The `other` text is recorded as the\nruling on the work item; it starts no build round.\n\n## Discoveries drain\n\nEach pass decides the records of up to `limit` finished runs, oldest first. Repeat\npasses until one finds nothing queued: its `decide` node is skipped\n(`archon workflow get <run-id> --verbose --json`). Stop early when a pass pauses with a\n`discoveries: question` item, and answer it first.\n\nIssues a pass files carry no labels. Run intake on each one to triage it.\n\n## Answering queue decisions\n\nQueue items follow one shape: what is being decided, why it matters, the\nrecommendation with its evidence and what would change it, the choices and what each\ndoes, and what happens if nobody answers. Answer them as `managing-runs.md`\n(\"Answering a decision well\") describes:\n\n```bash\narchon workflow respond <run-id> <choice> \"<reason>\" --detach\n```\n\nThe build queue and merge items owned by a build write the ruling into the issue\nbody as a dated \"Current scope\" section, and later rounds work from it. An enrolled\nown PR keeps the answer on its work item's merge decision entry; it writes no issue\nsection. Write the reason as a ruling a later round can act on: what is in scope,\nwhat is not, and why. An answer that\ncontinues an item (a build `continue`, a merge `merge` or `return`) starts the follow-up\nrun at once, on the run config the answered run started with, so you don't relaunch. A\nfollow-up in the same queue also keeps the answered run's input selections.\n\n- When the recommendation fits the user's standing direction, answer it yourself.\n- When it is unclear, risky or not decided by anything the user has said, bring it to\n  the user with the item's own summary.\n- `other` exists on every pull-request and build item. Use it when no declared choice\n  fits, and say exactly what to do instead.\n\n## Merging by hand\n\nA pull request the queues did not deliver has no work item, so the merge queue does\nnot take it, unless its author's `maintainers.json` entry sets\n`merge_queue: \"own-prs\"`. To merge one yourself:\n\n1. Read the review verdict and the open review threads, including automated\n   reviewers' inline comments.\n2. Merge at the head you read: `gh pr merge <n> --squash --match-head-commit <sha>`.\n   A push since your read makes the merge fail instead of landing unreviewed code.\n3. Update your checkout to the new default branch.\n","isInternal":false,"tokens":2994,"sizeBytes":12186},{"name":"running.md","path":".claude/skills/archon-cli/operate/running.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/operate/running.md","title":"Operate Skill","category":"anthropic-skill","format":"markdown","content":"# Running workflows\n\nHow to discover, select and launch Archon workflows against real work, and how to watch\nwhat you launched.\n\n## Discover what exists\n\n```bash\narchon workflow list                 # human-readable compact descriptions and errors\narchon workflow list --json          # compact descriptions + descriptionTruncated state\narchon workflow list <name> --full   # exact description for one candidate\narchon plugin add owner/repo     # install a workflow pack published on GitHub\n```\n\nThe live list is authoritative. Bundled workflows ship under the `archon-` prefix\n(e.g. `archon-ship`, `archon-review`, `archon-deliver`, `archon-investigate`,\n`archon-triage`, `archon-upkeep`); user-authored workflows may\nclaim any name and override a bundled one in that repo.\n\nIn JSON output, `descriptionTruncated: true` means `description` is a discovery preview,\nnot the complete routing contract. Human output marks the same state with `[truncated]`.\nUse the previews to identify plausible candidates, then run `archon workflow list <name>\n--full` for each candidate before choosing or launching. Map intent from those full\ndescriptions — never from memory of names you have seen elsewhere. If two names plausibly\nmatch, read both full descriptions, then say which you picked and why in one line.\n\n## The input is the contract\n\nEverything the run does is measured against its input — the message, the issue body, or the\ndocument it reads. Archon governs how the work happens; it cannot supply what the user\nactually wanted. This is the highest-leverage moment in the whole flow, and it is before any\nmoney is spent.\n\nCheck the input names all six. Solution steering is a seventh, optional, and last:\n\n1. **Problem to solve** — what is wrong today, concretely.\n2. **Why it is worth solving** — the cost of leaving it.\n3. **Why now** — what makes this the moment.\n4. **Desired outcome** — what is observably different afterwards.\n5. **Invariants** — what must stay true across any acceptable implementation.\n6. **Acceptance** — how completion is checked.\n\n**When something is missing, do not fix it silently and do not launch anyway.** Tell the user\nwhich of the six is absent, propose concrete wording, and let them decide. They own the\ncontract; your job is to notice it is thin before it costs them a run.\n\nA useful shape:\n\n> Before I launch: this brief states the problem and the outcome, but not the invariants or\n> acceptance. Without acceptance the run decides for itself when it is done. Suggest adding:\n> \"Acceptance: X passes, Y is covered by a test, Z is unchanged.\" Want me to run it with that,\n> or would you rather word it yourself?\n\nTwo failure modes worth naming, because they look like diligence:\n\n- **Naming a solution before the problem is settled.** It narrows the run to the first guess\n  and hides better answers. If the user supplied one, keep it, but make sure the problem is\n  stated too — a run that only knows the proposed fix cannot tell you it was the wrong fix.\n- **Dropping steering the user actually holds.** Optional does not mean unwanted. If they have\n  said anywhere in the conversation how they want this done — reuse a primitive, avoid a\n  dependency, migrate rather than rewrite — carry it into the input as explicit steering. An\n  unstated preference cannot be honoured, and the user discovers it only in the diff.\n- **Silence about uncertainty.** An assumption stated in the brief is something the run can\n  contradict. An assumption left out is one it will quietly inherit.\n\nIf the input is a GitHub issue, read the actual body before launching rather than trusting the\ntitle. Recommend edits to the issue itself when it is thin — the issue is the durable contract,\nand the next run against it inherits whatever you leave there.\n\n## Invocation\n\n```bash\n# Default: isolated worktree, detached, non-blocking\narchon workflow run <workflow> --branch <branch-name> \"<message>\" --detach\n\n# Start from a specific base instead of the current branch\narchon workflow run <workflow> --branch <name> --from <base> \"<message>\" --detach\n\n# Foreground (blocks the shell)\narchon workflow run <workflow> --branch <name> \"<message>\"\n\n# No isolation — only when the user explicitly asks to work on the checkout directly\narchon workflow run <workflow> --no-worktree \"<message>\"\n\n# First run for a registered non-git folder project (runs in place; no branch flags)\narchon workflow run <workflow> --folder \"<message>\"\n\n# From another clone of a registered repo: the run belongs to that project\narchon workflow run <workflow> --project <owner/repo> --branch <name> \"<message>\" --detach\n```\n\n`--project <id|name>` takes a name or ID from `archon project list`. The run uses the\nproject's identity, env, artifacts and logs, and never registers the clone you launch\nfrom. Workflow source and config come from the launch directory. A worktree is cut from\nthe registered checkout, so a branch that exists only in your clone is not there; add\n`--no-worktree` to execute in the launch directory instead. It cannot be combined with\n`--folder`, `--base-branch`, `--resume` or `--dry-run`.\n\nRules:\n\n1. **Detached is the default posture.** Workflows are long-running; `--detach`\n   returns immediately and the run appears in `archon workflow runs`. In your\n   harness, even foreground invocations belong in a background task.\n2. **For git projects, pass `--branch`** unless the user chose otherwise. Derive the name\n   from the domain of work — code: `fix/<topic-or-issue-N>`, `feat/<name>`,\n   `review/pr-N`; ops/process runs: e.g. `ops/<queue>-<item>`,\n   `upkeep/<dependency>`, `triage/<source>`. To run on\n   different models for one run, see `managing-runs.md` (\"Overriding models for one\n   run\").\n   A worktree created for `--branch` stays when the run ends; `archon isolation\n   cleanup --merged` removes it after its pull request merges. Without `--branch`, a\n   completed run removes the worktree it created on its own generated branch when the\n   checkout is clean and every commit is on a remote, keeping the branch. A kept\n   checkout is named in the run's output with what removing it would lose.\n   Folder projects run in place and reject `--branch`, `--from`, and `--base`.\n3. **One workflow per shell.** Multiple pieces of work = separate invocations with\n   separate branches; they cannot conflict because each gets its own worktree.\n4. **The message is the work order.** Write it as you would brief a competent\n   engineer: what outcome is wanted, where the context lives (issue number, plan\n   path), and any constraint the user actually stated. See\n   `../authoring/prompting-mistakes.md` before writing it.\n5. **Interactive workflows need a human at their gates.** Launch them in the\n   foreground. When they pause, read the rendered gate message and declared\n   decisions from `archon workflow get <run-id> --json` under\n   `metadata.approval`; assistant JSONL rows are AI transcripts, not the gate\n   contract. Resolve the gate per `managing-runs.md`. Continuation\n   actions on the paused run may use `--detach`.\n\n## Inputs\n\nA workflow that declares `inputs:` takes them as `--input <name>=<value>`, one flag\nper input. `archon workflow list <name> --full` shows the description; the workflow's\nYAML lists each input with its default and meaning. An input without a default is\nrequired, and an undeclared name is refused.\n\n```bash\narchon workflow run archon-intake \\\n  --input repo_host=github.com --input repo_path=<owner>/<repo> --input number=42 --detach\n```\n\nInputs and a message can be combined. Queue workflows such as the SDLC pack's take\ninputs only; `queues.md` lists them.\n\n## Monitoring\n\n```bash\narchon workflow attention --watch --json    # one stream: decisions and run ends, this project\narchon workflow attention --watch --json --all   # the same across the install\narchon workflow wait <run-id> --json        # block on one run until it ends or needs attention\narchon workflow runs --json                 # recent runs for this project\narchon workflow status --json               # active runs (running or paused) for this project\narchon workflow get <run-id> --json         # one run: status, outcome, artifacts\narchon workflow get <run-id> --verbose --json  # plus per-node detail\n```\n\nTerminal statuses: `completed`, `failed`, `cancelled`.\n\n**Watch with one stream.** Right after your first launch, run `archon workflow\nattention --watch --json` as a background task of your harness. It follows every run\nin scope, including runs started later by triggers, schedules or other sessions, and\nprints one JSON line per change:\n\n- `attention`: a run needs a decision. The line carries the whole item; answer it by\n  its `runId` and `occurrence` (see `managing-runs.md`).\n- `resolved`: the item no longer needs attention.\n- `terminal`: a run ended, with its `status` and `outcome`. Read its result.\n- `owner_lost`: a run says it is running but its process is gone.\n\nThe lines before `ready` are the current open set. Keep the `cursor` of the last line\nyou handled; after a restart, `--after <cursor>` replays what you missed.\n\n`workflow wait <run-id>` blocks on one run and suits one or two runs. Do not arm one\nwait per run for a batch: dozens of waiting processes contend for the database, and a\nsingle stream already wakes you per run. Never chain waits in one shell (`wait A &&\nwait B` sleeps on A while B pauses unattended).\n\n**Status is not verdict.** A `completed` run can carry a negative `outcome`. Read it\nwith `get --json`, find the report under `leave_behind.artifactFiles`, and use a\nseparate `get --verbose --json` call for node summaries. Read the report before\ntelling the user what the run concluded. For the bundled SDLC workflows the report\nmay list discoveries: findings outside the run's scope. The run records them but\nfiles nothing; `archon-discoveries` decides and files them (see `queues.md`).\n\n## Continuing finished work\n\n```bash\narchon workflow runs --open                                 # find the prior run id\narchon workflow run docs --adopt <run-id> \"Now also update the docs\"  # new run on the adopted worktree/branch\narchon complete fix/issue-42                                # remove worktree + local branch; keeps the remote branch\narchon isolation cleanup                                    # prune stale environments (7d)\narchon isolation cleanup --merged                           # prune merged ones now\n```\n\n## Resuming after failure\n\nA failed or paused run resumes from completed nodes:\n\n```bash\narchon workflow resume <run-id>\narchon workflow resume <run-id> --detach\n```\n\nDetached resume returns after engine acceptance, before completion. Your attention\nstream reports the run's next pause or end. If the\n60-second acceptance confirmation fails, inspect the run before retrying: the\nchild may still continue.\n\nThe ambient sequential session cursor is not reconstructed by a cold resume.\nExplicit `context: { resume: node-id }` ancestry is restored from the completed\nsource's saved handle, and a paused loop with `fresh_context: false` continues its\npre-pause session when the provider supports it. Use durable artifacts for every\nhandoff that must survive regardless of provider session availability.\n","isInternal":false,"tokens":2599,"sizeBytes":11224},{"name":"troubleshooting.md","path":".claude/skills/archon-cli/operate/troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/operate/troubleshooting.md","title":"Operate Skill","category":"anthropic-skill","format":"markdown","content":"# Troubleshooting Workflows\n\nWhere to look when a workflow fails, hangs, or does the wrong thing.\n\n## Log Locations\n\nWorkflow run logs are written as JSONL per run:\n\n```text\n~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl\n```\n\nEach line is a structured event. The discriminator is the `type` field. Values (see `packages/workflows/src/logger.ts` for the canonical list):\n\n| `type` | Meaning |\n|--------|---------|\n| `workflow_start` / `workflow_complete` / `workflow_error` | Run lifecycle |\n| `workflow_resume` | A resumed execution picked the run back up here. The first execution writes `workflow_start`; every resume writes one `workflow_resume` instead, so the rows after the n-th one belong to the run's (n+1)-th segment. Transcripts written before this row existed repeat `workflow_start` on resume |\n| `node_start` / `node_complete` / `node_error` / `node_skipped` | Node lifecycle |\n| `node_suspended` | A node paused the run; `content` names why (a gate kind such as `approval`, or `wait`) |\n| `gate_decision` | How a gate was resolved — `step` (the gate node), `decision` (`approved`, `rejected`, or an author-declared decision), and `content` (the operator's comment or rejection reason, as the run's `approval_received` event stores it: an approve without a comment records `Approved`, and the field is absent when the event stores neither). No actor is recorded. Written by the process that approved or rejected, which is often not the one running the workflow |\n| `assistant` | AI assistant message — has `content` field with the full AI output |\n| `tool` | SDK tool invocation — has `tool_name` and `tool_input` |\n| `exec_output` | What a `bash`/`script` node or `until_bash` probe printed — `stdout_tail`, `stderr_tail`, `exit_code` |\n| `watchdog_reset` | A streamed chunk renewed a node's idle watchdog — `chunk_type` only, never content. Renewals closer together than `WATCHDOG_RESET_BURST_GAP_MS` form a burst, recorded as its first and last renewal; `chunk_count` is the renewals since the node's previous record (absent on older transcripts, which wrote one row per renewal). A burst's last record can land after later rows, so order by `ts` |\n| `validation` | Historical compatibility only; current runs do not emit this type |\n\n> **Loop iteration lifecycle events are NOT in the JSONL file.** `loop_iteration_started` / `loop_iteration_completed` go through the workflow event emitter (WebSocket / `workflow_events` DB table) — query the DB or the Web UI dashboard for those. Per-iteration rows that DO reach the JSONL are keyed `<node-id>-iteration-<n>`: a loop's `node_complete` row, its `watchdog_reset` rows, and the `exec_output` row for each `until_bash` probe.\n\nFind the run ID from `archon workflow runs --status failed` (or `archon workflow runs` for the most recent run of any status; `workflow status` only shows active runs). Then:\n\n```bash\n# Last assistant message (what the AI said before failure)\njq -sr 'map(select(.type == \"assistant\") | .content) | last // empty' <log-file>\n\n# All error events (node failures + workflow-level failures)\njq 'select(.type == \"node_error\" or .type == \"workflow_error\")' <log-file>\n\n# What a specific node actually printed (evidence for \"did this really do X?\")\njq 'select(.type == \"exec_output\" and .step == \"<node-id>\")' <log-file>\n\n# Where the run was resumed, and how each gate was decided\njq 'select(.type == \"workflow_resume\" or .type == \"gate_decision\")' <log-file>\n\n# When a node's stream last renewed its watchdog, and with what chunk type\njq -s 'map(select(.type == \"watchdog_reset\" and .step == \"<node-id>\")) | sort_by(.ts) | last' <log-file>\n\n# Full event stream\ncat <log-file> | jq .\n```\n\nAdapter logs (Slack / Telegram / Web / GitHub) are emitted to stderr when `LOG_LEVEL=debug` is set on the server.\n\n## Artifact Locations\n\n```text\n~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/\n```\n\nInspect artifacts when a multi-node workflow produces wrong output. The failing node's upstream artifact is usually where the problem originated.\n\nNodes with `output_type:` also get engine-written sidecars under `runs/<run-id>/nodes/` (`<node-id>.md` + `<node-id>.meta.json`), and — for session-persisted workflows — mirrored copies under `artifacts/scopes/<workflow>/<scope>/nodes/` that survive across runs.\n\n```bash\nls ~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/\ncat ~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/issues/issue-42.md\n```\n\nArtifacts are **external** to the repo on purpose — they don't pollute git. They are kept until you remove them, except `.archon/scratch/` inside the run's directory, which holds throwaway checkouts and installs and is deleted once the run is completed, failed or cancelled and its nodes have stopped (for a container run, when its container is removed). A run whose nodes Archon cannot confirm stopped keeps its scratch.\n\n## Common Failure Modes\n\n### \"No base branch could be resolved\"\n\nA node references `$BASE_BRANCH`, but the run has no resolved base. Causes:\n- A git run where no `--base`, `worktree.baseBranch`, or codebase default was set and git detection failed. A shared-checkout child inherits this from its parent.\n- A folder project, which has no git base.\n- An isolated child that reused an existing branch without its original child run record. Its historical base is unknown, so config and parent flags cannot recover it.\n\n**Fix:**\n1. For a top-level git run, pass `--base <branch>` on `archon workflow run`, or set `worktree.baseBranch: main` (or `dev`, or whatever) in `.archon/config.yaml`. `--from` picks the starting point and does not set `$BASE_BRANCH`.\n2. For the reused-branch child, start a new parent run so the child cuts a new isolated branch with a known base.\n3. Or remove the `$BASE_BRANCH` reference if the node doesn't need it.\n\nA resumed run keeps its recorded base unless it is retargeted with `--base`. See [`$BASE_BRANCH` fail-fast](https://archon.diy/reference/variables/#base_branch-fail-fast).\n\n### \"Claude Code not found\" / \"Codex CLI binary not found\"\n\nCompiled-binary builds of Archon no longer embed Claude Code / Codex — you install them separately and Archon resolves the binary via env var or config.\n\n**Fix (Claude):**\n- Install: `curl -fsSL https://claude.ai/install.sh | bash` (or `npm install -g @anthropic-ai/claude-code`)\n- Set `CLAUDE_BIN_PATH=/path/to/claude` in `~/.archon/.env`, OR\n- Set `assistants.claude.claudeBinaryPath: /absolute/path` in `.archon/config.yaml`\n- Autodetect covers `$HOME/.local/bin/claude` (native installer) — no config needed if you used that path\n\n**Fix (Codex):**\n- Install: `npm install -g @openai/codex` (or platform-specific instructions)\n- Set `CODEX_BIN_PATH=/path/to/codex` or `assistants.codex.codexBinaryPath` in config\n- Autodetect covers the standard npm / Homebrew locations per platform\n\nSee [archon.diy/getting-started/installation/](https://archon.diy/getting-started/installation/) for full platform-specific install paths.\n\n### Workflow shows `running` for a long time but nothing happens\n\nFour possibilities:\n\n1. **The AI is actually working.** Check `~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl` — if you see recent `tool` or `assistant` events in the tail, it's fine. Wait.\n2. **The server crashed and left an orphan row.** Server startup no longer auto-fails orphaned `running` rows (per the \"Do not guess lifecycle ownership\" rule — `AGENTS.md`). Transition it manually:\n   - Any cancel (Web UI Cancel, `archon workflow cancel <run-id>`, chat `/workflow cancel [id]`) stops a live owner process before marking the row cancelled, and refuses when no owner answers — an orphan row gets that refusal, with the host and pid it recorded\n   - Verified orphan: `archon workflow abandon <run-id>` (or Abandon in the Web UI after the refused Cancel, or `/workflow abandon <id>` in chat) — marks the row cancelled\n3. **A node is past its `idle_timeout`.** The default is 30 minutes of complete silence (the timer resets on every streamed message — it's a deadlock detector, not a work limiter). Override with per-node `idle_timeout` (ms) if a node legitimately goes quiet for longer.\n4. **A tool call or background command never finished.** While the provider reports open work, `idle_timeout` does not fire. `archon workflow wait <run-id>` and `archon workflow status` show `stalled_tool_calls` attention once that work has made no progress for `workflows.toolCallAttentionMs` (30 minutes by default). `archon workflow end-turn <run-id>` ends that turn without cancelling the run; see `managing-runs.md`, \"A stalled turn\".\n\n### Workflow fails mid-way; how do I resume?\n\nFind the failed run, then resume it explicitly:\n\n```bash\narchon workflow runs\narchon workflow get <run-id>\narchon workflow resume <run-id>\n```\n\n`archon workflow resume <run-id>` targets that exact run and reuses its recorded working path/worktree. As a convenience, `archon workflow run my-workflow --resume` resumes the most recent resumable run for that workflow at the invocation cwd. A bare `archon workflow run my-workflow` starts a fresh run.\n\n**Session caveat:** A cold resume does not reconstruct the ambient sequential\n`context: shared` cursor. Explicit `context: { resume: node-id }` ancestry is\nrestored from saved run-scoped handles, and a deprecated interactive loop with\n`fresh_context: false` resumes its pre-pause session when the provider supports\nit. Artifact-based handoff remains the provider-independent path.\n\n### Paused run with no chat message\n\nA run is paused at a gate or an attention wait, but no message reached the conversation that started it.\n\n**Cause:** the platform could not deliver the pause notice. The run still waits. The failure is logged as `platform_message_send_failed` and recorded on the run as a `pause_notice_undelivered` event, which the console's run view shows. Answer the run by id: `archon workflow status`, then `approve`, `reject`, or `respond`.\n\n### `MCP server connection failed: <server>`\n\nArchon reports only the MCP servers a node declares with `mcp:`. Status for any other server goes to the debug log as `dag.mcp_server_status`. A declared server is required: on Claude and Codex the node fails as `misconfigured` before its first model call, with an error naming the server and its cause. Check the config JSON path, the server's `command` and `args`, and any env vars it references.\n\n### A node was skipped and I don't know why\n\nSkips carry a **reason** in the persisted `node_skipped` event (`archon workflow get <run-id> --verbose --json`, or the `workflow_events` table):\n\n| Reason | Meaning | Fix |\n|--------|---------|-----|\n| `trigger_rule` | Upstream states didn't satisfy the join rule (default `all_success` — a skipped upstream counts as not-success and cascades) | Use `one_success` / `none_failed_min_one_success` / `all_done` on merge nodes after `when:`-gated branches |\n| `when_condition` | The `when:` expression evaluated false | Expected behavior — check the upstream output it compared against |\n| `when_condition_parse_error` | The `when:` expression has invalid SYNTAX — fail-closed skip | Fix the expression (six operators, `&&`/`\\|\\|`, no parentheses) |\n| `prior_success` | Resume: the node completed in a prior run | Expected; use `always_run: true` to force re-execution |\n\n### Node FAILED with an `OutputRefError`-style message about `$node.output.field`\n\nNot a skip — strict field access failed loudly: the field isn't in the producer's `output_format` schema (typo), the schemaless producer's output isn't JSON / lacks the key, or the producer never ran. Fix the reference, add the field to the schema, or guard with `when:`/`trigger_rule`. (Only whole-text `$node.output` degrades silently to `''`.)\n\n### Node output is empty / a declared-optional field resolves to empty string\n\nCommon causes:\n\n1. Upstream node was **skipped** — whole-text `$node.output` is `''` for skipped producers.\n2. The field is declared in the producer's `output_format` but was absent/null in the actual output (declared-optional → `''` by design).\n3. Bash/script node printed to stderr, not stdout. Only stdout is captured.\n4. For script nodes, non-zero exit on a non-existent file / missing import fails the node. Check the run log for `node_error` entries.\n\n### A workflow-level field seems to have no effect\n\nInvalid values for optional workflow-level fields (`effort`, `sandbox`, `fallbackModel`, `betas`, `tags`, `worktree.enabled`, `mutates_checkout`) are **warn-and-drop**: the workflow still loads and runs, the field is discarded, and a warn-level `*_ignored` loader log is written (stderr for CLI commands, the server log for `archon serve`). AI-only fields on bash/script nodes, unknown keys, and deprecations are parse warnings instead. A key that belongs to another node type, such as `timeout:` on a prompt node, is a load error that names the node, the key and the node types that accept it. Run `archon validate workflows` or `archon workflow list` to see the warnings, or set `LOG_LEVEL=debug` to see them in the logs. Check both before assuming the field is broken.\n\n### Persisted session didn't restore (cold resume)\n\nWhen a node resumes a session that no longer exists, what happens depends on the provider. Pi starts a fresh session with a warning. Claude rejects the resume; when Archon confirms on the host that the transcript is gone and the rejection came before any output or tool call, it runs the node once more on a fresh session with the same warning. A second rejection, a rejection after output, a container run, or a named resume (`context: { resume }`) fails the node. Codex fails the node every time, so clear its saved session with `archon workflow reset-sessions <workflow>`. The fresh-session warning includes pointers to prior typed artifacts (`output_type` sidecars in the cross-run scope dir) so the prompt/agent can re-read lost context. If sessions are repeatedly cold, check server logs; clear broken state with `archon workflow reset-sessions <workflow>`.\n\n### `cannot fork a session` warning\n\nThe node's provider cannot fork a session (no `sessionFork` in the [provider capabilities](https://archon.diy/reference/provider-capabilities/)), so a `persist_session` node on it never continues an earlier run's session: every run starts fresh. `archon validate workflows` reports this when the provider is written in the workflow file. Once an earlier run has saved a session for this node and provider, each later run also posts this warning in chat and records a `node_session_not_continued` event naming the session it skipped. This is expected, not broken state, and `reset-sessions` does not change it. To carry context across runs, move the node to a provider that forks sessions, or have it write what the next run needs as an artifact and read that instead. If neither applies, remove `persist_session` from the node.\n\n## Useful Diagnostic Commands\n\n```bash\n# Environment sanity first: providers, DB, adapters (forge access: archon workflow run archon-forge-check)\narchon doctor\n\n# One run, any status, with raw events — the fastest \"why did node X skip/fail\"\narchon workflow get <run-id> --verbose --events --json | jq '.events[]'\n\n# Recent runs for this project, all statuses (\"did the review pass?\")\narchon workflow runs --json | jq '.runs[]'\n\n# Recent runs of any status (find a failed run's ID)\narchon workflow runs --status failed --limit 10\n\n# Active runs for this project (running / paused only — finished runs never appear here)\narchon workflow status --json | jq '.runs[]'\n\n# Active runs across all projects\narchon workflow status --all --json | jq '.runs[]'\n\n# Human-readable status of any active runs\narchon workflow status\n\n# Active worktrees and their last activity\narchon isolation list\n\n# Validate a specific workflow before running\narchon validate workflows my-workflow\n\n# Validate a specific command\narchon validate commands my-command\n\n# Dump the last 50 lines of a workflow's log\ntail -n 50 ~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl | jq .\n\n# Increase log verbosity (workflow run)\narchon workflow run my-workflow --verbose \"...\"\n\n# Increase server log verbosity\nLOG_LEVEL=debug bun run start\n```\n\n## Escalation: when nothing makes sense\n\n1. Run `archon version` and note the version.\n2. Run `archon validate workflows <name>` and capture the output.\n3. Grab the last ~50 lines of the run's JSONL log.\n4. Check the `CHANGELOG.md` for known issues / recent changes to the subsystem you're hitting.\n5. File an issue at https://github.com/coleam00/Archon/issues with version, validate output, log tail, and the YAML.\n","isInternal":false,"tokens":4002,"sizeBytes":16656},{"name":"setup-and-config.md","path":".claude/skills/archon-cli/setup/setup-and-config.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/setup/setup-and-config.md","title":"archon-cli-setup-config","category":"anthropic-skill","format":"markdown","content":"---\nname: archon-cli-setup-config\ndescription: Install Archon, verify with doctor, connect provider credentials, and change config.yaml (tiers, aliases, adapters). Read when setting up Archon or changing its configuration.\n---\n\n# Setup and config\n\nInstall Archon, verify it, and change its configuration. Run from the repo you\nwant to work on unless setting up globally.\n\n## First: is it already working?\n\n```bash\narchon --version      # CLI present?\narchon doctor         # installed providers, database, adapters\narchon doctor --full  # also probes the OpenCode runtime SDK\narchon workflow run archon-forge-check  # can the SDLC workflows reach the forge?\n```\n\nIf `doctor` is green, skip to Configuration. Only set up what is actually broken.\nFor the SDLC pack's queues, also read `../operate/queues.md` (\"Before the first run\").\n\n## Install\n\nPrerequisites: `git`, and Bun for source installs (a compiled binary needs only\nitself).\n\n```bash\n# Compiled binary; asks which providers to install\ncurl -fsSL https://archon.diy/install | bash\ncurl -fsSL https://archon.diy/install | bash -s -- --providers codex   # no question\n\n# Source install\ngit clone <archon-repo> && cd Archon\nbun install\ncd packages/cli && bun link && cd ../..\n\n# or run from a checkout without linking\nbun --cwd packages/cli src/cli.ts --help\n```\n\n## Providers (compiled binaries)\n\nA compiled binary ships without Claude, Codex and Pi; it runs only the ones\ninstalled. No provider is ever picked silently: a run needing a missing one\nfails before it starts and names the command.\n\n```bash\narchon provider add codex            # or: claude codex pi\narchon setup --providers codex           # same, then auth prompts\n```\n\nAn installed provider must match the CLI version: after upgrading Archon, rerun\n`archon provider add <ids>`. `archon doctor` shows what is installed and any\nstale provider. Source checkouts and Docker run all three from source; there\n`provider add` refuses.\n\n## Credentials — the setup wizard and its pieces\n\n```bash\narchon setup          # interactive wizard: providers + credentials + config + bundled skill\n```\n\nThe wizard composes these, which also work standalone:\n\n```bash\narchon ai list                      # which providers are connected\narchon ai key set anthropic        # API key (prompt/stdin) — anthropic, openai, ...\narchon ai login anthropic          # Claude Pro/Max subscription (Claude provider only)\narchon ai login openai             # ChatGPT/Codex subscription\narchon ai login github-copilot     # GitHub Copilot subscription\narchon auth github                  # GitHub identity via device flow\n```\n\nNotes:\n\n- Subscription logins cover Claude Pro/Max, ChatGPT/Codex, and GitHub Copilot.\n  API keys remain available through `ai key set`.\n- On multi-user installs each human connects their own credentials; runs execute\n  on the starter's keys. Solo installs just need one working provider.\n- `gh` must be authenticated (`gh auth status`) for any GitHub-touching workflow.\n\n## Config: two scopes, one schema\n\n| Scope | File | Wins |\n|---|---|---|\n| Repo | `<repo>/.archon/config.yaml` | over global |\n| Global/user | `~/.archon/config.yaml` | defaults |\n\nRead both before changing anything; change the narrowest scope that fits the ask.\n\nKey sections:\n\n```yaml\nassistants:\n  claude:\n    model: <default model for claude nodes>\n    # provider-specific options live here too\ntiers:                     # how much model a step deserves; keywords small/medium/large\n  large:\n    provider: claude\n    model: claude-<...>\n    effort: high\naliases:                   # @name shortcuts resolvable in workflows\n  \"@fast\":\n    provider: ...\n    model: ...\ndefaults:\n  loadDefaultWorkflows: true   # bundled archon-* pack\n  loadDefaultCommands: true\ndocs:\n  path: docs/                  # where $DOCS_DIR points\nenv:                           # per-project env vars injected into execution\nworktree:                      # repo scope only — see \"Files agents need in worktrees\"\n  baseBranch: main\n  copyFiles:\n    - .env\n```\n\n### Files agents need in worktrees\n\n`git worktree add` carries **tracked** files only. Everything gitignored is absent\nfrom a fresh worktree — `.env` above all — so an agent cannot start the project's\nserver, run integration tests, or reproduce anything reading local credentials\nfrom the checkout it was handed. Nothing errors; the agent just finds no config.\n\nIf a user asks why something works in their checkout but not in a run, check this\nfirst. The fix is one entry:\n\n```yaml\n# <repo>/.archon/config.yaml\nworktree:\n  copyFiles:\n    - .env\n```\n\nRules worth knowing before suggesting entries:\n\n- **Repo scope only.** `worktree` is read from `<repo>/.archon/config.yaml`.\n  Putting it in `~/.archon/config.yaml` parses and does nothing.\n- **Copy the real gitignored file, never a tracked template.** Suggesting\n  `.env.example` is wrong twice: a worktree already has it, and materialising a\n  placeholder `.env` makes a server start against fake credentials instead of\n  failing.\n- **Entries are plain paths.** Source and destination are always identical; there\n  is no rename syntax.\n- **Missing entries are skipped silently**, so listing an optional path is safe\n  and a contributor without the file is unaffected.\n- Nothing is copied unless listed. Workflows, commands, and scripts do **not**\n  need an entry — a run freezes its own source.\n\nOther common entries: `.vscode/`, `.claude/`, and local-only fixture or plans\ndirectories. Full semantics, including path-traversal rejection and the\n`worktree.path` interaction, are in `/reference/configuration/`.\n\nPrefer the typed commands over hand-editing when they exist — they validate:\n\n```bash\narchon ai tier set large claude claude-<model> --effort high\narchon ai tier list --json     # configured vs built-in defaults\narchon ai alias set @fast pi minimax/MiniMax-M3\narchon ai key set openai                         # masked prompt, or key via stdin\n```\n\nTier, alias, and default commands write install-wide settings unless you pass\n`--scope user`. Use the user scope for one person's preferences on a shared\ninstall; use the install scope for shared defaults.\n\nAfter edits, confirm with `archon doctor` and one cheap probe:\n`archon workflow list` should load with no errors.\n\n## Alternate config layers (per-run model setups)\n\nA user who wants a second model setup — say, a cheap free-model combo or a\npremium one — does not need to edit their main config. Create a **sparse YAML\nlayer** holding only the keys it overrides, and load it per run with `--config`:\n\n```yaml\n# e.g. .archon/config.free.yaml (operator-local; `.archon/config.*.yaml` is gitignored,\n# and `.archon/config.example.yaml` is the committed template to copy)\ntiers:\n  small:\n    provider: pi\n    model: minimax/MiniMax-M3\n  large:\n    provider: pi\n    model: minimax/MiniMax-M3\naliases:\n  \"@fast\":\n    provider: pi\n    model: minimax/MiniMax-M3\n```\n\nValid layer keys: `assistant` or `defaultAssistant`, `assistants`, `tiers`,\n`aliases`, `workflows`, `docs` (with `path`), and `env`. A tier or alias entry\nstores the Archon provider separately from the model. For `provider: pi`, the\nmodel is Pi's inner `<vendor>/<model>` reference, such as\n`minimax/MiniMax-M3`. The outer `pi/` appears only in a single-string CLI\nbinding such as `--model large=pi/minimax/MiniMax-M3`.\n\n```bash\narchon workflow run archon-ship --branch fix/x \"...\" --config .config.free.yaml\n```\n\nNotes:\n- The layer applies to that fresh run and its descendants only; nothing persists.\n- Mutually exclusive with resuming an existing run.\n- One-off, ad-hoc rebinding without a file: use `--model tier=provider/model`\n  instead — see `../operate/managing-runs.md`.\n\nTo help a user decide scope: persistent default → edit config.yaml (below);\na named alternate setup they will reuse → a config layer;\na one-time experiment → `--model` flags.\n\n## Adapters (Slack / Telegram / GitHub / Discord / Web)\n\nAdapters let platforms drive Archon remotely. Each configures via env vars and is\nverified by `doctor`. The web UI is the zero-config surface:\n\n```bash\narchon serve            # web console; downloads the server and UI on first run (default port 3090)\n```\n\nSet up a chat adapter only when the user actually asks to drive Archon from that\nplatform — do not proactively configure Slack/Telegram/Discord.\n\n## Project initialization for workflow authoring\n\nCreating `.archon/` content (workflows, commands, scripts) belongs to the authoring\ncapability: see `../authoring/authoring-workflows.md`. Setup only ensures the CLI works.\n","frontmatter":{"name":"archon-cli-setup-config","description":"Install Archon, verify with doctor, connect provider credentials, and change config.yaml (tiers, aliases, adapters). Read when setting up Archon or changing its configuration."},"isInternal":false,"tokens":2055,"sizeBytes":8574},{"name":"SKILL.md","path":".claude/skills/archon-cli/SKILL.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/archon-cli/SKILL.md","title":"archon-cli","category":"anthropic-skill","format":"markdown","content":"---\nname: archon-cli\ndescription: |\n  Drive Archon through its CLI: run AI workflows on a repo, watch and answer the runs\n  that need a decision, drive the SDLC pack's queues (intake, build queue, merge queue,\n  discoveries), set up Archon or change its config, and author and validate workflows.\n  Use when the user says \"use archon\", \"run archon\", \"archon workflow\", \"fix issue #N\n  with archon\", \"have archon review this PR\", \"what's running / check run <id>\",\n  \"approve/reject/answer/cancel/resume that run\", \"run the build queue\", \"drain\n  discoveries\", \"set up archon\", \"configure archon\", \"change my archon config\",\n  \"create a workflow\", \"author a workflow\", or asks how to write a better Archon prompt.\n  NOT for: doing the coding work yourself. Archon delegates it to isolated runs.\nargument-hint: \"[workflow | run-id | intent]\"\n---\n\n# Archon CLI\n\nArchon runs multi-step AI workflows through the `archon` CLI. Git projects run in\nisolated worktrees by default; registered folder projects run in place. You sit on\ntop of the runs: you write the brief, launch, watch one stream, answer the decisions\nthat reach you, and read what each run left behind.\n\nRoute by intent:\n\n| The user wants to... | Read |\n|---|---|\n| Run a workflow on real work | `operate/running.md` |\n| Inspect, answer, cancel or resume runs | `operate/managing-runs.md` |\n| Drive the SDLC pack's queues | `operate/queues.md` |\n| Find out why a run failed or hangs | `operate/troubleshooting.md` |\n| Set up Archon or change config | `setup/setup-and-config.md` |\n| Author or change a workflow | `authoring/authoring-workflows.md` (then `node-reference.md` and `variables.md` in the same folder) |\n| Improve a node prompt or a run message | `authoring/prompting-mistakes.md` |\n\n- When the intent is clear (\"build me a workflow for X\", \"run archon-ship on issue\n  #42\"), go straight to the reference. Do not ask.\n- When it is ambiguous, ask which of these the user wants.\n- On a machine where `archon doctor` fails, start with `setup/setup-and-config.md`.\n\n## The operating loop\n\n1. **Discover.** `archon workflow list --json` gives compact descriptions. For each\n   plausible candidate, read the full description with `archon workflow list <name>\n   --full`. Choose from full descriptions, never from names you remember.\n2. **Check the brief.** The message, issue body or document the run reads is the\n   contract the run is measured against. It must state the problem, why it is worth\n   solving, why now, the desired outcome, the invariants and the acceptance. If any\n   is missing, say which, propose wording, and get the user's agreement before\n   launching. `operate/running.md` has the full check.\n3. **Launch detached.** Pass the work as a message, the workflow's declared inputs\n   with `--input`, or both:\n\n   ```bash\n   archon workflow run <workflow> --branch <branch-name> \"<the work>\" --detach\n   archon workflow run <workflow> --input <name>=<value> --input <name>=<value> --detach\n   ```\n\n4. **Watch once.** Run one stream as a background task of your harness, however many\n   runs you launched:\n\n   ```bash\n   archon workflow attention --watch --json        # this project; add --all for the install\n   ```\n\n   Each line is JSON. `attention` means a run needs a decision, `resolved` means it no\n   longer does, and `terminal` means a run ended. Act on the line, then keep reading.\n5. **Answer deliberately.** Read the item's question, its recommendation and the\n   evidence it links. Answer with a reason, detached:\n   `archon workflow respond <run-id> <decision> \"<reason>\" --detach`.\n   `operate/managing-runs.md` covers every form.\n6. **Read the result.** A completed run is not a verdict. `archon workflow get\n   <run-id> --json` gives the normalized `outcome` and `leave_behind.artifactFiles`.\n   Read the report before telling the user what the run concluded.\n\n## Hard rules\n\n- **Never launch on a thin brief.** A weak input does not produce a weak result. It\n  produces a confident, well-formed answer to the wrong question, at full price.\n- **Launch and answer with `--detach`.** A detached run starts its own session and\n  survives your shell. A foreground `respond` or `resume` continues the run inside\n  your shell, and closing the shell stops it.\n- **One watcher, not one per run.** One `workflow attention --watch` stream covers\n  every run in scope, including runs that triggers or other sessions start. A\n  `workflow wait <run-id>` per run is fine for one or two runs; with dozens it\n  contends for the database.\n- **Stop work only when you mean to end it.** `archon workflow cancel` stops the run\n  mid-node, and that node's provider session and unsaved work are lost. Let a run reach\n  its next pause or end when you only want to stop launching more. When one turn is\n  stuck on a tool or background command that never finishes, `archon workflow\n  end-turn <run-id>` ends that turn and keeps the run (`operate/managing-runs.md`,\n  \"A stalled turn\").\n- **Merge only what was reviewed.** When you merge a pull request by hand, pin the\n  merge to the reviewed head (`gh pr merge <n> --match-head-commit <sha>`), read the\n  open review threads first, and sync your checkout afterwards.\n- **Author agent-first.** In a workflow you write, an agent node with typed output\n  judges, scripts read facts and act at boundaries, and YAML only wires them.\n  `authoring/authoring-workflows.md` (\"Agent-first\") has the rule and an example.\n\n## Gotchas\n\n- The current directory selects the project for discovery, launches and project\n  listings. `workflow status`, `workflow runs` and `workflow attention` default to\n  that project; add `--all` for the whole install. For a git project, run from the\n  repo root. Register a non-git folder with `workflow run --folder`. A full run ID\n  works from any directory.\n- Attention items are paused runs. A running run stuck on a stalled tool call is\n  not an item: `workflow wait <run-id>` and `workflow status` show it.\n- `workflow status` and `workflow runs` set `scopeFallback: true` in JSON when an\n  unregistered project produced an install-wide result.\n- Prefer `--json` whenever you will parse output.\n- `archon --help` is authoritative. If a flag this skill names is rejected, trust the\n  CLI's help and tell the user about the drift.\n","frontmatter":{"name":"archon-cli","description":"Drive Archon through its CLI: run AI workflows on a repo, watch and answer the runs\nthat need a decision, drive the SDLC pack's queues (intake, build queue, merge queue,\ndiscoveries), set up Archon or change its config, and author and validate workflows.\nUse when the user says \"use archon\", \"run archon\", \"archon workflow\", \"fix issue #N\nwith archon\", \"have archon review this PR\", \"what's running / check run <id>\",\n\"approve/reject/answer/cancel/resume that run\", \"run the build queue\", \"drain\ndiscoveries\", \"set up archon\", \"configure archon\", \"change my archon config\",\n\"create a workflow\", \"author a workflow\", or asks how to write a better Archon prompt.\nNOT for: doing the coding work yourself. Archon delegates it to isolated runs.\n","argument-hint":"[workflow | run-id | intent]"},"isInternal":false,"tokens":1569,"sizeBytes":6276},{"name":"SKILL.md","path":".claude/skills/docker-extend/SKILL.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/docker-extend/SKILL.md","title":"docker-extend","category":"anthropic-skill","format":"markdown","content":"---\nname: docker-extend\ndescription: |\n  Use when: User wants to extend Docker with custom tools, personalize the Docker environment,\n  or set up user-specific Docker customization.\n  Triggers: 'extend docker', 'docker-extend', 'add tools to docker', 'customize docker',\n            'add my tools to the container', 'personalize docker setup', 'docker user setup',\n            'install tools in docker'.\n  Does: Interactively sets up Dockerfile.user and docker-compose.override.yml so users can\n  add personal tools to their Docker environment without affecting maintainer files or\n  committing user-specific config to git.\nargument-hint: '[optional: tools to install, e.g. \"vim ripgrep jq\"]'\n---\n\n# Docker Extend\n\nSets up personal Docker tool customization using the project's gitignored override pattern.\nYour `Dockerfile.user` and `docker-compose.override.yml` are yours — never committed to git.\n\n## Step 1: Detect Context\n\nDetermine if the user is in dev (source build) or deploy (GHCR) mode:\n\n!`if [ -f docker-compose.yml ] && grep -q 'build: \\.' docker-compose.yml 2>/dev/null; then echo \"DEV_MODE: Building from source (root docker-compose.yml)\"; elif [ -f deploy/docker-compose.yml ]; then echo \"DEPLOY_MODE: Using GHCR image (deploy/docker-compose.yml)\"; else echo \"UNKNOWN_MODE: No docker-compose.yml found in root or deploy/\"; fi`\n\n**Decision:**\n- `DEV_MODE` → work with `Dockerfile.user` + `docker-compose.override.yml` (repo root)\n- `DEPLOY_MODE` → work with `deploy/Dockerfile.user` + `deploy/docker-compose.override.yml`\n- `UNKNOWN_MODE` → ask the user which directory their `docker-compose.yml` lives in before proceeding\n\n## Step 2: Check Current State\n\n**DEV_MODE — check root files:**\n\n!`echo \"=== Dockerfile.user ===\" && (cat Dockerfile.user 2>/dev/null || echo \"(not found — will create from example)\") && echo \"\" && echo \"=== docker-compose.override.yml ===\" && (cat docker-compose.override.yml 2>/dev/null || echo \"(not found — will create from example)\")`\n\n**DEPLOY_MODE — check deploy/ files:**\n\n!`echo \"=== deploy/Dockerfile.user ===\" && (cat deploy/Dockerfile.user 2>/dev/null || echo \"(not found — will create from example)\") && echo \"\" && echo \"=== deploy/docker-compose.override.yml ===\" && (cat deploy/docker-compose.override.yml 2>/dev/null || echo \"(not found — will create from example)\")`\n\n**If both files already exist:** Skip to Step 4 (add tools).\n**If files are missing:** Proceed to Step 3 (copy from examples).\n**If example files are missing too:** Tell the user the project hasn't added the example files yet and to check with the maintainers or the docs.\n\n## Step 3: Copy Example Files\n\nRun only the copy commands for whichever files are missing. Do NOT overwrite existing files.\n\n**DEV_MODE:**\n\n```bash\n# Only if Dockerfile.user does not exist:\ncp Dockerfile.user.example Dockerfile.user\n\n# Only if docker-compose.override.yml does not exist:\ncp docker-compose.override.example.yml docker-compose.override.yml\n```\n\n**DEPLOY_MODE:**\n\n```bash\n# Only if deploy/Dockerfile.user does not exist:\ncp deploy/Dockerfile.user.example deploy/Dockerfile.user\n\n# Only if deploy/docker-compose.override.yml does not exist:\ncp deploy/docker-compose.override.example.yml deploy/docker-compose.override.yml\n```\n\nAfter running the copy commands, confirm to the user which files were created.\n\n## Step 4: Add Tools\n\nParse `$ARGUMENTS` for tool names (space-separated, e.g., `vim ripgrep jq`).\n\n**If tools were specified:** Edit the appropriate `Dockerfile.user` to add (or uncomment) the `RUN apt-get install` block with the requested packages. Use the Edit tool — do not rewrite the entire file.\n\nThe block to add/modify:\n```dockerfile\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    <tool1> \\\n    <tool2> \\\n && rm -rf /var/lib/apt/lists/*\n```\n\n**If no tools were specified:** Show the user the file and ask which tools they want to add. Then edit the file accordingly.\n\n## Step 5: Rebuild\n\nShow the user the rebuild command for their context. Do NOT run it automatically.\n\n**DEV_MODE:**\n```bash\ndocker compose build && docker compose up -d\n```\n\n**DEPLOY_MODE:**\n```bash\ncd deploy && docker compose build && docker compose up -d\n```\n\n## Summary\n\nTell the user:\n1. Which files were created (if any)\n2. Which tools were added to `Dockerfile.user` (if any)\n3. The rebuild command to run when ready\n4. A reminder that `Dockerfile.user` and `docker-compose.override.yml` are gitignored — they're personal and won't be committed\n","frontmatter":{"name":"docker-extend","description":"Use when: User wants to extend Docker with custom tools, personalize the Docker environment,\nor set up user-specific Docker customization.\nTriggers: 'extend docker', 'docker-extend', 'add tools to docker', 'customize docker',\n          'add my tools to the container', 'personalize docker setup', 'docker user setup',\n          'install tools in docker'.\nDoes: Interactively sets up Dockerfile.user and docker-compose.override.yml so users can\nadd personal tools to their Docker environment without affecting maintainer files or\ncommitting user-specific config to git.\n","argument-hint":"[optional: tools to install, e.g. \"vim ripgrep jq\"]"},"isInternal":false,"tokens":1073,"sizeBytes":4497},{"name":"element-attributes.md","path":".claude/skills/playwright-cli/references/element-attributes.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/element-attributes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Inspecting Element Attributes\n\nWhen the snapshot doesn't show an element's `id`, `class`, `data-*` attributes, or other DOM properties, use `eval` to inspect them.\n\n## Examples\n\n```bash\nplaywright-cli snapshot\n# snapshot shows a button as e7 but doesn't reveal its id or data attributes\n\n# get the element's id\nplaywright-cli eval \"el => el.id\" e7\n\n# get all CSS classes\nplaywright-cli eval \"el => el.className\" e7\n\n# get a specific attribute\nplaywright-cli eval \"el => el.getAttribute('data-testid')\" e7\nplaywright-cli eval \"el => el.getAttribute('aria-label')\" e7\n\n# get a computed style property\nplaywright-cli eval \"el => getComputedStyle(el).display\" e7\n```\n","isInternal":false,"tokens":171,"sizeBytes":665},{"name":"playwright-tests.md","path":".claude/skills/playwright-cli/references/playwright-tests.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/playwright-tests.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Running Playwright Tests\n\nTo run Playwright tests, use the `npx playwright test` command, or a package manager script. To avoid opening the interactive html report, use `PLAYWRIGHT_HTML_OPEN=never` environment variable.\n\n```bash\n# Run all tests\nPLAYWRIGHT_HTML_OPEN=never npx playwright test\n\n# Run all tests through a custom npm script\nPLAYWRIGHT_HTML_OPEN=never npm run special-test-command\n```\n\n# Debugging Playwright Tests\n\nTo debug a failing Playwright test, run it with `--debug=cli` option. This command will pause the test at the start and print the debugging instructions.\n\n**IMPORTANT**: run the command in the background and check the output until \"Debugging Instructions\" is printed. Make sure to stop the command after you have finished.\n\nOnce instructions containing a session name are printed, use `playwright-cli` to attach the session and explore the page.\n\n```bash\n# Run the test\nPLAYWRIGHT_HTML_OPEN=never npx playwright test --debug=cli\n# ...\n# ... debugging instructions for \"tw-abcdef\" session ...\n# ...\n\n# Attach to the test\nplaywright-cli attach tw-abcdef\n```\n\nKeep the test running in the background while you explore and look for a fix.\nThe test is paused at the start, so you should step over or pause at a particular location\nwhere the problem is most likely to be.\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code.\nThis code appears in the output and can be copied directly into the test. Most of the time, a specific locator or an expectation should be updated, but it could also be a bug in the app. Use your judgement.\n\nAfter fixing the test, stop the background test run. Rerun to check that test passes.\n","isInternal":false,"tokens":375,"sizeBytes":1690},{"name":"request-mocking.md","path":".claude/skills/playwright-cli/references/request-mocking.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/request-mocking.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Request Mocking\n\nIntercept, mock, modify, and block network requests.\n\n## CLI Route Commands\n\n```bash\n# Mock with custom status\nplaywright-cli route \"**/*.jpg\" --status=404\n\n# Mock with JSON body\nplaywright-cli route \"**/api/users\" --body='[{\"id\":1,\"name\":\"Alice\"}]' --content-type=application/json\n\n# Mock with custom headers\nplaywright-cli route \"**/api/data\" --body='{\"ok\":true}' --header=\"X-Custom: value\"\n\n# Remove headers from requests\nplaywright-cli route \"**/*\" --remove-header=cookie,authorization\n\n# List active routes\nplaywright-cli route-list\n\n# Remove a route or all routes\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n## URL Patterns\n\n```\n**/api/users           - Exact path match\n**/api/*/details       - Wildcard in path\n**/*.{png,jpg,jpeg}    - Match file extensions\n**/search?q=*          - Match query parameters\n```\n\n## Advanced Mocking with run-code\n\nFor conditional responses, request body inspection, response modification, or delays:\n\n### Conditional Response Based on Request\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/login', route => {\n    const body = route.request().postDataJSON();\n    if (body.username === 'admin') {\n      route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) });\n    } else {\n      route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) });\n    }\n  });\n}\"\n```\n\n### Modify Real Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/user', async route => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.isPremium = true;\n    await route.fulfill({ response, json });\n  });\n}\"\n```\n\n### Simulate Network Failures\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/offline', route => route.abort('internetdisconnected'));\n}\"\n# Options: connectionrefused, timedout, connectionreset, internetdisconnected\n```\n\n### Delayed Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/slow', async route => {\n    await new Promise(r => setTimeout(r, 3000));\n    route.fulfill({ body: JSON.stringify({ data: 'loaded' }) });\n  });\n}\"\n```\n","isInternal":false,"tokens":549,"sizeBytes":2182},{"name":"running-code.md","path":".claude/skills/playwright-cli/references/running-code.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/running-code.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Running Custom Playwright Code\n\nUse `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands.\n\n## Syntax\n\n```bash\nplaywright-cli run-code \"async page => {\n  // Your Playwright code here\n  // Access page.context() for browser context operations\n}\"\n```\n\nYou can also load the function from a file:\n\n```bash\nplaywright-cli run-code --filename=./my-script.js\n```\n\n\nThe code must be a single function expression, it is wrapped in `(...)` and evaluated.\nimport/export/require syntax is not supported.\n\n## Geolocation\n\n```bash\n# Grant geolocation permission and set location\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });\n}\"\n\n# Set location to London\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 });\n}\"\n\n# Clear geolocation override\nplaywright-cli run-code \"async page => {\n  await page.context().clearPermissions();\n}\"\n```\n\n## Permissions\n\n```bash\n# Grant multiple permissions\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions([\n    'geolocation',\n    'notifications',\n    'camera',\n    'microphone'\n  ]);\n}\"\n\n# Grant permissions for specific origin\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read'], {\n    origin: 'https://example.com'\n  });\n}\"\n```\n\n## Media Emulation\n\n```bash\n# Emulate dark color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'dark' });\n}\"\n\n# Emulate light color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'light' });\n}\"\n\n# Emulate reduced motion\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ reducedMotion: 'reduce' });\n}\"\n\n# Emulate print media\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ media: 'print' });\n}\"\n```\n\n## Wait Strategies\n\n```bash\n# Wait for network idle\nplaywright-cli run-code \"async page => {\n  await page.waitForLoadState('networkidle');\n}\"\n\n# Wait for specific element\nplaywright-cli run-code \"async page => {\n  await page.locator('.loading').waitFor({ state: 'hidden' });\n}\"\n\n# Wait for function to return true\nplaywright-cli run-code \"async page => {\n  await page.waitForFunction(() => window.appReady === true);\n}\"\n\n# Wait with timeout\nplaywright-cli run-code \"async page => {\n  await page.locator('.result').waitFor({ timeout: 10000 });\n}\"\n```\n\n## Frames and Iframes\n\n```bash\n# Work with iframe\nplaywright-cli run-code \"async page => {\n  const frame = page.locator('iframe#my-iframe').contentFrame();\n  await frame.locator('button').click();\n}\"\n\n# Get all frames\nplaywright-cli run-code \"async page => {\n  const frames = page.frames();\n  return frames.map(f => f.url());\n}\"\n```\n\n## File Downloads\n\n```bash\n# Handle file download\nplaywright-cli run-code \"async page => {\n  const downloadPromise = page.waitForEvent('download');\n  await page.getByRole('link', { name: 'Download' }).click();\n  const download = await downloadPromise;\n  await download.saveAs('./downloaded-file.pdf');\n  return download.suggestedFilename();\n}\"\n```\n\n## Clipboard\n\n```bash\n# Read clipboard (requires permission)\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read']);\n  return await page.evaluate(() => navigator.clipboard.readText());\n}\"\n\n# Write to clipboard\nplaywright-cli run-code \"async page => {\n  await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!');\n}\"\n```\n\n## Page Information\n\n```bash\n# Get page title\nplaywright-cli run-code \"async page => {\n  return await page.title();\n}\"\n\n# Get current URL\nplaywright-cli run-code \"async page => {\n  return page.url();\n}\"\n\n# Get page content\nplaywright-cli run-code \"async page => {\n  return await page.content();\n}\"\n\n# Get viewport size\nplaywright-cli run-code \"async page => {\n  return page.viewportSize();\n}\"\n```\n\n## JavaScript Execution\n\n```bash\n# Execute JavaScript and return result\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(() => {\n    return {\n      userAgent: navigator.userAgent,\n      language: navigator.language,\n      cookiesEnabled: navigator.cookieEnabled\n    };\n  });\n}\"\n\n# Pass arguments to evaluate\nplaywright-cli run-code \"async page => {\n  const multiplier = 5;\n  return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier);\n}\"\n```\n\n## Error Handling\n\n```bash\n# Try-catch in run-code\nplaywright-cli run-code \"async page => {\n  try {\n    await page.getByRole('button', { name: 'Submit' }).click({ timeout: 1000 });\n    return 'clicked';\n  } catch (e) {\n    return 'element not found';\n  }\n}\"\n```\n\n## Complex Workflows\n\n```bash\n# Login and save state\nplaywright-cli run-code \"async page => {\n  await page.goto('https://example.com/login');\n  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n  await page.getByRole('textbox', { name: 'Password' }).fill('secret');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await page.waitForURL('**/dashboard');\n  await page.context().storageState({ path: 'auth.json' });\n  return 'Login successful';\n}\"\n\n# Scrape data from multiple pages\nplaywright-cli run-code \"async page => {\n  const results = [];\n  for (let i = 1; i <= 3; i++) {\n    await page.goto(\\`https://example.com/page/\\${i}\\`);\n    const items = await page.locator('.item').allTextContents();\n    results.push(...items);\n  }\n  return results;\n}\"\n```\n","isInternal":false,"tokens":1358,"sizeBytes":5642},{"name":"session-management.md","path":".claude/skills/playwright-cli/references/session-management.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/session-management.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Browser Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Browser Sessions\n\nUse `-s` flag to isolate browser contexts:\n\n```bash\n# Browser 1: Authentication flow\nplaywright-cli -s=auth open https://app.example.com/login\n\n# Browser 2: Public browsing (separate cookies, storage)\nplaywright-cli -s=public open https://example.com\n\n# Commands are isolated by browser session\nplaywright-cli -s=auth fill e1 \"user@example.com\"\nplaywright-cli -s=public snapshot\n```\n\n## Browser Session Isolation Properties\n\nEach browser session has independent:\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Browser Session Commands\n\n```bash\n# List all browser sessions\nplaywright-cli list\n\n# Stop a browser session (close the browser)\nplaywright-cli close                # stop the default browser\nplaywright-cli -s=mysession close   # stop a named browser\n\n# Stop all browser sessions\nplaywright-cli close-all\n\n# Forcefully kill all daemon processes (for stale/zombie processes)\nplaywright-cli kill-all\n\n# Delete browser session user data (profile directory)\nplaywright-cli delete-data                # delete default browser data\nplaywright-cli -s=mysession delete-data   # delete named browser data\n```\n\n## Environment Variable\n\nSet a default browser session name via environment variable:\n\n```bash\nexport PLAYWRIGHT_CLI_SESSION=\"mysession\"\nplaywright-cli open example.com  # Uses \"mysession\" automatically\n```\n\n## Common Patterns\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all browsers\nplaywright-cli -s=site1 open https://site1.com &\nplaywright-cli -s=site2 open https://site2.com &\nplaywright-cli -s=site3 open https://site3.com &\nwait\n\n# Take snapshots from each\nplaywright-cli -s=site1 snapshot\nplaywright-cli -s=site2 snapshot\nplaywright-cli -s=site3 snapshot\n\n# Cleanup\nplaywright-cli close-all\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nplaywright-cli -s=variant-a open \"https://app.com?variant=a\"\nplaywright-cli -s=variant-b open \"https://app.com?variant=b\"\n\n# Compare\nplaywright-cli -s=variant-a screenshot\nplaywright-cli -s=variant-b screenshot\n```\n\n### Persistent Profile\n\nBy default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk:\n\n```bash\n# Use persistent profile (auto-generated location)\nplaywright-cli open https://example.com --persistent\n\n# Use persistent profile with custom directory\nplaywright-cli open https://example.com --profile=/path/to/profile\n```\n\n## Attaching to a Running Browser\n\nUse `attach` to connect to a browser that is already running, instead of launching a new one.\n\n### Attach by channel name\n\nConnect to a running Chrome or Edge instance by its channel name. The browser must have remote debugging enabled — navigate to `chrome://inspect/#remote-debugging` in the target browser and check \"Allow remote debugging for this browser instance\".\n\n```bash\n# Attach to Chrome\nplaywright-cli attach --cdp=chrome\n\n# Attach to Chrome Canary\nplaywright-cli attach --cdp=chrome-canary\n\n# Attach to Microsoft Edge\nplaywright-cli attach --cdp=msedge\n\n# Attach to Edge Dev\nplaywright-cli attach --cdp=msedge-dev\n```\n\nSupported channels: `chrome`, `chrome-beta`, `chrome-dev`, `chrome-canary`, `msedge`, `msedge-beta`, `msedge-dev`, `msedge-canary`.\n\nWhen `--session` is not provided, the session is named after the channel (e.g. `--cdp=msedge` creates a session called `msedge`), so parallel attaches to Chrome and Edge don't collide on `default`. Pass `--session=<name>` to override.\n\n### Attach via CDP endpoint\n\nConnect to a browser that exposes a Chrome DevTools Protocol endpoint:\n\n```bash\nplaywright-cli attach --cdp=http://localhost:9222\n```\n\n### Attach via browser extension\n\nConnect to a browser with the Playwright extension installed:\n\n```bash\nplaywright-cli attach --extension\n```\n\n### Detach\n\nTear down an attached session without affecting the external browser:\n\n```bash\n# Detach the default attached session\nplaywright-cli detach\n\n# Detach a specific attached session\nplaywright-cli -s=msedge detach\n```\n\n`detach` only works on sessions created via `attach`. For sessions created via `open`, use `close`.\n\n## Default Browser Session\n\nWhen `-s` is omitted, commands use the default browser session:\n\n```bash\n# These use the same default browser session\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli close  # Stops default browser\n```\n\n## Browser Session Configuration\n\nConfigure a browser session with specific settings when opening:\n\n```bash\n# Open with config file\nplaywright-cli open https://example.com --config=.playwright/my-cli.json\n\n# Open with specific browser\nplaywright-cli open https://example.com --browser=firefox\n\n# Open in headed mode\nplaywright-cli open https://example.com --headed\n\n# Open with persistent profile\nplaywright-cli open https://example.com --persistent\n```\n\n## Best Practices\n\n### 1. Name Browser Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nplaywright-cli -s=github-auth open https://github.com\nplaywright-cli -s=docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nplaywright-cli -s=s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Stop browsers when done\nplaywright-cli -s=auth close\nplaywright-cli -s=scrape close\n\n# Or stop all at once\nplaywright-cli close-all\n\n# If browsers become unresponsive or zombie processes remain\nplaywright-cli kill-all\n```\n\n### 3. Delete Stale Browser Data\n\n```bash\n# Remove old browser data to free disk space\nplaywright-cli -s=oldsession delete-data\n```\n","isInternal":false,"tokens":1337,"sizeBytes":5671},{"name":"storage-state.md","path":".claude/skills/playwright-cli/references/storage-state.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/storage-state.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Storage Management\n\nManage cookies, localStorage, sessionStorage, and browser storage state.\n\n## Storage State\n\nSave and restore complete browser state including cookies and storage.\n\n### Save Storage State\n\n```bash\n# Save to auto-generated filename (storage-state-{timestamp}.json)\nplaywright-cli state-save\n\n# Save to specific filename\nplaywright-cli state-save my-auth-state.json\n```\n\n### Restore Storage State\n\n```bash\n# Load storage state from file\nplaywright-cli state-load my-auth-state.json\n\n# Reload page to apply cookies\nplaywright-cli open https://example.com\n```\n\n### Storage State File Format\n\nThe saved file contains:\n\n```json\n{\n  \"cookies\": [\n    {\n      \"name\": \"session_id\",\n      \"value\": \"abc123\",\n      \"domain\": \"example.com\",\n      \"path\": \"/\",\n      \"expires\": 1893456000,\n      \"httpOnly\": true,\n      \"secure\": true,\n      \"sameSite\": \"Lax\"\n    }\n  ],\n  \"origins\": [\n    {\n      \"origin\": \"https://example.com\",\n      \"localStorage\": [\n        { \"name\": \"theme\", \"value\": \"dark\" },\n        { \"name\": \"user_id\", \"value\": \"12345\" }\n      ]\n    }\n  ]\n}\n```\n\n## Cookies\n\n### List All Cookies\n\n```bash\nplaywright-cli cookie-list\n```\n\n### Filter Cookies by Domain\n\n```bash\nplaywright-cli cookie-list --domain=example.com\n```\n\n### Filter Cookies by Path\n\n```bash\nplaywright-cli cookie-list --path=/api\n```\n\n### Get Specific Cookie\n\n```bash\nplaywright-cli cookie-get session_id\n```\n\n### Set a Cookie\n\n```bash\n# Basic cookie\nplaywright-cli cookie-set session abc123\n\n# Cookie with options\nplaywright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax\n\n# Cookie with expiration (Unix timestamp)\nplaywright-cli cookie-set remember_me token123 --expires=1893456000\n```\n\n### Delete a Cookie\n\n```bash\nplaywright-cli cookie-delete session_id\n```\n\n### Clear All Cookies\n\n```bash\nplaywright-cli cookie-clear\n```\n\n### Advanced: Multiple Cookies or Custom Options\n\nFor complex scenarios like adding multiple cookies at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.context().addCookies([\n    { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true },\n    { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' }\n  ]);\n}\"\n```\n\n## Local Storage\n\n### List All localStorage Items\n\n```bash\nplaywright-cli localstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli localstorage-get token\n```\n\n### Set Value\n\n```bash\nplaywright-cli localstorage-set theme dark\n```\n\n### Set JSON Value\n\n```bash\nplaywright-cli localstorage-set user_settings '{\"theme\":\"dark\",\"language\":\"en\"}'\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli localstorage-delete token\n```\n\n### Clear All localStorage\n\n```bash\nplaywright-cli localstorage-clear\n```\n\n### Advanced: Multiple Operations\n\nFor complex scenarios like setting multiple values at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    localStorage.setItem('token', 'jwt_abc123');\n    localStorage.setItem('user_id', '12345');\n    localStorage.setItem('expires_at', Date.now() + 3600000);\n  });\n}\"\n```\n\n## Session Storage\n\n### List All sessionStorage Items\n\n```bash\nplaywright-cli sessionstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli sessionstorage-get form_data\n```\n\n### Set Value\n\n```bash\nplaywright-cli sessionstorage-set step 3\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli sessionstorage-delete step\n```\n\n### Clear sessionStorage\n\n```bash\nplaywright-cli sessionstorage-clear\n```\n\n## IndexedDB\n\n### List Databases\n\n```bash\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(async () => {\n    const databases = await indexedDB.databases();\n    return databases;\n  });\n}\"\n```\n\n### Delete Database\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    indexedDB.deleteDatabase('myDatabase');\n  });\n}\"\n```\n\n## Common Patterns\n\n### Authentication State Reuse\n\n```bash\n# Step 1: Login and save state\nplaywright-cli open https://app.example.com/login\nplaywright-cli snapshot\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\n\n# Save the authenticated state\nplaywright-cli state-save auth.json\n\n# Step 2: Later, restore state and skip login\nplaywright-cli state-load auth.json\nplaywright-cli open https://app.example.com/dashboard\n# Already logged in!\n```\n\n### Save and Restore Roundtrip\n\n```bash\n# Set up authentication state\nplaywright-cli open https://example.com\nplaywright-cli eval \"() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }\"\n\n# Save state to file\nplaywright-cli state-save my-session.json\n\n# ... later, in a new session ...\n\n# Restore state\nplaywright-cli state-load my-session.json\nplaywright-cli open https://example.com\n# Cookies and localStorage are restored!\n```\n\n## Security Notes\n\n- Never commit storage state files containing auth tokens\n- Add `*.auth-state.json` to `.gitignore`\n- Delete state files after automation completes\n- Use environment variables for sensitive data\n- By default, sessions run in-memory mode which is safer for sensitive operations\n","isInternal":false,"tokens":1249,"sizeBytes":5198},{"name":"test-generation.md","path":".claude/skills/playwright-cli/references/test-generation.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/test-generation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Test generation (plan → generate → heal)\n\nEnd-to-end workflow for authoring and maintaining Playwright tests with `playwright-cli`. Every `playwright-cli` action emits the equivalent Playwright TypeScript, and that generated code is the raw material for every test. The sections below can be used independently:\n\n- **How generation works** — the core mechanic everything else relies on: actions become TypeScript, plus how to add assertions.\n- **Plan** — explore the app, produce a spec file describing what to test.\n- **Generate** — turn a spec into Playwright test files. Update the spec if it's vague or stale.\n- **Heal** — diagnose failing tests, fix the code, reconcile the spec with reality.\n\nPlan / generate / heal lean on the same mechanic: run `npx playwright test --debug=cli` in the background, then `playwright-cli attach tw-XXXX` to drive the paused page interactively. See [playwright-tests.md](playwright-tests.md) for the debug/attach mechanics.\n\n---\n\n## 0. How generation works\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. This code appears in the output and can be copied directly into your test files.\n\n```bash\n# Start a session\nplaywright-cli open https://example.com/login\n\n# Take a snapshot to see elements\nplaywright-cli snapshot\n# Output shows: e1 [textbox \"Email\"], e2 [textbox \"Password\"], e3 [button \"Sign In\"]\n\n# Fill form fields - generates code automatically\nplaywright-cli fill e1 \"user@example.com\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n\nplaywright-cli fill e2 \"password123\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n\nplaywright-cli click e3\n# Ran Playwright code:\n# await page.getByRole('button', { name: 'Sign In' }).click();\n```\n\n### Building a test file\n\nCollect the generated code into a Playwright test:\n\n```typescript\nimport { test, expect } from '@playwright/test';\n\ntest('login flow', async ({ page }) => {\n  // Generated code from playwright-cli session:\n  await page.goto('https://example.com/login');\n  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n  await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n  await page.getByRole('button', { name: 'Sign In' }).click();\n\n  // Add assertions\n  await expect(page).toHaveURL(/.*dashboard/);\n});\n```\n\n### Use semantic locators\n\nThe generated code uses role-based locators when possible, which are more resilient:\n\n```typescript\n// Generated (good - semantic)\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Avoid (fragile - CSS selectors)\nawait page.locator('#submit-btn').click();\n```\n\n### Explore before recording\n\nTake snapshots to understand the page structure before recording actions:\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli snapshot\n# Review the element structure\nplaywright-cli click e5\n```\n\n### Add assertions manually\n\nGenerated code captures actions but not assertions. Add expectations in your test using one of the recommended matchers:\n\n- `toBeVisible()` — element is rendered and visible\n- `toHaveText(text)` — element text content matches\n- `toHaveValue(value) / toBeEmpty()` — input/select value matches\n- `toBeChecked() / toBeUnchecked()` — checkbox state matches\n- `toMatchAriaSnapshot(snapshot)` — page (or locator) matches a partial accessibility snapshot\n\nUse `playwright-cli generate-locator <target>` to produce the locator expression for the assertion, and the snapshot/eval commands to capture the expected value.\n\nWhen asserting text content, make sure that generated locator does not contain text from the element itself. `getByTestId()` or `getByLabel()` usually work well with asserting text. When locator is text-based, prefer `toBeVisible()` instead.\n\nSnapshot to be matched does not have to contain all the information - only capture what's necessary for the assertion. You can use regular expressions for unstable values.\n\n```bash\n# Get a stable locator for an element ref to use in the assertion\nplaywright-cli --raw generate-locator e5\n# getByRole('button', { name: 'Submit' })\n\n# Capture expected text content for toHaveText\nplaywright-cli --raw eval \"el => el.textContent\" e5\n\n# Capture expected input value for toHaveValue/toBeEmpty\nplaywright-cli --raw eval \"el => el.value\" e5\n\n# Capture expected aria snapshot for toMatchAriaSnapshot/toBeChecked\n# (whole page, or use a ref to scope to a region)\nplaywright-cli --raw snapshot\nplaywright-cli --raw snapshot e5\n```\n\n```typescript\n// Generated action\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Manual assertions using the outputs above:\nawait expect(page.getByRole('alert', { name: 'Success' })).toBeVisible();\nawait expect(page.getByTestId('main-header')).toHaveText('Welcome, user');\nawait expect(page.getByRole('textbox', { name: 'Email' })).toHaveValue('user@example.com');\nawait expect(page.getByRole('checkbox', { name: 'Enable notifications' })).toBeChecked();\n\n// toMatchAriaSnapshot on the whole page, finds a matching region\nawait expect(page).toMatchAriaSnapshot(`\n  - heading \"Welcome, user\"\n  - link /\\\\d+ new messages?/\n  - button \"Sign out\"\n`);\n\n// toMatchAriaSnapshot scoped to a region\nawait expect(page.getByRole('navigation')).toMatchAriaSnapshot(`\n  - link \"Home\"\n  - link /\\\\d+ new messages?/\n  - link \"Profile\"\n`);\n```\n\n---\n\n## 1. Planning\n\nGoal: produce a spec file (e.g. `specs/<feature>.plan.md`) that enumerates the scenarios to test. **Always** write the spec to a file.\n\n### 1.1 Prerequisite: workspace\n\nCheck the workspace has Playwright installed before anything else:\n\n```bash\n# Either of these confirms a workspace:\ntest -f playwright.config.ts || test -f playwright.config.js\nnpx --no-install playwright --version\n```\n\nIf there is no Playwright install, bootstrap one and let the user pick the defaults:\n\n```bash\nnpm init playwright@latest\n```\n\n### 1.2 Prerequisite: seed test\n\nA **seed test** is a minimal test that lands the page in the state every scenario starts from: navigation to the app, any required login, feature flags, etc. Scenarios assume a fresh start *after* the seed. `--debug=cli` pauses *inside* this test, so the seed is where every planning and generation session begins.\n\nMinimum viable seed:\n\n```ts\n// tests/seed.spec.ts\nimport { test } from '@playwright/test';\n\ntest('seed', async ({ page }) => {\n  await page.goto('https://example.com/');\n});\n```\n\nPreferred — push navigation into a fixture so scenario tests reuse it:\n\n```ts\n// tests/fixtures.ts\nimport { test as baseTest } from '@playwright/test';\nexport { expect } from '@playwright/test';\n\nexport const test = baseTest.extend({\n  page: async ({ page }, use) => {\n    await page.goto('https://example.com/');\n    await use(page);\n  },\n});\n```\n\n```ts\n// tests/seed.spec.ts\nimport { test } from './fixtures';\n\ntest('seed', async ({ page }) => {\n  // Fixture already navigates. This empty body tells agents where to start.\n});\n```\n\nIf no seed exists, create one that at least navigates to the app.\n\n### 1.3 Explore the app\n\nLaunch the app via the seed in the background and attach:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/seed.spec.ts --debug=cli\n# wait for \"Debugging Instructions\" and the session name tw-XXXX\nplaywright-cli attach tw-XXXX\n```\n\nResume so the seed runs, then probe the app:\n\n```bash\nplaywright-cli resume                   # resume so that seed test runs fully\nplaywright-cli snapshot                 # inventory of interactive elements\nplaywright-cli click e5                 # follow a flow\nplaywright-cli eval \"location.href\"     # read URL / state\nplaywright-cli show --annotate          # ask the user to point at something\n```\n\nMap out:\n\n- Interactive surfaces (forms, buttons, lists, filters, modals).\n- Primary user journeys end-to-end.\n- Edge cases: empty states, validation errors, very long input, boundary values.\n- Persistence: reload, local/session storage, URL fragments.\n- Navigation: which controls change the URL, back/forward behaviour.\n\n**Important**: Do not just open the app url with playwright-cli, always go through the test to capture any custom setup done there.\n**Important**: Stop the background test when done exploring.\n\n### 1.4 Write the spec file\n\nSave under `specs/<feature>.plan.md`. Use this structure:\n\n```markdown\n# <Feature> Test Plan\n\n## Application Overview\n\n<One paragraph describing what the feature does and why it matters.>\n\n## Test Scenarios\n\n### 1. <Group Name>\n\n**Seed:** `tests/seed.spec.ts`\n\n#### 1.1. <kebab-case-scenario-name>\n\n**File:** `tests/<group>/<kebab-case-scenario-name>.spec.ts`\n\n**Steps:**\n  1. <Concrete user step>\n    - expect: <observable outcome>\n    - expect: <another observable outcome>\n  2. <Next step>\n    - expect: <outcome>\n\n#### 1.2. <next-scenario>\n...\n\n### 2. <Next Group>\n\n**Seed:** `tests/seed.spec.ts`\n...\n```\n\nGuidelines:\n\n- Each scenario is independent and starts from the seed's fresh state — never chain scenarios.\n- Scenario names are kebab-case and match the test file name (`should-add-single-todo` → `should-add-single-todo.spec.ts`).\n- Cover happy path, edge cases, validation, negative flows, persistence.\n- Write steps at the user level (\"Type 'Buy milk' into the input\"), not the API level (\"call `fill`\").\n- Put observable outcomes in `- expect:` bullets; each becomes an assertion during generation.\n\n---\n\n## 2. Generate\n\nGoal: take a spec file and produce Playwright test files. Optionally update the spec if it has drifted.\n\n### 2.1 Inputs\n\n- **Spec file**, e.g. `specs/basic-operations.plan.md`.\n- **Target**: either a single scenario (e.g. `1.2`), a whole group (`1`), or all.\n- **Seed file**, read from the `**Seed:**` line of the scenario's group.\n\n### 2.2 Generate one scenario\n\nFor each target scenario, in sequence (never in parallel — scenarios share the seed session):\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test <seed-file> --debug=cli   # background\nplaywright-cli attach tw-XXXX\n# resume\n```\n\n**Do not** just open the app url with playwright-cli, always go through the test to capture any custom setup done there.\n\nWalk the scenario's `Steps:` one by one with `playwright-cli`, treating the spec as the plan and the live app as the source of truth. If a step is vague (\"click the button\" — which button?), references an element that no longer exists, or contradicts the app's actual behaviour, use your judgement: update the spec to match what the app really does, then keep going. Editing the spec mid-generation is expected.\n\nEvery action prints the equivalent Playwright TypeScript (see [How generation works](#0-how-generation-works)):\n\n```bash\nplaywright-cli snapshot                         # find refs\nplaywright-cli fill e3 \"John Doe\"               # -> page.getByRole('textbox', {...}).fill(...)\nplaywright-cli press Enter\nplaywright-cli click e7\n```\n\nFor each `- expect:` bullet, add an explicit assertion. See [How generation works](#0-how-generation-works) for details.\n\nCollect the generated code and write the test file at the path given in the spec:\n\n```ts\n// spec: specs/basic-operations.plan.md\n// seed: tests/seed.spec.ts\nimport { test, expect } from './fixtures';   // or '@playwright/test' if no fixtures file\n\ntest.describe('Signing in and out', () => {\n  test('should sign in', async ({ page }) => {\n    // 1. Navigate to the application\n    // (handled by the seed fixture)\n\n    // 2. Type 'John Doe' into the username field\n    await page.getByRole('textbox', { name: 'username' }).fill('John Doe');\n\n    // 3. Type password\n    await page.getByRole('textbox', { name: 'password' }).fill('TestPassword');\n\n    // 4. Press Enter to submit\n    await page.getByRole('textbox', { name: 'password' }).press('Enter');\n\n    await expect(page.getByRole('heading')).toContainText('Welcome, John Doe!');\n  });\n});\n```\n\nRules:\n\n- **One test per file.** File path, describe name, and test name come verbatim from the spec (minus the ordinal).\n- Prefix each numbered step with a `// N. <step text>` comment before its actions.\n- Use the describe group name verbatim from the spec (no `1.` ordinal).\n- Import from `./fixtures` if the project has one; otherwise `@playwright/test`.\n- **Important**: close the CLI session and stop the background test before moving to the next scenario.\n\n### 2.3 Generate multiple scenarios\n\nLoop 2.2 over the targeted scenarios one at a time, restarting the seed between each so every test starts from a clean page. This is safe to parallelise due to unique generated session names - just make sure each test run is stopped.\n\n### 2.4 Run generated tests\n\nAfter generation, run the new tests once:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/<group>/<scenario>.spec.ts\n```\n\nAny failure goes to Section 3.\n\n---\n\n## 3. Heal\n\nGoal: fix failing tests, and update the spec if the app's intended behaviour changed.\n\n### 3.1 Find failing tests\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test\n```\n\nRecord the list of failing `<file>:<line>` entries and process them one at a time. Do not attempt parallel fixes — shared state and the single CLI session make that fragile.\n\n### 3.2 Debug one failure\n\nRun the single failing test in debug mode in the background, then attach:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/<group>/<scenario>.spec.ts:<line> --debug=cli\n# wait for \"Debugging Instructions\" and the tw-XXXX session name\nplaywright-cli attach tw-XXXX\n```\n\nThe test is paused at the start. Step forward or run to until just before the failing action or assertion, then diagnose:\n\n```bash\nplaywright-cli snapshot                # did the element change / move / rename?\nplaywright-cli console                 # app-side errors?\nplaywright-cli requests                # failed request? wrong payload?\nplaywright-cli show --annotate         # ask the user to point somewhere\n```\n\nCommon causes: selector drift, new wrapper element, label/ARIA rename, timing (transition, async load), assertion text updated in the app, test data leaking between runs.\n\nRehearse the corrected interaction with `playwright-cli` — the generated code in the output is what you paste back into the test.\n\n### 3.3 Apply the fix\n\nEdit the test file: update the locator, assertion, step order, or inputs to match the corrected behaviour. Stop the background debug run. Rerun the single test to confirm green.\n\nNever skip hooks or add sleeps as a fix. Never use `networkidle`.\n\n### 3.4 Reconcile with the spec\n\nOpen the spec referenced by the `// spec:` header in the test file and locate the scenario that matches the test.\n\n- **Fix was purely technical** (locator drift, better assertion shape) and the spec's user-level behaviour still matches the app → leave the spec alone.\n- **Fix changed user-visible steps, inputs, order, or expected outcomes** that the spec describes → update the spec to match reality. Keep the scenario id and file path stable; only the step / expect lines change.\n- **Unclear whether the app change is intentional** (spec is stale) **or a regression** (test was right, app is wrong) → **stop and ask the user**. Provide:\n  - the scenario id (e.g. `2.3`),\n  - the spec lines that no longer match,\n  - the observed app behaviour (quote a snapshot excerpt or a concrete outcome).\n\nOnly after the user answers, either update the spec (intentional change) or file/flag the test as covering a bug (regression).\n\n### 3.5 Iteration and giving up\n\n- Fix failures one at a time; rerun after each.\n- If after thorough investigation you are confident the test is correct but the app is wrong *and* the user has confirmed it's a bug: mark the test `test.fixme(...)` with a comment pointing at the user's decision or issue link. Never silently skip.\n\n---\n\n## Cross-references\n\n| For... | See |\n|---|---|\n| `--debug=cli` / attach mechanics | [playwright-tests.md](playwright-tests.md) |\n| Mocking requests during exploration/generation | [request-mocking.md](request-mocking.md) |\n| Managing the CLI browser session | [session-management.md](session-management.md) |\n","isInternal":false,"tokens":3792,"sizeBytes":16114},{"name":"tracing.md","path":".claude/skills/playwright-cli/references/tracing.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/tracing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Tracing\n\nCapture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.\n\n## Basic Usage\n\n```bash\n# Start trace recording\nplaywright-cli tracing-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli click e1\nplaywright-cli fill e2 \"test\"\n\n# Stop trace recording\nplaywright-cli tracing-stop\n```\n\n## Trace Output Files\n\nWhen you start tracing, Playwright creates a `traces/` directory with several files:\n\n### `trace-{timestamp}.trace`\n\n**Action log** - The main trace file containing:\n- Every action performed (clicks, fills, navigations)\n- DOM snapshots before and after each action\n- Screenshots at each step\n- Timing information\n- Console messages\n- Source locations\n\n### `trace-{timestamp}.network`\n\n**Network log** - Complete network activity:\n- All HTTP requests and responses\n- Request headers and bodies\n- Response headers and bodies\n- Timing (DNS, connect, TLS, TTFB, download)\n- Resource sizes\n- Failed requests and errors\n\n### `resources/`\n\n**Resources directory** - Cached resources:\n- Images, fonts, stylesheets, scripts\n- Response bodies for replay\n- Assets needed to reconstruct page state\n\n## What Traces Capture\n\n| Category | Details |\n|----------|---------|\n| **Actions** | Clicks, fills, hovers, keyboard input, navigations |\n| **DOM** | Full DOM snapshot before/after each action |\n| **Screenshots** | Visual state at each step |\n| **Network** | All requests, responses, headers, bodies, timing |\n| **Console** | All console.log, warn, error messages |\n| **Timing** | Precise timing for each operation |\n\n## Use Cases\n\n### Debugging Failed Actions\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://app.example.com\n\n# This click fails - why?\nplaywright-cli click e5\n\nplaywright-cli tracing-stop\n# Open trace to see DOM state when click was attempted\n```\n\n### Analyzing Performance\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://slow-site.com\nplaywright-cli tracing-stop\n\n# View network waterfall to identify slow resources\n```\n\n### Capturing Evidence\n\n```bash\n# Record a complete user flow for documentation\nplaywright-cli tracing-start\n\nplaywright-cli open https://app.example.com/checkout\nplaywright-cli fill e1 \"4111111111111111\"\nplaywright-cli fill e2 \"12/25\"\nplaywright-cli fill e3 \"123\"\nplaywright-cli click e4\n\nplaywright-cli tracing-stop\n# Trace shows exact sequence of events\n```\n\n## Trace vs Video vs Screenshot\n\n| Feature | Trace | Video | Screenshot |\n|---------|-------|-------|------------|\n| **Format** | .trace file | .webm video | .png/.jpeg image |\n| **DOM inspection** | Yes | No | No |\n| **Network details** | Yes | No | No |\n| **Step-by-step replay** | Yes | Continuous | Single frame |\n| **File size** | Medium | Large | Small |\n| **Best for** | Debugging | Demos | Quick capture |\n\n## Best Practices\n\n### 1. Start Tracing Before the Problem\n\n```bash\n# Trace the entire flow, not just the failing step\nplaywright-cli tracing-start\nplaywright-cli open https://example.com\n# ... all steps leading to the issue ...\nplaywright-cli tracing-stop\n```\n\n### 2. Clean Up Old Traces\n\nTraces can consume significant disk space:\n\n```bash\n# Remove traces older than 7 days\nfind .playwright-cli/traces -mtime +7 -delete\n```\n\n## Limitations\n\n- Traces add overhead to automation\n- Large traces can consume significant disk space\n- Some dynamic content may not replay perfectly\n","isInternal":false,"tokens":820,"sizeBytes":3440},{"name":"video-recording.md","path":".claude/skills/playwright-cli/references/video-recording.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/references/video-recording.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Video Recording\n\nCapture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).\n\n## Basic Recording\n\n```bash\n# Open browser first\nplaywright-cli open\n\n# Start recording\nplaywright-cli video-start demo.webm\n\n# Add a chapter marker for section transitions\nplaywright-cli video-chapter \"Getting Started\" --description=\"Opening the homepage\" --duration=2000\n\n# Navigate and perform actions\nplaywright-cli goto https://example.com\nplaywright-cli snapshot\nplaywright-cli click e1\n\n# Add another chapter\nplaywright-cli video-chapter \"Filling Form\" --description=\"Entering test data\" --duration=2000\nplaywright-cli fill e2 \"test input\"\n\n# Stop and save\nplaywright-cli video-stop\n```\n\n## Best Practices\n\n### 1. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nplaywright-cli video-start recordings/login-flow-2024-01-15.webm\nplaywright-cli video-start recordings/checkout-test-run-42.webm\n```\n\n### 2. Record entire hero scripts.\n\nWhen recording a video for the user or as a proof of work, it is best to create a code snippet and execute it with run-code.\nIt allows inserting appropriate pauses between the actions and annotating the video. There are new Playwright APIs for that.\n\n1) Perform scenario using CLI and take note of all locators and actions. You'll need those locators to request their bounding boxes for highlight.\n2) Create a file with the intended script for video (below). Use pressSequentially w/ delay for nice typing, make reasonable pauses.\n3) Use playwright-cli run-code --filename your-script.js\n\n**Important**: Overlays are `pointer-events: none` — they do not interfere with page interactions. You can safely keep sticky overlays visible while clicking, filling, or performing any actions on the page.\n\n```js\nasync page => {\n  await page.screencast.start({ path: 'video.webm', size: { width: 1280, height: 800 } });\n  await page.goto('https://demo.playwright.dev/todomvc');\n\n  // Show a chapter card — blurs the page and shows a dialog.\n  // Blocks until duration expires, then auto-removes.\n  // Use this for simple use cases, but always feel free to hand-craft your own beautiful\n  // overlay via await page.screencast.showOverlay().\n  await page.screencast.showChapter('Adding Todo Items', {\n    description: 'We will add several items to the todo list.',\n    duration: 2000,\n  });\n\n  // Perform action\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Walk the dog', { delay: 60 });\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');\n  await page.waitForTimeout(1000);\n\n  // Show next chapter\n  await page.screencast.showChapter('Verifying Results', {\n    description: 'Checking the item appeared in the list.',\n    duration: 2000,\n  });\n\n  // Add a sticky annotation that stays while you perform actions.\n  // Overlays are pointer-events: none, so they won't block clicks.\n  const annotation = await page.screencast.showOverlay(`\n    <div style=\"position: absolute; top: 8px; right: 8px;\n      padding: 6px 12px; background: rgba(0,0,0,0.7);\n      border-radius: 8px; font-size: 13px; color: white;\">\n      ✓ Item added successfully\n    </div>\n  `);\n\n  // Perform more actions while the annotation is visible\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Buy groceries', { delay: 60 });\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');\n  await page.waitForTimeout(1500);\n\n  // Remove the annotation when done\n  await annotation.dispose();\n\n  // You can also highlight relevant locators and provide contextual annotations.\n  const bounds = await page.getByText('Walk the dog').boundingBox();\n  await page.screencast.showOverlay(`\n    <div style=\"position: absolute;\n      top: ${bounds.y}px;\n      left: ${bounds.x}px;\n      width: ${bounds.width}px;\n      height: ${bounds.height}px;\n      border: 1px solid red;\">\n    </div>\n    <div style=\"position: absolute;\n      top: ${bounds.y + bounds.height + 5}px;\n      left: ${bounds.x + bounds.width / 2}px;\n      transform: translateX(-50%);\n      padding: 6px;\n      background: #808080;\n      border-radius: 10px;\n      font-size: 14px;\n      color: white;\">Check it out, it is right above this text\n    </div>\n  `, { duration: 2000 });\n\n  await page.screencast.stop();\n}\n```\n\nEmbrace creativity, overlays are powerful.\n\n### Overlay API Summary\n\n| Method | Use Case |\n|--------|----------|\n| `page.screencast.showChapter(title, { description?, duration?, styleSheet? })` | Full-screen chapter card with blurred backdrop — ideal for section transitions |\n| `page.screencast.showOverlay(html, { duration? })` | Custom HTML overlay — use for callouts, labels, highlights |\n| `disposable.dispose()` | Remove a sticky overlay added without duration |\n| `page.screencast.hideOverlays()` / `page.screencast.showOverlays()` | Temporarily hide/show all overlays |\n\n## Tracing vs Video\n\n| Feature | Video | Tracing |\n|---------|-------|---------|\n| Output | WebM file | Trace file (viewable in Trace Viewer) |\n| Shows | Visual recording | DOM snapshots, network, console, actions |\n| Use case | Demos, documentation | Debugging, analysis |\n| Size | Larger | Smaller |\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n","isInternal":false,"tokens":1317,"sizeBytes":5387},{"name":"SKILL.md","path":".claude/skills/playwright-cli/SKILL.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/playwright-cli/SKILL.md","title":"playwright-cli","category":"anthropic-skill","format":"markdown","content":"---\nname: playwright-cli\ndescription: Automate browser interactions, test web pages and work with Playwright tests.\nallowed-tools: Bash(playwright-cli:*) Bash(npx:*) Bash(npm:*)\n---\n\n# Browser Automation with playwright-cli\n\n## Quick start\n\n```bash\n# open new browser\nplaywright-cli open\n# navigate to a page\nplaywright-cli goto https://playwright.dev\n# interact with the page using refs from the snapshot\nplaywright-cli click e15\nplaywright-cli type \"page.click\"\nplaywright-cli press Enter\n# take a screenshot (rarely used, as snapshot is more common)\nplaywright-cli screenshot\n# close the browser\nplaywright-cli close\n```\n\n## Commands\n\n### Core\n\n```bash\nplaywright-cli open\n# open and navigate right away\nplaywright-cli open https://example.com/\nplaywright-cli goto https://playwright.dev\nplaywright-cli type \"search query\"\nplaywright-cli click e3\nplaywright-cli dblclick e7\n# --submit presses Enter after filling the element\nplaywright-cli fill e5 \"user@example.com\"  --submit\nplaywright-cli drag e2 e8\n# drop files or data onto an element (from outside the page)\nplaywright-cli drop e4 --path=./image.png\nplaywright-cli drop e4 --data=\"text/plain=hello world\"\nplaywright-cli hover e4\nplaywright-cli select e9 \"option-value\"\nplaywright-cli upload ./document.pdf\nplaywright-cli check e12\nplaywright-cli uncheck e12\nplaywright-cli snapshot\n# search the snapshot for text or a regexp, returns matching nodes with surrounding context\nplaywright-cli find \"Sign in\"\nplaywright-cli find --regex \"Sign (in|up)\"\n# wrap the regexp in slashes to add flags, e.g. /i for case-insensitive\nplaywright-cli find --regex \"/sign (in|up)/i\"\nplaywright-cli eval \"document.title\"\nplaywright-cli eval \"el => el.textContent\" e5\n# get element id, class, or any attribute not visible in the snapshot\nplaywright-cli eval \"el => el.id\" e5\nplaywright-cli eval \"el => el.getAttribute('data-testid')\" e5\nplaywright-cli dialog-accept\nplaywright-cli dialog-accept \"confirmation text\"\nplaywright-cli dialog-dismiss\nplaywright-cli resize 1920 1080\nplaywright-cli close\n```\n\n### Navigation\n\n```bash\nplaywright-cli go-back\nplaywright-cli go-forward\nplaywright-cli reload\n```\n\n### Keyboard\n\n```bash\nplaywright-cli press Enter\nplaywright-cli press ArrowDown\nplaywright-cli keydown Shift\nplaywright-cli keyup Shift\n```\n\n### Mouse\n\n```bash\nplaywright-cli mousemove 150 300\nplaywright-cli mousedown\nplaywright-cli mousedown right\nplaywright-cli mouseup\nplaywright-cli mouseup right\nplaywright-cli mousewheel 0 100\n```\n\n### Save as\n\n```bash\nplaywright-cli screenshot\nplaywright-cli screenshot e5\nplaywright-cli screenshot --filename=page.png\nplaywright-cli screenshot --hires\nplaywright-cli pdf --filename=page.pdf\n```\n\n### Tabs\n\n```bash\nplaywright-cli tab-list\nplaywright-cli tab-new\nplaywright-cli tab-new https://example.com/page\nplaywright-cli tab-close\nplaywright-cli tab-close 2\nplaywright-cli tab-select 0\n```\n\n### Storage\n\n```bash\nplaywright-cli state-save\nplaywright-cli state-save auth.json\nplaywright-cli state-load auth.json\n\n# Cookies\nplaywright-cli cookie-list\nplaywright-cli cookie-list --domain=example.com\nplaywright-cli cookie-get session_id\nplaywright-cli cookie-set session_id abc123\nplaywright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure\nplaywright-cli cookie-delete session_id\nplaywright-cli cookie-clear\n\n# LocalStorage\nplaywright-cli localstorage-list\nplaywright-cli localstorage-get theme\nplaywright-cli localstorage-set theme dark\nplaywright-cli localstorage-delete theme\nplaywright-cli localstorage-clear\n\n# SessionStorage\nplaywright-cli sessionstorage-list\nplaywright-cli sessionstorage-get step\nplaywright-cli sessionstorage-set step 3\nplaywright-cli sessionstorage-delete step\nplaywright-cli sessionstorage-clear\n```\n\n### Network\n\n```bash\nplaywright-cli route \"**/*.jpg\" --status=404\nplaywright-cli route \"https://api.example.com/**\" --body='{\"mock\": true}'\nplaywright-cli route-list\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n### DevTools\n\n```bash\nplaywright-cli console\nplaywright-cli console warning\nplaywright-cli requests\nplaywright-cli request 5\nplaywright-cli run-code \"async page => await page.context().grantPermissions(['geolocation'])\"\nplaywright-cli run-code --filename=script.js\nplaywright-cli tracing-start\nplaywright-cli tracing-stop\nplaywright-cli video-start video.webm\nplaywright-cli video-chapter \"Chapter Title\" --description=\"Details\" --duration=2000\nplaywright-cli video-stop\n\n# annotate each subsequent action (click, type, ...) with a callout naming the action and highlighting the target\nplaywright-cli video-show-actions --duration=600 --position=top-right\nplaywright-cli video-hide-actions\n\n# launch the dashboard for UI review / design feedback — user annotates the page, you receive the annotated screenshot, snapshot, and notes\nplaywright-cli show --annotate\n\n# generate a Playwright locator for an element from its ref or selector\nplaywright-cli generate-locator e5 --raw\n\n# show a persistent highlight overlay for an element, optionally with a custom style\nplaywright-cli highlight e5\nplaywright-cli highlight e5 --style=\"outline: 3px dashed red\"\n# hide a single element highlight, or all page highlights when no target is given\nplaywright-cli highlight e5 --hide\nplaywright-cli highlight --hide\n```\n\n## Raw output\n\nThe global `--raw` option strips page status, generated code, and snapshot sections from the output, returning only the result value. Use it to pipe command output into other tools. Commands that don't produce output return nothing.\n\n```bash\nplaywright-cli --raw eval \"JSON.stringify(performance.timing)\" | jq '.loadEventEnd - .navigationStart'\nplaywright-cli --raw eval \"JSON.stringify([...document.querySelectorAll('a')].map(a => a.href))\" > links.json\nplaywright-cli --raw snapshot > before.yml\nplaywright-cli click e5\nplaywright-cli --raw snapshot > after.yml\ndiff before.yml after.yml\nTOKEN=$(playwright-cli --raw cookie-get session_id)\nplaywright-cli --raw localstorage-get theme\n```\n\nFor structured output wrapping every reply as JSON, pass --json\n```bash\nplaywright-cli list --json\n```\n\n## Open parameters\n```bash\n# Use specific browser when creating session\nplaywright-cli open --browser=chrome\nplaywright-cli open --browser=firefox\nplaywright-cli open --browser=webkit\nplaywright-cli open --browser=msedge\n\n# Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit).\n# Prefer this when a mobile layout is acceptable: mobile pages are usually\n# lighter, so snapshots are smaller and cheaper.\nplaywright-cli open --mobile\nplaywright-cli open --device=\"iPhone 15\"\n\n# Use persistent profile (by default profile is in-memory)\nplaywright-cli open --persistent\n# Use persistent profile with custom directory\nplaywright-cli open --profile=/path/to/profile\n\n# Connect to browser via Playwright Extension\nplaywright-cli attach --extension=chrome\n\n# Connect to a running Chrome or Edge by channel name\nplaywright-cli attach --cdp=chrome\nplaywright-cli attach --cdp=msedge\n\n# Connect to a running browser via CDP endpoint\nplaywright-cli attach --cdp=http://localhost:9222\n\n# Start with config file\nplaywright-cli open --config=my-config.json\n\n# Close the browser\nplaywright-cli close\n# Detach from an attached browser (leaves the external browser running)\nplaywright-cli -s=msedge detach\n# Delete user data for the default session\nplaywright-cli delete-data\n```\n\n## URLs with `&` on Windows\n\nOn Windows, `cmd.exe` and PowerShell treat `&` as a command separator, so URLs with multiple query parameters get truncated before `playwright-cli` runs. Escape `&` with `^&` in `cmd.exe`, or use `--%` in PowerShell:\n\n```batch\nplaywright-cli goto \"https://example.com/?a=1^&b=2\"\n```\n\n```powershell\nplaywright-cli --% goto \"https://example.com/?a=1&b=2\"\n```\n\n## Snapshots\n\nAfter each command, playwright-cli provides a snapshot of the current browser state.\n\n```bash\n> playwright-cli goto https://example.com\n### Page\n- Page URL: https://example.com/\n- Page Title: Example Domain\n### Snapshot\n[Snapshot](.playwright-cli/page-2026-02-14T19-22-42-679Z.yml)\n```\n\nYou can also take a snapshot on demand using `playwright-cli snapshot` command. All the options below can be combined as needed.\n\n```bash\n# default - save to a file with timestamp-based name\nplaywright-cli snapshot\n\n# save to file, use when snapshot is a part of the workflow result\nplaywright-cli snapshot --filename=after-click.yaml\n\n# snapshot an element instead of the whole page\nplaywright-cli snapshot \"#main\"\n\n# limit snapshot depth for efficiency, take a partial snapshot afterwards\nplaywright-cli snapshot --depth=4\nplaywright-cli snapshot e34\n\n# include each element's bounding box as [box=x,y,width,height]\nplaywright-cli snapshot --boxes\n\n# search a large snapshot instead of capturing it all — returns matching nodes\n# with 3 lines of context around each match (like grep -C)\nplaywright-cli find \"Add to cart\"\nplaywright-cli find --regex \"\\\\$[0-9]+\\\\.[0-9]{2}\"\n```\n\n## Targeting elements\n\nBy default, use refs from the snapshot to interact with page elements.\n\n```bash\n# get snapshot with refs\nplaywright-cli snapshot\n\n# interact using a ref\nplaywright-cli click e15\n```\n\nYou can also use css selectors or Playwright locators.\n\n```bash\n# css selector\nplaywright-cli click \"#main > button.submit\"\n\n# role locator\nplaywright-cli click \"getByRole('button', { name: 'Submit' })\"\n\n# test id\nplaywright-cli click \"getByTestId('submit-button')\"\n```\n\n## Browser Sessions\n\n```bash\n# create new browser session named \"mysession\" with persistent profile\nplaywright-cli -s=mysession open example.com --persistent\n# same with manually specified profile directory (use when requested explicitly)\nplaywright-cli -s=mysession open example.com --profile=/path/to/profile\nplaywright-cli -s=mysession click e6\nplaywright-cli -s=mysession close  # stop a named browser\nplaywright-cli -s=mysession delete-data  # delete user data for persistent session\n\nplaywright-cli list\n# Close all browsers\nplaywright-cli close-all\n# Forcefully kill all browser processes\nplaywright-cli kill-all\n```\n\n## Installation\n\nIf global `playwright-cli` command is not available, try a local version via `npx playwright cli`:\n\n```bash\nnpx --no-install playwright --version\n```\n\nWhen local version is available, use `npx playwright cli` in all commands. Otherwise, install `playwright-cli` as a global command:\n\n```bash\nnpm install -g @playwright/cli@latest\n```\n\n## Example: Form submission\n\n```bash\nplaywright-cli open https://example.com/form\nplaywright-cli snapshot\n\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Multi-tab workflow\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tab-new https://example.com/other\nplaywright-cli tab-list\nplaywright-cli tab-select 0\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Debugging with DevTools\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli console\nplaywright-cli requests\nplaywright-cli close\n```\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tracing-start\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli tracing-stop\nplaywright-cli close\n```\n\n## Example: Interactive session\n\nAsk the user for UI review or design feedback. The user draws boxes on the live page and types comments; you receive the annotated screenshot, the snapshot of the marked region, and the user's notes. Use this whenever the user asks for \"UI review\", \"design feedback\", or to \"ask the user what they think / want / mean\":\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli show --annotate\n```\n\n## Specific tasks\n\n* **Running and Debugging Playwright tests** [references/playwright-tests.md](references/playwright-tests.md)\n* **Request mocking** [references/request-mocking.md](references/request-mocking.md)\n* **Running Playwright code** [references/running-code.md](references/running-code.md)\n* **Browser session management** [references/session-management.md](references/session-management.md)\n* **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)\n* **Test generation (plan / generate / heal)** [references/test-generation.md](references/test-generation.md)\n* **Tracing** [references/tracing.md](references/tracing.md)\n* **Video recording** [references/video-recording.md](references/video-recording.md)\n* **Inspecting element attributes** [references/element-attributes.md](references/element-attributes.md)\n","frontmatter":{"name":"playwright-cli","description":"Automate browser interactions, test web pages and work with Playwright tests.","allowed-tools":"Bash(playwright-cli:*) Bash(npx:*) Bash(npm:*)","allowedTools":["Bash(playwright-cli:*)","Bash(npx:*)","Bash(npm:*)"]},"allowedTools":["Bash(playwright-cli:*)","Bash(npx:*)","Bash(npm:*)"],"isInternal":false,"tokens":3003,"sizeBytes":12587},{"name":"SKILL.md","path":".claude/skills/release/SKILL.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/release/SKILL.md","title":"release","category":"anthropic-skill","format":"markdown","content":"---\nname: release\ndescription: |\n  Create a release from dev branch. Generates changelog entries from commits,\n  bumps version, and creates a PR to main.\n\n  TRIGGERS - Use this skill when user says:\n  - \"/release\" - create a patch release (default)\n  - \"/release minor\" - create a minor release\n  - \"/release major\" - create a major release\n  - \"make a release\", \"cut a release\", \"ship it\", \"release to main\"\n---\n\n# Release Skill\n\nCreates a release by comparing dev to main, generating changelog entries from commits, bumping the version, and creating a PR. After the tag is pushed and the release workflow finishes building binaries, updates the Homebrew formula with the real SHA256 values from the published `checksums.txt`, syncs the `coleam00/homebrew-archon` tap, and verifies the end-to-end install path via `/test-release`.\n\n> **⚠️ CRITICAL — Homebrew formula SHAs cannot be known until after the release workflow builds binaries.**\n>\n> The `version` field in `homebrew/archon.rb` and the `sha256` fields must be updated **atomically**. Never update one without the other.\n>\n> The correct sequence is:\n> 1. Tag is pushed → release workflow fires → binaries built → `checksums.txt` uploaded\n> 2. Fetch `checksums.txt` from the published release\n> 3. Parse the SHA256 per platform\n> 4. Update `homebrew/archon.rb` with the new version AND the new SHAs in a single commit\n> 5. Sync to the `coleam00/homebrew-archon/Formula/archon.rb` tap repo\n>\n> Updating the formula's `version` field without also updating the `sha256` values creates a stale, misleading formula that looks valid but produces checksum mismatches on install. This has happened before (v0.3.0: version updated to 0.3.0 but SHAs were still from v0.2.13). Always do both or neither.\n\n## Process\n\n### Step 1: Validate State\n\n```bash\n# Must be on dev branch with clean working tree\ngit checkout dev\ngit pull origin dev --no-rebase\ngit status --porcelain  # must be empty\ngit fetch origin main\n```\n\nIf not on dev or working tree is dirty, abort with a clear message.\n\n**Then check that dev actually contains main.** Anything committed directly to\nmain — a docs typo fix, a hotfix, the previous release's formula commit — stays\nstranded there until someone merges it back. The release PR would then propose\n*reverting* it, and the next release inherits the drift.\n\n```bash\nif git merge-base --is-ancestor origin/main origin/dev; then\n  echo \"dev contains main — OK\"\nelse\n  echo \"DRIFT: commits exist on main that dev does not have:\"\n  git log origin/dev..origin/main --oneline\n  echo \"\"\n  echo \"Resync before releasing:\"\n  echo \"  git checkout dev && git pull origin main --no-rebase && git push origin dev\"\n  exit 1\nfi\n```\n\nObserved on the 0.7.1 release: `ae704a73 Update docs.mdx (#2155)` had been\ncommitted straight to main after 0.7.0 and never merged back. Someone had\nnoticed the *content* gap and hand-forward-ported it via a separate PR (#2403),\nso dev had the change but not the commit — and `main` was still not an ancestor\nof `dev`. Resolve this before Step 2; do not carry it into the release PR.\n\n### Step 1.5: Pre-flight compiled-binary smoke test (MANDATORY before any other step)\n\n> **Why this is first**: releases have ended up with zero working binaries because a module-init crash or bundler bug only surfaces in `bun build --compile` output, not in `bun run`. CI catches it — but only AFTER the tag is pushed and a GitHub Release is created. By then the damage (empty release, broken `releases/latest`, broken `install.sh`) is already live. Failing here, before any user-visible change, keeps the blast radius at \"no release was cut.\"\n\nRun locally on the native target. This takes ~15-30s and is cheaper than discovering the problem after tag+release.\n\n```bash\n# Guard: only run this for Node/Bun projects with a CLI entry point + build-binaries script.\nif [ -f scripts/build-binaries.sh ] && [ -f packages/cli/src/cli.ts ]; then\n  TMP_BINARY=$(mktemp)\n  trap \"rm -f $TMP_BINARY\" EXIT\n\n  # Compile for the native target only (not full cross-compile — that's CI's job).\n  # Match the real release flags so any bundler quirk reproduces locally.\n  bun build \\\n    --compile \\\n    --minify \\\n    --target=bun \\\n    --outfile=\"$TMP_BINARY\" \\\n    packages/cli/src/cli.ts\n\n  # Smoke test: the binary must start and exit 0 on a safe, non-interactive command.\n  # Use `--help` (NOT `version`). The `version` command's compiled-binary code\n  # path depends on BUNDLED_IS_BINARY=true, which is set by scripts/build-binaries.sh\n  # — but we're doing a bare `bun build --compile` here to keep the smoke fast,\n  # so BUNDLED_IS_BINARY is still `false`. That sends `version` down the dev\n  # branch of version.ts which tries to read package.json from a path that only\n  # exists in node_modules, producing a false-positive ENOENT. `--help` has no\n  # such dev/binary branch and exercises the same module-init graph we're\n  # actually testing. Must NOT touch network, database, or require env vars.\n  if ! \"$TMP_BINARY\" --help > /tmp/archon-preflight.log 2>&1; then\n    echo \"ERROR: compiled binary crashed at startup\"\n    cat /tmp/archon-preflight.log\n    echo \"\"\n    echo \"This usually means a dependency has a module-init-time side effect that\"\n    echo \"fails in a compiled binary context (readFileSync of a path that only\"\n    echo \"exists in node_modules, etc.). Fix before cutting the release — do NOT\"\n    echo \"proceed to version bump.\"\n    exit 1\n  fi\n\n  # Also grep for known crash markers that exit 0 but print a fatal error\n  # (some module-init errors are caught by top-level try/catch but still log).\n  if grep -qE \"Expected CommonJS module|TypeError:|ReferenceError:|SyntaxError:\" /tmp/archon-preflight.log; then\n    echo \"ERROR: compiled binary emitted a runtime error despite exit 0\"\n    cat /tmp/archon-preflight.log\n    exit 1\n  fi\n\n  echo \"Pre-flight binary smoke: PASSED\"\nfi\n```\n\nIf this fails, **abort the release entirely** — do not bump version, do not modify CHANGELOG, do not create a PR. Surface the error to the user, point at the failing output, and stop. Recovery is: fix the bundler / dependency issue on a feature branch, merge to dev, re-run `/release`.\n\n**Common failure modes this catches:**\n- Bun `--bytecode` flag producing broken bytecode for the current module graph\n- A dependency (e.g. an SDK) reading `package.json` or other files at module top level via paths that resolve fine in `node_modules/` but not next to a compiled binary\n- Circular imports that break under minification but work under plain `bun run`\n- A newly added package that ships CJS with an unusual wrapper shape\n\n### Step 2: Detect Stack and Current Version\n\nDetect the project's package manager and version file:\n\n1. **Check for `pyproject.toml`** — Python project, version in `version = \"x.y.z\"`\n2. **Check for `package.json`** — Node/Bun project, version in `\"version\": \"x.y.z\"`\n3. **Check for `Cargo.toml`** — Rust project, version in `version = \"x.y.z\"`\n4. **Check for `go.mod`** — Go project (version from git tags only, no file to bump)\n\nIf none found, abort: \"Could not detect project stack — no version file found.\"\n\nRead the current version from the detected file.\n\n### Step 3: Determine Version Bump\n\n**Bump rules based on argument:**\n- No argument or `patch` (default): `0.1.0 -> 0.1.1`\n- `minor`: `0.1.3 -> 0.2.0`\n- `major`: `0.3.5 -> 1.0.0`\n\n### Step 4: Collect Commits\n\n> **Do NOT use `git log main..dev`.** This repo **squash**-merges the release PR\n> into main, so main receives one new commit per release and never contains dev's\n> individual commits. `main..dev` therefore accumulates *every commit ever\n> squashed* and grows release over release. On 0.7.1 it returned **61** commits\n> when only **25** were new — the other 36 were already shipped in 0.7.0 and\n> already written into its changelog. Followed literally, it re-logs most of the\n> previous release.\n\nThe real boundary is dev's own last `Release x.y.z` commit:\n\n```bash\n# The previous release commit ON DEV (not the squashed one on main).\n#\n# The `$` anchor is load-bearing. Both commits exist on dev after Step 9's sync:\n#   6c6945ce  Release 0.7.1           <- dev's own version-bump commit  (WANT)\n#   c71f7f52  Release 0.7.1 (#2435)   <- main's squash, pulled back     (WRONG)\n# Picking the squash commit is catastrophic: its ancestry does not include dev's\n# individual history, so the range balloons to the whole repo (383 commits when\n# tested). Match the bare subject only.\nLAST_RELEASE=$(git log origin/dev \\\n  --grep='^Release [0-9]+\\.[0-9]+\\.[0-9]+$' --extended-regexp \\\n  --format='%H' -n 1)\n\n# First-ever release: fall back to the root commit. Without this the range\n# becomes `..origin/dev`, which git resolves against HEAD and which returns\n# ZERO commits — the skill would report \"nothing to release\" and stop.\nif [ -z \"$LAST_RELEASE\" ]; then\n  LAST_RELEASE=$(git rev-list --max-parents=0 origin/dev | tail -n 1)\nfi\necho \"Previous release commit: $(git log -1 --oneline \"$LAST_RELEASE\")\"\n\n# Everything after it is genuinely new, minus the release-plumbing commits\n# described below.\ngit log \"$LAST_RELEASE\"..origin/dev --oneline --no-merges \\\n  --grep='^Release [0-9]+\\.[0-9]+\\.[0-9]+' \\\n  --grep='^chore: update Homebrew formula for v' \\\n  --grep='^chore\\(homebrew\\):' \\\n  --extended-regexp --invert-grep\n```\n\n> The backslashes in `^chore\\(homebrew\\):` are required. Under `--extended-regexp`\n> bare `(` and `)` are grouping operators, so the unescaped form matches the\n> literal text `chorehomebrew:` and silently fails to filter anything.\n\nTwo commit kinds in that range are **release plumbing, not changelog material**,\nand the `--invert-grep` above drops them:\n\n- `Release x.y.z (#NNNN)` — main's squash commit, pulled back by Step 9's sync\n- `chore: update Homebrew formula for vx.y.z` / `chore(homebrew): …` — the CI job\n  and the Step 10 commit; note these appear as *two different SHAs* with the same\n  content, one per branch\n\n> The `Release` pattern is anchored to a full `x.y.z` version deliberately. A bare\n> `^Release ` would also swallow a legitimate feature commit whose subject starts\n> with that word. If a real commit is ever dropped, widen the range by hand rather\n> than loosening the pattern.\n\n**Sanity-check the boundary before drafting.** Cross-reference against what the\nprevious version already documented — any overlap means the range is wrong.\nDerive both headings from the current version so this does not rot:\n\n```bash\n# CURRENT_VERSION is the value read in Step 2, before the bump (e.g. 0.7.1)\nPREV_MAJOR_MINOR_PATCH=\"$CURRENT_VERSION\"\n# The heading immediately above the one we are about to write:\nawk -v prev=\"## [$PREV_MAJOR_MINOR_PATCH]\" '\n  index($0, prev) == 1 { on = 1; next }\n  on && /^## \\[/       { exit }\n  on                   { print }\n' CHANGELOG.md | grep -oE '#[0-9]{3,5}' | sort -u\n```\n\nIf a PR number in your commit range appears in that list, stop and re-derive the\nboundary — do not write it twice.\n\n**Also read the `[Unreleased]` section.** Contributors may write entries there by\nhand. They are input to this release, not a boundary error:\n\n```bash\nawk '\n  /^## \\[Unreleased\\]/ { on = 1; next }\n  on && /^## \\[/      { exit }\n  on                  { print }\n' CHANGELOG.md\n```\n\nIf no new commits, abort: \"Nothing to release — dev is up to date with main.\"\n\n### Step 5: Draft Changelog Entries\n\nRead the commit messages and the actual diffs (`git diff \"$LAST_RELEASE\"..origin/dev`) to understand what changed.\n\nPrefer the PR title and its `## Summary` section over the raw commit subject —\nthey state the user-visible problem, which is what a changelog entry needs:\n\n```bash\ngh pr view <N> --repo coleam00/Archon --json number,title,body,closingIssuesReferences\n```\n\nDraft entries only for changes the `[Unreleased]` entries from Step 4 do not\nalready describe, so each change appears once. Hand-written entries often cite\nthe issue a PR closes (`closingIssuesReferences`) rather than the PR, or cite\nnothing; judge by what the entry says, not by its number. Keep the substance of\nhand-written entries (migration tables, exact details) and tidy only their wording\nand placement. If you can't tell whether an entry covers a PR, ask in Step 7.\n\n**Categorize into Keep a Changelog sections, in this order (omit empty ones):**\n1. **Breaking** — changes that break existing behavior users may rely on\n2. **Security** — vulnerability fixes and security-relevant hardening\n3. **Added** — new features, new files, new capabilities\n4. **Changed** — modifications to existing behavior\n5. **Deprecated** — features still available but discouraged or planned for removal\n6. **Removed** — deleted features or code\n7. **Fixed** — bug fixes\n\n**Writing rules:**\n- Write entries as a human would — clear, concise, user-facing language\n- Do NOT just copy commit messages verbatim — rewrite them into proper changelog entries\n- Group related commits into single entries where it makes sense\n- Include PR numbers in parentheses when available: `(#12)`\n- Each entry should start with a noun or gerund describing WHAT changed\n- Skip internal-only changes (CI tweaks, typo fixes) unless they affect behavior\n- One blank line between sections\n\n### Step 6: Update Files\n\n1. **Version file** — update version to new value:\n   - `package.json`: update `\"version\": \"x.y.z\"`\n   - `pyproject.toml`: update `version = \"x.y.z\"`\n   - `Cargo.toml`: update `version = \"x.y.z\"`\n\n2. **Workspace version sync** (monorepo only):\n   - If `scripts/sync-versions.sh` exists, run `bash scripts/sync-versions.sh` to sync all `packages/*/package.json` versions to match the root version. The script also refreshes `bun.lock` itself, so the workspace versions land in the lockfile too.\n\n3. **Lockfile refresh** (stack-dependent) — skip the matching line if step 2's script already refreshed the lockfile:\n   - `package.json` + `bun.lock`: run `bun install`\n   - `package.json` + `package-lock.json`: run `npm install --package-lock-only`\n   - `pyproject.toml` + `uv.lock`: run `uv lock --quiet`\n   - `Cargo.toml`: run `cargo update --workspace`\n\n4. **`CHANGELOG.md`** — write one new version section below `[Unreleased]` from\n   the `[Unreleased]` entries and the Step 5 drafts, one `### <Category>` heading\n   per non-empty category in the Step 5 order:\n\n```markdown\n## [x.y.z] - YYYY-MM-DD\n\nOne-line summary of the release.\n\n### Breaking\n\n- Entry one (#PR)\n\n### Added\n\n- Entry one (#PR)\n- Entry two (#PR)\n\n### Fixed\n\n- Entry one (#PR)\n```\n\n**`### Breaking` must come first**, immediately after the one-line summary. Step 9's\nrelease workflow reads this section verbatim into the GitHub release body, so this\nfile's own section order is what makes breaking changes appear first on the release page.\n\nLeave the `[Unreleased]` header with nothing under it.\n\n### Step 7: Present for Review\n\nShow the user:\n1. The detected stack and version file\n2. The version bump (old -> new)\n3. The full changelog section that will be added\n4. The list of commits being included\n5. Any `[Unreleased]` entry you were unsure covers a PR, next to that PR\n\nAsk: \"Does this look good? I'll commit and create the PR.\"\n\n### Step 8: Commit and PR\n\nOnly after user approval:\n\n```bash\n# Stage version file, workspace packages, lockfile, and changelog\ngit add <version-file> packages/*/package.json <lockfile> CHANGELOG.md\ngit commit -m \"Release x.y.z\"\n\n# Push dev\ngit push origin dev\n\n# Create PR: dev -> main\ngh pr create --base main --head dev \\\n  --title \"Release x.y.z\" \\\n  --body \"$(cat <<'EOF'\n## Release x.y.z\n\n{changelog section content}\n\n---\n\nMerging this PR releases x.y.z to main.\nEOF\n)\"\n```\n\nReturn the PR URL to the user.\n\n### Step 9: Tag, Release, and Sync After Merge\n\nAfter the PR is merged (either by the user or via `gh pr merge`):\n\n```bash\n# Fetch the merge commit on main\ngit fetch origin main\n\n# Tag the merge commit\ngit tag vx.y.z origin/main\ngit push origin vx.y.z\n\n# Create a GitHub Release from the tag. --generate-notes is a placeholder: the\n# push above already triggered .github/workflows/release.yml, and once its\n# `release` job finishes building binaries (5-10 min), its \"Build release\n# notes\" step overwrites this release's body with CHANGELOG.md's own\n# `## [x.y.z]` section (Breaking first, per Step 6) plus install instructions.\n# That job is the single source of truth for the body, so it does not matter\n# that this step's placeholder is throwaway, or which of the two finishes\n# first — do NOT hand-craft curated notes here, they would just be discarded.\ngh release create vx.y.z --title \"vx.y.z\" --generate-notes\n\n# Sync dev with main so both branches are identical\ngit checkout dev\ngit pull origin main --no-rebase\ngit push origin dev\n\n# Verify the sync actually converged — do not assume it did. FAIL CLOSED:\n# continuing past a failed sync publishes a formula and tap from a tree that\n# does not match what was released.\ngit fetch origin\nif git merge-base --is-ancestor origin/main origin/dev; then\n  echo \"dev contains main — OK\"\nelse\n  echo \"STILL DIVERGED — stranded on main:\"\n  git log origin/dev..origin/main --oneline\n  exit 1\nfi\n```\n\n> **`--no-rebase` is required, not optional.** Without it, git aborts with\n> `fatal: Need to specify how to reconcile divergent branches` on any machine\n> that has not set `pull.rebase`. It also pins the behaviour to the merge this\n> step actually wants — a `pull.rebase=true` config would otherwise rewrite dev's\n> history, which is exactly what the warning below forbids.\n\n> **Expect to run this twice.** The CI `update-homebrew` job pushes its own\n> formula commit to dev while you are working, so the `git push origin dev` here\n> (and again in Step 10) can be rejected with `Updates were rejected`. That is\n> normal: `git pull origin dev --no-rebase`, then push again. On 0.7.1 both this\n> step and Step 10 required a second pass.\n\n> **Important**: This sync ensures dev has the merge commit from main. Without it,\n> dev and main diverge. The CI `update-homebrew` job only pushes the formula\n> commit to dev — it does not bring the PR merge commit onto dev. This manual\n> `git pull origin main --no-rebase` is what ensures dev has the merge commit.\n\n> **Do NOT** use `git pull origin main --ff-only` or `git reset --hard origin/main`\n> for this sync. Fast-forward is impossible across a squash merge — main's squash\n> commit has a different SHA than dev's release commit, so dev is never\n> fast-forwardable to main. And resetting dev to main rewrites dev's history,\n> which severs every open PR's merge-base from its original commit and balloons\n> their diffs to thousands of lines (confirmed against v0.3.10's release: PRs\n> went from `+80/-1` to `+6626/-300` after a `git reset --hard origin/main` on\n> dev). The plain `git pull origin main --no-rebase` above creates a regular merge commit on\n> dev. The merge bubble in dev's `git log` is the right cost for preserving\n> open-PR sanity. If the merge produces a `homebrew/archon.rb` conflict during a\n> recovery flow, resolve with `git checkout origin/main -- homebrew/archon.rb`\n> (note: `origin/main`, NOT `main` — local main is often stale because the\n> release pushes via `git push origin dev:main` without fast-forwarding the local\n> branch).\n\nThe GitHub Release is distinct from the git tag — without it, the release won't appear on the repository's Releases page. Always create it.\n\nIf the user merges the PR themselves and comes back, still offer to tag, release, and sync.\n\n### Step 9.5: Add the new tag's SQLite vintage fixture\n\nA release tag is a released schema shape. When `vx.y.z` changes the SQLite schema, `check:sqlite-vintages` (the `schema-upgrade` CI job) fails on every PR until the tag's fixture is committed. A tag that keeps the previous schema gets no fixture and needs nothing. Generate it on a branch off dev and commit whatever it writes:\n\n```bash\ngit fetch --tags origin\nbun run generate:sqlite-vintages   # writes packages/core/src/db/fixtures/sqlite-vintages/vx.y.z.sql\nbun run check:sqlite-vintages\n```\n\n### Step 10: Wait for Release Workflow and Update Homebrew Formula\n\n> **Note**: The `update-homebrew` CI job in `.github/workflows/release.yml` runs automatically\n> after the release job and handles the formula update + push to dev (part of Step 10).\n> Step 11 (tap sync to `coleam00/homebrew-archon`) is always manual. Check the Actions tab\n> before running Step 10 manually.\n\nAfter the tag is pushed, `.github/workflows/release.yml` builds platform binaries and uploads them to the GitHub release. This takes 5-10 minutes. The Homebrew formula SHA256 values cannot be known until these binaries exist.\n\n**Wait for all assets to appear on the release:**\n\n```bash\necho \"Waiting for release workflow to finish uploading binaries...\"\nWORKFLOW_FAILED=0\nfor i in {1..30}; do\n  ASSET_COUNT=$(gh release view \"vx.y.z\" --repo coleam00/Archon --json assets --jq '.assets | length')\n  # Expect 7 assets: 5 binaries (darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64.exe) + archon-web.tar.gz + checksums.txt\n  if [ \"$ASSET_COUNT\" -ge 7 ]; then\n    echo \"All $ASSET_COUNT assets uploaded\"\n    break\n  fi\n\n  # Short-circuit: if the release workflow itself has failed, stop waiting.\n  # Hanging for 15 min when CI already crashed just delays the recovery path.\n  WORKFLOW_STATUS=$(gh run list --workflow release.yml --event push --limit 1 --json conclusion,status --jq '.[0] | \"\\(.status)|\\(.conclusion)\"')\n  if [[ \"$WORKFLOW_STATUS\" == \"completed|failure\" ]]; then\n    echo \"Release workflow FAILED — no point waiting longer\"\n    WORKFLOW_FAILED=1\n    break\n  fi\n\n  echo \"  Assets so far: $ASSET_COUNT/7 — waiting 30s (attempt $i/30)...\"\n  sleep 30\ndone\n\nif [ \"$WORKFLOW_FAILED\" -eq 1 ] || [ \"$ASSET_COUNT\" -lt 7 ]; then\n  # Triage: rerun once in case it's transient, then check again.\n  RUN_ID=$(gh run list --workflow release.yml --event push --limit 1 --json databaseId --jq '.[0].databaseId')\n  echo \"Release workflow failed on run $RUN_ID. Rerunning failed jobs once to confirm...\"\n  gh run rerun \"$RUN_ID\" --failed\n  gh run watch \"$RUN_ID\" --exit-status --interval 30 || true\n\n  # Re-check asset count + run status after rerun.\n  ASSET_COUNT=$(gh release view \"vx.y.z\" --repo coleam00/Archon --json assets --jq '.assets | length')\n  if [ \"$ASSET_COUNT\" -ge 7 ]; then\n    echo \"Rerun succeeded — all assets now present\"\n  else\n    echo \"\"\n    echo \"===== DETERMINISTIC CI FAILURE =====\"\n    echo \"The release workflow failed on two consecutive runs. This is NOT a flake.\"\n    echo \"The tag and release exist but have no (or incomplete) assets.\"\n    echo \"\"\n    echo \"install.sh and similar 'releases/latest' paths are now 404-ing.\"\n    echo \"Proceeding with Homebrew/tap sync would publish a formula pointing at\"\n    echo \"missing or inconsistent binaries.\"\n    echo \"\"\n    echo \"Jump to the 'Recovery: deterministic release CI failure' section at the\"\n    echo \"bottom of this skill and execute it. Do NOT continue past this point.\"\n    exit 1\n  fi\nfi\n```\n\n**Fetch checksums.txt and extract SHA256 values:**\n\n```bash\nTMP_DIR=$(mktemp -d)\ngh release download \"vx.y.z\" --repo coleam00/Archon --pattern \"checksums.txt\" --dir \"$TMP_DIR\"\n\nDARWIN_ARM64_SHA=$(awk '/archon-darwin-arm64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nDARWIN_X64_SHA=$(awk '/archon-darwin-x64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nLINUX_ARM64_SHA=$(awk '/archon-linux-arm64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nLINUX_X64_SHA=$(awk '/archon-linux-x64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\n\n# Sanity check — all four must be present and non-empty\nfor var in DARWIN_ARM64_SHA DARWIN_X64_SHA LINUX_ARM64_SHA LINUX_X64_SHA; do\n  if [ -z \"${!var}\" ]; then\n    echo \"ERROR: $var is empty — checksums.txt may be malformed\"\n    cat \"$TMP_DIR/checksums.txt\"\n    exit 1\n  fi\ndone\n\nrm -rf \"$TMP_DIR\"\n```\n\n**Update `homebrew/archon.rb` in the main repo atomically with version AND SHAs:**\n\nRewrite the formula file using the exact template below. Before using it, diff it against the current `homebrew/archon.rb`: if the current formula has a block the template lacks, update the template first (v0.12.0's first regeneration dropped `caveats` this way). Do NOT edit in place with sed — the whole file should be regenerated from this template so there is zero risk of partial updates.\n\n```bash\ncat > homebrew/archon.rb << EOF\n# Homebrew formula for Archon CLI\n# To install: brew install coleam00/archon/archon\n#\n# This formula downloads pre-built binaries from GitHub releases.\n# For development, see: https://github.com/coleam00/Archon\n\nclass Archon < Formula\n  desc \"Governed agentic automation engine\"\n  homepage \"https://github.com/coleam00/Archon\"\n  version \"x.y.z\"\n  license \"MIT\"\n\n  on_macos do\n    on_arm do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-arm64\"\n      sha256 \"${DARWIN_ARM64_SHA}\"\n    end\n    on_intel do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-x64\"\n      sha256 \"${DARWIN_X64_SHA}\"\n    end\n  end\n\n  on_linux do\n    on_arm do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-arm64\"\n      sha256 \"${LINUX_ARM64_SHA}\"\n    end\n    on_intel do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-x64\"\n      sha256 \"${LINUX_X64_SHA}\"\n    end\n  end\n\n  def install\n    binary_name = case\n    when OS.mac? && Hardware::CPU.arm?\n      \"archon-darwin-arm64\"\n    when OS.mac? && Hardware::CPU.intel?\n      \"archon-darwin-x64\"\n    when OS.linux? && Hardware::CPU.arm?\n      \"archon-linux-arm64\"\n    when OS.linux? && Hardware::CPU.intel?\n      \"archon-linux-x64\"\n    end\n\n    bin.install binary_name => \"archon\"\n  end\n\n  def caveats\n    <<~EOS\n      Archon ships without AI providers. Add the ones you use:\n        archon setup\n      or directly:\n        archon provider add <claude|codex|pi>\n\n      Adding a provider connects Archon to your agent CLI or SDK; it does not install that CLI.\n\n      After `brew upgrade archon`, run `archon provider add` again for each\n      provider you use; installed providers must match the CLI version.\n    EOS\n  end\n\n  test do\n    # Basic version check - archon version should exit with 0 on success\n    assert_match version.to_s, shell_output(\"#{bin}/archon version\")\n  end\nend\nEOF\n```\n\n**Commit the formula update to main, then sync back to dev:**\n\n```bash\ngit checkout main\ngit pull origin main --no-rebase\ngit add homebrew/archon.rb\ngit commit -m \"chore(homebrew): update formula to vx.y.z\"\ngit push origin main\n\n# Sync dev with main so the formula update is on both branches\ngit checkout dev\ngit pull origin main --no-rebase\ngit push origin dev   # may be rejected — see below\n```\n\n**The CI `update-homebrew` job races you here.** It writes its own formula commit\ndirectly to dev (e.g. `chore: update Homebrew formula for vx.y.z`) while you are\ncommitting the same change to main. Both touch the same four `sha256` lines. The\npush above then fails with `Updates were rejected`:\n\n```bash\ngit pull origin dev --no-rebase   # merges CI's commit; usually resolves clean\ngit push origin dev\n```\n\n**A clean merge is not proof the SHAs are right.** Two commits editing the same\nlines can merge without conflict and still leave the wrong values. Verify against\nthe published checksums before pushing — this is the one file where a wrong value\nbreaks every user's install:\n\n```bash\nVERSION=x.y.z   # without the leading v\ngh release download \"v$VERSION\" --repo coleam00/Archon --pattern checksums.txt --dir /tmp/rel\n\nfail=0\n\n# Assert the formula version matches the release. A stale version points every\n# URL at the wrong release while carrying the new digests.\nformula_version=$(awk -F'\"' '/^[[:space:]]*version \"/ {print $2; exit}' homebrew/archon.rb)\nif [ \"$formula_version\" != \"$VERSION\" ]; then\n  echo \"  BAD version: formula says '$formula_version', release is '$VERSION'\"\n  fail=1\nfi\n\n# Compare each digest against the sha256 that FOLLOWS ITS OWN url line. A naive\n# `grep -q \"$digest\" formula` only asks whether the value appears anywhere, so\n# two platforms with swapped hashes both pass — and an empty digest degenerates\n# to `grep -q \"\"`, which matches every file.\nfor p in archon-darwin-arm64 archon-darwin-x64 archon-linux-arm64 archon-linux-x64; do\n  real=$(awk -v p=\"$p\" '$2 ~ \"(^|/)\" p \"$\" {print $1}' /tmp/rel/checksums.txt | head -1)\n  if ! printf '%s' \"$real\" | grep -qE '^[0-9a-f]{64}$'; then\n    echo \"  BAD $p: no 64-char digest in checksums.txt (got '$real')\"\n    fail=1\n    continue\n  fi\n  in_formula=$(awk -v p=\"$p\" '\n    index($0, \"/\" p \"\\\"\") { seen = 1; next }\n    seen && /sha256 \"/    { gsub(/.*sha256 \"|\".*/, \"\"); print; exit }\n  ' homebrew/archon.rb)\n  if [ \"$in_formula\" = \"$real\" ]; then\n    echo \"  OK   $p\"\n  else\n    echo \"  BAD  $p: formula has '$in_formula', release has '$real'\"\n    fail=1\n  fi\ndone\n\n[ \"$fail\" -eq 0 ] || { echo \"formula verification FAILED — do not push\"; exit 1; }\necho \"formula verified against v$VERSION checksums\"\n```\n\nEvery line must print `OK` and the version must match. On any `BAD`, regenerate\nthe formula from the Step 10 template rather than hand-editing — a hand edit is\nhow a digest ends up under the wrong platform in the first place.\n\nFinally, confirm convergence. **Fail closed** — Step 11 publishes to the tap that\nusers install from, so do not proceed on an unconverged tree:\n\n```bash\ngit fetch origin\nconverged=1\ngit merge-base --is-ancestor origin/main origin/dev \\\n  || { echo \"DIVERGED: dev does not contain main\"; converged=0; }\nstranded=$(git log origin/dev..origin/main --oneline | wc -l | tr -d ' ')\n[ \"$stranded\" -eq 0 ] || { echo \"STRANDED: $stranded commit(s) on main not on dev:\"; \\\n  git log origin/dev..origin/main --oneline; converged=0; }\n[ \"$converged\" -eq 1 ] || { echo \"sync incomplete — resolve before Step 11\"; exit 1; }\necho \"branches converged — OK\"\n```\n\n### Step 11: Sync the Homebrew Tap Repo\n\nThe `coleam00/homebrew-archon` repository hosts the actual tap formula that Homebrew reads when users run `brew tap coleam00/archon && brew install coleam00/archon/archon`. The file `coleam00/Archon/homebrew/archon.rb` is the source-of-truth template; the file `coleam00/homebrew-archon/Formula/archon.rb` is what users actually install from. These must be kept in sync.\n\n```bash\nTAP_DIR=$(mktemp -d)\ngit clone git@github.com:coleam00/homebrew-archon.git \"$TAP_DIR\"\ncp homebrew/archon.rb \"$TAP_DIR/Formula/archon.rb\"\n\ncd \"$TAP_DIR\"\nif git diff --quiet; then\n  echo \"Tap formula already matches — no sync needed\"\nelse\n  git add Formula/archon.rb\n  git commit -m \"chore: sync formula to vx.y.z\"\n  git push origin main\nfi\ncd -\nrm -rf \"$TAP_DIR\"\n```\n\nIf the `git clone` fails with a permissions error, the user running the release skill does not have push access to `coleam00/homebrew-archon`. Ask them to request push access from the repo owner, or to perform the sync manually via the GitHub web UI. Do not skip this step silently — the release is not complete until the tap is synced.\n\n### Step 12: Verify the Release End-to-End\n\nAfter the formula is synced, the final verification step is to actually install the released binary via Homebrew and run smoke tests. Use the `test-release` skill:\n\n```\n/test-release brew x.y.z\n```\n\nThis will:\n- Install via `brew tap coleam00/archon && brew install coleam00/archon/archon`\n- Verify the binary reports the correct version and `Build: binary`\n- Verify bundled workflows load\n- Verify the SDK spawn path works (a minimal archon-investigate run)\n- Verify the env-leak guard strips sensitive keys from a leaky .env (Test 4)\n- Uninstall cleanly\n- Produce a PASS/FAIL report\n\n**If `/test-release brew` fails, the release is not ready to announce.** File a hotfix issue for whatever broke, cut `x.y.z+1` with the fix, and re-run this skill. Do NOT advertise a release that fails `test-release`.\n\nAlso run `/test-release curl-mac x.y.z` to cover the curl install path. The two install paths test slightly different things (Homebrew tests the tap formula, curl tests `install.sh` and checksums from the release) and both need to work for users to have a reliable install experience.\n\nIf you have a VPS available, also run `/test-release curl-vps x.y.z <vps-target>` to verify the Linux binary.\n\n## Recovery: deterministic release CI failure\n\nReached here because Step 10 detected two consecutive workflow failures. The tag `vx.y.z` is pushed, the GitHub release exists, but assets are missing or incomplete. Every `install.sh` run currently resolves `releases/latest` to this broken release and 404s on download. Homebrew users are safe because Step 10's atomic formula update was blocked.\n\n**Do not re-run the release workflow a third time hoping it succeeds.** If the failure was reproducible twice, it's a code bug — you need to ship code to fix it.\n\n### Immediate mitigation (restore `install.sh`)\n\nDelete the GitHub Release so `releases/latest` falls back to the previous version. Keep the git tag — tag immutability matters and there are no shipped artifacts pointing at it anyway.\n\n```bash\ngh release delete \"vx.y.z\" --yes\n# Do NOT delete the tag:\n#   git push --delete origin vx.y.z   ← do not run\n# Tag stays so git history records the attempt; no release means no assets\n# means releases/latest resolves to the prior working release.\n```\n\nVerify:\n\n```bash\ngh api repos/coleam00/Archon/releases/latest --jq '.tag_name'\n# should now print the prior version (e.g. v0.3.6), not vx.y.z\n```\n\n### Diagnose\n\nThe release workflow logs tell you which target failed and at what stage (compile vs. smoke-test vs. upload):\n\n```bash\ngh run list --workflow release.yml --limit 2 --json databaseId,conclusion\ngh run view <RUN_ID> --log-failed\n```\n\nCommon causes:\n- **Bundler/bytecode bug** — Bun `--bytecode` produces invalid output for the current module graph. Symptom: `TypeError: Expected CommonJS module to have a function wrapper` at binary startup. Historically caused by a new dependency's CJS/ESM shape interacting with `--bytecode` — dropping the flag or lazy-importing the offending module has been the fix.\n- **Module-init crash** — a dependency does `readFileSync('package.json')` or similar at module top level via a path that exists in `node_modules/` but not next to a compiled binary. Symptom: every binary subcommand crashes immediately; error typically mentions a missing file adjacent to `process.execPath`. Fix by lazy-importing the dependency behind the code path that actually uses it.\n- **Smoke-test timeout on Windows** — not actually a bug in the code; the Windows runner is slow. Rerun once; if it recurs, bump the test timeout.\n\nStep 1.5 now runs a local compiled-binary smoke test before any user-visible step. If the failure mode above reproduces locally, you've found it. If it doesn't, the bug is platform-specific (Windows cross-compile, Linux glibc, etc.) and you need the CI logs.\n\n### Fix and re-release as the NEXT patch\n\n**Do not reuse `vx.y.z`.** Cut `vx.y.(z+1)` (or next-minor if warranted) with the fix. Rationale:\n- Tag immutability: `vx.y.z` is already recorded in git history and release cache\n- Semver clarity: users and tooling should see a new version number when the bits change\n- Audit trail: \"v0.3.7 was cut but had no shipped binaries; v0.3.8 is the first release with <fix>\" is cleaner than rewriting v0.3.7\n\nSteps:\n\n1. Cut a fix branch off dev, implement the fix, PR to dev, merge.\n2. Re-run `/release` (it will bump to the next patch — e.g. `0.3.8` — automatically).\n3. Step 1.5's pre-flight smoke will catch the same bug locally if the fix didn't actually fix it. Iterate until it passes before tagging.\n\n### CHANGELOG note for the hotfix release\n\nInclude a line in the new release's CHANGELOG that references the broken prior version so users understand why there's no binary artifact under that tag:\n\n```markdown\n### Fixed\n\n- **First release with working compiled binaries after vx.y.z's <bug>.** vx.y.z was tagged but its binary smoke test failed deterministically (see RUN_ID in CI history). The tag is preserved for history; this release (vx.y.(z+1)) is the first with shipped binaries. `install.sh` and Homebrew were never updated to vx.y.z, so users were not exposed to the broken state.\n```\n\n### What NOT to do\n\n- **Do not force-push or rewrite the tag.** Once a tag exists, it's a public promise of that SHA. Deleting and re-creating to a different SHA is tag-spoofing and breaks any downstream that cached the original.\n- **Do not skip this recovery path to \"just push more binaries to the broken release\".** The release exists with a specific commit SHA; uploading binaries built from a newer SHA creates binary/source drift that is hard to diagnose later.\n- **Do not update the Homebrew formula before v0.3.(z+1) is fully shipped.** The formula should always point at a version with all 7 assets uploaded and `/test-release brew` passing.\n\n## Important Rules\n\n- NEVER force push\n- **NEVER derive the changelog from `git log main..dev`.** main squash-merges, so\n  that range accumulates every previously-released commit and grows each release\n  (61 vs 25 actual on 0.7.1). Use dev's last `Release x.y.z` commit as the\n  boundary, and cross-check the result against the previous version's changelog\n  entries — any PR number appearing in both means the boundary is wrong.\n- **ALWAYS pass `--no-rebase` to `git pull`.** The bare form aborts with\n  `Need to specify how to reconcile divergent branches` unless `pull.rebase` is\n  configured, and a `pull.rebase=true` config would rewrite dev's history.\n- **ALWAYS verify sync convergence rather than assuming it.**\n  `git merge-base --is-ancestor origin/main origin/dev` after every sync step.\n  A commit made directly to main stays stranded silently otherwise.\n- **NEVER trust a clean `homebrew/archon.rb` merge.** The CI `update-homebrew`\n  job edits the same `sha256` lines on dev that you edit on main; the merge can\n  succeed and still be wrong. Diff the four values against the published\n  `checksums.txt` before pushing.\n- **Flag a version bump that contradicts the commit range.** If `patch` was\n  requested but the range contains `feat:` commits or new user-facing CLI\n  surface, say so at the Step 7 review and let the user decide — do not silently\n  ship features as a patch.\n- **NEVER skip Step 1.5 (pre-flight compiled-binary smoke).** If the stack is a Bun/Node project with a build-binaries script, the `bun build --compile` smoke test runs before version bump, PR, or tag. Skipping it means every bundler regression or module-init crash only surfaces after the tag is pushed — by which point `releases/latest` is already 404-ing for every user. The ~30s cost is paid to keep the failure mode local.\n- If Step 1.5 fails, **abort the release** and fix the underlying issue on a feature branch. Do not \"just skip it\" and hope CI doesn't repro the problem.\n- NEVER skip the review step — always show the changelog before committing\n- NEVER include \"Co-Authored-By: Claude\" or any AI attribution in the commit\n- NEVER add emoji to changelog entries unless the user asks\n- If the user says \"ship it\" without specifying bump type, default to patch\n- The commit message is just `Release x.y.z` — clean and simple\n- **NEVER update `homebrew/archon.rb` version field without also updating the `sha256` values**. They must move together atomically. The correct SHAs only exist after the release workflow finishes building binaries — see Step 10. Updating the version field alone produces a stale formula that looks valid but causes checksum mismatches on install.\n- **NEVER skip Step 11 (tap sync).** The `coleam00/Archon/homebrew/archon.rb` file is only a template; users install from `coleam00/homebrew-archon/Formula/archon.rb`. If you update one without the other, users get stale or wrong data.\n- **NEVER announce a release that failed `/test-release brew`.** A release that installs but crashes on first invocation is worse than no release — it burns user trust. If the release verification fails, cut a hotfix before telling anyone the release exists.\n","frontmatter":{"name":"release","description":"Create a release from dev branch. Generates changelog entries from commits,\nbumps version, and creates a PR to main.\n\nTRIGGERS - Use this skill when user says:\n- \"/release\" - create a patch release (default)\n- \"/release minor\" - create a minor release\n- \"/release major\" - create a major release\n- \"make a release\", \"cut a release\", \"ship it\", \"release to main\"\n"},"isInternal":false,"tokens":10381,"sizeBytes":40081},{"name":"rules-guide.md","path":".claude/skills/rulecheck/rules-guide.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/rulecheck/rules-guide.md","title":"Rulecheck Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Guide — Where to Find Project Rules\n\nReference for the rulecheck agent. Focus on rules that **linters can't enforce** —\narchitectural principles, patterns, and conventions from AGENTS.md.\n\n## Primary Source: `AGENTS.md` (Root)\n\nRead this file thoroughly, but derive checks only from its normative rules —\nsections like `Project Overview` and `Product Direction` are descriptive\ncontext, not something to enforce. Root `CLAUDE.md` is only a one-line pointer\nto this file — reading that instead gets you nothing.\n\n### Engineering Principles (each is a concrete rule, not a slogan)\n\nRead `AGENTS.md` for the repository's engineering rules.\n\nAlso read `.archon/engineering.md` for the aspirational engineering standard.\n","isInternal":false,"tokens":161,"sizeBytes":732},{"name":"SKILL.md","path":".claude/skills/rulecheck/SKILL.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/rulecheck/SKILL.md","title":"rulecheck","category":"anthropic-skill","format":"markdown","content":"---\nname: rulecheck\ndescription: |\n  Autonomous rule adherence checker. Scans the codebase for rule violations,\n  fixes the highest-impact ones in an isolated worktree, runs full validation,\n  creates a PR. Uses memory to track progress across runs.\ndisable-model-invocation: true\nagent: rulecheck-agent\nargument-hint: \"[focus area]\"\n---\n\n# Rulecheck\n\nLaunch the rulecheck-agent to autonomously scan and fix AGENTS.md rule violations.\n\n## Your Job (Main Agent)\n\nYou are the orchestrator. Your ONLY job is to launch the rulecheck-agent and\nreport its results when it completes. You do NOT do the scanning or fixing yourself.\n\n1. **Launch the rulecheck-agent** with the focus area (if any): `$ARGUMENTS`\n2. **Wait for it to complete** — do NOT poll, tail, or check on it. You will be\n   notified automatically when it finishes.\n3. **Report ONLY what the agent told you** — relay its final message verbatim\n   or summarize it. Do NOT search for PRs, read files, or fabricate results.\n   If the agent hit its context limit, say so. If it didn't produce a PR URL\n   in its output, don't go looking for one.\n\n## Rules for You\n\n- **Do NOT scan the codebase yourself** — that's the agent's job\n- **Do NOT grep, read source files, or run linters** — the agent handles all of that\n- **Do NOT edit any files** — you are the orchestrator, not the fixer\n- **Do NOT try to resume or check on the agent** while it's running — just wait\n- **Do NOT do the agent's work if it fails or hits context limits** — report the failure to the user and stop. NEVER pick up where the agent left off. You are not in a worktree and would be editing main directly.\n- **Do NOT update project memory** — the agent maintains its own memory at `.claude/agent-memory/rulecheck-agent/`. Do not duplicate run results into your project memory.\n- Trust the agent. It runs in an isolated worktree and will create a PR when done.\n","frontmatter":{"name":"rulecheck","description":"Autonomous rule adherence checker. Scans the codebase for rule violations,\nfixes the highest-impact ones in an isolated worktree, runs full validation,\ncreates a PR. Uses memory to track progress across runs.\n","disable-model-invocation":true,"agent":"rulecheck-agent","argument-hint":"[focus area]","disableModelInvocation":true},"isInternal":false,"tokens":460,"sizeBytes":1904},{"name":"workshop-guide.md","path":".claude/skills/rulecheck/workshop-guide.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/rulecheck/workshop-guide.md","title":"Rulecheck Skill","category":"anthropic-skill","format":"markdown","content":"# Rulecheck — Workshop Guide\n\n## What This Skill Demonstrates\n\nAn autonomous code quality agent that combines **17 Claude Code features** into one\ngenuinely useful tool: custom agents, safety hooks, all four hook types (command,\nprompt, http, agent), LLM-as-judge, persistent memory, Slack notifications, and\nworktree isolation.\n\nReplaces the existing advisory-only `code-rulecheck` agent with a fully autonomous\nversion that finds violations, fixes them, validates, creates PRs, and learns.\n\n**Full demo steps**: See [part2-guide.md](../../workshop/part2-guide.md) — Feature 8.\n\n## Architecture\n\n```\nUser invokes /rulecheck [focus area]\n        │\n        ▼\n┌──────────────────────────┐\n│  SKILL.md                │  agent: rulecheck-agent\n│  (orchestrator)          │  disable-model-invocation: true\n│                          │  argument-hint: \"[focus area]\"\n│  Launches agent,         │\n│  reports results         │  NO context: fork\n└────────┬─────────────────┘\n         │ delegates to agent\n         ▼\n┌──────────────────────────┐\n│  rulecheck-agent.md     │  isolation: worktree\n│  (.claude/agents/)       │  background: true\n│                          │  memory: project\n│  model: sonnet           │  permissionMode: acceptEdits\n│  maxTurns: 500           │\n│                          │\n│  hooks:                  │\n│  ├─ PreToolUse [Bash]    │──→ block-dangerous.sh (safety gate)\n│  ├─ PostToolUse [Edit]   │──→ bun run lint:fix (auto-fix)\n│  └─ Stop                 │\n│     ├─ command           │──→ slack-notify.sh (reads summary JSON)\n│     ├─ http              │──→ POST to Slack webhook URL\n│     └─ agent             │──→ meta-judge (LLM evaluation)\n└────────┬─────────────────┘\n         │ works in worktree\n         ▼\n┌──────────────────────────┐\n│  Worktree                │\n│  1. Read memory          │\n│  2. Read AGENTS.md rules │\n│  3. Deep scan source     │\n│  4. Group violations     │\n│  5. Fix one group        │\n│  6. Check own changes    │\n│  7. Write summary JSON   │\n│  8. gh pr create         │\n│  9. Update memory        │\n└──────────────────────────┘\n         │\n         ▼\n   Results reported to main conversation\n   + PR created + Slack notified + memory updated\n```\n\n## Features to Walk Through\n\n### 1. `agent: rulecheck-agent` — Custom Agent Delegation (no fork)\n\n**What is it?** The `agent:` field delegates to a custom agent with its own\nsystem prompt, model, hooks, and memory configuration.\n\n**In this skill**: The skill acts as an **orchestrator** — it launches the agent\nand reports results. There is no `context: fork`. The skill stays active in the\nmain conversation while the agent runs in the background in its own worktree.\n\n**Why no fork?** The rulecheck agent uses `background: true` and\n`isolation: worktree`, so it already runs in a completely separate context.\nForking would add an unnecessary layer. The skill just launches and waits.\n\n### 2. `disable-model-invocation: true` — User-Only Trigger\n\n**What is it?** Prevents Claude from auto-invoking this skill. Since it creates\nPRs and pushes branches (side effects), only the user should trigger it.\n\n### 3. `argument-hint: \"[focus area]\"` — Usage Hint\n\n**What is it?** Shows in `/help` and tab completion to tell users what arguments\nthe skill accepts. Optional — the agent works without arguments too.\n\n### 4. `!`command`` — Dynamic Context Injection\n\n**What is it?** Shell commands that execute at skill load time (before Claude sees\nthe prompt). Stdout replaces the placeholder.\n\n**In this skill**: Injects current branch, recent git log, and lint status so the\nagent starts with real context instead of having to fetch it.\n\n### 5. Supporting Files — On-Demand Loading\n\n**What is it?** Markdown files linked from the skill body with `[name](file.md)`.\nClaude loads them only when needed, keeping the initial prompt small.\n\n**In this skill**: `rules-guide.md` documents where to find rules and how to rank\nviolations. The agent loads it during the scan phase.\n\n### 6. `isolation: worktree` — Git Worktree Isolation\n\n**What is it?** The agent works in a temporary git worktree — a separate working\ndirectory with its own branch. Changes are isolated from the main repo.\n\n**In this skill**: The agent edits files and creates commits without affecting the\nuser's working directory. If something goes wrong, the worktree is disposable.\n\n### 7. `background: true` — Concurrent Execution\n\n**What is it?** The agent runs in the background. The user can continue working\nin the main conversation while the agent scans, fixes, and creates a PR.\n\n### 8. `memory: project` — Persistent Memory\n\n**What is it?** The agent has a persistent memory directory at\n`.claude/agent-memory/rulecheck-agent/`. It reads from and writes to `MEMORY.md`\nacross runs.\n\n**In this skill**: The agent remembers what it fixed last time, what's in the\nbacklog, and meta-judge feedback — so each run builds on the previous one.\n\n### 9. `permissionMode: acceptEdits` — Auto-Accept Edits\n\n**What is it?** File edits (Edit/Write tools) are automatically approved without\nuser confirmation. Other tools still require approval per the normal flow.\n\n**In this skill**: The agent needs to edit many files to fix violations. Prompting\nfor each edit would defeat the purpose of autonomous execution.\n\n### 10. `maxTurns: 500` — Safety Cap\n\n**What is it?** Limits the agent to 500 API round-trips. Prevents runaway agents\nthat loop endlessly. Set high here because the agent does a lot of work (scan,\nfix, validate, commit, push, PR, memory update).\n\n### 11. `hooks:` in Agent Frontmatter\n\n**What is it?** Hooks defined directly in the agent's YAML frontmatter. They\nactivate whenever this agent runs, regardless of which skill invokes it.\n\nThis skill demonstrates all four hook types:\n\n#### 11a. `type: \"command\"` — Shell Script Hooks\n\n**PreToolUse [Bash]**: `block-dangerous.sh` reads the command from stdin, checks\nagainst a blocklist (force push, git clean, hard reset, rm -rf), and exits 2 to\nblock dangerous commands.\n\n**PostToolUse [Edit|Write]**: Runs `bun run lint:fix` after every file edit to\nauto-correct formatting issues before they accumulate.\n\n**Stop**: `slack-notify.sh` reads the agent's summary file and sends a formatted\nSlack message with what was fixed, PR link, and remaining opportunities. This\ndemonstrates **inter-hook communication** — the agent writes a JSON file, the\nhook reads it.\n\n#### 11b. `type: \"http\"` — HTTP Webhook\n\n**Stop**: POSTs the event directly to a Slack webhook URL. No script needed —\njust a URL in the frontmatter. This is the simplest way to notify external\nservices. Compare with 11a above: the command hook gives you full control over\nthe message format, while the HTTP hook sends the raw event with zero code.\n\n#### 11c. `type: \"agent\"` — LLM Meta-Judge\n\n**Stop**: A third Stop hook spawns a subagent that evaluates the rulecheck's\nexecution. It reviews what was fixed, assesses prioritization quality, and writes\nstructured feedback to memory for the next run.\n\n### 12. `statusMessage` — Hook Progress Indicators\n\n**What is it?** A string shown in the Claude Code spinner while a hook runs.\nGives users visibility into what's happening during hook execution.\n\n**In this skill**: \"Checking command safety...\", \"Auto-fixing lint issues...\",\n\"Notifying Slack...\", \"Posting run event to Slack...\", \"Running meta-judge evaluation...\"\n\n### 13. `$ARGUMENTS` — Skill Arguments\n\n**What is it?** The text after the skill name (e.g., `/rulecheck error handling`)\nis available as `$ARGUMENTS` in the skill body and passed through to the agent.\n\n### 14. `${CLAUDE_SESSION_ID}` — Session Identifier\n\n**What is it?** Environment variable with the current session ID. Used in the\nmeta-judge prompt to tag feedback with the session that produced it.\n\n### 15. Summary File Pattern — Inter-Hook Communication\n\n**What is it?** The agent writes `.claude/archon/rulecheck-last-run.json` as a\nstructured summary. The Slack hook reads this file to format its notification.\n\nThis is a practical pattern for passing data between the agent and its hooks\nwhen the hook needs more than what's in the event JSON.\n\n## Live Demo Steps\n\n1. **Show the architecture** — open all files:\n   - `.claude/skills/rulecheck/SKILL.md` (skill entry point)\n   - `.claude/agents/rulecheck-agent.md` (the autonomous agent)\n   - `.claude/skills/rulecheck/hooks/block-dangerous.sh` (safety gate)\n   - `.claude/skills/rulecheck/hooks/slack-notify.sh` (Slack notification)\n   - `.claude/skills/rulecheck/rules-guide.md` (supporting reference)\n\n2. **Trace the delegation chain**:\n   skill → `agent: rulecheck-agent` → `isolation: worktree` + `background: true`\n\n3. **Show the hooks**: PreToolUse safety gate, PostToolUse lint auto-fix,\n   Stop Slack (command + http) + meta-judge (agent)\n\n4. **Test the safety hook**:\n   ```bash\n   echo '{\"tool_input\":{\"command\":\"git push --force\"}}' | .claude/skills/rulecheck/hooks/block-dangerous.sh\n   # Should exit 2 with error message\n   echo '{\"tool_input\":{\"command\":\"bun run lint\"}}' | .claude/skills/rulecheck/hooks/block-dangerous.sh\n   # Should exit 0\n   ```\n\n5. **Invoke**: `/rulecheck type safety`\n   - Show: background execution, user can keep working\n   - Show: the agent scanning, fixing, validating in the worktree\n   - Show: PR creation and Slack notification\n\n6. **Show the outputs**:\n   - PR on GitHub\n   - Slack notification\n   - Memory file (what was learned)\n   - Meta-judge feedback\n\n## Comparison: Before vs After\n\n| | Advisory Code-Rulecheck | Autonomous Rulecheck |\n|---|---|---|\n| Format | `.claude/agents/code-rulecheck.md` | Skill + agent + hooks |\n| Execution | Inline, blocks conversation | Background, worktree-isolated |\n| Output | Advisory report (no changes) | Actual fixes + PR |\n| Isolation | None (reads in-place) | Git worktree |\n| Safety | None | PreToolUse command blocklist |\n| Validation | None | Tests, lint, type-check on changes; CI is the full gate |\n| Notifications | None | Slack (command hook + HTTP hook) |\n| Learning | None | Persistent memory + meta-judge |\n| Autonomy | Reports findings only | Finds, fixes, validates, PRs |\n\n## Talking Points\n\n- \"This is 17 features in one skill. Each one is simple — the power is in composition.\"\n- \"The safety hook is a shell script. It reads JSON, checks a blocklist, exits 2 to block. No framework needed.\"\n- \"Memory makes the agent better over time. Each run builds on the last — it remembers what's in the backlog.\"\n- \"The meta-judge is an LLM evaluating another LLM. It writes structured feedback the agent reads next run.\"\n- \"Worktree isolation means the agent can break things safely. Your working directory is untouched.\"\n- \"Background execution means you keep working. The agent runs, creates a PR, notifies Slack — you review when ready.\"\n- \"The old code-rulecheck was advisory. This one actually fixes things. Same domain, fundamentally different capability.\"\n","isInternal":false,"tokens":2723,"sizeBytes":11437},{"name":"SKILL.md","path":".claude/skills/test-release/SKILL.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/test-release/SKILL.md","title":"test-release","category":"anthropic-skill","format":"markdown","content":"---\nname: test-release\ndescription: |\n  Verify a released archon binary works end-to-end via a specific install path.\n  Use when: cutting a new release, reproducing a user bug report on the released\n  version, or validating that a hotfix binary actually works after a re-tag.\n  Triggers: \"test the release\", \"test 0.3.1 via brew\", \"verify the curl install\",\n            \"smoke test the binary\", \"did the release binary work\",\n            \"run /test-release\", \"verify the release\".\n  NOT for: testing dev work (use bun link directly), testing unreleased changes\n  (build locally via scripts/build-binaries.sh first), or running the full\n  validate suite (bun run validate is separate).\nargument-hint: \"[brew|curl-mac|curl-vps] [optional: version to verify] [optional: vps-target]\"\n---\n\n# Test Release\n\nAutomated smoke test for a released archon binary. Covers three install paths:\n\n- `brew` — Homebrew tap on macOS (tests the formula and checksums)\n- `curl-mac` — `curl install.sh` on macOS (tests the install script, sandboxed to a temp dir)\n- `curl-vps` — `curl install.sh` on a remote Linux VPS (tests the Linux binary and full install path)\n\nEvery path installs the binary, runs a fixed smoke test suite, and cleans up. The dev `bun link` binary is never touched and remains the default `archon` on PATH throughout.\n\n**When NOT to use this skill:**\n\n- There is no release yet — build a local binary via `bash scripts/build-binaries.sh` and run it from `dist/binaries/` directly\n- You want to test the dev clone — use `bun run validate` or invoke source directly via `bun packages/cli/src/cli.ts`\n- You want to test the full server + web UI deploy flow — use the cloud-init from `deploy/cloud-init.yml` on a real VPS\n\n## Local build for pre-release QA\n\nTo build a binary locally with the exact same flags and constants that CI uses,\ninvoke `scripts/build-binaries.sh` directly. The script supports two modes:\n\n```bash\n# Multi-target mode (builds all 4 local platforms into dist/binaries/)\nVERSION=0.3.1 GIT_COMMIT=abc12345 bash scripts/build-binaries.sh\n\n# Single-target mode (matches one CI matrix job)\nVERSION=0.3.1 \\\nGIT_COMMIT=abc12345 \\\nTARGET=bun-darwin-arm64 \\\nOUTFILE=dist/test-archon-darwin-arm64 \\\nbash scripts/build-binaries.sh\n\n# Verify the binary — use the path from the mode you built:\n#   multi-target → ./dist/binaries/archon-darwin-arm64\n#   single-target → the OUTFILE you passed above\n./dist/test-archon-darwin-arm64 version\n# Expected: Archon CLI v0.3.1, Build: binary, Git commit: abc12345\n```\n\nRun this **before tagging a release** to catch build-time-constant issues\nlocally. The script is the canonical entry point — both local dev and the\nrelease workflow call it the same way, so a green local build means the CI\nbuild will exercise the same code path.\n\n## Phase 1 — Determine scope\n\nParse the arguments. The skill takes up to three:\n\n1. **Install path** (`brew` | `curl-mac` | `curl-vps`): which install flow to exercise\n2. **Expected version** (optional): the version tag the release should report, e.g. `0.3.1`. If not provided, fetch it:\n\n```bash\ngh release list --repo coleam00/Archon --limit 1 --json tagName --jq '.[0].tagName'\n```\n\n3. **VPS target** (only for `curl-vps`): SSH target in the form `user@host` or `host` (uses default SSH config)\n\nIf any argument is missing, ask the user for clarification BEFORE doing anything. Never guess the install path or the expected version.\n\nConfirm the plan with the user before proceeding to Phase 2. Output should look like:\n\n```\nAbout to test:\n  Path:     brew (Homebrew tap on macOS)\n  Version:  0.3.1 (expected)\n  Cleanup:  will uninstall after tests (brew uninstall + untap)\n            If `archon-stable` symlink is detected in Phase 2, it will be\n            restored at the end of Phase 5 by reinstalling the tap formula.\n\nProceed? (y/N)\n```\n\nDo not continue without explicit confirmation. Release testing touches install state and the user should be aware.\n\n## Phase 2 — Pre-flight\n\nBefore touching anything:\n\n1. Capture the current dev binary state for reference:\n\n```bash\nwhich -a archon\narchon version 2>&1 | head -5\n```\n\nRecord the path and version of the dev binary so the final report can show \"dev binary was untouched\".\n\n2. Verify prerequisites for the chosen path:\n\n   - **brew**: `brew --version` must succeed. If not, abort with \"Homebrew not installed — see https://brew.sh/\"\n   - **curl-mac**: `curl --version` must succeed (effectively always true on macOS)\n   - **curl-vps**: `ssh <target> 'uname -a'` must succeed. If not, abort with \"Cannot SSH to <target>\". Also verify `ssh <target> 'command -v curl'` returns a path.\n\n3. Confirm the release exists on GitHub:\n\n```bash\ngh release view v<version> --repo coleam00/Archon --json tagName,assets --jq '{tag: .tagName, assetCount: (.assets | length)}'\n```\n\nIf the release does not exist or has no assets, abort with a clear message. Do not proceed to install a non-existent release.\n\n4. **Detect persistent `archon-stable` install (brew path only).** If the user has renamed a prior brew install to `archon-stable` (the dual-homebrew pattern — see `~/.config/fish/functions/brew-upgrade-archon.fish`), Phase 5's `brew uninstall` will wipe it. Capture the state so Phase 5b can restore it:\n\n```bash\nARCHON_STABLE_WAS_INSTALLED=\"\"\nif [ -L /opt/homebrew/bin/archon-stable ] || [ -L /usr/local/bin/archon-stable ]; then\n  ARCHON_STABLE_WAS_INSTALLED=\"yes\"\n  echo \"Detected persistent archon-stable — will restore after Phase 5 uninstall.\"\nfi\n```\n\nExport `ARCHON_STABLE_WAS_INSTALLED` into the environment used by Phase 5b. Only applies to the `brew` path — `curl-mac` and `curl-vps` don't go through brew and don't disturb `archon-stable`.\n\n## Phase 3 — Install\n\n### Path: brew\n\n```bash\nbrew tap coleam00/archon\nbrew install coleam00/archon/archon\nBINARY=\"$(brew --prefix coleam00/archon/archon)/bin/archon\"\n```\n\nCapture `$BINARY` for Phase 4. Verify the file exists and is executable.\n\n### Path: curl-mac\n\nInstall to a dedicated tmp directory so the dev `bun link` binary stays on PATH unchanged:\n\n```bash\nINSTALL_DIR=/tmp/archon-test-release-$(date +%s)\nmkdir -p \"$INSTALL_DIR\"\ncurl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | INSTALL_DIR=\"$INSTALL_DIR\" bash\nBINARY=\"$INSTALL_DIR/archon\"\n```\n\n> **The env var must prefix `bash`, not `curl`.** In `VAR=x cmd1 | cmd2` the\n> assignment applies only to `cmd1`, so `INSTALL_DIR=… curl … | bash` sets it for\n> the *download* and leaves the *installer* on its `/usr/local/bin` default. The\n> failure is confusing rather than obvious: the script downloads and verifies the\n> checksum, then demands sudo and dies with\n> `sudo: a terminal is required to read the password`. Observed on the 0.7.1 test.\n>\n> `scripts/install.sh` carries the same broken form in its own header example\n> (`INSTALL_DIR=~/.local/bin curl -fsSL ... | bash`), so users copying it hit this\n> too. Fix both together; see issue #2436.\n\nVerify `$BINARY` exists and is executable. Capture the install directory for cleanup.\n\n### Path: curl-vps\n\nRun the install script on the VPS:\n\n```bash\nssh <target> 'curl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash'\n```\n\nDetermine where the binary landed — `install.sh` uses `/usr/local/bin/archon` by default, or falls back to `$HOME/.local/bin/archon` if `/usr/local/bin` is not writable:\n\n```bash\nssh <target> 'command -v archon'\n```\n\nCapture the remote path as `$REMOTE_BINARY`. For the rest of Phase 4, wrap every command as `ssh <target> '<cmd>'`.\n\n### Capture SHA256 and version\n\nImmediately after install, capture:\n\n```bash\n# Local paths (brew / curl-mac)\nshasum -a 256 \"$BINARY\" | awk '{print $1}'\n\"$BINARY\" version 2>&1\n\n# Remote path (curl-vps)\nssh <target> \"shasum -a 256 $REMOTE_BINARY || sha256sum $REMOTE_BINARY\" | awk '{print $1}'\nssh <target> \"$REMOTE_BINARY version\" 2>&1\n```\n\nRecord both for the report. The SHA256 lets us confirm later that a user reporting a bug is running the exact same artifact we tested.\n\n## Phase 4 — Smoke tests\n\nRun these in order against `$BINARY` (or `ssh <target> $REMOTE_BINARY` for curl-vps). **Always use the full binary path, never the `archon` on PATH**, so there is no ambiguity about which binary is under test.\n\nEach test should capture the full command output for the final report. If a test fails, continue to the next test (so the report is complete) but mark the overall result as FAIL.\n\n### Test 1 — Version reports correctly\n\n```bash\n\"$BINARY\" version\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- Output contains `Archon CLI v<expected-version>`\n- Output contains `Build: binary` (not `Build: source (bun)`)\n- Output contains a non-`unknown` git commit (i.e., `Git commit: <sha>`)\n\n**Common failures:**\n\n- Exit code non-zero → pino-pretty crash (#960) or similar startup failure\n- Wrong version reported → binary is stale or the build script failed to update `bundled-build.ts`\n- `Build: source (bun)` → `BUNDLED_IS_BINARY` was not set to `true` during the build (regression of #979)\n- `Git commit: unknown` → build script did not capture the commit\n\n### Test 2 — Bundled workflows load\n\nFrom 0.12.0, release binaries ship without providers, and a config that names one fails to load until it's installed. Run `\"$BINARY\" provider add claude codex pi` (the ones the config uses) before this test. That is the documented upgrade step, and it is part of what this test checks.\n\nCreate a temporary git repository so the CLI has something to operate on:\n\n```bash\nTESTREPO=/tmp/archon-test-repo-$(date +%s)\nmkdir -p \"$TESTREPO\"\ncd \"$TESTREPO\"\ngit init -q\ngit commit -q --allow-empty -m init\n\"$BINARY\" workflow list\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- Output lists the bundled `sdlc` pack workflows: archon-triage, archon-investigate, archon-plan, archon-implement, archon-pr, archon-review, archon-validate, archon-deliver, archon-upkeep, archon-ship\n- No errors about missing workflow files or JSON parse failures\n\n**Common failures:**\n\n- Empty list → bundled defaults were not embedded in the binary (regression of the `isBinaryBuild` detection path)\n- `Not in a git repository` → working directory handling bug\n- Parse errors → the embedded JSON is corrupt or stale\n\n### Test 3 — SDK path works (archon-investigate workflow)\n\n**Prerequisite.** Compiled binaries require Claude Code installed on the host and a configured binary path. Before running this test, ensure one of:\n\n```bash\n# Option A — env var (easy for ad-hoc testing)\n# After the native installer (Anthropic's default):\nexport CLAUDE_BIN_PATH=\"$HOME/.local/bin/claude\"\n# Or after npm global install:\nexport CLAUDE_BIN_PATH=\"$(npm root -g)/@anthropic-ai/claude-code/cli.js\"\n\n# Option B — config file (persistent)\n#   Add to ~/.archon/config.yaml:\n#   assistants:\n#     claude:\n#       claudeBinaryPath: /absolute/path/to/claude\n```\n\nThen in the same `$TESTREPO`:\n\n```bash\n\"$BINARY\" workflow run archon-investigate --no-worktree \"say hello and nothing else\" 2>&1 | tee /tmp/archon-test-investigate.log\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- The Claude subprocess spawns successfully (no `spawn EACCES`, `ENOENT`, or `process exited with code 1` in the early output)\n- No `Claude Code CLI not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)\n- A response is produced (any response — even just \"hello\" — proves the SDK round-trip works)\n\n**Common failures:**\n\n- `Claude Code not found` → `CLAUDE_BIN_PATH` / `claudeBinaryPath` is unset or points at a non-existent file. Fix the path and re-run.\n- `Module not found \"/Users/runner/...\"` → regression of #1210: the resolver was bypassed and the SDK's `import.meta.url` fallback leaked a build-host path. Investigate `packages/providers/src/claude/provider.ts` and the resolver.\n- `Credit balance is too low` → auth is pointing at an exhausted API key (check `CLAUDE_USE_GLOBAL_AUTH` and `~/.archon/.env`)\n- `unable to determine transport target for \"pino-pretty\"` → #960 regression, binary crashes on TTY\n- `package.json not found (bad installation?)` → #961 regression, `isBinaryBuild` detection broken\n- Process exits before producing output → generic spawn failure, capture stderr\n\n### Test 3b — Resolver error path (run without `CLAUDE_BIN_PATH`)\n\nQuickly verify the resolver fails loud when nothing is configured:\n\n```bash\n(unset CLAUDE_BIN_PATH; \"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" 2>&1 | tee /tmp/archon-test-no-path.log)\n```\n\n**Pass criteria (when no `~/.archon/config.yaml` configures `claudeBinaryPath`):**\n\n- Error message contains `Claude Code not found`\n- Error message mentions both `CLAUDE_BIN_PATH` and `claudeBinaryPath` as remediation options\n- No `Module not found` stack traces referencing the CI filesystem\n\nIf you *do* have `claudeBinaryPath` set globally, skip this test or temporarily rename `~/.archon/config.yaml`.\n\n### Test 4 — Env-leak guard strips sensitive keys from .env\n\nCreate a second throwaway repo with a fake sensitive key:\n\n```bash\nLEAKREPO=/tmp/archon-test-leak-$(date +%s)\nmkdir -p \"$LEAKREPO\"\ncd \"$LEAKREPO\"\ngit init -q\ngit commit -q --allow-empty -m init\nprintf 'ANTHROPIC_API_KEY=sk-ant-test-fake\\n' > .env\n\"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" 2>&1 | tee /tmp/archon-test-leak.log\n```\n\n**Pass criteria** (current behaviour — the guard **strips**, it does not refuse):\n\n- Output contains a strip line naming the repo and the source file, of the form:\n  `[archon] stripped N keys from <repo> (.env) to prevent target repo env from leaking into Archon processes`\n- `N` equals the number of keys planted (1 for the `.env` above) — a count of `0`\n  means the file was not read at all, which is a real regression\n- The workflow is then allowed to proceed and complete normally\n\nAssert on the strip line — pinned to **this** repo and **this** key count, so a\nstrip from some other directory cannot produce a false pass. Capture the exit\nstatus too: under the strip design the workflow is expected to succeed, so a\nnon-zero exit is its own failure.\n\n```bash\n\"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" > /tmp/archon-test-leak.log 2>&1\nleak_exit=$?\n\n# $LEAKREPO may be a symlinked path (/tmp -> /private/tmp on macOS); the binary\n# logs the resolved form, so compare against that.\nresolved_repo=$(cd \"$LEAKREPO\" && pwd -P)\nexpected=\"[archon] stripped 1 keys from ${resolved_repo} (.env) to prevent target repo env from leaking into Archon processes\"\n\nif [ \"$leak_exit\" -ne 0 ]; then\n  echo \"FAIL: workflow exited $leak_exit — the guard strips and proceeds, it should not abort\"\nelif grep -qxF \"$expected\" /tmp/archon-test-leak.log; then\n  echo \"PASS: env-leak guard stripped exactly the planted key from this repo\"\nelse\n  echo \"FAIL: expected strip line absent. Got:\"\n  grep -F '[archon] stripped' /tmp/archon-test-leak.log || echo \"  (no strip line at all — guard inactive)\"\nfi\n```\n\nAn exact-match assertion is deliberate here. A looser pattern such as\n`stripped [1-9][0-9]* keys .*\\.env` passes on **any** positive count from **any**\npath, so a strip belonging to a different repository — or a count inflated by an\nunrelated `.env` — reads as success while the regression it is meant to catch\ngoes unnoticed.\n\n> **History — do not re-assert the old criteria.** This test previously expected a\n> *refusal* (`Cannot add codebase` / `Cannot run workflow`, non-zero exit), which\n> was the #1036/#1038/#983 behaviour. The current design is the two-layer strip\n> guard: the target repo's `.env` is read, dangerous keys are removed from the\n> environment handed to Archon subprocesses, and the run continues. On the 0.7.1\n> test the old assertions reported FAIL against a binary whose guard was working\n> correctly — it stripped exactly the 1 planted key. Verify the security property\n> (the key never reaches the subprocess), not the obsolete remediation text.\n\n**Common failures:**\n\n- No strip line at all → the guard is not active; the `.env` is reaching subprocesses\n- `stripped 0 keys` → the file was located but not parsed\n- Strip line names the wrong file or repo → path resolution regression\n\nClean up the leak test repo:\n\n```bash\nrm -rf \"$LEAKREPO\"\n```\n\n### Test 5 — Isolation list works (sanity check)\n\nIn the same `$TESTREPO`:\n\n```bash\n\"$BINARY\" isolation list\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- No errors (the list may be empty if no worktrees have been created, which is fine)\n\nThis catches regressions in the isolation subsystem that would not surface from the other tests.\n\n### Test 6 — Cleanup test repos\n\n```bash\nrm -rf \"$TESTREPO\"\n```\n\nFor `curl-vps` path, also clean up any remote test repos created via SSH.\n\n## Phase 5 — Uninstall\n\n**Always run uninstall, even if Phase 4 failed.** The goal is to leave the system in the same state as before the test.\n\n### Path: brew\n\n```bash\nbrew uninstall coleam00/archon/archon\nbrew untap coleam00/archon\n```\n\nVerify the dev binary is still the default:\n\n```bash\nwhich -a archon\n# should show only the ~/.bun/bin/archon path, not a brew path\n\narchon version | head -1\n# should match the dev version captured in Phase 2\n```\n\n**Restore `archon-stable` if it existed before the test** (dual-homebrew pattern — see Phase 2 item 4):\n\n```bash\nif [ -n \"$ARCHON_STABLE_WAS_INSTALLED\" ]; then\n  echo \"Restoring archon-stable (detected before test)...\"\n  brew tap coleam00/archon\n  brew install coleam00/archon/archon\n  BREW_BIN=\"$(brew --prefix)/bin\"\n  if [ -e \"$BREW_BIN/archon\" ]; then\n    mv \"$BREW_BIN/archon\" \"$BREW_BIN/archon-stable\"\n    echo \"archon-stable restored: $(archon-stable version 2>/dev/null | head -1)\"\n  else\n    echo \"WARNING: brew install succeeded but $BREW_BIN/archon missing — check formula\"\n  fi\nfi\n```\n\n> **Note on the restored version**: this reinstalls from whatever the tap currently ships, which is typically the release you just tested (so `archon-stable` ends up at the newly-tested version). That's usually what the operator wants — you just verified the new release works, and you want `archon-stable` pointed at it. If you were testing an older version for back-version QA, the restored `archon-stable` will be the *current* tap formula, not the pre-test version. For that rare case, the operator should re-run `brew-upgrade-archon` manually after the test.\n\n### Path: curl-mac\n\n```bash\nrm -rf \"$INSTALL_DIR\"\n```\n\n### Path: curl-vps\n\n```bash\nssh <target> \"sudo rm -f /usr/local/bin/archon || rm -f \\$HOME/.local/bin/archon\"\n```\n\nOptional: the user may want to LEAVE the VPS binary installed for ongoing QA. Ask before removing.\n\n## Phase 6 — Report\n\nProduce a structured report with:\n\n- **Header**: release version tested, install path, timestamp, SHA256 of the tested binary\n- **Environment**: dev binary path + version (proof the dev install was not disturbed)\n- **Test results table**: one row per test with PASS / FAIL / SKIP\n- **Captured output**: for any FAIL, include the exact command, exit code, and last 20 lines of stderr/stdout\n- **Overall verdict**: PASS if all tests passed, FAIL if any test failed\n- **Next steps**: if FAIL, suggest concrete actions (file a hotfix issue, re-tag, check the build workflow, etc.)\n\nExample PASS report:\n\n```\nTest Release Report — archon v0.3.1 via brew\n────────────────────────────────────────────\nTested at:    2026-04-08 15:42 UTC\nBinary SHA:   e62eb73547b3740d56f242859b434a91d3830360a0d18f14de383da0fd7a0be6\nBinary path:  /opt/homebrew/Cellar/archon/0.3.1/bin/archon\nDev binary:   /Users/rasmus/.bun/bin/archon → ../install/.../cli.ts (unchanged)\n\n  [PASS]  Test 1  version reports 0.3.1, Build: binary, commit abc1234\n  [PASS]  Test 2  workflow list returned 21 bundled workflows\n  [PASS]  Test 3  workflow run archon-investigate produced output\n  [PASS]  Test 4  env-leak guard stripped exactly the planted key\n  [PASS]  Test 5  isolation list executed without errors\n  [PASS]  Cleanup brew uninstall + untap clean, dev binary unchanged\n\nOverall: PASS\n\nThis release is safe to announce. Next steps:\n  - Update release notes on GitHub if not done already\n  - Announce on whatever channels you use\n```\n\nExample FAIL report:\n\n```\nTest Release Report — archon v0.3.1 via curl-vps\n────────────────────────────────────────────────\nTested at:    2026-04-08 15:42 UTC\nBinary SHA:   0cf83e15e6af228e3c3473467ca30fa7525b6d7069818d85f97a115ea703d708\nBinary path:  user@vps:/usr/local/bin/archon\nDev binary:   /Users/rasmus/.bun/bin/archon (unchanged)\n\n  [PASS]  Test 1  version reports 0.3.1, Build: binary\n  [FAIL]  Test 2  workflow list returned 0 workflows\n\n    Command:  archon workflow list\n    Exit:     0\n    Output:\n      Discovering workflows in: /tmp/archon-test-repo-1712590923\n      Found 0 workflow(s):\n\n  [SKIP]  Test 3  SDK test skipped because Test 2 failed\n  [SKIP]  Test 4  env-leak guard test skipped because Test 2 failed\n  [PASS]  Test 5  isolation list executed without errors\n  [PASS]  Cleanup VPS binary removed\n\nOverall: FAIL\n\nLikely cause: bundled workflows were not embedded in the binary.\nCheck the build workflow for missing asset embedding, or verify that\nBUNDLED_WORKFLOWS in packages/workflows/src/defaults/bundled-defaults.ts\nwas populated at build time.\n\nNext steps:\n  1. File a P0 hotfix issue with the captured output\n  2. Do NOT announce v0.3.1 until the hotfix ships as v0.3.2\n  3. Consider adding a CI guard that blocks releases if BUNDLED_WORKFLOWS is empty\n```\n\n## Key behaviors\n\n- **Never touch the dev `bun link` binary.** Always use the installed binary path for Phase 4 tests. Verify before and after.\n- **Clean up on failure.** If Phase 4 fails mid-way, still run Phase 5 so the next run starts clean.\n- **Capture SHA256 immediately after install.** This lets bug reports reference the exact artifact under test.\n- **Explicit confirmation before install.** Never surprise the user by installing a second binary.\n- **Report the dev binary state in both preamble and postamble.** Proof that the test did not disturb the dev environment.\n- **Exit non-zero if any test failed.** The skill should propagate failure so automated wrappers (CI, scripts) can detect it.\n\n## Related\n\n- `scripts/build-binaries.sh` — builds the binary artifacts that end up in releases\n- `.github/workflows/release.yml` — builds and publishes the binary on tag push\n- `homebrew/archon.rb` — Homebrew tap formula (updated per release)\n- `scripts/install.sh` — the curl install script\n- `scripts/install-local.sh` / `install-local.ps1` — local-file install harnesses (for pre-release QA of binaries built from a branch, not from GitHub releases)\n- `/release` skill — the release procedure itself (opposite side of the flow)\n","frontmatter":{"name":"test-release","description":"Verify a released archon binary works end-to-end via a specific install path.\nUse when: cutting a new release, reproducing a user bug report on the released\nversion, or validating that a hotfix binary actually works after a re-tag.\nTriggers: \"test the release\", \"test 0.3.1 via brew\", \"verify the curl install\",\n          \"smoke test the binary\", \"did the release binary work\",\n          \"run /test-release\", \"verify the release\".\nNOT for: testing dev work (use bun link directly), testing unreleased changes\n(build locally via scripts/build-binaries.sh first), or running the full\nvalidate suite (bun run validate is separate).\n","argument-hint":"[brew|curl-mac|curl-vps] [optional: version to verify] [optional: vps-target]"},"isInternal":false,"tokens":5999,"sizeBytes":22905},{"name":"SKILL.md","path":".claude/skills/triage/SKILL.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/triage/SKILL.md","title":"triage","category":"anthropic-skill","format":"markdown","content":"---\nname: triage\ndescription: |\n  Triage GitHub issues by applying type, effort, priority, and area labels.\n  Runs in an isolated context to avoid polluting the main conversation with\n  issue details. Delegates to a specialized triage agent with label validation hooks.\nargument-hint: \"[unlabeled|all|N|N-M]\"\ndisable-model-invocation: true\ncontext: fork\nagent: triage-agent\nallowed-tools: Bash(gh *), Read, Glob, Grep\n---\n\n# Triage GitHub Issues\n\nTriage issues for this repository by applying appropriate labels.\n\n## Repository Context\n\n- **Current repo**: !`gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo \"unknown\"`\n- **Open issues**: !`gh issue list --state open --json number --jq 'length' 2>/dev/null || echo \"?\"`\n- **Existing labels**: !`gh label list --json name -q '.[].name' 2>/dev/null | head -20 || echo \"none found\"`\n\n---\n\n## Scope\n\nDetermine which issues to triage based on the arguments: **$ARGUMENTS**\n\n| Argument | Behavior |\n|----------|----------|\n| (empty) | Only unlabeled issues (default) |\n| `unlabeled` | Only issues without any labels |\n| `all` | All open issues |\n| `N` | Specific issue (e.g., `67`) |\n| `N-M` | Range of issues inclusive (e.g., `60-67`) |\n\n## Process\n\n1. **Fetch available labels** — run `gh label list --json name,description` to understand\n   the label taxonomy. Labels are organized into type, effort, priority, and area categories.\n\n2. **Fetch target issues** — based on the scope above. For each issue, fetch the full body:\n   ```bash\n   gh issue view {number} --json number,title,body,labels\n   ```\n\n3. **For each issue**:\n   - Read the title and full body carefully\n   - If needed, explore the codebase (`Glob`, `Grep`, `Read`) to understand the affected code\n   - Classify: one type, one effort, one priority, one or more areas\n   - Track relationships with other issues (duplicates, related, blocking)\n   - Apply labels:\n     ```bash\n     gh issue edit {number} --add-label \"type,effort/level,P#,area.domain\"\n     ```\n   - Skip issues that already have complete labeling (type + effort + priority + area)\n   - For partially labeled issues, only add missing label categories\n\n4. **Output a triage summary**:\n\n   ```\n   ## Triage Summary\n\n   | Issue | Title | Labels Applied | Reasoning |\n   |-------|-------|----------------|-----------|\n   | #67 | ... | bug, effort/low, P1, core.config | ... |\n\n   **Totals:**\n   - Issues triaged: X\n   - Already labeled (skipped): Y\n   - By priority: P0(n), P1(n), P2(n), P3(n)\n\n   ## Relationships Discovered\n\n   | Issues | Relationship | Notes |\n   |--------|--------------|-------|\n   | #61, #62 | Related | Both involve config/logging UX |\n   ```\n\n## Rules\n\n- **Don't hardcode labels** — always fetch current labels first, they may change\n- **Respect existing labels** — don't remove labels, only add missing ones\n- **Check issue body** — titles alone aren't enough context\n- **Use the codebase** — if understanding a relationship requires seeing how modules connect, look\n- **When uncertain, note it** — flag ambiguous issues in the summary rather than guessing\n","frontmatter":{"name":"triage","description":"Triage GitHub issues by applying type, effort, priority, and area labels.\nRuns in an isolated context to avoid polluting the main conversation with\nissue details. Delegates to a specialized triage agent with label validation hooks.\n","argument-hint":"[unlabeled|all|N|N-M]","disable-model-invocation":true,"context":"fork","agent":"triage-agent","allowed-tools":"Bash(gh *), Read, Glob, Grep","allowedTools":["Bash(gh","*),","Read,","Glob,","Grep"],"disableModelInvocation":true},"allowedTools":["Bash(gh","*),","Read,","Glob,","Grep"],"isInternal":false,"tokens":796,"sizeBytes":3091},{"name":"workshop-guide.md","path":".claude/skills/triage/workshop-guide.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/skills/triage/workshop-guide.md","title":"Triage Skill","category":"anthropic-skill","format":"markdown","content":"# Triage — Workshop Guide\n\n## What This Skill Demonstrates\n\nA command upgraded to a skill + custom agent pair, showcasing **context forking**,\n**custom agent delegation**, **prompt hooks as guardrails**, and **restricted toolsets**.\n\n**Full demo steps**: See [part2-guide.md](../../workshop/part2-guide.md) — Feature 7.\n\n## Architecture\n\n```\nUser invokes /triage 42\n        │\n        ▼\n┌─────────────────────┐\n│  SKILL.md           │  context: fork + agent: triage-agent\n│  (entry point)      │  Injects repo context via !`command`\n│  allowed-tools:     │  Restricts tools to gh CLI + read-only\n│    Bash(gh *)       │\n│    Read, Glob, Grep │\n└────────┬────────────┘\n         │ forks into isolated context\n         ▼\n┌─────────────────────┐\n│  triage-agent.md    │  Custom agent with PostToolUse hook\n│  (.claude/agents/)  │  Hook: type \"prompt\" validates every\n│                     │  gh issue edit command to ensure\n│  model: sonnet      │  all 4 label categories are present\n│  hooks: PostToolUse │\n└─────────────────────┘\n         │\n         ▼\n   Summary returned to main conversation\n   (intermediate tool calls discarded)\n```\n\n## Features to Walk Through\n\n### 1. `context: fork` — Isolated Execution\n\n**What is it?** A skill frontmatter field that runs the skill in a **new isolated\nsubagent context** instead of inline in the main conversation. The skill body\nbecomes the task prompt for the subagent. When the subagent finishes, only its\nfinal response returns to the main conversation — all intermediate tool calls,\nfile reads, and reasoning are discarded.\n\n**Key properties:**\n- The forked context has **no access** to the main conversation history\n- Only the **final summary** flows back — intermediate work is discarded\n- `CLAUDE.md` files are still loaded in the forked context; this project's root file is only a pointer to `AGENTS.md`, which holds the project rules\n- The fork cannot spawn further subagents (no nesting)\n- Forked skills must contain **concrete tasks**, not just reference material\n\n**In this skill**: Triaging 20 issues could consume 50K+ tokens of context\n(issue bodies, label lists, grep results). Without forking, all of that pollutes\nthe main conversation. With forking, only the structured triage summary returns.\n\n### 2. `agent: triage-agent` — Custom Agent Delegation\n\n**What is it?** When a skill has `context: fork`, the `agent:` field controls\nwhich agent type runs the forked context. Options:\n\n| Agent | Model | Tools | Use case |\n|-------|-------|-------|----------|\n| `Explore` | Haiku (fast, cheap) | Read-only | File search, codebase exploration |\n| `Plan` | Inherits from main | Read-only | Planning and research |\n| `general-purpose` | Inherits from main | All tools | Complex multi-step tasks |\n| Any custom agent | Whatever agent defines | Whatever agent defines | Domain-specific work |\n\nCustom agents are `.md` files in `.claude/agents/` with their own frontmatter\n(model, tools, hooks, memory, permissions) and a markdown body that serves as\nthe system prompt.\n\n**In this skill**: Instead of `agent: general-purpose`, this skill delegates to\n`triage-agent` — a custom agent defined in `.claude/agents/triage-agent.md` with:\n- A triage specialist system prompt (label taxonomy, classification rules)\n- Its own PostToolUse hook for label validation\n- Restricted tool access\n\n**Teaching moment**: Skills define *what* to do (scope, context, arguments).\nAgents define *how* to do it (persona, tools, guardrails). Separating them\nmakes both composable — the same agent could be used by multiple skills.\n\n### 3. `type: \"prompt\"` Hook — LLM as Guardrail\n\n**What is it?** One of four hook handler types. A prompt hook sends the event\ncontext to an LLM (Haiku by default) for a single-turn yes/no evaluation.\nThe LLM must return `{\"ok\": true}` to allow the action or\n`{\"ok\": false, \"reason\": \"...\"}` to block it.\n\n**How it differs from the other hook types:**\n- `type: \"command\"` — runs a shell script, blocks via exit code 2\n- `type: \"http\"` — POSTs JSON to a URL, blocks via JSON response\n- `type: \"prompt\"` — asks an LLM to evaluate, blocks via `{\"ok\": false}`\n- `type: \"agent\"` — spawns a subagent with Read/Grep/Glob to verify conditions\n\nPrompt hooks are ideal for **semantic validation** — cases where the decision\nrequires understanding intent, not just pattern matching.\n\n**In this skill**: The triage agent has a PostToolUse hook on Bash. When it\ndetects a `gh issue edit --add-label` command, the LLM verifies all four label\ncategories (type, effort, priority, area) are present.\n\n**What happens if validation fails**: The LLM returns\n`{\"ok\": false, \"reason\": \"Missing effort label\"}` and Claude receives that\nfeedback. It can fix the label application before moving to the next issue.\n\n**Smart filtering**: The prompt instructs the LLM to return `{\"ok\": true}`\nfor non-label commands (`gh issue list`, `gh label list`) — no false positives.\n\n### 4. `allowed-tools` — Restricted Toolset\n\n**What is it?** A skill frontmatter field that controls which tools Claude can\nuse **without per-use permission prompts** when the skill is active. It acts as\nboth an allowlist (pre-approves listed tools) and a restriction (only these\ntools are available in the forked context).\n\n**Wildcard patterns** are supported:\n- `Bash(gh *)` — only `gh` subcommands, no arbitrary shell\n- `Bash(npm *)` — only npm commands\n- `Read, Glob, Grep` — read-only codebase access\n\n**In this skill**: `allowed-tools: Bash(gh *), Read, Glob, Grep` means the\nagent can interact with GitHub and read the codebase, but can't run arbitrary\nshell commands, write files, or edit code. This is a security boundary.\n\n### 5. `disable-model-invocation: true`\n\n**What is it?** Prevents Claude from auto-invoking this skill. By default, Claude\nreads skill descriptions and can invoke them when it determines they're relevant.\nSetting this to `true` means only the user can trigger it via `/triage`.\n\n**In this skill**: Triage modifies GitHub issues (side effects). Only the user\nshould decide when to apply labels.\n\n### 6. Dynamic Context Injection (`!`command``)\n\n**What is it?** Shell commands in the skill body using `!`command`` syntax that\nexecute as preprocessing — before Claude sees the prompt. The command's stdout\nreplaces the placeholder. This is not tool use; it happens at skill load time.\n\n**In this skill**: Three commands inject live repo data:\n- Repository name (`gh repo view`)\n- Open issue count (`gh issue list | length`)\n- Existing label taxonomy (`gh label list`)\n\nThe agent starts with real context instead of instructions to go fetch it.\n\n## Live Demo Steps\n\n1. **Show the architecture** — open both files side by side:\n   - `.claude/skills/triage/SKILL.md` (the skill entry point)\n   - `.claude/agents/triage-agent.md` (the specialist agent)\n\n2. **Point out the delegation chain**: skill → `context: fork` → `agent: triage-agent`\n\n3. **Invoke on a single issue**: `/triage 42`\n   - Show the `statusMessage` spinner: \"Validating label application...\"\n   - Point out: the main conversation stays clean while triage happens in the fork\n\n4. **Show the summary** that returns to the main conversation\n   - All the intermediate work (fetching issues, reading code, applying labels) is gone\n   - Only the structured summary survives\n\n5. **Show the hook validation** — if a label application was incomplete, the prompt\n   hook would have caught it and Claude would have self-corrected\n\n## Comparison: Before vs After\n\n| | Old Command | New Skill + Agent |\n|---|---|---|\n| Format | `.claude/commands/archon/triage.md` | Skill + `.claude/agents/triage-agent.md` |\n| Execution | Inline (pollutes main context) | Forked (isolated context window) |\n| Validation | None | Prompt hook validates every label application |\n| Tool access | All Bash commands | `Bash(gh *)` only — no arbitrary shell |\n| Agent persona | Generic | Specialized triage agent with domain knowledge |\n| Context cost | All issue data stays in conversation | Only summary returns |\n\n## Talking Points\n\n- \"Context forking is about information hygiene — the triage work happens in a disposable context.\"\n- \"Custom agents let you embed domain expertise. The triage rules live in the agent, the scope lives in the skill.\"\n- \"The prompt hook is an LLM guardrail — it catches incomplete label applications before they reach GitHub.\"\n- \"`Bash(gh *)` is a security boundary — the agent can interact with GitHub but can't run arbitrary commands.\"\n- \"This pattern — skill as entry point, custom agent as specialist — is how you build composable workflows.\"\n","isInternal":false,"tokens":2094,"sizeBytes":8838},{"name":"discord.md","path":"packages/docs-web/src/content/docs/adapters/community/discord.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/community/discord.md","title":"Community Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Discord\ndescription: Connect Archon to Discord for AI coding assistance in servers and DMs.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\n:::note\nDiscord is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to Discord so you can interact with your AI coding assistant from any Discord server or DM.\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Discord account\n- \"Manage Server\" permission on the Discord server you want to add the bot to\n\n## Create Discord Bot\n\n1. Visit [Discord Developer Portal](https://discord.com/developers/applications)\n2. Click \"New Application\" > Enter a name > Click \"Create\"\n3. Go to the \"Bot\" tab in the left sidebar\n4. Click \"Add Bot\" > Confirm\n\n## Get Bot Token\n\n1. Under the Bot tab, click \"Reset Token\"\n2. Copy the token (starts with a long alphanumeric string)\n3. **Save it securely** -- you won't be able to see it again\n\n## Enable Message Content Intent (Required)\n\n1. Scroll down to \"Privileged Gateway Intents\"\n2. Enable **\"Message Content Intent\"** (required for the bot to read messages)\n3. Save changes\n\n:::caution\nSkipping this step causes Discord to reject the bot's connection with\n`Used disallowed intents`. Archon will log\n`discord.start_failed_continuing_without_adapter` and keep the rest of\nthe server running, but the Discord adapter will be unavailable until\nthe intent is enabled and the server is restarted.\n:::\n\n## Invite Bot to Your Server\n\n1. Go to \"OAuth2\" > \"URL Generator\" in the left sidebar\n2. Under \"Scopes\", select:\n   - `bot`\n3. Under \"Bot Permissions\", select:\n   - Send Messages\n   - Read Message History\n   - Create Public Threads (optional, for thread support)\n   - Send Messages in Threads (optional, for thread support)\n4. Copy the generated URL at the bottom\n5. Paste it in your browser and select your server\n6. Click \"Authorize\"\n\n**Note:** You need \"Manage Server\" permission to add bots.\n\n## Set Environment Variable\n\n```ini\nDISCORD_BOT_TOKEN=your_bot_token_here\n```\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users, enable Developer Mode in Discord:\n1. User Settings > Advanced > Enable \"Developer Mode\"\n2. Right-click on users > \"Copy User ID\"\n3. Add to environment:\n\n```ini\nDISCORD_ALLOWED_USER_IDS=123456789012345678,987654321098765432\n```\n\n## Configure Streaming Mode (Optional)\n\n```ini\nDISCORD_STREAMING_MODE=batch  # batch (default) | stream\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Configure Mention Requirement (Optional)\n\nBy default the bot only activates in servers when @mentioned (DMs are exempt). On single-user or private servers you can opt out so the bot responds to any authorized message:\n\n```ini\nDISCORD_REQUIRE_MENTION=false  # true (default) | false\n```\n\nOnly the literal value `false` disables the mention requirement. Mentions that are present are still stripped from the message.\n\n## Usage\n\nSelect a registered project by submitting `/setproject <name>` in the conversation or thread where you want to work. To register a local directory first, submit `/register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\nThe bot responds to:\n- **Direct Messages**: Just send messages directly\n- **Server Channels**: @mention the bot (e.g., `@YourBotName help me with this code`) — or any authorized message when `DISCORD_REQUIRE_MENTION=false`\n- **Threads**: Bot maintains context in thread conversations\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","frontmatter":{"title":"Discord","description":"Connect Archon to Discord for AI coding assistance in servers and DMs.","category":"adapters","area":"adapters","audience":["user","operator"],"status":"current","sidebar":{"order":5}},"isInternal":false,"tokens":1021,"sizeBytes":4405},{"name":"gitea.md","path":"packages/docs-web/src/content/docs/adapters/community/gitea.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/community/gitea.md","title":"Community Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Gitea\ndescription: Connect Archon to a Gitea instance for issue and PR automation.\ncategory: adapters\narea: adapters\naudience: [operator]\nsidebar:\n  order: 6\n---\n\n:::note\nGitea is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to a self-hosted Gitea instance so you can interact with your AI coding assistant from Gitea issues and pull requests.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Gitea instance with API access enabled\n- A Gitea personal access token (or dedicated bot account token)\n- Public endpoint for webhooks (or a tunnel for local development)\n\n:::tip\n`GITEA_TOKEN` also enables authenticated cloning of private Gitea and Forgejo repositories. If you set it for the adapter, clone authentication works automatically — no extra configuration needed.\n:::\n\n## Step 1: Create a Gitea Token\n\n1. Log in to your Gitea instance\n2. Go to **Settings > Applications > Manage Access Tokens**\n3. Create a new token with repository read/write permissions\n4. Copy the token -- you will need it for Step 3\n\n## Step 2: Generate a Webhook Secret\n\nOn Linux/Mac:\n```bash\nopenssl rand -hex 32\n```\n\nOn Windows (PowerShell):\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret for Steps 3 and 4.\n\n## Step 3: Set Environment Variables\n\n```ini\nGITEA_URL=https://gitea.example.com\nGITEA_TOKEN=your_personal_access_token\nGITEA_WEBHOOK_SECRET=your_secret_from_step_2\n```\n\nAll three variables are required. The adapter starts automatically when all three are set.\n\n**Optional variables:**\n\n```ini\n# Restrict who can trigger the bot (comma-separated usernames, case-insensitive)\nGITEA_ALLOWED_USERS=alice,bob\n\n# Custom @mention name (defaults to BOT_DISPLAY_NAME, then \"Archon\")\nGITEA_BOT_MENTION=archon\n```\n\n## Step 4: Configure Gitea Webhook\n\nGo to your repository settings in Gitea:\n- Navigate to **Settings > Webhooks > Add Webhook > Gitea**\n\n**Webhook Configuration:**\n\n| Field | Value |\n|-------|-------|\n| **Target URL** | `https://your-domain.com/webhooks/gitea` |\n| **HTTP Method** | `POST` |\n| **Content Type** | `application/json` |\n| **Secret** | Paste the secret from Step 2 |\n| **Events** | Issues, Issue Comments, Pull Requests, Pull Request Comments |\n\nClick **Add Webhook** and use the **Test Delivery** button to verify connectivity.\n\nWebhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** and are not processed.\n\n## Usage\n\nInteract by @mentioning the bot in issue or PR **comments**:\n\n```\n@archon can you analyze this bug?\n@archon review this implementation\n@archon /workflow run investigate \"why does the auth flow reject valid tokens?\"\n```\n\n**First mention behavior:**\n- Reuses an existing local registration at its saved checkout path. Otherwise, registers and clones the repository to `~/.archon/workspaces/`\n- Detects and loads commands from `.archon/commands/` if present\n- Injects full issue/PR context (title, description, labels) for the AI assistant\n\n**Subsequent mentions:**\n- Resumes the existing conversation\n- Maintains full context across comments\n\n:::note\nOnly comments trigger the bot. @mentions in issue or PR descriptions are ignored -- descriptions often contain example commands or documentation that are not intended as bot invocations.\n:::\n\n## How It Works\n\nThe Gitea adapter is a webhook-based forge adapter, similar to the GitHub adapter:\n\n- **Transport**: Receives HTTP POST webhooks from Gitea\n- **Signature verification**: HMAC SHA-256 over the exact received bytes, using the `X-Gitea-Signature` header\n- **Streaming mode**: Always batch (assembles the assistant reply after processing; long replies may span multiple comments; see [batch mode](/reference/configuration/#batch-mode) for filtering and fallback behavior)\n- **Conversation ID format**: `owner/repo#number` for issues, `owner/repo!number` for PRs\n- **Self-loop prevention**: Bot comments include a hidden HTML marker (`<!-- archon-bot-response -->`) to avoid re-triggering on its own messages\n- **Retry logic**: Transient network errors (timeouts, connection resets) are retried up to 3 times with exponential backoff\n\n### Close/Merge Cleanup\n\nWhen an issue is closed or a PR is merged/closed, the adapter automatically cleans up any associated worktree isolation environment.\n\n## Adding More Repositories\n\nAdd a webhook with the same secret to each repository you want the bot to monitor. The webhook secret must be identical across all repos pointing to the same Archon instance.\n\n## Further Reading\n\n- [Configuration](/reference/configuration/) -- Full environment variable reference\n- [Security](/reference/security/) -- Webhook verification and authorization details\n","frontmatter":{"title":"Gitea","description":"Connect Archon to a Gitea instance for issue and PR automation.","category":"adapters","area":"adapters","audience":["operator"],"sidebar":{"order":6}},"isInternal":false,"tokens":1147,"sizeBytes":4790},{"name":"gitlab.md","path":"packages/docs-web/src/content/docs/adapters/community/gitlab.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/community/gitlab.md","title":"Community Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: GitLab\ndescription: Connect Archon to GitLab for AI coding assistance in issues and merge requests.\ncategory: adapters\narea: adapters\naudience: [operator]\nsidebar:\n  order: 7\n---\n\n:::note\nGitLab is a **community adapter** — contributed and maintained by the community.\n:::\n\nConnect Archon to a GitLab instance (gitlab.com or self-hosted) so you can interact with your AI coding assistant from issues and merge requests.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- GitLab project with issues and merge requests enabled\n- GitLab Personal Access Token or Project Access Token with `api` scope\n- Public endpoint for webhooks (see ngrok setup below for local development)\n\n:::tip\n`GITLAB_TOKEN` also enables authenticated cloning of private GitLab repositories. If you set it for the adapter, clone authentication works automatically — no extra configuration needed.\n:::\n\n## Step 1: Create a GitLab Access Token\n\n### Personal Access Token (recommended for getting started)\n\n1. Go to **GitLab → User Settings → Access Tokens**\n2. Create a token with:\n   - **Name**: `archon`\n   - **Scopes**: `api`\n   - **Expiration**: Set as needed\n3. Copy the token (starts with `glpat-`)\n\n### Project Access Token (recommended for production)\n\n1. Go to **Project → Settings → Access Tokens**\n2. Create a token with:\n   - **Role**: Developer or Maintainer\n   - **Scopes**: `api`\n3. This creates a bot user scoped to the project\n\n## Step 2: Generate Webhook Secret\n\n```bash\nopenssl rand -hex 32\n```\n\nWindows (PowerShell):\n\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret — you'll need it for steps 3 and 4.\n\n## Step 3: Expose Local Server (Development Only)\n\n```bash\nngrok http 3090\n# Copy the HTTPS URL (e.g., https://abc123.ngrok-free.app)\n```\n\nFor production, use your deployed server URL directly.\n\n## Step 4: Configure GitLab Webhook\n\nNavigate to **Project → Settings → Webhooks → Add new webhook**:\n\n| Field | Value |\n|-------|-------|\n| **URL** | `https://your-domain.com/webhooks/gitlab` |\n| **Secret token** | The secret from Step 2 |\n| **Triggers** | Enable: `Comments`, `Issues events`, `Merge request events` |\n| **SSL verification** | Enable (recommended) |\n\nClick \"Add webhook\" and use **Test → Note events** to verify.\n\n:::note\nGitLab uses a plain secret token in the `X-Gitlab-Token` header (not HMAC like GitHub). The token must match your `GITLAB_WEBHOOK_SECRET` exactly.\n:::\n\n## Step 5: Set Environment Variables\n\n```ini\nGITLAB_URL=https://gitlab.com\nGITLAB_TOKEN=glpat-your-token-here\nGITLAB_WEBHOOK_SECRET=your-secret-here\n```\n\nOptional:\n\n```ini\nGITLAB_ALLOWED_USERS=alice,bob\nGITLAB_BOT_MENTION=archon\n```\n\nSee the [full environment variable reference](/reference/configuration/) for details.\n\n## Usage\n\nMention your bot in issue or MR comments:\n\n```\n@archon can you analyze this bug?\n@archon /status\n@archon review this implementation\n```\n\n**First mention** reuses an existing local registration at its saved checkout path; otherwise it registers and clones the repository. It detects `.archon/commands/` if present and injects full issue/MR context. For a one-level namespace, the checkout is `~/.archon/workspaces/<group>/<project>/source`. Nested namespaces are flattened after the top-level group: `org/team/api` uses `~/.archon/workspaces/org/team-api/source`. If another project owns that storage name, registration allocates a suffix such as `org/team-api-2`, with its checkout under `~/.archon/workspaces/org/team-api-2/source`. See the [directory reference](/reference/archon-directories/) for the full workspace layout.\n\n**Subsequent mentions** resume the existing conversation with full context.\n\n## Conversation ID Format\n\n| Type | Format | Example |\n|------|--------|---------|\n| Issue | `group/project#iid` | `myteam/api#42` |\n| Merge Request | `group/project!iid` | `myteam/api!15` |\n| Nested group | `group/subgroup/project#iid` | `org/team/api#7` |\n\n## Supported Events\n\n| GitLab Event | Action |\n|-------------|--------|\n| **Note Hook** (comment with @mention) | Triggers AI conversation |\n| **Issue Hook** (close) | Cleans up isolation environment |\n| **MR Hook** (close/merge) | Cleans up isolation environment |\n| Issue/MR opened | Ignored (descriptions are not commands) |\n\n## Adding Additional Projects\n\nAdd the same webhook to other projects:\n\n```bash\nglab api projects/<PROJECT_ID>/hooks \\\n  --method POST \\\n  -f url=\"https://YOUR_DOMAIN/webhooks/gitlab\" \\\n  -f token=\"YOUR_WEBHOOK_SECRET\" \\\n  -f note_events=true \\\n  -f issues_events=true \\\n  -f merge_requests_events=true\n```\n\nOr via GitLab UI with the same secret.\n\n## Troubleshooting\n\n| Issue | Cause | Fix |\n|-------|-------|-----|\n| `gitlab.invalid_webhook_token` | Secret mismatch | Ensure `GITLAB_WEBHOOK_SECRET` matches the webhook config exactly |\n| Clone hangs | macOS Keychain credential helper | The adapter disables it automatically |\n| `404 Project Not Found` | Token lacks access | Ensure token has `api` scope and project access |\n| `403 You are not allowed` | Insufficient permissions | Use a token with Developer role or higher |\n| No webhook delivery | ngrok URL changed | Update the webhook URL after restarting ngrok |\n| Webhook auto-disabled | 4+ consecutive failures | Fix the issue, then send a test event to re-enable |\n\n## glab CLI Reference\n\nThe AI agent uses `glab` CLI commands. Install and authenticate:\n\n```bash\nbrew install glab\nglab auth login\n```\n\n| Command | Purpose |\n|---------|---------|\n| `glab issue view <IID>` | View issue details |\n| `glab issue note <IID> -m \"...\"` | Comment on issue |\n| `glab mr view <IID>` | View merge request |\n| `glab mr diff <IID>` | View MR diff |\n| `glab mr note <IID> -m \"...\"` | Comment on MR |\n| `glab mr create --title \"...\" --description \"...\"` | Create MR |\n","frontmatter":{"title":"GitLab","description":"Connect Archon to GitLab for AI coding assistance in issues and merge requests.","category":"adapters","area":"adapters","audience":["operator"],"sidebar":{"order":7}},"isInternal":false,"tokens":1475,"sizeBytes":5855},{"name":"github-app-setup.md","path":"packages/docs-web/src/content/docs/adapters/github-app-setup.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/github-app-setup.md","title":"Adapters Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: GitHub App Setup\ndescription: Run Archon's bot identity as a registered GitHub App with multi-installation routing.\ncategory: adapters\narea: adapters\naudience: [operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\nThis is the recommended GitHub auth mode for teams sharing one Archon instance. It replaces the shared `GITHUB_TOKEN` PAT with a registered GitHub App so that:\n\n- Bot comments appear as `archon[bot]` with the App badge, not under an operator's personal account.\n- Installation access tokens rotate automatically every ~1h (smaller blast radius if leaked).\n- Webhooks centralise — one URL per App covers every installation.\n- A team's repos can span multiple GitHub orgs (or a mix of orgs and personal accounts) — Archon routes per-(owner, repo) to the right installation transparently.\n\nSolo installs that only need the PAT model can ignore this page; see [GitHub](/adapters/github/) for the legacy setup.\n\n## When to use App mode vs. PAT mode\n\n| Situation                                                           | Recommended mode |\n| ------------------------------------------------------------------- | ---------------- |\n| Solo developer, single GitHub account                               | PAT              |\n| Team of 2+ sharing one Archon instance                              | App              |\n| Repos across multiple orgs                                          | App              |\n| You want bot comments to attribute as `<slug>[bot]` with App badge  | App              |\n| You want short-lived (1h) tokens instead of long-lived PAT          | App              |\n\nArchon refuses to start with **both** modes configured. Pick one set of env vars.\n\n## Step 1: Register the GitHub App\n\n1. Go to <https://github.com/settings/apps/new> (or `https://github.com/organizations/<org>/settings/apps/new` for an org-owned App).\n2. Fill in:\n   - **GitHub App name** — e.g. `Archon Bot`. The slug derived from this (visible in the App URL) is what you'll later set as `GITHUB_APP_SLUG`. Self-filter compares against `<slug>[bot]`.\n   - **Homepage URL** — your team's Archon URL, e.g. `https://archon.example.com/`.\n   - **Webhook URL** — `https://archon.example.com/webhooks/github`.\n   - **Webhook secret** — same value as your `WEBHOOK_SECRET` env var.\n\n   Webhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** before the signature is checked; Archon stops reading them and creates no receipt or workflow start. This limit applies only to webhook routes.\n3. Uncheck **Active** on the user authorisation callback URL — Archon doesn't use OAuth in PR-B.\n\n## Step 2: Permissions (fine-grained)\n\n**Repository permissions:**\n\n| Permission       | Access       | Used for                                          |\n| ---------------- | ------------ | ------------------------------------------------- |\n| Actions          | Read & Write | Observing GitHub Actions workflow runs and rerunning failed CI |\n| Checks           | Read         | Waking workflow CI waits when a check concludes   |\n| Contents         | Read & Write | Cloning, reading repo metadata, pushing branches, and merging pull requests |\n| Workflows        | Read & Write | Required only when runs create or change files under `.github/workflows/` |\n| Issues           | Read & Write | `createComment` + `listComments`                  |\n| Pull requests    | Read & Write | `pulls.get` + comment posting                     |\n| Metadata         | Read         | Mandatory (auto-included)                         |\n\nGitHub refuses pushes that create or change files under `.github/workflows/` unless the App has **Workflows: Read & Write**, even when **Contents: Read & Write** is granted. See [GitHub's Git access permissions](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app#choosing-permissions-for-git-access).\n\n**Account permissions:** none.\n\n## Step 3: Subscribe to webhook events\n\nSubscribe to:\n\n- Issue comments\n- Check runs\n- Pull request review comments\n- Pull request\n- Issues (used for `closed` cleanup)\n\nExisting installations must accept the added Checks permission before check-run wake-ups work. If a check-run webhook is unavailable, delivery still progresses after the wait's five-minute deadline and probes CI again.\n\n## Step 4: Generate a private key\n\n1. After saving the App, scroll to **Private keys** and click **Generate a private key**.\n2. Save the downloaded `.pem` file in a location only readable by the Archon process — e.g. `/etc/archon/github-app.pem`.\n\n## Step 5: Install the App\n\nInstall the App on every org or personal account that holds repos your team operates on:\n\n1. From the App settings page, click **Install App**.\n2. Pick the org → grant access to all repos (or selected repos).\n3. Repeat for every org/account.\n\n> **Multi-installation:** Archon resolves `owner/repo → installation_id` via `GET /repos/{owner}/{repo}/installation` automatically. No per-install config is needed. `GITHUB_APP_INSTALLATION_ID` only pins the installation used for adapter API operations — see below.\n\n## Step 6: Configure Archon\n\nAdd the following to `~/.archon/.env` for the install or `<cwd>/.archon/.env` for the project:\n\n```dotenv\nGITHUB_APP_ID=123456                 # numeric App ID, visible on the App settings page\nGITHUB_APP_PRIVATE_KEY_PATH=/etc/archon/github-app.pem\nWEBHOOK_SECRET=<same value as on the GitHub side>\n\n# Optional:\n# GITHUB_APP_SLUG=archon-bot         # defaults to 'archon'; set this if you named your App\n#                                    # differently. The bot's posted-comment login is `<slug>[bot]`.\n# GITHUB_APP_INSTALLATION_ID=98765   # use a fixed installation for adapter API operations.\n#                                    # Repository tokens still verify the repository installation.\n```\n\nCLI workflow run, `--detach`, resume, wake, and signal use `GITHUB_APP_ID` plus either private-key setting. They do not require `WEBHOOK_SECRET`; that setting is required for the server's webhook adapter. Each detached child initializes its own provider from the inherited configuration.\n\nIncomplete App settings, malformed App or installation IDs, an unusable RSA private key, and a malformed per-user encryption key fail before workflow execution. App/key correspondence and installation access are checked remotely when resolving the repository token. A resolution failure fails the run before any node executes; it cannot fall back to another credential. With no App settings, CLI behavior is unchanged. CLI runs follow the same per-user policy as server runs: connected user credentials override the bot, and unconnected users have GitHub credentials scrubbed unless org fallback is explicitly enabled.\n\nEach workflow execution, including a resumed segment, resolves the codebase repository's installation token. The provider caches fresh tokens in memory and refreshes them near expiry; tokens are not cached on disk. A node's environment stays fixed during one execution, so this does not rotate tokens inside a workflow that runs longer than the token lifetime.\n\n### Inline private key (alternative)\n\nIf you can't write a file (e.g. a managed PaaS), set the PEM contents inline:\n\n```dotenv\nGITHUB_APP_PRIVATE_KEY=\"-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----\"\n```\n\nArchon normalises the literal `\\n` sequence in `.env`-quoted values into real newlines.\n\n### Unset the PAT\n\nArchon refuses to start if both modes are configured. Remove `GITHUB_TOKEN` from your env when switching to App mode.\n\n## Step 7: Restart Archon and verify\n\n1. Restart the server.\n2. Confirm the startup log shows `github.adapter_mode_app` with your slug.\n3. Trigger a webhook from a repo where the App is installed (e.g. comment `@archon ping` on an issue).\n4. Confirm the bot's reply appears as `<slug>[bot]` with the App badge in the GitHub UI.\n\n## Step 8 (optional): Enable per-user GitHub identity\n\nBy default GitHub API requests and pushes authenticate as the **bot** (`<slug>[bot]`), while commit authorship uses the checkout's ambient git identity. On a multi-user install teammates can connect their own GitHub identity for authentication and commit authorship.\n\nEnable the feature by adding two env vars on top of App mode:\n\n```bash\n# The App's Client ID (App settings → \"Client ID\", starts with Iv1./Iv23…).\n# Distinct from the numeric GITHUB_APP_ID.\nGITHUB_APP_CLIENT_ID=Iv23xxxxxxxxxxxx\n# 32-byte key used to encrypt stored per-user tokens at rest (AES-256-GCM).\nTOKEN_ENCRYPTION_KEY=$(openssl rand -hex 32)\n```\n\nThen, on the GitHub App settings page, enable **Device Flow** (under \"Identifying and authorizing users\"). The feature gate activates when `GITHUB_APP_ID` and `TOKEN_ENCRYPTION_KEY` are both set; `GITHUB_APP_CLIENT_ID` is required for the connect flow itself.\n\nTeammates connect once via any surface:\n\n- **CLI:** `archon auth github`\n- **Web UI:** Settings → **Connect GitHub**\n\nOnce enabled:\n\n- Run commits use the initiating connected user's GitHub login as author name and `<numeric_id>+<login>@users.noreply.github.com` as author email. The committer remains the ambient git identity. This applies to new and adopted worktrees, in-place runs, and folder projects without changing repository git config. Unconnected users and runs without an initiating user keep ambient authorship.\n- Workflows declaring `requires: [github]` **hard-block** unconnected users before any worktree/clone/AI cost.\n- An unconnected user's workflow `gh`/`git` has its GitHub token **scrubbed** by default (rather than silently using the shared org/bot token). The host's `GH_ENTERPRISE_TOKEN`/`GITHUB_ENTERPRISE_TOKEN` are cleared for every per-user run. The scrub also hides the host's stored logins: `gh` gets an empty config directory, so a `gh auth login` on the server is not used, and git's credential helpers (keychain, credential store, `gh auth git-credential`) are cleared. GitHub commands in such a run fail with an authentication error until the user connects. SSH remotes still use the host's SSH keys. Opt back into the shared token with `ARCHON_ALLOW_ORG_GITHUB_TOKEN_FALLBACK=true`.\n- Rotating `TOKEN_ENCRYPTION_KEY` invalidates all stored user tokens — everyone must reconnect.\n\nThis is fully backward compatible: leave the two vars unset and the App keeps working as the bot only. See the [configuration reference](/reference/configuration/#per-user-github-identity-app-mode-optional) for the full env var table.\n\n## Operational notes\n\n### Token rotation is invisible\n\nInstallation tokens are valid for 1h. Archon caches each `installation_id → token` pair and refreshes ~5 minutes before expiry on the next access. No background timer; no leaked handles.\n\n### `event.installation.id` short-circuits the lookup\n\nEvery webhook delivery from a GitHub App carries `installation.id`. Archon primes its `owner/repo → installation_id` cache from the payload, so the next outbound call to that repo skips the `GET /repos/{owner}/{repo}/installation` round trip.\n\n### 401 forces a single retry\n\nA 401 from any installation Octokit evicts the cached token and the call is retried once with a fresh token. Persistent 401s propagate as the original error.\n\n### Long-running workflow `git push`\n\nWorkflows that span >1h need a fresh token to push from the cloned worktree. In App mode, Archon installs a git credential helper after cloning and refreshes it whenever it sets up an existing repository, including webhook handling and adding a codebase by URL: the worktree's `.git/config` points at `~/.archon/bin/git-credential-archon`, which talks back to Archon's internal endpoint for a fresh installation token on each operation. Git skips credential helpers when a remote URL carries credentials, so the same setup removes any token embedded in the repository's `https://github.com` remote URLs, such as one left by an older PAT-mode clone. The helper reads the server's port from `ARCHON_PORT`, which Archon sets for the processes it launches. A `git` command run from an independent shell on the Archon host gets no credential unless it exports `ARCHON_PORT` with the server's port.\n\nThe helper is embedded in compiled binaries. Each setup replaces an outdated managed script and registers the helper and GitHub path forwarding in the repository's local Git config. Existing repositories are refreshed on their next setup, not swept at server startup. If helper installation or registration fails, repository setup stops before URL registration, sync, or workflow dispatch; a later setup retries registration and repairs partial config.\n\n### Long-running workflow `gh`\n\nA run's `GH_TOKEN` and `GITHUB_TOKEN` hold the installation token resolved when the run, or a resumed segment, starts. That token can have as little as 5 minutes left. When the run's process has `ARCHON_PORT` (every run the server dispatches, and a CLI run that exports the server's port), Archon also puts `~/.archon/bin/gh-shim` first on the run's `PATH`. On each call the shim asks the same internal endpoint for a current token for the run's repository and runs the real `gh` with it in the environment. If it gets no token, it exits 1 with a `gh (archon shim):` error instead of running `gh` with a stale token. A `GH_TOKEN` that is not an installation token, such as one a step sets itself, passes through unchanged. A per-user token or scrubbed token from [per-user GitHub identity](#step-8-optional-enable-per-user-github-identity) turns the shim off for that run.\n\n`gh` keeps the start-time token, and fails once it expires, in these cases:\n\n- **A CLI run without `ARCHON_PORT`.** No endpoint is reachable, so Archon logs `workflow.gh_token_refresh_unavailable` each time it resolves the run's GitHub environment. That happens when the run starts, when it runs worktree setup commands, and when it resumes, so one run can log it several times. The CLI mints its token at start, so it lasts about an hour. Export `ARCHON_PORT` with an App-mode server's port to cover longer runs.\n- **`gh` called by a Codex agent.** Codex's shell snapshot restores a fixed `PATH`, so its shell never finds the shim. Archon does not change that Codex setting. Bash, script and loop nodes, and Claude and Pi agents, use the shim.\n- **Windows.** The shim is a POSIX shell script, so a run on Windows logs the same warning and keeps the start-time token.\n\nRun output redacts the start-time token only. A token the shim fetches later lives in the `gh` process's environment, so it is not redacted if a step prints it, for example with `gh auth token`.\n\n### Internal endpoint security — REQUIRED\n\nThe credential-helper backend is exposed at `POST /internal/git-credential` and hands out live installation access tokens. **It MUST NOT be reachable from outside the Archon host.**\n\nArchon enforces this at startup: with App mode active and the server bound to a non-loopback interface (e.g. `0.0.0.0`), the process **refuses to start** and exits with `github_app.internal_endpoint_public_bind_rejected`. Two correct configurations:\n\n1. **Recommended (bare-metal / systemd) — bind Archon to `127.0.0.1`** (`HOST=127.0.0.1`) and put a reverse proxy in front. Configure the proxy to drop `/internal/*` paths.\n2. **Opt-in escape hatch — `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`** combined with `HOST=0.0.0.0` (or unset). Use ONLY when your reverse proxy already drops `/internal/*` AND your deployment topology genuinely requires the upstream to bind non-loopback (e.g. a container network where loopback isn't reachable from the proxy). **Docker / docker-compose deployments fall here** — the app container *must* bind `0.0.0.0` inside the container for docker-proxy to forward traffic, so option 1 isn't directly usable. See the [Canonical Docker setup](#canonical-docker-setup) below. Startup logs `github_app.internal_endpoint_exposed_acknowledged` so the choice is auditable.\n\nExample Caddy snippet that drops `/internal/*` (bare-metal):\n\n```txt\nexample.com {\n  @internal path /internal/*\n  respond @internal 404\n  reverse_proxy 127.0.0.1:3090\n}\n```\n\n### Canonical Docker setup\n\nThe canonical Archon Docker stack (`docker-compose.yml` + `docker-compose.override.yml` + Caddy) publishes the app container's port `3000` and runs Caddy as a reverse proxy. Because the app container can't bind `127.0.0.1` and still have docker-proxy forward traffic to it, option 1 above isn't directly usable. Take option 2 with three layers of defense:\n\n**1. In `docker-compose.override.yml` — bind the published host port to loopback only.**\n\n```yaml\nservices:\n  app:\n    ports: !override\n      - \"127.0.0.1:${PORT:-3000}:${PORT:-3000}\"\n```\n\nThe `!override` tag (compose-spec) replaces the base file's `ports` list instead of merging. After this, port `3000` is reachable only via the Docker network (where Caddy lives) or from the host itself — not from the public internet.\n\n**2. In `Caddyfile` — drop `/internal/*` requests.**\n\nThe maintained `Caddyfile.example` includes this denial. Keep it in your deployed `Caddyfile`; if you use an older or custom configuration, insert it before the fallthrough `handle { }` block:\n\n```txt\nhandle /internal/* {\n  respond \"Not Found\" 404\n}\n```\n\nCaddy evaluates `handle` blocks by path specificity, so `/internal/*` (more specific) wins over the generic fallthrough regardless of order.\n\n**3. In `.env` — opt out of the loopback-bind guard.**\n\n```ini\n# Safe because:\n#  1. docker-compose binds port 3000 to 127.0.0.1 only (override above)\n#  2. Caddy drops /internal/* (handle block above)\nARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1\n```\n\nWithout this flag, Archon refuses to start in App mode and exits with `github_app.internal_endpoint_public_bind_rejected`.\n\n**Apply + verify:**\n\n```sh\n# Apply the override + Caddyfile + env changes, then restart app + caddy.\ndocker compose up -d --force-recreate app caddy\n\n# From outside the host — must be 404/403 from the proxy.\n# Use POST (the endpoint's actual method) — a GET probe can false-pass when\n# the proxy 404s unmatched GETs while still forwarding the POST upstream.\ncurl -i -X POST https://your-archon/internal/git-credential \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"host\":\"github.com\",\"path\":\"any/repo\"}'\n\n# On the host — port should bind to loopback only\nss -tlnp | grep :3000\n# Expected: 127.0.0.1:3000   (NOT 0.0.0.0:3000)\n\n# Startup log should contain:\ndocker compose logs app --tail 200 | grep \"internal_endpoint_exposed_acknowledged\"\n# This event confirms the opt-out path was taken (audit trail).\n```\n\nIf the external POST probe returns anything other than `404` or `403` from the proxy — i.e. anything that suggests the request reached the Archon process — **stop and fix the proxy config** before going live. The endpoint hands out live installation tokens to whoever can reach it.\n\n## Migration from PAT mode\n\n1. Register the App and install on your orgs (Steps 1–5 above).\n2. Add `GITHUB_APP_*` env vars (Step 6). In Docker, put them in the container environment (the compose `.env`), not `/.archon/.env`; see [GitHub CLI authentication](/deployment/docker/#github-credentials).\n3. **Remove** `GITHUB_TOKEN` from your env (or comment it out). Archon refuses to start if both are set.\n4. **Remove** `GH_TOKEN` if you set it to the same PAT. Archon starts with it set, but the GitHub CLI in the container keeps using the old PAT.\n5. Restart Archon.\n6. Webhook URLs configured per-repo against the PAT-mode setup can stay or be removed — the App's single webhook URL covers everything once it's installed. New repos auto-join via App installation. While both webhooks are active, a comment delivered by each is deduplicated at ingest, so the bot still responds once.\n\nIn App mode, adding a private GitHub codebase by URL and inspecting its base branch use the repository's installation token; no PAT is needed. The clone keeps a credential-free remote URL and receives the credential helper before registration completes. If the App is not installed, Archon reports installation guidance. Adding an already-registered URL refreshes the helper without cloning again when the checkout exists. A missing Archon-managed checkout is cloned again at its registered path. A missing operator-owned checkout must be restored or repaired with `/update-project`.\n\nRun `archon doctor` to validate local App configuration after migration. A passing App check confirms the ID and RSA key are valid locally; it does not confirm GitHub accepts them or that the App has repository access.\n\n## Troubleshooting\n\n### `AppPrivateKeyError: Provided value is not a valid PEM-encoded private key`\n\n- Check the file content includes `-----BEGIN ... PRIVATE KEY-----` and `-----END ... PRIVATE KEY-----`.\n- For inline keys, ensure the `.env` value preserves newlines (either literal newlines in a multi-line value or the `\\n` escape inside double quotes).\n\n### `AppNotInstalledError: The Archon GitHub App is not installed on \"<owner>\"`\n\n- The App is not installed on that owner's org/account. Use the install link in the error message to add it.\n\n### 401 loop on GitHub API calls\n\nRepeated 401s on outbound API calls (`createComment`, `listComments`, `repos.get`, `pulls.get`) point at installation / token issues, not webhook config. Walk through:\n\n- Verify the App is installed on the target `owner` and has not been suspended or uninstalled. Visit `https://github.com/settings/installations` (or the org equivalent) to confirm.\n- Verify the private key in your env (`GITHUB_APP_PRIVATE_KEY` or `_PATH`) matches the same App that `GITHUB_APP_ID` points at. Mismatched key+ID is a common cause of \"401 from JWT\" errors at token-issuance time.\n- Verify `GITHUB_APP_INSTALLATION_ID` (if set) still corresponds to a live installation — uninstall + reinstall assigns a new ID.\n\n### GitHub refuses a branch push\n\nAn installation token needs Contents write access to push branches. If the branch creates or changes files under `.github/workflows/`, GitHub also requires Workflows write access. Check the App and installation permissions:\n\nVerify the App's permissions against the [permissions table](#step-2-permissions-fine-grained). Permission changes require operators to **review and accept** the new permissions on every installation; until accepted, that installation keeps its old permissions.\n\n### Webhook deliveries fail signature verification\n\nA webhook-secret mismatch causes `github.signature_mismatch` / `github.signature_length_mismatch` errors at the `POST /webhooks/github` endpoint — distinct from outbound API 401s. If GitHub's webhook delivery page shows red ❌ next to the delivery (rather than your bot just silently not responding), check:\n\n- `WEBHOOK_SECRET` in Archon's env matches the value entered in the GitHub App's webhook configuration page exactly.\n\n### Bot comments still appear under your personal account\n\n- You're still in PAT mode. Check `process.env.GITHUB_TOKEN` is unset and `GITHUB_APP_ID` is set; restart.\n\n### Server refused to start: `github_app.internal_endpoint_public_bind_rejected`\n\n- App mode is active but the server is bound to a non-loopback interface. This is a fail-fast guard — the `/internal/git-credential` endpoint hands out live installation access tokens and would leak credentials to the network. Either set `HOST=127.0.0.1` (bare-metal / systemd), or, if you're on Docker, follow the [Canonical Docker setup](#canonical-docker-setup) above (port→127.0.0.1 binding + Caddy `/internal/*` drop + `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`).\n\n### Server log shows `github_app.internal_endpoint_exposed_acknowledged`\n\n- You set `ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1`. Double-check that your reverse proxy actually drops `/internal/*` — a `curl https://your-archon/internal/git-credential -d '{\"host\":\"github.com\",\"path\":\"any/repo\"}'` from outside the host must return 404 or 403 from the proxy (NOT a token from Archon). On the canonical Docker stack, also confirm `ss -tlnp | grep :3000` shows `127.0.0.1:3000` (not `0.0.0.0:3000`).\n","frontmatter":{"title":"GitHub App Setup","description":"Run Archon's bot identity as a registered GitHub App with multi-installation routing.","category":"adapters","area":"adapters","audience":["operator"],"status":"current","sidebar":{"order":5}},"isInternal":false,"tokens":5497,"sizeBytes":24068},{"name":"github.md","path":"packages/docs-web/src/content/docs/adapters/github.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/github.md","title":"Adapters Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: GitHub\ndescription: Connect Archon to GitHub via webhooks to interact from issues and pull requests.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 4\n---\n\nConnect Archon to GitHub so you can interact with your AI coding assistant from issues and pull requests.\n\n> **Teams sharing one Archon instance:** prefer the [GitHub App setup](./github-app-setup.md). The PAT-mode setup on this page is the legacy path — it still works for solo installs, but bot comments all post under the PAT owner's avatar and tokens never auto-rotate. App mode gives you `<slug>[bot]` attribution, 1h token rotation, multi-org support, and one webhook URL.\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- GitHub repository with issues enabled\n- A GitHub personal access token set as `GITHUB_TOKEN` in `~/.archon/.env`\n- Public endpoint for webhooks (see ngrok setup below for local development)\n\n## Step 1: Generate Webhook Secret\n\nOn Linux/Mac:\n```bash\nopenssl rand -hex 32\n```\n\nOn Windows (PowerShell):\n```powershell\n-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) })\n```\n\nSave this secret -- you'll need it for steps 3 and 4.\n\n## Step 2: Expose Local Server (Development Only)\n\n### Using ngrok (Free Tier)\n\n```bash\n# Install ngrok: https://ngrok.com/download\n# Or: choco install ngrok (Windows)\n# Or: brew install ngrok (Mac)\n\n# Start tunnel\nngrok http 3090\n\n# Copy the HTTPS URL (e.g., https://abc123.ngrok-free.app)\n# Free tier URLs change on restart\n```\n\nKeep this terminal open while testing.\n\n### Using Cloudflare Tunnel (Persistent URLs)\n\n```bash\n# Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/\ncloudflared tunnel --url http://localhost:3090\n\n# Get persistent URL from Cloudflare dashboard\n```\n\nPersistent URLs survive restarts.\n\n**For production deployments**, use your deployed server URL (no tunnel needed).\n\n## Step 3: Configure GitHub Webhook\n\nGo to your repository settings:\n- Navigate to: `https://github.com/owner/repo/settings/hooks`\n- Click \"Add webhook\"\n- **Note**: For multiple repositories, you'll need to add the webhook to each one individually\n\n**Webhook Configuration:**\n\n| Field | Value |\n|-------|-------|\n| **Payload URL** | Local: `https://abc123.ngrok-free.app/webhooks/github`<br>Production: `https://your-domain.com/webhooks/github` |\n| **Content type** | `application/json` |\n| **Secret** | Paste the secret from Step 1 |\n| **SSL verification** | Enable SSL verification (recommended) |\n| **Events** | Select \"Let me select individual events\":<br>- Check runs<br>- Issues<br>- Issue comments<br>- Pull requests |\n\nClick \"Add webhook\" and verify it shows a green checkmark after delivery.\n\nWebhook bodies are limited to **32 MiB (33,554,432 bytes)**, inclusive. Larger bodies receive HTTP **413** before the signature is checked; Archon stops reading them and creates no receipt or workflow start. This limit applies only to webhook routes.\n\n## Step 4: Set Environment Variables\n\n```ini\nWEBHOOK_SECRET=your_secret_from_step_1\n```\n\n**Important**: The `WEBHOOK_SECRET` must match exactly what you entered in GitHub's webhook configuration.\n\n## Step 5: Configure Streaming (Optional)\n\nThe GitHub adapter always uses `batch` mode (hardcoded). It assembles the reply before delivery and splits long replies into multiple comments.\n\n## Usage\n\nInteract by @mentioning your bot in issue or PR **comments**:\n\n```\n@archon can you analyze this bug?\n@archon prime the codebase\n@archon review this implementation\n```\n\n**First mention behavior:**\n- Reuses an existing local registration at its saved checkout path, including a subdirectory of a Git checkout. Otherwise, registers and clones the repository to `~/.archon/workspaces/owner/repo/source/`, or to a suffixed path such as `owner/repo-2/source/` when that project name is taken.\n- For an existing registration, readiness skips syncing the checkout to preserve local changes.\n- Detects and loads commands from `.archon/commands/` if present\n- Injects full issue/PR context for the AI assistant\n\n**Subsequent mentions:**\n- Resumes existing conversation\n- Maintains full context across comments\n\n:::note\nOnly comments trigger the bot. @mentions in issue or PR descriptions are ignored -- descriptions often contain example commands or documentation that are not intended as bot invocations.\n:::\n\n## Adding Additional Repositories\n\nOnce your server is running, add more repos by creating a webhook with the same secret.\n\n**Via GitHub UI:** Repo Settings > Webhooks > Add webhook\n- **Payload URL**: Your server URL + `/webhooks/github`\n- **Content type**: `application/json`\n- **Secret**: Same `WEBHOOK_SECRET` from your `.env`\n- **Events**: Check runs, Issues, Issue comments, Pull requests\n\n**Via CLI:**\n\n```bash\n# Get your existing webhook secret\nWEBHOOK_SECRET=$(grep WEBHOOK_SECRET .env | cut -d= -f2)\n\n# Add webhook to new repo (replace OWNER/REPO)\ngh api repos/OWNER/REPO/hooks --method POST \\\n  -f \"config[url]=https://YOUR_DOMAIN/webhooks/github\" \\\n  -f \"config[content_type]=json\" \\\n  -f \"config[secret]=$WEBHOOK_SECRET\" \\\n  -f \"events[]=check_run\" \\\n  -f \"events[]=issues\" \\\n  -f \"events[]=issue_comment\" \\\n  -f \"events[]=pull_request\"\n```\n\n**Important**: The webhook secret must be identical across all repos.\n\nCheck-run deliveries wake CI waits quickly. They are not required for correctness: when GitHub does not deliver one, the workflow reaches its deadline and probes CI again.\n\n## GitHub Enterprise Server\n\nTo run the adapter against a self-hosted GitHub Enterprise Server, set its base URL:\n\n```ini\nGITHUB_URL=https://github.example.com\n```\n\nWith `GITHUB_URL` set, the adapter calls `https://github.example.com/api/v3`, clones from `https://github.example.com/<owner>/<repo>.git`, and `GH_TOKEN`/`GITHUB_TOKEN` authenticate HTTPS clones of that exact scheme, host and port. Leave it unset for github.com. A value with a path, query or credentials is rejected: the adapter and the GitHub App fail startup, and clones to the Enterprise host get no GitHub token. Clones from other forges are unaffected.\n\n- **Webhook:** create it on the Enterprise repository or organization, pointing at the same `/webhooks/github` endpoint with the same secret (Step 3).\n- **GitHub App mode:** register the App on the Enterprise instance (**Settings → Developer settings → GitHub Apps**) with the permissions listed in [GitHub App Setup](/adapters/github-app-setup/). The App's API calls and installation tokens go to the Enterprise instance.\n- **SDLC workflows:** the forge plugin needs a `forge.hosts` entry for the Enterprise host; see [forge plugin configuration](/reference/forge/#plugin-configuration).\n\nCurrent limits: in App mode, git operations after the first clone (sync, workflow pushes) and per-user identity (`archon auth github`, the device flow) still target github.com. Use PAT mode on Enterprise Server for now.\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","frontmatter":{"title":"GitHub","description":"Connect Archon to GitHub via webhooks to interact from issues and pull requests.","category":"adapters","area":"adapters","audience":["user","operator"],"status":"current","sidebar":{"order":4}},"isInternal":false,"tokens":1657,"sizeBytes":7042},{"name":"index.md","path":"packages/docs-web/src/content/docs/adapters/index.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/index.md","title":"Adapters Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Platform Adapters\ndescription: Overview of all platform adapters available for connecting to Archon.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 0\n---\n\nArchon supports multiple platform adapters. Each adapter connects Archon to a different communication channel, letting you trigger workflows and interact with AI agents from wherever you work.\n\n## Core Adapters\n\n| Adapter | Transport | Auth Required | Setup |\n|---------|-----------|---------------|-------|\n| [**Web UI**](/adapters/web/) | SSE streaming | None | Built-in |\n| [**CLI**](/reference/cli/) | stdout | None | Built-in |\n| [**Slack**](/adapters/slack/) | Socket Mode, as a chat plugin | Plugin + Bot and App tokens | [Setup guide](/adapters/slack/) |\n| [**Telegram**](/adapters/telegram/) (deprecated) | Bot API polling | Bot token | [Setup guide](/adapters/telegram/) |\n| [**GitHub**](/adapters/github/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/github/) |\n\n## Community Adapters\n\nCommunity adapters follow the same `IPlatformAdapter` interface but target platforms outside the core set.\n\n| Adapter | Transport | Auth Required | Setup |\n|---------|-----------|---------------|-------|\n| [**Discord**](/adapters/community/discord/) | WebSocket | Bot token | [Setup guide](/adapters/community/discord/) |\n| [**Gitea**](/adapters/community/gitea/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/community/gitea/) |\n| [**GitLab**](/adapters/community/gitlab/) | Webhooks | Token + webhook secret | [Setup guide](/adapters/community/gitlab/) |\n\n## How Adapters Work\n\nSlack runs as a chat plugin: a separate process the server starts from an installed plugin, speaking the chat plugin protocol. The other adapters are built into the server. All bundled adapters implement the `IPlatformAdapter` interface. They handle:\n\n- **Message ingestion** -- receiving messages from the platform and forwarding them to Archon's orchestrator\n- **Response delivery** -- streaming or batching AI responses back to the platform\n- **Authorization** -- optional user whitelists to restrict access\n- **Conversation tracking** -- mapping platform-specific identifiers (thread IDs, chat IDs, issue numbers) to Archon conversations\n\n## Choosing an Adapter\n\n- **Web UI** is the fastest way to get started -- no tokens or external services needed.\n- **Slack** is ideal for mobile access and team collaboration.\n- **GitHub** integrates directly into your issue and PR workflow.\n- **Discord** works well for community or team servers.\n\nYou can run multiple adapters simultaneously. Any bundled adapter with the required environment variables set will start automatically when you launch the server. Slack also needs its plugin installed; see the [Slack guide](/adapters/slack/).\n","frontmatter":{"title":"Platform Adapters","description":"Overview of all platform adapters available for connecting to Archon.","category":"adapters","area":"adapters","audience":["user","operator"],"status":"current","sidebar":{"order":0}},"isInternal":false,"tokens":639,"sizeBytes":2809},{"name":"slack.md","path":"packages/docs-web/src/content/docs/adapters/slack.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/slack.md","title":"Adapters Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Slack\ndescription: Install the Slack chat plugin and connect Archon to Slack over Socket Mode -- works behind firewalls with no public URL needed.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 2\n---\n\nConnect Archon to Slack so you can interact with your AI coding assistant from any Slack workspace.\n\nSlack runs as a chat plugin, `chat-slack`, released with Archon. The server starts the plugin as its own process and talks to it over the chat plugin protocol; the server itself contains no Slack code. You create a Slack app, set its two tokens in the server environment, and install the plugin. Docker provisions the image-provided plugin automatically when `SLACK_BOT_TOKEN` is configured.\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Slack workspace where you have permission to install apps\n- An Archon release that publishes the Slack plugin asset (`archon-chat-slack-<os>-<arch>`)\n\n## Overview\n\nArchon uses **Socket Mode** for Slack integration, which means:\n\n- No public HTTP endpoints needed\n- Works behind firewalls\n- Simpler local development\n- Not suitable for Slack App Directory (fine for personal/team use)\n\n## Step 1: Create a Slack App\n\n1. Go to [Slack API Apps](https://api.slack.com/apps)\n2. Log in if prompted\n3. Choose the workspace for your app\n4. Click **Create New App**\n5. Choose **From scratch**\n6. Enter:\n   - **App Name**: Any name (this is what you will use to @mention the bot)\n   - **Workspace**: Select your workspace\n7. Click **Create App**\n\n## Step 2: Enable Socket Mode\n\n1. In the left sidebar, click **Socket Mode**\n2. Toggle **Enable Socket Mode** to ON\n3. When prompted, create an App-Level Token:\n   - **Token Name**: `socket-mode`\n   - **Scopes**: Add `connections:write`\n   - Click **Generate**\n4. **Copy the token** (starts with `xapp-`) -- this is your `SLACK_APP_TOKEN`\n\n## Step 3: Configure Bot Scopes\n\n1. In the left sidebar, click **OAuth & Permissions**\n2. Scroll down to **Scopes** > **Bot Token Scopes**\n3. Add these scopes to bot token scopes:\n   - `app_mentions:read` -- Receive @mention events\n   - `chat:write` -- Send and edit messages (covers `chat.update` on the bot's own messages)\n   - `channels:history` -- Read messages in public channels (for thread context)\n   - `channels:join` -- Allow bot to join public channels\n   - `groups:history` -- Read messages in private channels (optional)\n   - `im:history` -- Read DM history (for DM support)\n   - `im:write` -- Send DMs\n   - `im:read` -- Read DM history (for DM support)\n   - `mpim:history` -- Read group DM history (optional)\n   - `mpim:write` -- Send group DMs\n   - `reactions:write` -- Add lifecycle reactions (🔄 / ✅ / ❌) to the triggering message\n   - `commands` -- Required for the `/archon` and `/archon-workflow` slash commands\n   - `users:read` -- Look up real names via `users.info` for user attribution. Without this scope messages still flow, but real names do not appear in the Archon database.\n\n## Step 4: Subscribe to Events\n\n1. In the left sidebar, click **Event Subscriptions**\n2. Toggle **Enable Events** to ON\n3. Under **Subscribe to bot events**, add:\n   - `app_mention` -- When someone @mentions your bot\n   - `message.im` -- Direct messages to your bot\n   - `message.channels` -- Messages in public channels (optional, for broader context)\n   - `message.groups` -- Messages in private channels (optional)\n4. Click **Save Changes**\n\n## Step 4b: Enable Interactivity\n\nInteractive buttons (gate decisions and Cancel on workflow runs) ride the same\nSocket Mode connection -- no public URL needed.\n\n1. In the left sidebar, click **Interactivity & Shortcuts**\n2. Toggle **Interactivity** to ON\n3. Leave the **Request URL** field blank -- Socket Mode handles routing\n4. Click **Save Changes**\n\n## Step 4c: Register Slash Commands\n\nTwo slash commands give the team an alternative to @mention:\n\n| Command | What it does |\n| --- | --- |\n| `/archon <message>` | Starts a new thread in the current channel. To continue an existing thread, post `@archon <message>` in that thread. |\n| `/archon-workflow <subcommand>` | Direct workflow control. Supports `list`, `status`, `get <id>`, `run <name> <args>`, `approve <id> [comment]`, `reject <id> [reason]`, `respond <id> <decision> [text]`, `abandon <id>`, `resume <id>`. |\n\nFor each command:\n\n1. In the left sidebar, click **Slash Commands**\n2. Click **Create New Command**\n3. Fill in:\n   - **Command**: `/archon` (or `/archon-workflow`)\n   - **Request URL**: leave blank -- Socket Mode handles routing\n   - **Short Description**: e.g. \"Talk to Archon\" or \"Archon workflow control\"\n   - **Usage Hint**: e.g. `<message>` or `<subcommand>`\n4. Save\n\nReinstall the app (Step 5) after adding scopes or commands so Slack issues a\nfresh token with the new permissions.\n\n## Step 5: Install to Workspace\n\n1. In the left sidebar, click **Install App**\n2. Click **Install to Workspace**\n3. Review the permissions and click **Allow**\n4. **Copy the Bot User OAuth Token** (starts with `xoxb-`) -- this is your `SLACK_BOT_TOKEN`\n\n## Step 6: Set Environment Variables\n\nAdd both tokens to the server's env file (`~/.archon/.env`, or the compose `.env` in Docker):\n\n```ini\nSLACK_BOT_TOKEN=xoxb-your-bot-token\nSLACK_APP_TOKEN=xapp-your-app-token\n```\n\nIn Docker, a nonempty `SLACK_BOT_TOKEN` declares installation at startup, including tokens in `/.archon/.env`. Both tokens are required for a connection.\n\nThe plugin reads them from the environment the server starts it with. If either is missing, the plugin fails to start and the server does not retry it.\n\n## Step 7: Install the Slack Plugin\n\nWith the release binary or Homebrew:\n\n```bash\narchon plugin add coleam00/Archon/plugins/chat-slack\n```\n\nIn a source checkout, run `bun run cli plugin add coleam00/Archon/plugins/chat-slack`.\n\nIn Docker, restart the container with its tokens configured. The image-provided release installs offline before the server starts, writes a verified receipt to `/.archon/plugins`, and logs why it was provisioned. No manual install is needed. A valid installed Slack plugin is retained at its current version. See [Building the image](/deployment/docker/#building-the-image) for the release pin and build args.\n\nWithout a tag, manual `plugin add` installs the latest release. Restart the server; Slack appears in `/api/health` once the plugin connects. Keep the plugin current with `archon plugin update coleam00/Archon/plugins/chat-slack`, and remove it with `archon plugin remove coleam00/Archon/plugins/chat-slack`. In Docker, remove Slack tokens from every effective env source as well, or the image plugin is reinstalled on restart. Outside Docker, removal followed by a restart keeps Slack off. See [`archon plugin`](/reference/cli/#plugin).\n\n## Step 8: Invite Bot to Channel\n\n1. Go to the Slack channel where you want to use the bot\n2. Type `/invite @your-bot` (your bot's display name)\n3. The bot should now respond to @mentions in that channel\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users:\n1. In Slack, go to a user's profile > click \"...\" > \"Copy member ID\"\n2. Add to environment:\n\n```ini\nSLACK_ALLOWED_USER_IDS=U01ABC123,U02DEF456\n```\n\nWhen set, only listed user IDs can interact with the bot. When empty/unset, the bot responds to all users. The server enforces this list, for messages and for button clicks alike, before anything reaches Archon.\n\n## Configure Streaming Mode (Optional)\n\n```ini\nSLACK_STREAMING_MODE=batch  # batch (default) | stream\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Usage\n\nSelect a registered project by posting `@your-bot /setproject <name>` in the target thread. To register a local directory first, post `@your-bot /register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nSlack registers `/archon` and `/archon-workflow`, not raw `/setproject` or `/register-project` slash commands. `/archon <message>` creates a new thread; use an @mention in the existing thread to scope that conversation.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\n### @Mention in Channels\n\n```\n@your-bot /clone https://github.com/user/repo\n```\n\n### Continue Work in Thread\n\nReply in the thread created by the initial message:\n\n```\n@your-bot /status\n```\n\n### Start Parallel Work (Worktree)\n\n```\n@your-bot /worktree feature-branch\n```\n\n### Direct Messages\n\nYou can also DM the bot directly -- no @mention needed:\n\n```\n/help\n```\n\n## In-Thread UX\n\nWhen a workflow runs in a Slack thread, Archon now:\n\n- Adds 🔄 to your triggering message when the run starts, and swaps it for ✅\n  on execution completion or ❌ on execution failure / cancellation. If a declared\n  authored outcome disagrees, it also adds the outcome reaction, so both facts remain visible\n- Posts a single status message in the thread that's edited in place as DAG\n  nodes start, complete, fail, or get skipped\n- When approval details are available, renders the gate's declared decisions as\n  buttons, using labels when supplied and sending the exact decision ID.\n  Legacy gates keep **Approve** / **Reject**.\n  The fallback text lists `/archon-workflow respond <id> <decision> [text]` commands;\n  use these to include optional text with a decision. For a parent blocked on a\n  child run, these parent controls redirect to the child instead of recording a\n  parent decision. Answer the child run using the displayed child command\n- If approval details cannot be loaded, posts an **Approval details unavailable**\n  notice naming the run instead of decision buttons or response commands. Check\n  the run status before responding\n- Adds a **Cancel** button on the status message while the run is\n  non-terminal, so the run's starter or an admin can abandon a runaway run\n  with a click\n- Appends a small italic cost / token footer after direct-chat replies\n  (e.g. `_cost: $0.0234 · 12.4k tokens · stop: end_turn_`) and on the\n  final status message when a workflow completes\n- Labels execution status and a non-null authored outcome separately in the final status message;\n  workflows without an authored outcome retain the existing status-only message\n- Annotates long responses split across multiple Slack messages with\n  `_part i/n_` footers so it's clear they belong together\n\nAll gate-decision and Cancel clicks run through the same\n`SLACK_ALLOWED_USER_IDS` whitelist as inbound messages -- clicks from users\noutside it are dropped. A member who is not the run's starter or an admin sees\na refusal only they can see.\n\n`/archon connect github` replies with a pointer to the Web UI or\n`archon auth github`; it does not run the GitHub device flow in Slack.\n\n## Upgrading From the Bundled Slack Adapter\n\nEarlier Archon versions ran Slack inside the server whenever `SLACK_BOT_TOKEN` and\n`SLACK_APP_TOKEN` were set. The server no longer contains that adapter: without the\nplugin, Slack stays off. When `SLACK_BOT_TOKEN` is set and no Slack plugin is installed,\nstartup logs a `chat.bundled_adapter_retired` warning with Docker provisioning guidance and the manual install command.\n\nIn Docker, existing token configuration provisions the image-provided plugin offline on restart; no manual step is needed. The boot log names the release and why it was installed. Outside Docker, install the plugin (Step 7) and restart. Nothing else changes: the tokens, the Slack app\nconfiguration, `SLACK_ALLOWED_USER_IDS` and `SLACK_STREAMING_MODE` stay as they are, and\nexisting conversations, linked identities and workspaces carry over because the plugin\nkeeps the platform id `slack`.\n\n## Troubleshooting\n\n### Bot Doesn't Respond\n\n1. Check that the plugin is installed: `archon plugin list` shows `coleam00/Archon/plugins/chat-slack`\n2. Check that Socket Mode is enabled\n3. Verify both tokens are correct in `.env`, then check the server log for `chat.bundled_adapter_retired` or a plugin start failure\n4. Ensure the bot is invited to the channel\n5. Make sure you're @mentioning the bot (not just typing)\n\n### \"channel_not_found\" Error\n\nThe bot needs to be invited to the channel:\n\n```\n/invite @your-bot\n```\n\n### \"missing_scope\" Error\n\nAdd the required scope in **OAuth & Permissions** and reinstall the app.\n\n### Thread Context Not Working\n\nEnsure these scopes are added:\n\n- `channels:history` (public channels)\n- `groups:history` (private channels)\n\n## Security Recommendations\n\n1. **Use User Whitelist**: Set `SLACK_ALLOWED_USER_IDS` to restrict bot access\n2. **Private Channels**: Invite the bot only to channels where it's needed\n3. **Token Security**: Never commit tokens to version control\n\n## Reference Links\n\n- [Slack API Documentation](https://api.slack.com/docs)\n- [Bolt for JavaScript](https://tools.slack.dev/bolt-js/)\n- [Socket Mode Guide](https://api.slack.com/apis/connections/socket)\n- [Permission Scopes](https://api.slack.com/scopes)\n","frontmatter":{"title":"Slack","description":"Install the Slack chat plugin and connect Archon to Slack over Socket Mode -- works behind firewalls with no public URL needed.","category":"adapters","area":"adapters","audience":["user","operator"],"status":"current","sidebar":{"order":2}},"isInternal":false,"tokens":3293,"sizeBytes":13614},{"name":"telegram.md","path":"packages/docs-web/src/content/docs/adapters/telegram.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/telegram.md","title":"Adapters Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Telegram\ndescription: Connect Archon to Telegram using the Bot API for mobile and desktop access.\ncategory: adapters\narea: adapters\naudience: [user, operator]\nstatus: current\nsidebar:\n  order: 3\n---\n\nConnect Archon to Telegram so you can interact with your AI coding assistant from any Telegram client.\n\n:::caution[Deprecated]\nThe Telegram adapter is deprecated and will be removed in the next minor release, 0.13.0. No Archon maintainer owns it. It keeps working until then, and the server logs a deprecation notice when it starts the adapter. If you need Telegram after that, build and own it as a [chat plugin](/guides/publishing-plugins/#chat-plugins). [#4641](https://github.com/coleam00/Archon/issues/4641) tracks the removal.\n:::\n\nNew identities are created as members. The operator can designate admins with [`archon user role <id> admin`](/reference/security/#users-and-roles); only a run's starter or an admin can act on it. See [Who can act on a run](/reference/security/#who-can-act-on-a-run).\n\n## Prerequisites\n\n- Archon server running (`archon serve`; see [Web UI](/adapters/web/))\n- A Telegram account\n\n## Create Telegram Bot\n\n1. Message [@BotFather](https://t.me/BotFather) on Telegram\n2. Send `/newbot` and follow the prompts\n3. Copy the bot token (format: `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`)\n\n## Set Environment Variable\n\n```ini\nTELEGRAM_BOT_TOKEN=123456789:ABCdefGHI...\n```\n\n## Configure User Whitelist (Optional)\n\nTo restrict bot access to specific users:\n1. Message [@userinfobot](https://t.me/userinfobot) on Telegram to get your user ID\n2. Add to environment:\n\n```ini\nTELEGRAM_ALLOWED_USER_IDS=123456789,987654321\n```\n\nWhen set, only listed user IDs can interact with the bot. When empty/unset, the bot responds to all users.\n\n## Configure Streaming Mode (Optional)\n\n```ini\nTELEGRAM_STREAMING_MODE=stream  # stream (default) | batch\n```\n\nFor streaming mode details, see [Configuration](/getting-started/configuration/).\n\n## Project selection\n\nSelect a registered project by submitting `/setproject <name>` in the conversation or thread where you want to work. To register a local directory first, submit `/register-project [--folder] <name> <path>`, then select it; registration alone does not bind the conversation. Projectless questions still work. Selection applies to that conversation, not every channel.\n\nOnce scoped, the assistant launches and manages runs through its native tools (Claude, Codex, Pi) or the existing CLI (Copilot/OpenCode). CLI launches are detached and headless, use CLI identity and return a run ID for inspection; they do not deliver progress or results to the originating chat. See [Deprecated providers](/getting-started/ai-assistants/#deprecated-providers).\n\n## Further Reading\n\n- [Configuration](/getting-started/configuration/)\n","frontmatter":{"title":"Telegram","description":"Connect Archon to Telegram using the Bot API for mobile and desktop access.","category":"adapters","area":"adapters","audience":["user","operator"],"status":"current","sidebar":{"order":3}},"isInternal":false,"tokens":676,"sizeBytes":2799},{"name":"web.md","path":"packages/docs-web/src/content/docs/adapters/web.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/adapters/web.md","title":"Adapters Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Web UI\ndescription: Built-in console for operating Archon from a browser.\ncategory: adapters\narea: adapters\naudience: [user]\nstatus: current\nsidebar:\n  order: 1\n---\n\nThe Web UI is Archon's built-in management console. It uses Archon's public API\nand does not require a separate hosted service.\n\n## Start the console\n\nFor local development, start the API server and Vite frontend together:\n\n```bash\nbun run dev\n```\n\nThe console is available at `http://localhost:5173`; the API server listens on\n`http://localhost:3090`.\n\nYou can also run them separately:\n\n```bash\nbun run dev:server\nbun run dev:web\n```\n\nFor a production build, the API server serves the compiled console on port 3090:\n\n```bash\nbun run build\nbun run start\n```\n\nThe backend binds to `0.0.0.0` by default. The Vite development server binds to\nlocalhost; pass `--host 0.0.0.0` only when you intentionally want to expose it:\n\n```bash\nbun run dev:web -- --host 0.0.0.0\n```\n\n## Authentication\n\nAuthentication is optional. A solo installation with Web authentication disabled\nopens the console directly. When authentication is enabled, the same console\nrequires a session and provides sign-in, signup when allowed by server policy,\nand sign-out.\n\n## Console navigation\n\nThe project rail is the main navigation surface:\n\n- **Runs** at `/console` shows runs across projects. Selecting a\n  project opens its scoped runs at `/console/p/:projectId`.\n- **Chat** at `/console/p/:projectId/chat` opens the operator conversation for\n  that project.\n- **Settings** at `/console/settings` manages assistant defaults, model tiers,\n  aliases, provider credentials, system status, GitHub identity, and alerts.\n- **Workflow builder** at `/console/builder` opens the experimental visual\n  authoring surface. Select a project before opening or creating a workflow.\n\nRegister a project with the add button in the rail. Remote Git URLs are cloned;\nlocal paths are registered in place. Removing a project removes its registration,\nnot its files.\n\n## Workflows and runs\n\nChoose a workflow from a project's run or chat view, supply its inputs, and\nlaunch it. The console follows execution through live events and exposes the\ngovernance actions that apply to the current state, including approval, rejection,\nresume, cancel, and abandon.\n\nA run can be opened from a project at\n`/console/p/:projectId/r/:runId` or directly by ID at\n`/console/r/:runId`. Direct links also work for runs that have no registered\nproject. Run details include the event log and artifacts; a graph is shown when\nthe run has the project context needed to load its workflow definition.\n\nTurn on **Alerts** in Settings to hear a sound, and see a browser notification\nwhere the browser allows it, when a run stops at an approval gate or an\nattention wait, or when it completes, fails, or is cancelled. The setting\nis saved in the current browser and is off by default. Runs that were already\nwaiting or finished when the console loaded do not alert. Settings shows when\nthe browser blocks the sound or notifications.\n\nOld settings, workflow-list, workflow-builder, and run bookmarks redirect to the\nclosest console route. Old chat and other retired pages land on the console\noverview because the conversation browser is no longer shipped.\n\n## Project chat\n\nProject chat streams assistant text and tool activity. When Web authentication\nis enabled, the console requests the signed-in user's project conversation and\nsends the active identity with each turn. On a solo installation, chat works\nwithout a user identity.\n\nAttachments are limited to five files of at most 10 MB each. The first message in\na new conversation cannot include attachments; create the conversation with a\ntext message, then attach files on the next turn.\n\n## Experimental workflow builder\n\nThe builder is retained as a bounded experiment inside the console. Use its\ncanvas, inspector, validation, and YAML preview to draft new workflows with\nprompt, command, bash, script, loop, approval, wait, and cancel nodes. Editing an\nexisting workflow and relying on a load/edit/save round trip is not supported\nuntil [#3378](https://github.com/coleam00/Archon/pull/3378) and\n[#3379](https://github.com/coleam00/Archon/pull/3379) land. Edit existing\nworkflows as YAML. The builder also does not represent include directives, loop\ngroups, or workflow sub-run nodes. Workflow YAML remains the authoritative\nartifact.\n\n## Further reading\n\n- [Quickstart](/getting-started/quick-start/)\n- [Configuration](/getting-started/configuration/)\n- [Workflow language](/reference/workflow-language/)\n- [API Reference](/reference/api/)\n","frontmatter":{"title":"Web UI","description":"Built-in console for operating Archon from a browser.","category":"adapters","area":"adapters","audience":["user"],"status":"current","sidebar":{"order":1}},"isInternal":false,"tokens":1039,"sizeBytes":4602},{"name":"index.md","path":"packages/docs-web/src/content/docs/brand/index.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/brand/index.md","title":"Brand Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Brand\ndescription: Archon brand foundation — colors, logo, and usage guidelines.\n---\n\n## Quick reference\n\nThe essentials at a glance:\n\n- **Primary gradient** — <code style=\"color:#ED10EC\">#ED10EC</code> → <code style=\"color:#8E40C8\">#8E40C8</code> → <code style=\"color:#06CE94\">#06CE94</code>\n- **Surface** — <code style=\"color:#0F1115;background:#fff;padding:0 6px;border-radius:3px\">#0F1115</code>\n- **Logo** — the shield mark with the abstract Archon glyph, stroked with the primary gradient\n\nUse the embedded foundation below for the canonical version. If you need a vector asset or have a question about an unusual usage, open a discussion on [GitHub](https://github.com/coleam00/Archon/discussions).\n\n## Full brand sheet\n\nThe Archon brand foundation lives below. It's a single self-contained page covering the logo, color system, typography, and approved usage.\n\n<iframe\n  src=\"/brand/foundation.html\"\n  title=\"Archon Brand Foundation\"\n  style=\"width:100%;height:80vh;border:1px solid var(--sl-color-gray-5);border-radius:8px;background:#0F1115;\"\n  loading=\"lazy\"\n></iframe>\n\nPrefer a full-window view? [Open the brand sheet in a new tab](/brand/foundation.html).\n","frontmatter":{"title":"Brand","description":"Archon brand foundation — colors, logo, and usage guidelines."},"isInternal":false,"tokens":315,"sizeBytes":1193},{"name":"cli-internals.md","path":"packages/docs-web/src/content/docs/contributing/cli-internals.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/contributing/cli-internals.md","title":"Contributing Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: CLI Internals\ndescription: Technical reference for the Archon CLI package — entry point flow, command routing, worktree logic, and adapter details.\ncategory: contributing\narea: cli\naudience: [developer]\nstatus: current\nsidebar:\n  order: 2\n---\n\nTechnical reference for understanding CLI internals.\n\n## Package Structure\n\n```\npackages/cli/\n├── src/\n│   ├── cli.ts              # Entry point, argument parsing, routing\n│   ├── commands/\n│   │   ├── workflow.ts     # workflow route module: re-exports every subcommand; reset-sessions/cleanup/event\n│   │   ├── workflow-control.ts # workflow resume/abandon/cancel/answer/approve/reject/respond (delegate to @archon/core/operations)\n│   │   ├── workflow-query.ts # workflow list/test/status/get/runs/logs/wait\n│   │   ├── workflow-run-*.ts # workflow run: select, admit, launch, execute\n│   │   ├── isolation.ts    # isolation list/cleanup (list/merged-cleanup delegate to @archon/core/operations)\n│   │   ├── setup.ts        # setup command implementation\n│   │   ├── chat.ts         # chat command implementation\n│   │   ├── validate.ts     # validate command implementation\n│   │   └── version.ts      # version command\n│   └── adapters/\n│       └── cli-adapter.ts  # IPlatformAdapter for stdout\n└── package.json            # Defines \"archon\" binary\n```\n\n## Entry Point Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon <command> [subcommand] [options] [arguments]             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ strip-cwd-env-boot  (first import, side-effect)                 │\n│   stripCwdEnv(): deletes Bun-auto-loaded <cwd>/.env* keys from  │\n│   process.env + CLAUDE_CODE_* session markers. Emits            │\n│   [archon] stripped N keys from <cwd> (...) when N > 0.         │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ loadCliEnv(cwd) — wraps loadArchonEnv with identity protection  │\n│   1. ~/.archon/.env        (home scope)                         │\n│   2. <cwd>/.archon/.env    (repo scope, exceptions below)       │\n│   Emits one [archon] loaded N keys from <path> line per file    │\n│   when N > 0 and ARCHON_VERBOSE_BOOT=1 or LOG_LEVEL=debug/trace.│\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Parse arguments                                         │\n│                 --cwd, --branch, --no-worktree, --help          │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Project resolution                                      │\n│                 Honor folder selection and registered folders;  │\n│                 otherwise resolve the nearest Git checkout      │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Route to command handler                                │\n│                 switch(command) → workflow | isolation | version│\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ cli.ts  Exit with code, always closeDatabase()                  │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/cli.ts`\n\nRepository env normally overrides home env. The CLI preserves the user-scope `GH_CONFIG_DIR` (including its absence), so repository env cannot redirect the stored gh login. Inside a workflow, inherited GitHub identity values take precedence over both env files, including empty values that disable host credentials. See [forge plugin configuration](/reference/forge/#plugin-configuration).\n\n**Project resolution:**\n\n- `workflow run --folder` uses the supplied canonical directory, even inside Git, and explicitly registers it as a folder project.\n- A registered folder and its ordinary descendants resolve to the folder directory. Archon runs folder workflows in place without managing a worktree, branch or PR.\n- A Git repository nested inside a registered folder retains its own repository identity. Other Git paths resolve to the nearest repository root, preserving linked-checkout ownership.\n- Unregistered non-Git execution requires folder registration; first-use CLI execution uses `workflow run --folder`. Without registration or that flag, execution exits with code 1.\n- File-only dry runs and fixture tests can inspect local files without registration. On a readable registry they honor folder ownership; if lookup fails they use the proven Git root or supplied directory. `workflow list` and `validate` retain their Git-root fallback when the registry cannot open.\n- `version`, `help`, `setup`, and `chat` bypass project resolution.\n\n---\n\n## `workflow list` Flow\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ archon workflow list [name] [--full] [--json]                    │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ workflow-query.ts  workflowListCommand(cwd, { name, full, json })│\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ @archon/workflows/workflow-discovery                              │\n│ discoverWorkflowsWithConfig(cwd, loadConfig, providerRegistry)    │\n│ - Loads bundled defaults                                         │\n│ - Searches .archon/workflows/ recursively                        │\n│ - Merges (repo overrides defaults by name)                       │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ Optional name resolution; project errors remain in the result    │\n│ full=false: bounded description + structured truncation state    │\n│ full=true: exact authored description                            │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n               ┌───────────────┴───────────────┐\n               │ json=true                     │ json=false\n               ▼                               ▼\n┌──────────────────────────┐   ┌───────────────────────────────────┐\n│ JSON output to stdout    │   │ Human-readable list to stdout     │\n│ descriptionTruncated     │   │ shortened descriptions carry     │\n│ { workflows, errors }    │   │ the ` [truncated]` marker         │\n└──────────────────────────┘   └───────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/commands/workflow-query.ts`\n\n---\n\n## `workflow run` Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon workflow run <name> [message] [--branch X] [--detach] …  │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ selectRunWorkflow()                                             │\n│ - Locate the run a continuation resumes                         │\n│ - Capture the workflow source, then discover from the capture   │\n│ - Find the workflow by name (error lists available)             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ admitRunExecution()   (--dry-run stops before this)             │\n│ - Flag, worktree policy, input and capability gates             │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                    ┌─────────────┴─────────────┐\n                    │                           │\n                --detach                    (default)\n                    │                           │\n                    ▼                           ▼\n┌───────────────────────────┐   ┌───────────────────────────────────┐\n│ launchDetachedOwnedRun()  │   │ prepareForegroundLaunch()         │\n│ Pre-flight, write the run │   │ Destination, project, adoption    │\n│ row, fork the child       │   │ lane, --resume binding, AI config │\n└───────────────────────────┘   └───────────────┬───────────────────┘\n                                                │\n                                                ▼\n                                ┌───────────────────────────────────┐\n                                │ provisionWorkspace()              │\n                                │ Folder seam, reused --branch      │\n                                │ worktree, or a new worktree       │\n                                └───────────────┬───────────────────┘\n                                                │\n                                                ▼\n                                ┌───────────────────────────────────┐\n                                │ executeOwnedRun()                 │\n                                │ Signal guard, progress, engine    │\n                                │ submit or resume, wait, teardown, │\n                                │ result                            │\n                                └───────────────────────────────────┘\n```\n\n**Code:** `workflowRunCommand` in `packages/cli/src/commands/workflow-run-owned.ts`, which owns the captured source and the durable-wait loop. The phases live in `workflow-run-admission.ts`, `workflow-run-launch.ts` and `workflow-run-execution.ts` beside it.\n\n**Worktree Provider:** `packages/isolation/src/providers/worktree.ts`\n\n---\n\n## `workflow event emit` Flow\n\n```\n┌──────────────────────────────────────────────────────────────────┐\n│ archon workflow event emit --run-id <run-id> --type <type> [...] │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ cli.ts  Validate --run-id, --type (required)                     │\n│         Validate --type against WORKFLOW_EVENT_TYPES              │\n│         Parse --data as JSON (warn + skip if invalid)            │\n└──────────────────────────────┬───────────────────────────────────┘\n                               │\n                               ▼\n┌──────────────────────────────────────────────────────────────────┐\n│ workflow.ts  workflowEventEmitCommand(..., cwd)                   │\n│              Resolve an unambiguous run-id prefix                 │\n│              Node state: persistWorkflowEvent(...)               │\n│              Observability: createWorkflowEvent(...)             │\n│              Run-ID resolution may fail                           │\n└──────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/cli.ts` (case 'event'), `packages/cli/src/commands/workflow.ts:workflowEventEmitCommand`\n\n**Contract:** The shared `isNodeStateEventType` predicate routes node-state events through\n`persistWorkflowEvent`, which propagates storage failures. The CLI prints `Event persisted`\nonly after that write succeeds. Other events use `createWorkflowEvent` and retain best-effort\npersistence: `Event submitted (best-effort)` does not guarantee storage. Run-ID resolution\ncan fail before either write.\n\n---\n\n## `isolation list` Flow\n\n```text\narchon isolation list → isolationListCommand()\n  │\n  ├─ @archon/core listEnvironments()\n  │    ├─ isolationDb.listAllActiveWithCodebase()\n  │    ├─ For each codebase: isolationDb.listByCodebaseWithAge()\n  │    │    └─ Reconcile missing worktrees, preserving live-run ownership\n  │    └─ Return environments grouped by codebase and reconciliation totals\n  │\n  ├─ Print worktree details and totals (return early if no codebases)\n  │\n  ├─ @archon/core listContainerEnvironments()\n  │    ├─ isolationDb.listActiveContainerEnvironments()\n  │    ├─ getRunByIsolationEnvId() for each container\n  │    │    └─ Return matched run or null; throw on query/status errors\n  │    └─ Build summaries; catch lookup errors and set runStatus: lookup-failed\n  │\n  └─ Print container details and totals: run status, no run (orphan),\n     or run lookup failed\n```\n\n**Code:** `packages/cli/src/commands/isolation.ts` (`isolationListCommand`),\n`packages/core/src/operations/isolation-operations.ts`\n(`listEnvironments`), `packages/core/src/services/cleanup/containers.ts`\n(`listContainerEnvironments`), `packages/core/src/db/workflow-run-reads.ts`\n(`getRunByIsolationEnvId`).\n\nThe container listing matches `metadata.isolation_env_id`; no match does not\nestablish that cleanup can remove the container. Cleanup checks wider live\nownership. A failed lookup is logged and displayed as `run lookup failed`.\n\n---\n\n## `isolation cleanup` Flow\n\n```text\narchon isolation cleanup [days] (default: 7)\n  │\n  ├─ Reconcile missing worktrees (preserve container rows)\n  │\n  ├─ findStaleEnvironments(days)\n  │    └─ Filter provider = 'worktree'\n  │         └─ For each stale worktree:\n  │              ├─ Skip environments owned by resumable runs\n  │              ├─ Skip any with a commit on no remote (getCleanupRunLock)\n  │              └─ provider.destroy(path, options)\n  │                   └─ Mark destroyed only after complete removal\n  │\n  └─ cleanupContainerEnvironments(days), even when no worktrees are stale\n       ├─ Skip containers owned by resumable runs\n       └─ Container backend removes container and volume\n            └─ Mark destroyed only after successful removal\n```\n\n**Code:** `isolationCleanupCommand` in `packages/cli/src/commands/isolation.ts`\n\n---\n\n## `isolation cleanup --merged` Flow\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ archon isolation cleanup --merged [--include-closed]            │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ isolation.ts  isolationCleanupMergedCommand({ includeClosed })  │\n│ For each codebase → cleanupMergedWorktrees(codebaseId, path)    │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │\n                     For each active environment\n                                  │\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ judgeBranchForRemoval() — three-signal union                    │\n│  localBranchExists()     gate: (a) and (b) need the local ref   │\n│  (a) isBranchMerged()    git ancestry (fast-forward/merge)      │\n│  (b) isPatchEquivalent() git cherry  (1-commit squash-merge)    │\n│  (c) getPrState()        gh CLI      (MERGED/CLOSED/OPEN/NONE/  │\n│                                      UNAVAILABLE)               │\n│                                                                 │\n│  any git signal          → 'merged' if the worktree HEAD        │\n│                            also passes `git cherry <base> HEAD` │\n│  MERGED                  → 'merged' if the worktree HEAD        │\n│                            and the branch ref (where each       │\n│                            exists) are at or behind the PR head │\n│  CLOSED                  → 'pr-closed' the same way, only if    │\n│                            includeClosed                        │\n│  OPEN                    → 'open-pr', always kept               │\n│  UNAVAILABLE             → 'pr-unavailable', kept and reported  │\n│  NONE and ref present    → 'unmerged', not reclaimed            │\n│  NONE and ref gone       → 'unjudgeable', kept and reported     │\n└─────────────────────────────────┬───────────────────────────────┘\n                                  │ 'merged' or 'pr-closed'\n                                  ▼\n┌─────────────────────────────────────────────────────────────────┐\n│ Guard checks: no uncommitted changes, no run lock (checkRemoval │\n│ → getCleanupRunLock; a failed run's lock is released by the     │\n│ verdict, running/pending/paused runs always lock)               │\n│ provider.destroy() → remove worktree and local branch           │\n│ → commit registration retirement; retain remote branch         │\n└─────────────────────────────────────────────────────────────────┘\n```\n\nCompletion and merged cleanup retain remote branches, including with force,\nbecause registration ownership cannot be proved after retirement. Retention is\nlogged as a warning and displayed in both completion and merged-cleanup reports.\nEnable GitHub's **Automatically delete head branches** setting for\nmerged pull requests, or follow [manual remote branch recovery](/reference/cli/#recovering-remote-branch-cleanup).\n\nSignals are evaluated in order — the first positive match short-circuits to avoid\nunnecessary `gh` API calls. The `gh` CLI is a soft dependency: when it is not installed or\nthe remote is not GitHub, only git signals are used (`NONE`). When `gh` is there but fails\n(auth, rate limit, unreadable output) the result is `UNAVAILABLE`, and the environment is\nkept and reported rather than passed to the staleness sweep.\n\nA multi-commit squash merge is invisible to both git signals, so PR state is the only\none that sees it. Both git signals also need the local branch ref: once it is gone, the\nPR is the only judge, and a branch with no PR is reported as unverifiable instead of\nfailing the check. A MERGED or CLOSED PR only covers the commits it carried. Run\nbranch names are reused, and removal deletes both the worktree and the branch, so each\nlocal tip that still exists, the worktree's HEAD and the branch ref, must be the PR's head\ncommit or an ancestor of it (`isRevCoveredBy`). A detached HEAD or a reused branch with\ncommits past the PR head is unmerged work. The same holds when git proves the merge: the worktree's\nHEAD must also pass `git cherry <base> HEAD`, which answers for both git signals (an\nancestor of the base lists nothing, a squash-merged commit lists as `-`). A HEAD it\ncannot answer for is reported as a failed merge check. A PR head pushed from elsewhere and never fetched here is\nfetched by SHA from the branch's remote first; if that fetch fails, the check fails and\nthe worktree is kept, with the fetch error in the reason. The scheduled sweep (`runScheduledCleanup`) makes the same call, so\nboth paths agree on what counts as merged.\n\n**Code:** `packages/core/src/services/cleanup/` — `judgeBranchForRemoval()` in `merge-verdict.ts`, `cleanupMergedWorktrees()` in `merged-sweep.ts`, `getCleanupRunLock()` in `run-lock.ts`; `runScheduledCleanup()` in `packages/core/src/services/cleanup-service.ts`\n**Code:** `packages/isolation/src/pr-state.ts` — `getPrState()`\n**Code:** `packages/git/src/branch.ts` — `isPatchEquivalent()`, `localBranchExists()`\n\n---\n\n## CLI Adapter\n\nImplements `IPlatformAdapter` for terminal output.\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ CLIAdapter                                                      │\n├─────────────────────────────────────────────────────────────────┤\n│ sendMessage(convId, msg) → Output to stdout                     │\n│ getStreamingMode()       → 'batch'                              │\n│ getPlatformType()        → 'cli'                                │\n│ ensureThread()           → passthrough                          │\n│ start() / stop()         → no-op                                │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Code:** `packages/cli/src/adapters/cli-adapter.ts:13-47`\n\n---\n\n## Key Dependencies\n\n| Function | Package | Location | Purpose |\n|----------|---------|----------|---------|\n| `discoverWorkflowsWithConfig(cwd, loadConfig, providerRegistry)` | `@archon/workflows/workflow-discovery` | `workflows/src/workflow-discovery.ts` | Find and parse workflow YAML |\n| `executeWorkflow(...)` | `@archon/workflows/executor` | `workflows/src/executor.ts` | Run workflow steps |\n| `getIsolationProvider()` | `@archon/isolation` | `isolation/src/factory.ts` | Get WorktreeProvider singleton |\n| `conversationDb.*` | `@archon/core` | `core/src/db/conversations.ts` | Conversation CRUD |\n| `codebaseDb.*` | `@archon/core` | `core/src/db/codebases.ts` | Codebase CRUD |\n| `isolationDb.*` | `@archon/core` | `core/src/db/isolation-environments.ts` | Worktree tracking |\n| `git.*` | `@archon/git` | `packages/git/src/` | Git operations |\n| `closeDatabase()` | `@archon/core` | `core/src/db/connection.ts` | Clean shutdown |\n\n---\n\n## Conversation ID Format\n\nCLI conversations use ID format: `cli-{timestamp}-{random}`\n\nThe random suffix contains 16 cryptographically random bytes encoded as 32 hex characters.\n\nGenerated at: `packages/cli/src/utils/conversation-id.ts`\n\n---\n\n## Worktree Reuse Logic\n\nWhen `--branch` is provided:\n\n1. **Lookup:** `isolationDb.findActiveByWorkflow(codebaseId, 'task', branchName)`\n2. **Health check:** `provider.healthCheck(path)` on existing\n3. **Reuse:** If found and healthy (warns if `--from` was specified but not applied)\n4. **Create:** If not found or unhealthy -- passes a `taskBranch: { kind: 'new', fromBranch }`\n   selection to the provider when `--from` is specified. Adoption instead passes\n   `taskBranch: { kind: 'existing', branch }`, which checks out that exact local branch\n   without creating a child branch or syncing it to a remote.\n\nWorktrees stored at: `~/.archon/workspaces/<owner>/<repo>/worktrees/<branch-slug>/`\n\n**Code:** `provisionWorktree` in `packages/cli/src/commands/workflow-run-launch.ts`\n\n---\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| 0 | Success, except for the JSON control refusals noted below. |\n| 1 | Error. Answer commands (`answer`, `respond`, `approve`, `reject`) and `resume --detach` also return 1 on refusal. Human-mode diagnostics go to stderr; JSON refusals emit `ok: false` with the reason on stdout. |\n\nNon-detached JSON `resume`, `abandon`, and `cancel` can return 0 with `ok: false`.\nCheck `ok` when consuming those acknowledgments.\n\n---\n\n## Database Connection\n\n- Connection opened on first database call\n- Always closed in `finally` block after command completes\n- **Default: SQLite** at `~/.archon/archon.db` (zero setup, auto-initialized)\n- **Optional: PostgreSQL** when `DATABASE_URL` is set (for cloud/advanced deployments)\n\n**Code:** `packages/cli/src/cli.ts`\n","frontmatter":{"title":"CLI Internals","description":"Technical reference for the Archon CLI package — entry point flow, command routing, worktree logic, and adapter details.","category":"contributing","area":"cli","audience":["developer"],"status":"current","sidebar":{"order":2}},"isInternal":false,"tokens":5186,"sizeBytes":31050},{"name":"dx-quirks.md","path":"packages/docs-web/src/content/docs/contributing/dx-quirks.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/contributing/dx-quirks.md","title":"Contributing Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: DX Quirks\ndescription: Known development experience quirks and workarounds when working on the Archon codebase.\ncategory: contributing\naudience: [developer]\nstatus: current\nsidebar:\n  order: 6\n---\n\nDevelopment experience notes and workarounds.\n\n## Bun Log Elision\n\nWhen running `bun dev` from the repo root, Bun's `--filter` truncates logs:\n\n```\n@archon/server dev $ bun --watch src/index.ts\n│ [129 lines elided]\n│ [Hono] Server listening on port 3090\n└─ Running...\n```\n\n**To see full logs**, run directly from the server package:\n\n```bash\ncd packages/server && bun --watch src/index.ts\n```\n\nOr:\n\n```bash\nbun --cwd packages/server run dev\n```\n\nNote: The root `bun dev` uses `--filter` to fix hot reload path issues, but this comes with log condensing.\n\n## `mock.module()` merges over the real module\n\nBun merges a `mock.module()` factory over the real module: an export the factory leaves out keeps its real implementation. When a mocked module gains an export, every factory that does not list it starts running real code (a database, the network, the filesystem) while its tests still pass, often until a 5-second timeout.\n\n`bun run check:mock-modules` (validate id `mock-modules`, run in CI) fails on every factory that leaves out a value export of the module it mocks, naming the file, line, specifier and missing names. Mock each missing export, or spread the real module to keep the rest real on purpose:\n\n```ts\nmock.module('../db/workflows', async () => ({\n  ...(await import('../db/workflows')),\n  getWorkflowRun: mockGetWorkflowRun,\n}));\n```\n\nA spreading factory provides every export, including ones the module gains later; that is the author's declaration. Where test files share a process (packages other than `@archon/core`, unless the file has its own group), the `import()` can return a module an earlier file already mocked.\n\nFactories that were incomplete when the check landed are grandfathered in `scripts/mock-module-baseline.json`. A grandfathered site may omit only the exports its module's grandfathered sites omitted then, so a new export still fails at every one of them. The baseline only shrinks: after completing a factory, run `bun run check:mock-modules --write`. `bun run check:ratchet` refuses entries the baseline gains unless a commit in the change carries `Ratchet-Allow: scripts/mock-module-baseline.json`.\n\nWhat the check does not prove:\n\n- It reads factory keys from the factory's type, so an `as` cast can misstate them.\n- It checks only literal specifiers, and refuses a non-literal one.\n- It refuses a module TypeScript cannot read (no type declarations, or a path that does not resolve) unless the site is grandfathered.\n- A grandfathered factory can drop a key that another grandfathered site of the same module already omits.\n- It says nothing about a client a test never stubbed, or about I/O outside `mock.module()`. The adapters package preloads `no-network` (it traps only `globalThis.fetch`) and `no-archon-home-writes` guards for part of that; Bun reads `bunfig.toml` only from the working directory, so those guards apply only when the tests run from `packages/adapters`. Their headers list the limits.\n- It runs in validate and CI, not inside `bun test`.\n\n## `mock.module()` Pollution\n\nBun's `mock.module()` is process-global and irreversible — `mock.restore()` does NOT undo it.\n\n- Never add `afterAll(() => mock.restore())` for `mock.module()` cleanup — it has no effect\n- Use `spyOn()` for internal modules that other test files import directly (e.g., `spyOn(git, 'checkout')`) — `spy.mockRestore()` DOES work for spies\n- Never `mock.module()` a module path that another test file also `mock.module()`s with a different implementation\n- When adding a new test file with `mock.module()` to `packages/core`, make its first line exactly `// @archon-test-isolated` so it runs in its own process. Core discovers its tests, and discovery fails on a file that calls `mock.module()` without the marker. A file that mocks through a helper needs the marker too; discovery cannot see that call\n- In the other packages, give the file its own entry in `testGroups` in the package's `package.json`, or its own `bun test` step for a package still using a `&&` chain\n- A package's `testConcurrency` in `package.json` runs that many `testGroups` at once, each still in its own process; leave it out to run groups one at a time\n- Groups start slowest first, by the times in `scripts/test-weights.json`. CI splits slow shards into parts with `ARCHON_TEST_PART=<index>/<count>`, using the same times; `ARCHON_TEST_PART=1/2 bun run test` in a package runs that part locally. A new group with no recorded time counts as one second, so refresh the file with `bun scripts/update-test-weights.ts <run-id>` when one part of a shard finishes well after its sibling\n\n## Worktree Port Allocation\n\nWorktrees auto-allocate ports (3190–4089 range, hash-based on path). Same worktree always gets same port.\n\n- Main repo defaults to 3090\n- Override: `PORT=4000 bun dev`\n- Same worktree always gets same port (deterministic)\n- If that port is already taken — another worktree hashed to it, or any unrelated\n  process holds it — the server takes the next free port in the range and logs\n  `worktree_port_reallocated` with both the port it wanted and the one it got.\n  The root `bun dev` launcher passes that selected port to both the server and Web\n  client, so the Vite proxy and SSE streams use the endpoint the server binds. It\n  returns to the hashed port once that frees up.\n- `PORT` is never moved: an explicit override binds where you said, or fails.\n\n## `bun run test` vs `bun test`\n\n**NEVER run `bun test` from the repo root** — it discovers all test files across all packages and runs them in one process, causing ~135 mock pollution failures.\n\nAlways use `bun run test`, which runs each package's own grouped suite for isolation.\n\n### Running one test\n\n`bun run test <path>` runs that path.\n\n```bash\nbun run test packages/paths/src/skills.test.ts   # from the repo root\ncd packages/paths && bun run test src/skills.test.ts\n```\n\nFrom the repo root, an argument naming a path under `packages/<name>/`, `scripts/` or\n`.archon/scripts/` decides where `bun test` runs; flags and substring filters are\nforwarded unchanged. Paths naming several packages run one package at a time and stop at\nthe first failure. An argument that names no owner fails instead of running the suite.\n\nA package whose `scripts.test` is still a `bun test ... && bun test ...` chain rather than\n`bun run ../../scripts/package-tests.ts` ignores the argument when you run it from inside\nthe package: `bun run` appends arguments to the script, so the whole chain runs and a wrong\npath still reports a pass. Run those from the repo root, which routes the path itself.\n","frontmatter":{"title":"DX Quirks","description":"Known development experience quirks and workarounds when working on the Archon codebase.","category":"contributing","audience":["developer"],"status":"current","sidebar":{"order":6}},"isInternal":false,"tokens":1619,"sizeBytes":6796},{"name":"index.md","path":"packages/docs-web/src/content/docs/contributing/index.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/contributing/index.md","title":"Contributing Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Contributing\ndescription: Developer guides for contributing to Archon — internals, testing, releasing.\ncategory: contributing\naudience: [developer]\nsidebar:\n  order: 0\n---\n\nGuides for developers contributing to the Archon codebase.\n\n## Developer Guides\n\n- **[New Developer Guide](/contributing/new-developer-guide/)** — Codebase orientation, architecture overview, and first steps\n- **[CLI Internals](/contributing/cli-internals/)** — CLI package internals, command routing, and worktree logic\n- **[Releasing](/contributing/releasing/)** — Version management and release process\n- **[DX Quirks](/contributing/dx-quirks/)** — Known development experience issues and workarounds\n\n","frontmatter":{"title":"Contributing","description":"Developer guides for contributing to Archon — internals, testing, releasing.","category":"contributing","audience":["developer"],"sidebar":{"order":0}},"isInternal":false,"tokens":161,"sizeBytes":699},{"name":"new-developer-guide.md","path":"packages/docs-web/src/content/docs/contributing/new-developer-guide.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/contributing/new-developer-guide.md","title":"Contributing Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: New Developer Guide\ndescription: Codebase orientation for new Archon developers — architecture overview, workflows, platforms, and first steps.\ncategory: contributing\naudience: [developer]\nstatus: current\nsidebar:\n  order: 1\n---\n\n> **TL;DR**: Archon lets you control AI coding assistants (Claude Code, Codex) from your phone via Telegram, Slack, Discord, or GitHub. Think of it as a remote control for AI pair programming.\n\n---\n\n## The Problem We Solve\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│                        WITHOUT ARCHON                               │\n│                                                                     │\n│   You're on the train, phone in hand...                            │\n│                                                                     │\n│   ┌──────────┐     ❌ Can't SSH      ┌──────────────────┐          │\n│   │  Phone   │ ──────────────────────│  Dev Machine     │          │\n│   │          │     ❌ No terminal    │  (Claude Code)   │          │\n│   └──────────┘     ❌ No IDE         └──────────────────┘          │\n│                                                                     │\n│   \"I wish I could just message Claude to fix that bug...\"          │\n└─────────────────────────────────────────────────────────────────────┘\n\n┌─────────────────────────────────────────────────────────────────────┐\n│                         WITH ARCHON                                 │\n│                                                                     │\n│   ┌──────────┐                       ┌──────────────────┐          │\n│   │  Phone   │ ─────Telegram────────▶│  Archon Server   │          │\n│   │          │     \"fix issue #42\"   │                  │          │\n│   └──────────┘                       │  ┌────────────┐  │          │\n│        │                             │  │Claude Code │  │          │\n│        │                             │  │   SDK      │  │          │\n│        │                             │  └─────┬──────┘  │          │\n│        │                             │        │         │          │\n│        │                             │  ┌─────▼──────┐  │          │\n│        │◀────\"PR created #127\"───────│  │ Git Repo   │  │          │\n│        │                             │  │ (worktree) │  │          │\n│                                      │  └────────────┘  │          │\n│                                      └──────────────────┘          │\n│                                                                     │\n│   You just fixed a bug from your phone.                            │\n└─────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Core Concept: Message → AI → Code → Response\n\n```\n┌──────────────────────────────────────────────────────────────────────────┐\n│                                                                          │\n│   USER                    ARCHON                         CODEBASE        │\n│                                                                          │\n│   ┌─────────┐            ┌─────────────────┐            ┌──────────┐    │\n│   │Telegram │            │                 │            │          │    │\n│   │  Slack  │───Message─▶│   Orchestrator  │───Claude──▶│ Git Repo │    │\n│   │ Discord │            │                 │   Code     │          │    │\n│   │ GitHub  │◀──Response─│   (routes to    │◀──────────│ (files)  │    │\n│   └─────────┘            │    AI client)   │            └──────────┘    │\n│                          └─────────────────┘                             │\n│                                                                          │\n│   That's it. You message, AI works on code, you get results.            │\n│                                                                          │\n└──────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## The Four Ways to Use Archon\n\n### 1. Command Line (Local Execution)\n\nRun workflows directly from your terminal without needing the server:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ TERMINAL                                                        │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ $ bun run cli workflow list                                     │\n│                                                                 │\n│ Available workflows in .archon/workflows/:                     │\n│   - archon-ship                  Issue to reviewed PR           │\n│   - archon-investigate           Prove a root cause             │\n│   - archon-review                Parallel PR review             │\n│                                                                 │\n│ $ bun run cli workflow run archon-investigate \"What does the    │\n│   orchestrator do?\"                                             │\n│                                                                 │\n│ 🔧 READ                                                         │\n│ Reading: packages/core/src/orchestrator/orchestrator.ts                │\n│                                                                 │\n│ The orchestrator is the main entry point that routes incoming  │\n│ messages. It checks if it's a slash command, loads conversation│\n│ context from the database, and routes to the appropriate AI     │\n│ client for processing...                                        │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n**Good for:** Running workflows locally, testing, automation scripts, CI/CD\n\n### 2. Direct Chat (Simple Questions)\n\nJust talk to the AI like you would in Claude Code terminal:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ TELEGRAM CHAT                                                   │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ You: What does the handleMessage function do?                   │\n│                                                                 │\n│ Archon: Looking at packages/core/src/orchestrator/orchestrator.ts...          │\n│                                                                 │\n│         The handleMessage function is the main entry point      │\n│         that routes incoming messages. It:                      │\n│         1. Checks if it's a slash command                       │\n│         2. Loads conversation context from database             │\n│         3. Routes to AI client for processing                   │\n│         4. Streams responses back to platform                   │\n│                                                                 │\n│         See: packages/core/src/orchestrator/orchestrator.ts            │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### 3. Slash Commands (Specific Operations)\n\nDeterministic commands that don't involve AI:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ SLASH COMMANDS                                                  │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ /clone https://github.com/user/repo    Clone a repository      │\n│ /status                                 Show current state      │\n│ /repos                                  List available repos    │\n│ /setcwd /path/to/dir                   Change working dir      │\n│ /reset                                  Clear AI session        │\n│ /help                                   Show all commands       │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### 4. Workflows (Multi-Step Automation)\n\nThis is where Archon shines - automated multi-step AI workflows:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ GITHUB ISSUE #42                                                │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│ Title: Login button doesn't work on mobile                      │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @user commented:                                                │\n│   @archon fix this issue                                        │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @archon commented:                                              │\n│   🔍 Investigation Complete                                     │\n│                                                                 │\n│   Root Cause: Touch event handler missing on mobile             │\n│   File: packages/server/src/components/LoginButton.tsx:45                       │\n│   Fix: Add onTouchEnd handler alongside onClick                 │\n│                                                                 │\n│   Creating PR...                                                │\n│                                                                 │\n│ ─────────────────────────────────────────────────────────────── │\n│                                                                 │\n│ @archon commented:                                              │\n│   ✅ Fix implemented: PR #127                                   │\n│   - Added touch event handling                                  │\n│   - Added mobile viewport tests                                 │\n│   - All tests passing                                           │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## How Workflows Work (The Magic)\n\nA workflow is a YAML file that chains AI prompts together:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   .archon/workflows/fix-github-issue.yaml                              │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ name: fix-github-issue                                          │  │\n│   │ description: Investigate and fix a GitHub issue                 │  │\n│   │                                                                 │  │\n│   │ nodes:                                                          │  │\n│   │   - id: investigate                                             │  │\n│   │     command: investigate-issue    ◀── Node 1: Research         │  │\n│   │   - id: implement                                               │  │\n│   │     command: implement-issue      ◀── Node 2: Fix              │  │\n│   │     depends_on: [investigate]                                   │  │\n│   │     context: fresh                                              │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│                              │                                          │\n│                              ▼                                          │\n│                                                                         │\n│   EXECUTION FLOW:                                                       │\n│                                                                         │\n│   ┌──────────────────┐      ┌──────────────────┐      ┌────────────┐  │\n│   │  investigate-    │      │   implement-     │      │            │  │\n│   │  issue.md        │─────▶│   issue.md       │─────▶│  PR #127   │  │\n│   │                  │      │                  │      │            │  │\n│   │  - Read issue    │      │  - Read artifact │      │  Created!  │  │\n│   │  - Explore code  │      │  - Make changes  │      │            │  │\n│   │  - Find root     │      │  - Run tests     │      │            │  │\n│   │    cause         │      │  - Commit        │      │            │  │\n│   │  - Save artifact │      │  - Create PR     │      │            │  │\n│   └──────────────────┘      └──────────────────┘      └────────────┘  │\n│                                                                         │\n│   Each \"command\" is a markdown file with AI instructions.              │\n│   The workflow executor runs nodes in dependency order.                │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## The Router: How Archon Picks Workflows\n\nWhen you send a message, an AI \"router\" decides what to do:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   USER MESSAGE                           ROUTER DECISION                │\n│                                                                         │\n│   \"fix this issue\"          ───────▶     archon-ship                   │\n│   \"review this PR\"          ───────▶     archon-review                 │\n│   \"what does X do?\"         ───────▶     answered directly, no workflow│\n│   \"update sharp\"            ───────▶     archon-upkeep                 │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   HOW IT WORKS:                                                         │\n│                                                                         │\n│   ┌──────────┐     ┌─────────────────────────────────────┐             │\n│   │ Message  │────▶│ Router AI reads workflow descriptions│             │\n│   │          │     │ and picks the best match             │             │\n│   └──────────┘     └──────────────────┬──────────────────┘             │\n│                                       │                                 │\n│                                       ▼                                 │\n│                    ┌─────────────────────────────────────┐             │\n│                    │ invoke_workflow: archon-ship        │             │\n│                    └─────────────────────────────────────┘             │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Available Workflows\n\nThe table below lists the key workflows in the bundled `sdlc` pack. All bundled workflows are prefixed with `archon-`. Run `bun run cli workflow list` to see the full current list.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   WORKFLOW                              TRIGGER PHRASES    WHAT IT DOES │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-ship               \"fix this issue\"        Triage +       │  │\n│   │                           \"implement #42\"         deliver a PR   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-review             \"review this PR\"        Parallel       │  │\n│   │                           \"is this ready\"         review lenses  │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-deliver            \"deliver this plan\"     Implement +    │  │\n│   │                           \"take this to a PR\"     PR + review    │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ archon-upkeep             \"update sharp\"          One dependency │  │\n│   │                           \"bump X to latest\"      bump + PR      │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Parallel Agents: The PR Review Example\n\nA quick route agent picks which specialist lenses the change earns, and `archon-review` runs them in fresh sessions as shown below. The code lens uses the configured `large` model tier.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   USER: \"review this PR\"                                               │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 1: scope + route          Resolve the PR, its diff, the    │  │\n│   │                                contract, and which lenses run   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 2: PARALLEL LENSES (each in a fresh session)               │  │\n│   │                                                                 │  │\n│   │   ┌──────────────┐  ┌──────────────┐                           │  │\n│   │   │ code         │  │ seams        │                           │  │\n│   │   │              │  │              │                           │  │\n│   │   │ Bugs,        │  │ Contracts    │                           │  │\n│   │   │ correctness  │  │ that drift   │                           │  │\n│   │   └──────────────┘  └──────────────┘                           │  │\n│   │                                                                 │  │\n│   │   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐         │  │\n│   │   │ tests        │  │ errors       │  │ docs         │         │  │\n│   │   │              │  │              │  │ (when docs   │         │  │\n│   │   │ Missing      │  │ Visibility & │  │  change)     │         │  │\n│   │   │ coverage     │  │ containment  │  │ Stale docs   │         │  │\n│   │   └──────────────┘  └──────────────┘  └──────────────┘         │  │\n│   │                                                                 │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 3: synthesize             Combine findings into one verdict│  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ Step 4: publish                Post one review comment on the PR│  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\nReview never edits code. Fixing findings belongs to `archon-implement`, or to `archon-deliver`, which runs review and corrections in a loop.\n\n---\n\n## Platform Integration\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   CLI                               HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Direct command execution         │\n│   │  Terminal        │              - Real-time streaming to stdout    │\n│   │                  │              - No server needed                 │\n│   │  bun run cli     │              - Good for local workflows         │\n│   │  workflow run    │              - Perfect for CI/CD                │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   TELEGRAM                          HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Bot polls for messages           │\n│   │  @archon_bot     │              - Real-time streaming (default)    │\n│   │                  │              - DM the bot directly              │\n│   │  \"fix issue #42\" │              - Good for mobile use              │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   SLACK                             HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Socket Mode (no webhooks)        │\n│   │  #dev-channel    │              - @mention in threads              │\n│   │                  │              - DM the bot                       │\n│   │  @archon review  │              - Good for team visibility         │\n│   │  this PR         │                                                  │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   DISCORD                           HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - WebSocket connection             │\n│   │  #coding-help    │              - @mention to activate             │\n│   │                  │              - Thread support                   │\n│   │  @Archon what    │              - Good for communities             │\n│   │  does this do?   │                                                  │\n│   └──────────────────┘                                                  │\n│                                                                         │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   GITHUB                            HOW IT WORKS                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   ┌──────────────────┐              - Webhook on issues/PRs            │\n│   │  Issue #42       │              - @archon in comments              │\n│   │                  │              - Batch mode (assembled reply)     │\n│   │  @archon fix     │              - Auto-creates PRs                 │\n│   │  this issue      │              - Good for automation              │\n│   └──────────────────┘                                                  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\nBatch mode assembles the reply before delivery. Adapters may split long replies into multiple messages or comments.\n\n## Isolation: Git Worktrees\n\nEach conversation gets its own isolated copy of the repo:\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   ~/.archon/workspaces/owner/repo/worktrees/                           │\n│   │                                                                     │\n│   ├── issue-42/              ◀── Conversation about issue #42         │\n│   │   └── (full repo)            Working on fix for mobile bug         │\n│   │                                                                     │\n│   ├── pr-127/                ◀── Conversation about PR #127           │\n│   │   └── (full repo)            Reviewing code changes                │\n│   │                                                                     │\n│   └── task-dark-mode/        ◀── Manual feature work                  │\n│       └── (full repo)            Adding dark mode feature              │\n│                                                                         │\n│   WHY WORKTREES?                                                        │\n│   ─────────────────────────────────────────────────────────────────    │\n│   - Multiple conversations can work simultaneously                     │\n│   - No branch conflicts between parallel work                          │\n│   - Each gets isolated file changes                                    │\n│   - Cleaned up when issue/PR closes                                    │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Configuration Hierarchy\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   CONFIGURATION LAYERS (later overrides earlier)                       │\n│                                                                         │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 1. DEFAULTS (hardcoded)                                         │  │\n│   │    assistant: claude                                            │  │\n│   │    streaming.telegram: stream                                   │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 2. GLOBAL CONFIG (~/.archon/config.yaml)                        │  │\n│   │    botName: MyBot                                               │  │\n│   │    defaultAssistant: claude                                     │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 3. REPO CONFIG (.archon/config.yaml)                            │  │\n│   │    assistant: codex          # This repo prefers Codex          │  │\n│   │    commands:                                                    │  │\n│   │      folder: .claude/commands/custom                            │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                              │                                          │\n│                              ▼                                          │\n│   ┌─────────────────────────────────────────────────────────────────┐  │\n│   │ 4. ENVIRONMENT VARIABLES (highest priority)                     │  │\n│   │    TELEGRAM_STREAMING_MODE=batch                                │  │\n│   │    DEFAULT_AI_ASSISTANT=claude                                  │  │\n│   └─────────────────────────────────────────────────────────────────┘  │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Directory Structure\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   YOUR REPO                         ARCHON SERVER                       │\n│                                                                         │\n│   my-app/                           ~/.archon/                          │\n│   ├── .archon/                      ├── config.yaml      (global cfg)  │\n│   │   ├── config.yaml               ├── workspaces/      (cloned repos)│\n│   │   ├── commands/                 │   └── user/repo/                 │\n│   │   │   ├── investigate-issue.md  │       ├── source/    (clone)      │\n│   │   │   ├── implement-issue.md   │       └── worktrees/ (isolation)  │\n│   │   │   └── assist.md            │           ├── issue-42/           │\n│   │   ├── workflows/               │           └── pr-127/             │\n│   │   │   ├── fix-github-issue.yaml                                    │\n│   │   │   └── assist.yaml                                              │\n│   │   └── artifacts/                                                   │\n│   │       └── issues/                                                  │\n│   │           └── issue-42.md                                          │\n│   ├── packages/                                                             │\n│   └── ...                                                              │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Quick Reference: Common Interactions\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   WHAT YOU WANT                     WHAT TO SAY (Platform/CLI)          │\n│                                                                         │\n│   Run workflow locally              bun run cli workflow run <name>     │\n│   List CLI workflows                bun run cli workflow list           │\n│   Fix a GitHub issue                \"@archon fix this issue\"            │\n│   Review a PR                       \"@archon review this PR\"            │\n│   Ask a question                    \"What does handleMessage do?\"       │\n│   Resolve conflicts                 \"@archon resolve the conflicts\"     │\n│   See current state                 \"/status\"                           │\n│   Clone a repo                      \"/clone https://github.com/u/r\"     │\n│   Switch repos                      \"/repos\" then pick one              │\n│   List available workflows          \"/workflow list\"                    │\n│   Reload workflow definitions       \"/workflow reload\"                  │\n│   Approve paused workflow           \"/workflow approve <id> [comment]\"  │\n│   Reject paused workflow           \"/workflow reject <id> [reason]\"   │\n│   Cancel stuck workflow             \"/workflow cancel\"                  │\n│   Start fresh                       \"/reset\"                            │\n│   Get help                          \"/help\"                             │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Summary\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                                                                         │\n│   ARCHON = Remote Control for AI Coding Assistants                     │\n│                                                                         │\n│   ┌────────────────────────────────────────────────────────────────┐   │\n│   │                                                                │   │\n│   │   Phone/Slack/GitHub ──▶ Archon Server ──▶ AI (Claude/Codex)  │   │\n│   │                              │                    │            │   │\n│   │                              ▼                    ▼            │   │\n│   │                         Workflows           Git Worktrees      │   │\n│   │                        (automation)         (isolation)        │   │\n│   │                                                                │   │\n│   └────────────────────────────────────────────────────────────────┘   │\n│                                                                         │\n│   KEY CAPABILITIES:                                                    │\n│   ─────────────────                                                    │\n│   ✓ Message from anywhere (phone, tablet, desktop)                    │\n│   ✓ Automated multi-step workflows                                    │\n│   ✓ Parallel AI agents for complex tasks                              │\n│   ✓ Isolated environments per conversation                            │\n│   ✓ Custom prompts versioned in Git                                   │\n│   ✓ GitHub integration (issues/PRs/comments)                          │\n│                                                                         │\n│   WHEN TO USE:                                                         │\n│   ─────────────                                                        │\n│   ✓ You want to fix bugs from your phone                              │\n│   ✓ You want automated PR reviews                                     │\n│   ✓ You want GitHub issue automation                                  │\n│   ✓ You want parallel development without conflicts                   │\n│   ✓ You want custom AI workflows for your team                        │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## Next Steps\n\n1. **Read**: [Quickstart](/getting-started/quick-start/) - Set up your first instance\n2. **Explore**: `.archon/workflows/` - See example workflows\n3. **Customize**: `.archon/commands/` - Create your own prompts\n4. **Configure**: `.archon/config.yaml` - Tweak settings\n\nWelcome to remote agentic coding!\n","frontmatter":{"title":"New Developer Guide","description":"Codebase orientation for new Archon developers — architecture overview, workflows, platforms, and first steps.","category":"contributing","audience":["developer"],"status":"current","sidebar":{"order":1}},"isInternal":false,"tokens":6537,"sizeBytes":50989},{"name":"releasing.md","path":"packages/docs-web/src/content/docs/contributing/releasing.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/contributing/releasing.md","title":"Contributing Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Releasing\ndescription: How to create a new release of the Archon CLI — version management, release process, and troubleshooting.\ncategory: contributing\narea: infra\naudience: [developer]\nstatus: current\nsidebar:\n  order: 3\n---\n\nThis guide covers how to create a new release of the Archon CLI.\n\n## Version Management\n\nVersions follow [Semantic Versioning](https://semver.org/):\n- **Major** (1.0.0): Breaking changes to CLI interface or workflow format\n- **Minor** (0.1.0): New features, new workflows, new commands\n- **Patch** (0.0.1): Bug fixes, documentation updates\n\nVersion is stored in the root `package.json` only -- this is the single source of truth.\n\n## Release Process\n\nReleases are created by merging `dev` into `main`. Never commit directly to `main`.\n\n### 1. Prepare the Release\n\nUse the `/release` skill (or follow these manual steps):\n\n```bash\n# Ensure dev is up to date\ngit checkout dev\ngit pull origin dev\n\n# Run default local validation\nbun run validate\nbun run validate --only format,workflow-fixtures\n```\n\nCI remains the full validation gate, including service and binary checks that local validation does not run.\n\nThe `/release` skill automates the following:\n1. Compares `dev` to `main` to generate changelog entries\n2. Bumps the version in root `package.json` (patch by default; use `/release minor` or `/release major` for other increments)\n3. Updates `CHANGELOG.md` following Keep a Changelog format\n4. Creates a PR from `dev` to `main`\n\n### 2. Merge and Tag\n\nOnce the release PR is reviewed and merged:\n\n```bash\n# Create and push the tag from main\ngit checkout main\ngit pull origin main\ngit tag vX.Y.Z\ngit push origin vX.Y.Z\n```\n\nThis triggers the GitHub Actions release workflow which:\n1. Builds binaries for all platforms (macOS arm64/x64, Linux arm64/x64, Windows x64)\n2. Generates checksums\n3. Creates a GitHub Release with all artifacts\n\n### 3. Update Homebrew Formula (Optional)\n\nAfter the release workflow completes:\n\n```bash\n# Update checksums in the Homebrew formula\n./scripts/update-homebrew.sh vX.Y.Z\n\n# Review and commit\ngit diff homebrew/archon.rb\ngit add homebrew/archon.rb\ngit commit -m \"chore: update Homebrew formula for vX.Y.Z\"\ngit push origin main\n```\n\nIf you maintain a Homebrew tap (`homebrew-archon`), copy the updated formula there.\n\n### 4. Verify the Release\n\n```bash\n# Test the install script (only works if repo is public)\ncurl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash\n\n# Verify version\narchon version\n```\n\n> **Note: Private Repository Installation**\n>\n> If the repository is private, the curl install script won't work for anonymous users.\n> Use the GitHub CLI instead:\n>\n> ```bash\n> # Download and install using gh (requires GitHub authentication)\n> gh release download v0.2.0 --repo coleam00/Archon \\\n>   --pattern \"archon-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/')\" \\\n>   --dir /tmp/archon-install\n>\n> # Install the binary\n> chmod +x /tmp/archon-install/archon-*\n> sudo mv /tmp/archon-install/archon-* /usr/local/bin/archon\n>\n> # Verify\n> archon version\n> ```\n\n## Manual Release (When GitHub Actions Unavailable)\n\nIf GitHub Actions can't run (billing issues, private repo limits), create the release manually:\n\n```bash\n# 1. Build binaries locally (every target in the script's local TARGETS list).\n#    TARGET or OUTFILE left set in the shell switches the script to single-target mode.\nunset TARGET OUTFILE\n./scripts/build-binaries.sh\n\n# 2. Generate checksums.txt, which the install scripts verify against\n./scripts/checksums.sh\n\n# 3. Create the release with binaries and checksums\ngh release create vX.Y.Z dist/binaries/* \\\n  --title \"Archon CLI vX.Y.Z\" \\\n  --generate-notes\n\n# 4. Verify the release\ngh release view vX.Y.Z\n```\n\n> **Note:** With `TARGET` and `OUTFILE` unset, `scripts/build-binaries.sh` cross-compiles\n> the CLI and server executables for every target in its local `TARGETS` list into\n> `dist/binaries/`. That list has no Windows target.\n>\n> A manual release is therefore incomplete. It lacks the assets only the CI release\n> builds: the Windows binaries, the plugin executables (`archon plugin add`\n> downloads these), and `archon-web.tar.gz` (`archon serve` downloads it). The `files:`\n> list of the release step in `.github/workflows/release.yml` is the full set CI publishes.\n\n## Manual Build (for Testing)\n\nTo build binaries locally without creating a release:\n\n```bash\n# Build every target in the script's local TARGETS list (no Windows)\nunset TARGET OUTFILE\n./scripts/build-binaries.sh\n\n# Binaries are in dist/binaries/\nls -la dist/binaries/\n\n# Generate checksums\n./scripts/checksums.sh\n```\n\n## Release Workflow Details\n\nThe `.github/workflows/release.yml` workflow:\n\n1. **Triggers on**:\n   - Push of tags matching `v*`\n   - Manual workflow dispatch with version input\n\n2. **Build job** (runs in parallel for each platform):\n   - Sets up Bun\n   - Installs dependencies\n   - Compiles binary with `bun build --compile`\n   - Uploads as artifact\n\n3. **Release job** (runs after all builds complete):\n   - Downloads all artifacts\n   - Generates SHA256 checksums\n   - Creates GitHub Release with:\n     - All binaries attached\n     - checksums.txt\n     - Auto-generated release notes\n     - Installation instructions\n\n## Troubleshooting\n\n### Build Fails on GitHub Actions\n\nCheck the Actions tab for specific errors. Common issues:\n- Dependency installation failure: Check `bun.lock` is committed\n- Type errors: Run `bun run type-check` locally first\n\n### Install Script Fails\n\nThe install script requires:\n- `curl` for downloading\n- `sha256sum` or `shasum` for verification\n- Write access to `/usr/local/bin` (or custom `INSTALL_DIR`)\n\n### Checksums Don't Match\n\nIf users report checksum failures:\n1. Check the release artifacts are complete\n2. Verify checksums.txt was generated correctly\n3. Ensure binaries weren't modified after checksum generation\n\n## Pre-release Versions\n\nFor testing releases before public announcement:\n\n```bash\n# Create a pre-release tag\ngit tag v0.3.0-beta.1\ngit push origin v0.3.0-beta.1\n```\n\nPre-releases (tags containing `-`) are marked as such on GitHub.\n\n## Hotfix Process\n\nFor urgent fixes to a released version:\n\n```bash\n# Create hotfix branch from tag\ngit checkout -b hotfix/0.2.1 v0.2.0\n\n# Make fixes, then tag\ngit tag v0.2.1\ngit push origin v0.2.1\n\n# Merge fixes back to dev\ngit checkout dev\ngit merge hotfix/0.2.1\ngit push origin dev\n```\n","frontmatter":{"title":"Releasing","description":"How to create a new release of the Archon CLI — version management, release process, and troubleshooting.","category":"contributing","area":"infra","audience":["developer"],"status":"current","sidebar":{"order":3}},"isInternal":false,"tokens":1644,"sizeBytes":6455},{"name":"cloud.md","path":"packages/docs-web/src/content/docs/deployment/cloud.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/deployment/cloud.md","title":"Deployment Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Cloud Deployment\ndescription: Prepare a cloud VPS for Archon, then run it with Docker Compose and automatic HTTPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 3\n---\n\nThis page prepares a VPS: a user, SSH, a firewall and DNS. The [Docker guide](/deployment/docker/) then installs and runs Archon on it with Docker Compose and Caddy for HTTPS. Using your own proxy instead of Caddy? See [Running Archon behind your own reverse proxy](/deployment/reverse-proxy/).\n\nFor the fastest route, use the [cloud-init script](/deployment/docker/#cloud-init-fastest-setup) instead. It creates a user, opens the firewall and installs Docker when the VPS first boots. You still create the [DNS record](#point-your-domain-at-the-server).\n\n## What you need\n\n- A VPS from any provider (DigitalOcean, Linode, AWS EC2, Vultr and so on): 1 to 2 vCPUs, 2 GB RAM minimum (4 GB recommended), 20 GB SSD, Ubuntu 22.04 LTS.\n- A domain or subdomain, such as `archon.yourdomain.com`.\n- An SSH key. Create one before the VPS, and add the public key when you create it:\n\n  ```bash\n  ssh-keygen -t ed25519 -C \"archon\"\n  cat ~/.ssh/id_ed25519.pub\n  ```\n\nWhen you create the VPS, choose the Ubuntu 22.04 image and your SSH key, and allow inbound SSH (22), HTTP (80) and HTTPS (443) if the provider has its own firewall or security group. Note the public IP.\n\n## Secure the server\n\nConnect with the image's default account: `root` on most VPS providers, `ubuntu` on AWS EC2, which disables root login. Create a deploy user with your key, and switch to it:\n\n```bash\nssh root@your-server-ip            # or: ssh ubuntu@your-server-ip on EC2\n\nsudo adduser deploy\nsudo usermod -aG sudo deploy\nsudo mkdir -p /home/deploy/.ssh\nsudo cp ~/.ssh/authorized_keys /home/deploy/.ssh/\nsudo chown -R deploy:deploy /home/deploy/.ssh\nsudo chmod 700 /home/deploy/.ssh\nsudo chmod 600 /home/deploy/.ssh/authorized_keys\n```\n\nTest `ssh deploy@your-server-ip` in a second terminal before you go on. Then turn off password logins by setting `PasswordAuthentication no` in `/etc/ssh/sshd_config`, and restart SSH:\n\n```bash\nsudo systemctl restart ssh\nsudo su - deploy\n```\n\nOpen the firewall:\n\n```bash\nsudo ufw allow 22/tcp\nsudo ufw allow 80/tcp\nsudo ufw allow 443\nsudo ufw --force enable\n```\n\n## Point your domain at the server\n\nAt your DNS provider, create an A record: name `archon` (or `@` for the root domain), value the server's public IP. With Cloudflare, set the record to DNS only, not proxied, so Caddy can obtain its certificate. Check it with `dig archon.yourdomain.com`.\n\n## Install and run Archon\n\nFollow the Docker guide's [manual server setup](/deployment/docker/#manual-server-setup). It installs Docker, clones the repository, sets up `.env` and the `Caddyfile`, and starts the stack. Before you expose the Web UI, choose a login in [Protect the Web UI](/deployment/docker/#protect-the-web-ui).\n\nCheck the deployment from your machine:\n\n```bash\ncurl https://archon.yourdomain.com/api/health\n```\n","frontmatter":{"title":"Cloud Deployment","description":"Prepare a cloud VPS for Archon, then run it with Docker Compose and automatic HTTPS.","category":"deployment","area":"infra","audience":["operator"],"status":"current","sidebar":{"order":3}},"isInternal":false,"tokens":794,"sizeBytes":3005},{"name":"docker.md","path":"packages/docs-web/src/content/docs/deployment/docker.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/deployment/docker.md","title":"Deployment Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Docker Guide\ndescription: Run Archon with Docker Compose, locally or on a server with PostgreSQL and automatic HTTPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 2\n---\n\nThis page installs and runs Archon with Docker Compose: on your own machine, or on a server with PostgreSQL and Caddy for automatic HTTPS. To prepare a VPS first (a user, SSH, the firewall and DNS), see [Cloud Deployment](/deployment/cloud/).\n\nThe image includes the Claude Code binary from the Claude Agent SDK. On start, the entrypoint points `CLAUDE_BIN_PATH` at it unless you set `CLAUDE_BIN_PATH` yourself.\n\n## Cloud-init (fastest setup)\n\n`deploy/cloud-init.yml` prepares a new VPS when it first boots. Paste its contents into the provider's User Data field when you create the server (Ubuntu 22.04+ or Debian 12+), add your SSH key, and point your domain's A record at the server's IP.\n\n| Provider | Where to paste it |\n|---|---|\n| DigitalOcean | Create Droplet, Advanced Options, User Data |\n| AWS EC2 | Launch Instance, Advanced Details, User Data |\n| Linode | Create Linode, Add Tags, Metadata (User Data) |\n| Hetzner | Create Server, Cloud config, User Data |\n| Vultr | Deploy, Additional Features, Cloud-Init User-Data |\n\nThe script installs Docker, opens ports 22, 80 and 443 (TCP, plus UDP 443 for HTTP/3), adds a 2 GB swapfile, clones the repository to `/opt/archon`, copies `.env.example` to `.env` and `Caddyfile.example` to `Caddyfile`, and builds the image. It also creates an `archon` user with Docker access but no sudo, and copies your SSH key to it. Use the default cloud user or root for system maintenance.\n\nSetup takes 5 to 8 minutes and writes `/opt/archon/SETUP_COMPLETE` when it finishes. Then:\n\n```bash\nssh archon@your-server-ip\nnano /opt/archon/.env          # see \"Configure .env\" below\ncd /opt/archon\ndocker compose --profile with-db --profile cloud up -d\n```\n\n## Local Docker Desktop (Windows and macOS)\n\nRun Archon on your own machine with SQLite and the Web UI. No domain or VPS is needed.\n\n```bash\ngit clone https://github.com/coleam00/Archon.git\ncd Archon\ncp .env.example .env           # set a Claude or Codex credential\ndocker compose up -d\n```\n\nOpen `http://localhost:3000`. Add `--profile with-db` to use the PostgreSQL container instead of SQLite (see [Profiles](#profiles)).\n\nOn Windows, build from a WSL terminal (for example in `/mnt/c/Users/YourName/path/to/Archon`), not PowerShell. Docker Desktop cannot follow Bun workspace symlinks from a Windows build context and fails with `The file cannot be accessed by the system`. If the container fails with `exec docker-entrypoint.sh: no such file or directory`, the checkout has CRLF line endings. `.gitattributes` forces LF for shell scripts, so re-clone the repository.\n\n## Manual server setup\n\nUse this instead of cloud-init when you want each step under your control. Prepare the server with [Cloud Deployment](/deployment/cloud/) first.\n\n1. Install Docker, then log out and back in so the group change applies:\n\n   ```bash\n   curl -fsSL https://get.docker.com | sh\n   sudo usermod -aG docker $USER\n   ```\n\n2. Clone the repository and create the config files:\n\n   ```bash\n   git clone https://github.com/coleam00/Archon.git\n   cd Archon\n   cp .env.example .env\n   cp Caddyfile.example Caddyfile\n   ```\n\n3. Edit `.env` as described in [Configure .env](#configure-env).\n\n4. Start the app, PostgreSQL and Caddy:\n\n   ```bash\n   docker compose --profile with-db --profile cloud up -d\n   ```\n\n5. Check it:\n\n   ```bash\n   docker compose --profile with-db --profile cloud ps\n   docker compose logs -f app\n   curl https://archon.example.com/api/health    # from your machine\n   ```\n\n## Pre-built image\n\nTo run `ghcr.io/coleam00/archon:latest` without building from source:\n\n```bash\nmkdir archon && cd archon\ncurl -O https://raw.githubusercontent.com/coleam00/Archon/main/deploy/docker-compose.yml\ncurl -O https://raw.githubusercontent.com/coleam00/Archon/main/.env.example\ncp .env.example .env           # see \"Configure .env\" below\ndocker compose up -d\n```\n\nThis compose file has only the app service. To add PostgreSQL, uncomment the `postgres` service and the `postgres_data` volume, and set `DATABASE_URL`. It has no Caddy service.\n\n### Verify image attestations\n\nPublished images carry BuildKit provenance and SPDX SBOM attestations. Resolve the tag to a digest, then inspect that digest so the image cannot change between inspection and deployment:\n\n```bash\ndocker buildx imagetools inspect ghcr.io/coleam00/archon:latest\n# Copy the top-level Digest, not a platform manifest's digest.\nimage='ghcr.io/coleam00/archon@sha256:<digest>'\ndocker buildx imagetools inspect \"$image\" --format '{{ json .Provenance }}'\ndocker buildx imagetools inspect \"$image\" --format '{{ json .SBOM }}'\n```\n\nBoth results must be non-empty: each platform has a `SLSA` provenance object and an `SPDX` SBOM object. In the provenance, check three things:\n\n- The source is `https://github.com/coleam00/Archon` and the revision is the release commit you intend to run. Current releases record them in `buildDefinition.externalParameters.request.args` as `label:org.opencontainers.image.source` and `label:org.opencontainers.image.revision`. Older BuildKit versions use `invocation.configSource` and `invocation.environment`.\n- The workflow ref in `buildDefinition.internalParameters.github_workflow_ref` names `.github/workflows/publish.yml` at the expected ref.\n- The builder ID (`builder.id` or `runDetails.builder.id`) links to the GitHub Actions run.\n\nThe package inventory is under `SPDX.packages`. The `unknown/unknown` entries in the manifest list are the attestation manifests. The attestations are unsigned metadata, so they do not authenticate the publisher by themselves. See [Docker's provenance documentation](https://docs.docker.com/build/metadata/attestations/slsa-provenance/).\n\n## Configure .env\n\nDocker Compose reads the repository's `.env`. Edit it directly. Don't run `archon setup` for a Docker install: the wizard writes Archon's CLI env files, not this one. `.env.example` documents every variable.\n\n### AI credentials\n\nSet at least one provider:\n\n```ini\n# Claude: an OAuth token from `claude setup-token` on your own machine\nCLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxxxx\n# or an API key from console.anthropic.com\n# CLAUDE_API_KEY=sk-ant-xxxxx\n\n# Codex: values from ~/.codex/auth.json after `codex login`\n# ARCHON_CODEX_ID_TOKEN=eyJhbGc...\n# ARCHON_CODEX_ACCESS_TOKEN=eyJhbGc...\n# ARCHON_CODEX_REFRESH_TOKEN=rt_...\n# ARCHON_CODEX_ACCOUNT_ID=6a6a7ba6-...\n```\n\n`.env.example` sets `CLAUDE_USE_GLOBAL_AUTH=true`. That flag only satisfies the server's startup check: it tells Archon to rely on a `claude /login` session, and a fresh container has none. Set a token or key. When one is set, Archon uses it whatever the flag says. See [AI Assistants](/getting-started/ai-assistants/) for every provider.\n\n### Server, domain and database\n\n| Variable | Default in Docker | Purpose |\n|---|---|---|\n| `PORT` | `3000` | App port, published on the host. Local development defaults to 3090. |\n| `DOMAIN` | none | Public hostname. Required for `--profile cloud`. |\n| `DATABASE_URL` | unset (SQLite) | PostgreSQL connection string. |\n| `POSTGRES_PORT` | `5432` | Host port for the PostgreSQL container, bound to `127.0.0.1`. |\n| `LOG_LEVEL` | `info` | `fatal`, `error`, `warn`, `info`, `debug` or `trace`. |\n\nFor the PostgreSQL container, use the service name as the host:\n\n```ini\nDATABASE_URL=postgresql://postgres:postgres@postgres:5432/remote_coding_agent\n```\n\nPlatform tokens (`TELEGRAM_BOT_TOKEN`, `SLACK_BOT_TOKEN`, `GH_TOKEN`, `WEBHOOK_SECRET` and so on) are covered in each adapter's page under [Adapters](/adapters/). For a GitHub webhook, the payload URL is `https://archon.example.com/webhooks/github`; see [GitHub](/adapters/github/).\n\n## Profiles\n\nCompose profiles add PostgreSQL, HTTPS or form login to the base app service:\n\n| Command | What runs |\n|---|---|\n| `docker compose up -d` | App with SQLite |\n| `docker compose --profile with-db up -d` | App and PostgreSQL 17 |\n| `docker compose --profile cloud up -d` | App and Caddy |\n| `docker compose --profile with-db --profile cloud up -d` | App, PostgreSQL and Caddy |\n\nAdd `--profile auth` for the [form login sidecar](#form-login-sidecar). For an external database such as Supabase or Neon, set `DATABASE_URL` and use no database profile. There is no `external-db` profile.\n\n- **No profile:** SQLite, stored in the data volume at `/.archon/archon.db`.\n- **`with-db`:** a PostgreSQL 17 container. The app creates and upgrades the schema on every start. If you start this profile without setting `DATABASE_URL`, the app logs a warning and uses SQLite, and the container sits unused.\n- **`cloud`:** [Caddy](https://caddyserver.com/) in front of the app, with Let's Encrypt certificates, HTTP to HTTPS redirects, HTTP/3 and unbuffered `/api/stream/*` for live updates. It needs a `Caddyfile` (copied from `Caddyfile.example`), `DOMAIN` set, the DNS A record in place, and ports 80 and 443 open. Caddy waits for the app's health check before it starts.\n\n## Protect the Web UI\n\nA Web UI on a public domain needs a login. Choose one of these:\n\n- **Better Auth** (PostgreSQL only, recommended): per-user accounts, and the server rejects every `/api/*` request without a session. Set `BETTER_AUTH_SECRET`; see [Web UI login](/reference/configuration/#web-ui-login-better-auth-optional). Signup is closed by default: invite people with `ARCHON_AUTH_ALLOWED_EMAILS`, or open it with `ARCHON_AUTH_OPEN_SIGNUP=true`. With Better Auth on, you don't need either Caddy option below; the [Security reference](/reference/security/#adapter-authorization) explains how to retire them.\n- **Caddy basic auth:** the browser's own login prompt, with no extra container.\n- **Form login sidecar:** a styled login page and a session cookie, served by the `auth-service` container.\n\nUse one Caddy option at a time, not both. Caddy always leaves `/webhooks/*` and `/api/health` open, and webhooks verify their own signatures. Caddy answers `/internal/*` with a 404 and never forwards it.\n\n### Caddy basic auth\n\n1. Generate a bcrypt hash. Never put a plaintext password in `.env`.\n\n   ```bash\n   docker run --rm caddy caddy hash-password --plaintext 'YOUR_PASSWORD'\n   ```\n\n2. Set `CADDY_BASIC_AUTH` in `.env`. Replace `admin` with your username and paste the complete hash unchanged:\n\n   ```ini\n   CADDY_BASIC_AUTH='basic_auth @protected {\n     admin REPLACE_WITH_COMPLETE_HASH_OUTPUT\n   }'\n   ```\n\n   This is a single-quoted multiline Compose value, so newlines and `$` characters survive as written. Don't double the dollar signs, and don't source the file in a shell. See [Compose interpolation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/) and [Caddy's basic_auth](https://caddyserver.com/docs/caddyfile/directives/basic_auth).\n\n3. Recreate Caddy so it reads the new value:\n\n   ```bash\n   docker compose --profile cloud up -d --force-recreate caddy\n   ```\n\nTo turn it off, leave `CADDY_BASIC_AUTH` empty or unset.\n\n### Form login sidecar\n\n1. Generate a bcrypt hash. The first run builds the `auth-service` image.\n\n   ```bash\n   docker compose --profile auth run --rm auth-service \\\n     node -e \"require('bcryptjs').hash('YOUR_PASSWORD', 12).then(h => console.log(h))\"\n   ```\n\n2. Generate a cookie signing secret:\n\n   ```bash\n   docker run --rm node:22-alpine \\\n     node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\n   ```\n\n3. Set them in `.env`, writing every `$` in the hash as `$$` so Compose does not interpolate it:\n\n   ```ini\n   AUTH_USERNAME=admin\n   AUTH_PASSWORD_HASH=$$2b$$12$$REPLACE_WITH_YOUR_HASH\n   COOKIE_SECRET=REPLACE_WITH_64_HEX_CHARS\n   # COOKIE_MAX_AGE=86400   # session length in seconds\n   ```\n\n4. In `Caddyfile`, uncomment the Option A block (`handle /login`, `handle /logout` and the `forward_auth` handle), and comment out the \"No auth\" `handle` block below it.\n\n5. Start with the `auth` profile:\n\n   ```bash\n   docker compose --profile with-db --profile cloud --profile auth up -d\n   ```\n\nYour domain now redirects to `/login`. Visit `/logout` to end the session.\n\n## Data and home volumes\n\nThe container keeps Archon's data in `/.archon`: workspaces, worktrees, artifacts, logs, plugins and the SQLite database. The user's home, `/home/appuser`, holds provider and tool state:\n\n| Path | What it holds |\n|---|---|\n| `~/.claude/` | Claude Code skills, commands, agents, hooks, MCP config, sessions, memory and OAuth state |\n| `~/.codex/` | Codex `auth.json` from `codex login`. With the `ARCHON_CODEX_*` variables set, startup overwrites it each time. |\n| `~/.pi/agent/` | Pi `auth.json` from `pi /login`, `models.json`, `settings.json` and sessions |\n| `~/.gitconfig` | Git identity, signing config, aliases and `safe.directory` entries |\n| `~/.config/gh/` | GitHub CLI login from `gh auth login` |\n| `~/.bash_history` | Shell history from `docker compose exec app bash` |\n\nBoth are Docker named volumes by default (`archon_data` and `archon_user_home`), so they survive restarts and rebuilds. To keep them at a host path instead, set `ARCHON_DATA` or `ARCHON_USER_HOME` in `.env`, and make the directory writable by UID 1001, the container user:\n\n```bash\nsudo mkdir -p /opt/archon-data /opt/archon-user-home\nsudo chown -R 1001:1001 /opt/archon-data /opt/archon-user-home\n```\n\n```ini\nARCHON_DATA=/opt/archon-data\nARCHON_USER_HOME=/opt/archon-user-home\n```\n\nOn every start the entrypoint fixes ownership of both paths, touching only files with the wrong owner.\n\n- `ARCHON_HOME` has no effect inside the container, which always uses `/.archon`. Use `ARCHON_DATA` to choose where that lives on the host.\n- A bind-mounted home starts with an empty `~/.gitconfig`, because Docker copies image content into new named volumes but never into bind mounts. The entrypoint still adds the `safe.directory` entries Archon needs. Set your Git identity with `git config --global` inside the container.\n- `safe.directory` entries added at runtime stay in the home volume, even after the checkout is gone. Archon never removes them, because concurrent setup may rely on the same entry. See [Git directory trust](/reference/security/#git-directory-trust).\n- To keep Pi's data on the data volume, set `PI_CODING_AGENT_DIR=/.archon/pi` before the container starts.\n- To wipe the home volume: `docker compose down && docker volume rm archon_archon_user_home`.\n\n### Root fallback for macOS bind mounts\n\nOn a macOS bind mount (VirtioFS), the ownership fix always fails: the host will not remap files to UID 1001. The entrypoint prints the `chown` error and exits 1, so the container restarts in a loop. Read-only mounts and SELinux or AppArmor denials on Linux fail the same way.\n\n`ARCHON_ALLOW_ROOT_FALLBACK=1` in `.env` makes the entrypoint print a warning and keep running as root instead. Any other value, or none, keeps the exit. The variable does nothing when the ownership fix succeeds.\n\n:::caution\nRunning as root also sets `IS_SANDBOX=1`, which disables the Claude provider's refusal to run `bypassPermissions` as root, so AI subprocesses run as root in the container. That is acceptable on a single-operator Mac where the bind mount already limits what the container can reach. On Linux the failure means the volume ownership is wrong: run `sudo chown -R 1001:1001 <path>` on the host instead.\n:::\n\n## GitHub credentials\n\nThe container uses `GH_TOKEN` from `.env`. You can also log in inside the container, and the login lands on the home volume:\n\n```bash\ndocker compose exec -u appuser -e HOME=/home/appuser app gh auth login\n```\n\n- With `GH_TOKEN` set and GitHub App mode off, every start resets Git's `https://github.com` credential helper to one that uses `GH_TOKEN`. A helper written by `gh auth login` applies only without `GH_TOKEN`.\n- In [GitHub App mode](/adapters/github-app-setup/) (`GITHUB_APP_ID`, a private key and `WEBHOOK_SECRET` set, `GITHUB_TOKEN` unset), startup removes only the PAT helper that the entrypoint wrote earlier. An incomplete App configuration leaves it in place. Other helpers are yours and stay.\n- The entrypoint reads these variables from the container environment, not from `/.archon/.env`. Keep `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY` or `GITHUB_APP_PRIVATE_KEY_PATH`, `WEBHOOK_SECRET`, `GITHUB_TOKEN` and `GH_TOKEN` in the compose `.env`. If they are only in `/.archon/.env`, the server runs in App mode while clones keep using the old PAT.\n- With `ARCHON_SDLC_FORGE=gh`, delete an empty `GH_TOKEN=` line. The SDLC workflows refuse an empty `GH_TOKEN` when `GITHUB_TOKEN` is unset, rather than let `gh` fall back to the stored login.\n\n## Plugins\n\nInstall plugins inside the running container, as `appuser` so the server owns the files:\n\n```bash\ndocker compose exec -u appuser app bun run cli plugin add coleam00/Archon/plugins/forge-github\ndocker compose exec -u appuser app bun run cli plugin add Wirasm/archon-video\n```\n\n`plugin update`, `plugin remove` and `plugin list` work the same way. Plugins live in `/.archon/plugins` on the data volume, so they survive restarts and rebuilds. See [Forge operations](/reference/forge/#install-the-github-plugin) and [Installed workflow packs](/guides/global-workflows/#installed-workflow-packs).\n\nThe bundled SDLC workflows use `gh` by default. To use the forge plugin instead, set `ARCHON_SDLC_FORGE=forge` and install forge-github v0.12.1 or later; some pack steps still need `gh`. The plugin looks for `GH_TOKEN`, then `GITHUB_TOKEN`, then the container's `gh` login. Your host's `gh` login is not visible inside the container. See [Use the forge path in the SDLC pack](/reference/forge/#use-the-forge-path-in-the-sdlc-pack).\n\nThe image includes a verified Slack plugin outside the data volume. When `SLACK_BOT_TOKEN` is set, in the container environment or in `/.archon/.env`, the entrypoint installs it offline into `/.archon/plugins` before the server starts, and logs the release and commit. You don't run `plugin add` for Slack. Slack still needs both `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` to connect; see [Slack](/adapters/slack/).\n\n- An installed, verified Slack plugin stays as it is, including a release you pinned from another source. The image does not update it.\n- Missing or changed Slack executables, ownership conflicts, or a broken image receipt when a new install is needed stop provisioning with an error in the boot logs. Repair the install before restarting.\n- To turn Slack off, remove its tokens from every env source, run `docker compose exec -u appuser app bun run cli plugin remove coleam00/Archon/plugins/chat-slack`, and restart. Removing only the plugin reinstalls it on the next start. Removing only the tokens leaves it installed and reporting missing credentials.\n\nThe image has `git` and Bun, but not `uv`, Python, or tools a pack's scripts call, such as ffmpeg. Add them with [Dockerfile.user](#customizing-the-image).\n\n## Container isolation is unavailable\n\n`archon workflow run ... --container` starts a sibling Docker container for each run. Inside this compose stack it fails at preflight with \"Cannot connect to the Docker daemon\": the image has no `docker` CLI, and the stack does not mount `/var/run/docker.sock`. Worktree isolation and in-place folder runs work normally.\n\n:::caution\nMounting the Docker socket into the app container is not part of the supported stack. The socket is root-equivalent: anything that can reach it can start a privileged container and take over the host. If you accept that on a single-operator host, read `packages/isolation/docker/SECURITY.md` first. To use `--container` without that risk, run Archon directly on a host with a local Docker daemon.\n:::\n\n## Building the image\n\n```bash\ndocker build -t archon .\ndocker run --env-file .env -e ARCHON_DOCKER=true -p 3000:3000 archon\n```\n\nThe Dockerfile builds in these stages:\n\n1. `manifests` and `sources` derive the workspace layout from `packages/`, so a new package needs no Dockerfile edit.\n2. `deps` installs all dependencies, including the dev dependencies the web build needs.\n3. `image-plugins` downloads the pinned Slack release, checks its commit and checksum, and records a verified receipt.\n4. `web-build` builds the Web UI with Vite.\n5. `production` keeps production dependencies and the built Web UI.\n\nThe production image runs Bun as the non-root `appuser` (UID 1001), which Claude Code requires. It includes `git`, `curl`, `gh`, `postgresql-client`, `ripgrep`, `jq`, Chromium and [agent-browser](https://github.com/vercel-labs/agent-browser), which drives the system Chromium for browser testing. It ships the source of every workspace package except `docs-web`, `web` (only the built UI ships) and `chat-slack` (shipped as the verified release under `/opt/archon/image-plugins`).\n\nThe Slack release is pinned separately from Archon's version by the build args `SLACK_PLUGIN_TAG` (default `v0.12.1`) and `SLACK_PLUGIN_COMMIT` (default `5f2b4841aaca38180c0abbd32bc6345593292019`). Override both together; the build rejects a tag that resolves to a different commit. Downloads happen only at build time.\n\n### Customizing the image\n\nTo add tools without editing the tracked Dockerfile:\n\n1. Copy `Dockerfile.user.example` to `Dockerfile.user` (use `deploy/Dockerfile.user.example` for a server deploy) and edit it.\n2. Copy `docker-compose.override.example.yml` to `docker-compose.override.yml` (or `deploy/docker-compose.override.example.yml`).\n3. Run `docker compose up -d`. Compose merges the override file automatically.\n\nBoth files are gitignored, so your changes stay local.\n\n## Maintenance\n\n```bash\ndocker compose logs -f app            # follow app logs\ndocker compose logs --tail=100 app    # last 100 lines\ndocker compose restart app\ndocker compose down                   # stop, keep data\ndocker compose down -v                # stop and delete volumes, including data\ndocker system df                      # Docker disk usage\n```\n\n### Update\n\n```bash\ngit pull\ndocker compose --profile with-db --profile cloud up -d --build\n```\n\nUse the same profiles you deployed with. The app applies schema changes on startup: it runs the idempotent `migrations/000_combined.sql` under a PostgreSQL advisory lock, so there is no manual migration step.\n\n## Troubleshooting\n\n### App exits with `no_ai_credentials`\n\nThe server found no Claude credential and no Codex credential. Set `CLAUDE_CODE_OAUTH_TOKEN`, `CLAUDE_API_KEY`, or `ARCHON_CODEX_ID_TOKEN` with `ARCHON_CODEX_ACCESS_TOKEN`. See [AI credentials](#ai-credentials).\n\n### Caddy fails with \"not a directory\"\n\n```\nerror mounting \"Caddyfile\": not a directory\n```\n\n`Caddyfile` did not exist, so Docker created a directory in its place:\n\n```bash\nrm -rf Caddyfile\ncp Caddyfile.example Caddyfile\ndocker compose --profile cloud up -d\n```\n\n### Caddy gets no certificate\n\nCheck that `dig archon.example.com` returns the server's IP, that `sudo ufw status` shows 80 and 443 open, and that `DOMAIN` in `.env` is spelled right. Then read `docker compose logs caddy`. DNS changes can take up to an hour to propagate.\n\n### Health check fails\n\nThe container health check calls `/api/health` on `PORT`, not `/health`:\n\n```bash\ncurl http://localhost:3000/api/health\n```\n\n### PostgreSQL connection refused\n\nWith `--profile with-db`, `DATABASE_URL` must use the service name `postgres` as the host, not `localhost`. Check the container with `docker compose ps postgres`.\n\n### Permission errors in `/.archon`\n\nThe entrypoint exits 1 with the `chown` error when it cannot give `/.archon` or `/home/appuser` to UID 1001. On Linux with a bind mount, run `sudo chown -R 1001:1001 /path/to/archon-data` on the host. On a macOS bind mount, `chown` on the host does not help; see [Root fallback for macOS bind mounts](#root-fallback-for-macos-bind-mounts).\n\n### Port already in use\n\nSet another `PORT` in `.env`, such as `PORT=3001`.\n\n### Container keeps restarting\n\nRun `docker compose ps` and `docker compose logs --tail=50 app`. The usual causes are a missing `.env`, an invalid credential or an unreachable database.\n","frontmatter":{"title":"Docker Guide","description":"Run Archon with Docker Compose, locally or on a server with PostgreSQL and automatic HTTPS.","category":"deployment","area":"infra","audience":["operator"],"status":"current","sidebar":{"order":2}},"isInternal":false,"tokens":6038,"sizeBytes":24085},{"name":"e2e-testing-wsl.md","path":"packages/docs-web/src/content/docs/deployment/e2e-testing-wsl.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/deployment/e2e-testing-wsl.md","title":"Deployment Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: E2E Testing on WSL\ndescription: Run agent-browser inside WSL for end-to-end testing when dev servers run on Windows.\ncategory: deployment\narea: infra\naudience: [developer]\nstatus: current\nsidebar:\n  order: 6\n---\n\n`agent-browser` (Vercel) has a [known Windows bug](https://github.com/vercel-labs/agent-browser/issues/56) where the daemon fails to start due to Unix domain socket incompatibility. The workaround is to run agent-browser inside WSL while the dev servers run on Windows.\n\n> **General setup:** For non-WSL platforms (macOS, Linux, Docker), see the [E2E Testing Guide](/deployment/e2e-testing/) instead.\n\n## Prerequisites\n\n- WSL2 with Ubuntu installed (`wsl --list --verbose`)\n- agent-browser installed in WSL: `npm install -g agent-browser`\n- Playwright chromium installed: `agent-browser install --with-deps` (needs sudo)\n\n## Setup\n\n### 1. Find the Windows host IP accessible from WSL\n\n```bash\nipconfig | findstr \"IPv4\" | findstr \"WSL\"\n# Example output: IPv4 Address. . . . . . . . . . . : 172.18.64.1\n```\n\nOr from inside WSL:\n```bash\nwsl -d Ubuntu -- bash -c \"cat /etc/resolv.conf | grep nameserver\"\n```\n\nThe Windows host IP for this system is `172.18.64.1`.\n\n### 2. Start dev servers on Windows (bound to all interfaces)\n\n```bash\n# Backend (Hono on port 3090) - already binds to 0.0.0.0 by default\nbun run dev:server &\n\n# Frontend (Vite on port 5173) - needs --host flag\ncd packages/web && bun x vite --host 0.0.0.0 &\n```\n\n### 3. Verify WSL can reach the servers\n\n```bash\nwsl -d Ubuntu -- curl -s http://172.18.64.1:3090/api/health\nwsl -d Ubuntu -- curl -s -o /dev/null -w \"%{http_code}\" http://172.18.64.1:5173\n```\n\n## Running agent-browser Commands\n\nAll commands are run from the Windows terminal, prefixed with `wsl -d Ubuntu --`:\n\n```bash\n# Open a page\nwsl -d Ubuntu -- agent-browser open http://172.18.64.1:5173\n\n# Take interactive snapshot (get element refs like @e1, @e2)\nwsl -d Ubuntu -- agent-browser snapshot -i\n\n# Click, fill, press\nwsl -d Ubuntu -- agent-browser click @e1\nwsl -d Ubuntu -- agent-browser fill @e2 \"some text\"\nwsl -d Ubuntu -- agent-browser press Enter\n\n# Wait for content to load\nwsl -d Ubuntu -- agent-browser wait 3000\n\n# Reload page (hard refresh)\nwsl -d Ubuntu -- agent-browser reload\n\n# Close browser\nwsl -d Ubuntu -- agent-browser close\n```\n\n## Taking Screenshots\n\nScreenshots must be saved to a WSL-native path first, then copied to the Windows filesystem via the `/mnt/c/` mount:\n\n```bash\n# Save to WSL home, then copy to project\nwsl -d Ubuntu -- bash -c '\n  agent-browser screenshot /home/user/screenshot.png 2>&1 &&\n  cp /home/user/screenshot.png /path/to/archon/e2e-screenshots/my-test.png\n'\n```\n\n**Why not save directly to `/mnt/c/...`?** agent-browser resolves paths through its Node.js process, which on some setups mangles `/mnt/c/` paths (e.g., prepending `C:/Program Files/Git/`). Saving to a WSL-native path and copying avoids this.\n\n## Gotchas\n\n- **`localhost` doesn't work from WSL2** - must use the Windows host IP (`172.18.64.1`)\n- **Vite must bind to `0.0.0.0`** - default `localhost` isn't reachable from WSL\n- **Git Bash path expansion** - `/status` gets expanded to `C:/Program Files/Git/status` when passed through Git Bash. Not an agent-browser issue; it's the shell expanding `/` paths\n- **SSE `Connected` indicator** - only shows for `web` platform conversations; Telegram/Slack conversations show `Disconnected` (expected)\n- **Daemon startup** - if `agent-browser open` fails with \"Daemon failed to start\", kill stale daemons: `wsl -d Ubuntu -- pkill -f daemon.js` and retry\n","frontmatter":{"title":"E2E Testing on WSL","description":"Run agent-browser inside WSL for end-to-end testing when dev servers run on Windows.","category":"deployment","area":"infra","audience":["developer"],"status":"current","sidebar":{"order":6}},"isInternal":false,"tokens":1021,"sizeBytes":3559},{"name":"e2e-testing.md","path":"packages/docs-web/src/content/docs/deployment/e2e-testing.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/deployment/e2e-testing.md","title":"Deployment Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: E2E Testing\ndescription: Set up agent-browser for end-to-end browser testing in Archon workflows.\ncategory: deployment\narea: infra\naudience: [developer, operator]\nstatus: current\nsidebar:\n  order: 5\n---\n\nArchon uses [agent-browser](https://github.com/vercel-labs/agent-browser) (by Vercel Labs) for end-to-end browser testing in workflows. It is an **optional** external dependency — core Archon functionality works without it.\n\n## Installation\n\n```bash\n# Install globally\nnpm install -g agent-browser\n\n# Download browser engine (Chrome for Testing)\nagent-browser install\n```\n\n## Verify Installation\n\n```bash\nagent-browser --version\n# Expected: prints version number (e.g., 0.x.x)\n\n# Quick smoke test — opens a page and closes\nagent-browser open https://example.com\nagent-browser close\n```\n\n## Where It's Used\n\nNo bundled workflow requires agent-browser. Your own workflows can call it from a `bash:` node or from an AI node's instructions, for example to reproduce a UI bug in a real browser or to check a change against a running dev server.\n\n## Platform-Specific Notes\n\n### Docker\n\nagent-browser is **pre-installed** in the Archon Docker image. No action needed.\n\n### macOS / Linux\n\nWorks natively after running the install commands above. If the daemon fails to start:\n\n```bash\n# Kill stale daemons and retry\npkill -f daemon.js\nagent-browser open http://localhost:3090\n```\n\n### Windows\n\nagent-browser has a [known bug](https://github.com/vercel-labs/agent-browser/issues/56) where the daemon fails to start due to Unix domain socket incompatibility on Windows.\n\n**Workaround:** Run agent-browser inside WSL while dev servers run on Windows. See the [E2E Testing on WSL](/deployment/e2e-testing-wsl/) guide for detailed setup instructions.\n\n## Running Without agent-browser\n\nIf agent-browser is not installed, the E2E workflow nodes will fail when the agent tries to invoke `agent-browser`. The AI agent is instructed (via prompt) to stop after 2 failed connection attempts and produce a code-review-only report — but this is a prompt-level instruction, not automated workflow logic. Results may vary depending on the AI model's adherence to the instruction.\n\nYou can safely run all non-E2E workflows without agent-browser installed.\n","frontmatter":{"title":"E2E Testing","description":"Set up agent-browser for end-to-end browser testing in Archon workflows.","category":"deployment","area":"infra","audience":["developer","operator"],"status":"current","sidebar":{"order":5}},"isInternal":false,"tokens":511,"sizeBytes":2254},{"name":"index.md","path":"packages/docs-web/src/content/docs/deployment/index.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/deployment/index.md","title":"Deployment Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Deployment Overview\ndescription: Overview of deployment options for running Archon locally, with Docker, or on a cloud VPS.\ncategory: deployment\narea: infra\naudience: [operator]\nstatus: current\nsidebar:\n  order: 0\n---\n\nArchon can run locally for development or be deployed to a server for always-on operation.\n\n## Deployment Options\n\n| Method | Best For | Guide |\n|--------|----------|-------|\n| **Local** | Development, personal use | [Local Development](/deployment/local/) |\n| **Docker** | Self-hosted servers, CI environments | [Docker](/deployment/docker/) |\n| **Cloud VPS** | 24/7 operation with automatic HTTPS | [Cloud Deployment](/deployment/cloud/) |\n| **Windows** | Native Windows or WSL2 | [Windows](/deployment/windows/) |\n\nUsing an existing proxy? See [Running Archon behind your own reverse proxy](/deployment/reverse-proxy/) for routing, authentication, webhook, and streaming requirements.\n\n## Database Options\n\n| Option | Setup | Best For |\n|--------|-------|----------|\n| **SQLite** (default) | Zero config, just omit `DATABASE_URL` | Single-user, CLI usage, local development |\n| **Remote PostgreSQL** | Set `DATABASE_URL` to hosted DB | Cloud deployments, shared access |\n| **Local PostgreSQL** | Docker `--profile with-db` | Self-hosted, Docker-based setups |\n\nSQLite stores data at `~/.archon/archon.db` (or `/.archon/archon.db` in Docker). It is auto-initialized on first run.\n\n## Testing\n\n| Guide | Audience |\n|-------|----------|\n| [E2E Testing](/deployment/e2e-testing/) | Developers and operators |\n| [E2E Testing on WSL](/deployment/e2e-testing-wsl/) | Developers on Windows |\n","frontmatter":{"title":"Deployment Overview","description":"Overview of deployment options for running Archon locally, with Docker, or on a cloud VPS.","category":"deployment","area":"infra","audience":["operator"],"status":"current","sidebar":{"order":0}},"isInternal":false,"tokens":391,"sizeBytes":1617},{"name":"local.md","path":"packages/docs-web/src/content/docs/deployment/local.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/deployment/local.md","title":"Deployment Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Flocal.md -->","frontmatter":{"title":"Local Development","description":"Run the Archon server and Web UI on your own machine, with SQLite or PostgreSQL.","category":"deployment","area":"infra","audience":["operator"],"status":"current","sidebar":{"order":1}},"isInternal":false,"tokens":516,"sizeBytes":2059},{"name":"reverse-proxy.md","path":"packages/docs-web/src/content/docs/deployment/reverse-proxy.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/deployment/reverse-proxy.md","title":"Deployment Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Freverse-proxy.md -->","frontmatter":{"title":"Running Archon behind your own reverse proxy","description":"The routing, authentication, webhook, and streaming contract for an Archon reverse proxy.","category":"deployment","area":"infra","audience":["operator"],"status":"current","sidebar":{"order":4}},"isInternal":false,"tokens":1907,"sizeBytes":8661},{"name":"windows.md","path":"packages/docs-web/src/content/docs/deployment/windows.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/deployment/windows.md","title":"Deployment Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fdeployment%2Fwindows.md -->","frontmatter":{"title":"Windows Setup","description":"Run Archon on Windows natively with Bun or with full WSL2 compatibility.","category":"deployment","area":"infra","audience":["operator"],"status":"current","sidebar":{"order":4}},"isInternal":false,"tokens":954,"sizeBytes":3779},{"name":"ai-assistants.md","path":"packages/docs-web/src/content/docs/getting-started/ai-assistants.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/ai-assistants.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fai-assistants.md -->","frontmatter":{"title":"AI Assistants","description":"Install, authenticate and configure Claude Code, Codex, Pi, OpenCode and GitHub Copilot for Archon.","category":"getting-started","area":"clients","audience":["user"],"status":"current","sidebar":{"order":4}},"isInternal":false,"tokens":7573,"sizeBytes":31443},{"name":"configuration.md","path":"packages/docs-web/src/content/docs/getting-started/configuration.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/configuration.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fconfiguration.md -->","frontmatter":{"title":"Configuration","description":"Configure Archon with API keys, assistants, and project settings.","category":"getting-started","area":"config","audience":["user","operator"],"sidebar":{"order":3}},"isInternal":false,"tokens":951,"sizeBytes":3625},{"name":"first-workflow.md","path":"packages/docs-web/src/content/docs/getting-started/first-workflow.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/first-workflow.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Ffirst-workflow.md -->","frontmatter":{"title":"Build your first workflow","description":"Build an agent-first workflow that plans a change, waits for your approval, implements it and fixes failing tests, and check every route before it spends a token.","category":"getting-started","audience":["user"],"sidebar":{"order":3}},"isInternal":false,"tokens":3505,"sizeBytes":14530},{"name":"installation.md","path":"packages/docs-web/src/content/docs/getting-started/installation.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/installation.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Finstallation.md -->","frontmatter":{"title":"Installation","description":"Install Archon on macOS, Linux, or Windows.","category":"getting-started","audience":["user","operator"],"sidebar":{"order":0}},"isInternal":false,"tokens":1492,"sizeBytes":5989},{"name":"operate.md","path":"packages/docs-web/src/content/docs/getting-started/operate.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/operate.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Foperate.md -->","frontmatter":{"title":"Operate runs","description":"Watch runs, answer the ones that need you, resume or stop them, read what they produced, and hand classes of questions to your own handler workflows.","category":"getting-started","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":5}},"isInternal":false,"tokens":4986,"sizeBytes":19997},{"name":"quick-start.md","path":"packages/docs-web/src/content/docs/getting-started/quick-start.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/quick-start.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fquick-start.md -->","frontmatter":{"title":"Quickstart","description":"Install the Archon binary, add a provider and run your first workflow.","category":"getting-started","audience":["user"],"sidebar":{"order":2}},"isInternal":false,"tokens":1138,"sizeBytes":4677},{"name":"sdlc-factory.md","path":"packages/docs-web/src/content/docs/getting-started/sdlc-factory.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/sdlc-factory.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fsdlc-factory.md -->","frontmatter":{"title":"Set up the SDLC factory","description":"Take a GitHub repository from nothing to an issue built by the build queue and merged by the merge queue, and know everything the pack writes.","category":"getting-started","audience":["user"],"sidebar":{"order":4}},"isInternal":false,"tokens":3308,"sizeBytes":13624},{"name":"updating.md","path":"packages/docs-web/src/content/docs/getting-started/updating.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/updating.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fupdating.md -->","frontmatter":{"title":"Updating Archon","description":"Update an existing Archon installation.","category":"getting-started","audience":["user","operator"],"sidebar":{"order":1}},"isInternal":false,"tokens":686,"sizeBytes":3043},{"name":"why-archon.md","path":"packages/docs-web/src/content/docs/getting-started/why-archon.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/getting-started/why-archon.md","title":"Getting-started Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fgetting-started%2Fwhy-archon.md -->","frontmatter":{"title":"Why Archon","description":"Archon is a factory builder. You write how work gets done as YAML workflows, and Archon runs them with agents, scripts and your decisions.","category":"getting-started","audience":["user"],"sidebar":{"order":1}},"isInternal":false,"tokens":1210,"sizeBytes":5118},{"name":"approval-nodes.md","path":"packages/docs-web/src/content/docs/guides/approval-nodes.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/approval-nodes.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fapproval-nodes.md -->","frontmatter":{"title":"Asking for an Answer","description":"Pause a workflow until it gets an answer with wait attention forms, and migrate legacy approval gates and interactive loops.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":4}},"isInternal":false,"tokens":2968,"sizeBytes":12132},{"name":"authoring-commands.md","path":"packages/docs-web/src/content/docs/guides/authoring-commands.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/authoring-commands.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fauthoring-commands.md -->","frontmatter":{"title":"Authoring Commands","description":"Write prompt templates that serve as building blocks for AI workflow nodes.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":2}},"isInternal":false,"tokens":3771,"sizeBytes":15974},{"name":"container-isolation.md","path":"packages/docs-web/src/content/docs/guides/container-isolation.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/container-isolation.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fcontainer-isolation.md -->","frontmatter":{"title":"Container Isolation","description":"Run folder-project workflows inside an overlay-isolated Docker container with governed, approval-gated write-back to the live root.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":12}},"isInternal":false,"tokens":2383,"sizeBytes":10902},{"name":"global-workflows.md","path":"packages/docs-web/src/content/docs/guides/global-workflows.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/global-workflows.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fglobal-workflows.md -->","frontmatter":{"title":"Global Workflows, Commands, and Scripts","description":"Define user-level workflows, commands, and scripts that apply to every project on your machine.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":9}},"isInternal":false,"tokens":2708,"sizeBytes":11380},{"name":"hooks.md","path":"packages/docs-web/src/content/docs/guides/hooks.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/hooks.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fhooks.md -->","frontmatter":{"title":"Per-Node Hooks","description":"Attach Claude Agent SDK hooks to individual workflow nodes for tool control, context injection, and input modification.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":6}},"isInternal":false,"tokens":3677,"sizeBytes":15245},{"name":"index.md","path":"packages/docs-web/src/content/docs/guides/index.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/index.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Findex.md -->","frontmatter":{"title":"Guides","description":"How-to guides for authoring workflows, commands, and configuring node features in Archon.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":0}},"isInternal":false,"tokens":618,"sizeBytes":2649},{"name":"loop-nodes.md","path":"packages/docs-web/src/content/docs/guides/loop-nodes.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/loop-nodes.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Floop-nodes.md -->","frontmatter":{"title":"Loop Nodes","description":"Configure iterative AI execution nodes that repeat until a completion condition is met.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":3}},"isInternal":false,"tokens":10885,"sizeBytes":47367},{"name":"mcp-servers.md","path":"packages/docs-web/src/content/docs/guides/mcp-servers.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/mcp-servers.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fmcp-servers.md -->","frontmatter":{"title":"Per-Node MCP Servers","description":"Attach MCP (Model Context Protocol) servers to individual workflow nodes for external tool access.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":7}},"isInternal":false,"tokens":4799,"sizeBytes":19460},{"name":"multi-repo-projects.md","path":"packages/docs-web/src/content/docs/guides/multi-repo-projects.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/multi-repo-projects.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fmulti-repo-projects.md -->","frontmatter":{"title":"Multi-Repo Projects","description":"Register a multi-repo root as a folder project and drive workflows and chat across every contained service repo.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":11}},"isInternal":false,"tokens":918,"sizeBytes":4180},{"name":"proactive-workflows.md","path":"packages/docs-web/src/content/docs/guides/proactive-workflows.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/proactive-workflows.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fproactive-workflows.md -->","frontmatter":{"title":"Proactive workflows","description":"Start agent work from an event, let a cheap agent decide whether it matters, and pause for an answer before anything leaves the run.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":10}},"isInternal":false,"tokens":1948,"sizeBytes":8153},{"name":"publishing-plugins.md","path":"packages/docs-web/src/content/docs/guides/publishing-plugins.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/publishing-plugins.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fpublishing-plugins.md -->","frontmatter":{"title":"Publish a plugin","description":"Publish Archon workflow packs, forge plugins, provider plugins, and chat plugins on GitHub and list them on archon.diy."},"isInternal":false,"tokens":3513,"sizeBytes":16047},{"name":"script-nodes.md","path":"packages/docs-web/src/content/docs/guides/script-nodes.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/script-nodes.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fscript-nodes.md -->","frontmatter":{"title":"Script Nodes","description":"Run TypeScript, JavaScript, or Python code as a DAG node without invoking an AI agent.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":5}},"isInternal":false,"tokens":5445,"sizeBytes":22845},{"name":"skills.md","path":"packages/docs-web/src/content/docs/guides/skills.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/skills.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fskills.md -->","frontmatter":{"title":"Per-Node Skills","description":"Select specialized knowledge for individual workflow nodes using provider-native skill systems.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":8}},"isInternal":false,"tokens":2973,"sizeBytes":12694},{"name":"workflow-triggers.md","path":"packages/docs-web/src/content/docs/guides/workflow-triggers.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/workflow-triggers.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fworkflow-triggers.md -->","frontmatter":{"title":"Start workflows from schedules and signed webhooks","description":"Configure governed workflow starts from native schedules, GitHub events, and Standard Webhooks.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":10}},"isInternal":false,"tokens":9252,"sizeBytes":43695},{"name":"worktrees.md","path":"packages/docs-web/src/content/docs/guides/worktrees.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/guides/worktrees.md","title":"Guides Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Fguides%2Fworktrees.md -->","frontmatter":{"title":"Run work in parallel with worktrees","description":"How Archon gives each repository run its own git worktree, and how to inspect, provision and clean up worktrees.","category":"guides","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":2}},"isInternal":false,"tokens":1172,"sizeBytes":5247},{"name":"api.md","path":"packages/docs-web/src/content/docs/reference/api.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/api.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fapi.md -->","frontmatter":{"title":"API Reference","description":"REST API endpoints for programmatic access to Archon.","category":"reference","area":"server","audience":["developer"],"sidebar":{"order":6}},"isInternal":false,"tokens":10214,"sizeBytes":42522},{"name":"architecture.md","path":"packages/docs-web/src/content/docs/reference/architecture.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/architecture.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Farchitecture.md -->","frontmatter":{"title":"Architecture","description":"How Archon's code is organized, and where the maintained architecture map lives.","category":"reference","audience":["developer"],"status":"current","sidebar":{"order":1}},"isInternal":false,"tokens":631,"sizeBytes":2786},{"name":"archon-directories.md","path":"packages/docs-web/src/content/docs/reference/archon-directories.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/archon-directories.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Farchon-directories.md -->","frontmatter":{"title":"Archon Directories","description":"Directory structure, path resolution, and configuration system for Archon.","category":"reference","area":"config","audience":["developer"],"status":"current","sidebar":{"order":2}},"isInternal":false,"tokens":3678,"sizeBytes":15551},{"name":"cli.md","path":"packages/docs-web/src/content/docs/reference/cli.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/cli.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fcli.md -->","frontmatter":{"title":"CLI Reference","description":"Every Archon command, what it does beyond its --help text, and the output and exit codes scripts can rely on.","category":"reference","area":"cli","audience":["user"],"status":"current","sidebar":{"order":3}},"isInternal":false,"tokens":11098,"sizeBytes":50040},{"name":"commands.md","path":"packages/docs-web/src/content/docs/reference/commands.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/commands.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fcommands.md -->","frontmatter":{"title":"Commands Reference","description":"Archon orchestrator commands and how to submit them, including through a bot mention in Slack threads.","category":"reference","area":"handlers","audience":["user"],"status":"current","sidebar":{"order":4}},"isInternal":false,"tokens":1506,"sizeBytes":6728},{"name":"configuration.md","path":"packages/docs-web/src/content/docs/reference/configuration.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/configuration.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fconfiguration.md -->","frontmatter":{"title":"Configuration Reference","description":"Full reference for Archon's layered configuration system including YAML config, environment variables, and streaming modes.","category":"reference","area":"config","audience":["user","operator"],"status":"current","sidebar":{"order":6}},"isInternal":false,"tokens":11475,"sizeBytes":50208},{"name":"database.md","path":"packages/docs-web/src/content/docs/reference/database.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/database.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fdatabase.md -->","frontmatter":{"title":"Database","description":"Persistence selection and database setup for files, SQLite, and PostgreSQL.","category":"reference","area":"database","audience":["developer","operator"],"status":"current","sidebar":{"order":5}},"isInternal":false,"tokens":5997,"sizeBytes":27444},{"name":"forge.md","path":"packages/docs-web/src/content/docs/reference/forge.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/forge.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fforge.md -->","frontmatter":{"title":"Forge operations","description":"Resolve repositories, read qualified pull-request checks, and perform verified pull-request writes through optional executable plugins.","category":"reference","area":"cli","audience":["user"],"status":"current"},"isInternal":false,"tokens":8843,"sizeBytes":40814},{"name":"index.md","path":"packages/docs-web/src/content/docs/reference/index.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/index.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Findex.md -->","frontmatter":{"title":"Reference","description":"Technical reference for Archon's architecture, CLI, commands, database, configuration, and troubleshooting.","category":"reference","audience":["developer"],"sidebar":{"order":0}},"isInternal":false,"tokens":474,"sizeBytes":2055},{"name":"node-execution.md","path":"packages/docs-web/src/content/docs/reference/node-execution.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/node-execution.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fnode-execution.md -->","frontmatter":{"title":"Node execution records","description":"Execution facts, measured usage, cache decisions, and durable resume projections.","category":"reference","area":"workflows","audience":["developer"],"status":"current"},"isInternal":false,"tokens":8720,"sizeBytes":50080},{"name":"provider-capabilities.md","path":"packages/docs-web/src/content/docs/reference/provider-capabilities.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/provider-capabilities.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fprovider-capabilities.md -->","frontmatter":{"title":"Provider Capability Matrix","description":"Canonical per-provider capability matrix, generated from each provider capabilities.ts.","category":"reference","area":"clients","audience":["user","developer"],"status":"current","sidebar":{"order":10}},"isInternal":false,"tokens":1644,"sizeBytes":6438},{"name":"sdlc-workflows.md","path":"packages/docs-web/src/content/docs/reference/sdlc-workflows.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/sdlc-workflows.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fsdlc-workflows.md -->","frontmatter":{"title":"SDLC pack workflows","description":"Every workflow in the bundled sdlc pack, what it produces and when to use it.","category":"reference","audience":["user"],"sidebar":{"order":4}},"isInternal":false,"tokens":5407,"sizeBytes":23403},{"name":"security.md","path":"packages/docs-web/src/content/docs/reference/security.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/security.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fsecurity.md -->","frontmatter":{"title":"Security","description":"Security model, permissions, authorization, and data privacy in Archon.","category":"reference","audience":["user","operator"],"sidebar":{"order":8}},"isInternal":false,"tokens":5185,"sizeBytes":23318},{"name":"troubleshooting.md","path":"packages/docs-web/src/content/docs/reference/troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/troubleshooting.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Ftroubleshooting.md -->","frontmatter":{"title":"Troubleshooting","description":"Common issues and solutions when running Archon locally or in Docker.","category":"reference","audience":["user","operator"],"status":"current","sidebar":{"order":7}},"isInternal":false,"tokens":4539,"sizeBytes":19964},{"name":"variables.md","path":"packages/docs-web/src/content/docs/reference/variables.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/variables.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fvariables.md -->","frontmatter":{"title":"Variable Reference","description":"Complete reference for all variable substitutions available in Archon commands and workflows.","category":"reference","area":"workflows","audience":["user"],"sidebar":{"order":5}},"isInternal":false,"tokens":7850,"sizeBytes":33811},{"name":"workflow-language.md","path":"packages/docs-web/src/content/docs/reference/workflow-language.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/src/content/docs/reference/workflow-language.md","title":"Reference Skill","category":"anthropic-skill","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fsrc%2Fcontent%2Fdocs%2Freference%2Fworkflow-language.md -->","frontmatter":{"title":"Workflow language","description":"Every workflow field, node type and engine behavior an Archon workflow YAML file can declare.","category":"reference","area":"workflows","audience":["user"],"status":"current","sidebar":{"order":4}},"isInternal":false,"tokens":11712,"sizeBytes":50113},{"name":"code-reviewer.md","path":".claude/agents/code-reviewer.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/code-reviewer.md","title":"code-reviewer","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcode-reviewer.md -->","frontmatter":{"name":"code-reviewer","description":"Reviews code for project guideline compliance, bugs, and quality issues. Use after writing code, before commits, or before PRs. Specify files to review or defaults to unstaged git changes. High-confidence issues only (80+) to minimize noise.","model":"sonnet"},"isInternal":false,"tokens":1101,"sizeBytes":4773},{"name":"code-simplifier.md","path":".claude/agents/code-simplifier.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/code-simplifier.md","title":"code-simplifier","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcode-simplifier.md -->","frontmatter":{"name":"code-simplifier","description":"Identifies code simplification opportunities for clarity and maintainability while preserving exact functionality. Use after writing or modifying code. Focuses on recently changed code unless told otherwise. Reports findings with before/after suggestions. Advisory only - does not modify files.","model":"sonnet"},"isInternal":false,"tokens":899,"sizeBytes":4064},{"name":"codebase-analyst.md","path":".claude/agents/codebase-analyst.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/codebase-analyst.md","title":"codebase-analyst","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcodebase-analyst.md -->","frontmatter":{"name":"codebase-analyst","description":"Use proactively to understand HOW code works. Analyzes implementation details, traces data flow, and documents technical workings with precise file:line references. The more specific your request, the better the analysis.","model":"sonnet"},"isInternal":false,"tokens":835,"sizeBytes":3788},{"name":"codebase-explorer.md","path":".claude/agents/codebase-explorer.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/codebase-explorer.md","title":"codebase-explorer","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcodebase-explorer.md -->","frontmatter":{"name":"codebase-explorer","description":"Comprehensive codebase exploration - finds WHERE code lives AND shows HOW it's implemented. Use when you need to locate files, understand directory structure, AND extract actual code patterns. Combines file finding with pattern extraction in one pass.","model":"sonnet"},"isInternal":false,"tokens":1081,"sizeBytes":4785},{"name":"comment-analyzer.md","path":".claude/agents/comment-analyzer.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/comment-analyzer.md","title":"comment-analyzer","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fcomment-analyzer.md -->","frontmatter":{"name":"comment-analyzer","description":"Analyzes code comments for accuracy, completeness, and long-term value. Verifies comments match actual code behavior. Use after generating documentation, before PRs with comment changes, or when auditing for comment rot. Advisory only.","model":"sonnet"},"isInternal":false,"tokens":817,"sizeBytes":3672},{"name":"docs-impact.md","path":".claude/agents/docs-impact.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/docs-impact.md","title":"docs-impact","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fdocs-impact.md -->","frontmatter":{"name":"docs-impact","description":"Reviews documentation affected by code changes. Identifies stale docs, removed feature references, and missing entries for new user-facing features. Reports findings with specific fixes. Advisory only - does not modify files.","model":"sonnet"},"isInternal":false,"tokens":803,"sizeBytes":3616},{"name":"pr-test-analyzer.md","path":".claude/agents/pr-test-analyzer.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/pr-test-analyzer.md","title":"pr-test-analyzer","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fpr-test-analyzer.md -->","frontmatter":{"name":"pr-test-analyzer","description":"Analyzes PR test coverage for quality and completeness. Focuses on behavioral coverage, not line metrics. Identifies critical gaps, evaluates test quality, and rates recommendations by criticality (1-10). Use after PR creation or before marking ready.","model":"sonnet"},"isInternal":false,"tokens":990,"sizeBytes":4249},{"name":"rulecheck-agent.md","path":".claude/agents/rulecheck-agent.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/rulecheck-agent.md","title":"rulecheck-agent","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Frulecheck-agent.md -->","frontmatter":{"name":"rulecheck-agent","description":"Autonomous code quality agent that scans for rule violations, fixes them\nin an isolated worktree, runs validation, creates a PR, and updates memory\nwith findings for future runs.\n","isolation":"worktree","memory":"project","permissionMode":"acceptEdits","maxTurns":500,"model":"sonnet","hooks":{"PostToolUse":[{"matcher":"Edit|Write","hooks":[{"type":"command","command":"bun run lint:fix --quiet 2>/dev/null || true","statusMessage":"Auto-fixing lint issues..."}]}],"PreToolUse":[{"matcher":"Bash","hooks":[{"type":"command","command":".claude/skills/rulecheck/hooks/block-dangerous.sh","statusMessage":"Checking command safety..."}]}],"Stop":[{"hooks":[{"type":"command","command":".claude/skills/rulecheck/hooks/slack-notify.sh","statusMessage":"Notifying Slack..."},{"type":"agent","prompt":"You are a meta-judge evaluating the rulecheck agent's execution.\n\nThe agent just finished a run. Here is its final message:\n\n$ARGUMENTS\n\n## Your Task\n\n1. **Review the work**: What violations did the agent find and fix?\n2. **Evaluate prioritization**: Were the right items addressed first?\n3. **Check quality**: Were fixes correct and non-breaking?\n4. **Check PR format**: Did it follow the project's PR template?\n5. **Assess validation**: Did the agent test, lint and type-check what it changed, and do those checks pass?\n\n## Write Feedback\n\nWrite structured feedback to `.claude/agent-memory/rulecheck-agent/meta-judge-feedback.md`:\n\n```markdown\n# Meta-Judge Feedback — ${CLAUDE_SESSION_ID}\n\n## Run Assessment\n- **Quality**: [1-5] — were fixes correct?\n- **Prioritization**: [1-5] — were the right things fixed?\n- **Completeness**: [1-5] — was validation thorough?\n\n## What Went Well\n- ...\n\n## Improvement Suggestions\n- ...\n\n## Recommendations for Next Run\n- ...\n```\n\nAlways return `{\"ok\": true}` — the agent should always be allowed to stop.\nThe value is in the written feedback, not in blocking.\n","statusMessage":"Running meta-judge evaluation..."}]}]}},"isInternal":false,"tokens":1849,"sizeBytes":7910},{"name":"sdk-verifier.md","path":".claude/agents/sdk-verifier.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/sdk-verifier.md","title":"agent-sdk-verifier-ts","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fsdk-verifier.md -->","frontmatter":{"name":"agent-sdk-verifier-ts","description":"Use this agent to verify that a TypeScript Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a TypeScript Agent SDK app has been created or modified.","model":"sonnet"},"isInternal":false,"tokens":1125,"sizeBytes":5467},{"name":"silent-failure-hunter.md","path":".claude/agents/silent-failure-hunter.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/silent-failure-hunter.md","title":"silent-failure-hunter","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fsilent-failure-hunter.md -->","frontmatter":{"name":"silent-failure-hunter","description":"Hunts for silent failures, inadequate error handling, and inappropriate fallbacks in code changes. Zero tolerance for swallowed errors. Use after implementing error handling, catch blocks, or fallback logic.","model":"sonnet"},"isInternal":false,"tokens":942,"sizeBytes":4252},{"name":"triage-agent.md","path":".claude/agents/triage-agent.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/triage-agent.md","title":"triage-agent","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Ftriage-agent.md -->","frontmatter":{"name":"triage-agent","description":"Specialized agent for triaging GitHub issues. Fetches issues, reads the codebase\nfor context, and applies type/effort/priority/area labels via gh CLI.\nUse when the triage skill delegates issue labeling work.\n","model":"sonnet","tools":"Bash, Read, Glob, Grep","hooks":{"PostToolUse":[{"matcher":"Bash","hooks":[{"type":"prompt","prompt":"A triage agent just executed a Bash command. Here is the tool call context:\n\n$ARGUMENTS\n\nIf this was a `gh issue edit --add-label` command, verify:\n1. Exactly one type label was applied (bug, feature, feature-request, docs, chore, question, security, performance, or breaking)\n2. Exactly one effort label (effort/low, effort/medium, or effort/high)\n3. Exactly one priority label (P0, P1, P2, or P3)\n4. At least one area label\n\nIf this was NOT a label command (e.g., gh issue list, gh label list, gh issue view),\nreturn {\"ok\": true} — no validation needed.\n\nReturn {\"ok\": true} if valid or not a label command.\nReturn {\"ok\": false, \"reason\": \"...\"} if a label command is missing required categories.\n","statusMessage":"Validating label application..."}]}]}},"isInternal":false,"tokens":741,"sizeBytes":3117},{"name":"type-design-analyzer.md","path":".claude/agents/type-design-analyzer.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/type-design-analyzer.md","title":"type-design-analyzer","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Ftype-design-analyzer.md -->","frontmatter":{"name":"type-design-analyzer","description":"Analyzes type design for encapsulation, invariant expression, and enforcement quality. Use when introducing new types, reviewing PRs with type changes, or refactoring existing types. Provides qualitative feedback and ratings (1-10) on four dimensions. Pragmatic focus.","model":"sonnet"},"isInternal":false,"tokens":1355,"sizeBytes":5506},{"name":"web-researcher.md","path":".claude/agents/web-researcher.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/agents/web-researcher.md","title":"web-researcher","category":"subagent-persona","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fagents%2Fweb-researcher.md -->","frontmatter":{"name":"web-researcher","description":"Need information beyond training data? Modern docs, recent APIs, or current best practices? Searches strategically, fetches relevant content, and synthesizes findings with proper citations. Re-run with refined prompts if initial results need more depth.","model":"sonnet"},"isInternal":false,"tokens":753,"sizeBytes":3604},{"name":"commit.md","path":".claude/commands/commit.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/commit.md","title":"Command: /commit","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fcommit.md -->","frontmatter":{"description":"Create an atomic commit for current changes"},"isInternal":false,"tokens":582,"sizeBytes":2513},{"name":"create-command.md","path":".claude/commands/create-command.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/create-command.md","title":"Command: /create-command","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fcreate-command.md -->","frontmatter":{"description":"Meta command creator - generates slash commands following established patterns","argument-hint":"<command-name> <purpose description>"},"isInternal":false,"tokens":2056,"sizeBytes":8349},{"name":"execute.md","path":".claude/commands/execute.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/execute.md","title":"Command: /execute","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fexecute.md -->","frontmatter":{"description":"Execute an Archon implementation plan file","argument-hint":"<path-to-plan.md>"},"isInternal":false,"tokens":1123,"sizeBytes":4646},{"name":"implement-fix.md","path":".claude/commands/github_bug_fix/implement-fix.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/github_bug_fix/implement-fix.md","title":"Command: /implement-fix","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fgithub_bug_fix%2Fimplement-fix.md -->","frontmatter":{"description":"Implement fix from RCA document for GitHub issue","argument-hint":"[github-issue-id]"},"isInternal":false,"tokens":869,"sizeBytes":3626},{"name":"rca.md","path":".claude/commands/github_bug_fix/rca.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/github_bug_fix/rca.md","title":"Command: /rca","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fgithub_bug_fix%2Frca.md -->","frontmatter":{"description":"Analyze and document root cause for a GitHub issue","argument-hint":"[github-issue-id]"},"isInternal":false,"tokens":949,"sizeBytes":4129},{"name":"handoff.md","path":".claude/commands/handoff.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/handoff.md","title":"Command: /handoff","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fhandoff.md -->","frontmatter":{"description":"Write a session handoff document for the next agent or session"},"isInternal":false,"tokens":1053,"sizeBytes":4509},{"name":"plan-feature.md","path":".claude/commands/plan-feature.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/plan-feature.md","title":"Command: /plan-feature","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fplan-feature.md -->","frontmatter":{"description":"Create a comprehensive implementation plan for an Archon feature","argument-hint":"<feature-name-or-description>"},"isInternal":false,"tokens":1287,"sizeBytes":5630},{"name":"prime-backend.md","path":".claude/commands/prime-backend.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/prime-backend.md","title":"Command: /prime-backend","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-backend.md -->","frontmatter":{"description":"Prime agent with Archon backend (core + server) context"},"isInternal":false,"tokens":807,"sizeBytes":3486},{"name":"prime-frontend.md","path":".claude/commands/prime-frontend.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/prime-frontend.md","title":"Command: /prime-frontend","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-frontend.md -->","frontmatter":{"description":"Prime agent with Archon frontend (React/Web UI) context"},"isInternal":false,"tokens":678,"sizeBytes":2806},{"name":"prime-isolation.md","path":".claude/commands/prime-isolation.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/prime-isolation.md","title":"Command: /prime-isolation","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-isolation.md -->","frontmatter":{"description":"Prime agent with Archon isolation system and git operations context"},"isInternal":false,"tokens":930,"sizeBytes":3909},{"name":"prime-workflows.md","path":".claude/commands/prime-workflows.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/prime-workflows.md","title":"Command: /prime-workflows","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime-workflows.md -->","frontmatter":{"description":"Prime agent with Archon workflow engine context"},"isInternal":false,"tokens":834,"sizeBytes":3495},{"name":"prime.md","path":".claude/commands/prime.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/prime.md","title":"Command: /prime","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fprime.md -->","frontmatter":{"description":"Prime agent with full Archon codebase understanding"},"isInternal":false,"tokens":532,"sizeBytes":2412},{"name":"review-doc.md","path":".claude/commands/review-doc.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/review-doc.md","title":"Command: /review-doc","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Freview-doc.md -->","isInternal":false,"tokens":376,"sizeBytes":1858},{"name":"validate.md","path":".claude/commands/validate.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/validate.md","title":"Command: /validate","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidate.md -->","frontmatter":{"description":"Run Archon's full validation suite with per-level reporting"},"isInternal":false,"tokens":916,"sizeBytes":3652},{"name":"code-review-fix.md","path":".claude/commands/validation/code-review-fix.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/validation/code-review-fix.md","title":"Command: /code-review-fix","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fcode-review-fix.md -->","frontmatter":{"description":"Fix bugs found in code review","argument-hint":"[code-review-file-or-description] [scope]"},"isInternal":false,"tokens":209,"sizeBytes":842},{"name":"code-review.md","path":".claude/commands/validation/code-review.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/validation/code-review.md","title":"Command: /code-review","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fcode-review.md -->","frontmatter":{"description":"Technical code review for quality, bugs, and AGENTS.md compliance"},"isInternal":false,"tokens":784,"sizeBytes":3359},{"name":"execution-report.md","path":".claude/commands/validation/execution-report.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/validation/execution-report.md","title":"Command: /execution-report","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fexecution-report.md -->","frontmatter":{"description":"Generate implementation report reflecting on completed work"},"isInternal":false,"tokens":467,"sizeBytes":2063},{"name":"system-review.md","path":".claude/commands/validation/system-review.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/validation/system-review.md","title":"Command: /system-review","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fsystem-review.md -->","frontmatter":{"description":"Analyze implementation against plan for process improvements","argument-hint":"[plan-file] [execution-report-file]"},"isInternal":false,"tokens":1178,"sizeBytes":5348},{"name":"validate-2.md","path":".claude/commands/validation/validate-2.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/.claude/commands/validation/validate-2.md","title":"Command: /validate-2","category":"command-prompt","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=.claude%2Fcommands%2Fvalidation%2Fvalidate-2.md -->","isInternal":false,"tokens":9765,"sizeBytes":36481},{"name":"README.md","path":"packages/adapters/src/community/chat/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/adapters/src/community/chat/README.md","title":"chat Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fadapters%2Fsrc%2Fcommunity%2Fchat%2FREADME.md -->","isInternal":false,"tokens":217,"sizeBytes":990},{"name":"README.md","path":"packages/adapters/src/community/forge/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/adapters/src/community/forge/README.md","title":"forge Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fadapters%2Fsrc%2Fcommunity%2Fforge%2FREADME.md -->","isInternal":false,"tokens":677,"sizeBytes":3154},{"name":"README.md","path":"packages/chat-contract/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/chat-contract/README.md","title":"chat-contract Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fchat-contract%2FREADME.md -->","isInternal":false,"tokens":1522,"sizeBytes":7141},{"name":"README.md","path":"packages/docs-web/public/brand/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/docs-web/public/brand/README.md","title":"brand Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fdocs-web%2Fpublic%2Fbrand%2FREADME.md -->","isInternal":false,"tokens":423,"sizeBytes":1699},{"name":"README.md","path":"packages/forge/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/forge/README.md","title":"forge Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fforge%2FREADME.md -->","isInternal":false,"tokens":615,"sizeBytes":3092},{"name":"README.md","path":"packages/provider-contract/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/provider-contract/README.md","title":"provider-contract Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fprovider-contract%2FREADME.md -->","isInternal":false,"tokens":2703,"sizeBytes":12876},{"name":"README.md","path":"packages/providers/src/codex/protocol/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/providers/src/codex/protocol/README.md","title":"protocol Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fproviders%2Fsrc%2Fcodex%2Fprotocol%2FREADME.md -->","isInternal":false,"tokens":74,"sizeBytes":259},{"name":"README.md","path":"packages/web/src/experiments/console/builder/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/web/src/experiments/console/builder/README.md","title":"builder Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fweb%2Fsrc%2Fexperiments%2Fconsole%2Fbuilder%2FREADME.md -->","isInternal":false,"tokens":573,"sizeBytes":2627},{"name":"README.md","path":"packages/web/src/experiments/console/README.md","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/packages/web/src/experiments/console/README.md","title":"console Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=packages%2Fweb%2Fsrc%2Fexperiments%2Fconsole%2FREADME.md -->","isInternal":false,"tokens":673,"sizeBytes":3273},{"name":"archon-plugin.json","path":"plugins/chat-slack/archon-plugin.json","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/plugins/chat-slack/archon-plugin.json","title":"chat-slack","category":"plugin-manifest","format":"json","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fchat-slack%2Farchon-plugin.json -->","frontmatter":{"schemaVersion":1,"kind":"chat","name":"chat-slack","description":"Slack conversations, workflow progress and run actions over Socket Mode.","executable":"archon-chat-slack"},"isInternal":false,"tokens":55,"sizeBytes":197},{"name":"archon-plugin.json","path":"plugins/forge-github/archon-plugin.json","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/plugins/forge-github/archon-plugin.json","title":"forge-github","category":"plugin-manifest","format":"json","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fforge-github%2Farchon-plugin.json -->","frontmatter":{"schemaVersion":1,"kind":"forge","name":"forge-github","description":"Resolves GitHub repositories and reads qualified pull-request checks.","executable":"archon-forge-github"},"isInternal":false,"tokens":54,"sizeBytes":199},{"name":"archon-plugin.json","path":"plugins/provider-claude/archon-plugin.json","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/plugins/provider-claude/archon-plugin.json","title":"provider-claude","category":"plugin-manifest","format":"json","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-claude%2Farchon-plugin.json -->","frontmatter":{"schemaVersion":1,"kind":"provider","name":"provider-claude","description":"Archon maintained Claude provider.","executable":"archon-provider-claude"},"isInternal":false,"tokens":50,"sizeBytes":173},{"name":"archon-plugin.json","path":"plugins/provider-codex/archon-plugin.json","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/plugins/provider-codex/archon-plugin.json","title":"provider-codex","category":"plugin-manifest","format":"json","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-codex%2Farchon-plugin.json -->","frontmatter":{"schemaVersion":1,"kind":"provider","name":"provider-codex","description":"Archon maintained Codex provider.","executable":"archon-provider-codex"},"isInternal":false,"tokens":51,"sizeBytes":170},{"name":"archon-plugin.json","path":"plugins/provider-pi/archon-plugin.json","rawUrl":"https://raw.githubusercontent.com/coleam00/Archon/HEAD/plugins/provider-pi/archon-plugin.json","title":"provider-pi","category":"plugin-manifest","format":"json","content":"<!-- Skill available on-demand via /api/skills/coleam00/Archon?path=plugins%2Fprovider-pi%2Farchon-plugin.json -->","frontmatter":{"schemaVersion":1,"kind":"provider","name":"provider-pi","description":"Archon maintained Pi provider.","executable":"archon-provider-pi"},"isInternal":false,"tokens":48,"sizeBytes":161}],"systemPromptSnippet":"<yakaai_skills repo=\"coleam00/Archon\">\n<!-- File: AGENTS.md (Tokens: ~3688 | Category: root-instruction) -->\n# Working on Archon\n\nArchon is a self-hostable, governed agentic automation engine. It runs workflows that mix deterministic steps, AI agents, human gates, and audit trails. Coding automation is its most mature surface, but the engine is intended for general business operations too.\n\nThis file is the canonical project guidance for coding agents. Keep it short, durable, and about judgment. Put changing product direction in [`.archon/direction.md`](.archon/direction.md), workflow-language design in [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md), aspirational engineering craft and review values in [`.archon/engineering.md`](.archon/engineering.md), user documentation in the docs site, and machine-checkable rules in code, types, tests, lint, or CI.\n\n## Read before changing code\n\n- Treat the source, its call sites, and its tests as the current evidence. The request defines the goal and constraints, but a prompt may describe old code.\n- Read every relevant consumer before changing a producer, contract, path, identifier, schema, or generated artifact.\n- Reproduce bugs when practical. If reproduction is impossible, establish another concrete causal chain before editing.\n- Do not trust a passing test by itself. Check that it exercises the behavior it claims to protect.\n- A question is read-only. Explain first; change code only when the user asks for a change.\n- Do not stop at editing. Finish the requested change and verify the outcome.\n- If the implementation keeps getting more complicated, stop and reconsider the model. Solve the underlying problem.\n\n## Product boundaries\n\n- Archon is a governed automation engine, not a replacement for the coding agents it orchestrates.\n- One install serves one operator or client. Client isolation belongs at the deployment layer. Multiple users within one install are supported and are not multi-tenancy.\n- Keep the conversation layer platform-agnostic. Platform adapters translate transport details; core behavior should not depend on Slack, Telegram, GitHub, Discord, CLI, or Web UI semantics.\n- Keep the core self-hostable. Do not make a proprietary hosted service a runtime dependency.\n- Keep the core small and flexible. Agentic engineering changes too quickly for one monolith to encode every useful workflow or integration; give users stable seams to assemble and extend the rest.\n- Treat bundled workflows as reference implementations, not a frozen compatibility surface.\n- A git worktree separates checkouts; it is not an execution or security sandbox.\n- Consult [`.archon/direction.md`](.archon/direction.md) before making product-scope or architecture-direction decisions.\n\n## Engineering taste\n\n### Prefer the smallest truthful system\n\n- Apply KISS and YAGNI. Do not add configuration, interfaces, lifecycle states, compatibility paths, or guards without a current requirement or concrete failure mode.\n- Before adding machinery, look for dead, redundant, or superseded machinery on the same path that can disappear.\n- Fix drift on the path you touch rather than preserving it for a cleaner diff. Two sites that should agree and do not are a defect, not a diff-size decision.\n- Duplicate small local logic when it keeps ownership clear. Extract only after a pattern has repeated and stabilized.\n- Prefer explicit control flow and narrow typed interfaces over meta-programming, shared mutable state, or hidden coupling.\n- Keep policy, transport, persistence, and execution concerns separate.\n- Consider a focused rewrite when it produces a smaller, clearer result without widening risk.\n\n### Let types carry invariants\n\n- Keep TypeScript strict. Avoid `any` and broad assertions when a sound type can express the state.\n- Two declarations that must agree to stay correct, kept in agreement by discipline rather than by a mechanism, are a present defect and not a future risk. Treat a hand-synced pair as a bug the moment it exists, and as a live one once the sides already disagree. Derive from the owner instead: an import, a derived or mapped type, a generated artifact with an owning script, or an enforced conformance test. A \"keep in sync\" comment is none of those; it records the defect rather than clearing it.\n- Import external SDK types rather than restating them. Where a boundary genuinely cannot share one definition, the copy owes a conformance test.\n- Use discriminated unions and constructors to make invalid states hard to represent.\n- Keep interfaces narrow. Extend an existing interface only when the new method belongs to the same responsibility.\n\n### Fail clearly\n\n- Fail early on unsupported, ambiguous, or unsafe states. Silent fallback in an agent runtime can waste money or broaden capabilities.\n- Never silently swallow an error or broaden permissions.\n- Translate errors at the boundary that can explain them, while preserving the original evidence for logs and diagnostics.\n- A fallback must be intentional, safe, documented near the branch, and observable enough to debug.\n- Never log credentials, secret values, user-message contents, or unnecessary personal data.\n\n### Write for the next maintainer\n\n- Comments should explain a non-obvious invariant, ownership decision, or reason a tempting alternative is wrong. Do not narrate the code.\n- Delete stale comments and documentation when behavior changes.\n- Use one precise name for each concept. Prefer direct prose, active voice, and concrete mechanisms over slogans or vague summaries.\n- Avoid hardcoded examples that will become false as files, counts, providers, or routes change.\n\n## Agentic-system boundaries\n\n### Natural language is not a wire format\n\n- Let the agent interpret the full natural-language message. Do not reconstruct intent with regexes, keyword lists, exact prose tokens, or hand-written parsers.\n- Put determinism after interpretation: give the agent typed tools, then validate resolved identifiers, arguments, permissions, and invariants at the tool boundary.\n- If exact syntax is required, expose a structured interface such as a CLI flag, typed tool, button action, or schema.\n- In a workflow, prose inside a node is model reasoning. Prose passed between nodes as a token to parse is an invented protocol; use structured output, typed inputs, exit status, or another explicit engine channel.\n- Vendor output is prose too. Classifying git, SDK, or vendor error text with a pattern is a last resort, not a carve-out: prefer the structured channel (exit code, typed error class, `--json` or porcelain output), then agent classification into a typed value, then an honest unclassified failure carrying the original evidence. A match that survives anchors to a machine token embedded in the message (an errno, an error code) and may enrich a report — never gate retry, fallback, or suppression, where a vendor rewording silently changes behavior.\n\n### Do not guess lifecycle ownership\n\n- A process must not mark non-terminal work failed, cancelled, or abandoned when it cannot distinguish a live owner from an orphaned process.\n- Surface ambiguous ownership and give the operator an explicit action.\n- Timers remain appropriate for recoverable operations such as retry backoff, subprocess timeouts, and cleanup of already-terminal data.\n- Terminal state transitions and completion signals belong to the engine, not to workflow-specific scripts that reinterpret run state.\n\n### Project guidance should be available, not sprayed everywhere\n\n- This `AGENTS.md` is the project-guidance channel for agent nodes. Provider integrations should preserve that contract through the provider's native context mechanism.\n- `AGENTS.md` may point to focused project context such as direction or language-design documents.\n- Do not inject every project document into every node. A node should receive only the context its responsibility requires.\n- The engine and reusable workflow packs must not hardcode a project's context paths.\n\n### Preserve provider configuration; scope capabilities\n\n- Provider integrations must preserve the provider's native user and project configuration, including its global and project guidance files. The user's provider configuration owns whether those sources load; Archon must not replace a provider home directory or add a parallel context switch.\n- Workflow AI nodes start without ambient skills, plugins, or MCP servers. A node receives only the skills, plugins, and MCP servers it explicitly names. Enforce that boundary without hiding unrelated provider settings.\n- Pi extensions are user-owned provider configuration, not Archon-managed plugins. Load them as Pi configures them; any tools or MCP clients an extension exposes are the user's responsibility.\n- Leave native prompt templates and commands under provider control unless the provider automatically advertises them to the model as a skill-like capability. In that case they follow the same explicit node-scoping rule as skills and plugins.\n- Preserve the selected agent provider's native authentication contract. A Claude Code subscription runs through the Claude Agent SDK; using an Anthropic model through another provider is not Claude Code subscription support. Do not reroute or relabel provider identity to reuse credentials.\n- Provider-boundary tests must prove both halves of the contract: native settings and guidance remain visible, while undeclared skills, plugins, and MCP servers remain unavailable and explicitly named capabilities load.\n\n## Workflow-language boundary\n\nThe governing rule is: **YAML coordinates. Code computes. Agents judge.** Read [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md) before changing the YAML surface. Read [`.archon/workflows/sdlc/CONSTITUTION.md`](.archon/workflows/sdlc/CONSTITUTION.md) before changing the SDLC pack.\n\n- YAML should expose only what the engine must see to govern a run: topology, gates, joins, retries, sessions, artifacts, durable waits, and reusable structure.\n- Computation belongs inside a `bash:`, `script:`, or `prompt:` node. Choosing a script instead of a prompt is an ordinary reliability and maintainability decision, not a constitutional rule.\n- A new YAML field or expression feature must add governance value that existing node bodies and wiring cannot provide.\n- Do not grow `when:` one operator at a time. Compute a complex decision in a node and gate on structured output.\n- Composition must preserve a graph the engine can validate and govern. Runtime-created independent work belongs in a child run, not hidden dynamic YAML structure.\n- Provider capabilities normally belong in provider config, tiers, or aliases. A new node field must earn genuine per-node variance.\n- Implicit behavior must be documented, defeatable where appropriate, visible, and fail-safe.\n\n## Architecture and ownership\n\n- Preserve package boundaries. Inspect package manifests and current imports before adding a dependency; do not rely on a copied package inventory.\n- Provider SDK dependencies and SDK-specific option translation belong in `@archon/providers`.\n- Workflow execution depends on injected contracts, not on core database or adapter implementations.\n- Platform-specific parsing and authorization belong inside the owning adapter. Core receives normalized intent and identity.\n- The Web client consumes generated API types through its local API layer; it does not import server or workflow runtime packages.\n- The engine (`packages/`) and the bundled SDLC pack (`.archon/workflows`, `.archon/scripts`) don't import each other's code, in source or in tests. They meet through the workflow language, the CLI and the provider and forge contracts.\n- Shared schemas are the source of truth for persisted and API shapes. Keep OpenAPI generation and runtime validation on the same schema path.\n- Use the type system, schema validation, and package boundaries to enforce architecture before adding prose rules.\n\n## Data and compatibility\n\n- Database schema evolution is additive-only. There is no migration ledger or version gate, and older binaries may open the same database.\n- Never rename, retype, or drop a shipped table or column. A new `NOT NULL` column needs a default that remains valid for older writers.\n- Keep SQLite and PostgreSQL schemas aligned unless a narrow, documented exception is already enforced.\n- Test upgrades from shipped schema shapes, not only fresh database creation.\n- Keep generated or mirrored artifacts synchronized through their owning scripts. Find those scripts in `package.json` and adjacent tests instead of copying a list here.\n- Persisted identifiers, paths, and wire values are contracts. Trace them through every reader before changing their meaning.\n\n## Git and workspace safety\n\n- `main` is the release branch and `dev` is the working base. Never commit directly to either for feature work, and never force-push `main` or `master`.\n- Preserve user changes, including untracked files. Do not use `git clean -fd`, destructive resets, or broad cleanup commands on user-owned workspaces.\n- Let git report conflicts and dirty state. Do not hide or automatically resolve a state the operator owns.\n- Hard reset is allowed only for an explicit reset mode on an Archon-owned checkout.\n- Run artifacts, logs, and state belong outside the repository under the Archon workspace. Do not stage them.\n- When stopping a process, target the recorded PID or exact bound port. Never kill by a broad process-name match.\n- Keep commits and pull requests focused. Do not mix unrelated cleanup into the requested outcome.\n\n## Running Archon workflows\n\nThe input is the contract. A run is only as accurate as the brief it starts from, and a\nworkflow cannot recover from a premise that was never stated — it will produce a confident,\nwell-formed answer to the wrong question. Treat writing the input as the work, not the\npreamble to it.\n\nBefore launching a run, the input — an issue body, a message, a document, whatever the run\nreads — must state:\n\n- the problem to solve;\n- why it is worth solving;\n- why now;\n- the desired outcome;\n- the invariants that must hold;\n- what acceptance looks like.\n\nSolution steering is optional and belongs last, but omitting it is a decision: the run then\nchooses its own approach. State a constraint you actually hold — reuse this primitive, no new\ndependency, migrate rather than rewrite — because an unstated preference cannot be honoured.\nNaming an implementation *before* the problem is settled narrows the run to your first guess.\n\nDo not launch a run against an input missing any of the six. Fix the input first, and say\nwhat you changed — a thin brief is cheaper to correct before a run than after one.\n\n## Tests and validation\n\n- Add tests for meaningful behavior and failure modes. Do not preserve deleted features through regression-test clutter.\n- The engine tests what it owns (the workflow language, script nodes, state, waits, resume) with its own minimal fixtures, never with a pack workflow. The pack tests only its own scripts at a tool boundary and never retests the engine's behavior.\n- Prefer deterministic, cheap tests. Remove accidental I/O and unnecessary subprocesses before increasing timeouts.\n- Remove test temp trees with `removeTempTree` or `trackTempRoots`: from `@archon/paths/test-utils` in engine and `scripts/` tests, and from the pack's own `.archon/scripts/__tests__/temp-roots.ts` in pack tests.\n- Spawn a subprocess only when the subprocess is the subject under test. To suppress an inherited env key for a Bun child, pass it as `''`; deleting it allows `.env` loading to restore it.\n- Bun's module mocks pollute the process cache. Use the package test scripts that preserve isolation; do not run `bun test` from the repository root.\n- Run lint through `bun run lint`. Besides Biome, it runs the repository checks Biome cannot express, such as the workflow persistence boundary.\n- Check what you changed: run the tests that cover it, lint and type-check the packages you touched, and run `bun run check:ratchet` before you push. CI refuses a change the ratchet refuses, and it takes seconds locally against a fetched `origin/dev`.\n- CI is the full validation. Don't run `bun run validate` to finish a change. `scripts/validate.ts` owns the check list and CI selects from it; workflow fixtures run locally only through `--only`.\n- Schema changes also need the PostgreSQL upgrade check. [`CONTRIBUTING.md`](CONTRIBUTING.md) lists it with its command.\n- For visual or runtime behavior, add direct evidence when static tests cannot prove the outcome.\n- Destructive verification, including DDL, migrations, and data writes, runs only against a scratch database you create and drop. A configured live DSN is read-only at most; when only a live resource exists, stop and surface it to the operator.\n\n## Pull requests\n\n- Branch from `dev` and open the pull request back to `dev`.\n- Use [`.github/pull_request_template.md`](.github/pull_request_template.md). Keep its required sections, remove instructional comments and irrelevant conditional sections, and describe the problem before the implementation.\n- Review the complete diff against the actual base branch before opening the pull request.\n- Link the source issue with a closing keyword when the work has one.\n- Keep titles concise and outcome-focused. Do not add AI attribution to commits or pull requests.\n\n## Find current facts\n\nDo not expand this file with copied reference material. Use the owning source:\n\n- Product direction: [`.archon/direction.md`](.archon/direction.md)\n- Code structure, flows, extension points and enforced boundaries: [`.archon/architecture.md`](.archon/architecture.md)\n- Workflow-language design: [`.archon/workflow-language-constitution.md`](.archon/workflow-language-constitution.md)\n- Engineering craft and review values: [`.archon/engineering.md`](.archon/engineering.md)\n- CLI: [`packages/docs-web/src/content/docs/reference/cli.md`](packages/docs-web/src/content/docs/reference/cli.md) and `archon --help`\n- Workflow authoring: [`packages/docs-web/src/content/docs/reference/workflow-language.md`](packages/docs-web/src/content/docs/reference/workflow-language.md) and workflow schemas\n- Database behavior: [`packages/docs-web/src/content/docs/reference/database.md`](packages/docs-web/src/content/docs/reference/database.md), migrations, and adapter tests\n- Available commands and generated-file checks: root and package `package.json` scripts\n\n<!-- File: CLAUDE.md (Tokens: ~9 | Category: claude-rule) -->\nAgent rules: read @AGENTS.md\n\n<!-- File: .claude/skills/archon-cli/authoring/authoring-workflows.md (Tokens: ~4027 | Category: anthropic-skill) -->\n---\nname: archon-cli-authoring\ndescription: Design and write an Archon workflow: outcome-first decomposition, gate design, node types, loop channels, packaging, fixtures. Read when creating or editing workflow YAML.\n---\n\n# Authoring Workflows\n\nDesign and write an Archon workflow — for code work, business operations, or any\ngoverned process. The method is outcome-first; the mechanics follow from it.\nExemplars: the bundled sdlc pack in any Archon checkout under\n`.archon/workflows/sdlc/<primitive>/` — read a pack YAML before writing your own.\nArchon is not code-only: triage an inbox of tickets, reconcile accounts,\nscreen applicants, keep CRM records honest — the same node types, gates, and\ndiscipline apply. What changes per domain is where the gates come from (below).\n\n## The design sequence\n\nWork through these questions in order, with the user where marked:\n\n1. **Desired outcome.** What artifact or world-state exists when this succeeds?\n   One sentence. If it cannot be stated as an outcome, it is not ready to build.\n2. **Specificity (with the user).** Is this workflow written against ONE project\n   or outcome, or meant to be generally reusable? This changes everything\n   downstream: a project-specific workflow may hardcode that repo's real gates,\n   real commands, real rules, and speak in its vocabulary — which makes it far\n   more powerful than any generic equivalent. Ask before designing.\n3. **Reuse check.** Which of the USER'S OWN workflows already do part of this?\n   (`archon workflow list`, then read their YAML.) Compose those via `include:`.\n\n   **The bundled sdlc pack is NOT a component library.** Its workflows exist to\n   teach how good workflows are written — read them as worked examples of gate\n   placement, prompt contracts, and fixture discipline, never `include:` them\n   into a user workflow by default. Their prompts and gates are deliberately\n   general so they fit any project; a user workflow composed out of them inherits\n   that generality and loses the specificity that made it valuable. If the user\n   asks for \"something like archon-ship but for my repo\", write a new workflow in\n   their project's terms, using the pack only as the craft reference.\n4. **Primitives.** Thinking in primitives is right: name the small jobs that\n   compose into the outcome — investigate, decide, implement, verify, publish;\n   or intake, enrich, judge, route, notify for an ops queue.\n   Package each coherent reusable primitive as a workflow folder with declared\n   `inputs:` and `returns:`. It can then run alone or compose into a parent with\n   `include:` and `with:`. Nodes implement a primitive's internals and the small\n   one-off glue in a parent: use an AI node for judgment and code for a checkable\n   fact. The primitive set comes from THIS outcome, not from copying another\n   workflow's node list.\n5. **Gate placement (with the user).** Where must the run stop before\n   continuing, and what decides? Three gate kinds, all first-class:\n\n   - **Deterministic gates** — a `bash:`/`script:` node owning an exit code or a\n     `when:` on a declared field. Use wherever a checkable fact settles it —\n     in code work: tests pass, file exists; in ops: totals reconcile, every\n     row has an owner, the export count matches the source. Cheapest and least\n     arguable.\n   - **Prompt command gates** — an independent verifier judging another agent's\n     output against an evidence bar. Start the verifier with `context: fresh`,\n     have it read the diff/artifact/entry, check claims and criteria, and return a\n     structured boolean that drives the loop or branch. When gate design is\n     unknown, START HERE: an agent checking agents is the most powerful\n     general-purpose gate and catches what no fixed check anticipated. Keep the\n     verdict structured (`output_format` boolean) so a machine consumes it; keep\n     the judgment prose in the prompt. Grounded on an **authored sidecar** — a\n     versioned rubric/directions file in the repo — an agent judgment gate stands\n     in for a typical human reviewer: same criteria, same questions, recorded\n     answers, every run.\n   - **Attention pauses** (`wait: { attention: ..., form: approval }`) — only\n     two cases justify them: an **interactive** workflow where someone is present\n     at the console and reads each iteration, or a **super load-bearing** action — irreversible,\n     public-facing, or expensive beyond easy rollback (deploy, post publicly,\n     delete data, send customer-facing communication). Everything else should be\n     decidable by code or by another agent. The pause holds the run durably in the\n     attention queue, whether it started in the foreground, detached, or from a\n     trigger. Container-isolated workflows cannot hold one: Archon refuses a\n     workflow with any `wait:` node under container isolation before creating the\n     run. Reject is an answer, not a cancel: add a `cancel:` node behind\n     `when: \"$<id>.output.decision == 'reject'\"` where rejecting should end the run.\n     The older `approval:` node is deprecated.\n\n   On a project-specific workflow the user often already knows some of these —\n   \"never push before I see the diff\", \"stop if the migration touches billing\",\n   \"no applicant advances without both scorecards agreeing\" — collect them\n   verbatim; they become attention pauses, cancel nodes, or `when:` conditions.\n   Everything else defaults to a prompt command gate until a real run proves\n   which part can be made deterministic.\n\n   **Gates are latent in every domain.** Any process humans govern already\n   invented its controls: double-entry, reconciliation, four-eyes review,\n   checklists, read-backs. Elicitation is the authoring work — have a strong\n   model propose candidate gates from domain knowledge, then have the user (the\n   domain expert) validate which ones actually bind. A proposed gate earns trust\n   only after it has demonstrably refused a planted bad outcome — prove gates red\n   the way you prove fixtures red.\n6. **Data flow.** What does each node hand the next? Declared values\n   (`$node.output`, `output_format` fields, workflow `inputs:`/`returns:`) carry\n   JSON values; artifacts (`$ARTIFACTS_DIR`) carry files. The producing node owns\n   the schema, including for script results and composed workflows. Read\n   `node-reference.md` → Result contracts before declaring a result, and\n   `variables.md` → Artifact pointers when returning a file reference.\n7. **Prompting** per node → see `prompting-mistakes.md` in this folder before writing prose.\n8. **Wiring** into YAML (below), then **fixtures**: every authored workflow ships\n   a dry-run fixture under `<pack>/<workflow>/fixtures/*.stubs.yaml` declaring its\n   expected outcome. Prove red paths red before trusting them. Fixtures prove the\n   paths that dry-run can execute. A reachable `workflow:` or `include:` +\n   `fan_out:` node fails dry-run by design, so prove that path with a small real\n   acceptance run while keeping fixtures for the surrounding wiring.\n9. **Validate** before any real run. None of these steps spends on a model:\n\n   ```bash\n   archon validate workflows <name>                    # schema, references, command files\n   archon workflow run <name> --dry-run --stubs-init stubs.yaml   # scratch stub scaffold\n   archon workflow run <name> --dry-run --stubs stubs.yaml\n   archon workflow test <name>                         # every declared fixture\n   ```\n\n   `--stubs-init` writes a block-style YAML stub scaffold to fill in. Keep it outside\n   `fixtures/`: a dry-run stub file is not a fixture. A fixture file declares a `fixture:` block, and\n   `--stubs` refuses it (\"this is a fixture file; run it with 'workflow test'\"). Copy\n   the stub values you need into a fixture and run it with `workflow test`. A dry run\n   walks the graph with stubbed node outputs and prints each node's resolved provider\n   and model; add `--exec-code` only for trusted `bash:` and `script:` nodes. Then make a planted\n   bad case fail: a fixture that never went red proves nothing. Finish with one real\n   run on a genuine task.\n\n## Agent-first: who decides what\n\nArchon's rule is **YAML coordinates. Code computes. Agents judge.** Give each kind\nof work to the one that does it well:\n\n- **Agents judge.** Reading a comment, a diff, a ticket or a CI log and deciding what\n  it means is a prompt node that returns a small typed result (`output_format`).\n  A script that tries the same judgment freezes one guess into code.\n- **Scripts give facts and act at boundaries.** A script reads the forge, git, files\n  or the CLI and hands the agent facts, or performs the write the agent decided.\n  It never re-judges what the agent already judged. Compute in a `bash:` or\n  `script:` node only when the rule is genuinely fixed (parsing JSON, counting,\n  comparing heads) or a typed invariant must hold before an irreversible action.\n- **YAML coordinates.** It holds topology, gates on typed fields, joins, sessions and\n  waits. Do not grow `when:` into logic; compute the decision in a node and gate on\n  its field.\n- **Natural language is not a wire format.** Never regex or keyword-match prose,\n  whether a user's message, an agent's output or a vendor's error text, to decide\n  what happens next. Let an agent read it and return a typed value.\n\nBad: a script classifies a review comment with a pattern, and the pattern is the\ndecision.\n\n```yaml\n- id: classify\n  bash: |\n    if printf '%s' \"$COMMENT\" | grep -qiE 'lgtm|approve|ship it'; then echo ready; else echo changes; fi\n- id: merge\n  depends_on: [classify]\n  when: \"$classify.output == 'ready'\"\n  script: merge\n  runtime: bun\n```\n\nGood: an agent reads the comment and returns a typed verdict, and the script only\nacts on it.\n\n```yaml\n- id: verdict\n  prompt: |\n    Read the review comment in $ARTIFACTS_DIR/comment.md. Decide whether the\n    reviewer approves merging this change as it stands.\n  output_format:\n    type: object\n    properties:\n      ready: { type: boolean }\n      reason: { type: string }\n    required: [ready, reason]\n- id: merge\n  depends_on: [verdict]\n  when: \"$verdict.output.ready == true\"\n  script: merge\n  runtime: bun\n```\n\nTest at the same seams. A script gets a test at its tool boundary: it writes an issue\nonce, reads back a pushed head, merges only what was approved. Agent judgment is\nchecked by outcome, through dry-run fixtures (`archon workflow test`) and real runs,\nnever by a test that pins a prompt's wording, the YAML's shape or the route a run\ntakes. Do not retest what the engine owns (`when:`, includes, loops, output\nvalidation). In an Archon checkout, `.archon/workflow-language-constitution.md` and\n`.archon/workflows/sdlc/CONSTITUTION.md` (sections 1 to 5 and 13) state these rules\nin full.\n\n## Start simple, then adapt to what runs teach\n\nThe first version is deliberately under-engineered:\n\n- Use few nodes, short prompts, and key gates. Do not add determinism without evidence;\n  before a real run, it is only a guess about where things fail.\n- Dogfood it immediately on something real — a genuine task in the actual repo,\n  not a demo. The run itself is the test.\n- Adapt from evidence, not anticipation: a failure shows which gate was missing;\n  a wasted review shows which check earned its place. Add machinery only where a\n  logged occurrence demands it.\n- Iterate in place: edit the folder, rerun the same kind of task, compare the\n  artifacts. The workflow converges on the project instead of being designed\n  up front.\n\nAll mechanical detail lives in `node-reference.md` (every node type and field,\nwhen:/trigger_rule wiring, fixtures) with the full substitution semantics in\n`variables.md` — read both when writing YAML.\n\n## File layout — one folder per workflow\n\nKeep each workflow in its own folder. If its scripts use pack-level shared\nmodules, copy the pack when moving it between repos. To start a workflow:\n\n```bash\nmkdir -p .archon/workflows/my-pack/my-workflow/{commands,scripts,fixtures}\n```\n\n```text\n.archon/workflows/<pack>/<workflow>/\n├── <workflow>.yaml      # name, description, inputs:, returns:, nodes:\n├── commands/*.md        # prompt files referenced by command: nodes\n├── scripts/*.py|.ts     # deterministic logic (Python/uv preferred, TS/bun fine)\n└── fixtures/*.stubs.yaml\n```\n\nSubstantive prompts live in command files, not inline. Inline `bash:` only for\none-or-two-line glue; anything with branching is a script file. Never shell as a\nscript language for logic. Ask the user their script-language preference if they\nwill maintain it. Declare Python dependencies inline (`deps:` for `uv`); keep\n`node_modules` and virtual environments out of the packaged script tree.\n\nPut shared `.ts`, `.js`, or `.py` modules and `.md` documents in\n`<pack>/.shared/` (subfolders are supported). This reserved directory supplies\nmodules and documents, never workflow definitions or `script:` targets. Documents\nship beside the modules in every build, so a script finds one relative to its own\nlocation and hands its path to a prompt node through `with:`. Missing packaged script targets fail at load time. From `<workflow>/scripts/publish.ts`, Bun can import\n`../../.shared/result.ts`. Python files can add\n`str(Path(__file__).resolve().parents[2] / \".shared\")` to `sys.path` using\n`pathlib.Path`, then import normally; Archon does not inject `PYTHONPATH`.\nAdjust the relative depth for grouped scripts.\n\nThe same paths work in project/global source, bundled binaries, and frozen\ncaptures. Keep imports inside the pack and require no npm packages or target\nproject configuration. Use regular module files: the binary generator rejects\nsymlinks under `.shared`. Write outputs to `ARTIFACTS_DIR` or `STATE_DIR`, never\nbeside a script; Python bytecode caching is disabled in runs and executable fixtures.\n\n## Node types — one per node\n\n`command` (prompt file) · `prompt` (inline) · `bash` (shell, no AI) · `script`\n(py/ts, no AI) · `loop` (iterate one AI node) · `loop_group` (repeat a sub-DAG) ·\n`approval` (deprecated; use `wait: attention`) · `cancel` (terminate with reason, usually behind `when:`) ·\n`wait` (durable suspension, or an `attention:` question answered in a form) · `workflow` (governed child run) · `include`\n(plain load-time composition or runtime-width in-run composition with `fan_out:`).\n\nSelection rules of thumb:\n\n- A machine-verifiable check → `bash:`/`script:` node, never an AI asked to \"check\".\n- Model judgment that gates downstream spend → AI node with `output_format` boolean\n  + a downstream gate or loop channel reading that field.\n- Repeated act→verify cycle → `loop_group` with the verify node's structured\n  boolean driving completion.\n\n## Loop completion channels — pick by tier\n\n- Completion externally checkable → `until_bash: \"<exit-0 check>\"`.\n- Completion is the model's judgment → `output_format` + `until_field: <bool-field>`.\n- Prose sentinel (`until: TOKEN`) is deprecated; prefer the channels above.\n  Declare at least one channel per loop.\n\n## Structured output: only where a machine decides\n\nDeclare `output_format` when a `when:`, `until_field`, or script consumes the\nfield. Otherwise let output stay whole-string prose — schemas are a reliability\ntax on non-enforcing providers. Provider-agnostic YAML always: tiers\n(`small|medium|large`) never literal model ids, no `provider:` keys in bundled work.\n\n## Isolation and interactivity\n\n- At workflow level, `worktree.enabled: true` pins worktree isolation. Omission\n  leaves the choice to the caller. On a `workflow:` child node,\n  `isolation: worktree` explicitly gives the child its own worktree. A worktree\n  bounds git files; it does NOT isolate `~/.archon`, env, database, or sessions.\n- A `wait: attention` node pauses the run in the attention queue until it is answered. It\n  needs no workflow-level declaration and does not stop a detached or triggered launch.\n- Composition (`include:`) runs inside the parent's single run/isolation/audit\n  trail. Use `workflow:` sub-runs only when a separately governed launch is the\n  actual requirement.\n\n## Common mistakes\n\nRead before writing YAML:\n\n- **Prose between nodes as a wire format.** Emitting a token so the next node can\n  grep it. Fix: structured output + typed reads, or a script exit code.\n- **AI doing what code should.** \"Check if tests pass\" as a prompt. Fix: bash/script\n  node owning the exit code.\n- **Parsing model prose to decide.** Regex over `$node.output`. Same fix.\n- **Hardcoding the project's toolchain** (`tsc`, `pytest`) in default-workflow\n  gates. Deterministic checks verify only what Archon owns (git state, artifacts,\n  exit codes of agent-discovered commands).\n- **Silent fallbacks.** An optional input that degrades quietly instead of failing.\n  Fail loud with the missing thing named.\n- **Trusting exit codes / run status as verdicts.** A declined task exits 0;\n  gate on artifacts existing and validating, and on declared outcome fields.\n- **Skipping fixtures.** Unfalsified wiring is unknown wiring. Expected-red paths\n  especially: seen red, or they prove nothing.\n- **Inline everything.** Long prompts inline in YAML, logic inline in bash.\n  Files age better, diff better, and are reusable across nodes.\n\n<!-- File: .claude/skills/archon-cli/authoring/node-reference.md (Tokens: ~4584 | Category: anthropic-skill) -->\n# Node Reference\n\nMechanical reference: every node type, its fields, how nodes wire together, and\nhow to prove wiring with fixtures. For design judgment (gates, primitives,\nprompting) read `authoring-workflows.md` in this folder first.\n\n## Workflow-level fields\n\n```yaml\nname: my-workflow            # unique; user workflows may shadow bundled names\ndescription: |\n  What this does and when to use it. Shown by `archon workflow list`.\nmodel: large                 # tier keyword (small|medium|large) or @alias — never a literal model id\ninputs:                      # declared signature; callers pass via --input name=value\n  work:\n    default: \"\"\n    description: Optional work override; prompts read $ARGUMENTS explicitly for fallback\nreturns: implement           # which node's output is the workflow's terminal output\noutcome_field: green         # persist one authored boolean beside run status (e.g. green/ready/rooted)\nworktree:\n  enabled: true              # pin worktree isolation. Bounds git files ONLY —\n                             # not ~/.archon, env vars, database, or sessions\nmutates_checkout: false      # declare when the workflow never touches the working tree\n```\n\nAn input default does not fall back to the invocation message automatically.\nWhen both are valid sources, give the model both values and state the precedence:\n\n```yaml\n- id: implement\n  prompt: |\n    Optional work override: $INPUTS.work\n    Original invocation: $ARGUMENTS\n    Use the work override when it is non-empty; otherwise use the original invocation.\n```\n\n## The eleven node types\n\nExactly one of these appears per node: `command`, `prompt`, `bash`, `script`,\n`loop`, `loop_group`, `approval`, `cancel`, `wait`, `workflow`, `include`.\n`approval` is deprecated; new pauses use `wait: { attention: ..., form: ... }`.\n\n### command / prompt — AI nodes\n\n```yaml\n- id: plan\n  command: plan              # loads <workflow-folder>/commands/plan.md — prefer files over inline prose\n  depends_on: [investigate]\n```\n```yaml\n- id: classify\n  prompt: \"Classify $ARGUMENTS. Set type to BUG or FEATURE.\"\n  allowed_tools: []          # no tools needed for pure judgment\n  output_format:             # declare ONLY when a machine consumes a field (gate/until_field/script)\n    type: object\n    properties:\n      type: { type: string, enum: [BUG, FEATURE] }\n    required: [type]\n```\n\nKey fields on AI nodes: `model`/`provider` (tiers), `output_format` (+ `output_type`\nfor typed artifact sidecars), `allowed_tools`/`denied_tools`, `effort`/`thinking`,\n`retry` (default 2x on transient), `persist_session` (continues across runs only on a\nprovider that supports session forks; others start fresh each run), `idle_timeout`.\n\nSession context is explicit when it matters:\n\n- Omitted context inherits the prior compatible provider session in a sequential\n  layer and starts fresh in a parallel layer.\n- `context: fresh` always starts clean. Use it for independent verification.\n- `context: shared` explicitly inherits the ambient session in a sequential\n  layer. It is invalid where parallel structure makes that ancestry ambiguous.\n- `context: { resume: plan }` forks the named completed upstream node's session.\n  Use it when one exact ancestry matters; the provider must support session forks.\n  Inside a `loop_group` body the source can be an enclosing node, whose session the\n  body continues across iterations, or an upstream node in the same body, whose\n  session of that iteration the consumer forks. The same-body form suits a follow-up that asks the agent to\n  finish a step it skipped.\n\n### bash — deterministic shell, no AI\n\n```yaml\n- id: test-gate\n  bash: bun run test\n  timeout: 300000            # ms; stdout captured as $test-gate.output\n```\nExit code decides pass/fail. On `timeout`, Archon signals a host node's process tree,\nso work behind `bun run` or another wrapper stops too. A process that leaves the node's\nprocess group (`setsid`) escapes. In a container node, a timeout ends only the local\n`docker exec` client, and the command inside the container may keep running. In a detached run on macOS or Linux, a timeout whose\ncleanup cannot be confirmed fails the node, even with `on_timeout: skip`.\n\nThe same node shape verifies non-code work:\n`bash: python3 scripts/reconcile.py --period 2026-08` (totals must balance),\nor a script that checks every imported row has an owner. One-or-two-line glue\nonly; anything with branching is a script node.\n\n### script — TypeScript (bun) or Python (uv), no AI\n\n```yaml\n- id: verify-report\n  script: verify-report      # loads <workflow-folder>/scripts/verify-report.py\n  runtime: uv\n  depends_on: [implement, review]\n  with:                      # typed value transport into INPUTS_<UPPER_SNAKE> env vars\n    green: \"$implement.output.green\"\n    prior: {from: \"$review.output\", if_skipped: \"none\"}\n```\n\nPrefer named script files over inline bodies. Python (`runtime: uv`) preferred,\nTypeScript (`runtime: bun`) fine. `deps:` adds uv dependencies.\n\n### Result contracts — the producer owns the schema\n\nUse the producing node's `output_format` for a JSON result. `returns:` selects\nthat node; it does not declare another schema. No value-shape declaration belongs\non `inputs:` or `returns:`.\n\n```yaml\nname: certified-result\ndescription: Certify a deterministic result and bind its field downstream\nreturns: build\nnodes:\n  - id: build\n    bash: printf '%s' '{\"ready\":true}'\n    output_format:\n      type: object\n      properties:\n        ready: {type: boolean}\n      required: [ready]\n  - id: consume\n    depends_on: [build]\n    with:\n      ready: \"$build.output.ready\"\n    runtime: uv\n    script: |\n      import os\n      print(os.environ[\"INPUTS_READY\"])\n```\n\nA `bash:` or `script:` node with `output_format` must print one strict JSON\ndocument satisfying that schema. Send diagnostics to stderr. There is no JSON\nrepair or reask; a contract violation fails the node and is not retried, even\nwith `retry:`. A schema that cannot compile is a load error. Without\n`output_format`, exec stdout remains raw text. Named scripts use the same rule;\nthe small bash example above keeps the handoff visible.\n\nAn `include:` alias exposes the selected producer's contract through flattening.\nA `workflow:` caller receives the child's selected result and declared field\nnames, persisted for cold resume. Do not repeat the schema on the caller:\n`output_format` on a `workflow:` node is a load error; on `include:` it is ignored\nwith a warning. A schemaless child stays schemaless. Fan-out returns an ordered\narray of item values; consume the whole aggregate, not a field on the array.\n\nFor field-access and file-pointer rules, read `variables.md` → Node Output Details and\nArtifact pointers. The docs' `reference/workflow-language.md` → Result contracts\ngives the full composition examples.\n\n### loop — iterate ONE AI job\n\n```yaml\n- id: implement\n  loop:\n    command: implement\n    until_field: done        # boolean in this node's output_format\n    max_iterations: 5\n  depends_on: [plan]\n  output_format:\n    type: object\n    properties:\n      done: {type: boolean}\n    required: [done]\n```\n\nCompletion channels — declare at least one, pick by tier:\n\n- `until_bash: \"<exit-0 check>\"` — completion externally checkable (deterministic)\n- `until_field: <bool>` — completion is the model's judgment, via `output_format`\n- `until: TOKEN` — deprecated prose sentinel; use one of the channels above\n\nAlso: `fresh_context: true` (each iteration starts clean) and `$LOOP_PREV_OUTPUT`\nfor previous-iteration state.\nA failed iteration fails the loop at `max_iterations` — bound exhaustion fails\ntruthfully rather than blessing a bad last attempt.\n\n### loop_group — repeat a sub-DAG\n\n```yaml\n- id: fix-cycle\n  depends_on: [review-findings]\n  loop_group:\n    nodes:\n      - id: fix\n        prompt: \"Address findings: $review-findings.output\"\n      - id: recheck\n        prompt: \"Verify each finding is fixed with evidence.\"\n        depends_on: [fix]\n        context: fresh\n        output_format: {type: object, properties: {ready: {type: boolean}}, required: [ready]}\n    until_bash: 'ready=$recheck.output.ready; test \"$ready\" = \"true\"'\n    max_iterations: 5\n```\n\nBody is sealed (no external `depends_on`); outer outputs reach bodies via\n`$nodeId.output`. In a `loop_group` body, `$LOOP_PREV.<body-id>.output` reads the\nprevious iteration: prompts text-substitute it, while `when:` conditions resolve it\nas a typed condition reference. A failed body node fails the group immediately.\n\n### approval — deprecated\n\n`approval:` still loads and runs, but the loader warns once per workflow file, naming\neach node and its replacement. Do not write new ones. Replace:\n\n- `approval: { message }` → `wait: { attention: <message>, form: approval }`. A legacy\n  reject cancels the run; the attention form continues with `{decision: 'reject'}`, so\n  add a `cancel:` node with `when: \"$<id>.output.decision == 'reject'\"` where rejecting\n  should end the run.\n- `approval.decisions` → `wait: { attention, form: select, options }` with the same ids\n  and labels; same `{decision, text}` output.\n- `capture_response` → read the answer's `$<id>.output.text`; every form records text.\n- `on_reject` / `$REJECTION_REASON` → a rework node with\n  `when: \"$<id>.output.decision == 'reject'\"` that reads `$<id>.output.text`, inside a\n  `loop_group` if it should repeat.\n\n### cancel — terminate deliberately\n\n```yaml\n- id: refuse\n  cancel: \"This workflow handles bugs only.\"\n  when: \"$classify.output.type != 'BUG'\"\n```\n\n### workflow / include — reuse another workflow\n\nUse `workflow:` to start a governed child run with its own artifacts, gates, cost,\nand audit trail. Use `include:` to flatten a reusable workflow block into the\ncurrent run at load time.\n\n```yaml\n- id: review-child\n  workflow: archon-review\n  with:\n    scope: \"$INPUTS.branch\"\n  isolation: worktree\n\n- id: review-inline\n  include: archon-review\n  with:\n    scope: \"$INPUTS.branch\"\n```\n\nThe `workflow:` target name is static YAML but resolves when the node executes,\nso a prior node may author the child workflow. `workflow:` also accepts `input:`\ninstead of `with:` and can declare `fan_out:`. A plain `include:` resolves and\nexpands fully at load time; the directive carries only structural graph fields\nplus `with:` and has no execution surface of its own. The include's `when:` gates the\nwhole block. Inside a `loop_group` it reads `$LOOP_PREV` from the loop that contains\nthe include.\n\nOn a `workflow:` node, `fan_out.max_parallel` limits unfinished children (default\n`5`). `max_parallel: all` starts every item at once: use it when each child asks an\nindependent attention question, because a paused child keeps its slot and a numeric\nwindow leaves later questions unstarted. Under `join: all_done` the node completes once\nevery child is terminal, even when some failed, never started or were cancelled; the run\nrecords each slot's outcome for operators (`workflow get`), but a downstream node that\njudges the results reads the `archon_failed` markers in `$<id>.output`.\n\nPut `fan_out:` on `include:` when runtime data determines how many copies of a\nstatic block should run inside the parent run:\n\n```yaml\n- id: list-scopes\n  bash: printf '[\"packages/cli\",\"packages/workflows\"]'\n\n- id: review-each\n  include: archon-review\n  depends_on: [list-scopes]\n  fan_out:\n    items: \"$list-scopes.output\"\n    as: scope               # required; each instance receives $INPUTS.scope\n    max_parallel: 1         # serial escape when the included block may write\n    join: all_done\n```\n\nThis form creates no child runs. It returns an item-ordered JSON array under\n`$review-each.output`. With `join: all_done`, a failed instance contributes an\n`archon_failed` marker; `all_success` fails the node. To run instances\nconcurrently, the complete included block must declare `mutates_checkout: false`.\nApproval gates, durable waits, and other suspension paths are not supported\ninside composed fan-out; put them before or after the fan-out.\n\n### wait — suspend durably\n\n```yaml\n- id: pause-for-ci\n  wait:\n    duration_ms: 300000\n```\n\nA wait declares exactly one condition: `duration_ms`, an ISO timestamp under\n`until`, `event` plus `deadline_ms`, or `attention`. Archon persists the suspension and\nreleases the worker slot. A time or event wait's fixed structured output includes\n`status` (`satisfied` or `expired`) and `waited_ms`; event waits may also return `event`\nand `payload`.\n\n`attention` asks a question that pauses the run until someone answers it in its `form`:\n\n```yaml\n- id: ask\n  depends_on: [plan]\n  wait:\n    attention: \"$plan.output.question\"\n    form: select              # message (default) | approval | select | multi_select\n    options:                  # select and multi_select only; or one whole \"$plan.output.options\"\n      - { id: use-main, label: \"Base on main\" }\n      - { id: use-dev }\n    labels:                   # optional; plain strings, runtime refs resolved at pause\n      about: \"$plan.output.topic\"\n```\n\nThe next node reads the answer: `{text}` for `message`, `{decision, text}` for `approval`\nand `select`, `{decisions, text}` for `multi_select`. In the `approval` form `reject` is\nan answer and never cancels the run; branch on `$ask.output.decision` with `when:`:\n\n```yaml\n- id: review\n  depends_on: [plan]\n  wait:\n    attention: \"Approve this plan? $plan.output\"\n    form: approval\n- id: stop\n  depends_on: [review]\n  when: \"$review.output.decision == 'reject'\"\n  cancel: \"Plan rejected.\"\n- id: implement\n  depends_on: [review]\n  when: \"$review.output.decision == 'approve'\"\n  command: implement\n```\n\nPause only for an interactive session or a super load-bearing action. The run pauses\nin the attention queue until answered, however it was started. To ask after each pass\nof a repeated body, end a `loop_group` body in the wait (it must be the body's sole\nterminal sink) and read the answer next iteration as `$LOOP_PREV.<wait-id>.output.text`.\n\n`labels` mean nothing to the engine; they let the user's handler workflows pick the\npause out of `archon workflow attention --label k=v`.\n\n## Wiring nodes together\n\n**`depends_on`** creates edges; independent nodes in one topological layer run\nconcurrently.\n\n**Data flow** — declared values carry scalars, artifacts carry documents:\n\n- `$nodeId.output` — whole text of an upstream node; unknown/skipped producer → `''`\n- `$nodeId.output.field` — strict JSON access: missing key **fails the consumer**\n  (never silent-empty); declared-optional field resolves to `''`\n- `with:` bindings (#2637) — prompt/command files read bound values as\n  `$INPUTS.<name>`; bash/script processes receive `INPUTS_<UPPER_SNAKE>` env vars.\n  `{from, if_skipped}` discriminates skipped producers from ran ones; a failed\n  producer fails the binding\n- `$INPUTS.<name>` — declared workflow inputs, also delivered as\n  `INPUTS_<UPPER_SNAKE>` to bash/script\n- `$ARTIFACTS_DIR` — pre-created directory for reports/evidence humans read.\n  Throwaway checkouts and dependency installs go under `$ARTIFACTS_DIR/.archon/scratch/`\n  (create it with `mkdir -p`), which Archon deletes once the run ends and its nodes have\n  stopped (a container run's when its container is removed); the rest of\n  `$ARTIFACTS_DIR` is kept\n- In `bash:` and `script:` sources, user-controlled variables (`$ARGUMENTS`,\n  `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`,\n  `$CONTEXT`) are **not** spliced as raw text (injection guard); they arrive as\n  environment variables (`ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`,\n  `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT`, `EXTERNAL_CONTEXT`,\n  `ISSUE_CONTEXT`). Read an environment-delivered value in `bash:` as `\"$ARGUMENTS\"`.\n  In `script:` read it via `process.env.ARGUMENTS` (bun) or `os.environ['ARGUMENTS']`\n  (uv/python).\n  A `loop_group` body's `bash:` node is the exception: the loop scope splices the\n  reply (`$LOOP_USER_INPUT`) into its source, shell-quoted, so the reply arrives\n  already quoted. Read the spliced reply as an already-quoted value — assign it\n  and quote the variable (`value=$LOOP_USER_INPUT; printf '%s' \"$value\"`) — rather\n  than as `\"$LOOP_USER_INPUT\"`.\n  `$nodeId.output` refs ARE substituted; give prose producers an `output_format`\n  before assigning their output directly in a script.\n\n**`when:` conditions** skip a node when false (skipped propagates to dependants):\n\n```yaml\nwhen: \"$classify.output.type == 'BUG'\"\nwhen: \"$score.output.confidence >= '0.9' && $gate.output.proceed == true\"\n```\n\nOperators: `==`, `!=`, `<`, `>`, `<=`, `>=` (numbers auto-parse, fail-closed on\nnon-numeric), compound with `&&`/`||` (`&&` binds tighter, no parentheses).\nMalformed expression → node skipped + warning. Dot-access contract violation →\nnode FAILS loudly. Guard optional paths with `when:` or `trigger_rule`.\n\n**`trigger_rule`** — join semantics over dependencies:\n\n| Value | Behavior |\n|-------|----------|\n| `all_success` | all deps succeeded (default) |\n| `one_success` | at least one dep succeeded |\n| `none_failed_min_one_success` | none failed AND ≥1 succeeded (skips OK) |\n| `all_done` | all deps terminal (completed, failed, OR skipped) |\n\n## Fixtures — prove wiring without AI spend\n\nEvery authored workflow ships dry-run fixtures in\n`<workflow-folder>/fixtures/<name>.stubs.yaml`:\n\n```yaml\n# Declared expectations + node stubs. Every non-reserved key is a node output.\nfixture:\n  expect: completed          # or failed / paused / cancelled\n  fail-node: assert-changed  # required when expect: failed — must match exactly\n  inputs:\n    branch: task-42\nimplement:\n  done: true\n  green: true\n  summary: \"stub summary\"\nexec-code: false             # true executes bash/script nodes for real (requires a git checkout)\n```\n\nRun them:\n\n```bash\narchon workflow test                    # everything discovered\narchon workflow test my-workflow        # one workflow's fixtures\n```\n\nA reachable attention wait pauses the simulation unless the fixture stubs it; the stub\nstands in for the answer and must match the form's output, e.g.\n`review: { decision: approve, text: \"\" }`. Deprecated `approval:` gates auto-approve.\n\nExpected-red fixtures are the important ones: a guard that has never been seen\nfailing proves nothing. Prove reds red before trusting them. `workflow test`\nmirrors what a real run does with its two directories: named scripts and command\nfiles come from one frozen capture of your working tree taken per invocation —\nuncommitted edits included, so authoring works as normal — while exec-code nodes\nexecute against a scratch worktree of HEAD. So a script that reaches outside\n`.archon/` with a `__file__`-relative path fails the fixture exactly as it would\nfail the run, and nothing a node executes can touch your checkout. Exec-code\nfixtures require a git checkout and fail explicitly outside one. Validate\nload-time errors with `archon workflow list`.\n\n<!-- File: .claude/skills/archon-cli/authoring/prompting-mistakes.md (Tokens: ~691 | Category: anthropic-skill) -->\n---\nname: archon-cli-prompting-mistakes\ndescription: Common prompt mistakes when invoking Archon workflows or writing node prompts. Short, negative-form guidance. Read before dispatching work or authoring prompts.\n---\n\n# Prompting Basics\n\n## General\n\n1. Agents are smart — take advantage of that. Each command node runs a real\n   coding-agent harness under the hood, with all its capabilities.\n2. Agents inside Archon do not necessarily know about Archon itself, this run,\n   or past failures. Do not reference context the agent has no awareness of.\n\n# Prompting Mistakes\n\nShort list. Applies in two places: the **message you pass when invoking a\nworkflow** (it becomes `$ARGUMENTS` for the whole run) and the **prompts inside\ncommand/prompt nodes** you author.\n\n## The mistakes\n\n1. **Vague outcome, implied method.** \"Look into the auth thing.\" Say what done\n   looks like and where context lives: \"Fix issue #42. Use the issue, comments,\n   and current source as evidence. Preserve the documented login behavior.\"\n2. **Smuggling your diagnosis.** If you want the run's investigation tested,\n   ask for the fix in ordinary language — do not hand it your suspected root\n   cause as fact unless you have proven it.\n3. **Narrating to nobody.** Unattended nodes produce artifacts, commits, and\n   declared fields — not chat. Prompts that say \"report to me below\" produce\n   output nothing ever reads. Point every instruction at the artifact it serves.\n4. **No evidence bar.** A judging prompt without \"cite the causal line / drop\n   anything that rests on 'might'\" collects plausible filler. State what counts\n   as proof, and demand silence otherwise.\n5. **Trusting exit codes as verdicts.** A declined task exits 0. Gates read\n   artifacts and declared fields, not success bits.\n6. **Prose wire formats.** Asking a node to end with a magic token the next node\n   greps. Use structured output fields or script exit codes instead.\n7. **Everything-but-the-kitchen-sink scope.** One node, one job, stated non-goals.\n   \"Also update docs and tests and the changelog\" dilutes all three. Negative\n   scope (\"do not touch the lockfile\") is as load-bearing as positive.\n8. **Assuming memory.** Each node may be a fresh session with no prior context.\n   Restate the input path, artifact path, and constraint inside each prompt.\n9. **Stop rules left unstated.** Tell the agent when to stop and surface:\n   ambiguity → present options; missing context → name it. Never \"use judgment\"\n   on decisions with real cost.\n10. **Soft failure language.** \"Try to...\" invites theater. Put checkable failure\n    in a `bash:`/`script:` node whose exit code fails the run, or have a judging\n    node return a structured boolean and gate on it deterministically.\n\n## The one-line test\n\nBefore sending any prompt — invocation or node — ask: *could a competent engineer\nwho has never seen this conversation act on exactly this text?* If the answer\nneeds context that lives only in your head, the prompt is not done.\n\n<!-- File: .claude/skills/archon-cli/authoring/variables.md (Tokens: ~2811 | Category: anthropic-skill) -->\n# Variable Substitution Reference\n\nVariables are placeholders in command files and workflow prompts that get replaced at execution time.\n\n## Variable Table\n\n| Variable | Scope | Description |\n|----------|-------|-------------|\n| `$ARGUMENTS` | All modes | The user's original message passed to the workflow |\n| `$USER_MESSAGE` | All modes | Same as `$ARGUMENTS` — both resolve to the user's message |\n| `$WORKFLOW_ID` | All modes | Unique workflow run ID (for tracking and logging) |\n| `$ARTIFACTS_DIR` | All modes | Pre-created directory for this workflow run's artifacts. Write outputs here. Scratch work (a throwaway checkout, a dependency install) goes under `$ARTIFACTS_DIR/.archon/scratch/`, which Archon deletes once the run ends and its nodes have stopped (a container run's when its container is removed) |\n| `$BASE_BRANCH` | All modes | The run's resolved or recorded base branch. Throws if referenced but unresolved. See the [Variable Reference](https://archon.diy/reference/variables/#workflow-variables) for resolution and child inheritance rules |\n| `$DOCS_DIR` | All modes | Documentation directory (config `docs.path`, default `docs/`). Never throws |\n| `$CONTEXT` | All modes | GitHub issue/PR context (if available from platform). Empty string if unavailable |\n| `$EXTERNAL_CONTEXT` | All modes | Alias for `$CONTEXT` |\n| `$ISSUE_CONTEXT` | All modes | Alias for `$CONTEXT` |\n| `$LOOP_USER_INPUT` | Loop / loop_group prompts | **Deprecated** with interactive loop gates; new loops end a `loop_group` body in a `wait: attention` node and read `$LOOP_PREV.<wait-id>.output.text`. Feedback from `/workflow approve <id> <text>` at an interactive loop gate. Populated ONLY on the first iteration after a resume; empty string everywhere else |\n| `$LOOP_PREV_OUTPUT` | Loop prompts | Previous iteration's cleaned output (completion tags stripped). Empty on iteration 1. Key tool for `fresh_context: true` loops that need to know what the last pass did |\n| `$LOOP_PREV.<nodeId>.output[.field]` | loop_group body prompts and `when:` conditions | Previous iteration's output of a specific body node. Empty on iteration 1. Field access follows the strict contract below (except genuinely-absent prior output → `''`). In a body `when:`, it is a typed condition reference rather than text substitution |\n| `$REJECTION_REASON` | Legacy `approval.on_reject` prompts | **Deprecated.** The reason from `/workflow reject <id> <reason>`. Empty string everywhere else. New workflows read an attention wait's `$<wait-id>.output.text` from a node behind `when: \"$<wait-id>.output.decision == 'reject'\"` |\n| `$nodeId.output` | DAG only | Full text output of a completed upstream node. Unknown/skipped producer → `''` |\n| `$nodeId.output.field` | DAG only | JSON field access — **strict**: an unresolvable field FAILS the consuming node (see below) |\n\n## Where Variables Are Substituted\n\n- **Command files** (`.archon/commands/*.md`) — the core set (`$ARGUMENTS`/`$USER_MESSAGE`, `$WORKFLOW_ID`, `$ARTIFACTS_DIR`, `$BASE_BRANCH`, `$DOCS_DIR`, `$CONTEXT` family), plus `$nodeId.output[.field]` when the command runs as a DAG node. A command file referenced by `loop.command` or a `loop_group` body receives the same populated loop variables as an inline loop prompt. Ordinary DAG command nodes receive `''` for loop/rejection variables. `$REJECTION_REASON` is populated only in an `approval.on_reject` prompt\n- **Inline `prompt:` fields** — in DAG prompt nodes, loop prompts, and loop_group body prompts\n- **`bash:` scripts** — SPECIAL: user-controlled variables (`$ARGUMENTS`, `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`, `$CONTEXT`) are **NOT spliced as raw text** into the script (shell-injection guard). A `loop_group` body's `bash:` node is the exception: the loop scope splices the reply (`$LOOP_USER_INPUT`) into its source, shell-quoted, so the reply arrives already quoted. Read the spliced reply as an already-quoted value — assign it and quote the variable (`value=$LOOP_USER_INPUT; printf '%s' \"$value\"`) — rather than as `\"$LOOP_USER_INPUT\"`. In `bash:`, they arrive as **environment variables** instead: `ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`, `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT` (+ `EXTERNAL_CONTEXT`/`ISSUE_CONTEXT`), plus `ARTIFACTS_DIR`, `LOG_DIR`, `BASE_BRANCH` — read an environment-delivered value with `\"$ARGUMENTS\"` as normal shell env access. `$nodeId.output` refs ARE substituted: small string values are single-quoted inline; values exceeding 32,768 UTF-16 code units (JavaScript `string.length`) spill to a file and substitute as unquoted `$(cat '<path>')`, which drops trailing newlines; the command substitution splits words and expands globs when used as a bare argument. Assign first (`value=$nodeId.output`), then quote the shell variable (`printf '%s' \"$value\"`) to handle both forms. See [Shell quoting guidance](https://archon.diy/reference/variables/#shell-quoting-in-bash-vs-script).\n- **`script:` bodies** — like `bash:`, user-controlled variables (`$ARGUMENTS`, `$USER_MESSAGE`, `$LOOP_USER_INPUT`, `$LOOP_PREV_OUTPUT`, `$REJECTION_REASON`, `$CONTEXT`) are **NOT spliced as raw text** into the source (injection guard, #2115). They arrive as **environment variables** — `ARGUMENTS`, `USER_MESSAGE`, `LOOP_USER_INPUT`, `LOOP_PREV_OUTPUT`, `REJECTION_REASON`, `CONTEXT` (+ `EXTERNAL_CONTEXT`/`ISSUE_CONTEXT`), plus `ARTIFACTS_DIR`, `LOG_DIR`, `BASE_BRANCH` and managed per-project env — read via `process.env.ARGUMENTS` (bun) / `os.environ['ARGUMENTS']` (uv/python) — env vars are the general injection-safe channel for untrusted values. A literal `$ARGUMENTS`/`$USER_MESSAGE`/`$CONTEXT` left in the source is not substituted: the text stays in the source. `$nodeId.output` values are still substituted **raw** (not shell-quoted). Direct assignment (`const data = $nodeId.output;`) is safe **only with `runtime: bun` when the producer uses `output_format`**. JSON booleans and nulls are not Python literals, so a `runtime: uv` script should bind the value with `with: { data: \"$nodeId.output\" }` and parse `os.environ['INPUTS_DATA']` with `json.loads`. For arbitrary-text producers (bash/script stdout or prose), use the same environment binding instead of embedding the raw value in source, then parse defensively only if the text is expected to contain JSON. **Avoid wrapping `$nodeId.output` in a `String.raw` template literal** — it silently breaks when the output contains a backtick (common in AI-generated markdown and `output_format` payloads)\n\n## Substitution Order\n\nA template is substituted in one pass. Each reference written in the template resolves once, and the value is inserted exactly as written: a message, issue context, loop reply, rejection reason, previous output, or input value that contains `$nodeId.output`, `$INPUTS.<name>`, a variable name, or `$&` reaches the node unchanged. Where two grammars match at the same `$`, the earlier group wins:\n\n1. Loop references inside a `loop_group` body (`$LOOP_PREV.<nodeId>.output`, the group's `$LOOP_USER_INPUT`)\n2. Workflow variables (`$WORKFLOW_ID`, `$ARGUMENTS`, `$ARTIFACTS_DIR`, `$BASE_BRANCH`, `$DOCS_DIR`, `$INPUTS.<name>`, `$CONTEXT`, loop/rejection vars)\n3. Node output references (`$nodeId.output`, `$nodeId.output.field`) — DAG mode only\n\n## Context Auto-Append\n\nIf `$CONTEXT` / `$EXTERNAL_CONTEXT` / `$ISSUE_CONTEXT` is NOT present anywhere in the prompt template but context exists (e.g., from a GitHub issue), it is automatically appended at the end after a `---` separator.\n\n## NOT Supported: `$1` … `$9` Positional Arguments\n\nDespite older docs suggesting otherwise, positional `$1`…`$9` are **not substituted** by the workflow engine — command files and prompts receive the whole message only, via `$ARGUMENTS`/`$USER_MESSAGE`. (A legacy positional-substitution helper exists in the codebase but is not wired into the execution path.) Parse arguments inside the prompt or with a `bash:`/`script:` node instead.\n\n## Node Output Details (DAG Only)\n\n`$nodeId.output` resolves to the full text output of the upstream node. If the node used `output_format:` (structured output), the output is the JSON-stringified validated result. Bash/script output without `output_format` is stdout with the trailing newline trimmed; with it, stdout is certified as JSON under the same result contract (see `node-reference.md` → Result contracts). Loop/loop_group output is the final iteration's output with completion-signal tags stripped. An attention wait outputs its answer by form: `{text}` for `message`, `{decision, text}` for `approval` and `select`, `{decisions, text}` for `multi_select`; read fields as `$ask.output.decision` and `$ask.output.text`. A deprecated gate with authored `approval.decisions` also outputs JSON `{decision, text}`. A deprecated gate without authored decisions keeps the old behavior: its approval comment is output only when `capture_response: true`, otherwise `''`. Unknown or skipped producers resolve to an empty string (with a warning logged).\n\n`$nodeId.output.field` and nested paths such as `$nodeId.output.proposal.action` are **strict** (no-silent-drop) — they either resolve or **fail the consuming node**. Paths are object field names only: no array indexing, wildcards or expressions. The same paths work in templates, `when:` (which needs a scalar), whole-value `with:` bindings and `$LOOP_PREV.<id>.output.a.b`. Do not flatten a producer's schema to reach a nested field.\n\n- Producer has `output_format`: every segment must be a declared `properties` entry; a declared field or parent that is absent or null resolves to `''` (declared-optional). An undeclared segment, at any depth, fails `archon validate workflows` when the producer's schema is known at load, and fails the consumer otherwise (typo protection).\n- Schemaless producer (no `output_format` on the producing node): the output must be a JSON object containing every key on the path, with an object at each intermediate step — anything else (non-JSON output, missing key, a scalar or array mid-path) fails the consumer.\n- Producer skipped or pending: fails the consumer — guard the reference with `when:` or a permissive `trigger_rule`.\n\nValues: strings pass through; numbers/booleans stringify; objects/arrays are JSON-stringified.\n\nFor `workflow:` results, these field rules use the child's `returns:` node's\nschema, whose declared paths travel with the value and survive cold resume. The\ncaller cannot add or narrow the contract. An `include:` alias uses its selected\nproducer directly after flattening.\n\n### Artifact pointers\n\nReturn a small JSON value containing a pointer when a machine consumer needs a\nfile result. The reserved shape is:\n\n```json\n{\"type\":\"archon_artifact\",\"run_id\":\"<producing run id>\",\"path\":\"review/report.md\"}\n```\n\nUse the actual `WORKFLOW_ID` value for `run_id`, not the literal variable name.\nThe producer must first write the file under its `$ARTIFACTS_DIR`. Sibling keys\non the pointer are allowed. Before persisting the result, the engine validates\ntagged pointers against the producing run: its own run id, a nonempty relative\npath with no `..` segment or NUL, lexical containment, and an existing regular\nfile. Absolute/escaping paths, directories, missing files, and another run's id\nfail the producer.\n\nA `workflow:` parent and fan-out aggregate relay pointers unchanged without\nrevalidating them against the parent. Events and resume retain the run id and\nrelative path. The engine does not expand the pointer into an absolute path,\nread its contents into a prompt, or provide an in-workflow resolver. For prompt\nhandoffs inside the producing run, keep using `$ARTIFACTS_DIR/<path>`.\n\nThe reader owns authorization, run reachability, and realpath containment at\nread time. Producer validation is not a read-side symlink guarantee; the artifact\nAPI's outstanding realpath check is tracked in #3160. Full semantics live in\nthe repository's `reference/variables.md` → Artifact pointers in a result.\n\n<!-- File: .claude/skills/archon-cli/operate/managing-runs.md (Tokens: ~4269 | Category: anthropic-skill) -->\n---\nname: archon-cli-manage-run\ndescription: Inspect, control, and resolve Archon workflow runs: list/get/status, approve, reject, respond, cancel vs abandon, resume. Read when a run is paused, failed, or the user asks about one.\n---\n\n# Managing runs\n\nEvery run-control verb, what it actually does, and the gate semantics that are\neasy to get wrong. Project listings scope by cwd; full run IDs remain globally\naddressable.\n\n## Output contract\n\n- `--json` → one clean JSON object on stdout; logs suppressed. Use when parsing.\n- No flag → human-readable text; diagnostics on stderr.\n\n## Overriding models for one run\n\nWhen the user asks to run on different models for a specific run, rebind the\ntier keywords or aliases at launch — the persistent config stays untouched:\n\n```bash\n# Rebind one or more tiers / @aliases for this run only; repeat per binding\narchon workflow run archon-ship --branch fix/x \"...\" \\\n  --model large=pi/minimax/minimax-m3 \\\n  --model small=openai/gpt-5-mini\n```\n\nSpec format: `<tier|@alias>=<provider>/<model-ref>` where `<model-ref>` is what\nthe named provider expects (for Pi backends that is itself a `<vendor>/<model>`\nref, e.g. `pi/minimax/minimax-m3`). An unknown tier/alias errors with the list\nof defined names.\n\nVerify before dispatching real work:\n\n```bash\n# dry-run prints each node's resolved provider/model without AI spend\narchon workflow run <workflow> \"test\" --dry-run --model large=...   # see 'runs on:' lines\n```\n\nFor reusable alternate setups, prefer a config layer instead of long flag lists\n(see `../setup/setup-and-config.md`, \"Alternate config layers\").\n\n## Verbs\n\n| Goal | Command |\n|---|---|\n| List recent runs (this project) | `archon workflow runs --json` |\n| Across all projects | `archon workflow runs --all --json` |\n| Filter by status / cap rows | `archon workflow runs --status running --limit 50 --json` |\n| One run's status/error | `archon workflow get <run-id> --json` |\n| One run with per-node detail | `archon workflow get <run-id> --verbose --json` |\n| One run with raw event rows | `archon workflow get <run-id> --verbose --events --json` |\n| List a run's artifact files | `archon workflow artifacts <run-id> --json` |\n| Read one artifact | `archon workflow artifacts <run-id> <path>` |\n| List registered projects (and their `--project` keys) | `archon project list --json` |\n| Active runs (this project) | `archon workflow status --json` |\n| Active runs (all projects) | `archon workflow status --all --json` |\n| Block until the run ends or needs attention | `archon workflow wait <run-id> --json` |\n| Every run that needs someone (this project / all) | `archon workflow attention --json [--all] [--label k=v]` |\n| The queue item one run stands for | `archon workflow attention <run-id> --json` |\n| Every change in the queue, as it happens | `archon workflow attention --watch --json [--all] [--label k=v] [--after <cursor>]` |\n| Classify an item without answering it | `archon workflow attention label <run-id> --occurrence <id> --set k=v --json` |\n| Resolve a gate with any declared decision | `archon workflow respond <run-id> <decision> [text]` |\n| Answer an attention wait in its form | `archon workflow answer <run-id> --occurrence <id> [--choose <id>]... [--text <t>]` |\n| Approve (default vocabulary) | `archon workflow approve <run-id> [text]` |\n| Reject (default vocabulary) | `archon workflow reject <run-id> \"<reason>\"` |\n| End a stalled turn on a running run, keep the run | `archon workflow end-turn <run-id>` |\n| Stop a running run (its live owner first) | `archon workflow cancel <run-id>` |\n| Mark paused/failed/orphaned run cancelled | `archon workflow abandon <run-id>` |\n| Resume failed/paused from completed nodes | `archon workflow resume <run-id>` |\n\n## Answering a decision well\n\nAn attention item is a question a run cannot settle. Most are written to be answered\nby whoever is on top of the runs, which is often you, not the user. Answer the ones\nthe user's standing direction already decides; bring the user only the ones that are\nunclear, risky, or outside what they have told you.\n\n1. Read the whole item: what is being decided, why it matters, the recommendation\n   with its evidence and what would change it, the choices and what each does, and\n   what happens if nobody answers. Open the evidence links before agreeing.\n2. Choose one of the declared choices. When none fits and the item offers `other`,\n   choose it and say what to do instead.\n3. Give a reason in the text. Workflows record the answer's text with the decision,\n   and some write it down as a ruling: the SDLC build queue and merge items owned by\n   a build add it to the issue body as a dated \"Current scope\" section that later\n   rounds work from. Enrolled own PRs keep the answer on the work item's merge\n   decision entry and write no issue section. A bare choice teaches\n   the next round nothing.\n4. Answer detached, so the continuation does not run in your shell:\n\n   ```bash\n   archon workflow respond <run-id> <choice> \"<reason>\" --detach\n   ```\n\n5. Pass `--occurrence <id>` when you answer from an item you read earlier: the\n   `occurrence` on its stream line, or `attention.occurrence` from `workflow wait\n   --json`. If the run has moved to a different pause, the answer is refused and the\n   error names the current occurrence, instead of landing on a question you never\n   read.\n\n## The approve/resume two-step\n\nWith `--json`, `approve`/`reject`/`respond` **record the decision and stop** —\nthis command does not execute the run (streaming output would corrupt the JSON).\nThe answered gate is a due continuation: a running `archon serve`, or\n`archon workflow wake [--watch]`, continues it without anyone resuming by hand.\nContainer runs (including a container write-back gate) are the exception: the\nhosts cannot continue them, so run `archon workflow resume <run-id>` yourself.\nTo record AND continue in this process:\n\n```bash\narchon workflow approve <run-id> \"ship it\"   # no --json: records + auto-resumes; background task!\n```\n\nA `wait: attention` pause (`attention.kind: \"attention\"`) is answered in the `form` it\nreports, with one of the `options` ids it lists:\n\n```bash\narchon workflow answer <run-id> --occurrence <id> --text \"done\" --json            # message\narchon workflow answer <run-id> --occurrence <id> --choose use-dev --json         # approval/select\narchon workflow answer <run-id> --occurrence <id> --choose a --choose b --json    # multi_select\n```\n\n`approve`/`reject`/`respond <option>` are shorthand for the same answer. In the\n`approval` form `reject` is an answer and does not cancel the run. Like a gate answer,\nthe answered wait is a due continuation. Answer only what the user actually decided; an\nundeclared option is refused. A `legacy:` occurrence is a wait paused before answers\nexisted: it takes no answer, so `workflow resume` it once its action is done.\n\nTo work the whole queue instead of one run, `archon workflow attention --json` lists\nevery item that needs someone, each with its `kind`, `occurrence`, `form`, `options`,\n`labels`, typed `project` and pointers to its transcript and artifacts. Answer an item\nby its `runId` and `occurrence` exactly as above. Labels are the user's routing data:\n`--label ci=other` selects one class, and `attention label` writes a classification\nwithout answering. Archon never routes by them.\n\nAn item of kind `usage_limit` asks nothing. Its run hit a provider usage limit that\nreported a reset time, and waits until then. A node that failed only downstream of the\nlimited one, such as an `all_done` node reading its output, does not turn the wait into a\nfailure. At the reset, a continuation host (`archon serve`, `archon workflow wake`, or\nthe CLI process that started the run) runs the failed nodes again. Cancel or abandon the\nrun to stop it.\n\nA workflow can ask several independent questions at once by fanning out one child run\nper question. Each child is its own item, with its own `runId` and `occurrence`, and\nits `fanOut.parentRunId` names the parent. `attention <parent-run-id>` returns only the\nfirst open child, so list the queue to find them all, and answer each child by its own\n`runId`. Answers can arrive in any order; the parent stays paused until every child\nfinishes.\n\n`workflow wait` follows one run whose id you hold. To follow every run in scope,\nincluding runs started later by triggers, schedules or other sessions, run\n`archon workflow attention --watch --json --all` as a background task. It prints one\nJSON line per change: each `attention` line is an item that needs attention, carrying\nthe same item the list returns, so treat it as a wake and answer by its `runId` and\n`occurrence`. `resolved` means it no longer does, and `terminal` means a run ended. The\nlines before `ready` are the whole open set. After a restart, `--after <cursor>`\nreplays what came after that cursor. A cursor names a feed event, not a line, and one\nevent can emit two lines with the same cursor (a `resolved` and the `attention` that\nreplaces it, or a `resolved` and a `terminal`). `--after` skips the whole event, so\ncheckpoint on a `bookmark` line you handled, or on the cursor before an event you\nhandled only partly, and accept duplicate lines on replay. Exit `4` means the store\ncannot read the stream's cursor: rerun without `--after`.\n\nOr deliberately in two steps:\n\n```bash\narchon workflow approve <run-id> \"ship it\" --json   # recorded, resumable: true\narchon workflow resume <run-id>                     # executes; background task\n```\n\nTo let the continuation own a background process, add `--detach` to\n`approve`/`reject`/`respond`/`resume`. The parent validates the run and returns an\nack with `continues: true`; the detached child records the decision and continues\nwithout `--json`. Detached `resume` acknowledges after engine acceptance clears the\nold pause, so immediately follow it with `workflow wait`. A new pause is still\nreported. If acceptance is unconfirmed after 60 seconds, inspect the run before\nretrying; the child may still continue.\n\n## Deprecated interactive-loop gates: comment or no comment is a decision\n\nRead the gate state first:\n\n```bash\narchon workflow get <run-id> --json | jq .metadata.approval.completionSignaled\n```\n\n- `true` and you approve **without** a comment → accept & complete: the node\n  finalizes from its already-computed output, no re-run.\n- You approve **with** a comment → another full iteration runs using your text\n  as feedback.\n- Reject always needs a reason. Gates with authored `decisions:` expose it as\n  `$gate.output.text`; only deprecated `approval.on_reject` prompts receive\n  `$REJECTION_REASON`. At an attention wait, reject is an answer\n  (`decision: reject`) and the run continues.\n\nChoose deliberately after reading what the gate produced — not by reflex.\n\n## Cancel vs abandon\n\n- `cancel` stops a running run. When another process owns it, that process is\n  stopped and its tree confirmed gone before `cancelled` is recorded. A run the\n  cancelling server executes itself, and a `workflow:` sub-run with no owner of\n  its own (it runs inside its root), is marked `cancelled` and stops at its\n  executor's next status check. Otherwise it refuses and changes nothing when no\n  owner answers. Use this to kill real work. Cancel still ends the run, and prints a\n  warning (`cleanupWarnings` in `--json`), when a process outside the run's process\n  group still holds a node's output, and on macOS and Linux when a host `bash:` or\n  `script:` command started or completed, because it may have left background work\n  running. The warning confirms neither a survivor nor cleanup, and Archon does not\n  signal or name such a process. Find it with `lsof` on the run's checkout, check whose\n  it is, and stop it before you reuse or remove the checkout.\n- `abandon` is for paused or failed runs, or a \"running\" row whose owner is gone\n  (crashed host). If an owner still answers on this host, it stops it first, like\n  `cancel`. If none answers, it records `cancelled` and prints the host and pid the\n  run recorded; when that host is another machine, check there before abandoning.\n  It also removes the worktree the run created, uncommitted files included, and keeps\n  the branch. Commit or copy anything you need from the worktree first. When stopping\n  the owner produced one of cancel's warnings, abandon keeps the checkout for you to\n  inspect instead.\n\n## A stalled turn: end-turn, cancel or resume\n\nAn agent or loop node can sit open on a provider tool call or reported background\ncommand that never finishes, such as a `while true; do ...; sleep 30; done` poll. The\nidle watchdog does not fire while that work is open. After\n`workflows.toolCallAttentionMs` without progress (30 minutes by default), the run raises\n`stalled_tool_calls` attention. The run stays `running`, so it is not an attention-queue\nitem and the `--watch` stream does not report it. `workflow wait <run-id>`\nreturns it as `attention.kind: \"stalled_tool_calls\"`, and `workflow status` prints it\nunder the run.\n\n- **`archon workflow end-turn <run-id>`** when the stalled work is done or useless and\n  the rest of the run is worth keeping. It records a request; the live owner checks\n  about every ten seconds and ends that turn if the same stall is still there. The\n  attempt fails as `timeout`, and the node's retry policy decides what happens next.\n  By default that is a fresh attempt, which can stall the same way. Exit `0` means the\n  request was recorded, not acted on: follow with `workflow wait <run-id>`. It refuses\n  a run that is not `running` or has nothing stalled.\n- **`cancel`** when the whole run should stop.\n- **`resume`** refuses a running run. Use it once the run has failed or paused, for\n  example after an ended turn exhausts its retries.\n\nA silent turn with no open tool or background work is a different case: its node's\n`idle_timeout` fails it as before, with no request needed.\n\n## Respond: gates beyond approve/reject\n\nSome gates declare decisions beyond the default pair (`respond <run-id> <decision>\n[text]`). Read the run's metadata to see which decisions its gates declare before\nguessing one.\n\n## Judging a finished run\n\nTerminal ≠ good. The three JSON modes expose different data:\n\n| Command | Read |\n|---|---|\n| `get <id> --json` | top-level normalized `outcome` (`succeeded`/`failed`) and `leave_behind.artifactFiles` |\n| `get <id> --verbose --json` | ordered `nodes` summaries and parse warnings |\n| `get <id> --verbose --events --json` | raw `events` rows |\n\n`outcome_field` selects a boolean from the return node, but the run stores only\nthe normalized top-level outcome. It does not expose the authored field name\n(`green`, `ready`, or `rooted`) as another top-level property. Locate the report\nthrough `leave_behind.artifactFiles`, then read the artifact before reporting to\nthe user.\n\nA completed `workflow:` fan-out node is not completed child work: under the default\n`join: all_done` it completes once every child is terminal, whatever each child's\nresult. When some slots did not complete, `workflow get <run-id>` prints a\n`Fan-out work:` line with the tally and those slots' outcomes. The full per-slot record (`completed`, `failed`,\n`not_started`, or a `cancelled_*` outcome naming who cancelled it) is\n`terminal_record.nodes[].fan_out_outcome` with `--json`.\n\nReport outcomes with their evidence: \"completed, review verdict ready:false —\n2 findings remain, report at <path>\" beats \"done\".\n\n## Discoveries: the SDLC pack's out-of-scope findings\n\nA convention of the bundled SDLC workflows (`archon-ship`, `archon-deliver`,\n`archon-review`, `archon-upkeep`), not of workflows in general. Their review lenses\nrecord proven findings that fall outside the run's accepted scope as discovery records\ninstead of blocking findings: raw per-producer files at\n`<artifacts>/discoveries/*.json` (records of `title`, `claim`, `evidence`, `relation:\nunrelated | scope_conflict | note`, `source_node`), consolidated by the review into\nthe verdict's `discoveries` records and a readable `discoveries.md`.\n\nThe run that found them files nothing. Its terminal report lists them, and\n`archon-discoveries` later decides every recorded discovery across finished runs:\nfile an issue with a full brief, track it on the open issue that already covers it,\ndrop it with a reason, or ask a maintainer. Run that drain rather than filing\ndiscoveries by hand (see `queues.md`, \"Discoveries drain\").\n\nWhen the user asks about one run's discoveries before a drain has run:\n\n1. Read the report's Discoveries section and, when it names any, open\n   `discoveries.md` (find it through `leave_behind.artifactFiles`).\n2. On a failed run, also check the raw `discoveries/*.json` records: consolidation\n   runs late, so a run that died mid-flight can hold records no report mentions.\n3. Tell the user what the run found. Do not re-litigate the verdict from them:\n   `unrelated` and `note` records never affected the run's readiness.\n\n## When a run looks wrong\n\n- Paused unexpectedly → `get --verbose`; look at the failing node's error and the\n  gate metadata.\n- Failed mid-flight → fix the cause if you can name it, then `resume <run-id>`\n  (background task). Resume skips completed nodes.\n- A \"running\" row with nothing alive → verify the process is truly gone, then\n  `abandon`.\n- Logs live under `~/.archon/workspaces/<project>/logs/`; artifacts under\n  `.../artifacts/runs/<run-id>/`. `archon doctor` checks the installation itself.\n\n<!-- File: .claude/skills/archon-cli/operate/queues.md (Tokens: ~2994 | Category: anthropic-skill) -->\n---\nname: archon-cli-queues\ndescription: Drive the SDLC pack's queues by hand: intake, the build queue, the merge queue and the discoveries drain. Their inputs, what each run does, the decisions they raise and how to answer them. Read when launching or tending any of them.\n---\n\n# Driving the SDLC queues\n\nThe bundled SDLC pack turns issues into merged pull requests through four queue\nworkflows. Each run does one step and stops only for a decision it cannot make:\n\n| Workflow | One run |\n|---|---|\n| `archon-intake` | Records an issue event and triages the best waiting issue. The verdict becomes the item's stage and a route label. |\n| `archon-build-queue` | Claims the best waiting triaged item, or the one `number` names, and takes it to a pull request whose review says Ready, one round per run. |\n| `archon-merge-queue` | Claims the items the build queue finished and merges what policy allows, one at a time, through a gate. |\n| `archon-discoveries` | Decides the out-of-scope findings finished runs recorded: file, track, drop or ask. |\n\nThe pack's README (`.archon/workflows/sdlc/README.md`) is the full contract. This\nreference covers launching and tending the queues by hand.\n\n## Before the first run\n\n- `archon workflow run archon-forge-check` must pass in the checkout. It proves the\n  pack can read the repository through its forge source (`gh` by default).\n- `.archon/sdlc/policy/maintainers.json` on the default branch lists the forge logins\n  whose issues are triaged and whose review comments count. An entry can be an object,\n  `{ \"login\": \"<login>\", \"merge_queue\": \"own-prs\" }`, which also sends that\n  maintainer's own open pull requests through the merge queue, except one a work item\n  already names, such as a pull request the build queue delivered. Without the file\n  every author is an outsider: intake labels their issues `archon-needs-maintainer`\n  and runs no agent.\n- The forge account the pack's review runs post as must also be on\n  `maintainers.json`. The merge queue counts only a review comment a listed\n  maintainer wrote; with no trusted review, no verdict is Ready and nothing merges.\n- The merge gate requires green CI at the pull request's head. A repository with no\n  CI checks reads as `none`, not green, so nothing merges there.\n- Policy documents at `.archon/sdlc/policy/<topic>.md` add to the pack's defaults\n  rather than replacing them. Where a line in the project's file conflicts with the\n  default, the project's line wins.\n- The pack installs no trigger bindings. Nothing runs on its own until the operator\n  adds one. The README shows a binding per queue; until then, launch by hand.\n\n## Inputs\n\nEvery queue takes the repository as two inputs: `repo_host` (such as `github.com`)\nand `repo_path` (`<owner>/<repo>`). Launch from the repository's checkout, detached.\n\n```bash\nR=\"--input repo_host=github.com --input repo_path=<owner>/<repo>\"\n\narchon workflow run archon-intake $R --input number=<issue> --detach\narchon workflow run archon-build-queue $R --detach\narchon workflow run archon-merge-queue $R --detach\narchon workflow run archon-discoveries $R --detach\n```\n\nOptional inputs, with their defaults in each workflow's YAML:\n\n- `archon-intake`: `action` (`opened` by default; `edited`, `reopened`, or `added`\n  for a label), `label` and `label_actor` for a label event.\n- `archon-build-queue`: `number` (the issue to build; empty claims the first in\n  order), `retriage_as` (a maintainer login; set, a run that names no item also\n  re-triages blocked items that no longer look blocked) and `round_limit` (default\n  `3`), the rounds after which a run that wants another also judges whether the rounds\n  converge.\n- `archon-merge-queue`: `limit` (default `10` items claimed), `merge_method`\n  (`squash` or `merge`) and `retriage_as` (a maintainer login; set, an issue still\n  open after its pull request merged goes back to intake).\n- `archon-discoveries`: `since` and `until` (ISO-8601) and `limit` (default `20` runs\n  per pass).\n\nIntake, the merge queue and the discoveries drain never change their checkout. They\ncreate no worktree and run in the launch checkout as it stands, so launch them from a\ncheckout that tracks the default branch. The build queue changes its checkout, so each\nrun gets its own worktree on a run-named branch. A completed round removes that\nworktree when it is clean and every commit is on a remote, and keeps the branch;\notherwise the run names the path and what removing it would lose.\n\n## Intake\n\nRun intake once per issue event. For a new issue, `--input number=<issue>` is enough.\n\nAn edit or a plain rerun does not re-triage a maintainer's issue that was already\ntriaged. To re-triage one, add the `archon-triage` label on the forge and launch the\nlabel event, naming who added it:\n\n```bash\narchon workflow run archon-intake $R --input number=<issue> \\\n  --input action=added --input label=archon-triage --input label_actor=<maintainer-login> --detach\n```\n\n`label_actor` must be on `maintainers.json`. The same event with a listed maintainer\npromotes an outside author's issue.\n\nRead the result from the issue's labels:\n\n- `archon-ready`, `archon-plan` or `archon-investigate`: routed to the build queue.\n- `archon-needs-contract`: the body misses one of the six brief elements. Fix the\n  body, then re-triage.\n- `archon-blocked`: a prerequisite or a decision must land first. Re-triage once it\n  has.\n- `archon-close`: a duplicate or already delivered. Intake closes it when the triage\n  policy allows.\n- `archon-needs-maintainer`: an outside author's issue, waiting for promotion.\n\n## Build queue\n\nA run claims the waiting item that sorts first and works it for one round. To build\na chosen issue, name it:\n\n```bash\narchon workflow run archon-build-queue $R --input number=<issue> --detach\n```\n\nA named issue that cannot be claimed refuses the run before any agent, with the\nreason: it has no work item, another run holds it, or it does not wait for the build\nqueue. Launch one run per item you want worked at the same time; the launch's\n`--config` decides which provider builds it.\n\nA round ends one of three ways, recorded on the item:\n\n- `done`: the pull request's latest review says Ready at its head. The merge queue\n  takes it from there. A pull request already reviewed Ready at its head is marked\n  ready without another round.\n- `continue`: the issue already answers what stopped the round. The run starts the\n  item's next round at once, as a detached `archon-build-queue --input number=<n>`\n  run on the same run config and the same `retriage_as` and `round_limit`.\n  If that launch fails, the item waits for the next queue run.\n- `ask`: the run pauses as one attention item labelled `build: route`, `build: stop`\n  or `build: scope`.\n\nAn item the merge queue returns waits for a new build round, with the merge queue's\nbrief. A re-triage after the queue's last decision starts the item over.\n\nDo not resume a failed build-queue run once a later build-queue run has released its\nclaim. A run that names no item releases every finished run's claim, whichever item it\nthen picks. A run named for the same `number` releases it too; one named for a\ndifferent `number` leaves it alone. A resume skips the completed `facts` node that\nclaimed the item, so the resumed run works the whole round, then its `apply` refuses\nbecause the item is no longer claimed by it. Launch a fresh run with\n`--input number=<issue>` instead.\n\n## Merge queue\n\nA sweep claims every waiting item up to `limit` and merges what policy allows, one\nat a time, through a gate that requires a Ready verdict for the head, green CI there\nand a default branch whose CI is not red. A pull request that merges cleanly into the\ndefault branch merges, even when it is behind, so one sweep can land many. The queue\nnever pushes or waits for CI: a conflict goes back to the build queue, and the default\nbranch's CI on a merge is the next sweep's first fact. Claims keep two sweeps off the\nsame item.\n\nA verdict counts only at the head it reviewed: after a push, the pull request goes\nback to the build queue for a new round. A maintainer's own pull request\n(`merge_queue: \"own-prs\"`) is an own-PR work item with no build record to return to,\nso where another would go back, the sweep asks instead and offers no `return`.\n\nA pull request whose read fails twice in a row is parked with the read error until\nthe build queue readies the item again. A maintainer's own pull request has no build\nrecord, so nothing readies it again: once parked, it stays parked, and you merge it by\nhand.\n\nA sweep merges a clear pull request on its own. An unclear one, such as a very large\nchange, pauses as an attention item labelled `merge: pr` with the choices `merge`,\n`return`, `park` and `other`. A red default branch pauses as `merge: base` with the\none choice `hold`, and the gate refuses every merge until it is green. A run raises at\nmost one attention item: the red default branch first, otherwise the first pull\nrequest the sweep asked about. A later sweep decides the rest again.\n\nA `merge` answer approves the pull request at the head it asked about, and a sweep\nthe answer starts, on the answered run's `limit`, `merge_method` and\n`retriage_as`, merges it once the gate passes. The approval carries to a later head only when the sweep\njudges that everything since is merges of the default branch and their conflict\nresolutions, and the verdict is Ready at the later head. A `return` hands the item\nback to the build queue, whose next round starts at once on the build queue's own\ninput defaults. A `park` stops work on the item until the build queue readies it again.\n\nFor an own pull request, `park` and `other` stop queue work until a later sweep\nobserves a head different from the head asked about. At the same head it stays\nparked, even after green CI or a new review. The `other` text is recorded as the\nruling on the work item; it starts no build round.\n\n## Discoveries drain\n\nEach pass decides the records of up to `limit` finished runs, oldest first. Repeat\npasses until one finds nothing queued: its `decide` node is skipped\n(`archon workflow get <run-id> --verbose --json`). Stop early when a pass pauses with a\n`discoveries: question` item, and answer it first.\n\nIssues a pass files carry no labels. Run intake on each one to triage it.\n\n## Answering queue decisions\n\nQueue items follow one shape: what is being decided, why it matters, the\nrecommendation with its evidence and what would change it, the choices and what each\ndoes, and what happens if nobody answers. Answer them as `managing-runs.md`\n(\"Answering a decision well\") describes:\n\n```bash\narchon workflow respond <run-id> <choice> \"<reason>\" --detach\n```\n\nThe build queue and merge items owned by a build write the ruling into the issue\nbody as a dated \"Current scope\" section, and later rounds work from it. An enrolled\nown PR keeps the answer on its work item's merge decision entry; it writes no issue\nsection. Write the reason as a ruling a later round can act on: what is in scope,\nwhat is not, and why. An answer that\ncontinues an item (a build `continue`, a merge `merge` or `return`) starts the follow-up\nrun at once, on the run config the answered run started with, so you don't relaunch. A\nfollow-up in the same queue also keeps the answered run's input selections.\n\n- When the recommendation fits the user's standing direction, answer it yourself.\n- When it is unclear, risky or not decided by anything the user has said, bring it to\n  the user with the item's own summary.\n- `other` exists on every pull-request and build item. Use it when no declared choice\n  fits, and say exactly what to do instead.\n\n## Merging by hand\n\nA pull request the queues did not deliver has no work item, so the merge queue does\nnot take it, unless its author's `maintainers.json` entry sets\n`merge_queue: \"own-prs\"`. To merge one yourself:\n\n1. Read the review verdict and the open review threads, including automated\n   reviewers' inline comments.\n2. Merge at the head you read: `gh pr merge <n> --squash --match-head-commit <sha>`.\n   A push since your read makes the merge fail instead of landing unreviewed code.\n3. Update your checkout to the new default branch.\n\n<!-- File: .claude/skills/archon-cli/operate/running.md (Tokens: ~2599 | Category: anthropic-skill) -->\n# Running workflows\n\nHow to discover, select and launch Archon workflows against real work, and how to watch\nwhat you launched.\n\n## Discover what exists\n\n```bash\narchon workflow list                 # human-readable compact descriptions and errors\narchon workflow list --json          # compact descriptions + descriptionTruncated state\narchon workflow list <name> --full   # exact description for one candidate\narchon plugin add owner/repo     # install a workflow pack published on GitHub\n```\n\nThe live list is authoritative. Bundled workflows ship under the `archon-` prefix\n(e.g. `archon-ship`, `archon-review`, `archon-deliver`, `archon-investigate`,\n`archon-triage`, `archon-upkeep`); user-authored workflows may\nclaim any name and override a bundled one in that repo.\n\nIn JSON output, `descriptionTruncated: true` means `description` is a discovery preview,\nnot the complete routing contract. Human output marks the same state with `[truncated]`.\nUse the previews to identify plausible candidates, then run `archon workflow list <name>\n--full` for each candidate before choosing or launching. Map intent from those full\ndescriptions — never from memory of names you have seen elsewhere. If two names plausibly\nmatch, read both full descriptions, then say which you picked and why in one line.\n\n## The input is the contract\n\nEverything the run does is measured against its input — the message, the issue body, or the\ndocument it reads. Archon governs how the work happens; it cannot supply what the user\nactually wanted. This is the highest-leverage moment in the whole flow, and it is before any\nmoney is spent.\n\nCheck the input names all six. Solution steering is a seventh, optional, and last:\n\n1. **Problem to solve** — what is wrong today, concretely.\n2. **Why it is worth solving** — the cost of leaving it.\n3. **Why now** — what makes this the moment.\n4. **Desired outcome** — what is observably different afterwards.\n5. **Invariants** — what must stay true across any acceptable implementation.\n6. **Acceptance** — how completion is checked.\n\n**When something is missing, do not fix it silently and do not launch anyway.** Tell the user\nwhich of the six is absent, propose concrete wording, and let them decide. They own the\ncontract; your job is to notice it is thin before it costs them a run.\n\nA useful shape:\n\n> Before I launch: this brief states the problem and the outcome, but not the invariants or\n> acceptance. Without acceptance the run decides for itself when it is done. Suggest adding:\n> \"Acceptance: X passes, Y is covered by a test, Z is unchanged.\" Want me to run it with that,\n> or would you rather word it yourself?\n\nTwo failure modes worth naming, because they look like diligence:\n\n- **Naming a solution before the problem is settled.** It narrows the run to the first guess\n  and hides better answers. If the user supplied one, keep it, but make sure the problem is\n  stated too — a run that only knows the proposed fix cannot tell you it was the wrong fix.\n- **Dropping steering the user actually holds.** Optional does not mean unwanted. If they have\n  said anywhere in the conversation how they want this done — reuse a primitive, avoid a\n  dependency, migrate rather than rewrite — carry it into the input as explicit steering. An\n  unstated preference cannot be honoured, and the user discovers it only in the diff.\n- **Silence about uncertainty.** An assumption stated in the brief is something the run can\n  contradict. An assumption left out is one it will quietly inherit.\n\nIf the input is a GitHub issue, read the actual body before launching rather than trusting the\ntitle. Recommend edits to the issue itself when it is thin — the issue is the durable contract,\nand the next run against it inherits whatever you leave there.\n\n## Invocation\n\n```bash\n# Default: isolated worktree, detached, non-blocking\narchon workflow run <workflow> --branch <branch-name> \"<message>\" --detach\n\n# Start from a specific base instead of the current branch\narchon workflow run <workflow> --branch <name> --from <base> \"<message>\" --detach\n\n# Foreground (blocks the shell)\narchon workflow run <workflow> --branch <name> \"<message>\"\n\n# No isolation — only when the user explicitly asks to work on the checkout directly\narchon workflow run <workflow> --no-worktree \"<message>\"\n\n# First run for a registered non-git folder project (runs in place; no branch flags)\narchon workflow run <workflow> --folder \"<message>\"\n\n# From another clone of a registered repo: the run belongs to that project\narchon workflow run <workflow> --project <owner/repo> --branch <name> \"<message>\" --detach\n```\n\n`--project <id|name>` takes a name or ID from `archon project list`. The run uses the\nproject's identity, env, artifacts and logs, and never registers the clone you launch\nfrom. Workflow source and config come from the launch directory. A worktree is cut from\nthe registered checkout, so a branch that exists only in your clone is not there; add\n`--no-worktree` to execute in the launch directory instead. It cannot be combined with\n`--folder`, `--base-branch`, `--resume` or `--dry-run`.\n\nRules:\n\n1. **Detached is the default posture.** Workflows are long-running; `--detach`\n   returns immediately and the run appears in `archon workflow runs`. In your\n   harness, even foreground invocations belong in a background task.\n2. **For git projects, pass `--branch`** unless the user chose otherwise. Derive the name\n   from the domain of work — code: `fix/<topic-or-issue-N>`, `feat/<name>`,\n   `review/pr-N`; ops/process runs: e.g. `ops/<queue>-<item>`,\n   `upkeep/<dependency>`, `triage/<source>`. To run on\n   different models for one run, see `managing-runs.md` (\"Overriding models for one\n   run\").\n   A worktree created for `--branch` stays when the run ends; `archon isolation\n   cleanup --merged` removes it after its pull request merges. Without `--branch`, a\n   completed run removes the worktree it created on its own generated branch when the\n   checkout is clean and every commit is on a remote, keeping the branch. A kept\n   checkout is named in the run's output with what removing it would lose.\n   Folder projects run in place and reject `--branch`, `--from`, and `--base`.\n3. **One workflow per shell.** Multiple pieces of work = separate invocations with\n   separate branches; they cannot conflict because each gets its own worktree.\n4. **The message is the work order.** Write it as you would brief a competent\n   engineer: what outcome is wanted, where the context lives (issue number, plan\n   path), and any constraint the user actually stated. See\n   `../authoring/prompting-mistakes.md` before writing it.\n5. **Interactive workflows need a human at their gates.** Launch them in the\n   foreground. When they pause, read the rendered gate message and declared\n   decisions from `archon workflow get <run-id> --json` under\n   `metadata.approval`; assistant JSONL rows are AI transcripts, not the gate\n   contract. Resolve the gate per `managing-runs.md`. Continuation\n   actions on the paused run may use `--detach`.\n\n## Inputs\n\nA workflow that declares `inputs:` takes them as `--input <name>=<value>`, one flag\nper input. `archon workflow list <name> --full` shows the description; the workflow's\nYAML lists each input with its default and meaning. An input without a default is\nrequired, and an undeclared name is refused.\n\n```bash\narchon workflow run archon-intake \\\n  --input repo_host=github.com --input repo_path=<owner>/<repo> --input number=42 --detach\n```\n\nInputs and a message can be combined. Queue workflows such as the SDLC pack's take\ninputs only; `queues.md` lists them.\n\n## Monitoring\n\n```bash\narchon workflow attention --watch --json    # one stream: decisions and run ends, this project\narchon workflow attention --watch --json --all   # the same across the install\narchon workflow wait <run-id> --json        # block on one run until it ends or needs attention\narchon workflow runs --json                 # recent runs for this project\narchon workflow status --json               # active runs (running or paused) for this project\narchon workflow get <run-id> --json         # one run: status, outcome, artifacts\narchon workflow get <run-id> --verbose --json  # plus per-node detail\n```\n\nTerminal statuses: `completed`, `failed`, `cancelled`.\n\n**Watch with one stream.** Right after your first launch, run `archon workflow\nattention --watch --json` as a background task of your harness. It follows every run\nin scope, including runs started later by triggers, schedules or other sessions, and\nprints one JSON line per change:\n\n- `attention`: a run needs a decision. The line carries the whole item; answer it by\n  its `runId` and `occurrence` (see `managing-runs.md`).\n- `resolved`: the item no longer needs attention.\n- `terminal`: a run ended, with its `status` and `outcome`. Read its result.\n- `owner_lost`: a run says it is running but its process is gone.\n\nThe lines before `ready` are the current open set. Keep the `cursor` of the last line\nyou handled; after a restart, `--after <cursor>` replays what you missed.\n\n`workflow wait <run-id>` blocks on one run and suits one or two runs. Do not arm one\nwait per run for a batch: dozens of waiting processes contend for the database, and a\nsingle stream already wakes you per run. Never chain waits in one shell (`wait A &&\nwait B` sleeps on A while B pauses unattended).\n\n**Status is not verdict.** A `completed` run can carry a negative `outcome`. Read it\nwith `get --json`, find the report under `leave_behind.artifactFiles`, and use a\nseparate `get --verbose --json` call for node summaries. Read the report before\ntelling the user what the run concluded. For the bundled SDLC workflows the report\nmay list discoveries: findings outside the run's scope. The run records them but\nfiles nothing; `archon-discoveries` decides and files them (see `queues.md`).\n\n## Continuing finished work\n\n```bash\narchon workflow runs --open                                 # find the prior run id\narchon workflow run docs --adopt <run-id> \"Now also update the docs\"  # new run on the adopted worktree/branch\narchon complete fix/issue-42                                # remove worktree + local branch; keeps the remote branch\narchon isolation cleanup                                    # prune stale environments (7d)\narchon isolation cleanup --merged                           # prune merged ones now\n```\n\n## Resuming after failure\n\nA failed or paused run resumes from completed nodes:\n\n```bash\narchon workflow resume <run-id>\narchon workflow resume <run-id> --detach\n```\n\nDetached resume returns after engine acceptance, before completion. Your attention\nstream reports the run's next pause or end. If the\n60-second acceptance confirmation fails, inspect the run before retrying: the\nchild may still continue.\n\nThe ambient sequential session cursor is not reconstructed by a cold resume.\nExplicit `context: { resume: node-id }` ancestry is restored from the completed\nsource's saved handle, and a paused loop with `fresh_context: false` continues its\npre-pause session when the provider supports it. Use durable artifacts for every\nhandoff that must survive regardless of provider session availability.\n\n<!-- File: .claude/skills/archon-cli/operate/troubleshooting.md (Tokens: ~4002 | Category: anthropic-skill) -->\n# Troubleshooting Workflows\n\nWhere to look when a workflow fails, hangs, or does the wrong thing.\n\n## Log Locations\n\nWorkflow run logs are written as JSONL per run:\n\n```text\n~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl\n```\n\nEach line is a structured event. The discriminator is the `type` field. Values (see `packages/workflows/src/logger.ts` for the canonical list):\n\n| `type` | Meaning |\n|--------|---------|\n| `workflow_start` / `workflow_complete` / `workflow_error` | Run lifecycle |\n| `workflow_resume` | A resumed execution picked the run back up here. The first execution writes `workflow_start`; every resume writes one `workflow_resume` instead, so the rows after the n-th one belong to the run's (n+1)-th segment. Transcripts written before this row existed repeat `workflow_start` on resume |\n| `node_start` / `node_complete` / `node_error` / `node_skipped` | Node lifecycle |\n| `node_suspended` | A node paused the run; `content` names why (a gate kind such as `approval`, or `wait`) |\n| `gate_decision` | How a gate was resolved — `step` (the gate node), `decision` (`approved`, `rejected`, or an author-declared decision), and `content` (the operator's comment or rejection reason, as the run's `approval_received` event stores it: an approve without a comment records `Approved`, and the field is absent when the event stores neither). No actor is recorded. Written by the process that approved or rejected, which is often not the one running the workflow |\n| `assistant` | AI assistant message — has `content` field with the full AI output |\n| `tool` | SDK tool invocation — has `tool_name` and `tool_input` |\n| `exec_output` | What a `bash`/`script` node or `until_bash` probe printed — `stdout_tail`, `stderr_tail`, `exit_code` |\n| `watchdog_reset` | A streamed chunk renewed a node's idle watchdog — `chunk_type` only, never content. Renewals closer together than `WATCHDOG_RESET_BURST_GAP_MS` form a burst, recorded as its first and last renewal; `chunk_count` is the renewals since the node's previous record (absent on older transcripts, which wrote one row per renewal). A burst's last record can land after later rows, so order by `ts` |\n| `validation` | Historical compatibility only; current runs do not emit this type |\n\n> **Loop iteration lifecycle events are NOT in the JSONL file.** `loop_iteration_started` / `loop_iteration_completed` go through the workflow event emitter (WebSocket / `workflow_events` DB table) — query the DB or the Web UI dashboard for those. Per-iteration rows that DO reach the JSONL are keyed `<node-id>-iteration-<n>`: a loop's `node_complete` row, its `watchdog_reset` rows, and the `exec_output` row for each `until_bash` probe.\n\nFind the run ID from `archon workflow runs --status failed` (or `archon workflow runs` for the most recent run of any status; `workflow status` only shows active runs). Then:\n\n```bash\n# Last assistant message (what the AI said before failure)\njq -sr 'map(select(.type == \"assistant\") | .content) | last // empty' <log-file>\n\n# All error events (node failures + workflow-level failures)\njq 'select(.type == \"node_error\" or .type == \"workflow_error\")' <log-file>\n\n# What a specific node actually printed (evidence for \"did this really do X?\")\njq 'select(.type == \"exec_output\" and .step == \"<node-id>\")' <log-file>\n\n# Where the run was resumed, and how each gate was decided\njq 'select(.type == \"workflow_resume\" or .type == \"gate_decision\")' <log-file>\n\n# When a node's stream last renewed its watchdog, and with what chunk type\njq -s 'map(select(.type == \"watchdog_reset\" and .step == \"<node-id>\")) | sort_by(.ts) | last' <log-file>\n\n# Full event stream\ncat <log-file> | jq .\n```\n\nAdapter logs (Slack / Telegram / Web / GitHub) are emitted to stderr when `LOG_LEVEL=debug` is set on the server.\n\n## Artifact Locations\n\n```text\n~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/\n```\n\nInspect artifacts when a multi-node workflow produces wrong output. The failing node's upstream artifact is usually where the problem originated.\n\nNodes with `output_type:` also get engine-written sidecars under `runs/<run-id>/nodes/` (`<node-id>.md` + `<node-id>.meta.json`), and — for session-persisted workflows — mirrored copies under `artifacts/scopes/<workflow>/<scope>/nodes/` that survive across runs.\n\n```bash\nls ~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/\ncat ~/.archon/workspaces/<owner>/<repo>/artifacts/runs/<run-id>/issues/issue-42.md\n```\n\nArtifacts are **external** to the repo on purpose — they don't pollute git. They are kept until you remove them, except `.archon/scratch/` inside the run's directory, which holds throwaway checkouts and installs and is deleted once the run is completed, failed or cancelled and its nodes have stopped (for a container run, when its container is removed). A run whose nodes Archon cannot confirm stopped keeps its scratch.\n\n## Common Failure Modes\n\n### \"No base branch could be resolved\"\n\nA node references `$BASE_BRANCH`, but the run has no resolved base. Causes:\n- A git run where no `--base`, `worktree.baseBranch`, or codebase default was set and git detection failed. A shared-checkout child inherits this from its parent.\n- A folder project, which has no git base.\n- An isolated child that reused an existing branch without its original child run record. Its historical base is unknown, so config and parent flags cannot recover it.\n\n**Fix:**\n1. For a top-level git run, pass `--base <branch>` on `archon workflow run`, or set `worktree.baseBranch: main` (or `dev`, or whatever) in `.archon/config.yaml`. `--from` picks the starting point and does not set `$BASE_BRANCH`.\n2. For the reused-branch child, start a new parent run so the child cuts a new isolated branch with a known base.\n3. Or remove the `$BASE_BRANCH` reference if the node doesn't need it.\n\nA resumed run keeps its recorded base unless it is retargeted with `--base`. See [`$BASE_BRANCH` fail-fast](https://archon.diy/reference/variables/#base_branch-fail-fast).\n\n### \"Claude Code not found\" / \"Codex CLI binary not found\"\n\nCompiled-binary builds of Archon no longer embed Claude Code / Codex — you install them separately and Archon resolves the binary via env var or config.\n\n**Fix (Claude):**\n- Install: `curl -fsSL https://claude.ai/install.sh | bash` (or `npm install -g @anthropic-ai/claude-code`)\n- Set `CLAUDE_BIN_PATH=/path/to/claude` in `~/.archon/.env`, OR\n- Set `assistants.claude.claudeBinaryPath: /absolute/path` in `.archon/config.yaml`\n- Autodetect covers `$HOME/.local/bin/claude` (native installer) — no config needed if you used that path\n\n**Fix (Codex):**\n- Install: `npm install -g @openai/codex` (or platform-specific instructions)\n- Set `CODEX_BIN_PATH=/path/to/codex` or `assistants.codex.codexBinaryPath` in config\n- Autodetect covers the standard npm / Homebrew locations per platform\n\nSee [archon.diy/getting-started/installation/](https://archon.diy/getting-started/installation/) for full platform-specific install paths.\n\n### Workflow shows `running` for a long time but nothing happens\n\nFour possibilities:\n\n1. **The AI is actually working.** Check `~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl` — if you see recent `tool` or `assistant` events in the tail, it's fine. Wait.\n2. **The server crashed and left an orphan row.** Server startup no longer auto-fails orphaned `running` rows (per the \"Do not guess lifecycle ownership\" rule — `AGENTS.md`). Transition it manually:\n   - Any cancel (Web UI Cancel, `archon workflow cancel <run-id>`, chat `/workflow cancel [id]`) stops a live owner process before marking the row cancelled, and refuses when no owner answers — an orphan row gets that refusal, with the host and pid it recorded\n   - Verified orphan: `archon workflow abandon <run-id>` (or Abandon in the Web UI after the refused Cancel, or `/workflow abandon <id>` in chat) — marks the row cancelled\n3. **A node is past its `idle_timeout`.** The default is 30 minutes of complete silence (the timer resets on every streamed message — it's a deadlock detector, not a work limiter). Override with per-node `idle_timeout` (ms) if a node legitimately goes quiet for longer.\n4. **A tool call or background command never finished.** While the provider reports open work, `idle_timeout` does not fire. `archon workflow wait <run-id>` and `archon workflow status` show `stalled_tool_calls` attention once that work has made no progress for `workflows.toolCallAttentionMs` (30 minutes by default). `archon workflow end-turn <run-id>` ends that turn without cancelling the run; see `managing-runs.md`, \"A stalled turn\".\n\n### Workflow fails mid-way; how do I resume?\n\nFind the failed run, then resume it explicitly:\n\n```bash\narchon workflow runs\narchon workflow get <run-id>\narchon workflow resume <run-id>\n```\n\n`archon workflow resume <run-id>` targets that exact run and reuses its recorded working path/worktree. As a convenience, `archon workflow run my-workflow --resume` resumes the most recent resumable run for that workflow at the invocation cwd. A bare `archon workflow run my-workflow` starts a fresh run.\n\n**Session caveat:** A cold resume does not reconstruct the ambient sequential\n`context: shared` cursor. Explicit `context: { resume: node-id }` ancestry is\nrestored from saved run-scoped handles, and a deprecated interactive loop with\n`fresh_context: false` resumes its pre-pause session when the provider supports\nit. Artifact-based handoff remains the provider-independent path.\n\n### Paused run with no chat message\n\nA run is paused at a gate or an attention wait, but no message reached the conversation that started it.\n\n**Cause:** the platform could not deliver the pause notice. The run still waits. The failure is logged as `platform_message_send_failed` and recorded on the run as a `pause_notice_undelivered` event, which the console's run view shows. Answer the run by id: `archon workflow status`, then `approve`, `reject`, or `respond`.\n\n### `MCP server connection failed: <server>`\n\nArchon reports only the MCP servers a node declares with `mcp:`. Status for any other server goes to the debug log as `dag.mcp_server_status`. A declared server is required: on Claude and Codex the node fails as `misconfigured` before its first model call, with an error naming the server and its cause. Check the config JSON path, the server's `command` and `args`, and any env vars it references.\n\n### A node was skipped and I don't know why\n\nSkips carry a **reason** in the persisted `node_skipped` event (`archon workflow get <run-id> --verbose --json`, or the `workflow_events` table):\n\n| Reason | Meaning | Fix |\n|--------|---------|-----|\n| `trigger_rule` | Upstream states didn't satisfy the join rule (default `all_success` — a skipped upstream counts as not-success and cascades) | Use `one_success` / `none_failed_min_one_success` / `all_done` on merge nodes after `when:`-gated branches |\n| `when_condition` | The `when:` expression evaluated false | Expected behavior — check the upstream output it compared against |\n| `when_condition_parse_error` | The `when:` expression has invalid SYNTAX — fail-closed skip | Fix the expression (six operators, `&&`/`\\|\\|`, no parentheses) |\n| `prior_success` | Resume: the node completed in a prior run | Expected; use `always_run: true` to force re-execution |\n\n### Node FAILED with an `OutputRefError`-style message about `$node.output.field`\n\nNot a skip — strict field access failed loudly: the field isn't in the producer's `output_format` schema (typo), the schemaless producer's output isn't JSON / lacks the key, or the producer never ran. Fix the reference, add the field to the schema, or guard with `when:`/`trigger_rule`. (Only whole-text `$node.output` degrades silently to `''`.)\n\n### Node output is empty / a declared-optional field resolves to empty string\n\nCommon causes:\n\n1. Upstream node was **skipped** — whole-text `$node.output` is `''` for skipped producers.\n2. The field is declared in the producer's `output_format` but was absent/null in the actual output (declared-optional → `''` by design).\n3. Bash/script node printed to stderr, not stdout. Only stdout is captured.\n4. For script nodes, non-zero exit on a non-existent file / missing import fails the node. Check the run log for `node_error` entries.\n\n### A workflow-level field seems to have no effect\n\nInvalid values for optional workflow-level fields (`effort`, `sandbox`, `fallbackModel`, `betas`, `tags`, `worktree.enabled`, `mutates_checkout`) are **warn-and-drop**: the workflow still loads and runs, the field is discarded, and a warn-level `*_ignored` loader log is written (stderr for CLI commands, the server log for `archon serve`). AI-only fields on bash/script nodes, unknown keys, and deprecations are parse warnings instead. A key that belongs to another node type, such as `timeout:` on a prompt node, is a load error that names the node, the key and the node types that accept it. Run `archon validate workflows` or `archon workflow list` to see the warnings, or set `LOG_LEVEL=debug` to see them in the logs. Check both before assuming the field is broken.\n\n### Persisted session didn't restore (cold resume)\n\nWhen a node resumes a session that no longer exists, what happens depends on the provider. Pi starts a fresh session with a warning. Claude rejects the resume; when Archon confirms on the host that the transcript is gone and the rejection came before any output or tool call, it runs the node once more on a fresh session with the same warning. A second rejection, a rejection after output, a container run, or a named resume (`context: { resume }`) fails the node. Codex fails the node every time, so clear its saved session with `archon workflow reset-sessions <workflow>`. The fresh-session warning includes pointers to prior typed artifacts (`output_type` sidecars in the cross-run scope dir) so the prompt/agent can re-read lost context. If sessions are repeatedly cold, check server logs; clear broken state with `archon workflow reset-sessions <workflow>`.\n\n### `cannot fork a session` warning\n\nThe node's provider cannot fork a session (no `sessionFork` in the [provider capabilities](https://archon.diy/reference/provider-capabilities/)), so a `persist_session` node on it never continues an earlier run's session: every run starts fresh. `archon validate workflows` reports this when the provider is written in the workflow file. Once an earlier run has saved a session for this node and provider, each later run also posts this warning in chat and records a `node_session_not_continued` event naming the session it skipped. This is expected, not broken state, and `reset-sessions` does not change it. To carry context across runs, move the node to a provider that forks sessions, or have it write what the next run needs as an artifact and read that instead. If neither applies, remove `persist_session` from the node.\n\n## Useful Diagnostic Commands\n\n```bash\n# Environment sanity first: providers, DB, adapters (forge access: archon workflow run archon-forge-check)\narchon doctor\n\n# One run, any status, with raw events — the fastest \"why did node X skip/fail\"\narchon workflow get <run-id> --verbose --events --json | jq '.events[]'\n\n# Recent runs for this project, all statuses (\"did the review pass?\")\narchon workflow runs --json | jq '.runs[]'\n\n# Recent runs of any status (find a failed run's ID)\narchon workflow runs --status failed --limit 10\n\n# Active runs for this project (running / paused only — finished runs never appear here)\narchon workflow status --json | jq '.runs[]'\n\n# Active runs across all projects\narchon workflow status --all --json | jq '.runs[]'\n\n# Human-readable status of any active runs\narchon workflow status\n\n# Active worktrees and their last activity\narchon isolation list\n\n# Validate a specific workflow before running\narchon validate workflows my-workflow\n\n# Validate a specific command\narchon validate commands my-command\n\n# Dump the last 50 lines of a workflow's log\ntail -n 50 ~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl | jq .\n\n# Increase log verbosity (workflow run)\narchon workflow run my-workflow --verbose \"...\"\n\n# Increase server log verbosity\nLOG_LEVEL=debug bun run start\n```\n\n## Escalation: when nothing makes sense\n\n1. Run `archon version` and note the version.\n2. Run `archon validate workflows <name>` and capture the output.\n3. Grab the last ~50 lines of the run's JSONL log.\n4. Check the `CHANGELOG.md` for known issues / recent changes to the subsystem you're hitting.\n5. File an issue at https://github.com/coleam00/Archon/issues with version, validate output, log tail, and the YAML.\n\n<!-- File: .claude/skills/archon-cli/setup/setup-and-config.md (Tokens: ~2055 | Category: anthropic-skill) -->\n---\nname: archon-cli-setup-config\ndescription: Install Archon, verify with doctor, connect provider credentials, and change config.yaml (tiers, aliases, adapters). Read when setting up Archon or changing its configuration.\n---\n\n# Setup and config\n\nInstall Archon, verify it, and change its configuration. Run from the repo you\nwant to work on unless setting up globally.\n\n## First: is it already working?\n\n```bash\narchon --version      # CLI present?\narchon doctor         # installed providers, database, adapters\narchon doctor --full  # also probes the OpenCode runtime SDK\narchon workflow run archon-forge-check  # can the SDLC workflows reach the forge?\n```\n\nIf `doctor` is green, skip to Configuration. Only set up what is actually broken.\nFor the SDLC pack's queues, also read `../operate/queues.md` (\"Before the first run\").\n\n## Install\n\nPrerequisites: `git`, and Bun for source installs (a compiled binary needs only\nitself).\n\n```bash\n# Compiled binary; asks which providers to install\ncurl -fsSL https://archon.diy/install | bash\ncurl -fsSL https://archon.diy/install | bash -s -- --providers codex   # no question\n\n# Source install\ngit clone <archon-repo> && cd Archon\nbun install\ncd packages/cli && bun link && cd ../..\n\n# or run from a checkout without linking\nbun --cwd packages/cli src/cli.ts --help\n```\n\n## Providers (compiled binaries)\n\nA compiled binary ships without Claude, Codex and Pi; it runs only the ones\ninstalled. No provider is ever picked silently: a run needing a missing one\nfails before it starts and names the command.\n\n```bash\narchon provider add codex            # or: claude codex pi\narchon setup --providers codex           # same, then auth prompts\n```\n\nAn installed provider must match the CLI version: after upgrading Archon, rerun\n`archon provider add <ids>`. `archon doctor` shows what is installed and any\nstale provider. Source checkouts and Docker run all three from source; there\n`provider add` refuses.\n\n## Credentials — the setup wizard and its pieces\n\n```bash\narchon setup          # interactive wizard: providers + credentials + config + bundled skill\n```\n\nThe wizard composes these, which also work standalone:\n\n```bash\narchon ai list                      # which providers are connected\narchon ai key set anthropic        # API key (prompt/stdin) — anthropic, openai, ...\narchon ai login anthropic          # Claude Pro/Max subscription (Claude provider only)\narchon ai login openai             # ChatGPT/Codex subscription\narchon ai login github-copilot     # GitHub Copilot subscription\narchon auth github                  # GitHub identity via device flow\n```\n\nNotes:\n\n- Subscription logins cover Claude Pro/Max, ChatGPT/Codex, and GitHub Copilot.\n  API keys remain available through `ai key set`.\n- On multi-user installs each human connects their own credentials; runs execute\n  on the starter's keys. Solo installs just need one working provider.\n- `gh` must be authenticated (`gh auth status`) for any GitHub-touching workflow.\n\n## Config: two scopes, one schema\n\n| Scope | File | Wins |\n|---|---|---|\n| Repo | `<repo>/.archon/config.yaml` | over global |\n| Global/user | `~/.archon/config.yaml` | defaults |\n\nRead both before changing anything; change the narrowest scope that fits the ask.\n\nKey sections:\n\n```yaml\nassistants:\n  claude:\n    model: <default model for claude nodes>\n    # provider-specific options live here too\ntiers:                     # how much model a step deserves; keywords small/medium/large\n  large:\n    provider: claude\n    model: claude-<...>\n    effort: high\naliases:                   # @name shortcuts resolvable in workflows\n  \"@fast\":\n    provider: ...\n    model: ...\ndefaults:\n  loadDefaultWorkflows: true   # bundled archon-* pack\n  loadDefaultCommands: true\ndocs:\n  path: docs/                  # where $DOCS_DIR points\nenv:                           # per-project env vars injected into execution\nworktree:                      # repo scope only — see \"Files agents need in worktrees\"\n  baseBranch: main\n  copyFiles:\n    - .env\n```\n\n### Files agents need in worktrees\n\n`git worktree add` carries **tracked** files only. Everything gitignored is absent\nfrom a fresh worktree — `.env` above all — so an agent cannot start the project's\nserver, run integration tests, or reproduce anything reading local credentials\nfrom the checkout it was handed. Nothing errors; the agent just finds no config.\n\nIf a user asks why something works in their checkout but not in a run, check this\nfirst. The fix is one entry:\n\n```yaml\n# <repo>/.archon/config.yaml\nworktree:\n  copyFiles:\n    - .env\n```\n\nRules worth knowing before suggesting entries:\n\n- **Repo scope only.** `worktree` is read from `<repo>/.archon/config.yaml`.\n  Putting it in `~/.archon/config.yaml` parses and does nothing.\n- **Copy the real gitignored file, never a tracked template.** Suggesting\n  `.env.example` is wrong twice: a worktree already has it, and materialising a\n  placeholder `.env` makes a server start against fake credentials instead of\n  failing.\n- **Entries are plain paths.** Source and destination are always identical; there\n  is no rename syntax.\n- **Missing entries are skipped silently**, so listing an optional path is safe\n  and a contributor without the file is unaffected.\n- Nothing is copied unless listed. Workflows, commands, and scripts do **not**\n  need an entry — a run freezes its own source.\n\nOther common entries: `.vscode/`, `.claude/`, and local-only fixture or plans\ndirectories. Full semantics, including path-traversal rejection and the\n`worktree.path` interaction, are in `/reference/configuration/`.\n\nPrefer the typed commands over hand-editing when they exist — they validate:\n\n```bash\narchon ai tier set large claude claude-<model> --effort high\narchon ai tier list --json     # configured vs built-in defaults\narchon ai alias set @fast pi minimax/MiniMax-M3\narchon ai key set openai                         # masked prompt, or key via stdin\n```\n\nTier, alias, and default commands write install-wide settings unless you pass\n`--scope user`. Use the user scope for one person's preferences on a shared\ninstall; use the install scope for shared defaults.\n\nAfter edits, confirm with `archon doctor` and one cheap probe:\n`archon workflow list` should load with no errors.\n\n## Alternate config layers (per-run model setups)\n\nA user who wants a second model setup — say, a cheap free-model combo or a\npremium one — does not need to edit their main config. Create a **sparse YAML\nlayer** holding only the keys it overrides, and load it per run with `--config`:\n\n```yaml\n# e.g. .archon/config.free.yaml (operator-local; `.archon/config.*.yaml` is gitignored,\n# and `.archon/config.example.yaml` is the committed template to copy)\ntiers:\n  small:\n    provider: pi\n    model: minimax/MiniMax-M3\n  large:\n    provider: pi\n    model: minimax/MiniMax-M3\naliases:\n  \"@fast\":\n    provider: pi\n    model: minimax/MiniMax-M3\n```\n\nValid layer keys: `assistant` or `defaultAssistant`, `assistants`, `tiers`,\n`aliases`, `workflows`, `docs` (with `path`), and `env`. A tier or alias entry\nstores the Archon provider separately from the model. For `provider: pi`, the\nmodel is Pi's inner `<vendor>/<model>` reference, such as\n`minimax/MiniMax-M3`. The outer `pi/` appears only in a single-string CLI\nbinding such as `--model large=pi/minimax/MiniMax-M3`.\n\n```bash\narchon workflow run archon-ship --branch fix/x \"...\" --config .config.free.yaml\n```\n\nNotes:\n- The layer applies to that fresh run and its descendants only; nothing persists.\n- Mutually exclusive with resuming an existing run.\n- One-off, ad-hoc rebinding without a file: use `--model tier=provider/model`\n  instead — see `../operate/managing-runs.md`.\n\nTo help a user decide scope: persistent default → edit config.yaml (below);\na named alternate setup they will reuse → a config layer;\na one-time experiment → `--model` flags.\n\n## Adapters (Slack / Telegram / GitHub / Discord / Web)\n\nAdapters let platforms drive Archon remotely. Each configures via env vars and is\nverified by `doctor`. The web UI is the zero-config surface:\n\n```bash\narchon serve            # web console; downloads the server and UI on first run (default port 3090)\n```\n\nSet up a chat adapter only when the user actually asks to drive Archon from that\nplatform — do not proactively configure Slack/Telegram/Discord.\n\n## Project initialization for workflow authoring\n\nCreating `.archon/` content (workflows, commands, scripts) belongs to the authoring\ncapability: see `../authoring/authoring-workflows.md`. Setup only ensures the CLI works.\n\n<!-- File: .claude/skills/archon-cli/SKILL.md (Tokens: ~1569 | Category: anthropic-skill) -->\n---\nname: archon-cli\ndescription: |\n  Drive Archon through its CLI: run AI workflows on a repo, watch and answer the runs\n  that need a decision, drive the SDLC pack's queues (intake, build queue, merge queue,\n  discoveries), set up Archon or change its config, and author and validate workflows.\n  Use when the user says \"use archon\", \"run archon\", \"archon workflow\", \"fix issue #N\n  with archon\", \"have archon review this PR\", \"what's running / check run <id>\",\n  \"approve/reject/answer/cancel/resume that run\", \"run the build queue\", \"drain\n  discoveries\", \"set up archon\", \"configure archon\", \"change my archon config\",\n  \"create a workflow\", \"author a workflow\", or asks how to write a better Archon prompt.\n  NOT for: doing the coding work yourself. Archon delegates it to isolated runs.\nargument-hint: \"[workflow | run-id | intent]\"\n---\n\n# Archon CLI\n\nArchon runs multi-step AI workflows through the `archon` CLI. Git projects run in\nisolated worktrees by default; registered folder projects run in place. You sit on\ntop of the runs: you write the brief, launch, watch one stream, answer the decisions\nthat reach you, and read what each run left behind.\n\nRoute by intent:\n\n| The user wants to... | Read |\n|---|---|\n| Run a workflow on real work | `operate/running.md` |\n| Inspect, answer, cancel or resume runs | `operate/managing-runs.md` |\n| Drive the SDLC pack's queues | `operate/queues.md` |\n| Find out why a run failed or hangs | `operate/troubleshooting.md` |\n| Set up Archon or change config | `setup/setup-and-config.md` |\n| Author or change a workflow | `authoring/authoring-workflows.md` (then `node-reference.md` and `variables.md` in the same folder) |\n| Improve a node prompt or a run message | `authoring/prompting-mistakes.md` |\n\n- When the intent is clear (\"build me a workflow for X\", \"run archon-ship on issue\n  #42\"), go straight to the reference. Do not ask.\n- When it is ambiguous, ask which of these the user wants.\n- On a machine where `archon doctor` fails, start with `setup/setup-and-config.md`.\n\n## The operating loop\n\n1. **Discover.** `archon workflow list --json` gives compact descriptions. For each\n   plausible candidate, read the full description with `archon workflow list <name>\n   --full`. Choose from full descriptions, never from names you remember.\n2. **Check the brief.** The message, issue body or document the run reads is the\n   contract the run is measured against. It must state the problem, why it is worth\n   solving, why now, the desired outcome, the invariants and the acceptance. If any\n   is missing, say which, propose wording, and get the user's agreement before\n   launching. `operate/running.md` has the full check.\n3. **Launch detached.** Pass the work as a message, the workflow's declared inputs\n   with `--input`, or both:\n\n   ```bash\n   archon workflow run <workflow> --branch <branch-name> \"<the work>\" --detach\n   archon workflow run <workflow> --input <name>=<value> --input <name>=<value> --detach\n   ```\n\n4. **Watch once.** Run one stream as a background task of your harness, however many\n   runs you launched:\n\n   ```bash\n   archon workflow attention --watch --json        # this project; add --all for the install\n   ```\n\n   Each line is JSON. `attention` means a run needs a decision, `resolved` means it no\n   longer does, and `terminal` means a run ended. Act on the line, then keep reading.\n5. **Answer deliberately.** Read the item's question, its recommendation and the\n   evidence it links. Answer with a reason, detached:\n   `archon workflow respond <run-id> <decision> \"<reason>\" --detach`.\n   `operate/managing-runs.md` covers every form.\n6. **Read the result.** A completed run is not a verdict. `archon workflow get\n   <run-id> --json` gives the normalized `outcome` and `leave_behind.artifactFiles`.\n   Read the report before telling the user what the run concluded.\n\n## Hard rules\n\n- **Never launch on a thin brief.** A weak input does not produce a weak result. It\n  produces a confident, well-formed answer to the wrong question, at full price.\n- **Launch and answer with `--detach`.** A detached run starts its own session and\n  survives your shell. A foreground `respond` or `resume` continues the run inside\n  your shell, and closing the shell stops it.\n- **One watcher, not one per run.** One `workflow attention --watch` stream covers\n  every run in scope, including runs that triggers or other sessions start. A\n  `workflow wait <run-id>` per run is fine for one or two runs; with dozens it\n  contends for the database.\n- **Stop work only when you mean to end it.** `archon workflow cancel` stops the run\n  mid-node, and that node's provider session and unsaved work are lost. Let a run reach\n  its next pause or end when you only want to stop launching more. When one turn is\n  stuck on a tool or background command that never finishes, `archon workflow\n  end-turn <run-id>` ends that turn and keeps the run (`operate/managing-runs.md`,\n  \"A stalled turn\").\n- **Merge only what was reviewed.** When you merge a pull request by hand, pin the\n  merge to the reviewed head (`gh pr merge <n> --match-head-commit <sha>`), read the\n  open review threads first, and sync your checkout afterwards.\n- **Author agent-first.** In a workflow you write, an agent node with typed output\n  judges, scripts read facts and act at boundaries, and YAML only wires them.\n  `authoring/authoring-workflows.md` (\"Agent-first\") has the rule and an example.\n\n## Gotchas\n\n- The current directory selects the project for discovery, launches and project\n  listings. `workflow status`, `workflow runs` and `workflow attention` default to\n  that project; add `--all` for the whole install. For a git project, run from the\n  repo root. Register a non-git folder with `workflow run --folder`. A full run ID\n  works from any directory.\n- Attention items are paused runs. A running run stuck on a stalled tool call is\n  not an item: `workflow wait <run-id>` and `workflow status` show it.\n- `workflow status` and `workflow runs` set `scopeFallback: true` in JSON when an\n  unregistered project produced an install-wide result.\n- Prefer `--json` whenever you will parse output.\n- `archon --help` is authoritative. If a flag this skill names is rejected, trust the\n  CLI's help and tell the user about the drift.\n\n<!-- File: .claude/skills/docker-extend/SKILL.md (Tokens: ~1073 | Category: anthropic-skill) -->\n---\nname: docker-extend\ndescription: |\n  Use when: User wants to extend Docker with custom tools, personalize the Docker environment,\n  or set up user-specific Docker customization.\n  Triggers: 'extend docker', 'docker-extend', 'add tools to docker', 'customize docker',\n            'add my tools to the container', 'personalize docker setup', 'docker user setup',\n            'install tools in docker'.\n  Does: Interactively sets up Dockerfile.user and docker-compose.override.yml so users can\n  add personal tools to their Docker environment without affecting maintainer files or\n  committing user-specific config to git.\nargument-hint: '[optional: tools to install, e.g. \"vim ripgrep jq\"]'\n---\n\n# Docker Extend\n\nSets up personal Docker tool customization using the project's gitignored override pattern.\nYour `Dockerfile.user` and `docker-compose.override.yml` are yours — never committed to git.\n\n## Step 1: Detect Context\n\nDetermine if the user is in dev (source build) or deploy (GHCR) mode:\n\n!`if [ -f docker-compose.yml ] && grep -q 'build: \\.' docker-compose.yml 2>/dev/null; then echo \"DEV_MODE: Building from source (root docker-compose.yml)\"; elif [ -f deploy/docker-compose.yml ]; then echo \"DEPLOY_MODE: Using GHCR image (deploy/docker-compose.yml)\"; else echo \"UNKNOWN_MODE: No docker-compose.yml found in root or deploy/\"; fi`\n\n**Decision:**\n- `DEV_MODE` → work with `Dockerfile.user` + `docker-compose.override.yml` (repo root)\n- `DEPLOY_MODE` → work with `deploy/Dockerfile.user` + `deploy/docker-compose.override.yml`\n- `UNKNOWN_MODE` → ask the user which directory their `docker-compose.yml` lives in before proceeding\n\n## Step 2: Check Current State\n\n**DEV_MODE — check root files:**\n\n!`echo \"=== Dockerfile.user ===\" && (cat Dockerfile.user 2>/dev/null || echo \"(not found — will create from example)\") && echo \"\" && echo \"=== docker-compose.override.yml ===\" && (cat docker-compose.override.yml 2>/dev/null || echo \"(not found — will create from example)\")`\n\n**DEPLOY_MODE — check deploy/ files:**\n\n!`echo \"=== deploy/Dockerfile.user ===\" && (cat deploy/Dockerfile.user 2>/dev/null || echo \"(not found — will create from example)\") && echo \"\" && echo \"=== deploy/docker-compose.override.yml ===\" && (cat deploy/docker-compose.override.yml 2>/dev/null || echo \"(not found — will create from example)\")`\n\n**If both files already exist:** Skip to Step 4 (add tools).\n**If files are missing:** Proceed to Step 3 (copy from examples).\n**If example files are missing too:** Tell the user the project hasn't added the example files yet and to check with the maintainers or the docs.\n\n## Step 3: Copy Example Files\n\nRun only the copy commands for whichever files are missing. Do NOT overwrite existing files.\n\n**DEV_MODE:**\n\n```bash\n# Only if Dockerfile.user does not exist:\ncp Dockerfile.user.example Dockerfile.user\n\n# Only if docker-compose.override.yml does not exist:\ncp docker-compose.override.example.yml docker-compose.override.yml\n```\n\n**DEPLOY_MODE:**\n\n```bash\n# Only if deploy/Dockerfile.user does not exist:\ncp deploy/Dockerfile.user.example deploy/Dockerfile.user\n\n# Only if deploy/docker-compose.override.yml does not exist:\ncp deploy/docker-compose.override.example.yml deploy/docker-compose.override.yml\n```\n\nAfter running the copy commands, confirm to the user which files were created.\n\n## Step 4: Add Tools\n\nParse `$ARGUMENTS` for tool names (space-separated, e.g., `vim ripgrep jq`).\n\n**If tools were specified:** Edit the appropriate `Dockerfile.user` to add (or uncomment) the `RUN apt-get install` block with the requested packages. Use the Edit tool — do not rewrite the entire file.\n\nThe block to add/modify:\n```dockerfile\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    <tool1> \\\n    <tool2> \\\n && rm -rf /var/lib/apt/lists/*\n```\n\n**If no tools were specified:** Show the user the file and ask which tools they want to add. Then edit the file accordingly.\n\n## Step 5: Rebuild\n\nShow the user the rebuild command for their context. Do NOT run it automatically.\n\n**DEV_MODE:**\n```bash\ndocker compose build && docker compose up -d\n```\n\n**DEPLOY_MODE:**\n```bash\ncd deploy && docker compose build && docker compose up -d\n```\n\n## Summary\n\nTell the user:\n1. Which files were created (if any)\n2. Which tools were added to `Dockerfile.user` (if any)\n3. The rebuild command to run when ready\n4. A reminder that `Dockerfile.user` and `docker-compose.override.yml` are gitignored — they're personal and won't be committed\n\n<!-- File: .claude/skills/playwright-cli/references/element-attributes.md (Tokens: ~171 | Category: anthropic-skill) -->\n# Inspecting Element Attributes\n\nWhen the snapshot doesn't show an element's `id`, `class`, `data-*` attributes, or other DOM properties, use `eval` to inspect them.\n\n## Examples\n\n```bash\nplaywright-cli snapshot\n# snapshot shows a button as e7 but doesn't reveal its id or data attributes\n\n# get the element's id\nplaywright-cli eval \"el => el.id\" e7\n\n# get all CSS classes\nplaywright-cli eval \"el => el.className\" e7\n\n# get a specific attribute\nplaywright-cli eval \"el => el.getAttribute('data-testid')\" e7\nplaywright-cli eval \"el => el.getAttribute('aria-label')\" e7\n\n# get a computed style property\nplaywright-cli eval \"el => getComputedStyle(el).display\" e7\n```\n\n<!-- File: .claude/skills/playwright-cli/references/playwright-tests.md (Tokens: ~375 | Category: anthropic-skill) -->\n# Running Playwright Tests\n\nTo run Playwright tests, use the `npx playwright test` command, or a package manager script. To avoid opening the interactive html report, use `PLAYWRIGHT_HTML_OPEN=never` environment variable.\n\n```bash\n# Run all tests\nPLAYWRIGHT_HTML_OPEN=never npx playwright test\n\n# Run all tests through a custom npm script\nPLAYWRIGHT_HTML_OPEN=never npm run special-test-command\n```\n\n# Debugging Playwright Tests\n\nTo debug a failing Playwright test, run it with `--debug=cli` option. This command will pause the test at the start and print the debugging instructions.\n\n**IMPORTANT**: run the command in the background and check the output until \"Debugging Instructions\" is printed. Make sure to stop the command after you have finished.\n\nOnce instructions containing a session name are printed, use `playwright-cli` to attach the session and explore the page.\n\n```bash\n# Run the test\nPLAYWRIGHT_HTML_OPEN=never npx playwright test --debug=cli\n# ...\n# ... debugging instructions for \"tw-abcdef\" session ...\n# ...\n\n# Attach to the test\nplaywright-cli attach tw-abcdef\n```\n\nKeep the test running in the background while you explore and look for a fix.\nThe test is paused at the start, so you should step over or pause at a particular location\nwhere the problem is most likely to be.\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code.\nThis code appears in the output and can be copied directly into the test. Most of the time, a specific locator or an expectation should be updated, but it could also be a bug in the app. Use your judgement.\n\nAfter fixing the test, stop the background test run. Rerun to check that test passes.\n\n<!-- File: .claude/skills/playwright-cli/references/request-mocking.md (Tokens: ~549 | Category: anthropic-skill) -->\n# Request Mocking\n\nIntercept, mock, modify, and block network requests.\n\n## CLI Route Commands\n\n```bash\n# Mock with custom status\nplaywright-cli route \"**/*.jpg\" --status=404\n\n# Mock with JSON body\nplaywright-cli route \"**/api/users\" --body='[{\"id\":1,\"name\":\"Alice\"}]' --content-type=application/json\n\n# Mock with custom headers\nplaywright-cli route \"**/api/data\" --body='{\"ok\":true}' --header=\"X-Custom: value\"\n\n# Remove headers from requests\nplaywright-cli route \"**/*\" --remove-header=cookie,authorization\n\n# List active routes\nplaywright-cli route-list\n\n# Remove a route or all routes\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n## URL Patterns\n\n```\n**/api/users           - Exact path match\n**/api/*/details       - Wildcard in path\n**/*.{png,jpg,jpeg}    - Match file extensions\n**/search?q=*          - Match query parameters\n```\n\n## Advanced Mocking with run-code\n\nFor conditional responses, request body inspection, response modification, or delays:\n\n### Conditional Response Based on Request\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/login', route => {\n    const body = route.request().postDataJSON();\n    if (body.username === 'admin') {\n      route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) });\n    } else {\n      route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) });\n    }\n  });\n}\"\n```\n\n### Modify Real Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/user', async route => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.isPremium = true;\n    await route.fulfill({ response, json });\n  });\n}\"\n```\n\n### Simulate Network Failures\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/offline', route => route.abort('internetdisconnected'));\n}\"\n# Options: connectionrefused, timedout, connectionreset, internetdisconnected\n```\n\n### Delayed Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/slow', async route => {\n    await new Promise(r => setTimeout(r, 3000));\n    route.fulfill({ body: JSON.stringify({ data: 'loaded' }) });\n  });\n}\"\n```\n\n<!-- File: .claude/skills/playwright-cli/references/running-code.md (Tokens: ~1358 | Category: anthropic-skill) -->\n# Running Custom Playwright Code\n\nUse `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands.\n\n## Syntax\n\n```bash\nplaywright-cli run-code \"async page => {\n  // Your Playwright code here\n  // Access page.context() for browser context operations\n}\"\n```\n\nYou can also load the function from a file:\n\n```bash\nplaywright-cli run-code --filename=./my-script.js\n```\n\n\nThe code must be a single function expression, it is wrapped in `(...)` and evaluated.\nimport/export/require syntax is not supported.\n\n## Geolocation\n\n```bash\n# Grant geolocation permission and set location\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });\n}\"\n\n# Set location to London\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 });\n}\"\n\n# Clear geolocation override\nplaywright-cli run-code \"async page => {\n  await page.context().clearPermissions();\n}\"\n```\n\n## Permissions\n\n```bash\n# Grant multiple permissions\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions([\n    'geolocation',\n    'notifications',\n    'camera',\n    'microphone'\n  ]);\n}\"\n\n# Grant permissions for specific origin\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read'], {\n    origin: 'https://example.com'\n  });\n}\"\n```\n\n## Media Emulation\n\n```bash\n# Emulate dark color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'dark' });\n}\"\n\n# Emulate light color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'light' });\n}\"\n\n# Emulate reduced motion\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ reducedMotion: 'reduce' });\n}\"\n\n# Emulate print media\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ media: 'print' });\n}\"\n```\n\n## Wait Strategies\n\n```bash\n# Wait for network idle\nplaywright-cli run-code \"async page => {\n  await page.waitForLoadState('networkidle');\n}\"\n\n# Wait for specific element\nplaywright-cli run-code \"async page => {\n  await page.locator('.loading').waitFor({ state: 'hidden' });\n}\"\n\n# Wait for function to return true\nplaywright-cli run-code \"async page => {\n  await page.waitForFunction(() => window.appReady === true);\n}\"\n\n# Wait with timeout\nplaywright-cli run-code \"async page => {\n  await page.locator('.result').waitFor({ timeout: 10000 });\n}\"\n```\n\n## Frames and Iframes\n\n```bash\n# Work with iframe\nplaywright-cli run-code \"async page => {\n  const frame = page.locator('iframe#my-iframe').contentFrame();\n  await frame.locator('button').click();\n}\"\n\n# Get all frames\nplaywright-cli run-code \"async page => {\n  const frames = page.frames();\n  return frames.map(f => f.url());\n}\"\n```\n\n## File Downloads\n\n```bash\n# Handle file download\nplaywright-cli run-code \"async page => {\n  const downloadPromise = page.waitForEvent('download');\n  await page.getByRole('link', { name: 'Download' }).click();\n  const download = await downloadPromise;\n  await download.saveAs('./downloaded-file.pdf');\n  return download.suggestedFilename();\n}\"\n```\n\n## Clipboard\n\n```bash\n# Read clipboard (requires permission)\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read']);\n  return await page.evaluate(() => navigator.clipboard.readText());\n}\"\n\n# Write to clipboard\nplaywright-cli run-code \"async page => {\n  await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!');\n}\"\n```\n\n## Page Information\n\n```bash\n# Get page title\nplaywright-cli run-code \"async page => {\n  return await page.title();\n}\"\n\n# Get current URL\nplaywright-cli run-code \"async page => {\n  return page.url();\n}\"\n\n# Get page content\nplaywright-cli run-code \"async page => {\n  return await page.content();\n}\"\n\n# Get viewport size\nplaywright-cli run-code \"async page => {\n  return page.viewportSize();\n}\"\n```\n\n## JavaScript Execution\n\n```bash\n# Execute JavaScript and return result\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(() => {\n    return {\n      userAgent: navigator.userAgent,\n      language: navigator.language,\n      cookiesEnabled: navigator.cookieEnabled\n    };\n  });\n}\"\n\n# Pass arguments to evaluate\nplaywright-cli run-code \"async page => {\n  const multiplier = 5;\n  return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier);\n}\"\n```\n\n## Error Handling\n\n```bash\n# Try-catch in run-code\nplaywright-cli run-code \"async page => {\n  try {\n    await page.getByRole('button', { name: 'Submit' }).click({ timeout: 1000 });\n    return 'clicked';\n  } catch (e) {\n    return 'element not found';\n  }\n}\"\n```\n\n## Complex Workflows\n\n```bash\n# Login and save state\nplaywright-cli run-code \"async page => {\n  await page.goto('https://example.com/login');\n  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n  await page.getByRole('textbox', { name: 'Password' }).fill('secret');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await page.waitForURL('**/dashboard');\n  await page.context().storageState({ path: 'auth.json' });\n  return 'Login successful';\n}\"\n\n# Scrape data from multiple pages\nplaywright-cli run-code \"async page => {\n  const results = [];\n  for (let i = 1; i <= 3; i++) {\n    await page.goto(\\`https://example.com/page/\\${i}\\`);\n    const items = await page.locator('.item').allTextContents();\n    results.push(...items);\n  }\n  return results;\n}\"\n```\n\n<!-- File: .claude/skills/playwright-cli/references/session-management.md (Tokens: ~1337 | Category: anthropic-skill) -->\n# Browser Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Browser Sessions\n\nUse `-s` flag to isolate browser contexts:\n\n```bash\n# Browser 1: Authentication flow\nplaywright-cli -s=auth open https://app.example.com/login\n\n# Browser 2: Public browsing (separate cookies, storage)\nplaywright-cli -s=public open https://example.com\n\n# Commands are isolated by browser session\nplaywright-cli -s=auth fill e1 \"user@example.com\"\nplaywright-cli -s=public snapshot\n```\n\n## Browser Session Isolation Properties\n\nEach browser session has independent:\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Browser Session Commands\n\n```bash\n# List all browser sessions\nplaywright-cli list\n\n# Stop a browser session (close the browser)\nplaywright-cli close                # stop the default browser\nplaywright-cli -s=mysession close   # stop a named browser\n\n# Stop all browser sessions\nplaywright-cli close-all\n\n# Forcefully kill all daemon processes (for stale/zombie processes)\nplaywright-cli kill-all\n\n# Delete browser session user data (profile directory)\nplaywright-cli delete-data                # delete default browser data\nplaywright-cli -s=mysession delete-data   # delete named browser data\n```\n\n## Environment Variable\n\nSet a default browser session name via environment variable:\n\n```bash\nexport PLAYWRIGHT_CLI_SESSION=\"mysession\"\nplaywright-cli open example.com  # Uses \"mysession\" automatically\n```\n\n## Common Patterns\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all browsers\nplaywright-cli -s=site1 open https://site1.com &\nplaywright-cli -s=site2 open https://site2.com &\nplaywright-cli -s=site3 open https://site3.com &\nwait\n\n# Take snapshots from each\nplaywright-cli -s=site1 snapshot\nplaywright-cli -s=site2 snapshot\nplaywright-cli -s=site3 snapshot\n\n# Cleanup\nplaywright-cli close-all\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nplaywright-cli -s=variant-a open \"https://app.com?variant=a\"\nplaywright-cli -s=variant-b open \"https://app.com?variant=b\"\n\n# Compare\nplaywright-cli -s=variant-a screenshot\nplaywright-cli -s=variant-b screenshot\n```\n\n### Persistent Profile\n\nBy default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk:\n\n```bash\n# Use persistent profile (auto-generated location)\nplaywright-cli open https://example.com --persistent\n\n# Use persistent profile with custom directory\nplaywright-cli open https://example.com --profile=/path/to/profile\n```\n\n## Attaching to a Running Browser\n\nUse `attach` to connect to a browser that is already running, instead of launching a new one.\n\n### Attach by channel name\n\nConnect to a running Chrome or Edge instance by its channel name. The browser must have remote debugging enabled — navigate to `chrome://inspect/#remote-debugging` in the target browser and check \"Allow remote debugging for this browser instance\".\n\n```bash\n# Attach to Chrome\nplaywright-cli attach --cdp=chrome\n\n# Attach to Chrome Canary\nplaywright-cli attach --cdp=chrome-canary\n\n# Attach to Microsoft Edge\nplaywright-cli attach --cdp=msedge\n\n# Attach to Edge Dev\nplaywright-cli attach --cdp=msedge-dev\n```\n\nSupported channels: `chrome`, `chrome-beta`, `chrome-dev`, `chrome-canary`, `msedge`, `msedge-beta`, `msedge-dev`, `msedge-canary`.\n\nWhen `--session` is not provided, the session is named after the channel (e.g. `--cdp=msedge` creates a session called `msedge`), so parallel attaches to Chrome and Edge don't collide on `default`. Pass `--session=<name>` to override.\n\n### Attach via CDP endpoint\n\nConnect to a browser that exposes a Chrome DevTools Protocol endpoint:\n\n```bash\nplaywright-cli attach --cdp=http://localhost:9222\n```\n\n### Attach via browser extension\n\nConnect to a browser with the Playwright extension installed:\n\n```bash\nplaywright-cli attach --extension\n```\n\n### Detach\n\nTear down an attached session without affecting the external browser:\n\n```bash\n# Detach the default attached session\nplaywright-cli detach\n\n# Detach a specific attached session\nplaywright-cli -s=msedge detach\n```\n\n`detach` only works on sessions created via `attach`. For sessions created via `open`, use `close`.\n\n## Default Browser Session\n\nWhen `-s` is omitted, commands use the default browser session:\n\n```bash\n# These use the same default browser session\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli close  # Stops default browser\n```\n\n## Browser Session Configuration\n\nConfigure a browser session with specific settings when opening:\n\n```bash\n# Open with config file\nplaywright-cli open https://example.com --config=.playwright/my-cli.json\n\n# Open with specific browser\nplaywright-cli open https://example.com --browser=firefox\n\n# Open in headed mode\nplaywright-cli open https://example.com --headed\n\n# Open with persistent profile\nplaywright-cli open https://example.com --persistent\n```\n\n## Best Practices\n\n### 1. Name Browser Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nplaywright-cli -s=github-auth open https://github.com\nplaywright-cli -s=docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nplaywright-cli -s=s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Stop browsers when done\nplaywright-cli -s=auth close\nplaywright-cli -s=scrape close\n\n# Or stop all at once\nplaywright-cli close-all\n\n# If browsers become unresponsive or zombie processes remain\nplaywright-cli kill-all\n```\n\n### 3. Delete Stale Browser Data\n\n```bash\n# Remove old browser data to free disk space\nplaywright-cli -s=oldsession delete-data\n```\n\n<!-- File: .claude/skills/playwright-cli/references/storage-state.md (Tokens: ~1249 | Category: anthropic-skill) -->\n# Storage Management\n\nManage cookies, localStorage, sessionStorage, and browser storage state.\n\n## Storage State\n\nSave and restore complete browser state including cookies and storage.\n\n### Save Storage State\n\n```bash\n# Save to auto-generated filename (storage-state-{timestamp}.json)\nplaywright-cli state-save\n\n# Save to specific filename\nplaywright-cli state-save my-auth-state.json\n```\n\n### Restore Storage State\n\n```bash\n# Load storage state from file\nplaywright-cli state-load my-auth-state.json\n\n# Reload page to apply cookies\nplaywright-cli open https://example.com\n```\n\n### Storage State File Format\n\nThe saved file contains:\n\n```json\n{\n  \"cookies\": [\n    {\n      \"name\": \"session_id\",\n      \"value\": \"abc123\",\n      \"domain\": \"example.com\",\n      \"path\": \"/\",\n      \"expires\": 1893456000,\n      \"httpOnly\": true,\n      \"secure\": true,\n      \"sameSite\": \"Lax\"\n    }\n  ],\n  \"origins\": [\n    {\n      \"origin\": \"https://example.com\",\n      \"localStorage\": [\n        { \"name\": \"theme\", \"value\": \"dark\" },\n        { \"name\": \"user_id\", \"value\": \"12345\" }\n      ]\n    }\n  ]\n}\n```\n\n## Cookies\n\n### List All Cookies\n\n```bash\nplaywright-cli cookie-list\n```\n\n### Filter Cookies by Domain\n\n```bash\nplaywright-cli cookie-list --domain=example.com\n```\n\n### Filter Cookies by Path\n\n```bash\nplaywright-cli cookie-list --path=/api\n```\n\n### Get Specific Cookie\n\n```bash\nplaywright-cli cookie-get session_id\n```\n\n### Set a Cookie\n\n```bash\n# Basic cookie\nplaywright-cli cookie-set session abc123\n\n# Cookie with options\nplaywright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax\n\n# Cookie with expiration (Unix timestamp)\nplaywright-cli cookie-set remember_me token123 --expires=1893456000\n```\n\n### Delete a Cookie\n\n```bash\nplaywright-cli cookie-delete session_id\n```\n\n### Clear All Cookies\n\n```bash\nplaywright-cli cookie-clear\n```\n\n### Advanced: Multiple Cookies or Custom Options\n\nFor complex scenarios like adding multiple cookies at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.context().addCookies([\n    { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true },\n    { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' }\n  ]);\n}\"\n```\n\n## Local Storage\n\n### List All localStorage Items\n\n```bash\nplaywright-cli localstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli localstorage-get token\n```\n\n### Set Value\n\n```bash\nplaywright-cli localstorage-set theme dark\n```\n\n### Set JSON Value\n\n```bash\nplaywright-cli localstorage-set user_settings '{\"theme\":\"dark\",\"language\":\"en\"}'\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli localstorage-delete token\n```\n\n### Clear All localStorage\n\n```bash\nplaywright-cli localstorage-clear\n```\n\n### Advanced: Multiple Operations\n\nFor complex scenarios like setting multiple values at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    localStorage.setItem('token', 'jwt_abc123');\n    localStorage.setItem('user_id', '12345');\n    localStorage.setItem('expires_at', Date.now() + 3600000);\n  });\n}\"\n```\n\n## Session Storage\n\n### List All sessionStorage Items\n\n```bash\nplaywright-cli sessionstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli sessionstorage-get form_data\n```\n\n### Set Value\n\n```bash\nplaywright-cli sessionstorage-set step 3\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli sessionstorage-delete step\n```\n\n### Clear sessionStorage\n\n```bash\nplaywright-cli sessionstorage-clear\n```\n\n## IndexedDB\n\n### List Databases\n\n```bash\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(async () => {\n    const databases = await indexedDB.databases();\n    return databases;\n  });\n}\"\n```\n\n### Delete Database\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    indexedDB.deleteDatabase('myDatabase');\n  });\n}\"\n```\n\n## Common Patterns\n\n### Authentication State Reuse\n\n```bash\n# Step 1: Login and save state\nplaywright-cli open https://app.example.com/login\nplaywright-cli snapshot\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\n\n# Save the authenticated state\nplaywright-cli state-save auth.json\n\n# Step 2: Later, restore state and skip login\nplaywright-cli state-load auth.json\nplaywright-cli open https://app.example.com/dashboard\n# Already logged in!\n```\n\n### Save and Restore Roundtrip\n\n```bash\n# Set up authentication state\nplaywright-cli open https://example.com\nplaywright-cli eval \"() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }\"\n\n# Save state to file\nplaywright-cli state-save my-session.json\n\n# ... later, in a new session ...\n\n# Restore state\nplaywright-cli state-load my-session.json\nplaywright-cli open https://example.com\n# Cookies and localStorage are restored!\n```\n\n## Security Notes\n\n- Never commit storage state files containing auth tokens\n- Add `*.auth-state.json` to `.gitignore`\n- Delete state files after automation completes\n- Use environment variables for sensitive data\n- By default, sessions run in-memory mode which is safer for sensitive operations\n\n<!-- File: .claude/skills/playwright-cli/references/test-generation.md (Tokens: ~3792 | Category: anthropic-skill) -->\n# Test generation (plan → generate → heal)\n\nEnd-to-end workflow for authoring and maintaining Playwright tests with `playwright-cli`. Every `playwright-cli` action emits the equivalent Playwright TypeScript, and that generated code is the raw material for every test. The sections below can be used independently:\n\n- **How generation works** — the core mechanic everything else relies on: actions become TypeScript, plus how to add assertions.\n- **Plan** — explore the app, produce a spec file describing what to test.\n- **Generate** — turn a spec into Playwright test files. Update the spec if it's vague or stale.\n- **Heal** — diagnose failing tests, fix the code, reconcile the spec with reality.\n\nPlan / generate / heal lean on the same mechanic: run `npx playwright test --debug=cli` in the background, then `playwright-cli attach tw-XXXX` to drive the paused page interactively. See [playwright-tests.md](playwright-tests.md) for the debug/attach mechanics.\n\n---\n\n## 0. How generation works\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. This code appears in the output and can be copied directly into your test files.\n\n```bash\n# Start a session\nplaywright-cli open https://example.com/login\n\n# Take a snapshot to see elements\nplaywright-cli snapshot\n# Output shows: e1 [textbox \"Email\"], e2 [textbox \"Password\"], e3 [button \"Sign In\"]\n\n# Fill form fields - generates code automatically\nplaywright-cli fill e1 \"user@example.com\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n\nplaywright-cli fill e2 \"password123\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n\nplaywright-cli click e3\n# Ran Playwright code:\n# await page.getByRole('button', { name: 'Sign In' }).click();\n```\n\n### Building a test file\n\nCollect the generated code into a Playwright test:\n\n```typescript\nimport { test, expect } from '@playwright/test';\n\ntest('login flow', async ({ page }) => {\n  // Generated code from playwright-cli session:\n  await page.goto('https://example.com/login');\n  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n  await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n  await page.getByRole('button', { name: 'Sign In' }).click();\n\n  // Add assertions\n  await expect(page).toHaveURL(/.*dashboard/);\n});\n```\n\n### Use semantic locators\n\nThe generated code uses role-based locators when possible, which are more resilient:\n\n```typescript\n// Generated (good - semantic)\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Avoid (fragile - CSS selectors)\nawait page.locator('#submit-btn').click();\n```\n\n### Explore before recording\n\nTake snapshots to understand the page structure before recording actions:\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli snapshot\n# Review the element structure\nplaywright-cli click e5\n```\n\n### Add assertions manually\n\nGenerated code captures actions but not assertions. Add expectations in your test using one of the recommended matchers:\n\n- `toBeVisible()` — element is rendered and visible\n- `toHaveText(text)` — element text content matches\n- `toHaveValue(value) / toBeEmpty()` — input/select value matches\n- `toBeChecked() / toBeUnchecked()` — checkbox state matches\n- `toMatchAriaSnapshot(snapshot)` — page (or locator) matches a partial accessibility snapshot\n\nUse `playwright-cli generate-locator <target>` to produce the locator expression for the assertion, and the snapshot/eval commands to capture the expected value.\n\nWhen asserting text content, make sure that generated locator does not contain text from the element itself. `getByTestId()` or `getByLabel()` usually work well with asserting text. When locator is text-based, prefer `toBeVisible()` instead.\n\nSnapshot to be matched does not have to contain all the information - only capture what's necessary for the assertion. You can use regular expressions for unstable values.\n\n```bash\n# Get a stable locator for an element ref to use in the assertion\nplaywright-cli --raw generate-locator e5\n# getByRole('button', { name: 'Submit' })\n\n# Capture expected text content for toHaveText\nplaywright-cli --raw eval \"el => el.textContent\" e5\n\n# Capture expected input value for toHaveValue/toBeEmpty\nplaywright-cli --raw eval \"el => el.value\" e5\n\n# Capture expected aria snapshot for toMatchAriaSnapshot/toBeChecked\n# (whole page, or use a ref to scope to a region)\nplaywright-cli --raw snapshot\nplaywright-cli --raw snapshot e5\n```\n\n```typescript\n// Generated action\nawait page.getByRole('button', { name: 'Submit' }).click();\n\n// Manual assertions using the outputs above:\nawait expect(page.getByRole('alert', { name: 'Success' })).toBeVisible();\nawait expect(page.getByTestId('main-header')).toHaveText('Welcome, user');\nawait expect(page.getByRole('textbox', { name: 'Email' })).toHaveValue('user@example.com');\nawait expect(page.getByRole('checkbox', { name: 'Enable notifications' })).toBeChecked();\n\n// toMatchAriaSnapshot on the whole page, finds a matching region\nawait expect(page).toMatchAriaSnapshot(`\n  - heading \"Welcome, user\"\n  - link /\\\\d+ new messages?/\n  - button \"Sign out\"\n`);\n\n// toMatchAriaSnapshot scoped to a region\nawait expect(page.getByRole('navigation')).toMatchAriaSnapshot(`\n  - link \"Home\"\n  - link /\\\\d+ new messages?/\n  - link \"Profile\"\n`);\n```\n\n---\n\n## 1. Planning\n\nGoal: produce a spec file (e.g. `specs/<feature>.plan.md`) that enumerates the scenarios to test. **Always** write the spec to a file.\n\n### 1.1 Prerequisite: workspace\n\nCheck the workspace has Playwright installed before anything else:\n\n```bash\n# Either of these confirms a workspace:\ntest -f playwright.config.ts || test -f playwright.config.js\nnpx --no-install playwright --version\n```\n\nIf there is no Playwright install, bootstrap one and let the user pick the defaults:\n\n```bash\nnpm init playwright@latest\n```\n\n### 1.2 Prerequisite: seed test\n\nA **seed test** is a minimal test that lands the page in the state every scenario starts from: navigation to the app, any required login, feature flags, etc. Scenarios assume a fresh start *after* the seed. `--debug=cli` pauses *inside* this test, so the seed is where every planning and generation session begins.\n\nMinimum viable seed:\n\n```ts\n// tests/seed.spec.ts\nimport { test } from '@playwright/test';\n\ntest('seed', async ({ page }) => {\n  await page.goto('https://example.com/');\n});\n```\n\nPreferred — push navigation into a fixture so scenario tests reuse it:\n\n```ts\n// tests/fixtures.ts\nimport { test as baseTest } from '@playwright/test';\nexport { expect } from '@playwright/test';\n\nexport const test = baseTest.extend({\n  page: async ({ page }, use) => {\n    await page.goto('https://example.com/');\n    await use(page);\n  },\n});\n```\n\n```ts\n// tests/seed.spec.ts\nimport { test } from './fixtures';\n\ntest('seed', async ({ page }) => {\n  // Fixture already navigates. This empty body tells agents where to start.\n});\n```\n\nIf no seed exists, create one that at least navigates to the app.\n\n### 1.3 Explore the app\n\nLaunch the app via the seed in the background and attach:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/seed.spec.ts --debug=cli\n# wait for \"Debugging Instructions\" and the session name tw-XXXX\nplaywright-cli attach tw-XXXX\n```\n\nResume so the seed runs, then probe the app:\n\n```bash\nplaywright-cli resume                   # resume so that seed test runs fully\nplaywright-cli snapshot                 # inventory of interactive elements\nplaywright-cli click e5                 # follow a flow\nplaywright-cli eval \"location.href\"     # read URL / state\nplaywright-cli show --annotate          # ask the user to point at something\n```\n\nMap out:\n\n- Interactive surfaces (forms, buttons, lists, filters, modals).\n- Primary user journeys end-to-end.\n- Edge cases: empty states, validation errors, very long input, boundary values.\n- Persistence: reload, local/session storage, URL fragments.\n- Navigation: which controls change the URL, back/forward behaviour.\n\n**Important**: Do not just open the app url with playwright-cli, always go through the test to capture any custom setup done there.\n**Important**: Stop the background test when done exploring.\n\n### 1.4 Write the spec file\n\nSave under `specs/<feature>.plan.md`. Use this structure:\n\n```markdown\n# <Feature> Test Plan\n\n## Application Overview\n\n<One paragraph describing what the feature does and why it matters.>\n\n## Test Scenarios\n\n### 1. <Group Name>\n\n**Seed:** `tests/seed.spec.ts`\n\n#### 1.1. <kebab-case-scenario-name>\n\n**File:** `tests/<group>/<kebab-case-scenario-name>.spec.ts`\n\n**Steps:**\n  1. <Concrete user step>\n    - expect: <observable outcome>\n    - expect: <another observable outcome>\n  2. <Next step>\n    - expect: <outcome>\n\n#### 1.2. <next-scenario>\n...\n\n### 2. <Next Group>\n\n**Seed:** `tests/seed.spec.ts`\n...\n```\n\nGuidelines:\n\n- Each scenario is independent and starts from the seed's fresh state — never chain scenarios.\n- Scenario names are kebab-case and match the test file name (`should-add-single-todo` → `should-add-single-todo.spec.ts`).\n- Cover happy path, edge cases, validation, negative flows, persistence.\n- Write steps at the user level (\"Type 'Buy milk' into the input\"), not the API level (\"call `fill`\").\n- Put observable outcomes in `- expect:` bullets; each becomes an assertion during generation.\n\n---\n\n## 2. Generate\n\nGoal: take a spec file and produce Playwright test files. Optionally update the spec if it has drifted.\n\n### 2.1 Inputs\n\n- **Spec file**, e.g. `specs/basic-operations.plan.md`.\n- **Target**: either a single scenario (e.g. `1.2`), a whole group (`1`), or all.\n- **Seed file**, read from the `**Seed:**` line of the scenario's group.\n\n### 2.2 Generate one scenario\n\nFor each target scenario, in sequence (never in parallel — scenarios share the seed session):\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test <seed-file> --debug=cli   # background\nplaywright-cli attach tw-XXXX\n# resume\n```\n\n**Do not** just open the app url with playwright-cli, always go through the test to capture any custom setup done there.\n\nWalk the scenario's `Steps:` one by one with `playwright-cli`, treating the spec as the plan and the live app as the source of truth. If a step is vague (\"click the button\" — which button?), references an element that no longer exists, or contradicts the app's actual behaviour, use your judgement: update the spec to match what the app really does, then keep going. Editing the spec mid-generation is expected.\n\nEvery action prints the equivalent Playwright TypeScript (see [How generation works](#0-how-generation-works)):\n\n```bash\nplaywright-cli snapshot                         # find refs\nplaywright-cli fill e3 \"John Doe\"               # -> page.getByRole('textbox', {...}).fill(...)\nplaywright-cli press Enter\nplaywright-cli click e7\n```\n\nFor each `- expect:` bullet, add an explicit assertion. See [How generation works](#0-how-generation-works) for details.\n\nCollect the generated code and write the test file at the path given in the spec:\n\n```ts\n// spec: specs/basic-operations.plan.md\n// seed: tests/seed.spec.ts\nimport { test, expect } from './fixtures';   // or '@playwright/test' if no fixtures file\n\ntest.describe('Signing in and out', () => {\n  test('should sign in', async ({ page }) => {\n    // 1. Navigate to the application\n    // (handled by the seed fixture)\n\n    // 2. Type 'John Doe' into the username field\n    await page.getByRole('textbox', { name: 'username' }).fill('John Doe');\n\n    // 3. Type password\n    await page.getByRole('textbox', { name: 'password' }).fill('TestPassword');\n\n    // 4. Press Enter to submit\n    await page.getByRole('textbox', { name: 'password' }).press('Enter');\n\n    await expect(page.getByRole('heading')).toContainText('Welcome, John Doe!');\n  });\n});\n```\n\nRules:\n\n- **One test per file.** File path, describe name, and test name come verbatim from the spec (minus the ordinal).\n- Prefix each numbered step with a `// N. <step text>` comment before its actions.\n- Use the describe group name verbatim from the spec (no `1.` ordinal).\n- Import from `./fixtures` if the project has one; otherwise `@playwright/test`.\n- **Important**: close the CLI session and stop the background test before moving to the next scenario.\n\n### 2.3 Generate multiple scenarios\n\nLoop 2.2 over the targeted scenarios one at a time, restarting the seed between each so every test starts from a clean page. This is safe to parallelise due to unique generated session names - just make sure each test run is stopped.\n\n### 2.4 Run generated tests\n\nAfter generation, run the new tests once:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/<group>/<scenario>.spec.ts\n```\n\nAny failure goes to Section 3.\n\n---\n\n## 3. Heal\n\nGoal: fix failing tests, and update the spec if the app's intended behaviour changed.\n\n### 3.1 Find failing tests\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test\n```\n\nRecord the list of failing `<file>:<line>` entries and process them one at a time. Do not attempt parallel fixes — shared state and the single CLI session make that fragile.\n\n### 3.2 Debug one failure\n\nRun the single failing test in debug mode in the background, then attach:\n\n```bash\nPLAYWRIGHT_HTML_OPEN=never npx playwright test tests/<group>/<scenario>.spec.ts:<line> --debug=cli\n# wait for \"Debugging Instructions\" and the tw-XXXX session name\nplaywright-cli attach tw-XXXX\n```\n\nThe test is paused at the start. Step forward or run to until just before the failing action or assertion, then diagnose:\n\n```bash\nplaywright-cli snapshot                # did the element change / move / rename?\nplaywright-cli console                 # app-side errors?\nplaywright-cli requests                # failed request? wrong payload?\nplaywright-cli show --annotate         # ask the user to point somewhere\n```\n\nCommon causes: selector drift, new wrapper element, label/ARIA rename, timing (transition, async load), assertion text updated in the app, test data leaking between runs.\n\nRehearse the corrected interaction with `playwright-cli` — the generated code in the output is what you paste back into the test.\n\n### 3.3 Apply the fix\n\nEdit the test file: update the locator, assertion, step order, or inputs to match the corrected behaviour. Stop the background debug run. Rerun the single test to confirm green.\n\nNever skip hooks or add sleeps as a fix. Never use `networkidle`.\n\n### 3.4 Reconcile with the spec\n\nOpen the spec referenced by the `// spec:` header in the test file and locate the scenario that matches the test.\n\n- **Fix was purely technical** (locator drift, better assertion shape) and the spec's user-level behaviour still matches the app → leave the spec alone.\n- **Fix changed user-visible steps, inputs, order, or expected outcomes** that the spec describes → update the spec to match reality. Keep the scenario id and file path stable; only the step / expect lines change.\n- **Unclear whether the app change is intentional** (spec is stale) **or a regression** (test was right, app is wrong) → **stop and ask the user**. Provide:\n  - the scenario id (e.g. `2.3`),\n  - the spec lines that no longer match,\n  - the observed app behaviour (quote a snapshot excerpt or a concrete outcome).\n\nOnly after the user answers, either update the spec (intentional change) or file/flag the test as covering a bug (regression).\n\n### 3.5 Iteration and giving up\n\n- Fix failures one at a time; rerun after each.\n- If after thorough investigation you are confident the test is correct but the app is wrong *and* the user has confirmed it's a bug: mark the test `test.fixme(...)` with a comment pointing at the user's decision or issue link. Never silently skip.\n\n---\n\n## Cross-references\n\n| For... | See |\n|---|---|\n| `--debug=cli` / attach mechanics | [playwright-tests.md](playwright-tests.md) |\n| Mocking requests during exploration/generation | [request-mocking.md](request-mocking.md) |\n| Managing the CLI browser session | [session-management.md](session-management.md) |\n\n<!-- File: .claude/skills/playwright-cli/references/tracing.md (Tokens: ~820 | Category: anthropic-skill) -->\n# Tracing\n\nCapture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.\n\n## Basic Usage\n\n```bash\n# Start trace recording\nplaywright-cli tracing-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli click e1\nplaywright-cli fill e2 \"test\"\n\n# Stop trace recording\nplaywright-cli tracing-stop\n```\n\n## Trace Output Files\n\nWhen you start tracing, Playwright creates a `traces/` directory with several files:\n\n### `trace-{timestamp}.trace`\n\n**Action log** - The main trace file containing:\n- Every action performed (clicks, fills, navigations)\n- DOM snapshots before and after each action\n- Screenshots at each step\n- Timing information\n- Console messages\n- Source locations\n\n### `trace-{timestamp}.network`\n\n**Network log** - Complete network activity:\n- All HTTP requests and responses\n- Request headers and bodies\n- Response headers and bodies\n- Timing (DNS, connect, TLS, TTFB, download)\n- Resource sizes\n- Failed requests and errors\n\n### `resources/`\n\n**Resources directory** - Cached resources:\n- Images, fonts, stylesheets, scripts\n- Response bodies for replay\n- Assets needed to reconstruct page state\n\n## What Traces Capture\n\n| Category | Details |\n|----------|---------|\n| **Actions** | Clicks, fills, hovers, keyboard input, navigations |\n| **DOM** | Full DOM snapshot before/after each action |\n| **Screenshots** | Visual state at each step |\n| **Network** | All requests, responses, headers, bodies, timing |\n| **Console** | All console.log, warn, error messages |\n| **Timing** | Precise timing for each operation |\n\n## Use Cases\n\n### Debugging Failed Actions\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://app.example.com\n\n# This click fails - why?\nplaywright-cli click e5\n\nplaywright-cli tracing-stop\n# Open trace to see DOM state when click was attempted\n```\n\n### Analyzing Performance\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://slow-site.com\nplaywright-cli tracing-stop\n\n# View network waterfall to identify slow resources\n```\n\n### Capturing Evidence\n\n```bash\n# Record a complete user flow for documentation\nplaywright-cli tracing-start\n\nplaywright-cli open https://app.example.com/checkout\nplaywright-cli fill e1 \"4111111111111111\"\nplaywright-cli fill e2 \"12/25\"\nplaywright-cli fill e3 \"123\"\nplaywright-cli click e4\n\nplaywright-cli tracing-stop\n# Trace shows exact sequence of events\n```\n\n## Trace vs Video vs Screenshot\n\n| Feature | Trace | Video | Screenshot |\n|---------|-------|-------|------------|\n| **Format** | .trace file | .webm video | .png/.jpeg image |\n| **DOM inspection** | Yes | No | No |\n| **Network details** | Yes | No | No |\n| **Step-by-step replay** | Yes | Continuous | Single frame |\n| **File size** | Medium | Large | Small |\n| **Best for** | Debugging | Demos | Quick capture |\n\n## Best Practices\n\n### 1. Start Tracing Before the Problem\n\n```bash\n# Trace the entire flow, not just the failing step\nplaywright-cli tracing-start\nplaywright-cli open https://example.com\n# ... all steps leading to the issue ...\nplaywright-cli tracing-stop\n```\n\n### 2. Clean Up Old Traces\n\nTraces can consume significant disk space:\n\n```bash\n# Remove traces older than 7 days\nfind .playwright-cli/traces -mtime +7 -delete\n```\n\n## Limitations\n\n- Traces add overhead to automation\n- Large traces can consume significant disk space\n- Some dynamic content may not replay perfectly\n\n<!-- File: .claude/skills/playwright-cli/references/video-recording.md (Tokens: ~1317 | Category: anthropic-skill) -->\n# Video Recording\n\nCapture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).\n\n## Basic Recording\n\n```bash\n# Open browser first\nplaywright-cli open\n\n# Start recording\nplaywright-cli video-start demo.webm\n\n# Add a chapter marker for section transitions\nplaywright-cli video-chapter \"Getting Started\" --description=\"Opening the homepage\" --duration=2000\n\n# Navigate and perform actions\nplaywright-cli goto https://example.com\nplaywright-cli snapshot\nplaywright-cli click e1\n\n# Add another chapter\nplaywright-cli video-chapter \"Filling Form\" --description=\"Entering test data\" --duration=2000\nplaywright-cli fill e2 \"test input\"\n\n# Stop and save\nplaywright-cli video-stop\n```\n\n## Best Practices\n\n### 1. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nplaywright-cli video-start recordings/login-flow-2024-01-15.webm\nplaywright-cli video-start recordings/checkout-test-run-42.webm\n```\n\n### 2. Record entire hero scripts.\n\nWhen recording a video for the user or as a proof of work, it is best to create a code snippet and execute it with run-code.\nIt allows inserting appropriate pauses between the actions and annotating the video. There are new Playwright APIs for that.\n\n1) Perform scenario using CLI and take note of all locators and actions. You'll need those locators to request their bounding boxes for highlight.\n2) Create a file with the intended script for video (below). Use pressSequentially w/ delay for nice typing, make reasonable pauses.\n3) Use playwright-cli run-code --filename your-script.js\n\n**Important**: Overlays are `pointer-events: none` — they do not interfere with page interactions. You can safely keep sticky overlays visible while clicking, filling, or performing any actions on the page.\n\n```js\nasync page => {\n  await page.screencast.start({ path: 'video.webm', size: { width: 1280, height: 800 } });\n  await page.goto('https://demo.playwright.dev/todomvc');\n\n  // Show a chapter card — blurs the page and shows a dialog.\n  // Blocks until duration expires, then auto-removes.\n  // Use this for simple use cases, but always feel free to hand-craft your own beautiful\n  // overlay via await page.screencast.showOverlay().\n  await page.screencast.showChapter('Adding Todo Items', {\n    description: 'We will add several items to the todo list.',\n    duration: 2000,\n  });\n\n  // Perform action\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Walk the dog', { delay: 60 });\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');\n  await page.waitForTimeout(1000);\n\n  // Show next chapter\n  await page.screencast.showChapter('Verifying Results', {\n    description: 'Checking the item appeared in the list.',\n    duration: 2000,\n  });\n\n  // Add a sticky annotation that stays while you perform actions.\n  // Overlays are pointer-events: none, so they won't block clicks.\n  const annotation = await page.screencast.showOverlay(`\n    <div style=\"position: absolute; top: 8px; right: 8px;\n      padding: 6px 12px; background: rgba(0,0,0,0.7);\n      border-radius: 8px; font-size: 13px; color: white;\">\n      ✓ Item added successfully\n    </div>\n  `);\n\n  // Perform more actions while the annotation is visible\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Buy groceries', { delay: 60 });\n  await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');\n  await page.waitForTimeout(1500);\n\n  // Remove the annotation when done\n  await annotation.dispose();\n\n  // You can also highlight relevant locators and provide contextual annotations.\n  const bounds = await page.getByText('Walk the dog').boundingBox();\n  await page.screencast.showOverlay(`\n    <div style=\"position: absolute;\n      top: ${bounds.y}px;\n      left: ${bounds.x}px;\n      width: ${bounds.width}px;\n      height: ${bounds.height}px;\n      border: 1px solid red;\">\n    </div>\n    <div style=\"position: absolute;\n      top: ${bounds.y + bounds.height + 5}px;\n      left: ${bounds.x + bounds.width / 2}px;\n      transform: translateX(-50%);\n      padding: 6px;\n      background: #808080;\n      border-radius: 10px;\n      font-size: 14px;\n      color: white;\">Check it out, it is right above this text\n    </div>\n  `, { duration: 2000 });\n\n  await page.screencast.stop();\n}\n```\n\nEmbrace creativity, overlays are powerful.\n\n### Overlay API Summary\n\n| Method | Use Case |\n|--------|----------|\n| `page.screencast.showChapter(title, { description?, duration?, styleSheet? })` | Full-screen chapter card with blurred backdrop — ideal for section transitions |\n| `page.screencast.showOverlay(html, { duration? })` | Custom HTML overlay — use for callouts, labels, highlights |\n| `disposable.dispose()` | Remove a sticky overlay added without duration |\n| `page.screencast.hideOverlays()` / `page.screencast.showOverlays()` | Temporarily hide/show all overlays |\n\n## Tracing vs Video\n\n| Feature | Video | Tracing |\n|---------|-------|---------|\n| Output | WebM file | Trace file (viewable in Trace Viewer) |\n| Shows | Visual recording | DOM snapshots, network, console, actions |\n| Use case | Demos, documentation | Debugging, analysis |\n| Size | Larger | Smaller |\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n\n<!-- File: .claude/skills/playwright-cli/SKILL.md (Tokens: ~3003 | Category: anthropic-skill) -->\n---\nname: playwright-cli\ndescription: Automate browser interactions, test web pages and work with Playwright tests.\nallowed-tools: Bash(playwright-cli:*) Bash(npx:*) Bash(npm:*)\n---\n\n# Browser Automation with playwright-cli\n\n## Quick start\n\n```bash\n# open new browser\nplaywright-cli open\n# navigate to a page\nplaywright-cli goto https://playwright.dev\n# interact with the page using refs from the snapshot\nplaywright-cli click e15\nplaywright-cli type \"page.click\"\nplaywright-cli press Enter\n# take a screenshot (rarely used, as snapshot is more common)\nplaywright-cli screenshot\n# close the browser\nplaywright-cli close\n```\n\n## Commands\n\n### Core\n\n```bash\nplaywright-cli open\n# open and navigate right away\nplaywright-cli open https://example.com/\nplaywright-cli goto https://playwright.dev\nplaywright-cli type \"search query\"\nplaywright-cli click e3\nplaywright-cli dblclick e7\n# --submit presses Enter after filling the element\nplaywright-cli fill e5 \"user@example.com\"  --submit\nplaywright-cli drag e2 e8\n# drop files or data onto an element (from outside the page)\nplaywright-cli drop e4 --path=./image.png\nplaywright-cli drop e4 --data=\"text/plain=hello world\"\nplaywright-cli hover e4\nplaywright-cli select e9 \"option-value\"\nplaywright-cli upload ./document.pdf\nplaywright-cli check e12\nplaywright-cli uncheck e12\nplaywright-cli snapshot\n# search the snapshot for text or a regexp, returns matching nodes with surrounding context\nplaywright-cli find \"Sign in\"\nplaywright-cli find --regex \"Sign (in|up)\"\n# wrap the regexp in slashes to add flags, e.g. /i for case-insensitive\nplaywright-cli find --regex \"/sign (in|up)/i\"\nplaywright-cli eval \"document.title\"\nplaywright-cli eval \"el => el.textContent\" e5\n# get element id, class, or any attribute not visible in the snapshot\nplaywright-cli eval \"el => el.id\" e5\nplaywright-cli eval \"el => el.getAttribute('data-testid')\" e5\nplaywright-cli dialog-accept\nplaywright-cli dialog-accept \"confirmation text\"\nplaywright-cli dialog-dismiss\nplaywright-cli resize 1920 1080\nplaywright-cli close\n```\n\n### Navigation\n\n```bash\nplaywright-cli go-back\nplaywright-cli go-forward\nplaywright-cli reload\n```\n\n### Keyboard\n\n```bash\nplaywright-cli press Enter\nplaywright-cli press ArrowDown\nplaywright-cli keydown Shift\nplaywright-cli keyup Shift\n```\n\n### Mouse\n\n```bash\nplaywright-cli mousemove 150 300\nplaywright-cli mousedown\nplaywright-cli mousedown right\nplaywright-cli mouseup\nplaywright-cli mouseup right\nplaywright-cli mousewheel 0 100\n```\n\n### Save as\n\n```bash\nplaywright-cli screenshot\nplaywright-cli screenshot e5\nplaywright-cli screenshot --filename=page.png\nplaywright-cli screenshot --hires\nplaywright-cli pdf --filename=page.pdf\n```\n\n### Tabs\n\n```bash\nplaywright-cli tab-list\nplaywright-cli tab-new\nplaywright-cli tab-new https://example.com/page\nplaywright-cli tab-close\nplaywright-cli tab-close 2\nplaywright-cli tab-select 0\n```\n\n### Storage\n\n```bash\nplaywright-cli state-save\nplaywright-cli state-save auth.json\nplaywright-cli state-load auth.json\n\n# Cookies\nplaywright-cli cookie-list\nplaywright-cli cookie-list --domain=example.com\nplaywright-cli cookie-get session_id\nplaywright-cli cookie-set session_id abc123\nplaywright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure\nplaywright-cli cookie-delete session_id\nplaywright-cli cookie-clear\n\n# LocalStorage\nplaywright-cli localstorage-list\nplaywright-cli localstorage-get theme\nplaywright-cli localstorage-set theme dark\nplaywright-cli localstorage-delete theme\nplaywright-cli localstorage-clear\n\n# SessionStorage\nplaywright-cli sessionstorage-list\nplaywright-cli sessionstorage-get step\nplaywright-cli sessionstorage-set step 3\nplaywright-cli sessionstorage-delete step\nplaywright-cli sessionstorage-clear\n```\n\n### Network\n\n```bash\nplaywright-cli route \"**/*.jpg\" --status=404\nplaywright-cli route \"https://api.example.com/**\" --body='{\"mock\": true}'\nplaywright-cli route-list\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n### DevTools\n\n```bash\nplaywright-cli console\nplaywright-cli console warning\nplaywright-cli requests\nplaywright-cli request 5\nplaywright-cli run-code \"async page => await page.context().grantPermissions(['geolocation'])\"\nplaywright-cli run-code --filename=script.js\nplaywright-cli tracing-start\nplaywright-cli tracing-stop\nplaywright-cli video-start video.webm\nplaywright-cli video-chapter \"Chapter Title\" --description=\"Details\" --duration=2000\nplaywright-cli video-stop\n\n# annotate each subsequent action (click, type, ...) with a callout naming the action and highlighting the target\nplaywright-cli video-show-actions --duration=600 --position=top-right\nplaywright-cli video-hide-actions\n\n# launch the dashboard for UI review / design feedback — user annotates the page, you receive the annotated screenshot, snapshot, and notes\nplaywright-cli show --annotate\n\n# generate a Playwright locator for an element from its ref or selector\nplaywright-cli generate-locator e5 --raw\n\n# show a persistent highlight overlay for an element, optionally with a custom style\nplaywright-cli highlight e5\nplaywright-cli highlight e5 --style=\"outline: 3px dashed red\"\n# hide a single element highlight, or all page highlights when no target is given\nplaywright-cli highlight e5 --hide\nplaywright-cli highlight --hide\n```\n\n## Raw output\n\nThe global `--raw` option strips page status, generated code, and snapshot sections from the output, returning only the result value. Use it to pipe command output into other tools. Commands that don't produce output return nothing.\n\n```bash\nplaywright-cli --raw eval \"JSON.stringify(performance.timing)\" | jq '.loadEventEnd - .navigationStart'\nplaywright-cli --raw eval \"JSON.stringify([...document.querySelectorAll('a')].map(a => a.href))\" > links.json\nplaywright-cli --raw snapshot > before.yml\nplaywright-cli click e5\nplaywright-cli --raw snapshot > after.yml\ndiff before.yml after.yml\nTOKEN=$(playwright-cli --raw cookie-get session_id)\nplaywright-cli --raw localstorage-get theme\n```\n\nFor structured output wrapping every reply as JSON, pass --json\n```bash\nplaywright-cli list --json\n```\n\n## Open parameters\n```bash\n# Use specific browser when creating session\nplaywright-cli open --browser=chrome\nplaywright-cli open --browser=firefox\nplaywright-cli open --browser=webkit\nplaywright-cli open --browser=msedge\n\n# Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit).\n# Prefer this when a mobile layout is acceptable: mobile pages are usually\n# lighter, so snapshots are smaller and cheaper.\nplaywright-cli open --mobile\nplaywright-cli open --device=\"iPhone 15\"\n\n# Use persistent profile (by default profile is in-memory)\nplaywright-cli open --persistent\n# Use persistent profile with custom directory\nplaywright-cli open --profile=/path/to/profile\n\n# Connect to browser via Playwright Extension\nplaywright-cli attach --extension=chrome\n\n# Connect to a running Chrome or Edge by channel name\nplaywright-cli attach --cdp=chrome\nplaywright-cli attach --cdp=msedge\n\n# Connect to a running browser via CDP endpoint\nplaywright-cli attach --cdp=http://localhost:9222\n\n# Start with config file\nplaywright-cli open --config=my-config.json\n\n# Close the browser\nplaywright-cli close\n# Detach from an attached browser (leaves the external browser running)\nplaywright-cli -s=msedge detach\n# Delete user data for the default session\nplaywright-cli delete-data\n```\n\n## URLs with `&` on Windows\n\nOn Windows, `cmd.exe` and PowerShell treat `&` as a command separator, so URLs with multiple query parameters get truncated before `playwright-cli` runs. Escape `&` with `^&` in `cmd.exe`, or use `--%` in PowerShell:\n\n```batch\nplaywright-cli goto \"https://example.com/?a=1^&b=2\"\n```\n\n```powershell\nplaywright-cli --% goto \"https://example.com/?a=1&b=2\"\n```\n\n## Snapshots\n\nAfter each command, playwright-cli provides a snapshot of the current browser state.\n\n```bash\n> playwright-cli goto https://example.com\n### Page\n- Page URL: https://example.com/\n- Page Title: Example Domain\n### Snapshot\n[Snapshot](.playwright-cli/page-2026-02-14T19-22-42-679Z.yml)\n```\n\nYou can also take a snapshot on demand using `playwright-cli snapshot` command. All the options below can be combined as needed.\n\n```bash\n# default - save to a file with timestamp-based name\nplaywright-cli snapshot\n\n# save to file, use when snapshot is a part of the workflow result\nplaywright-cli snapshot --filename=after-click.yaml\n\n# snapshot an element instead of the whole page\nplaywright-cli snapshot \"#main\"\n\n# limit snapshot depth for efficiency, take a partial snapshot afterwards\nplaywright-cli snapshot --depth=4\nplaywright-cli snapshot e34\n\n# include each element's bounding box as [box=x,y,width,height]\nplaywright-cli snapshot --boxes\n\n# search a large snapshot instead of capturing it all — returns matching nodes\n# with 3 lines of context around each match (like grep -C)\nplaywright-cli find \"Add to cart\"\nplaywright-cli find --regex \"\\\\$[0-9]+\\\\.[0-9]{2}\"\n```\n\n## Targeting elements\n\nBy default, use refs from the snapshot to interact with page elements.\n\n```bash\n# get snapshot with refs\nplaywright-cli snapshot\n\n# interact using a ref\nplaywright-cli click e15\n```\n\nYou can also use css selectors or Playwright locators.\n\n```bash\n# css selector\nplaywright-cli click \"#main > button.submit\"\n\n# role locator\nplaywright-cli click \"getByRole('button', { name: 'Submit' })\"\n\n# test id\nplaywright-cli click \"getByTestId('submit-button')\"\n```\n\n## Browser Sessions\n\n```bash\n# create new browser session named \"mysession\" with persistent profile\nplaywright-cli -s=mysession open example.com --persistent\n# same with manually specified profile directory (use when requested explicitly)\nplaywright-cli -s=mysession open example.com --profile=/path/to/profile\nplaywright-cli -s=mysession click e6\nplaywright-cli -s=mysession close  # stop a named browser\nplaywright-cli -s=mysession delete-data  # delete user data for persistent session\n\nplaywright-cli list\n# Close all browsers\nplaywright-cli close-all\n# Forcefully kill all browser processes\nplaywright-cli kill-all\n```\n\n## Installation\n\nIf global `playwright-cli` command is not available, try a local version via `npx playwright cli`:\n\n```bash\nnpx --no-install playwright --version\n```\n\nWhen local version is available, use `npx playwright cli` in all commands. Otherwise, install `playwright-cli` as a global command:\n\n```bash\nnpm install -g @playwright/cli@latest\n```\n\n## Example: Form submission\n\n```bash\nplaywright-cli open https://example.com/form\nplaywright-cli snapshot\n\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Multi-tab workflow\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tab-new https://example.com/other\nplaywright-cli tab-list\nplaywright-cli tab-select 0\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Debugging with DevTools\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli console\nplaywright-cli requests\nplaywright-cli close\n```\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tracing-start\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli tracing-stop\nplaywright-cli close\n```\n\n## Example: Interactive session\n\nAsk the user for UI review or design feedback. The user draws boxes on the live page and types comments; you receive the annotated screenshot, the snapshot of the marked region, and the user's notes. Use this whenever the user asks for \"UI review\", \"design feedback\", or to \"ask the user what they think / want / mean\":\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli show --annotate\n```\n\n## Specific tasks\n\n* **Running and Debugging Playwright tests** [references/playwright-tests.md](references/playwright-tests.md)\n* **Request mocking** [references/request-mocking.md](references/request-mocking.md)\n* **Running Playwright code** [references/running-code.md](references/running-code.md)\n* **Browser session management** [references/session-management.md](references/session-management.md)\n* **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)\n* **Test generation (plan / generate / heal)** [references/test-generation.md](references/test-generation.md)\n* **Tracing** [references/tracing.md](references/tracing.md)\n* **Video recording** [references/video-recording.md](references/video-recording.md)\n* **Inspecting element attributes** [references/element-attributes.md](references/element-attributes.md)\n\n<!-- File: .claude/skills/release/SKILL.md (Tokens: ~10381 | Category: anthropic-skill) -->\n---\nname: release\ndescription: |\n  Create a release from dev branch. Generates changelog entries from commits,\n  bumps version, and creates a PR to main.\n\n  TRIGGERS - Use this skill when user says:\n  - \"/release\" - create a patch release (default)\n  - \"/release minor\" - create a minor release\n  - \"/release major\" - create a major release\n  - \"make a release\", \"cut a release\", \"ship it\", \"release to main\"\n---\n\n# Release Skill\n\nCreates a release by comparing dev to main, generating changelog entries from commits, bumping the version, and creating a PR. After the tag is pushed and the release workflow finishes building binaries, updates the Homebrew formula with the real SHA256 values from the published `checksums.txt`, syncs the `coleam00/homebrew-archon` tap, and verifies the end-to-end install path via `/test-release`.\n\n> **⚠️ CRITICAL — Homebrew formula SHAs cannot be known until after the release workflow builds binaries.**\n>\n> The `version` field in `homebrew/archon.rb` and the `sha256` fields must be updated **atomically**. Never update one without the other.\n>\n> The correct sequence is:\n> 1. Tag is pushed → release workflow fires → binaries built → `checksums.txt` uploaded\n> 2. Fetch `checksums.txt` from the published release\n> 3. Parse the SHA256 per platform\n> 4. Update `homebrew/archon.rb` with the new version AND the new SHAs in a single commit\n> 5. Sync to the `coleam00/homebrew-archon/Formula/archon.rb` tap repo\n>\n> Updating the formula's `version` field without also updating the `sha256` values creates a stale, misleading formula that looks valid but produces checksum mismatches on install. This has happened before (v0.3.0: version updated to 0.3.0 but SHAs were still from v0.2.13). Always do both or neither.\n\n## Process\n\n### Step 1: Validate State\n\n```bash\n# Must be on dev branch with clean working tree\ngit checkout dev\ngit pull origin dev --no-rebase\ngit status --porcelain  # must be empty\ngit fetch origin main\n```\n\nIf not on dev or working tree is dirty, abort with a clear message.\n\n**Then check that dev actually contains main.** Anything committed directly to\nmain — a docs typo fix, a hotfix, the previous release's formula commit — stays\nstranded there until someone merges it back. The release PR would then propose\n*reverting* it, and the next release inherits the drift.\n\n```bash\nif git merge-base --is-ancestor origin/main origin/dev; then\n  echo \"dev contains main — OK\"\nelse\n  echo \"DRIFT: commits exist on main that dev does not have:\"\n  git log origin/dev..origin/main --oneline\n  echo \"\"\n  echo \"Resync before releasing:\"\n  echo \"  git checkout dev && git pull origin main --no-rebase && git push origin dev\"\n  exit 1\nfi\n```\n\nObserved on the 0.7.1 release: `ae704a73 Update docs.mdx (#2155)` had been\ncommitted straight to main after 0.7.0 and never merged back. Someone had\nnoticed the *content* gap and hand-forward-ported it via a separate PR (#2403),\nso dev had the change but not the commit — and `main` was still not an ancestor\nof `dev`. Resolve this before Step 2; do not carry it into the release PR.\n\n### Step 1.5: Pre-flight compiled-binary smoke test (MANDATORY before any other step)\n\n> **Why this is first**: releases have ended up with zero working binaries because a module-init crash or bundler bug only surfaces in `bun build --compile` output, not in `bun run`. CI catches it — but only AFTER the tag is pushed and a GitHub Release is created. By then the damage (empty release, broken `releases/latest`, broken `install.sh`) is already live. Failing here, before any user-visible change, keeps the blast radius at \"no release was cut.\"\n\nRun locally on the native target. This takes ~15-30s and is cheaper than discovering the problem after tag+release.\n\n```bash\n# Guard: only run this for Node/Bun projects with a CLI entry point + build-binaries script.\nif [ -f scripts/build-binaries.sh ] && [ -f packages/cli/src/cli.ts ]; then\n  TMP_BINARY=$(mktemp)\n  trap \"rm -f $TMP_BINARY\" EXIT\n\n  # Compile for the native target only (not full cross-compile — that's CI's job).\n  # Match the real release flags so any bundler quirk reproduces locally.\n  bun build \\\n    --compile \\\n    --minify \\\n    --target=bun \\\n    --outfile=\"$TMP_BINARY\" \\\n    packages/cli/src/cli.ts\n\n  # Smoke test: the binary must start and exit 0 on a safe, non-interactive command.\n  # Use `--help` (NOT `version`). The `version` command's compiled-binary code\n  # path depends on BUNDLED_IS_BINARY=true, which is set by scripts/build-binaries.sh\n  # — but we're doing a bare `bun build --compile` here to keep the smoke fast,\n  # so BUNDLED_IS_BINARY is still `false`. That sends `version` down the dev\n  # branch of version.ts which tries to read package.json from a path that only\n  # exists in node_modules, producing a false-positive ENOENT. `--help` has no\n  # such dev/binary branch and exercises the same module-init graph we're\n  # actually testing. Must NOT touch network, database, or require env vars.\n  if ! \"$TMP_BINARY\" --help > /tmp/archon-preflight.log 2>&1; then\n    echo \"ERROR: compiled binary crashed at startup\"\n    cat /tmp/archon-preflight.log\n    echo \"\"\n    echo \"This usually means a dependency has a module-init-time side effect that\"\n    echo \"fails in a compiled binary context (readFileSync of a path that only\"\n    echo \"exists in node_modules, etc.). Fix before cutting the release — do NOT\"\n    echo \"proceed to version bump.\"\n    exit 1\n  fi\n\n  # Also grep for known crash markers that exit 0 but print a fatal error\n  # (some module-init errors are caught by top-level try/catch but still log).\n  if grep -qE \"Expected CommonJS module|TypeError:|ReferenceError:|SyntaxError:\" /tmp/archon-preflight.log; then\n    echo \"ERROR: compiled binary emitted a runtime error despite exit 0\"\n    cat /tmp/archon-preflight.log\n    exit 1\n  fi\n\n  echo \"Pre-flight binary smoke: PASSED\"\nfi\n```\n\nIf this fails, **abort the release entirely** — do not bump version, do not modify CHANGELOG, do not create a PR. Surface the error to the user, point at the failing output, and stop. Recovery is: fix the bundler / dependency issue on a feature branch, merge to dev, re-run `/release`.\n\n**Common failure modes this catches:**\n- Bun `--bytecode` flag producing broken bytecode for the current module graph\n- A dependency (e.g. an SDK) reading `package.json` or other files at module top level via paths that resolve fine in `node_modules/` but not next to a compiled binary\n- Circular imports that break under minification but work under plain `bun run`\n- A newly added package that ships CJS with an unusual wrapper shape\n\n### Step 2: Detect Stack and Current Version\n\nDetect the project's package manager and version file:\n\n1. **Check for `pyproject.toml`** — Python project, version in `version = \"x.y.z\"`\n2. **Check for `package.json`** — Node/Bun project, version in `\"version\": \"x.y.z\"`\n3. **Check for `Cargo.toml`** — Rust project, version in `version = \"x.y.z\"`\n4. **Check for `go.mod`** — Go project (version from git tags only, no file to bump)\n\nIf none found, abort: \"Could not detect project stack — no version file found.\"\n\nRead the current version from the detected file.\n\n### Step 3: Determine Version Bump\n\n**Bump rules based on argument:**\n- No argument or `patch` (default): `0.1.0 -> 0.1.1`\n- `minor`: `0.1.3 -> 0.2.0`\n- `major`: `0.3.5 -> 1.0.0`\n\n### Step 4: Collect Commits\n\n> **Do NOT use `git log main..dev`.** This repo **squash**-merges the release PR\n> into main, so main receives one new commit per release and never contains dev's\n> individual commits. `main..dev` therefore accumulates *every commit ever\n> squashed* and grows release over release. On 0.7.1 it returned **61** commits\n> when only **25** were new — the other 36 were already shipped in 0.7.0 and\n> already written into its changelog. Followed literally, it re-logs most of the\n> previous release.\n\nThe real boundary is dev's own last `Release x.y.z` commit:\n\n```bash\n# The previous release commit ON DEV (not the squashed one on main).\n#\n# The `$` anchor is load-bearing. Both commits exist on dev after Step 9's sync:\n#   6c6945ce  Release 0.7.1           <- dev's own version-bump commit  (WANT)\n#   c71f7f52  Release 0.7.1 (#2435)   <- main's squash, pulled back     (WRONG)\n# Picking the squash commit is catastrophic: its ancestry does not include dev's\n# individual history, so the range balloons to the whole repo (383 commits when\n# tested). Match the bare subject only.\nLAST_RELEASE=$(git log origin/dev \\\n  --grep='^Release [0-9]+\\.[0-9]+\\.[0-9]+$' --extended-regexp \\\n  --format='%H' -n 1)\n\n# First-ever release: fall back to the root commit. Without this the range\n# becomes `..origin/dev`, which git resolves against HEAD and which returns\n# ZERO commits — the skill would report \"nothing to release\" and stop.\nif [ -z \"$LAST_RELEASE\" ]; then\n  LAST_RELEASE=$(git rev-list --max-parents=0 origin/dev | tail -n 1)\nfi\necho \"Previous release commit: $(git log -1 --oneline \"$LAST_RELEASE\")\"\n\n# Everything after it is genuinely new, minus the release-plumbing commits\n# described below.\ngit log \"$LAST_RELEASE\"..origin/dev --oneline --no-merges \\\n  --grep='^Release [0-9]+\\.[0-9]+\\.[0-9]+' \\\n  --grep='^chore: update Homebrew formula for v' \\\n  --grep='^chore\\(homebrew\\):' \\\n  --extended-regexp --invert-grep\n```\n\n> The backslashes in `^chore\\(homebrew\\):` are required. Under `--extended-regexp`\n> bare `(` and `)` are grouping operators, so the unescaped form matches the\n> literal text `chorehomebrew:` and silently fails to filter anything.\n\nTwo commit kinds in that range are **release plumbing, not changelog material**,\nand the `--invert-grep` above drops them:\n\n- `Release x.y.z (#NNNN)` — main's squash commit, pulled back by Step 9's sync\n- `chore: update Homebrew formula for vx.y.z` / `chore(homebrew): …` — the CI job\n  and the Step 10 commit; note these appear as *two different SHAs* with the same\n  content, one per branch\n\n> The `Release` pattern is anchored to a full `x.y.z` version deliberately. A bare\n> `^Release ` would also swallow a legitimate feature commit whose subject starts\n> with that word. If a real commit is ever dropped, widen the range by hand rather\n> than loosening the pattern.\n\n**Sanity-check the boundary before drafting.** Cross-reference against what the\nprevious version already documented — any overlap means the range is wrong.\nDerive both headings from the current version so this does not rot:\n\n```bash\n# CURRENT_VERSION is the value read in Step 2, before the bump (e.g. 0.7.1)\nPREV_MAJOR_MINOR_PATCH=\"$CURRENT_VERSION\"\n# The heading immediately above the one we are about to write:\nawk -v prev=\"## [$PREV_MAJOR_MINOR_PATCH]\" '\n  index($0, prev) == 1 { on = 1; next }\n  on && /^## \\[/       { exit }\n  on                   { print }\n' CHANGELOG.md | grep -oE '#[0-9]{3,5}' | sort -u\n```\n\nIf a PR number in your commit range appears in that list, stop and re-derive the\nboundary — do not write it twice.\n\n**Also read the `[Unreleased]` section.** Contributors may write entries there by\nhand. They are input to this release, not a boundary error:\n\n```bash\nawk '\n  /^## \\[Unreleased\\]/ { on = 1; next }\n  on && /^## \\[/      { exit }\n  on                  { print }\n' CHANGELOG.md\n```\n\nIf no new commits, abort: \"Nothing to release — dev is up to date with main.\"\n\n### Step 5: Draft Changelog Entries\n\nRead the commit messages and the actual diffs (`git diff \"$LAST_RELEASE\"..origin/dev`) to understand what changed.\n\nPrefer the PR title and its `## Summary` section over the raw commit subject —\nthey state the user-visible problem, which is what a changelog entry needs:\n\n```bash\ngh pr view <N> --repo coleam00/Archon --json number,title,body,closingIssuesReferences\n```\n\nDraft entries only for changes the `[Unreleased]` entries from Step 4 do not\nalready describe, so each change appears once. Hand-written entries often cite\nthe issue a PR closes (`closingIssuesReferences`) rather than the PR, or cite\nnothing; judge by what the entry says, not by its number. Keep the substance of\nhand-written entries (migration tables, exact details) and tidy only their wording\nand placement. If you can't tell whether an entry covers a PR, ask in Step 7.\n\n**Categorize into Keep a Changelog sections, in this order (omit empty ones):**\n1. **Breaking** — changes that break existing behavior users may rely on\n2. **Security** — vulnerability fixes and security-relevant hardening\n3. **Added** — new features, new files, new capabilities\n4. **Changed** — modifications to existing behavior\n5. **Deprecated** — features still available but discouraged or planned for removal\n6. **Removed** — deleted features or code\n7. **Fixed** — bug fixes\n\n**Writing rules:**\n- Write entries as a human would — clear, concise, user-facing language\n- Do NOT just copy commit messages verbatim — rewrite them into proper changelog entries\n- Group related commits into single entries where it makes sense\n- Include PR numbers in parentheses when available: `(#12)`\n- Each entry should start with a noun or gerund describing WHAT changed\n- Skip internal-only changes (CI tweaks, typo fixes) unless they affect behavior\n- One blank line between sections\n\n### Step 6: Update Files\n\n1. **Version file** — update version to new value:\n   - `package.json`: update `\"version\": \"x.y.z\"`\n   - `pyproject.toml`: update `version = \"x.y.z\"`\n   - `Cargo.toml`: update `version = \"x.y.z\"`\n\n2. **Workspace version sync** (monorepo only):\n   - If `scripts/sync-versions.sh` exists, run `bash scripts/sync-versions.sh` to sync all `packages/*/package.json` versions to match the root version. The script also refreshes `bun.lock` itself, so the workspace versions land in the lockfile too.\n\n3. **Lockfile refresh** (stack-dependent) — skip the matching line if step 2's script already refreshed the lockfile:\n   - `package.json` + `bun.lock`: run `bun install`\n   - `package.json` + `package-lock.json`: run `npm install --package-lock-only`\n   - `pyproject.toml` + `uv.lock`: run `uv lock --quiet`\n   - `Cargo.toml`: run `cargo update --workspace`\n\n4. **`CHANGELOG.md`** — write one new version section below `[Unreleased]` from\n   the `[Unreleased]` entries and the Step 5 drafts, one `### <Category>` heading\n   per non-empty category in the Step 5 order:\n\n```markdown\n## [x.y.z] - YYYY-MM-DD\n\nOne-line summary of the release.\n\n### Breaking\n\n- Entry one (#PR)\n\n### Added\n\n- Entry one (#PR)\n- Entry two (#PR)\n\n### Fixed\n\n- Entry one (#PR)\n```\n\n**`### Breaking` must come first**, immediately after the one-line summary. Step 9's\nrelease workflow reads this section verbatim into the GitHub release body, so this\nfile's own section order is what makes breaking changes appear first on the release page.\n\nLeave the `[Unreleased]` header with nothing under it.\n\n### Step 7: Present for Review\n\nShow the user:\n1. The detected stack and version file\n2. The version bump (old -> new)\n3. The full changelog section that will be added\n4. The list of commits being included\n5. Any `[Unreleased]` entry you were unsure covers a PR, next to that PR\n\nAsk: \"Does this look good? I'll commit and create the PR.\"\n\n### Step 8: Commit and PR\n\nOnly after user approval:\n\n```bash\n# Stage version file, workspace packages, lockfile, and changelog\ngit add <version-file> packages/*/package.json <lockfile> CHANGELOG.md\ngit commit -m \"Release x.y.z\"\n\n# Push dev\ngit push origin dev\n\n# Create PR: dev -> main\ngh pr create --base main --head dev \\\n  --title \"Release x.y.z\" \\\n  --body \"$(cat <<'EOF'\n## Release x.y.z\n\n{changelog section content}\n\n---\n\nMerging this PR releases x.y.z to main.\nEOF\n)\"\n```\n\nReturn the PR URL to the user.\n\n### Step 9: Tag, Release, and Sync After Merge\n\nAfter the PR is merged (either by the user or via `gh pr merge`):\n\n```bash\n# Fetch the merge commit on main\ngit fetch origin main\n\n# Tag the merge commit\ngit tag vx.y.z origin/main\ngit push origin vx.y.z\n\n# Create a GitHub Release from the tag. --generate-notes is a placeholder: the\n# push above already triggered .github/workflows/release.yml, and once its\n# `release` job finishes building binaries (5-10 min), its \"Build release\n# notes\" step overwrites this release's body with CHANGELOG.md's own\n# `## [x.y.z]` section (Breaking first, per Step 6) plus install instructions.\n# That job is the single source of truth for the body, so it does not matter\n# that this step's placeholder is throwaway, or which of the two finishes\n# first — do NOT hand-craft curated notes here, they would just be discarded.\ngh release create vx.y.z --title \"vx.y.z\" --generate-notes\n\n# Sync dev with main so both branches are identical\ngit checkout dev\ngit pull origin main --no-rebase\ngit push origin dev\n\n# Verify the sync actually converged — do not assume it did. FAIL CLOSED:\n# continuing past a failed sync publishes a formula and tap from a tree that\n# does not match what was released.\ngit fetch origin\nif git merge-base --is-ancestor origin/main origin/dev; then\n  echo \"dev contains main — OK\"\nelse\n  echo \"STILL DIVERGED — stranded on main:\"\n  git log origin/dev..origin/main --oneline\n  exit 1\nfi\n```\n\n> **`--no-rebase` is required, not optional.** Without it, git aborts with\n> `fatal: Need to specify how to reconcile divergent branches` on any machine\n> that has not set `pull.rebase`. It also pins the behaviour to the merge this\n> step actually wants — a `pull.rebase=true` config would otherwise rewrite dev's\n> history, which is exactly what the warning below forbids.\n\n> **Expect to run this twice.** The CI `update-homebrew` job pushes its own\n> formula commit to dev while you are working, so the `git push origin dev` here\n> (and again in Step 10) can be rejected with `Updates were rejected`. That is\n> normal: `git pull origin dev --no-rebase`, then push again. On 0.7.1 both this\n> step and Step 10 required a second pass.\n\n> **Important**: This sync ensures dev has the merge commit from main. Without it,\n> dev and main diverge. The CI `update-homebrew` job only pushes the formula\n> commit to dev — it does not bring the PR merge commit onto dev. This manual\n> `git pull origin main --no-rebase` is what ensures dev has the merge commit.\n\n> **Do NOT** use `git pull origin main --ff-only` or `git reset --hard origin/main`\n> for this sync. Fast-forward is impossible across a squash merge — main's squash\n> commit has a different SHA than dev's release commit, so dev is never\n> fast-forwardable to main. And resetting dev to main rewrites dev's history,\n> which severs every open PR's merge-base from its original commit and balloons\n> their diffs to thousands of lines (confirmed against v0.3.10's release: PRs\n> went from `+80/-1` to `+6626/-300` after a `git reset --hard origin/main` on\n> dev). The plain `git pull origin main --no-rebase` above creates a regular merge commit on\n> dev. The merge bubble in dev's `git log` is the right cost for preserving\n> open-PR sanity. If the merge produces a `homebrew/archon.rb` conflict during a\n> recovery flow, resolve with `git checkout origin/main -- homebrew/archon.rb`\n> (note: `origin/main`, NOT `main` — local main is often stale because the\n> release pushes via `git push origin dev:main` without fast-forwarding the local\n> branch).\n\nThe GitHub Release is distinct from the git tag — without it, the release won't appear on the repository's Releases page. Always create it.\n\nIf the user merges the PR themselves and comes back, still offer to tag, release, and sync.\n\n### Step 9.5: Add the new tag's SQLite vintage fixture\n\nA release tag is a released schema shape. When `vx.y.z` changes the SQLite schema, `check:sqlite-vintages` (the `schema-upgrade` CI job) fails on every PR until the tag's fixture is committed. A tag that keeps the previous schema gets no fixture and needs nothing. Generate it on a branch off dev and commit whatever it writes:\n\n```bash\ngit fetch --tags origin\nbun run generate:sqlite-vintages   # writes packages/core/src/db/fixtures/sqlite-vintages/vx.y.z.sql\nbun run check:sqlite-vintages\n```\n\n### Step 10: Wait for Release Workflow and Update Homebrew Formula\n\n> **Note**: The `update-homebrew` CI job in `.github/workflows/release.yml` runs automatically\n> after the release job and handles the formula update + push to dev (part of Step 10).\n> Step 11 (tap sync to `coleam00/homebrew-archon`) is always manual. Check the Actions tab\n> before running Step 10 manually.\n\nAfter the tag is pushed, `.github/workflows/release.yml` builds platform binaries and uploads them to the GitHub release. This takes 5-10 minutes. The Homebrew formula SHA256 values cannot be known until these binaries exist.\n\n**Wait for all assets to appear on the release:**\n\n```bash\necho \"Waiting for release workflow to finish uploading binaries...\"\nWORKFLOW_FAILED=0\nfor i in {1..30}; do\n  ASSET_COUNT=$(gh release view \"vx.y.z\" --repo coleam00/Archon --json assets --jq '.assets | length')\n  # Expect 7 assets: 5 binaries (darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64.exe) + archon-web.tar.gz + checksums.txt\n  if [ \"$ASSET_COUNT\" -ge 7 ]; then\n    echo \"All $ASSET_COUNT assets uploaded\"\n    break\n  fi\n\n  # Short-circuit: if the release workflow itself has failed, stop waiting.\n  # Hanging for 15 min when CI already crashed just delays the recovery path.\n  WORKFLOW_STATUS=$(gh run list --workflow release.yml --event push --limit 1 --json conclusion,status --jq '.[0] | \"\\(.status)|\\(.conclusion)\"')\n  if [[ \"$WORKFLOW_STATUS\" == \"completed|failure\" ]]; then\n    echo \"Release workflow FAILED — no point waiting longer\"\n    WORKFLOW_FAILED=1\n    break\n  fi\n\n  echo \"  Assets so far: $ASSET_COUNT/7 — waiting 30s (attempt $i/30)...\"\n  sleep 30\ndone\n\nif [ \"$WORKFLOW_FAILED\" -eq 1 ] || [ \"$ASSET_COUNT\" -lt 7 ]; then\n  # Triage: rerun once in case it's transient, then check again.\n  RUN_ID=$(gh run list --workflow release.yml --event push --limit 1 --json databaseId --jq '.[0].databaseId')\n  echo \"Release workflow failed on run $RUN_ID. Rerunning failed jobs once to confirm...\"\n  gh run rerun \"$RUN_ID\" --failed\n  gh run watch \"$RUN_ID\" --exit-status --interval 30 || true\n\n  # Re-check asset count + run status after rerun.\n  ASSET_COUNT=$(gh release view \"vx.y.z\" --repo coleam00/Archon --json assets --jq '.assets | length')\n  if [ \"$ASSET_COUNT\" -ge 7 ]; then\n    echo \"Rerun succeeded — all assets now present\"\n  else\n    echo \"\"\n    echo \"===== DETERMINISTIC CI FAILURE =====\"\n    echo \"The release workflow failed on two consecutive runs. This is NOT a flake.\"\n    echo \"The tag and release exist but have no (or incomplete) assets.\"\n    echo \"\"\n    echo \"install.sh and similar 'releases/latest' paths are now 404-ing.\"\n    echo \"Proceeding with Homebrew/tap sync would publish a formula pointing at\"\n    echo \"missing or inconsistent binaries.\"\n    echo \"\"\n    echo \"Jump to the 'Recovery: deterministic release CI failure' section at the\"\n    echo \"bottom of this skill and execute it. Do NOT continue past this point.\"\n    exit 1\n  fi\nfi\n```\n\n**Fetch checksums.txt and extract SHA256 values:**\n\n```bash\nTMP_DIR=$(mktemp -d)\ngh release download \"vx.y.z\" --repo coleam00/Archon --pattern \"checksums.txt\" --dir \"$TMP_DIR\"\n\nDARWIN_ARM64_SHA=$(awk '/archon-darwin-arm64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nDARWIN_X64_SHA=$(awk '/archon-darwin-x64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nLINUX_ARM64_SHA=$(awk '/archon-linux-arm64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\nLINUX_X64_SHA=$(awk '/archon-linux-x64$/ {print $1}' \"$TMP_DIR/checksums.txt\")\n\n# Sanity check — all four must be present and non-empty\nfor var in DARWIN_ARM64_SHA DARWIN_X64_SHA LINUX_ARM64_SHA LINUX_X64_SHA; do\n  if [ -z \"${!var}\" ]; then\n    echo \"ERROR: $var is empty — checksums.txt may be malformed\"\n    cat \"$TMP_DIR/checksums.txt\"\n    exit 1\n  fi\ndone\n\nrm -rf \"$TMP_DIR\"\n```\n\n**Update `homebrew/archon.rb` in the main repo atomically with version AND SHAs:**\n\nRewrite the formula file using the exact template below. Before using it, diff it against the current `homebrew/archon.rb`: if the current formula has a block the template lacks, update the template first (v0.12.0's first regeneration dropped `caveats` this way). Do NOT edit in place with sed — the whole file should be regenerated from this template so there is zero risk of partial updates.\n\n```bash\ncat > homebrew/archon.rb << EOF\n# Homebrew formula for Archon CLI\n# To install: brew install coleam00/archon/archon\n#\n# This formula downloads pre-built binaries from GitHub releases.\n# For development, see: https://github.com/coleam00/Archon\n\nclass Archon < Formula\n  desc \"Governed agentic automation engine\"\n  homepage \"https://github.com/coleam00/Archon\"\n  version \"x.y.z\"\n  license \"MIT\"\n\n  on_macos do\n    on_arm do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-arm64\"\n      sha256 \"${DARWIN_ARM64_SHA}\"\n    end\n    on_intel do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-x64\"\n      sha256 \"${DARWIN_X64_SHA}\"\n    end\n  end\n\n  on_linux do\n    on_arm do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-arm64\"\n      sha256 \"${LINUX_ARM64_SHA}\"\n    end\n    on_intel do\n      url \"https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-x64\"\n      sha256 \"${LINUX_X64_SHA}\"\n    end\n  end\n\n  def install\n    binary_name = case\n    when OS.mac? && Hardware::CPU.arm?\n      \"archon-darwin-arm64\"\n    when OS.mac? && Hardware::CPU.intel?\n      \"archon-darwin-x64\"\n    when OS.linux? && Hardware::CPU.arm?\n      \"archon-linux-arm64\"\n    when OS.linux? && Hardware::CPU.intel?\n      \"archon-linux-x64\"\n    end\n\n    bin.install binary_name => \"archon\"\n  end\n\n  def caveats\n    <<~EOS\n      Archon ships without AI providers. Add the ones you use:\n        archon setup\n      or directly:\n        archon provider add <claude|codex|pi>\n\n      Adding a provider connects Archon to your agent CLI or SDK; it does not install that CLI.\n\n      After `brew upgrade archon`, run `archon provider add` again for each\n      provider you use; installed providers must match the CLI version.\n    EOS\n  end\n\n  test do\n    # Basic version check - archon version should exit with 0 on success\n    assert_match version.to_s, shell_output(\"#{bin}/archon version\")\n  end\nend\nEOF\n```\n\n**Commit the formula update to main, then sync back to dev:**\n\n```bash\ngit checkout main\ngit pull origin main --no-rebase\ngit add homebrew/archon.rb\ngit commit -m \"chore(homebrew): update formula to vx.y.z\"\ngit push origin main\n\n# Sync dev with main so the formula update is on both branches\ngit checkout dev\ngit pull origin main --no-rebase\ngit push origin dev   # may be rejected — see below\n```\n\n**The CI `update-homebrew` job races you here.** It writes its own formula commit\ndirectly to dev (e.g. `chore: update Homebrew formula for vx.y.z`) while you are\ncommitting the same change to main. Both touch the same four `sha256` lines. The\npush above then fails with `Updates were rejected`:\n\n```bash\ngit pull origin dev --no-rebase   # merges CI's commit; usually resolves clean\ngit push origin dev\n```\n\n**A clean merge is not proof the SHAs are right.** Two commits editing the same\nlines can merge without conflict and still leave the wrong values. Verify against\nthe published checksums before pushing — this is the one file where a wrong value\nbreaks every user's install:\n\n```bash\nVERSION=x.y.z   # without the leading v\ngh release download \"v$VERSION\" --repo coleam00/Archon --pattern checksums.txt --dir /tmp/rel\n\nfail=0\n\n# Assert the formula version matches the release. A stale version points every\n# URL at the wrong release while carrying the new digests.\nformula_version=$(awk -F'\"' '/^[[:space:]]*version \"/ {print $2; exit}' homebrew/archon.rb)\nif [ \"$formula_version\" != \"$VERSION\" ]; then\n  echo \"  BAD version: formula says '$formula_version', release is '$VERSION'\"\n  fail=1\nfi\n\n# Compare each digest against the sha256 that FOLLOWS ITS OWN url line. A naive\n# `grep -q \"$digest\" formula` only asks whether the value appears anywhere, so\n# two platforms with swapped hashes both pass — and an empty digest degenerates\n# to `grep -q \"\"`, which matches every file.\nfor p in archon-darwin-arm64 archon-darwin-x64 archon-linux-arm64 archon-linux-x64; do\n  real=$(awk -v p=\"$p\" '$2 ~ \"(^|/)\" p \"$\" {print $1}' /tmp/rel/checksums.txt | head -1)\n  if ! printf '%s' \"$real\" | grep -qE '^[0-9a-f]{64}$'; then\n    echo \"  BAD $p: no 64-char digest in checksums.txt (got '$real')\"\n    fail=1\n    continue\n  fi\n  in_formula=$(awk -v p=\"$p\" '\n    index($0, \"/\" p \"\\\"\") { seen = 1; next }\n    seen && /sha256 \"/    { gsub(/.*sha256 \"|\".*/, \"\"); print; exit }\n  ' homebrew/archon.rb)\n  if [ \"$in_formula\" = \"$real\" ]; then\n    echo \"  OK   $p\"\n  else\n    echo \"  BAD  $p: formula has '$in_formula', release has '$real'\"\n    fail=1\n  fi\ndone\n\n[ \"$fail\" -eq 0 ] || { echo \"formula verification FAILED — do not push\"; exit 1; }\necho \"formula verified against v$VERSION checksums\"\n```\n\nEvery line must print `OK` and the version must match. On any `BAD`, regenerate\nthe formula from the Step 10 template rather than hand-editing — a hand edit is\nhow a digest ends up under the wrong platform in the first place.\n\nFinally, confirm convergence. **Fail closed** — Step 11 publishes to the tap that\nusers install from, so do not proceed on an unconverged tree:\n\n```bash\ngit fetch origin\nconverged=1\ngit merge-base --is-ancestor origin/main origin/dev \\\n  || { echo \"DIVERGED: dev does not contain main\"; converged=0; }\nstranded=$(git log origin/dev..origin/main --oneline | wc -l | tr -d ' ')\n[ \"$stranded\" -eq 0 ] || { echo \"STRANDED: $stranded commit(s) on main not on dev:\"; \\\n  git log origin/dev..origin/main --oneline; converged=0; }\n[ \"$converged\" -eq 1 ] || { echo \"sync incomplete — resolve before Step 11\"; exit 1; }\necho \"branches converged — OK\"\n```\n\n### Step 11: Sync the Homebrew Tap Repo\n\nThe `coleam00/homebrew-archon` repository hosts the actual tap formula that Homebrew reads when users run `brew tap coleam00/archon && brew install coleam00/archon/archon`. The file `coleam00/Archon/homebrew/archon.rb` is the source-of-truth template; the file `coleam00/homebrew-archon/Formula/archon.rb` is what users actually install from. These must be kept in sync.\n\n```bash\nTAP_DIR=$(mktemp -d)\ngit clone git@github.com:coleam00/homebrew-archon.git \"$TAP_DIR\"\ncp homebrew/archon.rb \"$TAP_DIR/Formula/archon.rb\"\n\ncd \"$TAP_DIR\"\nif git diff --quiet; then\n  echo \"Tap formula already matches — no sync needed\"\nelse\n  git add Formula/archon.rb\n  git commit -m \"chore: sync formula to vx.y.z\"\n  git push origin main\nfi\ncd -\nrm -rf \"$TAP_DIR\"\n```\n\nIf the `git clone` fails with a permissions error, the user running the release skill does not have push access to `coleam00/homebrew-archon`. Ask them to request push access from the repo owner, or to perform the sync manually via the GitHub web UI. Do not skip this step silently — the release is not complete until the tap is synced.\n\n### Step 12: Verify the Release End-to-End\n\nAfter the formula is synced, the final verification step is to actually install the released binary via Homebrew and run smoke tests. Use the `test-release` skill:\n\n```\n/test-release brew x.y.z\n```\n\nThis will:\n- Install via `brew tap coleam00/archon && brew install coleam00/archon/archon`\n- Verify the binary reports the correct version and `Build: binary`\n- Verify bundled workflows load\n- Verify the SDK spawn path works (a minimal archon-investigate run)\n- Verify the env-leak guard strips sensitive keys from a leaky .env (Test 4)\n- Uninstall cleanly\n- Produce a PASS/FAIL report\n\n**If `/test-release brew` fails, the release is not ready to announce.** File a hotfix issue for whatever broke, cut `x.y.z+1` with the fix, and re-run this skill. Do NOT advertise a release that fails `test-release`.\n\nAlso run `/test-release curl-mac x.y.z` to cover the curl install path. The two install paths test slightly different things (Homebrew tests the tap formula, curl tests `install.sh` and checksums from the release) and both need to work for users to have a reliable install experience.\n\nIf you have a VPS available, also run `/test-release curl-vps x.y.z <vps-target>` to verify the Linux binary.\n\n## Recovery: deterministic release CI failure\n\nReached here because Step 10 detected two consecutive workflow failures. The tag `vx.y.z` is pushed, the GitHub release exists, but assets are missing or incomplete. Every `install.sh` run currently resolves `releases/latest` to this broken release and 404s on download. Homebrew users are safe because Step 10's atomic formula update was blocked.\n\n**Do not re-run the release workflow a third time hoping it succeeds.** If the failure was reproducible twice, it's a code bug — you need to ship code to fix it.\n\n### Immediate mitigation (restore `install.sh`)\n\nDelete the GitHub Release so `releases/latest` falls back to the previous version. Keep the git tag — tag immutability matters and there are no shipped artifacts pointing at it anyway.\n\n```bash\ngh release delete \"vx.y.z\" --yes\n# Do NOT delete the tag:\n#   git push --delete origin vx.y.z   ← do not run\n# Tag stays so git history records the attempt; no release means no assets\n# means releases/latest resolves to the prior working release.\n```\n\nVerify:\n\n```bash\ngh api repos/coleam00/Archon/releases/latest --jq '.tag_name'\n# should now print the prior version (e.g. v0.3.6), not vx.y.z\n```\n\n### Diagnose\n\nThe release workflow logs tell you which target failed and at what stage (compile vs. smoke-test vs. upload):\n\n```bash\ngh run list --workflow release.yml --limit 2 --json databaseId,conclusion\ngh run view <RUN_ID> --log-failed\n```\n\nCommon causes:\n- **Bundler/bytecode bug** — Bun `--bytecode` produces invalid output for the current module graph. Symptom: `TypeError: Expected CommonJS module to have a function wrapper` at binary startup. Historically caused by a new dependency's CJS/ESM shape interacting with `--bytecode` — dropping the flag or lazy-importing the offending module has been the fix.\n- **Module-init crash** — a dependency does `readFileSync('package.json')` or similar at module top level via a path that exists in `node_modules/` but not next to a compiled binary. Symptom: every binary subcommand crashes immediately; error typically mentions a missing file adjacent to `process.execPath`. Fix by lazy-importing the dependency behind the code path that actually uses it.\n- **Smoke-test timeout on Windows** — not actually a bug in the code; the Windows runner is slow. Rerun once; if it recurs, bump the test timeout.\n\nStep 1.5 now runs a local compiled-binary smoke test before any user-visible step. If the failure mode above reproduces locally, you've found it. If it doesn't, the bug is platform-specific (Windows cross-compile, Linux glibc, etc.) and you need the CI logs.\n\n### Fix and re-release as the NEXT patch\n\n**Do not reuse `vx.y.z`.** Cut `vx.y.(z+1)` (or next-minor if warranted) with the fix. Rationale:\n- Tag immutability: `vx.y.z` is already recorded in git history and release cache\n- Semver clarity: users and tooling should see a new version number when the bits change\n- Audit trail: \"v0.3.7 was cut but had no shipped binaries; v0.3.8 is the first release with <fix>\" is cleaner than rewriting v0.3.7\n\nSteps:\n\n1. Cut a fix branch off dev, implement the fix, PR to dev, merge.\n2. Re-run `/release` (it will bump to the next patch — e.g. `0.3.8` — automatically).\n3. Step 1.5's pre-flight smoke will catch the same bug locally if the fix didn't actually fix it. Iterate until it passes before tagging.\n\n### CHANGELOG note for the hotfix release\n\nInclude a line in the new release's CHANGELOG that references the broken prior version so users understand why there's no binary artifact under that tag:\n\n```markdown\n### Fixed\n\n- **First release with working compiled binaries after vx.y.z's <bug>.** vx.y.z was tagged but its binary smoke test failed deterministically (see RUN_ID in CI history). The tag is preserved for history; this release (vx.y.(z+1)) is the first with shipped binaries. `install.sh` and Homebrew were never updated to vx.y.z, so users were not exposed to the broken state.\n```\n\n### What NOT to do\n\n- **Do not force-push or rewrite the tag.** Once a tag exists, it's a public promise of that SHA. Deleting and re-creating to a different SHA is tag-spoofing and breaks any downstream that cached the original.\n- **Do not skip this recovery path to \"just push more binaries to the broken release\".** The release exists with a specific commit SHA; uploading binaries built from a newer SHA creates binary/source drift that is hard to diagnose later.\n- **Do not update the Homebrew formula before v0.3.(z+1) is fully shipped.** The formula should always point at a version with all 7 assets uploaded and `/test-release brew` passing.\n\n## Important Rules\n\n- NEVER force push\n- **NEVER derive the changelog from `git log main..dev`.** main squash-merges, so\n  that range accumulates every previously-released commit and grows each release\n  (61 vs 25 actual on 0.7.1). Use dev's last `Release x.y.z` commit as the\n  boundary, and cross-check the result against the previous version's changelog\n  entries — any PR number appearing in both means the boundary is wrong.\n- **ALWAYS pass `--no-rebase` to `git pull`.** The bare form aborts with\n  `Need to specify how to reconcile divergent branches` unless `pull.rebase` is\n  configured, and a `pull.rebase=true` config would rewrite dev's history.\n- **ALWAYS verify sync convergence rather than assuming it.**\n  `git merge-base --is-ancestor origin/main origin/dev` after every sync step.\n  A commit made directly to main stays stranded silently otherwise.\n- **NEVER trust a clean `homebrew/archon.rb` merge.** The CI `update-homebrew`\n  job edits the same `sha256` lines on dev that you edit on main; the merge can\n  succeed and still be wrong. Diff the four values against the published\n  `checksums.txt` before pushing.\n- **Flag a version bump that contradicts the commit range.** If `patch` was\n  requested but the range contains `feat:` commits or new user-facing CLI\n  surface, say so at the Step 7 review and let the user decide — do not silently\n  ship features as a patch.\n- **NEVER skip Step 1.5 (pre-flight compiled-binary smoke).** If the stack is a Bun/Node project with a build-binaries script, the `bun build --compile` smoke test runs before version bump, PR, or tag. Skipping it means every bundler regression or module-init crash only surfaces after the tag is pushed — by which point `releases/latest` is already 404-ing for every user. The ~30s cost is paid to keep the failure mode local.\n- If Step 1.5 fails, **abort the release** and fix the underlying issue on a feature branch. Do not \"just skip it\" and hope CI doesn't repro the problem.\n- NEVER skip the review step — always show the changelog before committing\n- NEVER include \"Co-Authored-By: Claude\" or any AI attribution in the commit\n- NEVER add emoji to changelog entries unless the user asks\n- If the user says \"ship it\" without specifying bump type, default to patch\n- The commit message is just `Release x.y.z` — clean and simple\n- **NEVER update `homebrew/archon.rb` version field without also updating the `sha256` values**. They must move together atomically. The correct SHAs only exist after the release workflow finishes building binaries — see Step 10. Updating the version field alone produces a stale formula that looks valid but causes checksum mismatches on install.\n- **NEVER skip Step 11 (tap sync).** The `coleam00/Archon/homebrew/archon.rb` file is only a template; users install from `coleam00/homebrew-archon/Formula/archon.rb`. If you update one without the other, users get stale or wrong data.\n- **NEVER announce a release that failed `/test-release brew`.** A release that installs but crashes on first invocation is worse than no release — it burns user trust. If the release verification fails, cut a hotfix before telling anyone the release exists.\n\n<!-- File: .claude/skills/rulecheck/rules-guide.md (Tokens: ~161 | Category: anthropic-skill) -->\n# Rules Guide — Where to Find Project Rules\n\nReference for the rulecheck agent. Focus on rules that **linters can't enforce** —\narchitectural principles, patterns, and conventions from AGENTS.md.\n\n## Primary Source: `AGENTS.md` (Root)\n\nRead this file thoroughly, but derive checks only from its normative rules —\nsections like `Project Overview` and `Product Direction` are descriptive\ncontext, not something to enforce. Root `CLAUDE.md` is only a one-line pointer\nto this file — reading that instead gets you nothing.\n\n### Engineering Principles (each is a concrete rule, not a slogan)\n\nRead `AGENTS.md` for the repository's engineering rules.\n\nAlso read `.archon/engineering.md` for the aspirational engineering standard.\n\n<!-- File: .claude/skills/rulecheck/SKILL.md (Tokens: ~460 | Category: anthropic-skill) -->\n---\nname: rulecheck\ndescription: |\n  Autonomous rule adherence checker. Scans the codebase for rule violations,\n  fixes the highest-impact ones in an isolated worktree, runs full validation,\n  creates a PR. Uses memory to track progress across runs.\ndisable-model-invocation: true\nagent: rulecheck-agent\nargument-hint: \"[focus area]\"\n---\n\n# Rulecheck\n\nLaunch the rulecheck-agent to autonomously scan and fix AGENTS.md rule violations.\n\n## Your Job (Main Agent)\n\nYou are the orchestrator. Your ONLY job is to launch the rulecheck-agent and\nreport its results when it completes. You do NOT do the scanning or fixing yourself.\n\n1. **Launch the rulecheck-agent** with the focus area (if any): `$ARGUMENTS`\n2. **Wait for it to complete** — do NOT poll, tail, or check on it. You will be\n   notified automatically when it finishes.\n3. **Report ONLY what the agent told you** — relay its final message verbatim\n   or summarize it. Do NOT search for PRs, read files, or fabricate results.\n   If the agent hit its context limit, say so. If it didn't produce a PR URL\n   in its output, don't go looking for one.\n\n## Rules for You\n\n- **Do NOT scan the codebase yourself** — that's the agent's job\n- **Do NOT grep, read source files, or run linters** — the agent handles all of that\n- **Do NOT edit any files** — you are the orchestrator, not the fixer\n- **Do NOT try to resume or check on the agent** while it's running — just wait\n- **Do NOT do the agent's work if it fails or hits context limits** — report the failure to the user and stop. NEVER pick up where the agent left off. You are not in a worktree and would be editing main directly.\n- **Do NOT update project memory** — the agent maintains its own memory at `.claude/agent-memory/rulecheck-agent/`. Do not duplicate run results into your project memory.\n- Trust the agent. It runs in an isolated worktree and will create a PR when done.\n\n<!-- File: .claude/skills/rulecheck/workshop-guide.md (Tokens: ~2723 | Category: anthropic-skill) -->\n# Rulecheck — Workshop Guide\n\n## What This Skill Demonstrates\n\nAn autonomous code quality agent that combines **17 Claude Code features** into one\ngenuinely useful tool: custom agents, safety hooks, all four hook types (command,\nprompt, http, agent), LLM-as-judge, persistent memory, Slack notifications, and\nworktree isolation.\n\nReplaces the existing advisory-only `code-rulecheck` agent with a fully autonomous\nversion that finds violations, fixes them, validates, creates PRs, and learns.\n\n**Full demo steps**: See [part2-guide.md](../../workshop/part2-guide.md) — Feature 8.\n\n## Architecture\n\n```\nUser invokes /rulecheck [focus area]\n        │\n        ▼\n┌──────────────────────────┐\n│  SKILL.md                │  agent: rulecheck-agent\n│  (orchestrator)          │  disable-model-invocation: true\n│                          │  argument-hint: \"[focus area]\"\n│  Launches agent,         │\n│  reports results         │  NO context: fork\n└────────┬─────────────────┘\n         │ delegates to agent\n         ▼\n┌──────────────────────────┐\n│  rulecheck-agent.md     │  isolation: worktree\n│  (.claude/agents/)       │  background: true\n│                          │  memory: project\n│  model: sonnet           │  permissionMode: acceptEdits\n│  maxTurns: 500           │\n│                          │\n│  hooks:                  │\n│  ├─ PreToolUse [Bash]    │──→ block-dangerous.sh (safety gate)\n│  ├─ PostToolUse [Edit]   │──→ bun run lint:fix (auto-fix)\n│  └─ Stop                 │\n│     ├─ command           │──→ slack-notify.sh (reads summary JSON)\n│     ├─ http              │──→ POST to Slack webhook URL\n│     └─ agent             │──→ meta-judge (LLM evaluation)\n└────────┬─────────────────┘\n         │ works in worktree\n         ▼\n┌──────────────────────────┐\n│  Worktree                │\n│  1. Read memory          │\n│  2. Read AGENTS.md rules │\n│  3. Deep scan source     │\n│  4. Group violations     │\n│  5. Fix one group        │\n│  6. Check own changes    │\n│  7. Write summary JSON   │\n│  8. gh pr create         │\n│  9. Update memory        │\n└──────────────────────────┘\n         │\n         ▼\n   Results reported to main conversation\n   + PR created + Slack notified + memory updated\n```\n\n## Features to Walk Through\n\n### 1. `agent: rulecheck-agent` — Custom Agent Delegation (no fork)\n\n**What is it?** The `agent:` field delegates to a custom agent with its own\nsystem prompt, model, hooks, and memory configuration.\n\n**In this skill**: The skill acts as an **orchestrator** — it launches the agent\nand reports results. There is no `context: fork`. The skill stays active in the\nmain conversation while the agent runs in the background in its own worktree.\n\n**Why no fork?** The rulecheck agent uses `background: true` and\n`isolation: worktree`, so it already runs in a completely separate context.\nForking would add an unnecessary layer. The skill just launches and waits.\n\n### 2. `disable-model-invocation: true` — User-Only Trigger\n\n**What is it?** Prevents Claude from auto-invoking this skill. Since it creates\nPRs and pushes branches (side effects), only the user should trigger it.\n\n### 3. `argument-hint: \"[focus area]\"` — Usage Hint\n\n**What is it?** Shows in `/help` and tab completion to tell users what arguments\nthe skill accepts. Optional — the agent works without arguments too.\n\n### 4. `!`command`` — Dynamic Context Injection\n\n**What is it?** Shell commands that execute at skill load time (before Claude sees\nthe prompt). Stdout replaces the placeholder.\n\n**In this skill**: Injects current branch, recent git log, and lint status so the\nagent starts with real context instead of having to fetch it.\n\n### 5. Supporting Files — On-Demand Loading\n\n**What is it?** Markdown files linked from the skill body with `[name](file.md)`.\nClaude loads them only when needed, keeping the initial prompt small.\n\n**In this skill**: `rules-guide.md` documents where to find rules and how to rank\nviolations. The agent loads it during the scan phase.\n\n### 6. `isolation: worktree` — Git Worktree Isolation\n\n**What is it?** The agent works in a temporary git worktree — a separate working\ndirectory with its own branch. Changes are isolated from the main repo.\n\n**In this skill**: The agent edits files and creates commits without affecting the\nuser's working directory. If something goes wrong, the worktree is disposable.\n\n### 7. `background: true` — Concurrent Execution\n\n**What is it?** The agent runs in the background. The user can continue working\nin the main conversation while the agent scans, fixes, and creates a PR.\n\n### 8. `memory: project` — Persistent Memory\n\n**What is it?** The agent has a persistent memory directory at\n`.claude/agent-memory/rulecheck-agent/`. It reads from and writes to `MEMORY.md`\nacross runs.\n\n**In this skill**: The agent remembers what it fixed last time, what's in the\nbacklog, and meta-judge feedback — so each run builds on the previous one.\n\n### 9. `permissionMode: acceptEdits` — Auto-Accept Edits\n\n**What is it?** File edits (Edit/Write tools) are automatically approved without\nuser confirmation. Other tools still require approval per the normal flow.\n\n**In this skill**: The agent needs to edit many files to fix violations. Prompting\nfor each edit would defeat the purpose of autonomous execution.\n\n### 10. `maxTurns: 500` — Safety Cap\n\n**What is it?** Limits the agent to 500 API round-trips. Prevents runaway agents\nthat loop endlessly. Set high here because the agent does a lot of work (scan,\nfix, validate, commit, push, PR, memory update).\n\n### 11. `hooks:` in Agent Frontmatter\n\n**What is it?** Hooks defined directly in the agent's YAML frontmatter. They\nactivate whenever this agent runs, regardless of which skill invokes it.\n\nThis skill demonstrates all four hook types:\n\n#### 11a. `type: \"command\"` — Shell Script Hooks\n\n**PreToolUse [Bash]**: `block-dangerous.sh` reads the command from stdin, checks\nagainst a blocklist (force push, git clean, hard reset, rm -rf), and exits 2 to\nblock dangerous commands.\n\n**PostToolUse [Edit|Write]**: Runs `bun run lint:fix` after every file edit to\nauto-correct formatting issues before they accumulate.\n\n**Stop**: `slack-notify.sh` reads the agent's summary file and sends a formatted\nSlack message with what was fixed, PR link, and remaining opportunities. This\ndemonstrates **inter-hook communication** — the agent writes a JSON file, the\nhook reads it.\n\n#### 11b. `type: \"http\"` — HTTP Webhook\n\n**Stop**: POSTs the event directly to a Slack webhook URL. No script needed —\njust a URL in the frontmatter. This is the simplest way to notify external\nservices. Compare with 11a above: the command hook gives you full control over\nthe message format, while the HTTP hook sends the raw event with zero code.\n\n#### 11c. `type: \"agent\"` — LLM Meta-Judge\n\n**Stop**: A third Stop hook spawns a subagent that evaluates the rulecheck's\nexecution. It reviews what was fixed, assesses prioritization quality, and writes\nstructured feedback to memory for the next run.\n\n### 12. `statusMessage` — Hook Progress Indicators\n\n**What is it?** A string shown in the Claude Code spinner while a hook runs.\nGives users visibility into what's happening during hook execution.\n\n**In this skill**: \"Checking command safety...\", \"Auto-fixing lint issues...\",\n\"Notifying Slack...\", \"Posting run event to Slack...\", \"Running meta-judge evaluation...\"\n\n### 13. `$ARGUMENTS` — Skill Arguments\n\n**What is it?** The text after the skill name (e.g., `/rulecheck error handling`)\nis available as `$ARGUMENTS` in the skill body and passed through to the agent.\n\n### 14. `${CLAUDE_SESSION_ID}` — Session Identifier\n\n**What is it?** Environment variable with the current session ID. Used in the\nmeta-judge prompt to tag feedback with the session that produced it.\n\n### 15. Summary File Pattern — Inter-Hook Communication\n\n**What is it?** The agent writes `.claude/archon/rulecheck-last-run.json` as a\nstructured summary. The Slack hook reads this file to format its notification.\n\nThis is a practical pattern for passing data between the agent and its hooks\nwhen the hook needs more than what's in the event JSON.\n\n## Live Demo Steps\n\n1. **Show the architecture** — open all files:\n   - `.claude/skills/rulecheck/SKILL.md` (skill entry point)\n   - `.claude/agents/rulecheck-agent.md` (the autonomous agent)\n   - `.claude/skills/rulecheck/hooks/block-dangerous.sh` (safety gate)\n   - `.claude/skills/rulecheck/hooks/slack-notify.sh` (Slack notification)\n   - `.claude/skills/rulecheck/rules-guide.md` (supporting reference)\n\n2. **Trace the delegation chain**:\n   skill → `agent: rulecheck-agent` → `isolation: worktree` + `background: true`\n\n3. **Show the hooks**: PreToolUse safety gate, PostToolUse lint auto-fix,\n   Stop Slack (command + http) + meta-judge (agent)\n\n4. **Test the safety hook**:\n   ```bash\n   echo '{\"tool_input\":{\"command\":\"git push --force\"}}' | .claude/skills/rulecheck/hooks/block-dangerous.sh\n   # Should exit 2 with error message\n   echo '{\"tool_input\":{\"command\":\"bun run lint\"}}' | .claude/skills/rulecheck/hooks/block-dangerous.sh\n   # Should exit 0\n   ```\n\n5. **Invoke**: `/rulecheck type safety`\n   - Show: background execution, user can keep working\n   - Show: the agent scanning, fixing, validating in the worktree\n   - Show: PR creation and Slack notification\n\n6. **Show the outputs**:\n   - PR on GitHub\n   - Slack notification\n   - Memory file (what was learned)\n   - Meta-judge feedback\n\n## Comparison: Before vs After\n\n| | Advisory Code-Rulecheck | Autonomous Rulecheck |\n|---|---|---|\n| Format | `.claude/agents/code-rulecheck.md` | Skill + agent + hooks |\n| Execution | Inline, blocks conversation | Background, worktree-isolated |\n| Output | Advisory report (no changes) | Actual fixes + PR |\n| Isolation | None (reads in-place) | Git worktree |\n| Safety | None | PreToolUse command blocklist |\n| Validation | None | Tests, lint, type-check on changes; CI is the full gate |\n| Notifications | None | Slack (command hook + HTTP hook) |\n| Learning | None | Persistent memory + meta-judge |\n| Autonomy | Reports findings only | Finds, fixes, validates, PRs |\n\n## Talking Points\n\n- \"This is 17 features in one skill. Each one is simple — the power is in composition.\"\n- \"The safety hook is a shell script. It reads JSON, checks a blocklist, exits 2 to block. No framework needed.\"\n- \"Memory makes the agent better over time. Each run builds on the last — it remembers what's in the backlog.\"\n- \"The meta-judge is an LLM evaluating another LLM. It writes structured feedback the agent reads next run.\"\n- \"Worktree isolation means the agent can break things safely. Your working directory is untouched.\"\n- \"Background execution means you keep working. The agent runs, creates a PR, notifies Slack — you review when ready.\"\n- \"The old code-rulecheck was advisory. This one actually fixes things. Same domain, fundamentally different capability.\"\n\n<!-- File: .claude/skills/test-release/SKILL.md (Tokens: ~5999 | Category: anthropic-skill) -->\n---\nname: test-release\ndescription: |\n  Verify a released archon binary works end-to-end via a specific install path.\n  Use when: cutting a new release, reproducing a user bug report on the released\n  version, or validating that a hotfix binary actually works after a re-tag.\n  Triggers: \"test the release\", \"test 0.3.1 via brew\", \"verify the curl install\",\n            \"smoke test the binary\", \"did the release binary work\",\n            \"run /test-release\", \"verify the release\".\n  NOT for: testing dev work (use bun link directly), testing unreleased changes\n  (build locally via scripts/build-binaries.sh first), or running the full\n  validate suite (bun run validate is separate).\nargument-hint: \"[brew|curl-mac|curl-vps] [optional: version to verify] [optional: vps-target]\"\n---\n\n# Test Release\n\nAutomated smoke test for a released archon binary. Covers three install paths:\n\n- `brew` — Homebrew tap on macOS (tests the formula and checksums)\n- `curl-mac` — `curl install.sh` on macOS (tests the install script, sandboxed to a temp dir)\n- `curl-vps` — `curl install.sh` on a remote Linux VPS (tests the Linux binary and full install path)\n\nEvery path installs the binary, runs a fixed smoke test suite, and cleans up. The dev `bun link` binary is never touched and remains the default `archon` on PATH throughout.\n\n**When NOT to use this skill:**\n\n- There is no release yet — build a local binary via `bash scripts/build-binaries.sh` and run it from `dist/binaries/` directly\n- You want to test the dev clone — use `bun run validate` or invoke source directly via `bun packages/cli/src/cli.ts`\n- You want to test the full server + web UI deploy flow — use the cloud-init from `deploy/cloud-init.yml` on a real VPS\n\n## Local build for pre-release QA\n\nTo build a binary locally with the exact same flags and constants that CI uses,\ninvoke `scripts/build-binaries.sh` directly. The script supports two modes:\n\n```bash\n# Multi-target mode (builds all 4 local platforms into dist/binaries/)\nVERSION=0.3.1 GIT_COMMIT=abc12345 bash scripts/build-binaries.sh\n\n# Single-target mode (matches one CI matrix job)\nVERSION=0.3.1 \\\nGIT_COMMIT=abc12345 \\\nTARGET=bun-darwin-arm64 \\\nOUTFILE=dist/test-archon-darwin-arm64 \\\nbash scripts/build-binaries.sh\n\n# Verify the binary — use the path from the mode you built:\n#   multi-target → ./dist/binaries/archon-darwin-arm64\n#   single-target → the OUTFILE you passed above\n./dist/test-archon-darwin-arm64 version\n# Expected: Archon CLI v0.3.1, Build: binary, Git commit: abc12345\n```\n\nRun this **before tagging a release** to catch build-time-constant issues\nlocally. The script is the canonical entry point — both local dev and the\nrelease workflow call it the same way, so a green local build means the CI\nbuild will exercise the same code path.\n\n## Phase 1 — Determine scope\n\nParse the arguments. The skill takes up to three:\n\n1. **Install path** (`brew` | `curl-mac` | `curl-vps`): which install flow to exercise\n2. **Expected version** (optional): the version tag the release should report, e.g. `0.3.1`. If not provided, fetch it:\n\n```bash\ngh release list --repo coleam00/Archon --limit 1 --json tagName --jq '.[0].tagName'\n```\n\n3. **VPS target** (only for `curl-vps`): SSH target in the form `user@host` or `host` (uses default SSH config)\n\nIf any argument is missing, ask the user for clarification BEFORE doing anything. Never guess the install path or the expected version.\n\nConfirm the plan with the user before proceeding to Phase 2. Output should look like:\n\n```\nAbout to test:\n  Path:     brew (Homebrew tap on macOS)\n  Version:  0.3.1 (expected)\n  Cleanup:  will uninstall after tests (brew uninstall + untap)\n            If `archon-stable` symlink is detected in Phase 2, it will be\n            restored at the end of Phase 5 by reinstalling the tap formula.\n\nProceed? (y/N)\n```\n\nDo not continue without explicit confirmation. Release testing touches install state and the user should be aware.\n\n## Phase 2 — Pre-flight\n\nBefore touching anything:\n\n1. Capture the current dev binary state for reference:\n\n```bash\nwhich -a archon\narchon version 2>&1 | head -5\n```\n\nRecord the path and version of the dev binary so the final report can show \"dev binary was untouched\".\n\n2. Verify prerequisites for the chosen path:\n\n   - **brew**: `brew --version` must succeed. If not, abort with \"Homebrew not installed — see https://brew.sh/\"\n   - **curl-mac**: `curl --version` must succeed (effectively always true on macOS)\n   - **curl-vps**: `ssh <target> 'uname -a'` must succeed. If not, abort with \"Cannot SSH to <target>\". Also verify `ssh <target> 'command -v curl'` returns a path.\n\n3. Confirm the release exists on GitHub:\n\n```bash\ngh release view v<version> --repo coleam00/Archon --json tagName,assets --jq '{tag: .tagName, assetCount: (.assets | length)}'\n```\n\nIf the release does not exist or has no assets, abort with a clear message. Do not proceed to install a non-existent release.\n\n4. **Detect persistent `archon-stable` install (brew path only).** If the user has renamed a prior brew install to `archon-stable` (the dual-homebrew pattern — see `~/.config/fish/functions/brew-upgrade-archon.fish`), Phase 5's `brew uninstall` will wipe it. Capture the state so Phase 5b can restore it:\n\n```bash\nARCHON_STABLE_WAS_INSTALLED=\"\"\nif [ -L /opt/homebrew/bin/archon-stable ] || [ -L /usr/local/bin/archon-stable ]; then\n  ARCHON_STABLE_WAS_INSTALLED=\"yes\"\n  echo \"Detected persistent archon-stable — will restore after Phase 5 uninstall.\"\nfi\n```\n\nExport `ARCHON_STABLE_WAS_INSTALLED` into the environment used by Phase 5b. Only applies to the `brew` path — `curl-mac` and `curl-vps` don't go through brew and don't disturb `archon-stable`.\n\n## Phase 3 — Install\n\n### Path: brew\n\n```bash\nbrew tap coleam00/archon\nbrew install coleam00/archon/archon\nBINARY=\"$(brew --prefix coleam00/archon/archon)/bin/archon\"\n```\n\nCapture `$BINARY` for Phase 4. Verify the file exists and is executable.\n\n### Path: curl-mac\n\nInstall to a dedicated tmp directory so the dev `bun link` binary stays on PATH unchanged:\n\n```bash\nINSTALL_DIR=/tmp/archon-test-release-$(date +%s)\nmkdir -p \"$INSTALL_DIR\"\ncurl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | INSTALL_DIR=\"$INSTALL_DIR\" bash\nBINARY=\"$INSTALL_DIR/archon\"\n```\n\n> **The env var must prefix `bash`, not `curl`.** In `VAR=x cmd1 | cmd2` the\n> assignment applies only to `cmd1`, so `INSTALL_DIR=… curl … | bash` sets it for\n> the *download* and leaves the *installer* on its `/usr/local/bin` default. The\n> failure is confusing rather than obvious: the script downloads and verifies the\n> checksum, then demands sudo and dies with\n> `sudo: a terminal is required to read the password`. Observed on the 0.7.1 test.\n>\n> `scripts/install.sh` carries the same broken form in its own header example\n> (`INSTALL_DIR=~/.local/bin curl -fsSL ... | bash`), so users copying it hit this\n> too. Fix both together; see issue #2436.\n\nVerify `$BINARY` exists and is executable. Capture the install directory for cleanup.\n\n### Path: curl-vps\n\nRun the install script on the VPS:\n\n```bash\nssh <target> 'curl -fsSL https://raw.githubusercontent.com/coleam00/Archon/main/scripts/install.sh | bash'\n```\n\nDetermine where the binary landed — `install.sh` uses `/usr/local/bin/archon` by default, or falls back to `$HOME/.local/bin/archon` if `/usr/local/bin` is not writable:\n\n```bash\nssh <target> 'command -v archon'\n```\n\nCapture the remote path as `$REMOTE_BINARY`. For the rest of Phase 4, wrap every command as `ssh <target> '<cmd>'`.\n\n### Capture SHA256 and version\n\nImmediately after install, capture:\n\n```bash\n# Local paths (brew / curl-mac)\nshasum -a 256 \"$BINARY\" | awk '{print $1}'\n\"$BINARY\" version 2>&1\n\n# Remote path (curl-vps)\nssh <target> \"shasum -a 256 $REMOTE_BINARY || sha256sum $REMOTE_BINARY\" | awk '{print $1}'\nssh <target> \"$REMOTE_BINARY version\" 2>&1\n```\n\nRecord both for the report. The SHA256 lets us confirm later that a user reporting a bug is running the exact same artifact we tested.\n\n## Phase 4 — Smoke tests\n\nRun these in order against `$BINARY` (or `ssh <target> $REMOTE_BINARY` for curl-vps). **Always use the full binary path, never the `archon` on PATH**, so there is no ambiguity about which binary is under test.\n\nEach test should capture the full command output for the final report. If a test fails, continue to the next test (so the report is complete) but mark the overall result as FAIL.\n\n### Test 1 — Version reports correctly\n\n```bash\n\"$BINARY\" version\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- Output contains `Archon CLI v<expected-version>`\n- Output contains `Build: binary` (not `Build: source (bun)`)\n- Output contains a non-`unknown` git commit (i.e., `Git commit: <sha>`)\n\n**Common failures:**\n\n- Exit code non-zero → pino-pretty crash (#960) or similar startup failure\n- Wrong version reported → binary is stale or the build script failed to update `bundled-build.ts`\n- `Build: source (bun)` → `BUNDLED_IS_BINARY` was not set to `true` during the build (regression of #979)\n- `Git commit: unknown` → build script did not capture the commit\n\n### Test 2 — Bundled workflows load\n\nFrom 0.12.0, release binaries ship without providers, and a config that names one fails to load until it's installed. Run `\"$BINARY\" provider add claude codex pi` (the ones the config uses) before this test. That is the documented upgrade step, and it is part of what this test checks.\n\nCreate a temporary git repository so the CLI has something to operate on:\n\n```bash\nTESTREPO=/tmp/archon-test-repo-$(date +%s)\nmkdir -p \"$TESTREPO\"\ncd \"$TESTREPO\"\ngit init -q\ngit commit -q --allow-empty -m init\n\"$BINARY\" workflow list\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- Output lists the bundled `sdlc` pack workflows: archon-triage, archon-investigate, archon-plan, archon-implement, archon-pr, archon-review, archon-validate, archon-deliver, archon-upkeep, archon-ship\n- No errors about missing workflow files or JSON parse failures\n\n**Common failures:**\n\n- Empty list → bundled defaults were not embedded in the binary (regression of the `isBinaryBuild` detection path)\n- `Not in a git repository` → working directory handling bug\n- Parse errors → the embedded JSON is corrupt or stale\n\n### Test 3 — SDK path works (archon-investigate workflow)\n\n**Prerequisite.** Compiled binaries require Claude Code installed on the host and a configured binary path. Before running this test, ensure one of:\n\n```bash\n# Option A — env var (easy for ad-hoc testing)\n# After the native installer (Anthropic's default):\nexport CLAUDE_BIN_PATH=\"$HOME/.local/bin/claude\"\n# Or after npm global install:\nexport CLAUDE_BIN_PATH=\"$(npm root -g)/@anthropic-ai/claude-code/cli.js\"\n\n# Option B — config file (persistent)\n#   Add to ~/.archon/config.yaml:\n#   assistants:\n#     claude:\n#       claudeBinaryPath: /absolute/path/to/claude\n```\n\nThen in the same `$TESTREPO`:\n\n```bash\n\"$BINARY\" workflow run archon-investigate --no-worktree \"say hello and nothing else\" 2>&1 | tee /tmp/archon-test-investigate.log\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- The Claude subprocess spawns successfully (no `spawn EACCES`, `ENOENT`, or `process exited with code 1` in the early output)\n- No `Claude Code CLI not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)\n- A response is produced (any response — even just \"hello\" — proves the SDK round-trip works)\n\n**Common failures:**\n\n- `Claude Code not found` → `CLAUDE_BIN_PATH` / `claudeBinaryPath` is unset or points at a non-existent file. Fix the path and re-run.\n- `Module not found \"/Users/runner/...\"` → regression of #1210: the resolver was bypassed and the SDK's `import.meta.url` fallback leaked a build-host path. Investigate `packages/providers/src/claude/provider.ts` and the resolver.\n- `Credit balance is too low` → auth is pointing at an exhausted API key (check `CLAUDE_USE_GLOBAL_AUTH` and `~/.archon/.env`)\n- `unable to determine transport target for \"pino-pretty\"` → #960 regression, binary crashes on TTY\n- `package.json not found (bad installation?)` → #961 regression, `isBinaryBuild` detection broken\n- Process exits before producing output → generic spawn failure, capture stderr\n\n### Test 3b — Resolver error path (run without `CLAUDE_BIN_PATH`)\n\nQuickly verify the resolver fails loud when nothing is configured:\n\n```bash\n(unset CLAUDE_BIN_PATH; \"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" 2>&1 | tee /tmp/archon-test-no-path.log)\n```\n\n**Pass criteria (when no `~/.archon/config.yaml` configures `claudeBinaryPath`):**\n\n- Error message contains `Claude Code not found`\n- Error message mentions both `CLAUDE_BIN_PATH` and `claudeBinaryPath` as remediation options\n- No `Module not found` stack traces referencing the CI filesystem\n\nIf you *do* have `claudeBinaryPath` set globally, skip this test or temporarily rename `~/.archon/config.yaml`.\n\n### Test 4 — Env-leak guard strips sensitive keys from .env\n\nCreate a second throwaway repo with a fake sensitive key:\n\n```bash\nLEAKREPO=/tmp/archon-test-leak-$(date +%s)\nmkdir -p \"$LEAKREPO\"\ncd \"$LEAKREPO\"\ngit init -q\ngit commit -q --allow-empty -m init\nprintf 'ANTHROPIC_API_KEY=sk-ant-test-fake\\n' > .env\n\"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" 2>&1 | tee /tmp/archon-test-leak.log\n```\n\n**Pass criteria** (current behaviour — the guard **strips**, it does not refuse):\n\n- Output contains a strip line naming the repo and the source file, of the form:\n  `[archon] stripped N keys from <repo> (.env) to prevent target repo env from leaking into Archon processes`\n- `N` equals the number of keys planted (1 for the `.env` above) — a count of `0`\n  means the file was not read at all, which is a real regression\n- The workflow is then allowed to proceed and complete normally\n\nAssert on the strip line — pinned to **this** repo and **this** key count, so a\nstrip from some other directory cannot produce a false pass. Capture the exit\nstatus too: under the strip design the workflow is expected to succeed, so a\nnon-zero exit is its own failure.\n\n```bash\n\"$BINARY\" workflow run archon-investigate --no-worktree \"hello\" > /tmp/archon-test-leak.log 2>&1\nleak_exit=$?\n\n# $LEAKREPO may be a symlinked path (/tmp -> /private/tmp on macOS); the binary\n# logs the resolved form, so compare against that.\nresolved_repo=$(cd \"$LEAKREPO\" && pwd -P)\nexpected=\"[archon] stripped 1 keys from ${resolved_repo} (.env) to prevent target repo env from leaking into Archon processes\"\n\nif [ \"$leak_exit\" -ne 0 ]; then\n  echo \"FAIL: workflow exited $leak_exit — the guard strips and proceeds, it should not abort\"\nelif grep -qxF \"$expected\" /tmp/archon-test-leak.log; then\n  echo \"PASS: env-leak guard stripped exactly the planted key from this repo\"\nelse\n  echo \"FAIL: expected strip line absent. Got:\"\n  grep -F '[archon] stripped' /tmp/archon-test-leak.log || echo \"  (no strip line at all — guard inactive)\"\nfi\n```\n\nAn exact-match assertion is deliberate here. A looser pattern such as\n`stripped [1-9][0-9]* keys .*\\.env` passes on **any** positive count from **any**\npath, so a strip belonging to a different repository — or a count inflated by an\nunrelated `.env` — reads as success while the regression it is meant to catch\ngoes unnoticed.\n\n> **History — do not re-assert the old criteria.** This test previously expected a\n> *refusal* (`Cannot add codebase` / `Cannot run workflow`, non-zero exit), which\n> was the #1036/#1038/#983 behaviour. The current design is the two-layer strip\n> guard: the target repo's `.env` is read, dangerous keys are removed from the\n> environment handed to Archon subprocesses, and the run continues. On the 0.7.1\n> test the old assertions reported FAIL against a binary whose guard was working\n> correctly — it stripped exactly the 1 planted key. Verify the security property\n> (the key never reaches the subprocess), not the obsolete remediation text.\n\n**Common failures:**\n\n- No strip line at all → the guard is not active; the `.env` is reaching subprocesses\n- `stripped 0 keys` → the file was located but not parsed\n- Strip line names the wrong file or repo → path resolution regression\n\nClean up the leak test repo:\n\n```bash\nrm -rf \"$LEAKREPO\"\n```\n\n### Test 5 — Isolation list works (sanity check)\n\nIn the same `$TESTREPO`:\n\n```bash\n\"$BINARY\" isolation list\n```\n\n**Pass criteria:**\n\n- Exit code 0\n- No errors (the list may be empty if no worktrees have been created, which is fine)\n\nThis catches regressions in the isolation subsystem that would not surface from the other tests.\n\n### Test 6 — Cleanup test repos\n\n```bash\nrm -rf \"$TESTREPO\"\n```\n\nFor `curl-vps` path, also clean up any remote test repos created via SSH.\n\n## Phase 5 — Uninstall\n\n**Always run uninstall, even if Phase 4 failed.** The goal is to leave the system in the same state as before the test.\n\n### Path: brew\n\n```bash\nbrew uninstall coleam00/archon/archon\nbrew untap coleam00/archon\n```\n\nVerify the dev binary is still the default:\n\n```bash\nwhich -a archon\n# should show only the ~/.bun/bin/archon path, not a brew path\n\narchon version | head -1\n# should match the dev version captured in Phase 2\n```\n\n**Restore `archon-stable` if it existed before the test** (dual-homebrew pattern — see Phase 2 item 4):\n\n```bash\nif [ -n \"$ARCHON_STABLE_WAS_INSTALLED\" ]; then\n  echo \"Restoring archon-stable (detected before test)...\"\n  brew tap coleam00/archon\n  brew install coleam00/archon/archon\n  BREW_BIN=\"$(brew --prefix)/bin\"\n  if [ -e \"$BREW_BIN/archon\" ]; then\n    mv \"$BREW_BIN/archon\" \"$BREW_BIN/archon-stable\"\n    echo \"archon-stable restored: $(archon-stable version 2>/dev/null | head -1)\"\n  else\n    echo \"WARNING: brew install succeeded but $BREW_BIN/archon missing — check formula\"\n  fi\nfi\n```\n\n> **Note on the restored version**: this reinstalls from whatever the tap currently ships, which is typically the release you just tested (so `archon-stable` ends up at the newly-tested version). That's usually what the operator wants — you just verified the new release works, and you want `archon-stable` pointed at it. If you were testing an older version for back-version QA, the restored `archon-stable` will be the *current* tap formula, not the pre-test version. For that rare case, the operator should re-run `brew-upgrade-archon` manually after the test.\n\n### Path: curl-mac\n\n```bash\nrm -rf \"$INSTALL_DIR\"\n```\n\n### Path: curl-vps\n\n```bash\nssh <target> \"sudo rm -f /usr/local/bin/archon || rm -f \\$HOME/.local/bin/archon\"\n```\n\nOptional: the user may want to LEAVE the VPS binary installed for ongoing QA. Ask before removing.\n\n## Phase 6 — Report\n\nProduce a structured report with:\n\n- **Header**: release version tested, install path, timestamp, SHA256 of the tested binary\n- **Environment**: dev binary path + version (proof the dev install was not disturbed)\n- **Test results table**: one row per test with PASS / FAIL / SKIP\n- **Captured output**: for any FAIL, include the exact command, exit code, and last 20 lines of stderr/stdout\n- **Overall verdict**: PASS if all tests passed, FAIL if any test failed\n- **Next steps**: if FAIL, suggest concrete actions (file a hotfix issue, re-tag, check the build workflow, etc.)\n\nExample PASS report:\n\n```\nTest Release Report — archon v0.3.1 via brew\n────────────────────────────────────────────\nTested at:    2026-04-08 15:42 UTC\nBinary SHA:   e62eb73547b3740d56f242859b434a91d3830360a0d18f14de383da0fd7a0be6\nBinary path:  /opt/homebrew/Cellar/archon/0.3.1/bin/archon\nDev binary:   /Users/rasmus/.bun/bin/archon → ../install/.../cli.ts (unchanged)\n\n  [PASS]  Test 1  version reports 0.3.1, Build: binary, commit abc1234\n  [PASS]  Test 2  workflow list returned 21 bundled workflows\n  [PASS]  Test 3  workflow run archon-investigate produced output\n  [PASS]  Test 4  env-leak guard stripped exactly the planted key\n  [PASS]  Test 5  isolation list executed without errors\n  [PASS]  Cleanup brew uninstall + untap clean, dev binary unchanged\n\nOverall: PASS\n\nThis release is safe to announce. Next steps:\n  - Update release notes on GitHub if not done already\n  - Announce on whatever channels you use\n```\n\nExample FAIL report:\n\n```\nTest Release Report — archon v0.3.1 via curl-vps\n────────────────────────────────────────────────\nTested at:    2026-04-08 15:42 UTC\nBinary SHA:   0cf83e15e6af228e3c3473467ca30fa7525b6d7069818d85f97a115ea703d708\nBinary path:  user@vps:/usr/local/bin/archon\nDev binary:   /Users/rasmus/.bun/bin/archon (unchanged)\n\n  [PASS]  Test 1  version reports 0.3.1, Build: binary\n  [FAIL]  Test 2  workflow list returned 0 workflows\n\n    Command:  archon workflow list\n    Exit:     0\n    Output:\n      Discovering workflows in: /tmp/archon-test-repo-1712590923\n      Found 0 workflow(s):\n\n  [SKIP]  Test 3  SDK test skipped because Test 2 failed\n  [SKIP]  Test 4  env-leak guard test skipped because Test 2 failed\n  [PASS]  Test 5  isolation list executed without errors\n  [PASS]  Cleanup VPS binary removed\n\nOverall: FAIL\n\nLikely cause: bundled workflows were not embedded in the binary.\nCheck the build workflow for missing asset embedding, or verify that\nBUNDLED_WORKFLOWS in packages/workflows/src/defaults/bundled-defaults.ts\nwas populated at build time.\n\nNext steps:\n  1. File a P0 hotfix issue with the captured output\n  2. Do NOT announce v0.3.1 until the hotfix ships as v0.3.2\n  3. Consider adding a CI guard that blocks releases if BUNDLED_WORKFLOWS is empty\n```\n\n## Key behaviors\n\n- **Never touch the dev `bun link` binary.** Always use the installed binary path for Phase 4 tests. Verify before and after.\n- **Clean up on failure.** If Phase 4 fails mid-way, still run Phase 5 so the next run starts clean.\n- **Capture SHA256 immediately after install.** This lets bug reports reference the exact artifact under test.\n- **Explicit confirmation before install.** Never surprise the user by installing a second binary.\n- **Report the dev binary state in both preamble and postamble.** Proof that the test did not disturb the dev environment.\n- **Exit non-zero if any test failed.** The skill should propagate failure so automated wrappers (CI, scripts) can detect it.\n\n## Related\n\n- `scripts/build-binaries.sh` — builds the binary artifacts that end up in releases\n- `.github/workflows/release.yml` — builds and publishes the binary on tag push\n- `homebrew/archon.rb` — Homebrew tap formula (updated per release)\n- `scripts/install.sh` — the curl install script\n- `scripts/install-local.sh` / `install-local.ps1` — local-file install harnesses (for pre-release QA of binaries built from a branch, not from GitHub releases)\n- `/release` skill — the release procedure itself (opposite side of the flow)\n\n<!-- File: .claude/skills/triage/SKILL.md (Tokens: ~796 | Category: anthropic-skill) -->\n---\nname: triage\ndescription: |\n  Triage GitHub issues by applying type, effort, priority, and area labels.\n  Runs in an isolated context to avoid polluting the main conversation with\n  issue details. Delegates to a specialized triage agent with label validation hooks.\nargument-hint: \"[unlabeled|all|N|N-M]\"\ndisable-model-invocation: true\ncontext: fork\nagent: triage-agent\nallowed-tools: Bash(gh *), Read, Glob, Grep\n---\n\n# Triage GitHub Issues\n\nTriage issues for this repository by applying appropriate labels.\n\n## Repository Context\n\n- **Current repo**: !`gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo \"unknown\"`\n- **Open issues**: !`gh issue list --state open --json number --jq 'length' 2>/dev/null || echo \"?\"`\n- **Existing labels**: !`gh label list --json name -q '.[].name' 2>/dev/null | head -20 || echo \"none found\"`\n\n---\n\n## Scope\n\nDetermine which issues to triage based on the arguments: **$ARGUMENTS**\n\n| Argument | Behavior |\n|----------|----------|\n| (empty) | Only unlabeled issues (default) |\n| `unlabeled` | Only issues without any labels |\n| `all` | All open issues |\n| `N` | Specific issue (e.g., `67`) |\n| `N-M` | Range of issues inclusive (e.g., `60-67`) |\n\n## Process\n\n1. **Fetch available labels** — run `gh label list --json name,description` to understand\n   the label taxonomy. Labels are organized into type, effort, priority, and area categories.\n\n2. **Fetch target issues** — based on the scope above. For each issue, fetch the full body:\n   ```bash\n   gh issue view {number} --json number,title,body,labels\n   ```\n\n3. **For each issue**:\n   - Read the title and full body carefully\n   - If needed, explore the codebase (`Glob`, `Grep`, `Read`) to understand the affected code\n   - Classify: one type, one effort, one priority, one or more areas\n   - Track relationships with other issues (duplicates, related, blocking)\n   - Apply labels:\n     ```bash\n     gh issue edit {number} --add-label \"type,effort/level,P#,area.domain\"\n     ```\n   - Skip issues that already have complete labeling (type + effort + priority + area)\n   - For partially labeled issues, only add missing label categories\n\n4. **Output a triage summary**:\n\n   ```\n   ## Triage Summary\n\n   | Issue | Title | Labels Applied | Reasoning |\n   |-------|-------|----------------|-----------|\n   | #67 | ... | bug, effort/low, P1, core.config | ... |\n\n   **Totals:**\n   - Issues triaged: X\n   - Already labeled (skipped): Y\n   - By priority: P0(n), P1(n), P2(n), P3(n)\n\n   ## Relationships Discovered\n\n   | Issues | Relationship | Notes |\n   |--------|--------------|-------|\n   | #61, #62 | Related | Both involve config/logging UX |\n   ```\n\n## Rules\n\n- **Don't hardcode labels** — always fetch current labels first, they may change\n- **Respect existing labels** — don't remove labels, only add missing ones\n- **Check issue body** — titles alone aren't enough context\n- **Use the codebase** — if understanding a relationship requires seeing how modules connect, look\n- **When uncertain, note it** — flag ambiguous issues in the summary rather than guessing\n\n<!-- File: .claude/skills/triage/workshop-guide.md (Tokens: ~2094 | Category: anthropic-skill) -->\n# Triage — Workshop Guide\n\n## What This Skill Demonstrates\n\nA command upgraded to a skill + custom agent pair, showcasing **context forking**,\n**custom agent delegation**, **prompt hooks as guardrails**, and **restricted toolsets**.\n\n**Full demo steps**: See [part2-guide.md](../../workshop/part2-guide.md) — Feature 7.\n\n## Architecture\n\n```\nUser invokes /triage 42\n        │\n        ▼\n┌─────────────────────┐\n│  SKILL.md           │  context: fork + agent: triage-agent\n│  (entry point)      │  Injects repo context via !`command`\n│  allowed-tools:     │  Restricts tools to gh CLI + read-only\n│    Bash(gh *)       │\n│    Read, Glob, Grep │\n└────────┬────────────┘\n         │ forks into isolated context\n         ▼\n┌─────────────────────┐\n│  triage-agent.md    │  Custom agent with PostToolUse hook\n│  (.claude/agents/)  │  Hook: type \"prompt\" validates every\n│                     │  gh issue edit command to ensure\n│  model: sonnet      │  all 4 label categories are present\n│  hooks: PostToolUse │\n└─────────────────────┘\n         │\n         ▼\n   Summary returned to main conversation\n   (intermediate tool calls discarded)\n```\n\n## Features to Walk Through\n\n### 1. `context: fork` — Isolated Execution\n\n**What is it?** A skill frontmatter field that runs the skill in a **new isolated\nsubagent context** instead of inline in the main conversation. The skill body\nbecomes the task prompt for the subagent. When the subagent finishes, only its\nfinal response returns to the main conversation — all intermediate tool calls,\nfile reads, and reasoning are discarded.\n\n**Key properties:**\n- The forked context has **no access** to the main conversation history\n- Only the **final summary** flows back — intermediate work is discarded\n- `CLAUDE.md` files are still loaded in the forked context; this project's root file is only a pointer to `AGENTS.md`, which holds the project rules\n- The fork cannot spawn further subagents (no nesting)\n- Forked skills must contain **concrete tasks**, not just reference material\n\n**In this skill**: Triaging 20 issues could consume 50K+ tokens of context\n(issue bodies, label lists, grep results). Without forking, all of that pollutes\nthe main conversation. With forking, only the structured triage summary returns.\n\n### 2. `agent: triage-agent` — Custom Agent Delegation\n\n**What is it?** When a skill has `context: fork`, the `agent:` field controls\nwhich agent type runs the forked context. Options:\n\n| Agent | Model | Tools | Use case |\n|-------|-------|-------|----------|\n| `Explore` | Haiku (fast, cheap) | Read-only | File search, codebase exploration |\n| `Plan` | Inherits from main | Read-only | Planning and research |\n| `general-purpose` | Inherits from main | All tools | Complex multi-step tasks |\n| Any custom agent | Whatever agent defines | Whatever agent defines | Domain-specific work |\n\nCustom agents are `.md` files in `.claude/agents/` with their own frontmatter\n(model, tools, hooks, memory, permissions) and a markdown body that serves as\nthe system prompt.\n\n**In this skill**: Instead of `agent: general-purpose`, this skill delegates to\n`triage-agent` — a custom agent defined in `.claude/agents/triage-agent.md` with:\n- A triage specialist system prompt (label taxonomy, classification rules)\n- Its own PostToolUse hook for label validation\n- Restricted tool access\n\n**Teaching moment**: Skills define *what* to do (scope, context, arguments).\nAgents define *how* to do it (persona, tools, guardrails). Separating them\nmakes both composable — the same agent could be used by multiple skills.\n\n### 3. `type: \"prompt\"` Hook — LLM as Guardrail\n\n**What is it?** One of four hook handler types. A prompt hook sends the event\ncontext to an LLM (Haiku by default) for a single-turn yes/no evaluation.\nThe LLM must return `{\"ok\": true}` to allow the action or\n`{\"ok\": false, \"reason\": \"...\"}` to block it.\n\n**How it differs from the other hook types:**\n- `type: \"command\"` — runs a shell script, blocks via exit code 2\n- `type: \"http\"` — POSTs JSON to a URL, blocks via JSON response\n- `type: \"prompt\"` — asks an LLM to evaluate, blocks via `{\"ok\": false}`\n- `type: \"agent\"` — spawns a subagent with Read/Grep/Glob to verify conditions\n\nPrompt hooks are ideal for **semantic validation** — cases where the decision\nrequires understanding intent, not just pattern matching.\n\n**In this skill**: The triage agent has a PostToolUse hook on Bash. When it\ndetects a `gh issue edit --add-label` command, the LLM verifies all four label\ncategories (type, effort, priority, area) are present.\n\n**What happens if validation fails**: The LLM returns\n`{\"ok\": false, \"reason\": \"Missing effort label\"}` and Claude receives that\nfeedback. It can fix the label application before moving to the next issue.\n\n**Smart filtering**: The prompt instructs the LLM to return `{\"ok\": true}`\nfor non-label commands (`gh issue list`, `gh label list`) — no false positives.\n\n### 4. `allowed-tools` — Restricted Toolset\n\n**What is it?** A skill frontmatter field that controls which tools Claude can\nuse **without per-use permission prompts** when the skill is active. It acts as\nboth an allowlist (pre-approves listed tools) and a restriction (only these\ntools are available in the forked context).\n\n**Wildcard patterns** are supported:\n- `Bash(gh *)` — only `gh` subcommands, no arbitrary shell\n- `Bash(npm *)` — only npm commands\n- `Read, Glob, Grep` — read-only codebase access\n\n**In this skill**: `allowed-tools: Bash(gh *), Read, Glob, Grep` means the\nagent can interact with GitHub and read the codebase, but can't run arbitrary\nshell commands, write files, or edit code. This is a security boundary.\n\n### 5. `disable-model-invocation: true`\n\n**What is it?** Prevents Claude from auto-invoking this skill. By default, Claude\nreads skill descriptions and can invoke them when it determines they're relevant.\nSetting this to `true` means only the user can trigger it via `/triage`.\n\n**In this skill**: Triage modifies GitHub issues (side effects). Only the user\nshould decide when to apply labels.\n\n### 6. Dynamic Context Injection (`!`command``)\n\n**What is it?** Shell commands in the skill body using `!`command`` syntax that\nexecute as preprocessing — before Claude sees the prompt. The command's stdout\nreplaces the placeholder. This is not tool use; it happens at skill load time.\n\n**In this skill**: Three commands inject live repo data:\n- Repository name (`gh repo view`)\n- Open issue count (`gh issue list | length`)\n- Existing label taxonomy (`gh label list`)\n\nThe agent starts with real context instead of instructions to go fetch it.\n\n## Live Demo Steps\n\n1. **Show the architecture** — open both files side by side:\n   - `.claude/skills/triage/SKILL.md` (the skill entry point)\n   - `.claude/agents/triage-agent.md` (the specialist agent)\n\n2. **Point out the delegation chain**: skill → `context: fork` → `agent: triage-agent`\n\n3. **Invoke on a single issue**: `/triage 42`\n   - Show the `statusMessage` spinner: \"Validating label application...\"\n   - Point out: the main conversation stays clean while triage happens in the fork\n\n4. **Show the summary** that returns to the main conversation\n   - All the intermediate work (fetching issues, reading code, applying labels) is gone\n   - Only the structured summary survives\n\n5. **Show the hook validation** — if a label application was incomplete, the prompt\n   hook would have caught it and Claude would have self-corrected\n\n## Comparison: Before vs After\n\n| | Old Command | New Skill + Agent |\n|---|---|---|\n| Format | `.claude/commands/archon/triage.md` | Skill + `.claude/agents/triage-agent.md` |\n| Execution | Inline (pollutes main context) | Forked (isolated context window) |\n| Validation | None | Prompt hook validates every label application |\n| Tool access | All Bash commands | `Bash(gh *)` only — no arbitrary shell |\n| Agent persona | Generic | Specialized triage agent with domain knowledge |\n| Context cost | All issue data stays in conversation | Only summary returns |\n\n## Talking Points\n\n- \"Context forking is about information hygiene — the triage work happens in a disposable context.\"\n- \"Custom agents let you embed domain expertise. The triage rules live in the agent, the scope lives in the skill.\"\n- \"The prompt hook is an LLM guardrail — it catches incomplete label applications before they reach GitHub.\"\n- \"`Bash(gh *)` is a security boundary — the agent can interact with GitHub but can't run arbitrary commands.\"\n- \"This pattern — skill as entry point, custom agent as specialist — is how you build composable workflows.\"\n\n<!-- ... +109 more modular skills available via /api/skills/coleam00/Archon?path={path} -->\n</yakaai_skills>"}