@ericrisco/rsc 2.0.20 → 2.2.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 (97) hide show
  1. package/README.md +63 -21
  2. package/manifest.json +18 -85
  3. package/package.json +5 -1
  4. package/scripts/consult.js +8 -8
  5. package/scripts/install-apply.js +48 -11
  6. package/scripts/lib/default-skill-floor.js +5 -4
  7. package/scripts/lib/divergence.js +7 -3
  8. package/scripts/lib/domains.js +2 -2
  9. package/scripts/lib/onboarding-apply.js +43 -26
  10. package/scripts/lib/onboarding.js +5 -5
  11. package/scripts/lib/recommend.js +1 -1
  12. package/scripts/lib/repair.js +4 -2
  13. package/scripts/lib/retired-skills.js +20 -0
  14. package/scripts/rsc.js +25 -19
  15. package/scripts/skill-behavior-rubric.md +1 -1
  16. package/scripts/skill-migration-brief.md +3 -3
  17. package/skills/analyze/SKILL.md +3 -8
  18. package/skills/analyze/evals/cases.yaml +1 -1
  19. package/skills/author-skill/SKILL.md +3 -3
  20. package/skills/author-skill/evals/cases.yaml +1 -1
  21. package/skills/author-skill/references/rsc-conventions.md +1 -1
  22. package/skills/clarify/SKILL.md +7 -14
  23. package/skills/clarify/evals/cases.yaml +2 -2
  24. package/skills/code-review/SKILL.md +1 -1
  25. package/skills/constitution/SKILL.md +10 -13
  26. package/skills/constitution/evals/cases.yaml +3 -3
  27. package/skills/continuous-learning/SKILL.md +1 -1
  28. package/skills/continuous-learning/evals/cases.yaml +1 -1
  29. package/skills/debug/SKILL.md +8 -12
  30. package/skills/debug/evals/cases.yaml +1 -1
  31. package/skills/decision-challenge/SKILL.md +1 -1
  32. package/skills/deprecation/SKILL.md +1 -1
  33. package/skills/ftd/SKILL.md +1 -3
  34. package/skills/ftd/evals/cases.yaml +1 -1
  35. package/skills/harness/SKILL.md +10 -9
  36. package/skills/harness/references/claude-md-template.md +1 -1
  37. package/skills/harness/references/wiki-protocol.md +5 -6
  38. package/skills/idea-refinement/SKILL.md +1 -1
  39. package/skills/implement/SKILL.md +14 -18
  40. package/skills/implement/evals/cases.yaml +1 -1
  41. package/skills/init/SKILL.md +36 -45
  42. package/skills/init/evals/README.md +3 -3
  43. package/skills/init/evals/cases.yaml +7 -7
  44. package/skills/init/references/accompaniment-and-profile.md +22 -40
  45. package/skills/init/references/discovery.md +2 -2
  46. package/skills/init/references/recommend-skills.md +5 -5
  47. package/skills/init/scripts/verify.sh +3 -7
  48. package/skills/knowledge-ops/SKILL.md +1 -1
  49. package/skills/orient/SKILL.md +52 -41
  50. package/skills/orient/evals/README.md +11 -9
  51. package/skills/orient/evals/cases.yaml +39 -29
  52. package/skills/orient/references/explain-ladder.md +93 -0
  53. package/skills/orient/references/orientation-contract.md +70 -18
  54. package/skills/parallel/SKILL.md +1 -1
  55. package/skills/parallel/evals/cases.yaml +2 -2
  56. package/skills/plan/SKILL.md +9 -13
  57. package/skills/review/SKILL.md +5 -8
  58. package/skills/roast-me/SKILL.md +1 -1
  59. package/skills/sdd/SKILL.md +10 -14
  60. package/skills/sdd/evals/README.md +2 -2
  61. package/skills/sdd/evals/cases.yaml +4 -4
  62. package/skills/sdd/references/model-routing.md +5 -12
  63. package/skills/sdd-init/SKILL.md +3 -3
  64. package/skills/ship/SKILL.md +6 -11
  65. package/skills/simplify-code/SKILL.md +1 -1
  66. package/skills/skill-scout/SKILL.md +1 -1
  67. package/skills/source-grounded-development/SKILL.md +1 -1
  68. package/skills/specify/SKILL.md +13 -14
  69. package/skills/specify/evals/cases.yaml +1 -1
  70. package/skills/specify/references/eliciting-requirements.md +5 -5
  71. package/skills/suggest/SKILL.md +3 -9
  72. package/skills/suggest/evals/cases.yaml +1 -1
  73. package/skills/tasks/SKILL.md +6 -7
  74. package/skills/tasks/evals/cases.yaml +1 -1
  75. package/skills/unslop/SKILL.md +39 -10
  76. package/skills/unslop/evals/README.md +7 -7
  77. package/skills/unslop/evals/cases.yaml +10 -9
  78. package/skills/verify/SKILL.md +5 -8
  79. package/skills/verify/evals/cases.yaml +1 -1
  80. package/skills/worktrees/SKILL.md +14 -18
  81. package/skills/worktrees/evals/cases.yaml +1 -1
  82. package/targets/_md-block.js +7 -1
  83. package/targets/auto-update.mjs +200 -0
  84. package/targets/claude.js +2 -0
  85. package/targets/clone-bootstrap.mjs +5 -2
  86. package/targets/opt-outs.js +1 -0
  87. package/targets/session-start.mjs +5 -78
  88. package/targets/update-wiring.js +153 -0
  89. package/skills/bro/SKILL.md +0 -76
  90. package/skills/bro/evals/README.md +0 -22
  91. package/skills/bro/evals/cases.yaml +0 -62
  92. package/skills/eli5/SKILL.md +0 -63
  93. package/skills/eli5/evals/README.md +0 -22
  94. package/skills/eli5/evals/cases.yaml +0 -54
  95. package/skills/show-me/SKILL.md +0 -140
  96. package/skills/show-me/evals/README.md +0 -23
  97. 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
 
