@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
package/README.md CHANGED
@@ -85,7 +85,7 @@ remove the need to check the result.
85
85
  1. **Describe the project.** Choose the assistants you use and how technical the conversation should be.
86
86
  2. **Review the setup.** See each proposed component and its reason, then accept the plan you want applied.
87
87
  3. **Ask for the work.** The agent names the appropriate lane: an answer, a focused change (FTD), or the
88
- specification-led chain (SDD) for decisions that affect each other.
88
+ specification-led chain (ASDD) for decisions that affect each other.
89
89
  4. **Keep the result and its evidence.** Project knowledge goes into `02-DOCS/`; implementation stays in
90
90
  your code. Future sessions can consult both.
91
91
 
@@ -109,7 +109,7 @@ npx @ericrisco/rsc@latest onboard
109
109
 
110
110
  The first run asks **one** question about conversation (technical, or with analogies), then what the
111
111
  project is for, its goal and the assistants to target. Then it presents the complete plan. A small
112
- website can defer SDD, agents and code guards; an operations harness does not receive them merely
112
+ website can defer ASDD, agents and code guards; an operations harness does not receive them merely
113
113
  because it lives in a repository. Deferred components record the evidence that would make rsc
114
114
  recommend them later, and `rsc reassess` reports it without installing anything.
115
115
 
@@ -186,10 +186,13 @@ Every turn takes exactly one lane, and the agent names it in one line, in your l
186
186
  2. **FTD, Fast-Track Development.** The request authorises a change. This is the default for ordinary
187
187
  work. One feature document holds intent, scope, a checklist, the evidence and the next step. Tasks
188
188
  are checked off against observed proof, never against intention.
189
- 3. **SDD, the ten-phase chain.** For the big or complex: several decisions that affect each other.
190
- **The agent enters it on its own**, and you still decide *what* gets built: you approve the spec
191
- and answer the clarifying questions, then review each phase or let it run on autopilot. Publishing
192
- always asks.
189
+ 3. **ASDD, Agent Spec-Driven Development.** For the big or complex: several decisions that affect
190
+ each other, and work an agent may run for hours. **The agent enters it on its own**, and you still
191
+ decide *what* gets built: the spec answers four questions (what the user can do, what result they
192
+ get, what it must respect, how it proves it) and you approve the spec. Then every task is reviewed inside
193
+ the build, scaled by `rsc risk`; what the agent must not decide alone waits in a decision queue
194
+ while independent work continues. Low-risk work lands on green CI; auth, billing, CI and migrations
195
+ wait for you.
193
196
 
194
197
  Ask for the other lane and it switches.
195
198
 
@@ -229,7 +232,7 @@ project. rsc handles four things by default:
229
232
  | --- | --- | --- |
230
233
  | **You choose how the project works** | The install asks once: straight on `main`, or branches and pull requests? With branches, a commit on `main` is refused and the agent asks before each change: this branch, a new one, or unlock `main`? It never opens a branch on its own. | «unlock main», or `rsc main unlock` |
231
234
  | **One workspace per agent** | If another session works in the same folder, new work goes to `.worktrees/<branch>/` at the root you work in (never inside a project folder) and is never committed. A `post-merge` hook retires it once the branch lands. | `rsc isolation off` |
232
- | **The agent picks the method** | FTD for simple changes, SDD for complex ones. The agent decides and says why. | Ask for the other lane |
235
+ | **The agent picks the method** | FTD for simple changes, ASDD for complex ones. The agent decides and says why. | Ask for the other lane |
233
236
  | **Knowledge reaches everyone** | `01-TOOLS/`, `02-DOCS/wiki/` and `02-DOCS/attachments/` travel through one exchange branch, `rsc/knowledge`, and reach `main` inside your normal pull requests. | `rsc knowledge-sync off` |
234
237
 
235
238
  **How it knows a project is complex.** A hook counts what anyone can check: a CI setup, a deployment
