From 6c0fe7022e2faf5db035a369e0e0019e4df6c061 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Sat, 15 Aug 2026 09:18:15 +0200 Subject: [PATCH] feat: Add design and format templates for orchestrator documentation --- .claude/skills/shepherd/SKILL.md | 84 ++++--------- .claude/skills/shepherd/templates/design.md | 31 +++++ .claude/skills/shepherd/templates/formats.md | 44 +++++++ .../shepherd/templates/standing-checks.md | 17 +++ README.md | 5 +- tests/test_orchestrator_contract.py | 114 ++++++++++++++---- 6 files changed, 207 insertions(+), 88 deletions(-) create mode 100644 .claude/skills/shepherd/templates/design.md create mode 100644 .claude/skills/shepherd/templates/formats.md create mode 100644 .claude/skills/shepherd/templates/standing-checks.md diff --git a/.claude/skills/shepherd/SKILL.md b/.claude/skills/shepherd/SKILL.md index 28a86a7..c67e77c 100644 --- a/.claude/skills/shepherd/SKILL.md +++ b/.claude/skills/shepherd/SKILL.md @@ -128,6 +128,12 @@ Method line omitted. For stage key `K` with assignment `S`: > {role.standing} (reviewer and final-reviewer roles only; omit the line otherwise). > **Write:** `{role.writes}` in this format: {role.format}. +`{role.standing}` is `templates/standing-checks.md`'s content, pasted verbatim regardless of what +the design emphasizes. `{role.format}` is the role's own `##` section of `templates/formats.md`, +except `architect`, whose format is `templates/design.md` pasted directly. All three paths resolve +relative to this skill's own directory — the same convention as the registry's `../_vendored/` +engine paths. + 4. Completion signal: dispatch prefers blocking; when backgrounded, or fanning reviewers out in parallel, `{role.writes}` on disk is the completion signal everywhere it's dispatched. File presence is the floor; a terminal-marker format (a ledger verdict line, `VERDICT:`) isn't done @@ -144,36 +150,17 @@ If the dispatched agent has no write access, it returns the artifact verbatim as message and the orchestrator persists it to `{role.writes}` **unchanged** — a mechanical relay, not authorship; the no-judgment-files rule is not violated. Note the relay in `_progress.md`. -`{role.standing}` for reviewer and final-reviewer roles always carries these checks, regardless -of what the design emphasizes: committed code must not reference run-internal artifacts -(`.shepherd/`, plan files, session paths); cruft preserved by a faithful migration is still a -finding — "byte-identical" instructions cover assertions/behavior, not carried-over dead code; -a comment the diff adds, edits, or moves must still be true of the code it now describes — -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. 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. Every finding carries evidence -checked against the repository, and every identifier it relies on must exist; a suspicion you -cannot ground is a question, not a finding — it belongs in your `Questions:` section, never as a -finding and never dropped. - -| role | reads | do NOT read | writes | format | -|------|-------|-------------|--------|--------| -| `verify` | `_user_request.md`, `1-triage.md`, codebase, referenced issue, current upstream sources for any claim resting on facts outside the repo | `2-design.md`, `3-success-criteria.md` | `_request_fact_check.md` | claim ledger: every request claim tagged `VALID \| STALE \| LIKELY-FIXED \| UNVERIFIABLE` with evidence (claims resting on facts outside the repo: check current upstream sources, not model memory), plus a one-line verdict — never empty | -| `explorer` | codebase | `.shepherd/` internals | `_codebase_map.md` | ≤1 page: key files · patterns · data flow · risks | -| `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, `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); then a `Questions:` section — every suspicion you could not ground, or `none` — which never blocks PASS and is surfaced to the human at the same gate | -| `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 Scope split, all review files, accepted-open-finding decisions from `_progress.md`, `question-verification.md` if present, plus open issues and PRs (`gh issue list`, `gh pr list`) — cite an existing issue it duplicates or extends | `3-success-criteria.md`, `claim.md` | `iter-N/followups.md` | ledger: item · origin (`scope-split` \| review file \| `question-verification`) · proposed disposition `fix-here \| issue \| pr-note \| drop` · one-line why; include every pre-existing, human-accepted open, and confirmed-question finding; never empty — "none" explicitly | +| role | reads | do NOT read | writes | +|------|-------|-------------|--------| +| `verify` | `_user_request.md`, `1-triage.md`, codebase, referenced issue, current upstream sources for any claim resting on facts outside the repo | `2-design.md`, `3-success-criteria.md` | `_request_fact_check.md` | +| `explorer` | codebase | `.shepherd/` internals | `_codebase_map.md` | +| `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` | +| `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` | +| `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` | +| `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` | +| `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` | +| `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` | +| `followups` | pasted Scope split, all review files, accepted-open-finding decisions from `_progress.md`, `question-verification.md` if present, plus open issues and PRs (`gh issue list`, `gh pr list`) — cite an existing issue it duplicates or extends | `3-success-criteria.md`, `claim.md` | `iter-N/followups.md` | ### Model tiering @@ -247,36 +234,10 @@ Otherwise set `state.phase="design"`. implementer reuse it. Skip for `trivial`/`small`, or when the verify fact-check already maps the files and the change is mechanical or localized (deletion, rename, inlining) — note why in `_progress.md`. -- Dispatch the `architect` stage to write `.shepherd/2-design.md`. ~1 page, no code blocks, no - file:line dumps. Product first, implementation second. A design that unifies a - style/format/template must pin it with one fully-worked example (a complete sentence or - instance showing placement and punctuation), not only named parts: - - ``` - ## What we're solving (product: the problem and who hits it) - ## How it will work (product: user-visible behavior after the change) - ## 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) - ``` - - 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. +- Dispatch the `architect` stage to write `.shepherd/2-design.md`, following `templates/design.md` + as its format. 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. - 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 "done" independently — the architect never reads it, and it never sees the solution. @@ -452,7 +413,8 @@ findings, and every confirmed question finding into `iter-N/followups.md`. - Never self-approve a gate. Write `_design.approved` / `_create_pr.approved` only on an explicit human approval — accepting the plan dialog, a clear chat "yes", or the approval skill; a rejected/edited plan, tool error, closed stream, or "continue" message is NEVER approval. The - on-disk marker is the only approval signal. + on-disk marker is the only approval signal. The agent never stands in for the human's side of a + gate. - Triage has no gate; iterate the design with the human before the gate — chat is never the record. - Verify runs on every run; the claim ledger is never empty. - Never report a dispatched stage as still running, and never end a turn waiting on one, without diff --git a/.claude/skills/shepherd/templates/design.md b/.claude/skills/shepherd/templates/design.md new file mode 100644 index 0000000..8b63cef --- /dev/null +++ b/.claude/skills/shepherd/templates/design.md @@ -0,0 +1,31 @@ +~1 page, no code blocks, no file:line dumps. Product first, implementation second. A design that +unifies a style/format/template must pin it with one fully-worked example (a complete sentence or +instance showing placement and punctuation), not only named parts: + +``` +## What we're solving (product: the problem and who hits it) +## How it will work (product: user-visible behavior after the change) +## 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) +``` + +`## Decisions`: This is an index, not a store: one line of gist plus a pointer, and the detail +lives in exactly one place. + +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). Ticket a Follow-up when you can state the question precisely now; when you +cannot yet phrase it that sharply, record it as fog — a not-yet-sharp known-unknown is a +Follow-up, never a forced Open question. 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. diff --git a/.claude/skills/shepherd/templates/formats.md b/.claude/skills/shepherd/templates/formats.md new file mode 100644 index 0000000..d7dcb5f --- /dev/null +++ b/.claude/skills/shepherd/templates/formats.md @@ -0,0 +1,44 @@ +## verify + +claim ledger: every request claim tagged `VALID | STALE | LIKELY-FIXED | UNVERIFIABLE` with +evidence (claims resting on facts outside the repo: check current upstream sources, not model memory), +plus a one-line verdict — never empty + +## explorer + +≤1 page: key files · patterns · data flow · risks + +## success_criteria + +numbered, testable criteria — each verifiable by a command or an observable behavior; no +solution details + +## implementer + +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 + +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); then a `Questions:` section — every suspicion you could not ground, or +`none` — which never blocks PASS and is surfaced to the human at the same gate + +## final_reviewer + +same verdict format as reviewer + +## fulfillment + +first line `VERDICT: PASS|FAIL`, then each criterion `MET | NOT MET` with evidence + +## followups + +ledger: item · origin (`scope-split` | review file | `question-verification`) · proposed +disposition `fix-here | issue | pr-note | drop` · one-line why; include every pre-existing, +human-accepted open, and confirmed-question finding; never empty — "none" explicitly. +This is an index, not a store: one line of gist plus a pointer, and the detail lives in exactly +one place. diff --git a/.claude/skills/shepherd/templates/standing-checks.md b/.claude/skills/shepherd/templates/standing-checks.md new file mode 100644 index 0000000..7519458 --- /dev/null +++ b/.claude/skills/shepherd/templates/standing-checks.md @@ -0,0 +1,17 @@ +committed code must not reference run-internal artifacts +(`.shepherd/`, plan files, session paths); cruft preserved by a faithful migration is still a +finding — "byte-identical" instructions cover assertions/behavior, not carried-over dead code; +a comment the diff adds, edits, or moves must still be true of the code it now describes — +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. 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. Every finding carries evidence +checked against the repository, and every identifier it relies on must exist; a suspicion you +cannot ground is a question, not a finding — it belongs in your `Questions:` section, never as a +finding and never dropped. diff --git a/README.md b/README.md index a861617..fa5ed05 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,9 @@ the plugin. Use the commands without a `shepherd:` prefix. ### Prompt reads during a run -During a run, shepherd may read engine files under `.claude/skills/_vendored/` as -instruction text. These read-only prompts are expected. +During a run, shepherd may read engine files under `.claude/skills/_vendored/` and bundled +template files under `.claude/skills/shepherd/templates/` as instruction text. These read-only +prompts are expected. If you copied `.claude/skills/` into your repo or attached this repo, allowlist the prompt reads in `.claude/settings.json`: diff --git a/tests/test_orchestrator_contract.py b/tests/test_orchestrator_contract.py index 0eaeb84..3b1ed95 100644 --- a/tests/test_orchestrator_contract.py +++ b/tests/test_orchestrator_contract.py @@ -1,12 +1,18 @@ import re -from conftest import REPO_ROOT +from conftest import REPO_ROOT, load_json ORCH = (REPO_ROOT / ".claude/skills/shepherd/SKILL.md").read_text() APPROVE_DESIGN = ( REPO_ROOT / ".claude/skills/shepherd-approve-design/SKILL.md" ).read_text() +TEMPLATES_DIR = REPO_ROOT / ".claude/skills/shepherd/templates" +STANDING = (TEMPLATES_DIR / "standing-checks.md").read_text() +DESIGN = (TEMPLATES_DIR / "design.md").read_text() +FORMATS = (TEMPLATES_DIR / "formats.md").read_text() +REGISTRY = load_json(REPO_ROOT / ".claude/skills/shepherd/registry.base.json") + def test_orchestrator_reads_config_and_registry(): assert "config.json" in ORCH @@ -100,8 +106,8 @@ def test_orchestrator_documents_why_files_are_separate(): def test_design_is_product_first(): - assert "Product first, implementation second" in ORCH - assert "## How it will work" in ORCH + assert "Product first, implementation second" in DESIGN + assert "## How it will work" in DESIGN assert "product questions first" in ORCH @@ -118,7 +124,7 @@ def test_design_iteration_uses_feedback_file_and_revision_passes(): assert "one question at a time" in ORCH assert "recommended answer" in ORCH assert "Open questions is empty" in ORCH - assert "Decisions" in ORCH + assert "## Decisions" in DESIGN # Batched rounds; the orchestrator writes only the feedback transcript, verbatim. assert "Batch a round of answers" in ORCH assert "verbatim" in ORCH and "_design_feedback.md" in ORCH @@ -245,7 +251,7 @@ def test_orchestrator_converges_on_zero_findings(): def test_fulfillment_check_gates_the_pr(): # An explicit criteria-vs-reality check runs before the create-PR confirm. assert "fulfillment.md" in ORCH - assert "MET | NOT MET" in ORCH or "MET \\| NOT MET" in ORCH + assert "MET | NOT MET" in FORMATS assert "reopens the inner loop" in ORCH assert "No PR without fulfillment" in ORCH @@ -322,10 +328,10 @@ def test_orchestrator_finish_writes_plain_commit_and_pr(): def test_open_questions_are_real_decisions(): - assert "real decisions only" in ORCH - assert "no filler" in ORCH - assert "no minimum or maximum" in ORCH - assert "Facts verifiable" in ORCH + assert "real decisions only" in DESIGN + assert "no filler" in DESIGN + assert "no minimum or maximum" in DESIGN + assert "Facts verifiable" in DESIGN def test_design_iterate_grills_decisions(): @@ -337,10 +343,10 @@ def test_design_iterate_grills_decisions(): def test_standing_checks_include_ai_slop(): - assert "comments longer than the" in ORCH - assert "abnormal defensive" in ORCH - assert "type-escape casts" in ORCH - assert "early returns" in ORCH + assert "comments longer than the" in STANDING + assert "abnormal defensive" in STANDING + assert "type-escape casts" in STANDING + assert "early returns" in STANDING def test_triage_defers_underspecified_requests(): @@ -350,7 +356,7 @@ def test_triage_defers_underspecified_requests(): def test_verify_checks_external_spec_claims(): assert "facts outside the repo" in ORCH - assert "not model memory" in ORCH + assert "not model memory" in FORMATS def test_step_references_name_their_target(): @@ -371,18 +377,18 @@ def test_step_headings_declare_their_phase(): 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 + assert "these three checks" in STANDING + assert re.search(r"test-exercised on both\s+sides", STANDING) + assert "input classes the old path handled" in STANDING + assert "silently invert" in STANDING + assert "conventions doc" in STANDING def test_scope_split_and_followups_ledger(): - assert "## Scope split" in ORCH + # Retained inline: the explicit gate-decision obligation on a non-empty Prerequisite + # refactor, plus the followups ledger / approval pins — orchestrator-obeyed procedure. 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 @@ -391,6 +397,16 @@ def test_scope_split_and_followups_ledger(): assert "never an issue without approval" in ORCH +def test_design_scope_split_wording_and_fog_test(): + # The scope-split explanatory paragraph (minus the gate-decision sentences above) and the + # fog-test wayfinder borrowing live in the architect's format template. + assert "## Scope split" in DESIGN + assert "Prerequisite refactor" in DESIGN + assert "refactor-separation" in DESIGN + assert "record it as fog" in DESIGN + assert "never a forced Open question" in DESIGN + + def test_followups_stage_is_integrated_with_configuration_and_resume(): assert "`fulfillment`, `followups`) may be absent" in ORCH assert "fulfillment or followups" in ORCH @@ -418,10 +434,10 @@ def test_reviewer_questions_slot_keeps_ungrounded_suspicion_visible(): # Findings must be grounded, but grounding must not become a silent-drop channel: PASS is # defined as zero findings, so an ungroundable suspicion needs its own slot in the reviewer # format and a route to the human, or the zero-findings convergence rule is trivially gamed. - assert "every identifier it relies on must exist" in ORCH - assert "a question, not a finding" in ORCH - assert "`Questions:` section" in ORCH - assert "never blocks PASS" in ORCH + assert "every identifier it relies on must exist" in STANDING + assert "a question, not a finding" in STANDING + assert "`Questions:` section" in STANDING + assert "never blocks PASS" in FORMATS assert "every reviewer `Questions:` entry" in ORCH @@ -471,3 +487,51 @@ def test_new_files_are_diffed_with_no_index(): # git diff -- is silent for untracked files; a per-file check on five new # files reported "unchanged" and masked a leftover workaround (run #1140). assert "git diff --no-index /dev/null " in ORCH + + +def test_skill_names_all_three_template_paths(): + # issue #27: the dispatch section must name each bundled template file by path, so the + # source of {role.standing} and {role.format} is discoverable by reading SKILL.md alone. + assert "templates/standing-checks.md" in ORCH + assert "templates/design.md" in ORCH + assert "templates/formats.md" in ORCH + + +def test_template_files_exist_and_are_non_empty(): + # Loading each as a module-level constant above already enforces existence (a missing file + # raises FileNotFoundError at collection); assert non-emptiness explicitly too, cheaply. + for name in ("standing-checks.md", "design.md", "formats.md"): + path = TEMPLATES_DIR / name + assert path.stat().st_size > 0 + + +def test_no_template_references_another_template(): + # References stay one level deep: no bundled template may point at another (or at any + # templates/ path) — the architect's format is templates/design.md pasted directly, stated + # in SKILL.md, never as a pointer inside a template. + for content in (STANDING, DESIGN, FORMATS): + assert "templates/" not in content + + +def test_formats_has_a_section_per_registry_role_except_architect(): + roles = set(REGISTRY["stage_roles"].values()) + roles.discard("architect") + assert roles, "expected at least one non-architect role" + for role in roles: + assert f"## {role}" in FORMATS + # architect's format is design.md pasted directly, not a formats.md section + assert "## architect" not in FORMATS + + +def test_index_not_a_store_wayfinder_borrowing(): + # Design's ## Decisions section and the followups ledger are each documented as an index, + # not a store: one-line gist plus a pointer, detail lives in exactly one place. + sentence = "This is an index, not a store" + assert sentence in DESIGN + assert sentence in FORMATS + + +def test_hitl_sentence_in_hard_rules(): + # Hard rules carry a one-sentence HITL summary: no dispatched agent, nor the orchestrator + # itself, answers on the human's behalf at a gate. + assert "The agent never stands in for the human's side of a" in ORCH