@ericrisco/rsc 3.7.5 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +16 -9
  2. package/manifest.json +48 -40
  3. package/package.json +1 -1
  4. package/scripts/lib/domains.js +1 -1
  5. package/scripts/lib/onboarding-apply.js +1 -1
  6. package/scripts/lib/onboarding.js +7 -7
  7. package/scripts/lib/retired-skills.js +4 -0
  8. package/scripts/lib/review-findings.js +3 -0
  9. package/scripts/lib/review-ledger.js +187 -0
  10. package/scripts/lib/risk.js +68 -0
  11. package/scripts/lib/spec-gate.js +10 -1
  12. package/scripts/rsc.js +104 -6
  13. package/scripts/spec-gate.js +13 -3
  14. package/skills/analyze/SKILL.md +3 -3
  15. package/skills/asdd/SKILL.md +183 -0
  16. package/skills/{sdd → asdd}/evals/README.md +10 -10
  17. package/skills/{sdd → asdd}/evals/cases.yaml +5 -5
  18. package/skills/{sdd → asdd}/references/model-routing.md +2 -2
  19. package/skills/building-agents/SKILL.md +1 -1
  20. package/skills/clarify/SKILL.md +59 -55
  21. package/skills/clarify/evals/cases.yaml +6 -7
  22. package/skills/constitution/SKILL.md +30 -16
  23. package/skills/constitution/evals/README.md +1 -1
  24. package/skills/constitution/evals/cases.yaml +1 -1
  25. package/skills/debug/SKILL.md +3 -3
  26. package/skills/fastapi/SKILL.md +1 -1
  27. package/skills/flutter/SKILL.md +1 -1
  28. package/skills/ftd/SKILL.md +26 -10
  29. package/skills/ftd/evals/cases.yaml +1 -1
  30. package/skills/ftd/evals/routing.json +4 -4
  31. package/skills/go/SKILL.md +1 -1
  32. package/skills/implement/SKILL.md +147 -132
  33. package/skills/implement/evals/cases.yaml +4 -2
  34. package/skills/implement/references/per-task-review.md +26 -24
  35. package/skills/implement/scripts/task-brief +28 -22
  36. package/skills/nextjs/SKILL.md +1 -1
  37. package/skills/parallel/SKILL.md +1 -1
  38. package/skills/plan/SKILL.md +3 -3
  39. package/skills/plan/evals/cases.yaml +1 -1
  40. package/skills/plan/references/plan-template.md +2 -2
  41. package/skills/postgresdb/SKILL.md +1 -1
  42. package/skills/react/SKILL.md +1 -1
  43. package/skills/review/SKILL.md +80 -41
  44. package/skills/sdd-init/SKILL.md +11 -9
  45. package/skills/ship/SKILL.md +58 -30
  46. package/skills/specify/SKILL.md +42 -41
  47. package/skills/specify/evals/cases.yaml +2 -2
  48. package/skills/specify/references/eliciting-requirements.md +1 -1
  49. package/skills/specify/references/spec-template.md +28 -6
  50. package/skills/suggest/SKILL.md +4 -4
  51. package/skills/tasks/SKILL.md +37 -33
  52. package/skills/tasks/evals/README.md +1 -1
  53. package/skills/tasks/evals/cases.yaml +3 -3
  54. package/skills/verify/SKILL.md +34 -18
  55. package/skills/verify/evals/README.md +2 -2
  56. package/skills/verify/evals/cases.yaml +2 -1
  57. package/skills/verify/references/generic-gate.md +43 -0
  58. package/skills/worktrees/SKILL.md +1 -1
  59. package/targets/clone-bootstrap.mjs +1 -1
  60. package/targets/hook-once.mjs +2 -2
  61. package/targets/sello.mjs +9 -3
  62. package/skills/sdd/SKILL.md +0 -250
@@ -1,10 +1,12 @@
1
1
  #!/bin/sh
2
2
  # task-brief PLAN_FILE N
3
- # Extracts task number N from a plan/tasks markdown file into a brief file in the
4
- # sdd-workspace dir, and prints its path.
3
+ # Extracts task N from a plan/tasks markdown file into a brief file in the sdd-workspace dir, and
4
+ # prints its path. N may be written 2, 002 or T002: ids compare as numbers.
5
5
  #
6
- # A task starts at a heading matching: ### T<N> / ### T<N>( / ### T<N> — / ## T<N>
7
- # and ends just before the next ### T / ## heading.
6
+ # Two task shapes are read, because `tasks` prescribes a table and older plans used headings:
7
+ # - a table row `| T002 | ... |` → the table's header, its separator and that row;
8
+ # - a heading `### T2 …` / `## T002 …` → the block up to the next ## / ### heading.
9
+ # Both are printed when both exist (the row is the contract, the heading its detail).
8
10
  # If task N is not found, exits nonzero with a clear message.
9
11
 
10
12
  # Resolve the directory of this script portably.
@@ -13,7 +15,7 @@ _SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
15
  usage() {
14
16
  printf 'Usage: task-brief <PLAN_FILE> <N>\n' >&2
15
17
  printf ' PLAN_FILE path to the tasks markdown file\n' >&2
16
- printf ' N task number to extract (e.g. 2 for T2)\n' >&2
18
+ printf ' N task id to extract: 2, 002 or T002\n' >&2
17
19
  exit 1
18
20
  }
19
21
 