@@ -318,7 +321,11 @@ git-excluded local path, keeps 30 days, and injects at most 4,096 bytes. `rsc me
318
321
 
319
322
  ### Review that only brings you what is real
320
323
 
321
- In SDD, `review` sends three adversarial reviewers at the diff (correctness, security, tests). They
324
+ In ASDD, review runs inside every task, as deep as `rsc risk` says: none for docs, one fresh-context
325
+ reviewer for ordinary code, three adversarial reviewers (correctness, security, tests) for auth,
326
+ billing, CI or migrations. `rsc review ledger` caps the fix loop at three rounds and never lets a
327
+ finding be raised twice, and `rsc review escapes` turns every revert into a case for the reviewer. The
328
+ three reviewers
322
329
  often report the same defect in different words, and a reviewer hunting for problems can overstate
323
330
  one. So `rsc review consolidate` merges the duplicates, and every serious finding left goes to a
324
331
  fresh `finding-verifier` agent whose starting position is that the finding is false. It blocks the
@@ -513,7 +520,7 @@ its own when a task matches.
513
520
  Two lanes for change: `ftd` for ordinary work, and the ten-phase chain when durable artifacts would
514
521
  settle a real ambiguity. `idea-refinement` runs inside `specify`; `decision-challenge` is on demand.
515
522
 
516
- [ftd](skills/ftd/) · [sdd](skills/sdd/) · [constitution](skills/constitution/) · [idea-refinement](skills/idea-refinement/) · [specify](skills/specify/) · [clarify](skills/clarify/) · [plan](skills/plan/) · [tasks](skills/tasks/) · [analyze](skills/analyze/) · [decision-challenge](skills/decision-challenge/) · [implement](skills/implement/) · [source-grounded-development](skills/source-grounded-development/) · [verify](skills/verify/) · [review](skills/review/) · [simplify-code](skills/simplify-code/) · [ship](skills/ship/) · [debug](skills/debug/) · [worktrees](skills/worktrees/) · [parallel](skills/parallel/) · [tidy](skills/tidy/)
523
+ [ftd](skills/ftd/) · [asdd](skills/asdd/) · [constitution](skills/constitution/) · [idea-refinement](skills/idea-refinement/) · [specify](skills/specify/) · [clarify](skills/clarify/) · [plan](skills/plan/) · [tasks](skills/tasks/) · [analyze](skills/analyze/) · [decision-challenge](skills/decision-challenge/) · [implement](skills/implement/) · [source-grounded-development](skills/source-grounded-development/) · [verify](skills/verify/) · [review](skills/review/) · [simplify-code](skills/simplify-code/) · [ship](skills/ship/) · [debug](skills/debug/) · [worktrees](skills/worktrees/) · [parallel](skills/parallel/) · [tidy](skills/tidy/)
517
524
 
518
525
  ### 💼 Run a business
519
526
 
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.7.5",
2
+ "version": "4.0.1",
3
3
  "counts": {
4
4
  "skills": 274,
5
5
  "agents": 34,
@@ -260,6 +260,27 @@
260
260
  ],
261
261
  "profiles": []
262
262
  },
263
+ {
264
+ "id": "asdd",
265
+ "description": "Use when a feature or substantial change should go from idea to shipped, verified software and an agent may run it for hours on its own — the ASDD (Agent Spec-Driven Development) dispatcher and front door, in any language. A spec that must prove itself, review inside every task scaled by `rsc risk`, a decision queue for what the agent must not decide alone. Routes constitution -> specify -> clarify -> plan -> tasks -> analyze -> implement (verify + review per task) -> closing -> ship. Successor of `sdd`. NOT a single phase (it dispatches), NOT small authorised changes (that is `ftd`), NOT the workspace harness (that is `harness`).",
266
+ "tags": [
267
+ "sdd",
268
+ "asdd",
269
+ "spec",
270
+ "workflow",
271
+ "plan",
272
+ "review"
273
+ ],
274
+ "recommends": [
275
+ "sdd-init",
276
+ "constitution",
277
+ "specify"
278
+ ],
279
+ "profiles": [
280
+ "core",
281
+ "full"
282
+ ]
283
+ },
263
284
  {
264
285
  "id": "astro",
265
286
  "description": "Use when building a content-driven or marketing site with Astro 6: static-first pages, islands and partial hydration, content collections, server islands, per-route on-demand rendering, deploy adapters, and Astro 5→6 migration. NOT app-router React with server actions and heavy client interactivity (that is `nextjs`).",
@@ -589,7 +610,7 @@
589
610
  },
590
611
  {
591
612
  "id": "clarify",
592
- "description": "Use when a spec exists and must be de-risked before planning — hunt its ambiguities, unstated assumptions and edge cases, ask the few build-changing questions, bake the answers back into the spec. The rsc SDD gate between `specify` (writes the spec) and `plan` (designs the build). NOT the cross-artifact consistency check (that is `analyze`).",
613
+ "description": "Use when a spec exists and must be de-risked before planning — run the gap checklist over its four ASDD questions and out-of-scope, ask only what changes what gets built (one question at a time, recommendation first), record every other call as an assumption, bake the answers back into the spec. The second half of ASDD stage 2 (Spec), between `specify` (writes the spec) and `plan` (designs the build). NOT the cross-artifact consistency check (that is `analyze`).",
593
614
  "tags": [
594
615
  "sdd",
595
616
  "clarify",
@@ -844,11 +865,12 @@
844
865
  },
845
866
  {
846
867
  "id": "constitution",
847
- "description": "Use when setting or amending a project's non-negotiables — stack canon, quality bars, conventions, security/a11y floors — as numbered, testable rules later phases obey. First rsc-sdd phase; writes 02-DOCS/wiki/sdd/constitution.md. NOT a feature spec (that is `specify`), NOT the technical plan (that is `plan`), NOT the wiki itself (that is `harness`).",
868
+ "description": "Use when setting or amending a project's non-negotiables — stack canon, quality bars, conventions, security/a11y floors — as numbered, testable rules later phases obey, and drafting the REVIEW.md the reviewer reads. ASDD's base stage; writes 02-DOCS/wiki/sdd/constitution.md and REVIEW.md at the project root. NOT a feature spec (that is `specify`), NOT the technical plan (that is `plan`), NOT the wiki itself (that is `harness`).",
848
869
  "tags": [
849
- "sdd",
870
+ "asdd",
850
871
  "constitution",
851
- "principles"
872
+ "principles",
873
+ "review"
852
874
  ],
853
875
  "recommends": [
854
876
  "specify"
@@ -1736,7 +1758,6 @@
1736
1758
  "epistemics"
1737
1759
  ],
1738
1760
  "recommends": [
1739
- "sdd",
1740
1761
  "verify",
1741
1762
  "debug",
1742
1763
  "review"
@@ -1929,7 +1950,7 @@
1929
1950
  },
1930
1951
  {
1931
1952
  "id": "ftd",
1932
- "description": "Use for ordinary authorised change — the default lane once the decisor has ruled out a question and not selected the chain. Keeps one feature document per feature in 02-DOCS holding intent, scope, checklist, evidence and next step, checks tasks off only against observed proof, and asks whether to branch before the work writes code. NOT the ten-phase chain (that is `sdd`), NOT the classification itself (that is the decisor in `suggest`), NOT a request for information, which writes nothing.",
1953
+ "description": "Use for ordinary authorised change — the default lane once the decisor has ruled out a question and not selected the chain. Keeps one feature document per feature in 02-DOCS holding intent, scope, checklist, evidence and next step, checks tasks off only against observed proof, asks whether to branch before the work writes code, and runs a minimal risk loop (self-check, `rsc risk`, one reviewer from tier 1). NOT the full method (that is `asdd`), NOT the classification itself (that is the decisor in `suggest`), NOT a request for information, which writes nothing.",
1933
1954
  "tags": [
1934
1955
  "ftd",
1935
1956
  "lane",
@@ -2431,14 +2452,16 @@
2431
2452
  },
2432
2453
  {
2433
2454
  "id": "implement",
2434
- "description": "Use when an approved plan and task list exist and it is time to turn them into working, tested code — the SDD phase after `analyze` and before `verify`. Enforces TDD: a failing test comes before the code that makes it pass, one task at a time, appended to a progress ledger that survives compaction. Delegates test tooling to the stack skill and fans disjoint tasks out via `parallel`. NOT spec writing (that is `specify`), NOT planning (that is `plan`), NOT the final lint/test/audit gate (that is `verify`).",
2455
+ "description": "Use when an approved plan and task list exist and it is time to turn them into working, reviewed code — ASDD stage 4, the per-task loop after `analyze`: TDD build and self-check, `rsc risk` on the diff, review scaled by tier (0/1/3 reviewers), mandatory controls (tests, build, security, traceability), at most 3 fix rounds, then fix-and-record or park in the decision queue. Progress ledger survives compaction; disjoint tasks fan out via `parallel`. NOT spec writing (that is `specify`), NOT planning (that is `plan`), NOT the closing whole-branch gate (stage 5: `verify` + `review`).",
2435
2456
  "tags": [
2457
+ "asdd",
2436
2458
  "sdd",
2437
2459
  "implement",
2438
2460
  "code"
2439
2461
  ],
2440
2462
  "recommends": [
2441
- "verify"
2463
+ "verify",
2464
+ "review"
2442
2465
  ],
2443
2466
  "profiles": [
2444
2467
  "core",
@@ -4134,11 +4157,12 @@
4134
4157
  },
4135
4158
  {
4136
4159
  "id": "review",
4137
- "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`).",
4160
+ "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`).",
4138
4161
  "tags": [
4139
- "sdd",
4162
+ "asdd",
4140
4163
  "review",
4141
- "code-review"
4164
+ "code-review",
4165
+ "risk"
4142
4166
  ],