@@ -338,8 +355,9 @@ pinned version. It asks; it does not install anything on its own and it does not
338
355
  whatever you sat down to do. Say no and it stops asking on that machine.
339
356
 
340
357
  Not `@latest`, and the difference is the whole point of sharing: a teammate who clones in three
341
- months gets what you had, not what shipped since. Upgrading is something a person decides, writes
342
- into `.rsc.json`, and commits — and then it reaches everyone through git, like any other change.
358
+ months gets what you had, not what shipped since. Once it is built, the same rules as any project
359
+ apply: releases in the same major install themselves (see [Update](#update)), and the new
360
+ `catalogVersion` in `.rsc.json` reaches everyone when somebody commits it.
343
361
 
344
362
  When someone changes the harness and you `git pull`, `rsc doctor` tells you what no longer
345
363
  matches. **Nothing is ever written to your machine by a pull** — you are told, and you decide.
@@ -386,8 +404,26 @@ so rsc does not repair, move or delete them — not even when rebuilding from sc
386
404
 
387
405
  ## Update
388
406
 
389
- `rsc` is an npm package, so updating is two steps — bump the package, then
390
- re-sync what's already wired into your project:
407
+ **rsc updates itself.** When a session starts, rsc checks npm for a newer version:
408
+
409
+ - **Same major** (`2.1.0 → 2.1.1` or `2.2.0`): it installs that exact version in the background.
410
+ It takes effect in the next session, and that session tells you it happened.
411
+ - **New major** (`2.x → 3.0.0`): it can change how the harness works, so the assistant asks first.
412
+ - **Failed:** the assistant asks instead, and rsc retries once a day. The output is in
413
+ `.rsc/auto-update.log`.
414
+
415
+ A new install asks whether you want this (yes by default). To turn it off later, create
416
+ `.rsc/.no-auto-update`; every release then asks. It is a project decision: it travels in
417
+ `.rsc.json` like the other switches. The update can leave harness files changed in git; rsc never
418
+ commits them for you.
419
+
420
+ Where it runs on its own: **Claude Code, Codex, Gemini CLI, Cursor and OpenCode**, from a
421
+ session-start hook. Every other assistant has no such hook, so the always-on instructions ask the
422
+ agent to run `node .rsc/auto-update.mjs` on its first turn — same rules, but it depends on the agent
423
+ doing it (and some assistants ask your permission before running a command). Codex asks you to
424
+ trust new hooks once.
425
+
426
+ To update by hand, bump the package, then re-sync what's already wired into your project:
391
427
 
392
428
  ```bash
393
429
  npm install -g @ericrisco/rsc@latest # global install: pull the newest catalog
@@ -404,6 +440,11 @@ Running through `npx` (no global install)? There's nothing to upgrade —
404
440
  `npx @ericrisco/rsc@latest` always fetches the latest published catalog; just run
405
441
  `rsc sync` afterwards if the project already has skills installed.
406
442
 
443
+ **From 2.0.x to 2.1.0:** `eli5` and `show-me` became part of `orient`, and `bro` became part of
444
+ `unslop`. The update removes the three old skills (only the copies rsc installed) and installs the
445
+ ones that replace them. An `accompaniment_level` line in your profile is ignored: the only setting
446
+ now is `technical_level`.
447
+
407
448
  Every sync snapshots the project first, so a bad update is always reversible:
408
449
 
409
450
  ```bash
@@ -434,20 +475,21 @@ just asks in plain language.
434
475
 
435
476
  ## The catalog
436
477
 
437
- 273 skills, grouped by what you're trying to do. Click any skill to read its
478
+ 270 skills, grouped by what you're trying to do. Click any skill to read its
438
479
  `SKILL.md`. It fires on its own when a task matches.
439
480
 
440
481
  ### 🧭 Core & control plane
441
482
  The front door and the workspace brain.
442
483
 
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/)
484
+ [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
485
 
445
486
  > **harness** is the Karpathy *chaos→knowledge* engine — a `01-TOOLS/` layer (one
446
487
  > folder per provider, each with a working `test_connection`) and a `02-DOCS/`
447
488
  > 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.
489
+ > always-on voice: short STE-style sentences, every answer understandable on its own,
490
+ > technical or with analogies, and a diagram or an HTML page when you do not follow.
491
+ > **unslop** is installed with every profile and makes text you send to others sound
492
+ > like a person, with no AI tells.
451
493
 
452
494
  <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
495
 
@@ -571,7 +613,7 @@ Three engines + engine-agnostic disciplines. Every engine skill pins the current
571
613
 
572
614
  ### 🧠 Knowledge & meta
573
615
 
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/)
616
+ [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
617
 
576
618
  ---
577
619
 
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "2.0.20",
2
+ "version": "2.2.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.20",
3
+ "version": "2.2.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": {
@@ -109,6 +109,10 @@
109
109
  {
110
110
  "fixedIn": "2.0.20",
111
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"
112
116
  }
113
117
  ]
114
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'],
@@ -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 {
@@ -21,6 +22,7 @@ import {
21
22
  import {
22
23
  wireMemory, unwireMemory, memoryManagedPaths, memoryModeFor, memoryEnabledForProject, memoryArtifactsPresent,
23
24
  } from '../targets/memory.js';
25
+ import { wireUpdate, unwireUpdate, updateManagedPaths, updateArtifactsPresent } from '../targets/update-wiring.js';
24
26
 
25
27
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
26
28
  const CLI_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version;
@@ -77,6 +79,7 @@ export function generatedHookFiles({ target, cwd, policy }) {
77
79
  join(cwd, '.rsc', 'worklog-checkpoint.mjs'),
78
80
  join(cwd, '.rsc', 'hook-once.mjs'),
79
81
  join(cwd, '.rsc', 'worktree-reaper.mjs'),
82
+ join(cwd, '.rsc', 'auto-update.mjs'),
80
83
  ];
81
84
  // The danger guard is declared on its own terms (#273): present unless the plan says otherwise,
82
85
  // whether or not the code guards are.
@@ -94,6 +97,7 @@ export function managedPathsForInstall({ skillIds, agentIds = [], target, home,
94
97
  const out = [paths.stateFile, versionFile(cwd), baseVersionsFile(cwd)];
95
98
  if (policy?.context7 === false) out.push(join(cwd, '.rsc', '.no-context7'));
96
99
  if (policy?.memory !== false) out.push(...memoryManagedPaths(target, cwd));
100
+ out.push(...updateManagedPaths(target, cwd));
97
101
  if (targetHasAgents(target)) {
98
102
  const state = readState(paths.stateFile);
99
103
  const explicit = [...new Set([...(state.explicitAgents || readManifest(cwd)?.agents || []), ...agentIds])];
@@ -196,7 +200,9 @@ export function recordInManifest({ cwd, target, skillIds, agentIds = [], catalog
196
200
  // `dropTarget` is the one exception — a MOVE, where leaving the old one declared would
197
201
  // have every clone rebuild a harness that was deliberately abandoned.
198
202
  targets: union(prev.targets, [target]).filter((t) => t !== dropTarget),
199
- skills: union(prev.skills, skillIds),
203
+ // Through the retirement map: the living list must stop naming a skill the catalog no longer
204
+ // ships, or every clone would go looking for it.
205
+ skills: replaceRetired(union(prev.skills, skillIds)).sort(),
200
206
  agents: union(prev.agents, agentIds),
201
207
  ownSkills: prev.ownSkills || [],
202
208
  catalogVersion,
@@ -215,7 +221,23 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
215
221
  const fromScratch = !existsSync(join(cwd, '.rsc'));
216
222
  const paths = targetPaths(target, home, cwd);
217
223
  const plan = planInstall({ skillIds, target, home, cwd, hooks: policy?.alwaysOn !== false });
218
- const managedPaths = managedPathsForInstall({ skillIds, agentIds, target, home, cwd, policy });
224
+ // What this pass prunes, decided BEFORE the backup so the backup holds it: a skill link this
225
+ // target's state proves is ours and that is no longer wanted (always for a retired skill, and for
226
+ // any skill when a plan governs), and the shared base of a retired skill once no other assistant
227
+ // in this project still records it. Taking a base another assistant links to would leave that
228
+ // assistant dangling until its own sync.
229
+ const priorState = readState(paths.stateFile);
230
+ const prunedSkills = Object.keys(priorState.skills || {})
231
+ .filter((id) => !skillIds.includes(id) && (policy || isRetired(id)));
232
+ const stillRecordedElsewhere = (id) => TARGET_IDS.some((other) => other !== target
233
+ && Object.hasOwn(readState(targetPaths(other, home, cwd).stateFile).skills || {}, id));
234
+ const prunedBases = Object.keys(readBaseVersions(cwd))
235
+ .filter((id) => isRetired(id) && !skillIds.includes(id) && !stillRecordedElsewhere(id));
236
+ const managedPaths = [...new Set([
237
+ ...managedPathsForInstall({ skillIds, agentIds, target, home, cwd, policy }),
238
+ ...prunedSkills.map((id) => paths.skillDir(id)),
239
+ ...prunedBases.map((id) => baseDir(id, cwd)),
240
+ ])];
219
241
  if (dryRun) return { dryRun: true, skills: skillIds, agents: agentIds, paths: managedPaths };
220
242
  // Rebuild the declared decisions first, so everything below (the agent tier especially) reads
221
243
  // the state the team declared rather than the default it would otherwise assume.
@@ -227,12 +249,17 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
227
249
  // syncs — a single global marker would be bumped by the first target and make later
228
250
  // targets skip refreshing their exclusive skills' bases.
229
251
  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
- }
252
+ // A retired skill is pruned even without a governing plan: nothing can install it any more, so
253
+ // keeping it would only leave a dead copy behind. Ownership is proven by the state file — a
254
+ // same-named directory rsc never recorded is somebody's own work and is not touched.
255
+ for (const id of prunedSkills) {
256
+ rmSync(paths.skillDir(id), { recursive: true, force: true });
257
+ delete state.skills[id];
258
+ }
259
+ // Its shared base too, proven ours by the base-version ledger rather than by its name.
260
+ for (const id of prunedBases) {
261
+ rmSync(baseDir(id, cwd), { recursive: true, force: true });
262
+ delete baseVersions[id];
236
263
  }
237
264
  for (const step of plan) {
238
265
  if (step.kind === 'skill') {
@@ -273,6 +300,8 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
273
300
  ? { mode: 'disabled', reason: 'onboarding-policy', paths: unwireMemory(target, cwd) }
274
301
  : wireMemory(target, cwd);
275
302
  state.memory = { mode: memoryResult.mode, reason: memoryResult.reason, paths: memoryResult.paths };
303
+ const updateResult = wireUpdate(target, cwd);
304
+ state.update = { mode: updateResult.mode, ...(updateResult.reason ? { reason: updateResult.reason } : {}) };
276
305
  const context7OptOut = join(cwd, '.rsc', '.no-context7');
277
306
  if (policy?.context7 === false) {
278
307
  mkdirSync(dirname(context7OptOut), { recursive: true });
@@ -528,7 +557,9 @@ export function removeTargetInstall({ target, home, cwd = process.cwd() }) {
528
557
  ];
529
558
  const declaredLocationsAreExact = Object.entries(state.skills || {}).every(([id, entry]) =>
530
559
  /^[a-z0-9][a-z0-9._-]*$/i.test(id)
531
- && existsSync(join(ROOT, 'skills', id))
560
+ // A retired id no longer has a catalog directory, but `paths.skillDir` below still proves where
561
+ // it lives — refusing it made every re-onboard that drops an old assistant loop on recovery.
562
+ && (existsSync(join(ROOT, 'skills', id)) || isRetired(id))
532
563
  && (entry.files || []).every((file) => resolve(file) === resolve(paths.skillDir(id))))
533
564
  && (state.agents || []).every((name) => {
534
565
  if (!allAgentNames().includes(name)) return false;
@@ -570,6 +601,7 @@ export function removeTargetInstall({ target, home, cwd = process.cwd() }) {
570
601
  }
571
602
  unwireHook(target, paths);
572
603
  unwireMemory(target, cwd);
604
+ unwireUpdate(target, cwd);
573
605
  rmSync(paths.stateFile, { force: true });
574
606
  const gitignore = join(cwd, '.gitignore');
575
607
  if (existsSync(gitignore)) {
@@ -607,9 +639,12 @@ export async function syncInstalled({ target, home, cwd = process.cwd(), dryRun
607
639
  // then rebuilt from a governed list that predated it and pruned the difference, recursively.
608
640
  // Reported by a user and reproduced 2026-09-17. The receipt stays untouched, because it is
609
641
  // hash-checked against what the user accepted and is not a log of what happened since.
610
- const declaredRaw = governedSkills
642
+ //
643
+ // Read through the retirement map: a declaration older than the retirement of eli5, show-me and
644
+ // bro names skills the catalog no longer ships, and the receipt that names them is never rewritten.
645
+ const declaredRaw = replaceRetired(governedSkills
611
646
  ? [...new Set([...governedSkills, ...(manifest?.skills || [])])].sort()
612
- : (ids.length ? ids : (manifest?.skills || []));
647
+ : (ids.length ? ids : (manifest?.skills || []))).sort();
613
648
  // A declaration is frozen at the moment it was written, so a skill the catalog later makes
614
649
  // mandatory never reaches an existing harness: `add` and `install` apply the floor, sync did
615
650
  // not, and sync is the only one an upgrade runs. 2.0.0 shipped the three-lane decisor inside
@@ -675,8 +710,10 @@ export async function purge({ home, cwd = process.cwd(), withDocs = false, dryRu
675
710
  if (!dryRun) {
676
711
  removed.push(...unwireHook(target, paths));
677
712
  removed.push(...unwireMemory(target, cwd));
713
+ removed.push(...unwireUpdate(target, cwd));
678
714
  } else {
679
715
  removed.push(...memoryArtifactsPresent(target, cwd));
716
+ removed.push(...updateArtifactsPresent(target, cwd));
680
717
  }
681
718
  // A lost state file also loses proof of ownership. Leave same-named user files
682
719
  // behind rather than guessing from a catalog id and deleting their work.
@@ -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() {