From 5309c8e03260f66da57366fc99f901e989e5660a Mon Sep 17 00:00:00 2001 From: Matthew Goodwin Date: Thu, 29 Oct 2026 14:35:30 -0500 Subject: [PATCH 1/2] rule-selector: optional check line, run once by stage-implement Co-Authored-By: Claude Opus 5.5 --- .../attachments/acme-rule-selector/SKILL.md | 3 +- .../fixtures/rule-selector/pack/skills.jsonc | 15 +++- .../fixtures/rule-selector/pack/stubs.jsonc | 3 +- lib/skills/__tests__/rule-selector.test.ts | 65 +++++++++++++- plugins/mattstack/.claude-plugin/plugin.json | 2 +- plugins/mattstack/CERTIFICATION.md | 1 + plugins/mattstack/README.md | 3 +- .../stage-implement/fix-rule-breaks.block.md | 12 +++ .../pipeline/stage-implement/index.block.md | 15 +++- .../stage-implement/rule-selector.block.md | 43 +++++++++ .../the-contract.block.md | 9 ++ .../plugin/skills/extending-a-pack/SKILL.md | 2 +- .../plugin/skills/extending-a-pack/props.md | 6 +- plugins/mattstack/skills/shepherdr/SKILL.md | 90 +++++++++---------- .../mattstack/skills/shepherdr/compiled.json | 4 +- plugins/mattstack/skills/wrap-up/SKILL.md | 4 +- .../mattstack/skills/wrap-up/compiled.json | 4 +- .../writing-style-conversational/SKILL.md | 24 ++--- .../compiled.json | 4 +- .../skills/writing-style-sparse/SKILL.md | 24 ++--- .../skills/writing-style-sparse/compiled.json | 4 +- .../skills/writing-style-structured/SKILL.md | 24 ++--- .../writing-style-structured/compiled.json | 4 +- 23 files changed, 259 insertions(+), 106 deletions(-) create mode 100644 plugins/mattstack/attachments/pipeline/stage-implement/fix-rule-breaks.block.md create mode 100644 plugins/mattstack/attachments/pipeline/stage-implement/rule-selector.block.md diff --git a/lib/skills/__tests__/fixtures/rule-selector/attachments/acme-rule-selector/SKILL.md b/lib/skills/__tests__/fixtures/rule-selector/attachments/acme-rule-selector/SKILL.md index f4cfa7c24d..a5cc344196 100644 --- a/lib/skills/__tests__/fixtures/rule-selector/attachments/acme-rule-selector/SKILL.md +++ b/lib/skills/__tests__/fixtures/rule-selector/attachments/acme-rule-selector/SKILL.md @@ -9,8 +9,9 @@ metadata: # acme rule selector Resolve to `selector: none` when the `ACME_RULES` environment variable is -`off`. Otherwise resolve to these three lines: +`off`. Otherwise resolve to these four lines: selector: acme-rules select --format {format} verdicts: acme-rules verdicts --selection {selection} --verdicts {verdicts} fence: acme-verdicts +check: acme-rules check --base {base} diff --git a/lib/skills/__tests__/fixtures/rule-selector/pack/skills.jsonc b/lib/skills/__tests__/fixtures/rule-selector/pack/skills.jsonc index 4faeab9b3b..7ba30e958b 100644 --- a/lib/skills/__tests__/fixtures/rule-selector/pack/skills.jsonc +++ b/lib/skills/__tests__/fixtures/rule-selector/pack/skills.jsonc @@ -1,8 +1,21 @@ { "version": 2, + "pipelines": { + "feature": [ + "mattstack:stage-provision", + "mattstack:stage-plan", + "mattstack:stage-gates", + "mattstack:stage-evidence", + "mattstack:stage-implement", + "mattstack:stage-self-review", + "mattstack:stage-ship", + "mattstack:stage-watch-ci" + ] + }, "bindings": { "mattstack:review/rule-selector": { "rule-selector": "acme:acme-rule-selector" }, "mattstack:self-review/rule-selector": { "rule-selector": "acme:acme-rule-selector" }, - "mattstack:receive-review/rule-selector": { "rule-selector": "acme:acme-rule-selector" } + "mattstack:receive-review/rule-selector": { "rule-selector": "acme:acme-rule-selector" }, + "mattstack:stage-implement/rule-selector": { "rule-selector": "acme:acme-rule-selector" } } } diff --git a/lib/skills/__tests__/fixtures/rule-selector/pack/stubs.jsonc b/lib/skills/__tests__/fixtures/rule-selector/pack/stubs.jsonc index 7af15e9db4..1dbee66ac3 100644 --- a/lib/skills/__tests__/fixtures/rule-selector/pack/stubs.jsonc +++ b/lib/skills/__tests__/fixtures/rule-selector/pack/stubs.jsonc @@ -2,6 +2,7 @@ "verbs": { "review": { "engine": "review", "description": "Review a teammate's MR" }, "self-review": { "engine": "self-review", "description": "Self-review the current branch" }, - "receive-review": { "engine": "receive-review", "description": "Act on review feedback on your MR" } + "receive-review": { "engine": "receive-review", "description": "Act on review feedback on your MR" }, + "work": { "engine": "work", "pipeline": "feature", "description": "Run a unit of work end to end" } } } diff --git a/lib/skills/__tests__/rule-selector.test.ts b/lib/skills/__tests__/rule-selector.test.ts index 69e2e5d873..33ef4317be 100644 --- a/lib/skills/__tests__/rule-selector.test.ts +++ b/lib/skills/__tests__/rule-selector.test.ts @@ -15,13 +15,18 @@ const seam = (verb: string) => ``. | 2026-10-23 | mattstack:work-progress | pure | pass | 5/5 scenarios, 5/5 haiku reps each (2 herd kept, epic and 2 plan added); full roster 22/23, the one miss (billing creating-a-pack) also misses on the old description | replaces the herd-only progress skill: the bundled script renders a herd (`--herd`) or plans and epics (`--plan` repeatable, `--title`, `--tree`) from rt's plan records; the skill picks the input, prints the view verbatim and never calls a plan done unless its row says so; RED: no skill loaded on plan or epic prompts, 6 hand calls and a hand-built table, plan 1 guessed done from plan 2 having started; GREEN (claude -p --plugin-dir, opus, sandboxed rt): epic prompt Skill + ls + 1 script call, plan "where is my plan at" and "how far along is it" Skill + 1 script call each, all printed verbatim, "herd progress" triggers it; the brief's description first missed "where is my plan at", fixed in 15e6d8f by adding that phrase; certify exit 0 | | 2026-10-23 | mattstack:shepherdr | pure | pass | n/a (description unchanged) | Progress pointer renamed to mattstack:work-progress, recompiled into skills/shepherdr; certify exit 0 | | 2026-10-28 | mattstack:review, mattstack:self-review, mattstack:receive-review | pure | pass | 4/4 GREEN walkthrough scenarios | optional `rule-selector` prop (`rule-selector@1`, replace, default empty): the engines select written rules before dispatch, ask the reviewer for one verdict per rule and turn failed rules into findings; unbound it is a no-op; GREEN describe-only walk of the compiled review: unbound skips every rule step, a selector exiting 1 is one setup observation and no rule steps, a reply with no verdict block gets exactly one repair then an `Unanswered rules:` line in the Assessment, an empty selection sends no verdict request and saves an empty verdict file; certify exit 0 on every touched root | +| 2026-10-29 | mattstack:stage-implement, mattstack:review-rules-select-body | pure | pass | 4/4 GREEN walkthrough scenarios | `rule-selector@1` takes an optional fourth line `check: ` with `{base}`; stage-implement gains a `rule-selector` prop and a Check the work against the team's rules step between committing and recording the commits: runs the check once from the branch point, fixes the rules the change breaks, never reruns and never fails the stage; the review verbs read a fill with a `check:` line as the three-line shape; RED: implement never ran the check and self-review read the four-line fill as neither shape; GREEN: check runs once, a non-zero exit is noted for the return, `selector: none` skips, self-review runs the selector and ignores `check:`; certify exit 0 on every touched root | (Ledger rows are appended by the orchestrating session as each lane lands -- lanes themselves never edit this file.) diff --git a/plugins/mattstack/README.md b/plugins/mattstack/README.md index 7ba7f41fa8..603ff9f9f5 100644 --- a/plugins/mattstack/README.md +++ b/plugins/mattstack/README.md @@ -174,7 +174,8 @@ running that one step outside a full pipeline. - **mattstack:stage-evidence** -- capture the before-state per the evidence plan, before implementation. - **mattstack:stage-implement** -- do the implementation under the TDD - floor (no props; pure methodology). + floor, then run the bound rule selector's optional `check:` once + (`rule-selector` prop). - **mattstack:stage-self-review** -- self-review checkpoint between implementation and ship. - **mattstack:stage-ship** -- publish the unit of work: push, open the diff --git a/plugins/mattstack/attachments/pipeline/stage-implement/fix-rule-breaks.block.md b/plugins/mattstack/attachments/pipeline/stage-implement/fix-rule-breaks.block.md new file mode 100644 index 0000000000..87cbe3acef --- /dev/null +++ b/plugins/mattstack/attachments/pipeline/stage-implement/fix-rule-breaks.block.md @@ -0,0 +1,12 @@ +--- +node: fix_rule_breaks +--- + +# Fix the rules the change breaks, then commit + +Fix each rule the check showed the change breaking. A fix that changes +behavior is test-first, as in the rest of this stage. Commit the fixes on +the branch, inside the worktree. A rule you judge does not apply, or a +fix you cannot make without leaving the plan, stays as it is: name it in +one line when the stage returns to the orchestrator, and self-review will +judge it. Then record the commits; there is no second check. diff --git a/plugins/mattstack/attachments/pipeline/stage-implement/index.block.md b/plugins/mattstack/attachments/pipeline/stage-implement/index.block.md index 46fc2b0e62..1177a88b26 100644 --- a/plugins/mattstack/attachments/pipeline/stage-implement/index.block.md +++ b/plugins/mattstack/attachments/pipeline/stage-implement/index.block.md @@ -33,6 +33,10 @@ flowchart TB commit_incrementally[Commit incrementally on branch, inside worktree] next_move{Next move once committed?} stop_implement_push[STOP: implement never pushes; ship owns the push]:::stop + check_bound{Does the rule selector resolve to a check line?} + check_rules + check_result{What did the check report?} + fix_rule_breaks git_log_shas[/git log --format=%h ..HEAD/] run_field_set_commits[/"run_field_set {key: commits, value: , stage: implement}"/] run_stage_fail[/"run_stage {action: fail, stage: implement, reason}"/] @@ -54,9 +58,16 @@ flowchart TB superpowers_chain --> commit_incrementally superpowers_chain -->|a failure the approach cannot get past| run_stage_fail commit_incrementally --> next_move - next_move -->|record the commits| git_log_shas + next_move -->|check the work, then record the commits| check_bound next_move -->|tempted to push| stop_implement_push - stop_implement_push --> git_log_shas + stop_implement_push --> check_bound + check_bound -->|no: unbound, selector: none, or no check: line| git_log_shas + check_bound -->|yes| check_rules + check_rules --> check_result + check_result -->|exit 0, nothing the change breaks| git_log_shas + check_result -->|exit 0, rules the change breaks| fix_rule_breaks + check_result -->|non-zero exit: noted for the return| git_log_shas + fix_rule_breaks --> git_log_shas git_log_shas --> run_field_set_commits run_field_set_commits --> implement_done run_stage_fail --> stage_failed diff --git a/plugins/mattstack/attachments/pipeline/stage-implement/rule-selector.block.md b/plugins/mattstack/attachments/pipeline/stage-implement/rule-selector.block.md new file mode 100644 index 0000000000..1f4c090c8b --- /dev/null +++ b/plugins/mattstack/attachments/pipeline/stage-implement/rule-selector.block.md @@ -0,0 +1,43 @@ +--- +node: check_rules +props: + rule-selector: + merge: replace + default: "" + contract: "rule-selector@1" +--- + +# Check the work against the team's rules + +The pack's rule selector is the text after the italic line at the end of +this section; an unbound slot leaves it empty. Resolve it now, on this +run, since it may consult settings or the environment. It resolves to the +single line `selector: none`, or to `selector:`, `verdicts:` and `fence:` +lines with an optional fourth, `check: `. Only the +`check:` line matters here. Skip this step (the `no` edge of Does the rule +selector resolve to a check line?) when the text is empty, resolves to +`selector: none`, or has no `check:` line. + +With a `check:` line: + +1. Find the branch point the way self-review does: `git merge-base + origin/HEAD HEAD`, or the default branch by name when `origin/HEAD` is + unset. +2. Replace `{base}` in the command with that sha and run it once on Bash, + in the worktree, with nothing on stdin. Capture its stdout and its exit + status. +3. Read the stdout. It is written for you: the rules that apply to this + change and the lines worth a look. Decide, rule by rule, whether the + change breaks it. + +An exit other than 0 is one observation, never a failure of the stage: +quote the exit status and the first line of stderr in one line when the +stage returns to the orchestrator, fix nothing on its account, and record +the commits. + +Run it once. The check never runs again in this stage: self-review judges +the result with verdicts. + +*If a rule below asks for a move this skill's graph marks STOP, take the off-script edge instead.* + +{{props.rule-selector}} diff --git a/plugins/mattstack/attachments/review-rules-select-body/the-contract.block.md b/plugins/mattstack/attachments/review-rules-select-body/the-contract.block.md index 32267e0e7b..10a5a3351f 100644 --- a/plugins/mattstack/attachments/review-rules-select-body/the-contract.block.md +++ b/plugins/mattstack/attachments/review-rules-select-body/the-contract.block.md @@ -18,6 +18,15 @@ shapes. - `fence: `, the tag of the fenced code block the reviewer returns its verdict set in. + An optional fourth line, `check: `, holds the + placeholder `{base}`. The implement stage runs it in the worktree with + nothing on stdin, `{base}` replaced by the branch point, and reads its + stdout as the rules that apply and the lines worth a look; exit 0 means + it ran, and any other exit is one observation that never blocks. The + review verbs never run it: a fill with a `check:` line is still the + three-line shape here. The line is additive, so the contract stays + `rule-selector@1`, and a fill without it behaves as before. + **The selection** is the JSON the selector prints. This skill reads only its `rules` list: each rule has an `id`, a `severity` (`blocking` or `advisory`) and a `summary`. diff --git a/plugins/mattstack/plugin/skills/extending-a-pack/SKILL.md b/plugins/mattstack/plugin/skills/extending-a-pack/SKILL.md index 9fdcb032d1..2b42a7a688 100644 --- a/plugins/mattstack/plugin/skills/extending-a-pack/SKILL.md +++ b/plugins/mattstack/plugin/skills/extending-a-pack/SKILL.md @@ -384,7 +384,7 @@ metadata: ``` `provides` is the prop's contract when it has one (`criteria` takes -`review-criteria@1`, `rule-selector` takes `rule-selector@1`); a `rules` prop has none, so any `provides` binds. +`review-criteria@1`, `rule-selector` takes `rule-selector@1`, whose optional `check:` line `stage-implement` runs); a `rules` prop has none, so any `provides` binds. The body: the rule, its reason, and the decision it changes. The stage keeps its own flow; the fill carries only what the team adds. diff --git a/plugins/mattstack/plugin/skills/extending-a-pack/props.md b/plugins/mattstack/plugin/skills/extending-a-pack/props.md index 06267d0e82..5243ee3ded 100644 --- a/plugins/mattstack/plugin/skills/extending-a-pack/props.md +++ b/plugins/mattstack/plugin/skills/extending-a-pack/props.md @@ -74,7 +74,7 @@ block. One fill may bind to any number of blocks. | MR preflight, fast checks, commit shape, description shape, labels | `stage-ship` and `ship`: the step blocks below, `domain-rules` | | CI job names, retry rules, triage | `stage-watch-ci` and `watch-ci`: `classify-triage-report`, `domain-rules`, `forge` | | what reviewers check | `review`, `self-review`, `receive-review`: `criteria` | -| which written rules a review is checked against, one verdict per rule | `review`, `self-review`, `receive-review`: `rule-selector` | +| which written rules a review is checked against, one verdict per rule; the check implement runs before self-review | `review`, `self-review`, `receive-review`, `stage-implement`: `rule-selector` | | how to answer review threads | `receive-review`: `reply-rules` | | fan-out rules for parallel agents | `shepherdr`: the root's `rules`, or a step block below | @@ -94,7 +94,7 @@ default. The other props: | `work/sub-agent-tiering` | `tiering` | replace | `model-tiering@1` | nothing | | `review/criteria`, `self-review/criteria`, `receive-review/criteria` | `criteria` | replace | `review-criteria@1` | nothing | | `review/reviewer`, `self-review/reviewer`, `receive-review/reviewer` | `reviewer` | replace | `reviewer-dispatch@1` | nothing | -| `review/rule-selector`, `self-review/rule-selector`, `receive-review/rule-selector` | `rule-selector` | replace | `rule-selector@1` | empty (no rule selection) | +| `review/rule-selector`, `self-review/rule-selector`, `receive-review/rule-selector`, `stage-implement/rule-selector` | `rule-selector` | replace | `rule-selector@1` | empty (no rule selection) | | `receive-review/reply-rules` | `reply-rules` | replace | `reply-rules@1` | nothing | | `shepherdr` | `tiering` | replace | `model-tiering@1` | `mattstack:model-tiering` | | `shepherdr` | `strategy` | replace | `execution-strategy@1` | `mattstack:execution-strategy` | @@ -103,6 +103,8 @@ default. The other props: A `rule-selector` fill resolves to `selector: none`, or three lines: `selector:` (a command with `{format}`), `verdicts:` (a command with `{selection}` and `{verdicts}`) and `fence:` (the info string of the reviewer's verdict block). +It may add an optional fourth line, `check: ` with `{base}`, +which only `stage-implement` runs; still `rule-selector@1`, since it is additive. A `shepherdr` door compiles with nothing bound; bind one of its three replace props only to swap in the pack's own fill. diff --git a/plugins/mattstack/skills/shepherdr/SKILL.md b/plugins/mattstack/skills/shepherdr/SKILL.md index 25f07bcd7f..fe6a6cb9e2 100644 --- a/plugins/mattstack/skills/shepherdr/SKILL.md +++ b/plugins/mattstack/skills/shepherdr/SKILL.md @@ -4,14 +4,14 @@ description: "Use when fanning work out across parallel Claude Code agents in he allowed-tools: - "Bash(*/scripts/pick-account.py:*)" metadata: - compiled: "mattstack@0.32.32 + mattstack:model-tiering@0.32.32 + mattstack:execution-strategy@0.32.32 + mattstack:cswap-accounts@0.32.32" + compiled: "mattstack@0.32.33 + mattstack:model-tiering@0.32.33 + mattstack:execution-strategy@0.32.33 + mattstack:cswap-accounts@0.32.33" --- -Pointers here are `@keys`. Resolve them once per run: if you already hold a `skills_resolve` map that covers `mattstack@0.32.32:shepherdr`, use it; otherwise call `skills_resolve {id: "mattstack@0.32.32:shepherdr"}` and use the path it returns for each key, in anything you hand a subagent or a tool too. If it fails, stop and say so; never look for the file another way. +Pointers here are `@keys`. Resolve them once per run: if you already hold a `skills_resolve` map that covers `mattstack@0.32.33:shepherdr`, use it; otherwise call `skills_resolve {id: "mattstack@0.32.33:shepherdr"}` and use the path it returns for each key, in anything you hand a subagent or a tool too. If it fails, stop and say so; never look for the file another way. - + # shepherdr You are the shepherd: a thin delegator, not a reviewer. You break work into jobs, spawn an agent per job (own herdr pane, own git worktree), present what the daemon pushes, and route small messages between the user and the herd. Model, method and account are policy, answered by the bound skills below; you are transport. @@ -28,15 +28,15 @@ For herdr CLI mechanics, load the `herdr` skill. *If a rule below asks for a move this graph marks STOP, take the off-script edge instead.* - -For tiering, invoke the `mattstack:model-tiering` skill (mattstack:model-tiering@0.32.32) when this flow needs it. + +For tiering, invoke the `mattstack:model-tiering` skill (mattstack:model-tiering@0.32.33) when this flow needs it. - + ## Strategy *If a rule below asks for a move this graph marks STOP, take the off-script edge instead.* - + ### Execution Strategy Given a unit of work and the surface it will execute on, name the method @@ -127,7 +127,7 @@ for you -- name the strategy and pass it, nothing more. Either way, do not compose method prose per job; the bodies carry the worker-boundary rules and the report contract. - + ## Domain rules Domain rules are optional: a pack binds them here or at a step. Bound @@ -141,7 +141,7 @@ names keeps this skill's default as written. *A domain rule sits here or in its step's own section; if one asks for a move this graph marks STOP, take the off-script edge instead.* - + ## Accounts *If a rule below asks for a move this graph marks STOP, take the off-script edge instead.* @@ -153,7 +153,7 @@ question, the per-spawn pick (passed as `account` on `herd_spawn` or `herd_respawn`), and the exhaustion decision tree. Accounts in use are each job's `account` in `herd_status`, never memory. - + ### cswap account pool Given the herd's model mix and the accounts already assigned this run, @@ -222,7 +222,7 @@ cache is a shared symlink), so a worker pane missing a plugin's tools is a real failure: check the pane's plugin list and the shared cache instead of dismissing it. - + ## Start and spawn Walk this graph from the trigger; a move it does not show goes to the shepherd off-script gate. @@ -318,7 +318,7 @@ digraph shepherdr_start { } ``` - + ### Specify the jobs **Domain hook -- intake.** Unbound: the jobs are what the user hands you; @@ -348,7 +348,7 @@ keep it out of the user's checkout, and the delegator rules then do not apply. Apply the bound rules' single-worker exception if they name one. A later job for a live herd is never this case. - + ### Ask the strategy and model per job Strategy predicts the work shape, and the shape predicts the tier: one @@ -375,13 +375,13 @@ the strategy (its method skill is the brief's Method); the recommendation starts at the floor, the pin is a fixed half, and a spawn below the floor is wrong. - + ### Ask the account pool question Ask it once per herd, after the models are chosen: per-model pools hide headroom until the model mix is known. A later job for a live herd keeps the herd's pool answer. The Accounts section says when there is no question to ask. - + ### Collect the repo conventions A herd pane is a real `claude` session in a git worktree: user-level @@ -404,7 +404,7 @@ rules' method skill owns the conventions, `Repo conventions` names only the branch; a brief that restates gates, process or evidence competes with the worker's skill chain. - + ### Fix what the refusal names - **An unfilled marker**: the refusal lists the leftover slots; add a `fill` entry for each. @@ -414,7 +414,7 @@ worker's skill chain. Never copy the template or the strategies file elsewhere to get past a refusal; the retry budget hands a stubborn one to the off-script gate. - + ### Shepherd off-script gate: ask the user Every unresolved refusal and spent budget, in all three graphs, lands @@ -435,13 +435,13 @@ never a count from memory. A same-turn retry count kept in your head, since no daemon budget covers it; it answers yes once two retries have run. - + ### Make the recorded move once Make exactly the move the user took, once, as its value spells it. If it fails too, that is a new off-script gate, never a retry of your own. - + ## What the start graph cannot show **The herd contract.** Every herd is an rt daemon registry row plus one @@ -518,7 +518,7 @@ off-script gate. - **Stagger 4+ spawns**: spawn one, confirm it returned, spawn the next. - Agents never work in the user's checkout; skip isolation only for read-only jobs or when the user says to work in place. The worker's directory must be a **linked worktree**, which `herd_spawn` guarantees. - + ## The watch loop After the spawns, do nothing until something arrives. @@ -687,7 +687,7 @@ digraph shepherdr_watch { } ``` - + ### End the turn until something arrives The daemon pushes gates and room lines; nothing arms, and you never read a @@ -695,7 +695,7 @@ pane or scrollback. Your room posts deliver as `@here` and wake every worker, so a question for one worker is a DM. - + ### Present the open gates Present up to 4 open gates in one AskUserQuestion call. Options are the @@ -719,36 +719,36 @@ with `writeIn`, else ask them to pick. When the user parks a gate, from the job's `herd_status` row: `lastGate`, with `lastGateStatus: parked`; relay it verbatim. - + ### Say which answer won, in one line Name `winner.answers` and `winner.by`; never re-ask. - + ### Fix the answers: each value verbatim Resend each value from `invalid[].validValues`, verbatim. - + ### Pick the account per the accounts rules The Accounts section decides whether to respawn now or ask first; its pick is the `account` on `herd_respawn`. Announce the respawn. - + ### Report the stall to the user An expired login with no smart-distribute pool: only the user's `/login` fixes it. Name the job, its pane and the verdict. - + ### Ask: attend or respawn A dialog the watchdog could not clear parks the job (`stuck-at-modal`). **Attend** opens the pane for the user; **Respawn** reuses the stored brief. - + ### Point the user at the job's tab The job's pane in `herd_status` has no `bg:` prefix: the herd is visible, @@ -757,20 +757,20 @@ which opens tabs only for a hidden herd's panes, would refuse. Tell the user in one message which job, its pane ref and tab, and what waits on them (the diagnosis reason, or the parked dialog). - + ### Report the crash with its job and pane The pane died with the job live. Tell the user both; never respawn silently. - + ### Ask: let them finish, kill and respawn, or hold One sentence naming the running agents, then the bare three options: **Let them finish** (recommended) / **Kill and respawn with the new briefs** / **Hold**. No restated heading, no per-option descriptions. - + ## What the watch loop cannot show - **The push is transport.** A gate push is one line, `[gate] is now open; re-read the gate registry.`, carrying only an id. Its arrival proves the daemon, the subscription and the herd; the `herd_gates` read is the verification, made first. `herd_gates` defaults to `HERD_ID` or the sole active herd and also returns open pipeline-run gates in a herd job's worktree. Room lines arrive the same way (` #: ` a report, ` #: milestone: ` a milestone). A stale id or an unrelated room line is normal, never a reason to reach for another command or distrust the push. @@ -800,7 +800,7 @@ brief unless `brief` is new, and keeps the job's model, effort and account unless passed. The old session's open and parked gates close as abandoned so the new worker re-asks; answered ones are marked consumed. - + ## Lanes and wrap-up ```dot @@ -920,13 +920,13 @@ digraph shepherdr_lanes { } ``` - + ### Flag the drift or collision to the user Files outside the job's write fence are drift; a file two jobs changed is a collision. Flag it now, not at integration. - + ### Relay only what you measured A worker's claims about its environment (servers up, ports free, CI green) @@ -934,7 +934,7 @@ are routinely stale. Measure first (`rt_verb {args: ["endpoint", "lookup", "", "--path", ""]}`, `curl`, `lsof`, a CI status call), or attribute it: "the worker reports X". - + ### Ask the integration job's strategy and model One AskUserQuestion like every job's: the tier table's integration row is @@ -942,14 +942,14 @@ the recommendation, the strategy half from the strategy table. Keep the herd's pool answer and pass the Accounts section's per-spawn pick as `account`. - + ### Gate: merge this lane? Only when bound domain rules give the shepherd the merge. One sentence with the PR or MR link, its state and its CI, then **Merge** / **Not yet** / **Hold**. - + ### Flip the lane's ticket, when it has one Flip it just before `herd_close`, for each lane an integration report @@ -958,7 +958,7 @@ merges. Only you close a done job's pane. The watchdog nags past skipped; for a lane awaiting the integration job or a merge gate it is normal, so end the turn. - + ### Show the status table Reformat `herd_status {herd}`, flagging failures and the latest @@ -970,7 +970,7 @@ Reformat `herd_status {herd}`, flagging failures and the latest | api tests | w3:p1 | 2 | direct-tdd | done | A1-A4 done, 12 tests, suite green | ``` - + ### Gate wrap-up: the form contract One form, per the wrap-up form contract below: @@ -984,7 +984,7 @@ One form, per the wrap-up form contract below: Never auto-remove a tree or job dir; `herd_wrap_up` executes exactly the form's answer. - + ### Measure what still runs The stop calls cover every job in the dispose list or with a closing pane: @@ -1005,13 +1005,13 @@ Measure before you say a job's servers stopped (`lsof -ti tcp:` or the endpoint lookup). Report a survivor as still running, never kill it by hand, and anything unmeasured as "not verified". - + ### Report the refusal in the guard's words Quote the disposal refusal as the guard gave it. In hidden mode, also offer the Bash command `rt herd stop --hidden` (no tool runs it); never run it unprompted. - + ## What the lanes graph cannot show - **The two objective checks.** `herd_lanes {herd}` returns a row per active job (`commits`; `drift.files`, committed and uncommitted work since the merge base; `collidesWith`) plus herd-wide `collisions`. Compare each job's `drift.files` against its write fence yourself; no fence is stored. A job whose tree is gone has no row (it is in `skipped` with a reason), and the drift check skips it. @@ -1021,12 +1021,12 @@ the Bash command `rt herd stop --hidden` (no tool runs it); never run it unpromp - **Domain hook: after the report.** Unbound: the integration job merges. Bound domain rules may define what follows a report (the worker ships through its own skill chain, the shepherd merges after a gate, or a human merges on the forge and the lane closes when `herd_lanes` reads `merged`; how jobs feeding one deliverable integrate) and whether it waits for the user to ask. Fixing and pushing stay with workers. - **Domain hook: wrap-up.** Unbound: as drawn. Bound domain rules may state their own tree lifecycle (trees that dispose themselves on merge, what a disposal refusal means); follow it over the defaults here. - + ## wrap-up form contract *If a rule below asks for a move this graph marks STOP, take the off-script edge instead.* - + ### Wrap-up The reply is one optional sentence of context, then a form, then stop. Wait @@ -1055,7 +1055,7 @@ next call after the answers return, never into the context sentence. | "I can hand them a default to save them answering" | Recommended options already do that in the form; a typed reply still costs more than a tap and leaves no record of the pick. | | "The context sentence, then a line introducing the form" | Two sentences. The form needs no introduction; the sentence is the whole preamble. | - + ## Rationalizations | Thought | Reality | diff --git a/plugins/mattstack/skills/shepherdr/compiled.json b/plugins/mattstack/skills/shepherdr/compiled.json index c01ffcd4e0..e7fabad9bf 100644 --- a/plugins/mattstack/skills/shepherdr/compiled.json +++ b/plugins/mattstack/skills/shepherdr/compiled.json @@ -1,8 +1,8 @@ { - "id": "mattstack@0.32.32:shepherdr", + "id": "mattstack@0.32.33:shepherdr", "dir": "skills/shepherdr", "covers": [ - "mattstack@0.32.32:shepherdr" + "mattstack@0.32.33:shepherdr" ], "keys": { "shepherdr/SKILL.md": "skills/shepherdr/SKILL.md", diff --git a/plugins/mattstack/skills/wrap-up/SKILL.md b/plugins/mattstack/skills/wrap-up/SKILL.md index bc6b638e82..1ca0dda6c0 100644 --- a/plugins/mattstack/skills/wrap-up/SKILL.md +++ b/plugins/mattstack/skills/wrap-up/SKILL.md @@ -2,12 +2,12 @@ name: "wrap-up" description: "Use when wrapping up a session, checking in before continuing, ending a turn with open decisions, when the user asks what you need from them, what decisions are open, or what the next steps are, when they invoke wrap-up or check-in, or when a pipeline gate needs its decision presented as a form." metadata: - compiled: "mattstack@0.32.32" + compiled: "mattstack@0.32.33" --- - + # Wrap-up The reply is one optional sentence of context, then a form, then stop. Wait diff --git a/plugins/mattstack/skills/wrap-up/compiled.json b/plugins/mattstack/skills/wrap-up/compiled.json index 3a7782891a..bd0645a34c 100644 --- a/plugins/mattstack/skills/wrap-up/compiled.json +++ b/plugins/mattstack/skills/wrap-up/compiled.json @@ -1,8 +1,8 @@ { - "id": "mattstack@0.32.32:wrap-up", + "id": "mattstack@0.32.33:wrap-up", "dir": "skills/wrap-up", "covers": [ - "mattstack@0.32.32:wrap-up" + "mattstack@0.32.33:wrap-up" ], "keys": {} } diff --git a/plugins/mattstack/skills/writing-style-conversational/SKILL.md b/plugins/mattstack/skills/writing-style-conversational/SKILL.md index 5c6868f0e4..923b22b838 100644 --- a/plugins/mattstack/skills/writing-style-conversational/SKILL.md +++ b/plugins/mattstack/skills/writing-style-conversational/SKILL.md @@ -2,27 +2,27 @@ name: "writing-style-conversational" description: "Use only when the mattstack writing-style lookup names mattstack:writing-style-conversational, or falls back to it because the lookup failed or the skill it named will not load. The voice for review comments, replies, PR descriptions and commit messages posted under the operator's name: short, friendly sentences in sentence case, like talking to a teammate." metadata: - compiled: "mattstack@0.32.32" + compiled: "mattstack@0.32.33" --- -Pointers here are `@keys`. Resolve them once per run: if you already hold a `skills_resolve` map that covers `mattstack@0.32.32:writing-style-conversational`, use it; otherwise call `skills_resolve {id: "mattstack@0.32.32:writing-style-conversational"}` and use the path it returns for each key, in anything you hand a subagent or a tool too. If it fails, stop and say so; never look for the file another way. +Pointers here are `@keys`. Resolve them once per run: if you already hold a `skills_resolve` map that covers `mattstack@0.32.33:writing-style-conversational`, use it; otherwise call `skills_resolve {id: "mattstack@0.32.33:writing-style-conversational"}` and use the path it returns for each key, in anything you hand a subagent or a tool too. If it fails, stop and say so; never look for the file another way. - + # Conversational Short, friendly, and plain, like talking to a teammate across a desk. Load this before the first draft and write in it from the first word. - + ## Rules every style shares These bind anything posted under the operator's name: review findings, the review summary, replies on their own PR, PR descriptions, commit messages. The preset sets the voice; these set the floor. - + ### Never - **Em or en dashes.** Rephrase, or use parentheses, a colon, or an ellipsis @@ -40,7 +40,7 @@ The preset sets the voice; these set the floor. noticed:", "Took a look", "I just wanted to". Start with the claim. - **Invented precision.** "most" beats "~96%" unless the number was measured. - + ### Always - **Compress before posting.** The first draft is a rough cut. Before posting @@ -73,7 +73,7 @@ The preset sets the voice; these set the floor. notes. It never recaps a finding, lists strengths, or reports what you ran. A finding with no line to anchor to is its own top-level comment. - + ## Voice - **Sentence case** everywhere, with contractions. @@ -88,7 +88,7 @@ The preset sets the voice; these set the floor. Compression target: would this read naturally if you said it out loud to the author? Cut anything you would not say. - + ## Review findings Open with a bolded Conventional Comments label, never a code span @@ -110,13 +110,13 @@ const pageCount = Math.ceil(total / pageSize); A process ask is one line that leads with the approval if there is one: `**suggestion:** Looks good! Could you add a test for the empty-list case?` - + ## Review summary One line: `Looks good to me, no blockers. Left a few comments inline.` Only a blocker joins it. - + ## Replies on your own PR One to three short sentences. No sign-offs; thank someone only if they @@ -127,12 +127,12 @@ matters. - `Good call, switched to Record.` - `I'd skip the lock for now. The job runs on one worker, and a lock adds a round trip per write.` - + ## PR descriptions Read `@writing-style-conversational/pr-description.md` before drafting. - + ## Commit messages - Subject: imperative, sentence case, under 72 characters, with the ticket diff --git a/plugins/mattstack/skills/writing-style-conversational/compiled.json b/plugins/mattstack/skills/writing-style-conversational/compiled.json index 1387d77b49..7bf853f63e 100644 --- a/plugins/mattstack/skills/writing-style-conversational/compiled.json +++ b/plugins/mattstack/skills/writing-style-conversational/compiled.json @@ -1,8 +1,8 @@ { - "id": "mattstack@0.32.32:writing-style-conversational", + "id": "mattstack@0.32.33:writing-style-conversational", "dir": "skills/writing-style-conversational", "covers": [ - "mattstack@0.32.32:writing-style-conversational" + "mattstack@0.32.33:writing-style-conversational" ], "keys": { "writing-style-conversational/pr-description.md": "skills/writing-style-conversational/pr-description.md" diff --git a/plugins/mattstack/skills/writing-style-sparse/SKILL.md b/plugins/mattstack/skills/writing-style-sparse/SKILL.md index 44f52bd5df..e966c83dd7 100644 --- a/plugins/mattstack/skills/writing-style-sparse/SKILL.md +++ b/plugins/mattstack/skills/writing-style-sparse/SKILL.md @@ -2,28 +2,28 @@ name: "writing-style-sparse" description: "Use only when the mattstack writing-style lookup names mattstack:writing-style-sparse. The voice for review comments, replies, PR descriptions and commit messages posted under the operator's name: terse, lowercase technical prose, one tight paragraph per point." metadata: - compiled: "mattstack@0.32.32" + compiled: "mattstack@0.32.33" --- -Pointers here are `@keys`. Resolve them once per run: if you already hold a `skills_resolve` map that covers `mattstack@0.32.32:writing-style-sparse`, use it; otherwise call `skills_resolve {id: "mattstack@0.32.32:writing-style-sparse"}` and use the path it returns for each key, in anything you hand a subagent or a tool too. If it fails, stop and say so; never look for the file another way. +Pointers here are `@keys`. Resolve them once per run: if you already hold a `skills_resolve` map that covers `mattstack@0.32.33:writing-style-sparse`, use it; otherwise call `skills_resolve {id: "mattstack@0.32.33:writing-style-sparse"}` and use the path it returns for each key, in anything you hand a subagent or a tool too. If it fails, stop and say so; never look for the file another way. - + # Sparse Terse and conversational. The reader is a colleague who can follow a chain from one sentence. Load this before the first draft and write in it from the first word; it is not a pass applied to a finished draft. - + ## Rules every style shares These bind anything posted under the operator's name: review findings, the review summary, replies on their own PR, PR descriptions, commit messages. The preset sets the voice; these set the floor. - + ### Never - **Em or en dashes.** Rephrase, or use parentheses, a colon, or an ellipsis @@ -41,7 +41,7 @@ The preset sets the voice; these set the floor. noticed:", "Took a look", "I just wanted to". Start with the claim. - **Invented precision.** "most" beats "~96%" unless the number was measured. - + ### Always - **Compress before posting.** The first draft is a rough cut. Before posting @@ -74,7 +74,7 @@ The preset sets the voice; these set the floor. notes. It never recaps a finding, lists strengths, or reports what you ran. A finding with no line to anchor to is its own top-level comment. - + ## Voice - **Lowercase for technical content**: findings, assertions, code talk. @@ -93,7 +93,7 @@ Compression target: shorter and more conversational. Ask "would a busy teammate send this as written, or trim it first?" If there is any doubt, trim. - + ## Review findings Open with a bolded Conventional Comments label, never a code span: @@ -140,14 +140,14 @@ A brief, specific credit is fine folded in after the worry, never as the opener: `**thought (non-blocking):** only worry here: nothing tests the fallback. the version's pinned now, nice catch.`. A bare compliment is not. - + ## Review summary One line, sentence case: `Looks solid to me, no blockers. Left a few inline notes.` or `Left a few comments!` Only a blocker may join it: `Left one blocker inline.` - + ## Replies on your own PR Very short. No sign-offs, no thanks unless they thanked first. Soften even @@ -162,13 +162,13 @@ blank-line paragraphs. - `i don't think we need a lock here. the job runs on one worker, and a lock adds a round trip per write.` - `👍` - + ## PR descriptions Read `@writing-style-sparse/pr-description.md` before drafting. It carries the length target, the structure, and the anti-patterns. - + ## Commit messages - Subject: lowercase, imperative, under 72 characters, prefixed with the diff --git a/plugins/mattstack/skills/writing-style-sparse/compiled.json b/plugins/mattstack/skills/writing-style-sparse/compiled.json index 5ea3305353..f9aea9b342 100644 --- a/plugins/mattstack/skills/writing-style-sparse/compiled.json +++ b/plugins/mattstack/skills/writing-style-sparse/compiled.json @@ -1,8 +1,8 @@ { - "id": "mattstack@0.32.32:writing-style-sparse", + "id": "mattstack@0.32.33:writing-style-sparse", "dir": "skills/writing-style-sparse", "covers": [ - "mattstack@0.32.32:writing-style-sparse" + "mattstack@0.32.33:writing-style-sparse" ], "keys": { "writing-style-sparse/pr-description.md": "skills/writing-style-sparse/pr-description.md" diff --git a/plugins/mattstack/skills/writing-style-structured/SKILL.md b/plugins/mattstack/skills/writing-style-structured/SKILL.md index e183e0ee19..0caa52d99d 100644 --- a/plugins/mattstack/skills/writing-style-structured/SKILL.md +++ b/plugins/mattstack/skills/writing-style-structured/SKILL.md @@ -2,28 +2,28 @@ name: "writing-style-structured" description: "Use only when the mattstack writing-style lookup names mattstack:writing-style-structured. The voice for review comments, replies, PR descriptions and commit messages posted under the operator's name: labelled lines and short bullets for teams that prefer formal write-ups." metadata: - compiled: "mattstack@0.32.32" + compiled: "mattstack@0.32.33" --- -Pointers here are `@keys`. Resolve them once per run: if you already hold a `skills_resolve` map that covers `mattstack@0.32.32:writing-style-structured`, use it; otherwise call `skills_resolve {id: "mattstack@0.32.32:writing-style-structured"}` and use the path it returns for each key, in anything you hand a subagent or a tool too. If it fails, stop and say so; never look for the file another way. +Pointers here are `@keys`. Resolve them once per run: if you already hold a `skills_resolve` map that covers `mattstack@0.32.33:writing-style-structured`, use it; otherwise call `skills_resolve {id: "mattstack@0.32.33:writing-style-structured"}` and use the path it returns for each key, in anything you hand a subagent or a tool too. If it fails, stop and say so; never look for the file another way. - + # Structured Clear and scannable, for teams that like formal write-ups. Every point is labelled so a reader can triage without reading the whole thing. Load this before the first draft and write in it from the first word. - + ## Rules every style shares These bind anything posted under the operator's name: review findings, the review summary, replies on their own PR, PR descriptions, commit messages. The preset sets the voice; these set the floor. - + ### Never - **Em or en dashes.** Rephrase, or use parentheses, a colon, or an ellipsis @@ -41,7 +41,7 @@ The preset sets the voice; these set the floor. noticed:", "Took a look", "I just wanted to". Start with the claim. - **Invented precision.** "most" beats "~96%" unless the number was measured. - + ### Always - **Compress before posting.** The first draft is a rough cut. Before posting @@ -74,7 +74,7 @@ The preset sets the voice; these set the floor. notes. It never recaps a finding, lists strengths, or reports what you ran. A finding with no line to anchor to is its own top-level comment. - + ## Voice - **Sentence case**, complete but short sentences. @@ -87,7 +87,7 @@ The preset sets the voice; these set the floor. Compression target: every line earns its label. Cut any line a reader could skip without missing the ask. - + ## Review findings Open with a bolded Conventional Comments label, never a code span, then @@ -107,13 +107,13 @@ const pageCount = Math.ceil(total / pageSize); Use only the lines that carry something: a `nitpick` is the label and one line. A process ask is one line. - + ## Review summary One line: `No blockers. Inline notes on paging and tests.` Only a blocker joins it: `One blocker inline (page count).` - + ## Replies on your own PR One or two sentences, plain and direct. No thanks opener. State the outcome @@ -122,12 +122,12 @@ first. - `Changed to Record.` - `Skipping the lock: the job runs on one worker, and a lock adds a round trip per write.` - + ## PR descriptions Read `@writing-style-structured/pr-description.md` before drafting. - + ## Commit messages - Subject: imperative, sentence case, under 72 characters, with the ticket diff --git a/plugins/mattstack/skills/writing-style-structured/compiled.json b/plugins/mattstack/skills/writing-style-structured/compiled.json index 3c521b41b2..3eb3da344a 100644 --- a/plugins/mattstack/skills/writing-style-structured/compiled.json +++ b/plugins/mattstack/skills/writing-style-structured/compiled.json @@ -1,8 +1,8 @@ { - "id": "mattstack@0.32.32:writing-style-structured", + "id": "mattstack@0.32.33:writing-style-structured", "dir": "skills/writing-style-structured", "covers": [ - "mattstack@0.32.32:writing-style-structured" + "mattstack@0.32.33:writing-style-structured" ], "keys": { "writing-style-structured/pr-description.md": "skills/writing-style-structured/pr-description.md" From abb5d97cda77a1bb0179be2e59329688705633ea Mon Sep 17 00:00:00 2001 From: Matthew Goodwin Date: Thu, 29 Oct 2026 14:52:22 -0500 Subject: [PATCH 2/2] rule-selector check: review fixes Co-Authored-By: Claude Opus 5.5 --- plugins/mattstack/CERTIFICATION.md | 1 - .../pipeline/stage-implement/fix-rule-breaks.block.md | 6 +++--- .../pipeline/stage-implement/rule-selector.block.md | 6 ++++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/mattstack/CERTIFICATION.md b/plugins/mattstack/CERTIFICATION.md index 919bcbe9b5..e44c37d6cd 100644 --- a/plugins/mattstack/CERTIFICATION.md +++ b/plugins/mattstack/CERTIFICATION.md @@ -245,6 +245,5 @@ that quotes a shell form carries ``. | 2026-10-23 | mattstack:work-progress | pure | pass | 5/5 scenarios, 5/5 haiku reps each (2 herd kept, epic and 2 plan added); full roster 22/23, the one miss (billing creating-a-pack) also misses on the old description | replaces the herd-only progress skill: the bundled script renders a herd (`--herd`) or plans and epics (`--plan` repeatable, `--title`, `--tree`) from rt's plan records; the skill picks the input, prints the view verbatim and never calls a plan done unless its row says so; RED: no skill loaded on plan or epic prompts, 6 hand calls and a hand-built table, plan 1 guessed done from plan 2 having started; GREEN (claude -p --plugin-dir, opus, sandboxed rt): epic prompt Skill + ls + 1 script call, plan "where is my plan at" and "how far along is it" Skill + 1 script call each, all printed verbatim, "herd progress" triggers it; the brief's description first missed "where is my plan at", fixed in 15e6d8f by adding that phrase; certify exit 0 | | 2026-10-23 | mattstack:shepherdr | pure | pass | n/a (description unchanged) | Progress pointer renamed to mattstack:work-progress, recompiled into skills/shepherdr; certify exit 0 | | 2026-10-28 | mattstack:review, mattstack:self-review, mattstack:receive-review | pure | pass | 4/4 GREEN walkthrough scenarios | optional `rule-selector` prop (`rule-selector@1`, replace, default empty): the engines select written rules before dispatch, ask the reviewer for one verdict per rule and turn failed rules into findings; unbound it is a no-op; GREEN describe-only walk of the compiled review: unbound skips every rule step, a selector exiting 1 is one setup observation and no rule steps, a reply with no verdict block gets exactly one repair then an `Unanswered rules:` line in the Assessment, an empty selection sends no verdict request and saves an empty verdict file; certify exit 0 on every touched root | -| 2026-10-29 | mattstack:stage-implement, mattstack:review-rules-select-body | pure | pass | 4/4 GREEN walkthrough scenarios | `rule-selector@1` takes an optional fourth line `check: ` with `{base}`; stage-implement gains a `rule-selector` prop and a Check the work against the team's rules step between committing and recording the commits: runs the check once from the branch point, fixes the rules the change breaks, never reruns and never fails the stage; the review verbs read a fill with a `check:` line as the three-line shape; RED: implement never ran the check and self-review read the four-line fill as neither shape; GREEN: check runs once, a non-zero exit is noted for the return, `selector: none` skips, self-review runs the selector and ignores `check:`; certify exit 0 on every touched root | (Ledger rows are appended by the orchestrating session as each lane lands -- lanes themselves never edit this file.) diff --git a/plugins/mattstack/attachments/pipeline/stage-implement/fix-rule-breaks.block.md b/plugins/mattstack/attachments/pipeline/stage-implement/fix-rule-breaks.block.md index 87cbe3acef..d9bad32e10 100644 --- a/plugins/mattstack/attachments/pipeline/stage-implement/fix-rule-breaks.block.md +++ b/plugins/mattstack/attachments/pipeline/stage-implement/fix-rule-breaks.block.md @@ -7,6 +7,6 @@ node: fix_rule_breaks Fix each rule the check showed the change breaking. A fix that changes behavior is test-first, as in the rest of this stage. Commit the fixes on the branch, inside the worktree. A rule you judge does not apply, or a -fix you cannot make without leaving the plan, stays as it is: name it in -one line when the stage returns to the orchestrator, and self-review will -judge it. Then record the commits; there is no second check. +fix you cannot make without leaving the plan, stays as it is: name the +rule and the line in one line when the stage returns to the orchestrator. +Then record the commits; there is no second check. diff --git a/plugins/mattstack/attachments/pipeline/stage-implement/rule-selector.block.md b/plugins/mattstack/attachments/pipeline/stage-implement/rule-selector.block.md index 1f4c090c8b..4e51b4d122 100644 --- a/plugins/mattstack/attachments/pipeline/stage-implement/rule-selector.block.md +++ b/plugins/mattstack/attachments/pipeline/stage-implement/rule-selector.block.md @@ -35,8 +35,10 @@ quote the exit status and the first line of stderr in one line when the stage returns to the orchestrator, fix nothing on its account, and record the commits. -Run it once. The check never runs again in this stage: self-review judges -the result with verdicts. +Run it once. The check never runs again in this stage. Rule verdicts come +later only where the pack's self-review runs the self-review verb with +this same selector bound; the default self-review stage reads the diff +without the rules. *If a rule below asks for a move this skill's graph marks STOP, take the off-script edge instead.*