4143
4167
  "recommends": [
4144
4168
  "ship"
@@ -4341,28 +4365,9 @@
4341
4365
  "full"
4342
4366
  ]
4343
4367
  },
4344
- {
4345
- "id": "sdd",
4346
- "description": "Use when you want a disciplined, spec-driven path from a feature idea to shipped, verified software — the SDD dispatcher and front door, in any language. States the method, reads the user's register from 02-DOCS, and routes to the right phase: constitution -> specify -> clarify -> plan -> tasks -> analyze -> implement -> verify -> review -> ship, with debug / worktrees / parallel on demand. Use it to start a feature, to govern the whole flow, or when unsure which phase you are in. NOT a single phase itself (it dispatches), NOT the workspace harness (that is `harness`), NOT a stack build skill.",
4347
- "tags": [
4348
- "sdd",
4349
- "spec",
4350
- "workflow",
4351
- "plan"
4352
- ],
4353
- "recommends": [
4354
- "sdd-init",
4355
- "constitution",
4356
- "specify"
4357
- ],
4358
- "profiles": [
4359
- "core",
4360
- "full"
4361
- ]
4362
- },
4363
4368
  {
4364
4369
  "id": "sdd-init",
4365
- "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`).",
4370
+ "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`).",
4366
4371
  "tags": [
4367
4372
  "sdd",
4368
4373
  "init",
@@ -4371,9 +4376,9 @@
4371
4376
  "registry"
4372
4377
  ],
4373
4378
  "recommends": [
4374
- "sdd",
4379
+ "asdd",
4380
+ "constitution",
4375
4381
  "specify",
4376
- "implement",
4377
4382
  "verify"
4378
4383
  ],
4379
4384
  "profiles": [
@@ -4439,12 +4444,13 @@
4439
4444
  },
4440
4445
  {
4441
4446
  "id": "ship",
4442
- "description": "Use when a verified, review-approved branch has to land — the close of the rsc SDD chain: safety checks, then three landing options (merge, PR, park/discard), authorship always Eric and never an AI trailer. NOT running the gates (that is `verify`), NOT reading the diff for defects (that is `review`), NOT shipping to a server (that is `deployment`).",
4447
+ "description": "Use when a verified branch that passed its closing review has to land — the close of rsc ASDD: safety checks, an explainer for the human (what changed, what the checks proved and would not catch), then three landing options (merge, PR with Testing/Risk/How to revert, park/discard). Risk tier 0-1 with CI green may self-merge; tier 2 waits for human approval. Authorship always the human, never an AI trailer. NOT running the gates (that is `verify`), NOT reading the diff for defects (that is `review`), NOT shipping to a server (that is `deployment`).",
4443
4448
  "tags": [
4444
- "sdd",
4449
+ "asdd",
4445
4450
  "ship",
4446
4451
  "release",
4447
- "pr"
4452
+ "pr",
4453
+ "risk"
4448
4454
  ],
4449
4455
  "recommends": [],
4450
4456
  "profiles": [
@@ -4691,7 +4697,7 @@
4691
4697
  },
4692
4698
  {
4693
4699
  "id": "specify",
4694
- "description": "Use when a feature, change, or product idea is still fuzzy and must become an approved WHAT/WHY spec before any planning or code — the brainstorming front door of SDD. Turns a one-line intent into problem, goals, users, scope, behaviour and acceptance criteria, via frontier-round dialogue and 2-3 approaches with a recommendation, carrying zero implementation detail. Fires whenever someone jumps to HOW before WHAT is agreed, in any language. NOT the technical plan (that is `plan`), NOT the de-risking ambiguity sweep (that is `clarify`), NOT project-wide principles (that is `constitution`).",
4700
+ "description": "Use when a feature, change, or product idea is still fuzzy and must become an approved WHAT/WHY spec before any planning or code — the brainstorming front door of ASDD stage 2 (Spec). Turns a one-line intent into problem, goals, users, out-of-scope, the four ASDD questions (what the user can do, what result they get, what it must respect, how it proves it) and acceptance criteria, via frontier-round dialogue and 2-3 approaches with a recommendation, carrying zero implementation detail. Fires whenever someone jumps to HOW before WHAT is agreed, in any language. NOT the technical plan (that is `plan`), NOT the de-risking ambiguity sweep (that is `clarify`), NOT project-wide principles (that is `constitution`).",
4695
4701
  "tags": [
4696
4702
  "sdd",
4697
4703
  "spec",
@@ -4913,8 +4919,9 @@
4913
4919
  },
4914
4920
  {
4915
4921
  "id": "tasks",
4916
- "description": "Use when an approved plan needs slicing into an ordered task list before any code — the SDD `tasks` phase. Each row carries a literal done-check, dependencies, a parallel-safe marker and a spec trace, appended to the plan artifact. NOT writing the plan (that is `plan`), NOT the consistency gate (that is `analyze`), NOT executing the tasks (that is `implement`).",
4922
+ "description": "Use when an approved plan needs slicing into an ordered task list before any code — the `tasks` step of ASDD stage 3 (Plan). Each row carries the spec criterion it covers, a risk forecast (tier 0/1/2), a literal done-check, dependencies and a parallel-safe marker, appended to the plan artifact. NOT writing the plan (that is `plan`), NOT the consistency gate (that is `analyze`), NOT executing the tasks (that is `implement`).",
4917
4923
  "tags": [
4924
+ "asdd",
4918
4925
  "sdd",
4919
4926
  "tasks",
4920
4927
  "breakdown"
@@ -5411,8 +5418,9 @@
5411
5418
  },
5412
5419
  {
5413
5420
  "id": "verify",
5414
- "description": "Use when implementation is finished and about to be called done or merged — the rsc-sdd evidence gate: runs the stack's scripts/verify.sh (lint, type, test, audit), walks every task done-check and acceptance criterion, records a dated verdict. NOT judging the diff by eye (that is `review`, spec-less `code-review`), NOT diagnosis (that is `debug`).",
5421
+ "description": "Use when work is about to be called done — the ASDD evidence gate, in two modes: per task inside `implement`'s loop (tests, build, security, traceability) and at closing over the whole branch against main (stack scripts/verify.sh, every done-check and acceptance criterion, rollback plan, dated verdict). A check that did not execute is reported not run, never passed. NOT judging the diff by eye (that is `review`, spec-less `code-review`), NOT diagnosis (that is `debug`).",
5415
5422
  "tags": [
5423
+ "asdd",
5416
5424
  "sdd",
5417
5425
  "verify",
5418
5426
  "test"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "3.7.5",
3
+ "version": "4.0.1",
4
4
  "description": "A guided harness builder that turns your goal into a minimal, verified AI-agent setup using strict best practices.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,7 +5,7 @@
5
5
 
6
6
  export const DOMAINS = [
7
7
  { title: 'Core & control plane', ids: ['init', 'harness', 'orient', 'suggest', 'unslop', 'author-skill', 'sdd-init'] },
8
- { title: 'Spec-Driven Development', ids: ['ftd', 'sdd', 'constitution', 'idea-refinement', 'specify', 'clarify', 'plan', 'tasks', 'analyze', 'decision-challenge', 'implement', 'source-grounded-development', 'verify', 'review', 'simplify-code', 'ship', 'debug', 'worktrees', 'parallel', 'tidy'] },
8
+ { title: 'Spec-Driven Development', ids: ['ftd', 'asdd', 'constitution', 'idea-refinement', 'specify', 'clarify', 'plan', 'tasks', 'analyze', 'decision-challenge', 'implement', 'source-grounded-development', 'verify', 'review', 'simplify-code', 'ship', 'debug', 'worktrees', 'parallel', 'tidy'] },
9
9
  { title: 'Run a business', ids: ['finance-ops', 'invoicing', 'bookkeeping', 'pricing', 'sales-pipeline', 'lead-gen', 'cold-outreach', 'proposals', 'contracts', 'customer-support', 'client-onboarding', 'retention', 'hiring', 'people-ops', 'inventory', 'logistics-ops', 'procurement', 'meeting-notes', 'sop-builder', 'project-ops'] },
10
10
  { title: 'Raise & model money', ids: ['pitch-deck', 'investor-materials', 'financial-model', 'fundraising', 'unit-economics', 'grants'] },
11
11
  { title: 'Legal, privacy & compliance', ids: ['gdpr-privacy', 'terms-conditions', 'compliance', 'data-policy', 'ip-trademark'] },
@@ -111,7 +111,7 @@ export function renderOnboardingDocuments(plan, planId) {
111
111
  const profile = `---\ntechnical_level: ${plan.record.technicalLevel}\nproject_kind: ${plan.record.projectKind}\n---\n\n# User profile\n\nGoal: ${plan.record.goal}\n`;
112
112
  const rows = plan.decisions.map((d) => `| ${d.kind} | ${d.id} | ${d.state} | ${d.reason} | ${d.reevaluateWhen.join('; ') || '—'} |`).join('\n');
113
113
  const installation = `# Accepted harness plan\n\nPlan id: \`${planId}\`\n\n| Kind | Component | Decision | Reason | Reevaluate when |\n| --- | --- | --- | --- | --- |\n${rows}\n`;
114
- const decisions = `# Harness decisions\n\n- Accepted plan \`${planId}\`.\n- Project kind: ${plan.record.projectKind}.\n- SDD: ${plan.decisions.find((d) => d.id === 'sdd')?.state || 'selected through profile'}.\n`;
114
+ const decisions = `# Harness decisions\n\n- Accepted plan \`${planId}\`.\n- Project kind: ${plan.record.projectKind}.\n- SDD: ${plan.decisions.find((d) => d.id === 'asdd')?.state || 'selected through profile'}.\n`;
115
115
  return { profile, installation, decisions };
116
116
  }
117
117
 
@@ -276,10 +276,10 @@ export function buildOnboardingPlan(record, evidence) {
276
276
  // `sdd` is installed everywhere now, but its DECISION is about practice, not presence — so where
277
277
  // the chain is not practised the explicit `deferred` entry below must be the only one, or the
278
278
  // generic "installed → selected" mapping would shadow it and `reassess` would see nothing to watch.
279
- const decisions = skills.filter((id) => practisesSdd || id !== 'sdd').map((id) => detectedSkills.includes(id)
279
+ const decisions = skills.filter((id) => practisesSdd || id !== 'asdd').map((id) => detectedSkills.includes(id)
280
280
  ? selected(id, 'skill', `Detected ${id} evidence inside the selected project root.`, 'workspace-evidence')
281
281
  : selected(id, 'skill', needsSdd
282
- ? (id === 'sdd' && complexitySignals.length
282
+ ? (id === 'asdd' && complexitySignals.length
283
283
  ? `Included because the accepted work has ${complexitySignals.join(', ')} complexity.`
284
284
  : `Included in the development workflow for ${normalized.softwareScope} software.`)
285
285
  : `Included in the lightweight foundation for this ${normalized.projectKind} project.`));
@@ -293,16 +293,16 @@ export function buildOnboardingPlan(record, evidence) {
293
293
  // so the chain is always one request away; this decision is what `reassess` watches, and what the
294
294
  // floor reads. Collapsing the two would have deleted reassess's reason to exist as a side effect
295
295
  // of an installer change, which is not a decision an installer change gets to make.
296
- // The reason says both halves, because the plan lists `.claude/skills/sdd` under managed paths
296
+ // The reason says both halves, because the plan lists `.claude/skills/asdd` under managed paths
297
297
  // and a bare "deferred" next to it read as a contradiction (E2E 2026-10-07). Only the wording
298
- // changes: the decision stays the one deferred `workflow/sdd` entry that reassess watches.
298
+ // changes: the decision stays the one deferred `workflow/asdd` entry that reassess watches.
299
299
  const whyDeferred = isSoftware
300
300
  ? 'the software scope is small, so specification overhead is not justified yet.'
301
301
  : 'SDD applies to substantial software work, which is not the declared project purpose.';
302
- const sddReason = skills.includes('sdd')
303
- ? `The sdd skill is installed, so the chain is one request away; the practice is deferred: ${whyDeferred}`
302
+ const sddReason = skills.includes('asdd')
303
+ ? `The asdd skill is installed, so the chain is one request away; the practice is deferred: ${whyDeferred}`
304
304
  : whyDeferred.charAt(0).toUpperCase() + whyDeferred.slice(1);
305
- if (!practisesSdd) decisions.push(deferred('sdd', 'workflow', sddReason, sddTriggers, softwareTriggers));
305
+ if (!practisesSdd) decisions.push(deferred('asdd', 'workflow', sddReason, sddTriggers, softwareTriggers));
306
306
  if (baseAgents) {
307
307
  for (const id of agents) decisions.push(selected(id, 'agent', 'The accepted substantial software workflow requires this implementation or review role.'));
308
308
  } else {
@@ -14,10 +14,14 @@
14
14
  //
15
15
  // `ui-engineering` folded into `ux` in 3.4.0: the screen states and form behaviour it owned are
16
16
  // `ux`'s state matrix now, and where component state lives is `react`'s.
17
+ //
18
+ // `sdd` became `asdd` in 4.0.0 (spec asdd-v4): the same phases, with the review moved inside each
19
+ // task of `implement` and scaled by `rsc risk`. Artifacts stay under 02-DOCS/wiki/sdd/.
17
20
  export const RETIRED_SKILLS = Object.freeze({
18
21
  eli5: 'orient', 'show-me': 'orient', bro: 'unslop',
19
22
  'design-loop': 'design', 'design-dna': 'design', 'motion-craft': 'design',
20
23
  'ui-engineering': 'ux',
24
+ sdd: 'asdd',
21
25
  });
22
26
 
23
27
  const retiredTo = (id) => (Object.hasOwn(RETIRED_SKILLS, id) ? RETIRED_SKILLS[id] : id);
@@ -68,6 +68,7 @@ export function normalizeFinding(f) {
68
68
  claim: text(f.claim) || text(f.title) || text(f.summary),
69
69
  failure_scenario: text(f.failure_scenario) || text(f.repro),
70
70
  evidence: text(f.evidence),
71
+ class: text(f.class) || text(f.bug_class) || text(f.defect_class),
71
72
  notes: [],
72
73
  };
73
74
  if (!out.severity) {
@@ -140,6 +141,8 @@ export function consolidate(rawFindings, { window = DEFAULT_WINDOW } = {}) {
140
141
  claims: uniq(members.map((m) => m.claim)),
141
142
  failure_scenarios: uniq(members.map((m) => m.failure_scenario)),
142
143
  evidence: uniq(members.map((m) => m.evidence)),
144
+ // Carried through so `review ledger record` can fingerprint by bug class.
145
+ class: uniq(members.map((m) => m.class))[0] || '',
143
146
  notes: uniq(members.flatMap((m) => m.notes)),
144
147
  reported: members.length,
145
148
  verify: rank(severity) <= rank('should-fix'),
@@ -0,0 +1,187 @@
1
+ // The review ledger — the deterministic half of ASDD's fix loop and of the reviewer's learning.
2
+ //
3
+ // Anthropic's reviewer and author once looped more than fifty rounds on one PR, and about 59% of what
4
+ // the reviewer kept flagging was code it had already seen. Three rules stopped that: cap the rounds,
5
+ // never repeat a finding, and record what happened to each finding so noise can be measured. A rule
6
+ // that only lives in a skill's prose is the decorative gate (P2), so the three live here.
7
+ // Spec: 02-DOCS/wiki/sdd/specs/asdd-v4.md.
8
+ //
9
+ // One JSONL file, append-only: `finding`, `outcome` and `round` entries. It sits under .rsc/ and is
10
+ // excluded from the sello's candidate like every other journal the harness writes (targets/sello.mjs).
11
+
12
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { spawnSync } from 'node:child_process';
15
+
16
+ export const LEDGER_PATH = '.rsc/review-ledger.jsonl';
17
+ // `parked`: the finding's task went to the decision queue; neither fixed nor dismissed, a human decides.
18
+ export const OUTCOMES = ['fixed', 'false', 'intentional', 'irrelevant', 'parked'];
19
+ export const MAX_ROUNDS = 3;
20
+ export const EVALS_DIR = '02-DOCS/wiki/sdd/review-evals';
21
+
22
+ // Two findings are the same claim when they sit in the same file and share most of their words, or
23
+ // name the same bug class AND still share some. A class alone is not enough: two different
24
+ // null-check bugs in one file are two findings, and suppressing the second hides a real defect.
25
+ // The line is left out on purpose: it moves between rounds, and a repeat must survive the fix
26
+ // that moved it.
27
+ export const SAME_CLAIM = 0.5;
28
+ export const SAME_CLASS_CLAIM = 0.2;
29
+ const STOP = new Set('the and for with not that this its are was from any only one but has have into than then when which will can'.split(' '));
30
+ const words = (s) => new Set(String(s || '').toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 2 && !STOP.has(w)));
31
+ function overlap(a, b) {
32
+ const A = words(a); const B = words(b);
33
+ let shared = 0;
34
+ for (const w of A) if (B.has(w)) shared += 1;
35
+ const union = A.size + B.size - shared;
36
+ return union ? shared / union : 0;
37
+ }
38
+
39
+ const text = (v) => (typeof v === 'string' ? v.trim() : '');
40
+ const normFile = (f) => text(f).replace(/\\/g, '/').replace(/^\.\//, '');
41
+
42
+ // Accepts what the lenses emit and what `review consolidate --json` prints.
43
+ export function normalize(f) {
44
+ return {
45
+ file: normFile(f.file),
46
+ class: text(f.class) || text(f.bug_class) || text(f.defect_class),
47
+ claim: text(f.claim) || (Array.isArray(f.claims) ? text(f.claims[0]) : '') || text(f.title) || text(f.summary),
48
+ severity: text(f.severity) || 'question',
49
+ };
50
+ }
51
+
52
+ export function sameClaim(a, b) {
53
+ if (a.file !== b.file) return false;
54
+ const o = overlap(a.claim, b.claim);
55
+ if (a.class && b.class && a.class.toLowerCase() === b.class.toLowerCase()) return o >= SAME_CLASS_CLAIM;
56
+ return o >= SAME_CLAIM;
57
+ }
58
+
59
+ export function readLedger(root) {
60
+ const path = join(root, LEDGER_PATH);
61
+ if (!existsSync(path)) return [];
62
+ return readFileSync(path, 'utf8').split('\n').filter(Boolean).flatMap((line) => {
63
+ try { return [JSON.parse(line)]; } catch { return []; }
64
+ });
65
+ }
66
+
67
+ function append(root, entries) {
68
+ mkdirSync(join(root, '.rsc'), { recursive: true });
69
+ appendFileSync(join(root, LEDGER_PATH), entries.map((e) => `${JSON.stringify(e)}\n`).join(''));
70
+ }
71
+
72
+ // → [{id, status: 'new'|'repeat', repeatOf?, ...finding}]. A repeat is recorded too (it is the noise
73
+ // being measured) but points at the original, and the caller must not raise it again.
74
+ export function record(root, rawFindings, { task = '', now = new Date().toISOString() } = {}) {
75
+ const ledger = readLedger(root);
76
+ // A finding closed as fixed that shows up again is a regression, not noise: it is raised anew.
77
+ const fixed = new Set(ledger.filter((e) => e.type === 'outcome' && e.outcome === 'fixed').map((e) => e.id));
78
+ const seen = ledger.filter((e) => e.type === 'finding' && !e.repeatOf && !fixed.has(e.id));
79
+ let n = ledger.filter((e) => e.type === 'finding').length;
80
+ const out = [];
81
+ for (const raw of rawFindings) {
82
+ if (!raw || typeof raw !== 'object') continue;
83
+ const f = normalize(raw);
84
+ n += 1;
85
+ const original = seen.find((s) => sameClaim(s, f));
86
+ const entry = { type: 'finding', id: `L${n}`, task, at: now, ...f, ...(original ? { repeatOf: original.id } : {}) };
87
+ if (!original) seen.push(entry);
88
+ out.push(entry);
89
+ }
90
+ append(root, out);
91
+ return out.map((e) => ({ ...e, status: e.repeatOf ? 'repeat' : 'new' }));
92
+ }
93
+
94
+ export function outcome(root, id, value, { now = new Date().toISOString() } = {}) {
95
+ if (!OUTCOMES.includes(value)) {
96
+ return { ok: false, message: `rsc: unknown outcome "${value}". Use one of: ${OUTCOMES.join(', ')}.` };
97
+ }
98
+ if (!readLedger(root).some((e) => e.type === 'finding' && e.id === id)) {
99
+ return { ok: false, message: `rsc: no finding "${id}" in ${LEDGER_PATH}. Run \`npx @ericrisco/rsc review ledger stats\` to see the recorded ids.` };
100
+ }
101
+ append(root, [{ type: 'outcome', id, outcome: value, at: now }]);
102
+ return { ok: true, message: `${id}: ${value}` };
103
+ }
104
+
105
+ // Counts one more review round for a task. The fourth is refused, with the way out in the message
106
+ // (P6): rethink the approach, or park the task in the decision queue and go on with independent work.
107
+ // The task is qualified by its spec (`<slug>/T001`): every plan numbers its tasks from T001, and a
108
+ // bare id would let one feature inherit another's rounds and park work never reviewed.
109
+ export const QUALIFIED_TASK = /^[^/\s]+\/[^/\s]+$/;
110
+ export function round(root, task, { now = new Date().toISOString() } = {}) {
111
+ if (!QUALIFIED_TASK.test(task)) {
112
+ return { ok: false, round: 0, message: `rsc: qualify the task with its spec slug, e.g. \`review ledger round <slug>/${task || 'T001'}\` — task ids repeat across plans.` };
113
+ }
114
+ const n = readLedger(root).filter((e) => e.type === 'round' && e.task === task).length + 1;
115
+ if (n > MAX_ROUNDS) {
116
+ return {
117
+ ok: false, round: n,
118
+ message: `rsc: task "${task}" has had ${MAX_ROUNDS} review rounds and has not converged. Stop fixing line by line: rethink the approach, or park the task in the decision queue (02-DOCS/wiki/sdd/progress/<slug>.md, "## Decision queue") and continue with independent tasks.`,
119
+ };
120
+ }
121
+ append(root, [{ type: 'round', task, n, at: now }]);
122
+ return { ok: true, round: n, message: `task "${task}": review round ${n} of ${MAX_ROUNDS}` };
123
+ }
124
+
125
+ export function stats(root) {
126
+ const ledger = readLedger(root);
127
+ const findings = ledger.filter((e) => e.type === 'finding');
128
+ const originals = findings.filter((f) => !f.repeatOf);
129
+ const last = new Map();
130
+ for (const e of ledger) if (e.type === 'outcome') last.set(e.id, e.outcome);
131
+ const outcomes = Object.fromEntries([...OUTCOMES, 'open'].map((k) => [k, 0]));
132
+ for (const f of originals) outcomes[last.get(f.id) || 'open'] += 1;
133
+ // Parked is undecided: a human holds it. Counting it as closed would understate the false rate.
134
+ const closed = originals.length - outcomes.open - outcomes.parked;
135
+ const rounds = new Map();
136
+ for (const e of ledger) if (e.type === 'round') rounds.set(e.task, Math.max(rounds.get(e.task) || 0, e.n));
137
+ const totalRounds = [...rounds.values()].reduce((a, b) => a + b, 0);
138
+ return {
139
+ findings: originals.length,
140
+ repeats: findings.length - originals.length,
141
+ outcomes,
142
+ falseRate: closed ? outcomes.false / closed : null,
143
+ tasks: rounds.size,
144
+ meanRounds: rounds.size ? totalRounds / rounds.size : null,
145
+ open: originals.filter((f) => !last.has(f.id)).map((f) => f.id),
146
+ };
147
+ }
148
+
149
+ export function renderStats(s) {
150
+ const pct = (x) => (x === null ? 'n/a' : `${Math.round(x * 100)}%`);
151
+ return [
152
+ `findings: ${s.findings} unique, ${s.repeats} repeat(s) caught`,
153
+ `outcomes: ${OUTCOMES.map((k) => `${k} ${s.outcomes[k]}`).join(' · ')} · open ${s.outcomes.open}`,
154
+ `false rate: ${pct(s.falseRate)} of closed findings`,
155
+ `rounds: ${s.tasks} task(s), mean ${s.meanRounds === null ? 'n/a' : s.meanRounds.toFixed(2)} round(s) per task`,
156
+ ...(s.open.length ? [`still open: ${s.open.join(', ')} — close each with \`review ledger outcome <id> <${OUTCOMES.join('|')}>\``] : []),
157
+ ].join('\n');
158
+ }
159
+
160
+ // Escapes: reverts that reached the trunk. Each one should become a case the reviewer is scored
161
+ // against (the diff, where the bug was, what the review should have said). Only the conventional
162
+ // shapes are visible — `Revert "…"` from git, and a `revert:` commit type — and the output says so.
163
+ const REVERT = /^(?:\S+\s+)?(?:Revert "|revert(?:\([^)]*\))?!?:)/;
164
+ export function escapes(root, { since = '' } = {}) {
165
+ const range = since ? [`${since}..HEAD`] : [];
166
+ const r = spawnSync('git', ['-C', root, 'log', '--format=%H%x00%s', ...range], { encoding: 'utf8', windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
167
+ if (r.status !== 0) return { ok: false, message: `rsc: cannot read git history${since ? ` since ${since}` : ''} (${(r.stderr || '').trim() || 'not a git repository'}).`, reverts: [] };
168
+ let cases = [];
169
+ try { cases = readdirSync(join(root, EVALS_DIR)).filter((f) => f.endsWith('.md')); } catch { cases = []; }
170
+ const reverts = r.stdout.split('\n').filter(Boolean).map((line) => {
171
+ const [sha, subject] = line.split('\0');
172
+ return { sha: sha.slice(0, 7), subject };
173
+ }).filter((c) => REVERT.test(c.subject)).map((c) => ({ ...c, covered: cases.some((f) => f.startsWith(c.sha)) }));
174
+ return { ok: true, reverts };
175
+ }
176
+
177
+ export function renderEscapes(res) {
178
+ if (!res.reverts.length) return 'No reverts found (only `Revert "…"` subjects and `revert:` commits are recognised).';
179
+ const pending = res.reverts.filter((c) => !c.covered).length;
180
+ return [
181
+ ...res.reverts.map((c) => `${c.covered ? 'case ' : 'NO CASE'} ${c.sha} ${c.subject}`),
182
+ '',
183
+ pending
184
+ ? `${pending} revert(s) without a case. Write each as ${EVALS_DIR}/<sha>.md: the diff, where the bug was, what the review should have said.`
185
+ : 'Every revert has its case.',
186
+ ].join('\n');
187
+ }