@ericrisco/rsc 3.7.4 → 4.0.0

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 (77) hide show
  1. package/README.md +16 -9
  2. package/manifest.json +49 -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 +185 -0
  10. package/scripts/lib/risk.js +65 -0
  11. package/scripts/lib/spec-gate.js +10 -1
  12. package/scripts/rsc.js +91 -6
  13. package/skills/analyze/SKILL.md +3 -3
  14. package/skills/asdd/SKILL.md +159 -0
  15. package/skills/{sdd → asdd}/evals/README.md +10 -10
  16. package/skills/{sdd → asdd}/evals/cases.yaml +4 -4
  17. package/skills/{sdd → asdd}/references/model-routing.md +2 -2
  18. package/skills/building-agents/SKILL.md +1 -1
  19. package/skills/clarify/SKILL.md +48 -55
  20. package/skills/clarify/evals/cases.yaml +6 -7
  21. package/skills/connect-tool/SKILL.md +119 -21
  22. package/skills/connect-tool/THIRD_PARTY.md +43 -0
  23. package/skills/connect-tool/bridge-kit/01-TOOLS-plantilla/test_connection.sh +33 -0
  24. package/skills/connect-tool/bridge-kit/LICENSE +27 -0
  25. package/skills/connect-tool/bridge-kit/README.md +48 -0
  26. package/skills/connect-tool/bridge-kit/UPSTREAM.json +8 -0
  27. package/skills/connect-tool/bridge-kit/access/access_a_sqlite.py +82 -0
  28. package/skills/connect-tool/bridge-kit/dbhub/dbhub.toml.ejemplo +65 -0
  29. package/skills/connect-tool/bridge-kit/red/cloudflared-config.yml.ejemplo +18 -0
  30. package/skills/connect-tool/bridge-kit/red/tailscale-grants.hujson +22 -0
  31. package/skills/connect-tool/bridge-kit/sqlserver/01-login-servidor.sql +25 -0
  32. package/skills/connect-tool/bridge-kit/sqlserver/02-usuario-y-vistas.sql +69 -0
  33. package/skills/connect-tool/bridge-kit/sqlserver/03-prueba-solo-lectura.sql +65 -0
  34. package/skills/connect-tool/bridge-kit/sqlserver/copia-nocturna.ps1 +68 -0
  35. package/skills/connect-tool/evals/cases.yaml +41 -4
  36. package/skills/connect-tool/references/connect-or-migrate.md +96 -0
  37. package/skills/connect-tool/references/database-bridge.md +159 -0
  38. package/skills/connect-tool/references/file-exchange.md +103 -0
  39. package/skills/connect-tool/references/messages.md +120 -0
  40. package/skills/connect-tool/references/program-types.md +97 -0
  41. package/skills/connect-tool/references/spain.md +103 -0
  42. package/skills/connect-tool/references/what-to-find-out.md +57 -0
  43. package/skills/constitution/SKILL.md +21 -8
  44. package/skills/constitution/evals/README.md +1 -1
  45. package/skills/debug/SKILL.md +1 -1
  46. package/skills/fastapi/SKILL.md +1 -1
  47. package/skills/flutter/SKILL.md +1 -1
  48. package/skills/ftd/SKILL.md +26 -10
  49. package/skills/ftd/evals/cases.yaml +1 -1
  50. package/skills/ftd/evals/routing.json +4 -4
  51. package/skills/go/SKILL.md +1 -1
  52. package/skills/implement/SKILL.md +126 -113
  53. package/skills/implement/evals/cases.yaml +4 -2
  54. package/skills/implement/references/per-task-review.md +19 -26
  55. package/skills/nextjs/SKILL.md +1 -1
  56. package/skills/parallel/SKILL.md +1 -1
  57. package/skills/plan/SKILL.md +2 -2
  58. package/skills/postgresdb/SKILL.md +1 -1
  59. package/skills/react/SKILL.md +1 -1
  60. package/skills/review/SKILL.md +58 -31
  61. package/skills/sdd-init/SKILL.md +6 -6
  62. package/skills/ship/SKILL.md +38 -18
  63. package/skills/specify/SKILL.md +32 -33
  64. package/skills/specify/evals/cases.yaml +1 -1
  65. package/skills/specify/references/spec-template.md +27 -5
  66. package/skills/suggest/SKILL.md +4 -4
  67. package/skills/tasks/SKILL.md +37 -33
  68. package/skills/tasks/evals/README.md +1 -1
  69. package/skills/tasks/evals/cases.yaml +2 -2
  70. package/skills/verify/SKILL.md +30 -15
  71. package/skills/verify/evals/README.md +2 -2
  72. package/skills/verify/evals/cases.yaml +1 -0
  73. package/skills/worktrees/SKILL.md +1 -1
  74. package/targets/clone-bootstrap.mjs +1 -1
  75. package/targets/hook-once.mjs +2 -2
  76. package/targets/sello.mjs +1 -1
  77. 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.4",
