@ericrisco/rsc 2.0.19 → 2.1.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 (93) hide show
  1. package/README.md +40 -17
  2. package/manifest.json +18 -85
  3. package/package.json +9 -1
  4. package/scripts/consult.js +8 -8
  5. package/scripts/doctor.js +8 -2
  6. package/scripts/install-apply.js +40 -11
  7. package/scripts/lib/default-skill-floor.js +5 -4
  8. package/scripts/lib/divergence.js +7 -3
  9. package/scripts/lib/domains.js +2 -2
  10. package/scripts/lib/onboarding-apply.js +43 -26
  11. package/scripts/lib/onboarding.js +5 -5
  12. package/scripts/lib/recommend.js +1 -1
  13. package/scripts/lib/repair.js +4 -2
  14. package/scripts/lib/retired-skills.js +20 -0
  15. package/scripts/rsc.js +25 -19
  16. package/scripts/skill-behavior-rubric.md +1 -1
  17. package/scripts/skill-migration-brief.md +3 -3
  18. package/skills/analyze/SKILL.md +3 -8
  19. package/skills/analyze/evals/cases.yaml +1 -1
  20. package/skills/author-skill/SKILL.md +3 -3
  21. package/skills/author-skill/evals/cases.yaml +1 -1
  22. package/skills/author-skill/references/rsc-conventions.md +1 -1
  23. package/skills/clarify/SKILL.md +7 -14
  24. package/skills/clarify/evals/cases.yaml +2 -2
  25. package/skills/code-review/SKILL.md +1 -1
  26. package/skills/constitution/SKILL.md +10 -13
  27. package/skills/constitution/evals/cases.yaml +3 -3
  28. package/skills/continuous-learning/SKILL.md +1 -1
  29. package/skills/continuous-learning/evals/cases.yaml +1 -1
  30. package/skills/debug/SKILL.md +8 -12
  31. package/skills/debug/evals/cases.yaml +1 -1
  32. package/skills/decision-challenge/SKILL.md +1 -1
  33. package/skills/deprecation/SKILL.md +1 -1
  34. package/skills/ftd/SKILL.md +1 -3
  35. package/skills/ftd/evals/cases.yaml +1 -1
  36. package/skills/harness/SKILL.md +10 -9
  37. package/skills/harness/references/claude-md-template.md +1 -1
  38. package/skills/harness/references/wiki-protocol.md +5 -6
  39. package/skills/idea-refinement/SKILL.md +1 -1
  40. package/skills/implement/SKILL.md +14 -18
  41. package/skills/implement/evals/cases.yaml +1 -1
  42. package/skills/init/SKILL.md +26 -44
  43. package/skills/init/evals/README.md +3 -3
  44. package/skills/init/evals/cases.yaml +7 -7
  45. package/skills/init/references/accompaniment-and-profile.md +22 -40
  46. package/skills/init/references/discovery.md +2 -2
  47. package/skills/init/references/recommend-skills.md +5 -5
  48. package/skills/init/scripts/verify.sh +3 -7
  49. package/skills/knowledge-ops/SKILL.md +1 -1
  50. package/skills/orient/SKILL.md +52 -41
  51. package/skills/orient/evals/README.md +11 -9
  52. package/skills/orient/evals/cases.yaml +39 -29
  53. package/skills/orient/references/explain-ladder.md +93 -0
  54. package/skills/orient/references/orientation-contract.md +70 -18
  55. package/skills/parallel/SKILL.md +1 -1
  56. package/skills/parallel/evals/cases.yaml +2 -2
  57. package/skills/plan/SKILL.md +9 -13
  58. package/skills/review/SKILL.md +5 -8
  59. package/skills/roast-me/SKILL.md +1 -1
  60. package/skills/sdd/SKILL.md +10 -14
  61. package/skills/sdd/evals/README.md +2 -2
  62. package/skills/sdd/evals/cases.yaml +4 -4
  63. package/skills/sdd/references/model-routing.md +5 -12
  64. package/skills/sdd-init/SKILL.md +3 -3
  65. package/skills/ship/SKILL.md +6 -11
  66. package/skills/simplify-code/SKILL.md +1 -1
  67. package/skills/skill-scout/SKILL.md +1 -1
  68. package/skills/source-grounded-development/SKILL.md +1 -1
  69. package/skills/specify/SKILL.md +13 -14
  70. package/skills/specify/evals/cases.yaml +1 -1
  71. package/skills/specify/references/eliciting-requirements.md +5 -5
  72. package/skills/suggest/SKILL.md +3 -9
  73. package/skills/suggest/evals/cases.yaml +1 -1
  74. package/skills/tasks/SKILL.md +6 -7
  75. package/skills/tasks/evals/cases.yaml +1 -1
  76. package/skills/unslop/SKILL.md +39 -10
  77. package/skills/unslop/evals/README.md +7 -7
  78. package/skills/unslop/evals/cases.yaml +10 -9
  79. package/skills/verify/SKILL.md +5 -8
  80. package/skills/verify/evals/cases.yaml +1 -1
  81. package/skills/worktrees/SKILL.md +14 -18
  82. package/skills/worktrees/evals/cases.yaml +1 -1
  83. package/targets/clone-bootstrap.mjs +4 -1
  84. package/targets/session-start.mjs +1 -1
  85. package/skills/bro/SKILL.md +0 -76
  86. package/skills/bro/evals/README.md +0 -22
  87. package/skills/bro/evals/cases.yaml +0 -62
  88. package/skills/eli5/SKILL.md +0 -63
  89. package/skills/eli5/evals/README.md +0 -22
  90. package/skills/eli5/evals/cases.yaml +0 -54
  91. package/skills/show-me/SKILL.md +0 -140
  92. package/skills/show-me/evals/README.md +0 -23
  93. package/skills/show-me/evals/cases.yaml +0 -61
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/@ericrisco/rsc?color=63d68a&labelColor=12161c&label=npm)](https://www.npmjs.com/package/@ericrisco/rsc)
8
8
  [![downloads](https://img.shields.io/npm/dm/@ericrisco/rsc?color=63d68a&labelColor=12161c&label=downloads)](https://www.npmjs.com/package/@ericrisco/rsc)
9
- [![skills](https://img.shields.io/badge/skills-273-63d68a?labelColor=12161c)](#the-catalog)
9
+ [![skills](https://img.shields.io/badge/skills-270-63d68a?labelColor=12161c)](#the-catalog)
10
10
  [![license](https://img.shields.io/badge/license-MIT-63d68a?labelColor=12161c)](LICENSE)
11
11
  [![stars](https://img.shields.io/github/stars/ericrisco/rsc-harness?color=63d68a&labelColor=12161c)](https://github.com/ericrisco/rsc-harness/stargazers)
12
12
 
@@ -42,8 +42,8 @@ Open standards already cover individual parts — [Agent Skills](https://agentsk
42
42
  which complete harness this project needs. rsc-harness does that job through one guided flow:
43
43
 
44
44
  1. **You state the outcome.** No skill, hook or MCP vocabulary required.
45
- 2. **The wizard reads only the project root** and asks technical level, accompaniment, project kind,
46
- goal and assistants.
45
+ 2. **The wizard reads only the project root** and asks how technical to talk, project kind, goal and
46
+ assistants.
47
47
  3. **A proportional plan explains every choice.** Selected, deferred and excluded pieces all have a
48
48
  reason. Nothing writes before you accept the exact plan.
49
49
  4. **The result is verified.** Shared sources prevent duplication; knowledge loads progressively;
@@ -66,8 +66,8 @@ project they're equipping** — not inside a clone of this repo (see the note un
66
66
  npx @ericrisco/rsc@latest onboard
67
67
  ```
68
68
 
69
- That launches the binding plain-language onboarding. It asks the user for technical level,
70
- accompaniment, project kind and goal **before it writes anything**, reads only the selected project
69
+ That launches the binding plain-language onboarding. It asks the user how technical to talk
70
+ (technical or with analogies), project kind and goal **before it writes anything**, reads only the selected project
71
71
  root, and shows every selected or deferred skill, agent and hook with its reason. Relay those
72
72
  questions to the user; never invent or default their answers. The first pass writes nothing and
73
73
  prints a SHA-256 plan id. Only rerun with `--accept-plan <id>` after the user accepts that exact plan.
@@ -78,11 +78,11 @@ regenerating the plan keeps theirs.
78
78
  For a non-interactive agent, collect the answers and preview the same plan explicitly:
79
79
 
80
80
  ```bash
81
- npx @ericrisco/rsc@latest onboard --technical-level mixed --accompaniment L1 \
81
+ npx @ericrisco/rsc@latest onboard --technical-level technical \
82
82
  --project-kind software --goal "small compound-interest website" \
83
83
  --software-scope small --target codex
84
84
  # After the user accepts the printed plan:
85
- npx @ericrisco/rsc@latest onboard --technical-level mixed --accompaniment L1 \
85
+ npx @ericrisco/rsc@latest onboard --technical-level technical \
86
86
  --project-kind software --goal "small compound-interest website" \
87
87
  --software-scope small --target codex --accept-plan PRINTED_SHA256_ID
88
88
  ```
@@ -131,6 +131,24 @@ is which one you land in by default, and who decides.
131
131
 
132
132
  ---
133
133
 
134
+ ## 🗣️ It talks so you understand
135
+
136
+ Agents assume you saw their reasoning, and they write too much. rsc fixes the voice, not the reader.
137
+
138
+ - **Short sentences, one idea each.** The style comes from ASD-STE100, the controlled writing of
139
+ aircraft maintenance manuals, applied at about 80% and in your language.
140
+ - **Every answer stands alone.** No "that fix" or "the second commit": the answer names the thing,
141
+ with the minimum context to understand it.
142
+ - **One question at install:** technical, or with analogies. That is the only setting.
143
+ - **A ladder when you do not follow.** Text first. Then one diagram. Then one HTML page. A video
144
+ only if you ask for it.
145
+
146
+ Every turn still closes with the compass: where you are, and the next step as a question. The voice
147
+ lives in `orient`, which every harness installs. Text you send to other people (emails, posts,
148
+ READMEs) goes through `unslop` instead: it sounds like a person and carries no AI tells.
149
+
150
+ ---
151
+
134
152
  ## Why this exists
135
153
 
136
154
  An improvised harness tends to grow by accumulation: more files, more context and more rules, with
@@ -211,7 +229,7 @@ cd ~/rsc-skills && npm install && npm link
211
229
  > `node scripts/rsc.js …`, the `npm link` above, or pin the published build with
212
230
  > `npx @ericrisco/rsc@latest …`.
213
231
 
214
- The first run asks **how technical the conversation should be**, the accompaniment level, what the
232
+ The first run asks **one** question about conversation: technical, or with analogies. Then what the
215
233
  project is for, its goal and the assistants to target. It then presents the complete plan. A small
216
234
  website can defer SDD, agents and code guards; an operations harness does not receive them merely
217
235
  because it lives in a repository. Deferred components record the evidence that would make rsc
@@ -235,10 +253,9 @@ $ rsc onboard
235
253
  ██████╗ ███████╗ ██████╗ ← animated gradient wordmark
236
254
  ██╔══██╗██╔════╝██╔════╝
237
255
  ██████╔╝███████╗██║
238
- 273 skills · one CLI · zero bloat
256
+ 270 skills · one CLI · zero bloat
239
257
 
240
- How technical should the conversation be?
241
- How much accompaniment do you want?
258
+ How should I talk to you?
242
259
  What are you building or running?
243
260
  What do you want this project to achieve?
244
261
 
@@ -404,6 +421,11 @@ Running through `npx` (no global install)? There's nothing to upgrade —
404
421
  `npx @ericrisco/rsc@latest` always fetches the latest published catalog; just run
405
422
  `rsc sync` afterwards if the project already has skills installed.
406
423
 
424
+ **From 2.0.x to 2.1.0:** `eli5` and `show-me` became part of `orient`, and `bro` became part of
425
+ `unslop`. The update removes the three old skills (only the copies rsc installed) and installs the
426
+ ones that replace them. An `accompaniment_level` line in your profile is ignored: the only setting
427
+ now is `technical_level`.
428
+
407
429
  Every sync snapshots the project first, so a bad update is always reversible:
408
430
 
409
431
  ```bash
@@ -434,20 +456,21 @@ just asks in plain language.
434
456
 
435
457
  ## The catalog
436
458
 
437
- 273 skills, grouped by what you're trying to do. Click any skill to read its
459
+ 270 skills, grouped by what you're trying to do. Click any skill to read its
438
460
  `SKILL.md`. It fires on its own when a task matches.
439
461
 
440
462
  ### 🧭 Core & control plane
441
463
  The front door and the workspace brain.
442
464
 
443
- [init](skills/init/) · [harness](skills/harness/) · [orient](skills/orient/) · [suggest](skills/suggest/) · [bro](skills/bro/) · [unslop](skills/unslop/) · [author-skill](skills/author-skill/) · [sdd-init](skills/sdd-init/)
465
+ [init](skills/init/) · [harness](skills/harness/) · [orient](skills/orient/) · [suggest](skills/suggest/) · [unslop](skills/unslop/) · [author-skill](skills/author-skill/) · [sdd-init](skills/sdd-init/)
444
466
 
445
467
  > **harness** is the Karpathy *chaos→knowledge* engine — a `01-TOOLS/` layer (one
446
468
  > folder per provider, each with a working `test_connection`) and a `02-DOCS/`
447
469
  > self-improving wiki. It governs software *or* a whole company. **orient** is the
448
- > always-on compass that keeps a non-technical human oriented after every step.
449
- > **bro** is installed with every profile and rewrites any answer in plain, natural
450
- > language when the user asks — without making its full body always-on.
470
+ > always-on voice: short STE-style sentences, every answer understandable on its own,
471
+ > technical or with analogies, and a diagram or an HTML page when you do not follow.
472
+ > **unslop** is installed with every profile and makes text you send to others sound
473
+ > like a person, with no AI tells.
451
474
 
452
475
  <img src="https://raw.githubusercontent.com/ericrisco/rsc-harness/main/site/company-brain.svg" alt="The rsc company brain as a 3D knowledge graph: loose material drifts in from the inbox on the left and is absorbed into the bright green wiki cluster, whose brightest hubs are its .base views; dim clusters behind are raw sources and the agent's own captured worklog; an amber cluster is gaps.md, what the wiki knows it is missing." width="960">
453
476
 
@@ -571,7 +594,7 @@ Three engines + engine-agnostic disciplines. Every engine skill pins the current
571
594
 
572
595
  ### 🧠 Knowledge & meta
573
596
 
574
- [knowledge-ops](skills/knowledge-ops/) · [codebase-onboarding](skills/codebase-onboarding/) · [research-ops](skills/research-ops/) · [decision-records](skills/decision-records/) · [continuous-learning](skills/continuous-learning/) · [skill-scout](skills/skill-scout/) · [context-budget](skills/context-budget/) · [roast-me](skills/roast-me/) · [show-me](skills/show-me/) · [eli5](skills/eli5/) · [fable-operator](skills/fable-operator/)
597
+ [knowledge-ops](skills/knowledge-ops/) · [codebase-onboarding](skills/codebase-onboarding/) · [research-ops](skills/research-ops/) · [decision-records](skills/decision-records/) · [continuous-learning](skills/continuous-learning/) · [skill-scout](skills/skill-scout/) · [context-budget](skills/context-budget/) · [roast-me](skills/roast-me/) · [fable-operator](skills/fable-operator/)
575
598
 
576
599
  ---
577
600
 
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "2.0.19",
2
+ "version": "2.1.0",
3
3
  "counts": {
4
- "skills": 273,
4
+ "skills": 270,
5
5
  "agents": 33,
6
6
  "commands": 53,
7
7
  "fixedCommands": 20
@@ -461,30 +461,6 @@
461
461
  ],
462
462
  "profiles": []
463
463
  },
464
- {
465
- "id": "bro",
466
- "description": "Use whenever the user asks to make an answer or draft sound human, natural, plain-spoken or less AI-written — including a terse bro after the last response. Rewrites or drafts in the same language while preserving meaning, facts and channel. NOT a reusable brand voice (that is `brand-voice`), NOT translation (that is `translation-l10n`).",
467
- "tags": [
468
- "bro",
469
- "human-writing",
470
- "humano",
471
- "plain-language",
472
- "natural-language",
473
- "rewrite",
474
- "no-jargon"
475
- ],
476
- "recommends": [
477
- "unslop",
478
- "brand-voice",
479
- "technical-writing",
480
- "translation-l10n"
481
- ],
482
- "profiles": [
483
- "minimal",
484
- "core",
485
- "full"
486
- ]
487
- },
488
464
  {
489
465
  "id": "building-agents",
490
466
  "description": "Use when building or restructuring an LLM agent — provider adapter, tool calling, structured output, RAG, agent loop, eval gate, cost routing, tracing, MCP server — model-agnostic across OpenAI/Anthropic/Gemini/OSS so a model swap is a config change. NOT vector-store SQL alone (that is `postgresdb`) or service deployment (that is `deployment`).",
@@ -1599,29 +1575,6 @@
1599
1575
  ],
1600
1576
  "profiles": []
1601
1577
  },
1602
- {
1603
- "id": "eli5",
1604
- "description": "Use when a topic must be explained from zero to someone who knows nothing about it: one page, big pictures, very few words, everyday analogies, no jargon and nothing assumed. NOT the smallest in-conversation visual for someone already following (that is `show-me`), NOT a course with exercises (that is `course-builder`).",
1605
- "tags": [
1606
- "eli5",
1607
- "explain",
1608
- "beginner",
1609
- "simple",
1610
- "analogy",
1611
- "no-jargon",
1612
- "explicame"
1613
- ],
1614
- "recommends": [
1615
- "show-me",
1616
- "technical-writing",
1617
- "course-builder"
1618
- ],
1619
- "profiles": [
1620
- "minimal",
1621
- "core",
1622
- "full"
1623
- ]
1624
- },
1625
1578
  {
1626
1579
  "id": "elixir",
1627
1580
  "description": "Use when writing or refactoring Elixir/OTP on the BEAM — GenServers, supervision trees and restart strategies, pattern matching, mix projects and releases — or when processes misbehave (restart loops, mailbox growth, call timeouts). NOT a Phoenix web app, LiveView, Ecto or channels (that is `phoenix`).",
@@ -2475,7 +2428,7 @@
2475
2428
  },
2476
2429
  {
2477
2430
  "id": "init",
2478
- "description": "Use when starting from nothing or pointing rsc at an existing project — the front door. Gauges technical level and the accompaniment dial first (non-technical by default), discovers what the user wants to build or govern (any stack, or a non-code harness: company/ops, research, knowledge, content), writes the profile to 02-DOCS, and installs the skills discovery justified. NOT the scaffolder (that is `harness`), NOT a stack skill.",
2431
+ "description": "Use when starting from nothing or pointing rsc at an existing project — the front door. First asks one question — technical terms or analogies (analogies by default) — then discovers what the user wants to build or govern (any stack, or a non-code harness: company/ops, research, knowledge, content), writes the profile to 02-DOCS, and installs the skills discovery justified. NOT the scaffolder (that is `harness`), NOT a stack skill.",
2479
2432
  "tags": [
2480
2433
  "init",
2481
2434
  "bootstrap",
@@ -3448,13 +3401,16 @@
3448
3401
  },
3449
3402
  {
3450
3403
  "id": "orient",
3451
- "description": "Always-on. The brújula: close every turn by situating the person — where they are, what just happened, why it mattered, and the next step as a question, never a dead end. Reads the accompaniment and technical dials from 02-DOCS and calibrates how much it explains, rewriting the dial when the user asks for more or less. NOT the missing-skill installer (that is `suggest`).",
3404
+ "description": "Always-on. How the harness talks to the person: STE-style short sentences, every answer understandable on its own, technical or with analogies by the profile, and a ladder (text → diagram → HTML page → video on request) when the person does not follow. Closes every turn with the brújula. Fires on 'no entiendo', 'explícamelo', 'enséñame', 'dibújamelo', 'explain it from zero', '¿dónde estamos?'. NOT the missing-skill installer (that is `suggest`), NOT a text the user sends to others (that is `unslop`).",
3452
3405
  "tags": [
3453
3406
  "orient",
3454
- "guide",
3407
+ "voice",
3408
+ "ste",
3409
+ "explain",
3410
+ "diagram",
3411
+ "eli5",
3412
+ "show-me",
3455
3413
  "compass",
3456
- "dial",
3457
- "meta",
3458
3414
  "always-on"
3459
3415
  ],
3460
3416
  "recommends": [],
@@ -4316,7 +4272,7 @@
4316
4272
  },
4317
4273
  {
4318
4274
  "id": "sdd",
4319
- "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 accompaniment dial 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.",
4275
+ "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.",
4320
4276
  "tags": [
4321
4277
  "sdd",
4322
4278
  "spec",
@@ -4542,31 +4498,6 @@
4542
4498
  ],
4543
4499
  "profiles": []
4544
4500
  },
4545
- {
4546
- "id": "show-me",
4547
- "description": "Use when the user needs to see how something works instead of reading about it: picks the smallest visual that carries the point — pseudocode, call tree, file tree, component tree, Mermaid, a shaped diff, or one HTML page. NOT visual identity (that is `design-loop`), NOT slides (that is `presentations`), NOT a from-zero explainer (that is `eli5`).",
4548
- "tags": [
4549
- "show-me",
4550
- "explain",
4551
- "diagram",
4552
- "visual",
4553
- "mermaid",
4554
- "diff",
4555
- "call-tree",
4556
- "ensename"
4557
- ],
4558
- "recommends": [
4559
- "eli5",
4560
- "presentations",
4561
- "technical-writing",
4562
- "codebase-onboarding"
4563
- ],
4564
- "profiles": [
4565
- "minimal",
4566
- "core",
4567
- "full"
4568
- ]
4569
- },
4570
4501
  {
4571
4502
  "id": "simplify-code",
4572
4503
  "description": "Use when correct working code is unnecessarily hard to read, change or verify and needs behaviour-preserving simplification in a bounded scope. Characterizes the observable contract, removes accidental complexity one concept at a time and proves equivalence. NOT fixing a failure (`debug`), NOT read-only diff judgment (`review`/`code-review`).",
@@ -5264,18 +5195,20 @@
5264
5195
  },
5265
5196
  {
5266
5197
  "id": "unslop",
5267
- "description": "Use when a written text must be audited against a named catalogue of AI tells before it ships: puffery, `not just X but Y`, the rule of three, em dashes, filler, hedging, abstract metaphor nouns. Names each hit and fixes it. NOT the register rewrite in the user's own voice (that is `bro`), NOT a reusable voice guide (that is `brand-voice`).",
5198
+ "description": "Use when a text that leaves the session (email, post, README, message, landing copy) must sound like a person and carry no AI tells: rewrites it in the user's language fitted to the channel and relationship, and audits it against a named catalogue of tells (puffery, `not just X but Y`, rule of three, em dashes, filler, hedging). Fires on 'bro', 'hazlo más humano', 'que no suene a IA', 'más natural', 'unslop this'. NOT how the harness talks to the user (that is `orient`), NOT a reusable voice guide (that is `brand-voice`).",
5268
5199
  "tags": [
5269
5200
  "unslop",
5201
+ "human-writing",
5202
+ "humano",
5203
+ "natural-language",
5204
+ "plain-language",
5270
5205
  "ai-tells",
5271
5206
  "editing",
5272
- "audit",
5273
- "slop",
5274
- "publish-check",
5207
+ "rewrite",
5208
+ "bro",
5275
5209
  "revisar-texto"
5276
5210
  ],
5277
5211
  "recommends": [
5278
- "bro",
5279
5212
  "brand-voice",
5280
5213
  "technical-writing"
5281
5214
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "2.0.19",
3
+ "version": "2.1.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": {
@@ -105,6 +105,14 @@
105
105
  {
106
106
  "fixedIn": "2.0.19",
107
107
  "symptom": "rsc doctor reported missing pieces or an unwired hook but still exited with 0, so a CI or an editor extension could not tell"
108
+ },
109
+ {
110
+ "fixedIn": "2.0.20",
111
+ "symptom": "In a project whose path contains a space, rsc doctor reported every hook script as missing and exited 1, though the scripts were there and the hooks ran"
112
+ },
113
+ {
114
+ "fixedIn": "2.1.0",
115
+ "symptom": "Answers were long and leaned on hidden reasoning, and onboarding asked an accompaniment level few could answer; now: short stand-alone sentences, one question"
108
116
  }
109
117
  ]
110
118
  }
@@ -43,14 +43,14 @@ const SYNONYMS = {
43
43
  presentacion: ['presentations'], 'presentación': ['presentations'], diapositivas: ['slides'],
44
44
  curso: ['course', 'teaching'], 'enseñar': ['teaching'], ensenar: ['teaching'],
45
45
  marketing: ['marketing'], copy: ['copywriting'], texto: ['copywriting'], textos: ['copywriting'],
46
- human: ['bro', 'human-writing', 'natural-language', 'plain-language'],
47
- humano: ['bro', 'human-writing', 'natural-language', 'plain-language'],
48
- humana: ['bro', 'human-writing', 'natural-language', 'plain-language'],
49
- huma: ['bro', 'human-writing', 'natural-language', 'plain-language'],
50
- natural: ['bro', 'human-writing', 'natural-language', 'plain-language'],
51
- robotico: ['bro', 'human-writing'], robotica: ['bro', 'human-writing'],
52
- robotic: ['bro', 'human-writing'], roboticamente: ['bro', 'human-writing'],
53
- jerga: ['bro', 'plain-language', 'no-jargon'], jargon: ['bro', 'plain-language', 'no-jargon'],
46
+ human: ['unslop', 'human-writing', 'natural-language', 'plain-language'],
47
+ humano: ['unslop', 'human-writing', 'natural-language', 'plain-language'],
48
+ humana: ['unslop', 'human-writing', 'natural-language', 'plain-language'],
49
+ huma: ['unslop', 'human-writing', 'natural-language', 'plain-language'],
50
+ natural: ['unslop', 'human-writing', 'natural-language', 'plain-language'],
51
+ robotico: ['unslop', 'human-writing'], robotica: ['unslop', 'human-writing'],
52
+ robotic: ['unslop', 'human-writing'], roboticamente: ['unslop', 'human-writing'],
53
+ jerga: ['unslop', 'plain-language', 'no-jargon'], jargon: ['unslop', 'plain-language', 'no-jargon'],
54
54
  // Cross-cutting workflow vocabulary. These map multilingual/colloquial
55
55
  // wording to the exact compound tags used by the catalog.
56
56
  challenge: ['adversarial-review', 'decision-quality', 'preflight'], challenged: ['adversarial-review'],
package/scripts/doctor.js CHANGED
@@ -65,6 +65,10 @@ function selloStatus(root) {
65
65
  //
66
66
  // Hookless targets get an empty list by construction: their always-on surface is a
67
67
  // markdown block with no script behind it, so there is nothing that can go missing.
68
+ // A quoted span counts as a path only if it starts like one and holds no shell operator: a whole
69
+ // `sh -c '…'` snippet is not a path, and reading it as one reports a present script missing.
70
+ const SCRIPT_RE =
71
+ /"((?:[A-Za-z]:)?[\\/~][^"&;|]*[\\/]\.rsc[\\/][^"\s]+?\.mjs)"|'((?:[A-Za-z]:)?[\\/~][^'&;|]*[\\/]\.rsc[\\/][^'\s]+?\.mjs)'|([^"'\s]*[\\/]\.rsc[\\/][^"'\s]+\.mjs)/g;
68
72
  export function missingHookScripts({ target, home = homedir(), cwd = process.cwd() } = {}) {
69
73
  if (HOOKLESS_TARGETS.has(target)) return [];
70
74
  const file = targetPaths(target, home, cwd).hookTarget;
@@ -84,8 +88,10 @@ export function missingHookScripts({ target, home = homedir(), cwd = process.cwd
84
88
  const expand = (c) => c.split('${CLAUDE_PROJECT_DIR}').join(cwd);
85
89
  for (const raw of commands) {
86
90
  const cmd = expand(raw);
87
- for (const m of cmd.matchAll(/["']?([^"'\s]*[\\/]\.rsc[\\/][^"'\s]+\.mjs)["']?/g)) {
88
- const script = m[1];
91
+ // Quoted forms first: a project path may hold a space, and stopping at whitespace kept only
92
+ // the tail (`entreno/.rsc/x.mjs`), which then resolved against the cwd and read as missing (#283).
93
+ for (const m of cmd.matchAll(SCRIPT_RE)) {
94
+ const script = m[1] ?? m[2] ?? m[3];
89
95
  if (!existsSync(script)) seen.add(script);
90
96
  }
91
97
  }
@@ -13,6 +13,7 @@ import {
13
13
  import { projectOptOuts } from '../targets/opt-outs.js';
14
14
  import { readState, writeState } from './lib/state.js';
15
15
  import { withDefaultSkillFloor } from './lib/default-skill-floor.js';
16
+ import { isRetired, replaceRetired } from './lib/retired-skills.js';
16
17
  import { readManifest, writeManifest } from './lib/manifest-file.js';
17
18
  import { createBackup } from './lib/backups.js';
18
19
  import {
@@ -196,7 +197,9 @@ export function recordInManifest({ cwd, target, skillIds, agentIds = [], catalog
196
197
  // `dropTarget` is the one exception — a MOVE, where leaving the old one declared would
197
198
  // have every clone rebuild a harness that was deliberately abandoned.
198
199
  targets: union(prev.targets, [target]).filter((t) => t !== dropTarget),
199
- skills: union(prev.skills, skillIds),
200
+ // Through the retirement map: the living list must stop naming a skill the catalog no longer
201
+ // ships, or every clone would go looking for it.
202
+ skills: replaceRetired(union(prev.skills, skillIds)).sort(),
200
203
  agents: union(prev.agents, agentIds),
201
204
  ownSkills: prev.ownSkills || [],
202
205
  catalogVersion,
@@ -215,7 +218,23 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
215
218
  const fromScratch = !existsSync(join(cwd, '.rsc'));
216
219
  const paths = targetPaths(target, home, cwd);
217
220
  const plan = planInstall({ skillIds, target, home, cwd, hooks: policy?.alwaysOn !== false });
218
- const managedPaths = managedPathsForInstall({ skillIds, agentIds, target, home, cwd, policy });
221
+ // What this pass prunes, decided BEFORE the backup so the backup holds it: a skill link this
222
+ // target's state proves is ours and that is no longer wanted (always for a retired skill, and for
223
+ // any skill when a plan governs), and the shared base of a retired skill once no other assistant
224
+ // in this project still records it. Taking a base another assistant links to would leave that
225
+ // assistant dangling until its own sync.
226
+ const priorState = readState(paths.stateFile);
227
+ const prunedSkills = Object.keys(priorState.skills || {})
228
+ .filter((id) => !skillIds.includes(id) && (policy || isRetired(id)));
229
+ const stillRecordedElsewhere = (id) => TARGET_IDS.some((other) => other !== target
230
+ && Object.hasOwn(readState(targetPaths(other, home, cwd).stateFile).skills || {}, id));
231
+ const prunedBases = Object.keys(readBaseVersions(cwd))
232
+ .filter((id) => isRetired(id) && !skillIds.includes(id) && !stillRecordedElsewhere(id));
233
+ const managedPaths = [...new Set([
234
+ ...managedPathsForInstall({ skillIds, agentIds, target, home, cwd, policy }),
235
+ ...prunedSkills.map((id) => paths.skillDir(id)),
236
+ ...prunedBases.map((id) => baseDir(id, cwd)),
237
+ ])];
219
238
  if (dryRun) return { dryRun: true, skills: skillIds, agents: agentIds, paths: managedPaths };
220
239
  // Rebuild the declared decisions first, so everything below (the agent tier especially) reads
221
240
  // the state the team declared rather than the default it would otherwise assume.
@@ -227,12 +246,17 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
227
246
  // syncs — a single global marker would be bumped by the first target and make later
228
247
  // targets skip refreshing their exclusive skills' bases.
229
248
  const baseVersions = readBaseVersions(cwd);
230
- if (policy) {
231
- for (const id of Object.keys(state.skills || {})) {
232
- if (skillIds.includes(id)) continue;
233
- rmSync(paths.skillDir(id), { recursive: true, force: true });
234
- delete state.skills[id];
235
- }
249
+ // A retired skill is pruned even without a governing plan: nothing can install it any more, so
250
+ // keeping it would only leave a dead copy behind. Ownership is proven by the state file — a
251
+ // same-named directory rsc never recorded is somebody's own work and is not touched.
252
+ for (const id of prunedSkills) {
253
+ rmSync(paths.skillDir(id), { recursive: true, force: true });
254
+ delete state.skills[id];
255
+ }
256
+ // Its shared base too, proven ours by the base-version ledger rather than by its name.
257
+ for (const id of prunedBases) {
258
+ rmSync(baseDir(id, cwd), { recursive: true, force: true });
259
+ delete baseVersions[id];
236
260
  }
237
261
  for (const step of plan) {
238
262
  if (step.kind === 'skill') {
@@ -528,7 +552,9 @@ export function removeTargetInstall({ target, home, cwd = process.cwd() }) {
528
552
  ];
529
553
  const declaredLocationsAreExact = Object.entries(state.skills || {}).every(([id, entry]) =>
530
554
  /^[a-z0-9][a-z0-9._-]*$/i.test(id)
531
- && existsSync(join(ROOT, 'skills', id))
555
+ // A retired id no longer has a catalog directory, but `paths.skillDir` below still proves where
556
+ // it lives — refusing it made every re-onboard that drops an old assistant loop on recovery.
557
+ && (existsSync(join(ROOT, 'skills', id)) || isRetired(id))
532
558
  && (entry.files || []).every((file) => resolve(file) === resolve(paths.skillDir(id))))
533
559
  && (state.agents || []).every((name) => {
534
560
  if (!allAgentNames().includes(name)) return false;
@@ -607,9 +633,12 @@ export async function syncInstalled({ target, home, cwd = process.cwd(), dryRun
607
633
  // then rebuilt from a governed list that predated it and pruned the difference, recursively.
608
634
  // Reported by a user and reproduced 2026-09-17. The receipt stays untouched, because it is
609
635
  // hash-checked against what the user accepted and is not a log of what happened since.
610
- const declaredRaw = governedSkills
636
+ //
637
+ // Read through the retirement map: a declaration older than the retirement of eli5, show-me and
638
+ // bro names skills the catalog no longer ships, and the receipt that names them is never rewritten.
639
+ const declaredRaw = replaceRetired(governedSkills
611
640
  ? [...new Set([...governedSkills, ...(manifest?.skills || [])])].sort()
612
- : (ids.length ? ids : (manifest?.skills || []));
641
+ : (ids.length ? ids : (manifest?.skills || []))).sort();
613
642
  // A declaration is frozen at the moment it was written, so a skill the catalog later makes
614
643
  // mandatory never reaches an existing harness: `add` and `install` apply the floor, sync did
615
644
  // not, and sync is the only one an upgrade runs. 2.0.0 shipped the three-lane decisor inside
@@ -1,15 +1,16 @@
1
1
  // Skills every installation path equips, even when the user picks a profile,
2
2
  // adds one skill directly, or browses the catalog by hand.
3
3
  //
4
- // `orient` and `suggest` are runtime always-on. `bro` is installed by default
5
- // but remains conditionally loaded: its description routes human-language asks
6
- // without paying for its body on unrelated turns.
4
+ // `orient` and `suggest` are runtime always-on. `unslop` is installed by default
5
+ // but remains conditionally loaded: its description routes "rewrite this for
6
+ // other people" asks without paying for its body on unrelated turns. It took the
7
+ // place of `bro`, which was retired into it (see retired-skills.js).
7
8
  //
8
9
  // `ftd` joined in 2.0.1 by the same criterion as `suggest`: the always-on decisor
9
10
  // routes ordinary work to `../ftd/SKILL.md`, so a harness without it has a default
10
11
  // lane that points at nothing. Membership of every profile was not enough — a
11
12
  // declaration written before the skill existed never gains it (see `syncInstalled`).
12
- export const DEFAULT_SKILL_FLOOR = Object.freeze(['orient', 'suggest', 'bro', 'ftd']);
13
+ export const DEFAULT_SKILL_FLOOR = Object.freeze(['orient', 'suggest', 'unslop', 'ftd']);
13
14
 
14
15
  export function withDefaultSkillFloor(skillIds = []) {
15
16
  return [...new Set([...DEFAULT_SKILL_FLOOR, ...skillIds])];
@@ -3,6 +3,7 @@ import { readState } from './state.js';
3
3
  import { targetPaths } from '../../targets/index.js';
4
4
  import { existsSync } from 'node:fs';
5
5
  import { DEFAULT_SKILL_FLOOR } from './default-skill-floor.js';
6
+ import { replaceRetired } from './retired-skills.js';
6
7
 
7
8
  // What the team declared versus what is on this machine.
8
9
  //
@@ -28,13 +29,16 @@ export function divergence({ cwd = process.cwd(), target, home } = {}) {
28
29
 
29
30
  const paths = targetPaths(target, home, cwd);
30
31
  const installed = new Set(Object.keys(readState(paths.stateFile).skills || {}));
32
+ // A declaration written before eli5, show-me and bro were retired names them; it is read as
33
+ // their successors, so a teammate who has not synced yet is not told to install a ghost.
34
+ const declared = replaceRetired(manifest.skills || []);
31
35
 
32
36
  return {
33
- missing: (manifest.skills || []).filter((id) => !installed.has(id)),
34
- extra: [...installed].filter((id) => !(manifest.skills || []).includes(id)),
37
+ missing: declared.filter((id) => !installed.has(id)),
38
+ extra: [...installed].filter((id) => !declared.includes(id)),
35
39
  ownMissing: (manifest.ownSkills || []).filter((name) => !existsSync(paths.skillDir(name))),
36
40
  // Read off the declaration, not off the disk: a skill installed by hand into a harness
37
41
  // that still does not declare it is one sync away from being pruned again.
38
- floorMissing: DEFAULT_SKILL_FLOOR.filter((id) => !(manifest.skills || []).includes(id)),
42
+ floorMissing: DEFAULT_SKILL_FLOOR.filter((id) => !declared.includes(id)),
39
43
  };
40
44
  }
@@ -4,7 +4,7 @@
4
4
  // catalog grows deliberately, never silently.
5
5
 
6
6
  export const DOMAINS = [
7
- { title: 'Core & control plane', ids: ['init', 'harness', 'orient', 'suggest', 'bro', 'unslop', 'author-skill', 'sdd-init'] },
7
+ { title: 'Core & control plane', ids: ['init', 'harness', 'orient', 'suggest', 'unslop', 'author-skill', 'sdd-init'] },
8
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'] },
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'] },
@@ -26,7 +26,7 @@ export const DOMAINS = [
26
26
  { title: 'Ship & operate — quality & security', ids: ['code-review', 'security-scan', 'secure-coding', 'testing-py', 'testing-web', 'testing-go', 'e2e-testing', 'accessibility', 'performance', 'error-handling', 'observability'] },
27
27
  { title: 'Motion & interface craft', ids: ['motion-craft', 'ui-engineering', 'variant-explorer'] },
28
28
  { title: 'Design & content craft', ids: ['design-loop', 'design', 'design-dna', 'presentations', 'course-storytelling', 'course-builder', 'technical-writing', 'translation-l10n'] },
29
- { title: 'Knowledge & meta', ids: ['knowledge-ops', 'codebase-onboarding', 'research-ops', 'decision-records', 'continuous-learning', 'skill-scout', 'context-budget', 'roast-me', 'show-me', 'eli5', 'fable-operator'] },
29
+ { title: 'Knowledge & meta', ids: ['knowledge-ops', 'codebase-onboarding', 'research-ops', 'decision-records', 'continuous-learning', 'skill-scout', 'context-budget', 'roast-me', 'fable-operator'] },
30
30
  ];
31
31
 
32
32
  export function allDomainIds() {