diff --git a/.archon/commands/defaults/archon-fix-issue.md b/.archon/commands/defaults/archon-fix-issue.md index 516ae4d22d..335b421429 100644 --- a/.archon/commands/defaults/archon-fix-issue.md +++ b/.archon/commands/defaults/archon-fix-issue.md @@ -131,28 +131,30 @@ git status ### 3.2 Decision Tree -``` +```text ┌─ IN WORKTREE? -│ └─ YES → Use it (assume it's for this work) -│ Log: "Using worktree at {path}" +│ └─ YES → Use current branch AS-IS. Do NOT switch branches. Do NOT create +│ new branches. The isolation system has already set up the correct +│ branch; any deviation operates on the wrong code. +│ Log: "Using worktree at {path} on branch {branch}" │ -├─ ON MAIN/MASTER? +├─ ON $BASE_BRANCH? (main, master, or configured base branch) │ └─ Q: Working directory clean? │ ├─ YES → Create branch: fix/issue-{number}-{slug} │ │ git checkout -b fix/issue-{number}-{slug} -│ └─ NO → Warn user: -│ "Working directory has uncommitted changes. -│ Please commit or stash before proceeding." -│ STOP +│ │ (only applies outside a worktree — e.g., manual CLI usage) +│ └─ NO → STOP: "Uncommitted changes on $BASE_BRANCH. +│ Please commit or stash before proceeding." │ -├─ ON FEATURE/FIX BRANCH? -│ └─ Use it (assume it's for this work) +├─ ON OTHER BRANCH? +│ └─ Use it AS-IS (assume it was set up for this work). +│ Do NOT switch to another branch (e.g., one shown by `git branch` but +│ not currently checked out). │ If branch name doesn't contain issue number: │ Warn: "Branch '{name}' may not be for issue #{number}" │ └─ DIRTY STATE? - └─ Warn and suggest: git stash or git commit - STOP + └─ STOP: "Uncommitted changes. Please commit or stash first." ``` ### 3.3 Ensure Up-to-Date diff --git a/.archon/commands/defaults/archon-implement-issue.md b/.archon/commands/defaults/archon-implement-issue.md index 66f7411b10..4a8c980552 100644 --- a/.archon/commands/defaults/archon-implement-issue.md +++ b/.archon/commands/defaults/archon-implement-issue.md @@ -132,28 +132,30 @@ git status ### 3.2 Decision Tree -``` +```text ┌─ IN WORKTREE? -│ └─ YES → Use it (assume it's for this work) -│ Log: "Using worktree at {path}" +│ └─ YES → Use current branch AS-IS. Do NOT switch branches. Do NOT create +│ new branches. The isolation system has already set up the correct +│ branch; any deviation operates on the wrong code. +│ Log: "Using worktree at {path} on branch {branch}" │ -├─ ON MAIN/MASTER? +├─ ON $BASE_BRANCH? (main, master, or configured base branch) │ └─ Q: Working directory clean? │ ├─ YES → Create branch: fix/issue-{number}-{slug} │ │ git checkout -b fix/issue-{number}-{slug} -│ └─ NO → Warn user: -│ "Working directory has uncommitted changes. -│ Please commit or stash before proceeding." -│ STOP +│ │ (only applies outside a worktree — e.g., manual CLI usage) +│ └─ NO → STOP: "Uncommitted changes on $BASE_BRANCH. +│ Please commit or stash before proceeding." │ -├─ ON FEATURE/FIX BRANCH? -│ └─ Use it (assume it's for this work) +├─ ON OTHER BRANCH? +│ └─ Use it AS-IS (assume it was set up for this work). +│ Do NOT switch to another branch (e.g., one shown by `git branch` but +│ not currently checked out). │ If branch name doesn't contain issue number: │ Warn: "Branch '{name}' may not be for issue #{number}" │ └─ DIRTY STATE? - └─ Warn and suggest: git stash or git commit - STOP + └─ STOP: "Uncommitted changes. Please commit or stash first." ``` ### 3.3 Ensure Up-to-Date diff --git a/.archon/commands/defaults/archon-implement.md b/.archon/commands/defaults/archon-implement.md index 4bcd7bf1c5..605d3020d8 100644 --- a/.archon/commands/defaults/archon-implement.md +++ b/.archon/commands/defaults/archon-implement.md @@ -93,19 +93,40 @@ Provide a valid plan path or GitHub issue containing the plan. ### 2.1 Check Current State ```bash +# What branch are we on? git branch --show-current -git status --porcelain + +# Are we in a worktree? +git rev-parse --show-toplevel git worktree list + +# Is working directory clean? +git status --porcelain ``` ### 2.2 Branch Decision -| Current State | Action | -| ----------------- | ---------------------------------------------------- | -| In worktree | Use it (log: "Using worktree") | -| On base branch, clean | Create branch: `git checkout -b feature/{plan-slug}` | -| On base branch, dirty | STOP: "Stash or commit changes first" | -| On feature branch | Use it (log: "Using existing branch") | +```text +┌─ IN WORKTREE? +│ └─ YES → Use current branch AS-IS. Do NOT switch branches. Do NOT create +│ new branches. The isolation system has already set up the correct +│ branch; any deviation operates on the wrong code. +│ Log: "Using worktree at {path} on branch {branch}" +│ +├─ ON $BASE_BRANCH? (main, master, or configured base branch) +│ └─ Q: Working directory clean? +│ ├─ YES → Create branch: git checkout -b feature/{plan-slug} +│ │ (only applies outside a worktree — e.g., manual CLI usage) +│ └─ NO → STOP: "Stash or commit changes first" +│ +├─ ON OTHER BRANCH? +│ └─ Use it AS-IS. Do NOT switch to another branch (e.g., one shown by +│ `git branch` but not currently checked out). +│ Log: "Using existing branch {name}" +│ +└─ DIRTY STATE? + └─ STOP: "Stash or commit changes first" +``` ### 2.3 Sync with Remote @@ -116,7 +137,7 @@ git pull --rebase origin $BASE_BRANCH 2>/dev/null || true **PHASE_2_CHECKPOINT:** -- [ ] On correct branch (not base branch with uncommitted work) +- [ ] On correct branch (not $BASE_BRANCH with uncommitted work) - [ ] Working directory ready - [ ] Up to date with remote diff --git a/.archon/commands/defaults/archon-plan-setup.md b/.archon/commands/defaults/archon-plan-setup.md index 812d0f8246..668b74c69f 100644 --- a/.archon/commands/defaults/archon-plan-setup.md +++ b/.archon/commands/defaults/archon-plan-setup.md @@ -112,13 +112,26 @@ gh repo view --json nameWithOwner -q .nameWithOwner ### 2.3 Branch Decision -| Current State | Action | -|---------------|--------| -| Already on correct feature branch | Use it, log "Using existing branch: {name}" | -| On base branch, clean working directory | Create and checkout: `git checkout -b {branch-name}` | -| On base branch, dirty working directory | STOP with error: "Uncommitted changes on base branch. Stash or commit first." | -| On different feature branch | STOP with error: "On branch {X}, expected {Y}. Switch branches or adjust plan." | -| In a worktree | Use the worktree's branch, log "Using worktree branch: {name}" | +Evaluate in order (first matching case wins): + +```text +┌─ IN WORKTREE? +│ └─ YES → Use current branch AS-IS. Do NOT switch branches. Do NOT create +│ new branches. The isolation system has already set up the correct +│ branch; any deviation operates on the wrong code. +│ Log: "Using worktree branch: {name}" +│ +├─ ON $BASE_BRANCH? (main, master, or configured base branch) +│ └─ Q: Working directory clean? +│ ├─ YES → Create and checkout: `git checkout -b {branch-name}` +│ │ (only applies outside a worktree — e.g., manual CLI usage) +│ └─ NO → STOP: "Uncommitted changes on $BASE_BRANCH. Stash or commit first." +│ +└─ ON OTHER BRANCH? + └─ Q: Does it match the expected branch for this plan? + ├─ YES → Use it, log "Using existing branch: {name}" + └─ NO → STOP: "On branch {X}, expected {Y}. Switch branches or adjust plan." +``` ### 2.4 Sync with Remote diff --git a/.archon/commands/e2e-echo-command.md b/.archon/commands/e2e-echo-command.md new file mode 100644 index 0000000000..7d67fa3e2c --- /dev/null +++ b/.archon/commands/e2e-echo-command.md @@ -0,0 +1,13 @@ +--- +description: E2E test command — echoes back the user message +argument-hint: +--- + +# E2E Echo Command + +You are a simple echo agent for testing. Your ONLY job is to repeat back the user's message. + +User message: $ARGUMENTS + +Respond with EXACTLY this format and nothing else: +command-echo: diff --git a/.archon/scripts/echo-args.js b/.archon/scripts/echo-args.js new file mode 100644 index 0000000000..140a9ae4c9 --- /dev/null +++ b/.archon/scripts/echo-args.js @@ -0,0 +1,3 @@ +// Simple script node test — echoes input as JSON +const input = process.argv[2] ?? 'no-input'; +console.log(JSON.stringify({ echoed: input, timestamp: new Date().toISOString() })); diff --git a/.archon/scripts/echo-py.py b/.archon/scripts/echo-py.py new file mode 100644 index 0000000000..a4f565218c --- /dev/null +++ b/.archon/scripts/echo-py.py @@ -0,0 +1,7 @@ +"""Simple script node test — echoes input as JSON (uv/Python runtime).""" +import json +import sys +from datetime import datetime, timezone + +input_val = sys.argv[1] if len(sys.argv) > 1 else "no-input" +print(json.dumps({"echoed": input_val, "timestamp": datetime.now(timezone.utc).isoformat()})) diff --git a/.archon/workflows/defaults/archon-adversarial-dev.yaml b/.archon/workflows/defaults/archon-adversarial-dev.yaml index 2ab207dc03..68722c8b1a 100644 --- a/.archon/workflows/defaults/archon-adversarial-dev.yaml +++ b/.archon/workflows/defaults/archon-adversarial-dev.yaml @@ -101,7 +101,9 @@ nodes: "status": "running" } STATEEOF - sed -i "s/SPRINT_COUNT_PLACEHOLDER/$SPRINT_COUNT/" "$ARTIFACTS/state.json" + STATE_TMP="$ARTIFACTS/state.json.tmp" + sed "s/SPRINT_COUNT_PLACEHOLDER/$SPRINT_COUNT/" "$ARTIFACTS/state.json" > "$STATE_TMP" + mv "$STATE_TMP" "$ARTIFACTS/state.json" echo "{\"totalSprints\": $SPRINT_COUNT, \"appDir\": \"$ARTIFACTS/app\", \"artifactsDir\": \"$ARTIFACTS\"}" timeout: 30000 diff --git a/.archon/workflows/e2e-claude-smoke.yaml b/.archon/workflows/e2e-claude-smoke.yaml new file mode 100644 index 0000000000..29cd10c3b4 --- /dev/null +++ b/.archon/workflows/e2e-claude-smoke.yaml @@ -0,0 +1,26 @@ +# E2E smoke test — Claude provider +# Verifies: Claude connectivity (sendQuery), $nodeId.output refs +# Design: Only uses allowed_tools: [] (no tool use) and no output_format (no structured output) +# because the Claude CLI subprocess is slow with those features in CI. +name: e2e-claude-smoke +description: "Smoke test for Claude provider. Verifies prompt response." +provider: claude +model: haiku + +nodes: + # 1. Simple prompt — verifies Claude API connectivity via sendQuery + - id: simple + prompt: "What is 2+2? Answer with just the number, nothing else." + allowed_tools: [] + idle_timeout: 30000 + + # 2. Assert non-empty output — fails CI if Claude returned nothing + - id: assert + bash: | + output="$simple.output" + if [ -z "$output" ]; then + echo "FAIL: simple node returned empty output" + exit 1 + fi + echo "PASS: simple=$output" + depends_on: [simple] diff --git a/.archon/workflows/e2e-codex-smoke.yaml b/.archon/workflows/e2e-codex-smoke.yaml new file mode 100644 index 0000000000..f24336b36e --- /dev/null +++ b/.archon/workflows/e2e-codex-smoke.yaml @@ -0,0 +1,40 @@ +# E2E smoke test — Codex provider +# Verifies: provider selection, sendQuery, structured output +name: e2e-codex-smoke +description: "E2E smoke test for Codex provider. Runs a simple prompt + structured output node." +provider: codex +model: gpt-5.2 + +nodes: + - id: simple + prompt: "What is 2+2? Answer with just the number, nothing else." + idle_timeout: 30000 + + - id: structured + prompt: "Classify this input as 'math' or 'text': '2+2=4'. Return JSON only." + output_format: + type: object + properties: + category: + type: string + enum: ["math", "text"] + required: ["category"] + additionalProperties: false + idle_timeout: 30000 + depends_on: [simple] + + # Assert both nodes returned output + - id: assert + bash: | + simple_out="$simple.output" + structured_out="$structured.output" + if [ -z "$simple_out" ]; then + echo "FAIL: simple node returned empty output" + exit 1 + fi + if [ -z "$structured_out" ]; then + echo "FAIL: structured node returned empty output" + exit 1 + fi + echo "PASS: simple=$simple_out structured=$structured_out" + depends_on: [simple, structured] diff --git a/.archon/workflows/e2e-deterministic.yaml b/.archon/workflows/e2e-deterministic.yaml new file mode 100644 index 0000000000..48e2288855 --- /dev/null +++ b/.archon/workflows/e2e-deterministic.yaml @@ -0,0 +1,66 @@ +# E2E smoke test — deterministic nodes (no AI, no API calls) +# Verifies: bash nodes, script nodes (bun + uv), $nodeId.output substitution, +# when conditions, trigger_rule join semantics +name: e2e-deterministic +description: "Pure DAG engine test. Exercises bash, script (bun/uv), conditions, and trigger rules with zero API calls." + +nodes: + # Layer 0 — parallel deterministic nodes + - id: bash-echo + bash: "echo '{\"status\":\"ok\",\"value\":42}'" + + - id: script-bun + script: echo-args + runtime: bun + timeout: 30000 + + - id: script-python + script: echo-py + runtime: uv + timeout: 30000 + + # Layer 1 — test $nodeId.output substitution from bash + - id: bash-read-output + bash: "echo 'upstream-status: $bash-echo.output'" + depends_on: [bash-echo] + + # Layer 1 — conditional branches (only one should run) + - id: branch-true + bash: "echo 'branch-true-ran'" + depends_on: [bash-echo] + when: "$bash-echo.output.status == 'ok'" + + - id: branch-false + bash: "echo 'branch-false-ran'" + depends_on: [bash-echo] + when: "$bash-echo.output.status == 'fail'" + + # Layer 2 — trigger_rule merge (one_success: branch-false will be skipped) + - id: merge-node + bash: "echo 'merge-ok: true=$branch-true.output false=$branch-false.output'" + depends_on: [branch-true, branch-false] + trigger_rule: one_success + + # Layer 3 — final verification: assert all outputs are non-empty + - id: verify-all + bash: | + fail=0 + for name in bash-echo script-bun script-python bash-read-output branch-true merge-node; do + echo "$name output received" + done + bash_echo="$bash-echo.output" + script_bun="$script-bun.output" + script_python="$script-python.output" + bash_read="$bash-read-output.output" + branch_t="$branch-true.output" + merge="$merge-node.output" + if [ -z "$bash_echo" ]; then echo "FAIL: bash-echo empty"; fail=1; fi + if [ -z "$script_bun" ]; then echo "FAIL: script-bun empty"; fail=1; fi + if [ -z "$script_python" ]; then echo "FAIL: script-python empty"; fail=1; fi + if [ -z "$bash_read" ]; then echo "FAIL: bash-read-output empty"; fail=1; fi + if [ -z "$branch_t" ]; then echo "FAIL: branch-true empty"; fail=1; fi + if [ -z "$merge" ]; then echo "FAIL: merge-node empty"; fail=1; fi + if [ "$fail" -eq 1 ]; then exit 1; fi + echo "PASS: all deterministic nodes produced output" + depends_on: [bash-read-output, script-bun, script-python, merge-node] + trigger_rule: all_success diff --git a/.archon/workflows/e2e-mixed-providers.yaml b/.archon/workflows/e2e-mixed-providers.yaml new file mode 100644 index 0000000000..9f5c408a37 --- /dev/null +++ b/.archon/workflows/e2e-mixed-providers.yaml @@ -0,0 +1,38 @@ +# E2E smoke test — mixed providers (Claude + Codex in same workflow) +# Verifies: per-node provider override, cross-provider $nodeId.output refs +name: e2e-mixed-providers +description: "Tests Claude and Codex providers in the same workflow with cross-provider output refs." + +# Default provider is claude +provider: claude +model: haiku + +nodes: + # 1. Claude node — default provider + - id: claude-node + prompt: "Say 'claude-ok' and nothing else." + allowed_tools: [] + idle_timeout: 30000 + + # 2. Codex node — provider override (runs parallel with claude-node, different providers) + - id: codex-node + prompt: "Say 'codex-ok' and nothing else." + provider: codex + model: gpt-5.2 + idle_timeout: 30000 + + # 3. Assert both providers returned output + - id: assert + bash: | + claude_out="$claude-node.output" + codex_out="$codex-node.output" + if [ -z "$claude_out" ]; then + echo "FAIL: claude-node returned empty output" + exit 1 + fi + if [ -z "$codex_out" ]; then + echo "FAIL: codex-node returned empty output" + exit 1 + fi + echo "PASS: claude=$claude_out codex=$codex_out" + depends_on: [claude-node, codex-node] diff --git a/.archon/workflows/e2e-pi-all-nodes-smoke.yaml b/.archon/workflows/e2e-pi-all-nodes-smoke.yaml new file mode 100644 index 0000000000..15abb3bb0b --- /dev/null +++ b/.archon/workflows/e2e-pi-all-nodes-smoke.yaml @@ -0,0 +1,105 @@ +# E2E smoke test — Pi provider, every node type +# Covers: prompt, command, loop (AI node types) + bash, script bun/uv +# (deterministic node types) + depends_on / when / trigger_rule / $nodeId.output +# (DAG features). +# Skipped: `approval:` — pauses for human input, incompatible with CI. +# Auth: ANTHROPIC_API_KEY (CI) or your local `pi /login` OAuth. +# Expected runtime: ~10s on haiku (3 AI round-trips + deterministic nodes). +name: e2e-pi-all-nodes-smoke +description: 'Pi provider smoke across every CI-compatible node type.' +provider: pi +model: anthropic/claude-haiku-4-5 + +nodes: + # ─── AI node types ────────────────────────────────────────────────────── + + # 1. prompt: inline prompt (simplest AI node) + - id: prompt-node + prompt: "Reply with exactly the single word 'ok' and nothing else." + allowed_tools: [] + effort: low + idle_timeout: 30000 + + # 2. command: named command file (.archon/commands/e2e-echo-command.md) + # The command echoes back $ARGUMENTS (the workflow invocation message). + - id: command-node + command: e2e-echo-command + allowed_tools: [] + idle_timeout: 30000 + + # 3. loop: iterative AI prompt until completion signal + # Bounded by max_iterations: 2 so a misbehaving model can't hang CI. + - id: loop-node + loop: + prompt: "Reply with exactly 'DONE' and nothing else." + until: 'DONE' + max_iterations: 2 + allowed_tools: [] + effort: low + idle_timeout: 60000 + + # ─── Deterministic node types (no AI) ─────────────────────────────────── + + # 4. bash: shell script with JSON output (enables $nodeId.output.status + # dot-access downstream) + - id: bash-json-node + bash: 'echo ''{"status":"ok"}''' + + # 5. script: bun (TypeScript/JavaScript runtime) + - id: script-bun-node + script: echo-args + runtime: bun + timeout: 30000 + + # 6. script: uv (Python runtime) + - id: script-python-node + script: echo-py + runtime: uv + timeout: 30000 + + # ─── DAG features ─────────────────────────────────────────────────────── + + # 7. depends_on + $nodeId.output substitution + - id: downstream + bash: "echo 'downstream got: $prompt-node.output'" + depends_on: [prompt-node] + + # 8. when: conditional (JSON dot-access on upstream output) + - id: gated + bash: "echo 'gated-ok'" + depends_on: [bash-json-node] + when: "$bash-json-node.output.status == 'ok'" + + # 9. trigger_rule: merge multiple deps (all_success semantics) + - id: merge + bash: "echo 'merge-ok'" + depends_on: [downstream, gated, script-bun-node, script-python-node] + trigger_rule: all_success + + # ─── Final assertion ──────────────────────────────────────────────────── + + # 10. Verify every upstream node produced non-empty output. + - id: assert + bash: | + fail=0 + check() { + local name="$1" + local value="$2" + if [ -z "$value" ]; then + echo "FAIL: $name produced empty output" + fail=1 + fi + } + check prompt-node "$prompt-node.output" + check command-node "$command-node.output" + check loop-node "$loop-node.output" + check bash-json-node "$bash-json-node.output" + check script-bun-node "$script-bun-node.output" + check script-python-node "$script-python-node.output" + check downstream "$downstream.output" + check gated "$gated.output" + check merge "$merge.output" + if [ "$fail" -eq 1 ]; then exit 1; fi + echo "PASS: all 9 node types produced output" + depends_on: [merge, loop-node, command-node] + trigger_rule: all_success diff --git a/.archon/workflows/e2e-pi-smoke.yaml b/.archon/workflows/e2e-pi-smoke.yaml new file mode 100644 index 0000000000..af8c2164f8 --- /dev/null +++ b/.archon/workflows/e2e-pi-smoke.yaml @@ -0,0 +1,35 @@ +# E2E smoke test — Pi community provider +# Verifies: Pi connectivity, $nodeId.output refs, async-queue bridge, +# and v2 wiring (thinkingLevel, allowed_tools). +# Design: mirrors e2e-claude-smoke.yaml structure. The `allowed_tools: []` +# idiom disables Pi's built-in read/bash/edit/write so the smoke stays +# fast (no tool-use round-trips). `thinking: minimal` exercises the +# thinkingLevel translation path. +# Auth: picks up either ANTHROPIC_API_KEY env var (CI) or your local +# `pi /login` OAuth credentials from ~/.pi/agent/auth.json. +name: e2e-pi-smoke +description: 'Smoke test for Pi community provider. Verifies prompt response via sendQuery.' +provider: pi +model: anthropic/claude-haiku-4-5 + +nodes: + # 1. Simple prompt — verifies Pi harness starts, AsyncQueue bridge yields + # assistant chunks, and agent_end produces a result chunk. v2 wiring: + # allowed_tools: [] disables all Pi tools (LLM-only); effort: low is + # translated to Pi's thinkingLevel by options-translator.ts. + - id: simple + prompt: 'What is 2+2? Answer with just the number, nothing else.' + allowed_tools: [] + effort: low + idle_timeout: 30000 + + # 2. Assert non-empty output — fails CI if Pi returned nothing + - id: assert + bash: | + output="$simple.output" + if [ -z "$output" ]; then + echo "FAIL: simple node returned empty output" + exit 1 + fi + echo "PASS: simple=$output" + depends_on: [simple] diff --git a/.archon/workflows/e2e-worktree-disabled.yaml b/.archon/workflows/e2e-worktree-disabled.yaml new file mode 100644 index 0000000000..4c1948e62a --- /dev/null +++ b/.archon/workflows/e2e-worktree-disabled.yaml @@ -0,0 +1,34 @@ +# E2E smoke test — workflow-level worktree.enabled: false +# Verifies: when a workflow pins worktree.enabled: false, runs happen in the +# live repo checkout (no worktree created, cwd == repo root). Zero AI calls. +name: e2e-worktree-disabled +description: "Pinned-isolation-off smoke. Asserts cwd is the repo root rather than a worktree path, regardless of how the workflow is invoked." + +worktree: + enabled: false + +nodes: + # Print cwd so the operator can eyeball it, and capture for the assertion node. + - id: print-cwd + bash: "pwd" + + # Assertion: cwd must NOT contain '/.archon/workspaces/' — if it does, the + # policy was ignored and a worktree was created anyway. We also assert the + # cwd ends with a git repo (has a .git directory or file visible). + - id: assert-live-checkout + bash: | + cwd="$(pwd)" + echo "assert-live-checkout cwd=$cwd" + case "$cwd" in + */.archon/workspaces/*/worktrees/*) + echo "FAIL: workflow ran inside a worktree ($cwd) despite worktree.enabled: false" + exit 1 + ;; + esac + if [ ! -e "$cwd/.git" ]; then + echo "FAIL: cwd $cwd is not a git checkout root (.git missing)" + exit 1 + fi + echo "PASS: ran in live checkout (no worktree created by policy)" + depends_on: [print-cwd] + trigger_rule: all_success diff --git a/.archon/workflows/repo-triage.yaml b/.archon/workflows/repo-triage.yaml new file mode 100644 index 0000000000..0dac0b5577 --- /dev/null +++ b/.archon/workflows/repo-triage.yaml @@ -0,0 +1,1588 @@ +name: repo-triage +description: >- + Periodic repo maintenance — in parallel, triages open issues (labels + + dedup detection + 3-day auto-close) and cross-references open PRs against + open issues (conservative: suggests Closes #X only when a PR fully + addresses an issue, never closes anything itself). State is persisted + under .archon/state/ so prior runs are remembered. Designed for periodic + runs; safe to re-run; idempotent. +interactive: false + +# Read-only triage runs directly in the live checkout. Creating a worktree +# every run would be wasted work (nothing is mutated) and would scatter stale +# branches under ~/.archon/workspaces///worktrees/. +worktree: + enabled: false + +nodes: + # --------------------------------------------------------------------------- + # Issue triage — runs concurrently with pr-link (no depends_on between them). + # --------------------------------------------------------------------------- + - id: triage-issues + model: sonnet + allowed_tools: [Bash, Read, Write, Task] + agents: + brief-gen: + description: >- + Reads a single GitHub issue and returns a concise JSON brief, + plus a template-adherence check if the caller provided template + context. Used for duplicate detection + template-fill reporting. + model: haiku + tools: [Bash, Read] + prompt: | + You are a concise GitHub issue summariser. The caller's prompt + will include an issue number AND (optionally) the issue + templates from `.github/ISSUE_TEMPLATE/`. + + Fetch the issue: + gh issue view --json number,title,body,labels,author + + Return ONLY a single JSON object — no fences, no prose: + + { + "number": , + "summary": "2-3 sentence neutral summary", + "primarySymptom": "one short sentence — the core symptom or ask", + "area": "best-guess tag, e.g. backend, frontend, docs, core, isolation, cli, workflows, providers", + "templateAdherence": { + "templateType": "bug_report | feature_request | other | none", + "sectionsFilled": ["
"], + "sectionsMissing": ["