2
+ "version": "4.0.0",
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",
@@ -813,7 +834,7 @@
813
834
  },
814
835
  {
815
836
  "id": "connect-tool",
816
- "description": "Use when the user names a specific tool to connect to the agent (an ERP, accounting or desktop program, CRM, WhatsApp, a spreadsheet, a database): research its real docs, choose the safest route, build `01-TOOLS/<TOOL>/` with a test that proves it, and guide the user. NOT a full typed API client (that is `api-connector-builder`), NOT a whole-workspace tooling scan (that is `harness`).",
837
+ "description": "Use when the user names a specific tool to connect to the agent (an ERP, accounting or desktop program, CRM, WhatsApp, a spreadsheet, a database): research its real docs first, and when none exist, diagnose the program with the user and keep going. Picks the safest route, builds `01-TOOLS/<TOOL>/` with a test that proves it. NOT a full typed API client (that is `api-connector-builder`), NOT a whole-workspace tooling scan (that is `harness`).",
817
838
  "tags": [
818
839
  "connect",
819
840
  "integration",
@@ -825,6 +846,8 @@
825
846
  "mcp",
826
847
  "api",
827
848
  "export",
849
+ "import",
850
+ "file-exchange",
828
851
  "least-privilege",
829
852
  "onboarding"
830
853
  ],
@@ -842,11 +865,12 @@
842
865
  },
