diff --git a/README.md b/README.md index 10d852c..7bc9050 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Presets move planner, implementer and researcher; the reviewer follows the autho ```bash agent-run route --role implementer --preset main-claude # who runs this, where, how -agent-run route --role reviewer --diff review.diff # depth, family, cost +agent-run route --role reviewer --diff review.diff --author-backend codex # depth, family, cost; self when you wrote it agent-run preset main-claude # persist; alone: show the effective table agent-run run --role planner --brief .scratch/tariffs/brief.md diff --git a/skills/delegate-kit/SKILL.md b/skills/delegate-kit/SKILL.md index da5df0d..059a45b 100644 --- a/skills/delegate-kit/SKILL.md +++ b/skills/delegate-kit/SKILL.md @@ -59,7 +59,7 @@ Review when: any delegated implementation; a risk zone (auth, payments, migratio ``` agent-wt diff > review.diff -agent-run route --role reviewer --diff review.diff [--author-backend self] +agent-run route --role reviewer --diff review.diff --author-backend ``` `single` runs straight away. `panel` / `led` are **proposed with the printed numbers and run on the user's yes**. Reviewers run in parallel and blind to each other; merge by `references/review.md`. diff --git a/skills/delegate-kit/references/review.md b/skills/delegate-kit/references/review.md index 310c691..8670114 100644 --- a/skills/delegate-kit/references/review.md +++ b/skills/delegate-kit/references/review.md @@ -1,6 +1,6 @@ # Review: depth, lenses, composition -How many reviewers a diff deserves, which angle each one takes, and how their findings become one list. `agent-run route --role reviewer --diff ` applies all of it and prints the result; this file is the reasoning behind that output. +How many reviewers a diff deserves, which angle each one takes, and how their findings become one list. `agent-run route --role reviewer --diff --author-backend ` applies all of it and prints the result; this file is the reasoning behind that output. ## Independence is the first slot, not the whole review diff --git a/skills/delegate-kit/references/roles.md b/skills/delegate-kit/references/roles.md index 8cf88fe..d98fe16 100644 --- a/skills/delegate-kit/references/roles.md +++ b/skills/delegate-kit/references/roles.md @@ -53,6 +53,7 @@ The Codex CLI is offered `sol`, `terra` and `luna` only (`~/.codex/models_cache. - **Default** the other family than the author, high effort. After a Codex implementer: `claude` opus high. After a Claude implementer: `codex` gpt-5.6-sol high. UX/product review: `claude` opus. - **Why the other family**: the same family reviewing itself shares blind spots. Independence is the value — the first slot of a panel, never something a preset moves. When the other CLI is missing, `route` falls back to a fresh native reviewer and says so; a fresh context is the floor, the other family the preference. - **Depth and lenses**: one reviewer by default; a panel of two or three lenses for large or risky diffs, proposed with numbers and run only on the user's yes. `review.md`. +- **The author is named, never assumed.** `route` and `run` take `--author-backend claude|codex|self` for reviewer and verifier; a diff the coordinator wrote itself is `self`, so the same family is never marked independent by default. - **Read-only.** Give it the frozen diff (`agent-wt diff`) and the spec. Ask for findings with severity, file:line, the claim, the evidence, and a suggested fix. Ask it to separate "spec mismatch" from "standards" from "nit". Ask it not to restate the diff. - **Effort**: high by default; xhigh only in risk zones (auth, payments, migrations). diff --git a/skills/delegate-kit/scripts/agent-run b/skills/delegate-kit/scripts/agent-run index ae0a119..4df20ea 100755 --- a/skills/delegate-kit/scripts/agent-run +++ b/skills/delegate-kit/scripts/agent-run @@ -607,8 +607,24 @@ function resolveRoute(opts = {}) { // Who wrote the code decides the reviewer. Default to whoever this preset would have // implemented with — including the UI exception, or the reviewer flips to the wrong family. const defaultAuthor = preset === "auto" && uiKind ? "claude" : table.implementer; + /* + * Для reviewer и verifier автор — обязательный флаг. Раньше он молча + * подставлялся из implementer'а пресета, и diff, написанный самим + * координатором на Claude под auto, получал автора codex, ревьюера claude и + * пометку independent: true — ревью того же семейства выдавалось за + * независимое. Явный --backend на run автора не требует: семейство уже + * названо, а независимость там не считается. + */ + const authorGiven = opts["author-backend"] && opts["author-backend"] !== true; + // The exemption is for `run` only (family named, independence not computed there) and for a + // resume (the family is the persisted one). `route` always needs the author: it is what + // `independent` is computed against. + const runWithBackend = opts._run && opts.backend && opts.backend !== true; + if (["reviewer", "verifier"].includes(role) && !authorGiven && !runWithBackend && !opts._composed) { + die(`--author-backend claude|codex|self is required for --role ${role}: who wrote the diff decides the family. A delegated implementer → its family; a diff the coordinator wrote itself → self.`); + } // `self`: the parent wrote the change itself, so the author is the parent's family - let author = opts["author-backend"] && opts["author-backend"] !== true ? String(opts["author-backend"]) : defaultAuthor; + let author = authorGiven ? String(opts["author-backend"]) : defaultAuthor; if (author === "self") { if (!parent) die("--author-backend self needs a detectable parent (or --parent)"); author = parent; } if (!["claude", "codex"].includes(author)) die("--author-backend must be claude, codex or self"); @@ -860,7 +876,7 @@ async function cmdRun(opts, resumeOf = null) { const role = resumeOf ? resumeOf.role : opts.role; if (!role || !ROLES[role]) die(`--role must be one of ${Object.keys(ROLES).join("|")}`); const spec = ROLES[role]; - const route = resolveRoute({ ...opts, role }); + const route = resolveRoute({ ...opts, role, _run: true, backend: resumeOf ? resumeOf.backend : opts.backend }); const backend = resumeOf ? resumeOf.backend : route.backend; const model = resumeOf ? (opts.model && opts.model !== true ? opts.model : resumeOf.model) : route.model; const effort = resumeOf ? (opts.effort && opts.effort !== true ? opts.effort : resumeOf.effort) : route.effort; @@ -1049,7 +1065,8 @@ switch (sub) { console.log(`agent-run — headless workers for Claude Code / Codex with role defaults, worktree locks and a ledger agent-run route --role <${Object.keys(ROLES).join("|")}> [--preset P] [--parent claude|codex] [--author-backend claude|codex|self] [--kind ui|refactor|mechanical] - who should run this role, on which family, natively or externally + who should run this role, on which family, natively or externally; + --author-backend is required for reviewer and verifier (self = the coordinator wrote the diff) agent-run route --role reviewer [--diff FILE] [--depth single|panel|led] how many reviewers this diff deserves, which lens and family each gets, and the lead agent-run preset [${Object.keys(PRESETS).join("|")}] show or persist the default preset diff --git a/skills/delegate-kit/tests/route.sh b/skills/delegate-kit/tests/route.sh index 2347159..645b0fd 100755 --- a/skills/delegate-kit/tests/route.sh +++ b/skills/delegate-kit/tests/route.sh @@ -31,13 +31,13 @@ echo "── пресет auto, родитель Claude, оба CLI" ok "planner → claude fable, нативно" "$(route both --role planner --parent claude '[.backend,.model,.dispatch,.invoke.subagent_type]|join(" ")')" "claude fable native dk-planner" ok "implementer → codex sol, внешне" "$(route both --role implementer --parent claude '[.backend,.model,.effort,.dispatch]|join(" ")')" "codex gpt-5.6-sol high external" ok "researcher → claude sonnet medium" "$(route both --role researcher --parent claude '[.backend,.model,.effort]|join(" ")')" "claude sonnet medium" -ok "reviewer после codex-автора → claude, независим, нативно" "$(route both --role reviewer --parent claude '.reviewers[0]|[.backend,(.independent|tostring),.dispatch]|join(" ")')" "claude true native" -ok "verifier — третья сторона к ревьюеру: обратно на codex" "$(route both --role verifier --parent claude '.backend + " " + (.why[0]|contains("third party")|tostring)')" "codex true" +ok "reviewer после codex-автора → claude, независим, нативно" "$(route both --role reviewer --parent claude --author-backend codex '.reviewers[0]|[.backend,(.independent|tostring),.dispatch]|join(" ")')" "claude true native" +ok "verifier — третья сторона к ревьюеру: обратно на codex" "$(route both --role verifier --parent claude --author-backend codex '.backend + " " + (.why[0]|contains("third party")|tostring)')" "codex true" ok "review-lead следует за planner'ом" "$(route both --role review-lead --parent claude '[.backend,.model]|join(" ")')" "claude fable" echo "── пресеты и приоритет" ok "main-claude: implementer нативно на claude" "$(route both --role implementer --parent claude --preset main-claude '[.backend,.dispatch]|join(" ")')" "claude native" -ok "main-claude: ревьюер следует за автором → codex" "$(route both --role reviewer --parent claude --preset main-claude '.author + " " + .reviewers[0].backend')" "claude codex" +ok "main-claude: ревьюер следует за автором → codex" "$(route both --role reviewer --parent claude --preset main-claude --author-backend claude '.author + " " + .reviewers[0].backend')" "claude codex" ok "main-codex под Codex-родителем: всё нативно" "$(route both --role planner --parent codex --preset main-codex '[.backend,.dispatch]|join(" ")')" "codex native" ok "флаг сильнее env" "$(DELEGATE_KIT_PRESET=main-codex route both --role planner --parent claude --preset main-claude '.preset')" "main-claude" ok "env сильнее config" "$($NODE "$AR" preset main-claude >/dev/null; DELEGATE_KIT_PRESET=main-codex route both --role planner --parent claude '.preset')" "main-codex" @@ -51,7 +51,7 @@ rm -f "$DELEGATE_KIT_HOME/config.json" echo "── автор и независимость" ok "--author-backend self: ревьюер на другом семействе" "$(route both --role reviewer --parent claude --author-backend self '.author + " " + .reviewers[0].backend + " " + (.reviewers[0].independent|tostring)')" "claude codex true" ok "--kind ui под auto: implementer на claude" "$(route both --role implementer --parent claude --kind ui '.backend')" "claude" -ok "--kind ui: ревьюер следует за UI-автором → codex" "$(route both --role reviewer --parent claude --kind ui '.author + " " + .reviewers[0].backend')" "claude codex" +ok "--kind ui: ревьюер следует за UI-автором → codex" "$(route both --role reviewer --parent claude --kind ui --author-backend claude '.author + " " + .reviewers[0].backend')" "claude codex" echo "── отсутствие CLI другого семейства" ok "ревьюер падает на нативного, independent=false" "$(route none --role reviewer --parent claude --author-backend self '.reviewers[0]|[.backend,.dispatch,(.independent|tostring)]|join(" ")')" "claude native false" @@ -91,6 +91,17 @@ ok "led: 5 сессий в cost_note" "$(route both $R --diff "$D/led.diff" '.co ok "--kind refactor: линзы correctness+standards" "$(route both $R --diff "$D/400.diff" --kind refactor '[.reviewers[].lens]|join(" ")')" "correctness standards" ok "merge_rules только при панели" "$(route both $R --diff "$D/399.diff" '.merge_rules==null')" "true" +echo "── автор обязателен для reviewer и verifier" +ok "route reviewer без автора — отказ" "$(PATH="$BASE/both" "$NODE" "$AR" route --role reviewer --parent claude 2>&1 | grep -c 'author-backend claude|codex|self is required')" "1" +ok "route verifier без автора — отказ" "$(PATH="$BASE/both" "$NODE" "$AR" route --role verifier --parent claude 2>&1 | grep -c 'is required for --role verifier')" "1" +ok "run reviewer без автора и без --backend — отказ" "$(PATH="$BASE/both" "$NODE" "$AR" run --role reviewer --prompt x --parent claude 2>&1 | grep -c 'is required')" "1" +ok "run reviewer с явным --backend автора не требует" "$(PATH="$BASE/both" "$NODE" "$AR" run --role reviewer --backend claude --model gpt-5.6-sol --prompt x --parent claude 2>&1 | grep -c 'is a codex model')" "1" +ok "planner автора не требует" "$(route both --role planner --parent claude '.backend')" "claude" +ok "route reviewer с --backend, но без автора — всё равно отказ" "$(PATH="$BASE/both" "$NODE" "$AR" route --role reviewer --parent claude --backend claude 2>&1 | grep -c 'is required')" "1" +mkdir -p "$DELEGATE_KIT_HOME/runs/rv-1"; jq -n '{id:"rv-1",role:"reviewer",backend:"codex",model:"gpt-5.6-sol",effort:"high",cwd:"/tmp",write:false,status:"finished",pid:1,started:"2026-01-01T00:00:00Z",finished:"2026-01-01T00:01:00Z",sessionId:"sess-1"}' > "$DELEGATE_KIT_HOME/runs/rv-1/meta.json" +ok "resume ревьюера без автора не отказывает (семейство из meta)" "$(PATH="$BASE/both" "$NODE" "$AR" resume rv-1 --prompt x --parent claude --no-route-hint 2>&1 | grep -c 'is required')" "0" +ok "resume ушёл на семейство прогона" "$(PATH="$BASE/both" "$NODE" "$AR" list | jq -r '[.[]|select(.id!="rv-1")][0].backend')" "codex" + echo "── отказы" ok "модель чужого семейства на run" "$(PATH="$BASE/both" "$NODE" "$AR" run --role planner --backend claude --model gpt-5.6-sol --prompt x --parent claude 2>&1 | grep -c 'is a codex model but --backend is claude')" "1" ok "--depth вне списка" "$(PATH="$BASE/both" "$NODE" "$AR" route $R --diff "$D/399.diff" --depth deep 2>&1 | grep -c 'depth must be')" "1"