@@ -23,41 +25,44 @@ if [ $# -lt 2 ]; then
23
25
  fi
24
26
 
25
27
  PLAN_FILE="$1"
26
- N="$2"
28
+ N="${2#T}"
29
+ N="${N#t}"
27
30
 
28
31
  if [ ! -f "$PLAN_FILE" ]; then
29
32
  printf 'error: plan file not found: %s\n' "$PLAN_FILE" >&2
30
33
  exit 1
31
34
  fi
32
35
 
33
- # Validate N is a positive integer.
34
36
  case "$N" in
35
37
  ''|*[!0-9]*)
36
- printf 'error: N must be a positive integer, got: %s\n' "$N" >&2
38
+ printf 'error: N must be a task number (2, 002 or T002), got: %s\n' "$2" >&2
37
39
  exit 1
38
40
  ;;
39
41
  esac
40
42
 
41
- # Use awk to extract the task block.
42
- # Match start: a heading line (## or ###) followed by T<N> then space, (, —, or end of line.
43
- # Match end: the next heading line starting with ## (which includes ###).
44
- # Exit code: 0 if found, 1 if not found.
45
43
  CONTENT="$(awk -v n="$N" '
46
- BEGIN { printing=0; found=0 }
44
+ function idnum(s) { sub(/^[Tt]/, "", s); return s + 0 }
45
+ BEGIN { n = n + 0; printing = 0; found = 0; head = ""; sep = "" }
46
+ # Table rows: remember the latest header + separator, print the matching row.
47
+ /^\|/ {
48
+ split($0, cell, "|"); id = cell[2]; gsub(/[[:space:]]/, "", id)
49
+ if (id ~ /^[Tt][0-9]+$/) {
50
+ if (idnum(id) == n) { if (!printing) { if (head != "") print head; if (sep != "") print sep }; print; found = 1; next }
51
+ } else if ($0 ~ /^\|[-:| ]+\|[[:space:]]*$/) { sep = $0 }
52
+ else { head = $0; sep = "" }
53
+ if (!printing) next
54
+ }
47
55
  /^#{2,3}[[:space:]]/ {
