diff --git a/.claude/skills/shepherd/SKILL.md b/.claude/skills/shepherd/SKILL.md index bafc1ed..68a76c0 100644 --- a/.claude/skills/shepherd/SKILL.md +++ b/.claude/skills/shepherd/SKILL.md @@ -30,7 +30,7 @@ ephemeral; the files are the record. `_design_feedback.md`, `_panel.json`, `_state.json`, `_progress.md`, `_design.approved`, `_create_pr.approved`. - Per iteration in `iter-N/`: `claim.md`, `review-.md`, `final-review-.md`, - `fulfillment.md`, and the regenerable (gitignored) `diff.patch`, `test-results.txt` + `fulfillment.md`, `followups.md`, and the regenerable (gitignored) `diff.patch`, `test-results.txt` (plus `baseline.txt` and `predirty.txt` in `iter-1/` only — the pre-change oracle metrics and any pre-existing dirty paths). @@ -79,7 +79,8 @@ reliably type a slash-command. Surface everything they need into the conversatio - Resolve `registry.base.json` plus optional `.shepherd/registry.json` `uses`. - Validate every configured `use` against `registry.stage_roles` and `registry.uses`; no duplicate `use` inside `reviewers` or `final_reviewers`. Single stages (`verify`, - `architect`, `implementer`, `success_criteria`, `fulfillment`) may be absent from config. + `architect`, `implementer`, `success_criteria`, `fulfillment`, `followups`) may be absent + from config. - Record `oracle.commands`, limits, plan-mode setting, and the fully-resolved registry in `_progress.md`. - As each dispatched stage completes, append one ledger line to `_progress.md`: stage · @@ -97,8 +98,9 @@ Resume by phase: - `phase=design-gate` without the marker → re-present the design + panel (step 4, Design gate) and wait. - `phase=inner-loop`, `final-review`, or `review-run` → continue that phase. - `phase=create-pr` + `_create_pr.approved` → go to step 9 (Finish). -- `phase=create-pr` without the marker → dispatch fulfillment first if the latest - `iter-N/fulfillment.md` is missing, then act on its verdict per step 7 (Fulfillment). +- `phase=create-pr` without the marker → dispatch either fulfillment or followups if its latest + artifact (`iter-N/fulfillment.md` or `iter-N/followups.md`) is missing, then act on their + verdicts per step 7 (Fulfillment). - `phase=done` → the run is complete; report and stop. - Otherwise, re-announce the stop being waited on and stop. @@ -138,7 +140,13 @@ a comment the diff adds, edits, or moves must still be true of the code it now d stale references, wrong claims, comments restating the obvious, and comments longer than the code they describe are findings; also flag AI-slop — abnormal defensive try/catch (defensive code at trust boundaries is fine), type-escape casts (`any` or equivalent), deep nesting that -should be early returns, and other patterns inconsistent with the surrounding file. +should be early returns, and other patterns inconsistent with the surrounding file. Also require +these three checks: a conditional branch or guard the diff adds must be test-exercised on both +sides, and a rewritten path must be exercised against the input classes the old path handled — +an untested new arm is a finding; a behavior delta versus design or base that the design leaves +unstated is a finding, including a changed helper whose default/no-arg semantics silently invert; +and whatever the diff names, places, or exports must follow the repo's stated conventions doc, +with a new module importing no heavier layer than its role needs. | role | reads | do NOT read | writes | format | |------|-------|-------------|--------|--------| @@ -147,22 +155,23 @@ should be early returns, and other patterns inconsistent with the surrounding fi | `architect` | `_user_request.md`, `1-triage.md`, `_request_fact_check.md`, `_codebase_map.md` if present, `_design_feedback.md` if present (settled human decisions — constraints, not suggestions), codebase; on a revision pass also its previous `2-design.md` | `3-success-criteria.md` | `2-design.md` | the design template in step 3 (Design) | | `success_criteria` | pasted content of the "What we're solving" and "How it will work" sections of `2-design.md`, plus `_user_request.md`, `1-triage.md`, and `_request_fact_check.md` (verified facts — real paths, real coverage gaps — so criteria reference reality instead of guessing; it contains no solution) — nothing else | the rest of `2-design.md` (the solution), `claim.md` | `3-success-criteria.md` | numbered, testable criteria — each verifiable by a command or an observable behavior; no solution details | | `implementer` | `2-design.md`, `3-success-criteria.md`, `_request_fact_check.md`, `_codebase_map.md` if present, all prior `iter-*/review-*.md` + `final-review-*.md` + `fulfillment.md` | — | source edits + `iter-N/claim.md` | what done · every finding fixed, none skipped or deferred · for a behavior change, add a regression test — ideally shown red before the fix and green after, with the red→green noted in `claim.md` — never weaken/delete tests | -| `reviewer` | pasted content of `2-design.md`, `3-success-criteria.md`, `iter-N/diff.patch`, `iter-N/test-results.txt`, plus the repository itself (working tree, git history, read-only commands) — no other `.shepherd/` files | `claim.md`, peer reviewers' output | `iter-N/review-.md` | first line `VERDICT: PASS\|FAIL` (PASS = zero findings), then findings tagged `blocker\|major\|minor\|nit` | +| `reviewer` | pasted content of `2-design.md`, `3-success-criteria.md`, `iter-N/diff.patch`, `iter-N/test-results.txt`, plus the repository itself (working tree, git history, read-only commands) — no other `.shepherd/` files | `claim.md`, peer reviewers' output | `iter-N/review-.md` | first line `VERDICT: PASS\|FAIL` (PASS = zero findings, `pre-existing`-tagged ones excepted), then findings tagged `blocker\|major\|minor\|nit`; a defect in adjacent code that predates the diff carries the extra tag `pre-existing` — reported, never silenced, routed to step 7 (Fulfillment + create-PR confirm) | | `final_reviewer` | same as reviewer, but judging the post-fix integrated state: interactions with unchanged code, consumer/contract impact, doc/AGENTS staleness — not a second pass over the patch | `claim.md`, peer reviewers' output | `iter-N/final-review-.md` | same verdict format as reviewer | | `fulfillment` | pasted content of `3-success-criteria.md`, `iter-N/diff.patch`, `iter-N/test-results.txt`, `iter-N/claim.md`, plus the working tree (may run the non-mutating check a criterion names) | `2-design.md` solution details, review files | `iter-N/fulfillment.md` | first line `VERDICT: PASS\|FAIL`, then each criterion `MET \| NOT MET` with evidence | +| `followups` | pasted content of the design's "Scope split" section and of every `iter-*/review-*.md` + `final-review-*.md` (all iterations) | `3-success-criteria.md`, `claim.md` | `iter-N/followups.md` | ledger: item · origin (`scope-split` \| review file) · proposed disposition `fix-here \| issue \| pr-note \| drop` · one-line why; never empty — "none" explicitly | ### Model tiering `"auto"` (the shipped default) lets the orchestrator pick a model per role and triage tier; an explicit name (`opus`, `sonnet`, `haiku`) is used verbatim. Resolve `"auto"` as: `implementer` → `haiku` (`sonnet` for `medium`/`large` — a subtle change is not transcription); `verify`, -`explorer`, `success_criteria`, `fulfillment`, `reviewer` → `sonnet`; `architect` → `opus` +`explorer`, `success_criteria`, `fulfillment`, `followups`, `reviewer` → `sonnet`; `architect` → `opus` (`sonnet` for a revision pass — it folds feedback into an existing design without re-exploring); `final_reviewer` → `opus` (`sonnet` for `trivial`/`small`). `sonnet` is the floor for review — never `haiku`. Pre-gate stages (verify, explorer, architect, success_criteria) resolve `"auto"` at dispatch time from this table. At step 4 (Design gate), record all picks in `_panel.json`: the pre-gate ones as the record of what ran, the post-gate ones (implementer, -reviewers, final reviewers, fulfillment) for the human to edit before approving. +reviewers, final reviewers, fulfillment, followups) for the human to edit before approving. ## Procedure @@ -204,8 +213,11 @@ switch) — every later stage reads that checkout. Then set `state.phase="verify ### 2. Verify — `phase=verify` -Run the `verify` stage on every run — never skipped by tier. It builds the authoritative claim -ledger in `_request_fact_check.md`: every claim in the request tagged +Run the `verify` stage on every run — never skipped by tier. On a fresh run whose archived +predecessor targets the same repo and base commit, verify may run in delta mode: re-check only +claims whose subject changed (new decisions, moved code, upstream PR state), re-affirming the +rest against the archived ledger with a citation — narrowed, never skipped. It builds the +authoritative claim ledger in `_request_fact_check.md`: every claim in the request tagged `VALID | STALE | LIKELY-FIXED | UNVERIFIABLE` with evidence (running an existing test to verify a claim is fine — remove artifacts it leaves). **For a review-only run the claim source is the PR/branch description** (fetch it, e.g. `gh pr view`): tag each thing the PR says it does against @@ -237,6 +249,7 @@ Otherwise set `state.phase="design"`. ## Proposed solution (implementation approach) ## Alternatives + the call ## Major changes (key files/areas only — never an exhaustive file list) + ## Scope split (This PR · Prerequisite refactor · Follow-ups) ## Risks ## Open questions (real decisions only — each: options + recommended answer; no filler) ## Decisions (from _design_feedback.md when it exists; otherwise starts empty) @@ -245,6 +258,17 @@ Otherwise set `state.phase="design"`. Facts verifiable in the repo or issue belong in the design body, not Open questions — ask as many decisions as the design needs, no minimum or maximum. + Scope split partitions the work: This PR (what the diff will contain), Prerequisite refactor + (restructuring the change needs — lands first, as its own PR, never as a commit inside the + main PR), Follow-ups (adjacent debt or gaps found during exploration that this PR + deliberately leaves). Before declaring Prerequisite refactor empty, check the repo's + refactor-separation policy (CLAUDE.md / CONTRIBUTING): where the repo mandates + refactor-lands-first, any "refactor first, then the change" structure IS a non-empty + prerequisite — calling it internal commit sequencing is a design defect. A non-empty + Prerequisite refactor is an explicit gate decision: surface it to the human and proceed only + on their confirmed choice — deliver the prerequisite first, or (only on the human's explicit + pick, never as the default) fold it in. + For a review-only run, `2-design.md` is the review scope: what to check and which reviewers. - Dispatch the `success_criteria` stage: paste it ONLY the two product sections of the design (plus request, triage, and the fact-check) and have it write `.shepherd/3-success-criteria.md`. It defines @@ -255,8 +279,10 @@ Otherwise set `state.phase="design"`. **Iterate — the conversation is the orchestrator's; every rewrite is a subagent's.** - Present the FULL `2-design.md` + `3-success-criteria.md` (see "Keep the human in the loop"). - Grill decisions one question at a time (wait for each answer): options + your recommended - answer; product questions first, implementation after. Look up facts yourself; only decisions - go to the human. Walk dependencies in order — if Open questions miss a real fork, ask it. + answer; product questions first, implementation after. Look up facts yourself — including the + repo's conventions doc for any naming/signature/parameter question; a convention that settles + the question is a fact, not a human decision. Only decisions go to the human. Walk + dependencies in order — if Open questions miss a real fork, ask it. YAGNI — cut speculative scope. - **Batch a round of answers**, then append them verbatim to `_design_feedback.md` (append-only; the orchestrator writes only this file, never the design or criteria). @@ -277,7 +303,11 @@ Do not edit source files until `.shepherd/_design.approved` exists. Set Propose the per-run review panel from the configured roster: start from the triage tier, adjust for the actual design scope, and pick in config order unless the design's risk calls for a specific reviewer. Two or more reviewers must differ in lens (e.g. diff-correctness vs -adversarial vs live-probe vs contract/consumer). **Resolve every `"auto"` model to a concrete name** (see Model +adversarial vs live-probe vs contract/consumer). At every human gate, present the panel with a +one-line lens-fit assessment per reviewer stating whether the lens is live for this design or +structurally muted (e.g. a complexity/deletion lens on a behavior-preserving move that forbids +cuts), and invite roster/model changes; the orchestrator never edits the panel itself, and a panel +change folds into the gate, not a new stop. **Resolve every `"auto"` model to a concrete name** (see Model tiering) at the settled tier — inline on each reviewer, and in a `models` map for the single stages (only those whose config model is `"auto"`; an explicit model keeps its name). Write `.shepherd/_panel.json`: @@ -285,7 +315,7 @@ stages (only those whose config model is `"auto"`; an explicit model keeps its n ```json { "tier": "small", "reason": "localized low-risk change", "models": { "verify": "sonnet", "architect": "opus", "implementer": "haiku", - "success_criteria": "sonnet", "fulfillment": "sonnet" }, + "success_criteria": "sonnet", "fulfillment": "sonnet", "followups": "sonnet" }, "reviewers": [{ "use": "staff-review", "model": "sonnet" }], "final_reviewers": [{ "use": "thermonuclear", "model": "sonnet" }], "inner_iterations": 2, "final_review_rounds": 1 } @@ -346,8 +376,10 @@ For each iteration `N`: `3-success-criteria.md`, `diff.patch`, and `test-results.txt`, plus read access to the repository. They stay blind to `claim.md` and peer reviews. 6. Converge when the oracle is green and baseline-consistent and every reviewer verdict is PASS - — every finding gets fixed, whatever its severity: nits too; the implementer - never skips or defers one. The lone exception is the human's: a finding fixable only by + — every finding gets fixed, whatever its severity: nits too (`pre-existing`-tagged findings + skip the loop and route to the followups ledger at step 7 (Fulfillment + create-PR confirm)); + the implementer + never skips or defers one. The other exception is the human's: a finding fixable only by changing the approved design or criteria (see Hard rules). Otherwise iterate until `inner_iterations`; then stop and present a findings table (fixed / open), the oracle status, and the options: extend the limit, accept with open findings recorded, or abandon. @@ -370,13 +402,24 @@ step 5 (Inner loop) convergence rule, set `state.phase="create-pr"`. On entering `phase="create-pr"`, dispatch the `fulfillment` stage: it judges the diff, tests, and claim against `3-success-criteria.md` and writes `iter-N/fulfillment.md` with each criterion -`MET | NOT MET`. +`MET | NOT MET`. Also dispatch the `followups` stage: it compiles the design's Scope split +leftovers and every `pre-existing`-tagged finding into `iter-N/followups.md` with a proposed +disposition per item. - Any `NOT MET` criterion reopens the inner loop like a blocker finding, within the same limits. When limits are exhausted, or the human disputes a criterion itself, ask the human: accept with the exception recorded, extend the limit, or abandon. -- When fulfillment passes: no plan mode. Summarize in chat — the fulfillment table, oracle - status, reviewer verdicts, fixed findings, `git diff --stat`. Ask **"commit & open PR?"** and +- When fulfillment passes: no plan mode. Summarize in chat — the fulfillment table, the + followups ledger verbatim, oracle status, reviewer verdicts, fixed findings, `git diff --stat`. + The human dispositions each ledger item: `fix-here` reopens the inner loop; `issue` is created + only now, on this approval; `pr-note` lands in the PR body; `drop` is recorded in + `_progress.md`. Never silent; never an issue without approval. When every fix-here item is + doc/comment/style-only with no behavior or signature change (combined ≤10 lines), propose — + in the same gate exchange, with each option's cost — converging that fix round via ONE + delta-focused panel reviewer plus a fulfillment-delta check instead of the full panel; the + human's reply picks the mode, and any finding, oracle delta, or out-of-scope change in a + light round escalates back to the full step 5 (Inner loop) convergence rule. Ask + **"commit & open PR?"** and proceed only on a clear yes, which records `_create_pr.approved`. Headless runs use `/shepherd-approve-create-pr`. This approves creating the PR, not merging it. @@ -402,7 +445,10 @@ from step 5 (Inner loop). message, never enumerate changes obvious from the diff; evidence (fulfillment, oracle, reviews) is one short clause, not a transcript; run files stay ignored. When the run completes a tracked issue, end the PR body with `Closes #N` (auto-close on merge); reference - parent/epic issues non-closingly (`Part of #M`). + parent/epic issues non-closingly (`Part of #M`). Approved `pr-note` items land as a short + Follow-ups list in the body. Every number or factual claim in the body (test counts, + referenced files/issues) must match the final oracle run and repo state — a + stale count or nonexistent reference is a defect. 3. If a writable remote exists, push and open a PR. Record the evidence summary, approval timestamps, and PR URL in `_progress.md`, then set `state.phase="done"`. @@ -432,4 +478,7 @@ from step 5 (Inner loop). or the human explicitly accepts the exception. - A finding fixable only by changing the approved `2-design.md` / `3-success-criteria.md` is the human's call — surface it at the gate; never edit an approved artifact to silence a finding. +- A design or review scope may downgrade severity or route a finding to follow-up; it + never instructs reviewers not to report a class of findings — adjacent pre-existing defects + are tagged, surfaced at the create-PR gate, and issues for them are created only on human approval. - Commit/PR text: plain, PR-template-following, no obvious-from-the-diff narration (step 9, Finish). diff --git a/.claude/skills/shepherd/config.schema.json b/.claude/skills/shepherd/config.schema.json index 17a5fce..b9ce13a 100644 --- a/.claude/skills/shepherd/config.schema.json +++ b/.claude/skills/shepherd/config.schema.json @@ -7,7 +7,7 @@ "required": ["stages"], "properties": { "stages": { - "description": "Engine and model assignments for each shepherd stage. Single stages (verify, architect, implementer, success_criteria, fulfillment) are optional: when absent, the built-in role table drives the stage without an engine.", + "description": "Engine and model assignments for each shepherd stage. Single stages (verify, architect, implementer, success_criteria, fulfillment, followups) are optional: when absent, the built-in role table drives the stage without an engine.", "type": "object", "additionalProperties": false, "required": ["reviewers", "final_reviewers"], @@ -17,6 +17,7 @@ "implementer": { "$ref": "#/$defs/singleStageValue" }, "success_criteria": { "$ref": "#/$defs/singleStageValue" }, "fulfillment": { "$ref": "#/$defs/singleStageValue" }, + "followups": { "$ref": "#/$defs/singleStageValue" }, "reviewers": { "description": "Roster for inner-loop reviewers. The design gate selects a per-run subset.", "type": "array", "items": { "$ref": "#/$defs/stageValue" }, "minItems": 1 }, "final_reviewers": { "description": "Roster for post-convergence reviewers. May be empty.", "type": "array", "items": { "$ref": "#/$defs/stageValue" }, "minItems": 0 } } diff --git a/.claude/skills/shepherd/registry.base.json b/.claude/skills/shepherd/registry.base.json index 32f3301..ebc158d 100644 --- a/.claude/skills/shepherd/registry.base.json +++ b/.claude/skills/shepherd/registry.base.json @@ -5,6 +5,7 @@ "implementer": "implementer", "success_criteria": "success_criteria", "fulfillment": "fulfillment", + "followups": "followups", "reviewers": "reviewer", "final_reviewers": "final_reviewer" }, @@ -27,12 +28,12 @@ "staff-review": { "roles": ["reviewer", "final_reviewer"], "engine": "../_vendored/staff-review/ENGINE.md", - "scope": "apply to the pasted diff; focus on correctness, edge cases, test coverage, and adherence to the repo's stated conventions (CLAUDE.md / CONTRIBUTING and their AGENTS.md children); skip its argument parsing and gh/PR flows" + "scope": "apply to the pasted diff; focus on correctness, edge cases, test coverage, and adherence to the repo's stated conventions (CLAUDE.md / CONTRIBUTING and their AGENTS.md children); verify every conditional arm the diff adds is test-exercised on both sides and a rewritten path against the old path's input classes; a changed helper keeps its call contract (no silently inverted defaults); check names, enum/type aliases, and test placement against the repo's conventions doc; skip its argument parsing and gh/PR flows" }, "thermonuclear": { "roles": ["reviewer", "final_reviewer"], "engine": "../_vendored/thermonuclear/ENGINE.md", - "scope": "follow as instruction text, not as an invoked skill; apply maintainability standards to the pasted diff: 1k-line file ceiling, no spaghetti branching, and code-judo simplification; map presumptive blockers to blocker or major" + "scope": "follow as instruction text, not as an invoked skill; apply maintainability standards to the pasted diff: 1k-line file ceiling, no spaghetti branching, and code-judo simplification; trace the import graph of any new module — importing heavier layers than its role needs, or module-global state shared across instances, is a finding; map presumptive blockers to blocker or major" }, "code-review": { "roles": ["reviewer", "final_reviewer"], @@ -42,7 +43,7 @@ "ponytail-review": { "roles": ["reviewer", "final_reviewer"], "engine": "../_vendored/ponytail-review/ENGINE.md", - "scope": "apply to the pasted diff; complexity/deletion lens only — correctness stays with the other reviewers. Judge every cut against the pasted 2-design.md: a cut that contradicts an approved decision or a stated behavior-preservation constraint is a human-call finding, not a recommendation. First line VERDICT: PASS|FAIL, then its one-line findings mapped to severities (delete/yagni → minor, shrink/doc-style → nit), keeping its net-lines metric at the end. Clarity outranks the line count: never propose nested ternaries, dense one-liners, or concern-merging as cuts — a cut that makes the code harder to read is not a finding" + "scope": "apply to the pasted diff; complexity/deletion lens only — correctness stays with the other reviewers. Judge every cut against the pasted 2-design.md: a cut that contradicts an approved decision or a stated behavior-preservation constraint is a human-call finding, not a recommendation. First line VERDICT: PASS|FAIL, then its one-line findings mapped to severities (delete/yagni → minor, shrink/doc-style → nit), keeping its net-lines metric at the end. Half-migrated call sites and machinery disproportionate to the problem are findings. Clarity outranks the line count: never propose nested ternaries, dense one-liners, or concern-merging as cuts — a cut that makes the code harder to read is not a finding" } } } diff --git a/tests/test_orchestrator_contract.py b/tests/test_orchestrator_contract.py index 6988182..f2431cc 100644 --- a/tests/test_orchestrator_contract.py +++ b/tests/test_orchestrator_contract.py @@ -15,7 +15,7 @@ def test_orchestrator_reads_config_and_registry(): def test_orchestrator_skill_stays_compact(): # Keep the orchestrator readable, but do not force removal of operational guidance. - assert len(ORCH.splitlines()) <= 450 + assert len(ORCH.splitlines()) <= 500 def test_orchestrator_documents_per_reviewer_files(): @@ -328,7 +328,8 @@ def test_open_questions_are_real_decisions(): def test_design_iterate_grills_decisions(): assert "Grill decisions" in ORCH assert "Look up facts yourself" in ORCH - assert "only decisions" in ORCH + assert "Only decisions go to the human" in ORCH + assert "a convention that settles" in ORCH assert "miss a real fork" in ORCH @@ -364,3 +365,53 @@ def test_step_headings_declare_their_phase(): assert len(headings) == 9 missing = [h for h in headings if "`phase=" not in h] assert missing == [], f"headings without a phase annotation: {missing}" + + +def test_standing_checks_cover_new_arms_and_disclosure(): + assert "these three checks" in ORCH + assert re.search(r"test-exercised on both\s+sides", ORCH) + assert "input classes the old path handled" in ORCH + assert "silently invert" in ORCH + assert "conventions doc" in ORCH + + +def test_scope_split_and_followups_ledger(): + assert "## Scope split" in ORCH + assert "Prerequisite refactor" in ORCH + assert "explicit gate decision" in ORCH + assert "refactor-separation" in ORCH + assert "never as the default" in ORCH + assert "followups.md" in ORCH + assert "pre-existing" in ORCH + assert "never instructs reviewers not to report" in ORCH + assert "only on human approval" in ORCH + assert "never an issue without approval" in ORCH + + +def test_followups_stage_is_integrated_with_configuration_and_resume(): + assert "`fulfillment`, `followups`) may be absent" in ORCH + assert "fulfillment or followups" in ORCH + assert "`iter-N/followups.md`" in ORCH + assert "fulfillment, followups)" in ORCH + assert '"followups": "sonnet"' in ORCH + + +def test_pr_body_claims_are_verified(): + assert "must match the final oracle run" in ORCH + assert "stale count or nonexistent reference" in ORCH + + +def test_verify_delta_mode_on_rerun(): + assert "delta mode" in ORCH + assert "narrowed, never skipped" in ORCH + + +def test_gate_panel_lens_fit_nudge(): + assert "lens-fit assessment" in ORCH + assert "never edits the panel itself" in ORCH + + +def test_light_rereview_for_trivial_fix_here(): + assert "delta-focused panel reviewer plus a fulfillment-delta check" in ORCH + assert "picks the mode" in ORCH + assert "escalates back to the full step 5" in ORCH diff --git a/tests/test_registry_shape.py b/tests/test_registry_shape.py index 4dd7d43..283ca6e 100644 --- a/tests/test_registry_shape.py +++ b/tests/test_registry_shape.py @@ -3,7 +3,7 @@ REGISTRY = load_json(REPO_ROOT / ".claude/skills/shepherd/registry.base.json") CONFIG = load_json(REPO_ROOT / ".claude/skills/shepherd/config.default.json") -SINGLE_STAGES = ("verify", "architect", "implementer", "success_criteria", "fulfillment") +SINGLE_STAGES = ("verify", "architect", "implementer", "success_criteria", "fulfillment", "followups") STAGES = set(SINGLE_STAGES) | {"reviewers", "final_reviewers"} ROLES = set(SINGLE_STAGES) | {"reviewer", "final_reviewer"}