843
866
  {
844
867
  "id": "constitution",
845
- "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`).",
846
869
  "tags": [
847
- "sdd",
870
+ "asdd",
848
871
  "constitution",
849
- "principles"
872
+ "principles",
873
+ "review"
850
874
  ],
851
875
  "recommends": [
852
876
  "specify"
@@ -1734,7 +1758,6 @@
1734
1758
  "epistemics"
1735
1759
  ],
1736
1760
  "recommends": [
1737
- "sdd",
1738
1761
  "verify",
1739
1762
  "debug",
1740
1763
  "review"
@@ -1927,7 +1950,7 @@
1927
1950
  },
1928
1951
  {
1929
1952
  "id": "ftd",
1930
- "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.",
1931
1954
  "tags": [
1932
1955
  "ftd",
1933
1956
  "lane",
@@ -2429,14 +2452,16 @@
2429
2452
  },
2430
2453
  {
2431
2454
  "id": "implement",
2432
- "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`).",
2433
2456
  "tags": [
2457
+ "asdd",
2434
2458
  "sdd",
2435
2459
  "implement",
2436
2460
  "code"
2437
2461
  ],
2438
2462
  "recommends": [
2439
- "verify"
2463
+ "verify",
2464
+ "review"
2440
2465
  ],
2441
2466
  "profiles": [
2442
2467
  "core",
@@ -4132,11 +4157,12 @@
4132
4157
  },
4133
4158
  {
4134
4159
  "id": "review",
4135
- "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`).",
4136
4161
  "tags": [
4137
- "sdd",
4162
+ "asdd",
4138
4163
  "review",
4139
- "code-review"
4164
+ "code-review",
4165
+ "risk"
4140
4166
  ],
4141
4167
  "recommends": [
4142
4168
  "ship"
@@ -4339,28 +4365,9 @@
4339
4365
  "full"
4340
4366
  ]
4341
4367
  },
4342
- {
4343
- "id": "sdd",
4344
- "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.",
4345
- "tags": [
4346
- "sdd",
4347
- "spec",
4348
- "workflow",
4349
- "plan"
4350
- ],
4351
- "recommends": [
4352
- "sdd-init",
4353
- "constitution",
4354
- "specify"
4355
- ],
4356
- "profiles": [
4357
- "core",
4358
- "full"
4359
- ]
4360
- },
4361
4368
  {
4362
4369
  "id": "sdd-init",
4363
- "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`).",
4364
4371
  "tags": [
4365
4372
  "sdd",
4366
4373
  "init",
@@ -4369,7 +4376,6 @@
4369
4376
  "registry"
4370
4377
  ],
4371
4378
  "recommends": [
4372
- "sdd",
4373
4379
  "specify",
4374
4380
  "implement",
4375
4381
  "verify"
@@ -4437,12 +4443,13 @@
4437
4443
  },
4438
4444
  {
4439
4445
  "id": "ship",
4440
- "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`).",
4446
+ "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`).",
4441
4447
  "tags": [
4442
- "sdd",
4448
+ "asdd",
4443
4449
  "ship",
4444
4450
  "release",
4445
- "pr"
4451
+ "pr",
4452
+ "risk"
4446
4453
  ],
4447
4454
  "recommends": [],
4448
4455
  "profiles": [
@@ -4689,7 +4696,7 @@
4689
4696
  },
4690
4697
  {
4691
4698
  "id": "specify",
4692
- "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`).",
4699
+ "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`).",
4693
4700
  "tags": [
4694
4701
  "sdd",
4695
4702
  "spec",
@@ -4911,8 +4918,9 @@
4911
4918
  },
4912
4919
  {
4913
4920
  "id": "tasks",
4914
- "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`).",
4921
+ "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`).",
4915
4922
  "tags": [
4923
+ "asdd",
4916
4924
  "sdd",
4917
4925
  "tasks",
4918
4926
  "breakdown"
@@ -5409,8 +5417,9 @@
5409
5417
  },
5410
5418
  {
5411
5419
  "id": "verify",
5412
- "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`).",
5420
+ "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`).",
5413
5421
  "tags": [
5422
+ "asdd",
5414
5423
  "sdd",
5415
5424
  "verify",
5416
5425
  "test"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "3.7.4",
3
+ "version": "4.0.0",
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,185 @@
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
+ export const OUTCOMES = ['fixed', 'false', 'intentional', 'irrelevant'];
18
+ export const MAX_ROUNDS = 3;
19
+ export const EVALS_DIR = '02-DOCS/wiki/sdd/review-evals';
20
+
21
+ // Two findings are the same claim when they sit in the same file and share most of their words, or
22
+ // name the same bug class AND still share some. A class alone is not enough: two different
23
+ // null-check bugs in one file are two findings, and suppressing the second hides a real defect.
24
+ // The line is left out on purpose: it moves between rounds, and a repeat must survive the fix
25
+ // that moved it.
26
+ export const SAME_CLAIM = 0.5;
27
+ export const SAME_CLASS_CLAIM = 0.2;
28
+ 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(' '));
29
+ const words = (s) => new Set(String(s || '').toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 2 && !STOP.has(w)));
30
+ function overlap(a, b) {
31
+ const A = words(a); const B = words(b);
32
+ let shared = 0;
33
+ for (const w of A) if (B.has(w)) shared += 1;
34
+ const union = A.size + B.size - shared;
35
+ return union ? shared / union : 0;
36
+ }
37
+
38
+ const text = (v) => (typeof v === 'string' ? v.trim() : '');
39
+ const normFile = (f) => text(f).replace(/\\/g, '/').replace(/^\.\//, '');
40
+
41
+ // Accepts what the lenses emit and what `review consolidate --json` prints.
42
+ export function normalize(f) {
43
+ return {
44
+ file: normFile(f.file),
45
+ class: text(f.class) || text(f.bug_class) || text(f.defect_class),
46
+ claim: text(f.claim) || (Array.isArray(f.claims) ? text(f.claims[0]) : '') || text(f.title) || text(f.summary),
47
+ severity: text(f.severity) || 'question',
48
+ };
49
+ }
50
+
51
+ export function sameClaim(a, b) {
52
+ if (a.file !== b.file) return false;
53
+ const o = overlap(a.claim, b.claim);
54
+ if (a.class && b.class && a.class.toLowerCase() === b.class.toLowerCase()) return o >= SAME_CLASS_CLAIM;
55
+ return o >= SAME_CLAIM;
56
+ }
57
+
58
+ export function readLedger(root) {
59
+ const path = join(root, LEDGER_PATH);
60
+ if (!existsSync(path)) return [];
61
+ return readFileSync(path, 'utf8').split('\n').filter(Boolean).flatMap((line) => {
62
+ try { return [JSON.parse(line)]; } catch { return []; }
63
+ });
64
+ }
65
+
66
+ function append(root, entries) {
67
+ mkdirSync(join(root, '.rsc'), { recursive: true });
68
+ appendFileSync(join(root, LEDGER_PATH), entries.map((e) => `${JSON.stringify(e)}\n`).join(''));
69
+ }
70
+
71
+ // → [{id, status: 'new'|'repeat', repeatOf?, ...finding}]. A repeat is recorded too (it is the noise
72
+ // being measured) but points at the original, and the caller must not raise it again.
73
+ export function record(root, rawFindings, { task = '', now = new Date().toISOString() } = {}) {
74
+ const ledger = readLedger(root);
75
+ // A finding closed as fixed that shows up again is a regression, not noise: it is raised anew.
76
+ const fixed = new Set(ledger.filter((e) => e.type === 'outcome' && e.outcome === 'fixed').map((e) => e.id));
77
+ const seen = ledger.filter((e) => e.type === 'finding' && !e.repeatOf && !fixed.has(e.id));
78
+ let n = ledger.filter((e) => e.type === 'finding').length;
79
+ const out = [];
80
+ for (const raw of rawFindings) {
81
+ if (!raw || typeof raw !== 'object') continue;
82
+ const f = normalize(raw);
83
+ n += 1;
84
+ const original = seen.find((s) => sameClaim(s, f));
85
+ const entry = { type: 'finding', id: `L${n}`, task, at: now, ...f, ...(original ? { repeatOf: original.id } : {}) };
86
+ if (!original) seen.push(entry);
87
+ out.push(entry);
88
+ }
89
+ append(root, out);
90
+ return out.map((e) => ({ ...e, status: e.repeatOf ? 'repeat' : 'new' }));
91
+ }
92
+
93
+ export function outcome(root, id, value, { now = new Date().toISOString() } = {}) {
94
+ if (!OUTCOMES.includes(value)) {
95
+ return { ok: false, message: `rsc: unknown outcome "${value}". Use one of: ${OUTCOMES.join(', ')}.` };
96
+ }
97
+ if (!readLedger(root).some((e) => e.type === 'finding' && e.id === id)) {
98
+ return { ok: false, message: `rsc: no finding "${id}" in ${LEDGER_PATH}. Run \`npx @ericrisco/rsc review ledger stats\` to see the recorded ids.` };
99
+ }
100
+ append(root, [{ type: 'outcome', id, outcome: value, at: now }]);
101
+ return { ok: true, message: `${id}: ${value}` };
102
+ }
103
+
104
+ // Counts one more review round for a task. The fourth is refused, with the way out in the message
105
+ // (P6): rethink the approach, or park the task in the decision queue and go on with independent work.
106
+ // The task is qualified by its spec (`<slug>/T001`): every plan numbers its tasks from T001, and a
107
+ // bare id would let one feature inherit another's rounds and park work never reviewed.
108
+ export const QUALIFIED_TASK = /^[^/\s]+\/[^/\s]+$/;
109
+ export function round(root, task, { now = new Date().toISOString() } = {}) {
110
+ if (!QUALIFIED_TASK.test(task)) {
111
+ 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.` };
112
+ }
113
+ const n = readLedger(root).filter((e) => e.type === 'round' && e.task === task).length + 1;
114
+ if (n > MAX_ROUNDS) {
115
+ return {
116
+ ok: false, round: n,
117
+ 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.`,
118
+ };
119
+ }
120
+ append(root, [{ type: 'round', task, n, at: now }]);
121
+ return { ok: true, round: n, message: `task "${task}": review round ${n} of ${MAX_ROUNDS}` };
122
+ }
123
+
124
+ export function stats(root) {
125
+ const ledger = readLedger(root);
126
+ const findings = ledger.filter((e) => e.type === 'finding');
127
+ const originals = findings.filter((f) => !f.repeatOf);
128
+ const last = new Map();
129
+ for (const e of ledger) if (e.type === 'outcome') last.set(e.id, e.outcome);
130
+ const outcomes = Object.fromEntries([...OUTCOMES, 'open'].map((k) => [k, 0]));
131
+ for (const f of originals) outcomes[last.get(f.id) || 'open'] += 1;
132
+ const closed = originals.length - outcomes.open;
133
+ const rounds = new Map();
134
+ for (const e of ledger) if (e.type === 'round') rounds.set(e.task, Math.max(rounds.get(e.task) || 0, e.n));
135
+ const totalRounds = [...rounds.values()].reduce((a, b) => a + b, 0);
136
+ return {
137
+ findings: originals.length,
138
+ repeats: findings.length - originals.length,
139
+ outcomes,
140
+ falseRate: closed ? outcomes.false / closed : null,
141
+ tasks: rounds.size,
142
+ meanRounds: rounds.size ? totalRounds / rounds.size : null,
143
+ open: originals.filter((f) => !last.has(f.id)).map((f) => f.id),
144
+ };
145
+ }
146
+
147
+ export function renderStats(s) {
148
+ const pct = (x) => (x === null ? 'n/a' : `${Math.round(x * 100)}%`);
149
+ return [
150
+ `findings: ${s.findings} unique, ${s.repeats} repeat(s) caught`,
151
+ `outcomes: ${OUTCOMES.map((k) => `${k} ${s.outcomes[k]}`).join(' · ')} · open ${s.outcomes.open}`,
152
+ `false rate: ${pct(s.falseRate)} of closed findings`,
153
+ `rounds: ${s.tasks} task(s), mean ${s.meanRounds === null ? 'n/a' : s.meanRounds.toFixed(2)} round(s) per task`,
154
+ ...(s.open.length ? [`still open: ${s.open.join(', ')} — close each with \`review ledger outcome <id> <${OUTCOMES.join('|')}>\``] : []),
155
+ ].join('\n');
156
+ }
157
+
158
+ // Escapes: reverts that reached the trunk. Each one should become a case the reviewer is scored
159
+ // against (the diff, where the bug was, what the review should have said). Only the conventional
160
+ // shapes are visible — `Revert "…"` from git, and a `revert:` commit type — and the output says so.
161
+ const REVERT = /^(?:\S+\s+)?(?:Revert "|revert(?:\([^)]*\))?!?:)/;
162
+ export function escapes(root, { since = '' } = {}) {
163
+ const range = since ? [`${since}..HEAD`] : [];
164
+ const r = spawnSync('git', ['-C', root, 'log', '--format=%H%x00%s', ...range], { encoding: 'utf8', windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
165
+ if (r.status !== 0) return { ok: false, message: `rsc: cannot read git history${since ? ` since ${since}` : ''} (${(r.stderr || '').trim() || 'not a git repository'}).`, reverts: [] };
166
+ let cases = [];
167
+ try { cases = readdirSync(join(root, EVALS_DIR)).filter((f) => f.endsWith('.md')); } catch { cases = []; }
168
+ const reverts = r.stdout.split('\n').filter(Boolean).map((line) => {
169
+ const [sha, subject] = line.split('\0');
170
+ return { sha: sha.slice(0, 7), subject };
171
+ }).filter((c) => REVERT.test(c.subject)).map((c) => ({ ...c, covered: cases.some((f) => f.startsWith(c.sha)) }));
172
+ return { ok: true, reverts };
173
+ }
174
+
175
+ export function renderEscapes(res) {
176
+ if (!res.reverts.length) return 'No reverts found (only `Revert "…"` subjects and `revert:` commits are recognised).';
177
+ const pending = res.reverts.filter((c) => !c.covered).length;
178
+ return [
179
+ ...res.reverts.map((c) => `${c.covered ? 'case ' : 'NO CASE'} ${c.sha} ${c.subject}`),
180
+ '',
181
+ pending
182
+ ? `${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.`
183
+ : 'Every revert has its case.',
184
+ ].join('\n');
185
+ }