48
- # Check if this heading is the target task T<n>
49
- line=$0
50
- pat="^#{2,3}[[:space:]]+T" n "([[:space:](]|[-]|$)"
51
- if (match(line, pat)) {
52
- printing=1; found=1; print; next
56
+ if (printing) { printing = 0 }
57
+ if (match($0, /^#{2,3}[[:space:]]+[Tt][0-9]+([[:space:](]|-|$)/)) {
58
+ t = $0; sub(/^#{2,3}[[:space:]]+/, "", t); sub(/[^Tt0-9].*$/, "", t)
59
+ if (idnum(t) == n) { printing = 1; found = 1; print ""; print; next }
53
60
  }
54
- # Any other ## / ### heading stops printing
55
- if (printing) { exit }
56
61
  }
57
62
  { if (printing) print }
58
63
  END { if (!found) exit 1 }
59
64
  ' "$PLAN_FILE")" || {
60
- printf 'error: task T%s not found in %s\n' "$N" "$PLAN_FILE" >&2
65
+ printf 'error: task T%s not found in %s (looked for a "| T%s |" table row and a "### T%s" heading)\n' "$N" "$PLAN_FILE" "$N" "$N" >&2
61
66
  exit 1
62
67
  }
63
68
 
@@ -71,7 +76,8 @@ SCRATCH="$(sh "$_SCRIPT_DIR/sdd-workspace")"
71
76
 
72
77
  # Derive a readable unique name from the plan file basename and task number.
73
78
  PLAN_BASENAME="$(basename "$PLAN_FILE" .md)"
74
- OUT="$SCRATCH/brief-${PLAN_BASENAME}-T${N}.txt"
79
+ NUM="$(printf %s "$N" | sed "s/^0*//")"
80
+ OUT="$SCRATCH/brief-${PLAN_BASENAME}-T${NUM:-0}.txt"
75
81
 
76
82
  printf '%s\n' "$CONTENT" > "$OUT"
77
83
  printf '%s\n' "$OUT"
@@ -14,7 +14,7 @@ origin: risco
14
14
  > behaviour change** and there is **no approved spec + plan** under `02-DOCS/wiki/sdd/`, STOP and
15
15
  > hand off to `../specify/SKILL.md` (brainstorm → spec → plan → tasks); it routes back here once the
16
16
  > plan is approved. Build directly only for a genuinely one-line / low-risk change. Method:
17
- > `../sdd/SKILL.md`.
17
+ > `../asdd/SKILL.md`.
18
18
 
19
19
  **Not this skill:** Pages Router (`pages/`) — note the difference, defer to the Next.js Pages docs.
20
20
  A pure React SPA (Vite/CRA) → `../react/SKILL.md`; React Native / Expo → `../react-native/SKILL.md`;
@@ -73,7 +73,7 @@ SUBAGENT BRIEF (one per unit)
73
73
 
74
74
  Each subagent still owns its own discipline inside its scope — TDD via `implement`, the stack skill's test mechanics, decision logging. This skill does not relax any of that; it just runs several of them at once.
75
75
 
76
- **Per-unit model tier (when routing is enabled).** `parallel` has *no fixed tier* — this is the most concrete place per-phase model routing pays off. When `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`, give each unit the tier of the *kind of work it does*, not one tier for the whole fan-out: an implement-type unit → `balanced`, a scan/research/boilerplate unit → `light`, a unit doing genuine design or root-cause reasoning → `heavy`. Resolve the tier to a concrete model via `models.tiers` and **dispatch that subagent on that model** (e.g. Claude Code's `model` field on the Task/subagent) — real routing, independent of the session model. If routing is off or no profile exists, dispatch on the session model and say nothing. Full protocol: `../sdd/references/model-routing.md`.
76
+ **Per-unit model tier (when routing is enabled).** `parallel` has *no fixed tier* — this is the most concrete place per-phase model routing pays off. When `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`, give each unit the tier of the *kind of work it does*, not one tier for the whole fan-out: an implement-type unit → `balanced`, a scan/research/boilerplate unit → `light`, a unit doing genuine design or root-cause reasoning → `heavy`. Resolve the tier to a concrete model via `models.tiers` and **dispatch that subagent on that model** (e.g. Claude Code's `model` field on the Task/subagent) — real routing, independent of the session model. If routing is off or no profile exists, dispatch on the session model and say nothing. Full protocol: `../asdd/references/model-routing.md`.
77
77
 
78
78
  **Set the model on every dispatch — an omitted model silently inherits the session's, usually the most expensive one, and that is how a fan-out's cost quietly explodes.** rsc installs a `developer` subagent pinned to the **balanced** tier (Sonnet by default; the user's onboarding choice in `.rsc/developer.json`, never `light`). For implement-type units, **dispatch to the `developer` agent** (e.g. Claude Code `subagent_type: developer`) — that's the deliberate cost cap the user asked for. Only escalate a genuinely heavy unit (real design / root-cause) to a heavy model, and only when routing is enabled; otherwise balanced is the floor and the ceiling. If you dispatch a raw subagent instead, name its tier explicitly.
79
79
 
@@ -85,7 +85,7 @@ Three of them carry the weight and fail quietly:
85
85
 
86
86
  Write `02-DOCS/wiki/sdd/plans/<slug>.md`, `<slug>` matching the spec's slug exactly — one plan per
87
87
  spec, same name, because that is how `tasks`, `analyze` and `implement` find it. Then index it in
88
- `02-DOCS/wiki/index.md` (the Knowledge map; root `CLAUDE.md` keeps only a pointer). If a plan for
88
+ `02-DOCS/wiki/index.md` (the Knowledge map) — never the root `CLAUDE.md`. If a plan for
89
89
  this slug already exists, update it in place and note what changed — never fork a `-v2`.
90
90
 
91
91
  ## Model tier — `heavy` (opt-in routing)
@@ -93,7 +93,7 @@ this slug already exists, update it in place and note what changed — never for
93
93
  Architecture, interfaces, data flow and risk are the heaviest reasoning in the chain, so this
94
94
  phase's default tier is **`heavy`**. Routing is off unless `models.enabled: true` in
95
95
  `02-DOCS/wiki/sdd/config.yaml`; the resolution order, the announce rule and the model table live in
96
- `../sdd/references/model-routing.md`. Routing off or no profile → session model, silently.
96
+ `../asdd/references/model-routing.md`. Routing off or no profile → session model, silently.
97
97
 
98
98
  ## Talking about the plan
99
99
 
@@ -140,7 +140,7 @@ regardless. (`implement` re-checks this as a hard gate before its first commit.)
140
140
  ## Result envelope
141
141
 
142
142
  End with the parseable block every SDD phase shares, so the dispatcher can chain without
143
- interpreting prose (contract: `../sdd/SKILL.md`):
143
+ interpreting prose (contract: `../asdd/SKILL.md`):
144
144
 
145
145
  ```json result-envelope
146
146
  {
@@ -46,4 +46,4 @@ capability:
46
46
  - "Testing strategy maps each acceptance criterion to a test level (unit/contract/integration/e2e), states what each asserts and what it mocks (e.g. the payments provider), and defers the test runner/tooling to the stack skill"
47
47
  - "Risks section is ranked with trigger + impact + mitigation per risk (e.g. payments timeout, double-reservation race); does NOT claim zero risk"
48
48
  - "Stays stack-neutral above the seam — no framework route decorators, ORM session code, or test-runner flags in the plan"
49
- - "Ends by pointing to the tasks phase and notes indexing the plan in the root CLAUDE.md Knowledge map"
49
+ - "Ends by pointing to the tasks phase and notes indexing the plan in 02-DOCS/wiki/index.md (the Knowledge map), never the root CLAUDE.md"
@@ -145,8 +145,8 @@ unit / contract / integration / e2e. Tooling is the stack skill's job — name t
145
145
  defend it, not to enumerate every option without choosing.
146
146
  - **Empty section = defect.** Every one of the seven must say something real before `tasks` runs.
147
147
  If a section genuinely has no content (e.g. no migration), write "none — <why>", not silence.
148
- - **Index it.** After writing, add the plan to the root `CLAUDE.md` `## Knowledge map` so the
149
- harness wiki and later phases can find it.
148
+ - **Index it.** After writing, add the plan to `02-DOCS/wiki/index.md` (the Knowledge map) so the
149
+ harness wiki and later phases can find it. Never edit the root `CLAUDE.md` — that is `harness`'s, and tier 2.
150
150
  - **Global Constraints carry the verbatim values.** §0 is not a summary of §1 — §1 explains *which*
151
151
  constraints shape the design and *why*; §0 lists the exact values a blind implementer and the
152
152
  reviewer must honor. A value that lives only in prose ("use the project's naming rule") is a value
@@ -13,7 +13,7 @@ fix slow SQL, run zero-downtime migrations, and operate/secure the database. Too
13
13
  example is runnable.
14
14
 
15
15
  New, non-trivial feature with no approved spec + plan under `02-DOCS/wiki/sdd/`? Hand off to
16
- [`specify`](../specify/SKILL.md) before writing feature code (method: [`sdd`](../sdd/SKILL.md)); build
16
+ [`specify`](../specify/SKILL.md) before writing feature code (method: [`asdd`](../asdd/SKILL.md)); build
17
17
  straight from here only for a genuinely one-line, low-risk change.
18
18
 
19
19
  Deep dives: [schema-and-indexing](references/schema-and-indexing.md) (types, constraints, every index
@@ -17,7 +17,7 @@ or screen, and its motion layer, is `../design/SKILL.md`'s.
17
17
 
18
18
  > Build or review a fast, typed, correctly-architected **client-side** React single-page app bundled by Vite. No server runtime, no RSC tree, no framework router. Server-rendered React (App Router, server actions, SSR/SSG) is not this skill — that is `../nextjs/SKILL.md`.
19
19
 
20
- **SDD gate — before writing feature code.** If this skill fired on a **new, non-trivial feature or behaviour change** and there is **no approved spec + plan** under `02-DOCS/wiki/sdd/`, stop and hand off to `../specify/SKILL.md`: it runs brainstorm → spec → plan → tasks, then routes back here once the plan is approved. Build directly only for a genuinely one-line / low-risk change. Method: `../sdd/SKILL.md`.
20
+ **SDD gate — before writing feature code.** If this skill fired on a **new, non-trivial feature or behaviour change** and there is **no approved spec + plan** under `02-DOCS/wiki/sdd/`, stop and hand off to `../specify/SKILL.md`: it runs brainstorm → spec → plan → tasks, then routes back here once the plan is approved. Build directly only for a genuinely one-line / low-risk change. Method: `../asdd/SKILL.md`.
21
21
 
22
22
  ## First: confirm it's a Vite SPA, not a framework
23
23
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: review
3
- description: "Use when a green diff needs adversarial judgment in the rsc SDD chain, between verify and ship — give a review keyed to the spec/plan/constitution in 02-DOCS/wiki/sdd/, or receive one and verify each comment before agreeing. NOT running lint/type/tests (that is `verify`), NOT the spec-less standalone pass (that is `code-review`), NOT merging (that is `ship`)."
4
- tags: [sdd, review, code-review]
3
+ description: "Use when a diff needs adversarial judgment in rsc ASDD — per task inside implement's loop (depth set by `rsc risk`: none, one fresh-context reviewer, or the three-refuter panel) or as the closing review of the whole branch against the spec — keyed to the spec/plan/constitution in 02-DOCS/wiki/sdd/ and the project's REVIEW.md; or receive a review and verify each comment before agreeing. Converges via `rsc review ledger` (max 3 rounds, no repeated finding). NOT running lint/type/tests (that is `verify`), NOT the spec-less standalone pass (that is `code-review`), NOT merging (that is `ship`)."
4
+ tags: [asdd, review, code-review, risk]
5
5
  recommends: [ship]
6
6
  profiles: [core, full]
7
7
  origin: risco
@@ -9,7 +9,10 @@ origin: risco
9
9
 
10
10
  # Review — adversarial code review, both directions
11
11
 
12
- Review is the **penultimate gate** of the rsc SDD chain: `constitution → specify → clarify → plan → tasks → analyze → implement → verify → **review** → ship`. By the time a diff reaches review, the `verify` phase has already proven that lint, types, tests and the stack `verify.sh` are green. Review answers a different question: **is the green diff actually correct, well-scoped, and faithful to the spec — or did it pass for the wrong reasons?**
12
+ Tests prove the code works, not that it does what was asked. Review answers that other question: **is the diff correct, well-scoped and faithful to the spec — or did it pass for the wrong reasons?** In ASDD (`../asdd/SKILL.md`) it runs in two modes:
13
+
14
+ - **Per task** — called from `implement`'s loop after each task's self-check. Depth comes from `npx @ericrisco/rsc risk [--base <ref>] [paths…]` (same path table as the sello, works with the sello off): **tier 0** → no review; **tier 1** → one fresh-context reviewer (a subagent that never saw the implementation conversation); **tier 2** → the three-refuter panel below, and the branch will wait for human approval at ship.
15
+ - **Closing review** — once, before `ship`: the whole branch against the spec. Forty individually-correct tasks can still fail to be the feature together: a criterion nobody owned, two tasks that each assumed the other handled an edge, scope that crept in a slice at a time. Read the spec's acceptance criteria and "must respect" list against `git diff <base>...HEAD`, not the task reviews. Its record — tier, counts, verdict, each finding with its outcome — goes to `02-DOCS/wiki/sdd/reviews/<slug>.md`.
13
16
 
14
17
  This skill owns **two roles that share one discipline**:
15
18
 
@@ -22,9 +25,10 @@ The discipline is the same in both directions: **every finding and every rebutta
22
25
 
23
26
  ## Read these first
24
27
 
25
- 1. `02-DOCS/wiki/sdd/specs/<slug>.md` and `02-DOCS/wiki/sdd/plans/<slug>.md` — what the diff was *supposed* to do. A review with no spec is a review of vibes.
26
- 2. `02-DOCS/wiki/sdd/constitution.md` — the project's non-negotiables (stack canon, quality bars, conventions). Constitution violations are findings even when the code "works".
27
- 3. `02-DOCS/wiki/harness/user-profile.md` — `technical_level`, the register (see "How a review reads" below).
28
+ 1. `REVIEW.md` at the project root, if present (drafted by `constitution`): what failure looks like in this repo, paths to skip, the nit cap, and that the summary starts with a count ("3 findings: 1 blocker, 2 nits"). Where it differs from this skill's defaults, it wins.
29
+ 2. `02-DOCS/wiki/sdd/specs/<slug>.md` and `02-DOCS/wiki/sdd/plans/<slug>.md` — what the diff was *supposed* to do. A review with no spec is a review of vibes.
30
+ 3. `02-DOCS/wiki/sdd/constitution.md` — the project's non-negotiables (stack canon, quality bars, conventions). Constitution violations are findings even when the code "works".
31
+ 4. `02-DOCS/wiki/harness/user-profile.md` — `technical_level`, the register (see "How a review reads" below).
28
32
 
29
33
  If there is no spec/plan (someone jumped straight to code), say so and review against the constitution + the diff's own stated intent. Don't pretend a spec exists.
30
34
 
@@ -126,12 +130,14 @@ Every state transition is deterministic CLI, never tokens:
126
130
  gap deliberately with `--accept-partial-lenses` (it is recorded). Every approval also appends to
127
131
  `.rsc/sello-log.jsonl`, so what shipped under which verdict survives the next freeze.
128
132
 
129
- **Lenses by risk tier** — tier 0 never reaches you (the gate passes docs/copy silently);
130
- tier 1 → run the single most relevant pass from the table above yourself; tier 2 → dispatch
131
- **three parallel fresh-context subagents** (`refuter-correctness` · `refuter-security` ·
132
- `refuter-tests`), each told
133
- to *refute* readiness, not confirm it. Fresh context is the point: a reviewer who inherits the
134
- implementer's context inherits its blind spots.
133
+ ### Depth by risk — who reviews
134
+
135
+ `rsc risk` and `sello freeze` read the same path table, so the tier is the same with the sello on
136
+ or off. Tier 0 never reaches you (docs/copy pass silently); tier 1 → **one fresh-context
137
+ reviewer** running the most relevant pass from the table above; tier 2 → **three parallel
138
+ fresh-context subagents** (`refuter-correctness` · `refuter-security` · `refuter-tests`), each
139
+ told to *refute* readiness, not confirm it. Fresh context is the point: a reviewer who inherits the
140
+ implementer's context inherits its blind spots. The closing review is always at least tier 1.
135
141
 
136
142
  **Give each refuter exactly four inputs — no more, no less.** The diff alone is not enough, and
137
143
  that is not a detail: with only the diff, the one failure class no test suite can structurally
@@ -169,21 +175,28 @@ Two extra finding classes the four inputs unlock, neither reachable from a diff:
169
175
  report one defect three ways, and a lens hunting for something can overstate what it found. Two
170
176
  steps, in this order:
171
177
 
172
- 1. **Group (deterministic).** Each lens ends its report with a ` ```json findings ` block. Save the
173
- three reports and run `npx @ericrisco/rsc review consolidate <reports…>`. It merges findings on
174
- the same file within 3 lines, or quoting the same evidence; keeps the highest severity and every
175
- lens that saw it; and downgrades a blocker or should-fix with no `file:line` or failure scenario to
176
- a question. Several lenses on one finding is a reason to look first, never a reason to raise its
177
- severity.
178
- 2. **Verify (judgement).** For each finding it marks `verify`, dispatch the **`finding-verifier`**
179
- agent with that one finding plus the same four inputs, in parallel. It starts from "this is false"
180
- and returns **CONFIRMED** (reproduced or traced, with the proof), **REFUTED** (cites the guard that
181
- makes it impossible), or **UNPROVEN** (becomes a question). It never gets the lens's reasoning.
178
+ 1. **Group (deterministic).** Each lens ends its report with a ` ```json findings ` block — an
179
+ array of objects in exactly this shape (give it to every reviewer verbatim):
180
+
181
+ ```json
182
+ {"severity":"blocker|should-fix|nit|question","file":"path","line":N,"claim":"…","failure_scenario":"concrete input → wrong result","evidence":"quoted code","class":"bug class"}
183
+ ```
184
+
185
+ Save the reports and run `npx @ericrisco/rsc review consolidate <reports…>`. It merges findings
186
+ on the same place that say the same thing, or that quote the same evidence; keeps the highest
187
+ severity and every lens that saw it; and **downgrades a blocker or should-fix with no
188
+ `file:line` or no `failure_scenario` to a question**. Several lenses on one finding is a reason
189
+ to look first, never a reason to raise its severity.
190
+ 2. **Verify (judgement).** Consolidate marks `verify` on **every blocker and every should-fix**.
191
+ For each, dispatch the **`finding-verifier`** agent with that one finding plus the same four
192
+ inputs, in parallel. It starts from "this is false" and returns **CONFIRMED** (reproduced or
193
+ traced, with the proof), **REFUTED** (cites the guard that makes it impossible), or **UNPROVEN**
194
+ (becomes a question). It never gets the lens's reasoning.
182
195
 
183
196
  Report the consolidated count honestly ("11 reported → 4 unique → 2 confirmed"), and list what was
184
197
  refuted and why: a refuted finding hidden from the author is a review nobody can audit. At tier 1
185
- (one pass, run by you) there is nothing to group, but a blocker still goes to `finding-verifier`
186
- before it blocks.
198
+ (one reviewer) there is nothing to group, but run `review consolidate` on its one report anyway:
199
+ it applies the downgrade, and every blocker and should-fix still goes to `finding-verifier`.
187
200
 
188
201
  **A finding blocks only if it survives all three filters** — no exceptions, and eagerness to
189
202
  find something is not evidence:
@@ -195,11 +208,38 @@ find something is not evidence:
195
208
  3. **Evidence** — `finding-verifier` returned **CONFIRMED** with its proof. REFUTED is dropped (with
196
209
  its reason in the report); UNPROVEN is a `[question]`, and questions don't block.
197
210
 
198
- **Fixing a blocker is budgeted, one attempt.** Before touching anything: estimate the fix and
199
- declare it — `npx @ericrisco/rsc sello budget --lines <N>`. After the fix:
200
- `sello budget-check` (over budget → justify with `--justify "…"` or shrink; an unexplained
201
- overrun is how over-engineering enters disguised as a fix). Then `sello freeze` + re-review
202
- **only the divergence**, and approve. Still broken after one attempt → stop, hand it to the human.
211
+ **Fixing a blocker is budgeted.** Before touching anything: estimate the fix and declare it —
212
+ `npx @ericrisco/rsc sello budget --lines <N>`. After the fix: `sello budget-check` (over budget →
213
+ justify with `--justify "…"` or shrink; an unexplained overrun is how over-engineering enters
214
+ disguised as a fix). Then `sello freeze` + re-review **only the divergence**, and approve. Each
215
+ fix-and-re-review is one round of the loop below.
216
+
217
+ ### Convergence — the fix loop ends
218
+
219
+ A review loop that never converges is worse than none: Anthropic saw a single PR take 50 rounds.
220
+ Four rules, enforced by `npx @ericrisco/rsc review ledger` (`.rsc/review-ledger.jsonl`) rather
221
+ than by memory:
222
+
223
+ 1. **Max 3 rounds per task.** Call `review ledger round <slug>/<task>` before **every** review of
224
+ the task, the first included: round 1 is the task's first review, not its first fix. It exits
225
+ non-zero on the 4th. That is not "try harder": rethink the approach, or park the task in the
226
+ decision queue (a `blocking` entry) and continue with independent work.
227
+ 2. **From round 2, re-review only the new diff** — what changed since the last round, not the task
228
+ again from scratch.
229
+ 3. **Flag the bug class, not the line.** "Every handler in this file trusts `req.params.id`" — so
230
+ one fix covers every instance and round 3 does not find the sibling.
231
+ 4. **Never repeat a finding.** Record each round's findings with `review ledger record
232
+ <findings.json>` (the consolidate output works). A finding already seen — same file, class and
233
+ normalized title — comes back marked `repeat`; it is never re-raised as new. A repeat means the
234
+ last fix missed the class, so say that instead.
235
+
236
+ **Every finding closes with an outcome**: `review ledger outcome <id> fixed|false|intentional|irrelevant|parked`
237
+ — `parked` when the finding's task went to the decision queue (neither fixed nor dismissed; a human
238
+ decides). `review ledger stats` then shows totals per outcome, the false rate and mean rounds per
239
+ task — the only way to tell whether a change to the reviewer made it better or worse.
240
+
241
+ The ledger is **local**: it lives in `.rsc/` and is never committed, so it does not travel between
242
+ machines or clones. What lasts is what you write from it — review-evals cases and rules (below).
203
243
 
204
244
  ---
205
245
 
@@ -276,26 +316,25 @@ In the `orient` voice: the verdict first, then the blocker list — e.g. `CHANGE
276
316
 
277
317
  ## Model tier — `heavy` (opt-in routing)
278
318
 
279
- This phase's default model tier is **`heavy`** — adversarial diff reading is where the strongest model pays off most. Routing is **off** unless `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`. When on: resolve this phase's tier (`models.overrides` wins over `models.phases`), map it to a model via `models.tiers`, and apply per `../sdd/references/model-routing.md` — announce the switch in one line when it differs from the session model, and dispatch any `Task`/`parallel` subagents on that model. Routing off or no profile → honor the session model silently. Never fake a switch a tool can't make; skip routing on a one-line change.
280
-
281
- ## Where this writes
319
+ This phase's default model tier is **`heavy`** — adversarial diff reading is where the strongest model pays off most. Routing is **off** unless `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`. When on: resolve this phase's tier (`models.overrides` wins over `models.phases`), map it to a model via `models.tiers`, and apply per `../asdd/references/model-routing.md` — announce the switch in one line when it differs from the session model, and dispatch any `Task`/`parallel` subagents on that model. Routing off or no profile → honor the session model silently. Never fake a switch a tool can't make; skip routing on a one-line change.
282
320
 
283
- Review is mostly a conversation, but two artifacts persist into the harness wiki so the knowledge model grows:
321
+ ## Where this writes — and how the reviewer learns
284
322
 
285
323
  - **Accepted/declined findings** of consequence → append to `02-DOCS/wiki/sdd/decisions.md` (e.g. "declined NPE finding on documents.py:42 — guarded by require_auth; added regression test"). This is the same append-only log `implement` writes to.
286
- - If a finding reveals a **missing constitution rule** (a defect class the project keeps hitting), propose adding it to `02-DOCS/wiki/sdd/constitution.md` so the next review catches it earlier.
324
+ - **Escapes become cases.** `npx @ericrisco/rsc review escapes [--since <ref>]` lists every revert commit and whether it has a case at `02-DOCS/wiki/sdd/review-evals/<shortsha>.md`. Write the missing ones: the diff that shipped, where the bug was, and what the reviewer should have said. Only `Revert "…"` / `revert:` commits are seen; a hand-made revert is invisible.
325
+ - **A finding that keeps repeating becomes a rule** — a lint rule, a constitution principle (`02-DOCS/wiki/sdd/constitution.md`, amended via `constitution`) or a line in `REVIEW.md`. Ask what the model didn't have in front of it: a repeat is almost always missing context, not a careless reviewer.
287
326
 
288
- Index both in `02-DOCS/wiki/index.md` (the Knowledge map; root `CLAUDE.md` keeps only a short pointer) under the `sdd/` topic — the harness owns that map; this skill just keeps its rows honest.
327
+ Index them in `02-DOCS/wiki/index.md` (the Knowledge map; root `CLAUDE.md` keeps only a short pointer) under the `sdd/` topic — the harness owns that map; this skill just keeps its rows honest.
289
328
 
290
329
  ## Result envelope
291
330
 
292
- End with the parseable block every SDD phase shares, so the dispatcher can chain without
293
- interpreting prose (contract: `../sdd/SKILL.md`):
331
+ End with the parseable block every phase shares, so the dispatcher can chain without
332
+ interpreting prose (contract: `../asdd/SKILL.md`):
294
333
 
295
334
  ```json result-envelope
296
335
  {
297
336
  "status": "complete|blocked|failed",
298
- "executive_summary": "Adversarial review against spec/plan/constitution; verdict and blocking findings.",
337
+ "executive_summary": "Per-task or closing review against spec/plan/constitution/REVIEW.md; tier, round, verdict and blocking findings.",
299
338
  "artifact": "02-DOCS/wiki/sdd/reviews/<slug>.md",
300
339
  "next_recommended": "ship",
301
340
  "risk": "low|medium|high",
@@ -303,12 +342,12 @@ interpreting prose (contract: `../sdd/SKILL.md`):
303
342
  "used": ["review"],
304
343
  "missing": [],
305
344
  "fallback": [],
306
- "compact_rules": ["Rank or it is noise: blocker / should-fix / nit.", "A finding without a repro is a question, not a defect."]
345
+ "compact_rules": ["Rank or it is noise: blocker / should-fix / nit.", "A finding without a repro is a question, not a defect.", "Max 3 rounds; never repeat a finding."]
307
346
  },
308
- "evidence": ["verdict stated (APPROVE | APPROVE WITH NITS | CHANGES REQUESTED)", "each blocker carries location + repro + fix"]
347
+ "evidence": ["rsc risk tier", "verdict stated (APPROVE | APPROVE WITH NITS | CHANGES REQUESTED)", "each blocker carries location + repro + fix", "every finding has a ledger outcome"]
309
348
  }
310
349
  ```
311
350
 
312
351
  ## Next in the chain
313
352
 
314
- When the diff carries an **APPROVE** / **APPROVE WITH NITS** verdict and every blocker is resolved, hand off to **ship** — close the branch via PR / merge / cleanup, with **git authorship as Eric, never Claude**. If the review came back **CHANGES REQUESTED**, the loop goes back to **implement** (fix), then **verify** (re-prove green), then back here for re-review. Don't ship a diff that hasn't earned its verdict.
353
+ Per task: **APPROVE** returns to `implement`'s loop for the next task; **CHANGES REQUESTED** goes back to the fix step of the same task, as the next round. Closing review: **APPROVE** / **APPROVE WITH NITS** with every blocker resolved hands off to **ship**, with **git authorship as the human, never an AI**; anything else goes back to `implement`. Don't ship a diff that hasn't earned its verdict.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: sdd-init
3
- description: "Use when calibrating an existing repo for the rsc SDD chain: detect stack, package manager, test runners and apply/verify commands, refresh the skill registry, and write the runtime config at 02-DOCS/wiki/sdd/config.yaml. NOT the dispatcher that routes SDD phases (that is `sdd`), NOT first-contact user/workspace bootstrap (that is `init`)."
3
+ description: "Use when calibrating an existing repo for the rsc SDD chain: detect stack, package manager, test runners and apply/verify commands, refresh the skill registry, and write the runtime config at 02-DOCS/wiki/sdd/config.yaml. NOT the dispatcher that routes SDD phases (that is `asdd`), NOT first-contact user/workspace bootstrap (that is `init`)."
4
4
  tags: [sdd, init, config, testing, registry]
5
- recommends: [sdd, specify, implement, verify]
5
+ recommends: [asdd, constitution, specify, verify]
6
6
  profiles: [core, full]
7
7
  origin: risco
8
8
  ---
@@ -15,7 +15,7 @@ Step zero for technical SDD work: read the repo, detect how it should be built a
15
15
  02-DOCS/wiki/sdd/config.yaml
16
16
  ```
17
17
 
18
- That config is the runtime contract later phases read before choosing commands, TDD strictness, artifact paths, review budget or skill briefs. `sdd` dispatches the phases that consume it, `init` owns first contact and user profiling, `harness` owns `01-TOOLS/` and `02-DOCS/` scaffolding. This skill only calibrates.
18
+ That config is the runtime contract later phases read before choosing commands, TDD strictness, artifact paths, review budget or skill briefs. `asdd` dispatches the phases that consume it, `init` owns first contact and user profiling, `harness` owns `01-TOOLS/` and `02-DOCS/` scaffolding. This skill only calibrates.
19
19
 
20
20
  ## Inputs
21
21
 
@@ -28,18 +28,20 @@ Read-only first:
28
28
 
29
29
  If `02-DOCS/` does not exist, create only the `02-DOCS/wiki/sdd/` path needed for the config. Do not run full harness scaffolding unless the user asked for `harness`.
30
30
 
31
+ **Empty repo — nothing to detect?** Then there is nothing to calibrate yet: `constitution` runs first and decides the stack, and this skill calibrates against that decision (or re-runs once the first manifest exists). Never invent runners for an empty tree.
32
+
31
33
  ## Preflight Choices
32
34
 
33
35
  Ask only when the answer changes behavior; explain each trade-off in one line, in the register `technical_level` sets.
34
36
 
35
37
  | Setting | Default | Options |
36
38
  | --- | --- | --- |
37
- | `execution_mode` | `interactive` | `interactive` pauses at review-risk gates; `automatic` chains phases until a blocker/risk appears. |
39
+ | `execution_mode` | `interactive` | `interactive` pauses at review-risk gates; `automatic` chains phases until a blocker/risk appears. `sdd.execution_mode: automatic` is the switch `asdd`'s autopilot reads. |
38
40
  | `artifact_store` | `02-DOCS/wiki/sdd` | Keep RSC artifacts in `02-DOCS`; do not create an `openspec/` parallel tree. |
39
41
  | `review_budget.line_budget` | `400` | Lower for solo tight review; higher only with explicit approval. |
40
42
  | `delivery_strategy.default` | `ask-on-risk` | `ask-on-risk`, `single-pr`, `autochain`, `exception`. |
41
43
  | `models.enabled` | `false` | Per-phase model routing is opt-in; leave off unless the user asks for it. |
42
- | `models.provider` | `anthropic` | Which provider the tiers resolve to. Set from the detected assistant when obvious, else `anthropic`. See `../sdd/references/model-routing.md` for other providers. |
44
+ | `models.provider` | `anthropic` | Which provider the tiers resolve to. Set from the detected assistant when obvious, else `anthropic`. See `../asdd/references/model-routing.md` for other providers. |
43
45
 
44
46
  ## Detection
45
47
 
@@ -145,7 +147,7 @@ models:
145
147
  overrides: {} # per-phase tier overrides set by the user
146
148
  ```
147
149
 
148
- The `models` block is the **per-phase model routing** profile: each phase declares a tier (`heavy`/`balanced`/`light`) and the tiers resolve to concrete models for `provider`. The full protocol — how phases apply it, the per-assistant switch mechanism, the provider→model table — lives in `../sdd/references/model-routing.md`. Keep this block byte-for-byte in sync with that reference.
150
+ The `models` block is the **per-phase model routing** profile: each phase declares a tier (`heavy`/`balanced`/`light`) and the tiers resolve to concrete models for `provider`. The full protocol — how phases apply it, the per-assistant switch mechanism, the provider→model table — lives in `../asdd/references/model-routing.md`. Keep this block byte-for-byte in sync with that reference.
149
151
 
150
152
  Preserve user edits if the file exists: update detected facts and leave comments/custom policy fields intact when possible. **Never flip `models.enabled` or drop `models.overrides` on re-calibration** — a calibration pass that silently switches the user's models is the one failure they cannot detect from the diff, so preserve both verbatim and only refresh `tiers`/`provider` if the user asks. If preservation is risky, write a proposed replacement next to it as `config.proposed.yaml` and ask.
151
153
 
@@ -158,7 +160,7 @@ End with the standard SDD result envelope:
158
160
  "status": "complete",
159
161
  "executive_summary": "SDD config calibrated and registry refreshed.",
160
162
  "artifact": "02-DOCS/wiki/sdd/config.yaml",
161
- "next_recommended": "sdd",
163
+ "next_recommended": "constitution|specify",
162
164
  "risk": "low",
163
165
  "skill_resolution": {
164
166
  "used": ["sdd-init"],
@@ -181,9 +183,9 @@ End with the standard SDD result envelope:
181
183
  | "I'll skip config and remember the commands in chat." | Chat is not source of truth. Write `config.yaml`. |
182
184
  | "No test command detected, but I'll still say strict TDD is active." | Strict TDD needs a runner. Record the gap. |
183
185
  | "Load all skills so the agent has context." | That pollutes context. Use registry -> selected skills -> compact rules. |
184
- | "This is the same as init." | No. `init` profiles user/workspace, `sdd` dispatches the phases, `sdd-init` calibrates the technical SDD runtime they both rely on. |
186
+ | "This is the same as init." | No. `init` profiles user/workspace, `asdd` dispatches the phases, `sdd-init` calibrates the technical SDD runtime they both rely on. |
185
187
  | "Create openspec/ because Gentle does." | RSC uses `02-DOCS/wiki/sdd/` as source of truth. |
186
188
 
187
189
  ## Next
188
190
 
189
- After `sdd-init`, return to `sdd`. If no spec exists, route to `specify`. If the work is ambiguous or architectural, write a proposal first under `02-DOCS/wiki/sdd/proposals/`.
191
+ After `sdd-init`, return to `asdd` with `next_recommended` set to the next phase: `constitution` if there is none (or it is `status: draft`), else `specify` when no spec exists. If the work is ambiguous or architectural, write a proposal first under `02-DOCS/wiki/sdd/proposals/`.