@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.
- package/README.md +63 -21
- package/manifest.json +18 -85
- package/package.json +5 -1
- package/scripts/consult.js +8 -8
- package/scripts/install-apply.js +48 -11
- package/scripts/lib/default-skill-floor.js +5 -4
- package/scripts/lib/divergence.js +7 -3
- package/scripts/lib/domains.js +2 -2
- package/scripts/lib/onboarding-apply.js +43 -26
- package/scripts/lib/onboarding.js +5 -5
- package/scripts/lib/recommend.js +1 -1
- package/scripts/lib/repair.js +4 -2
- package/scripts/lib/retired-skills.js +20 -0
- package/scripts/rsc.js +25 -19
- package/scripts/skill-behavior-rubric.md +1 -1
- package/scripts/skill-migration-brief.md +3 -3
- package/skills/analyze/SKILL.md +3 -8
- package/skills/analyze/evals/cases.yaml +1 -1
- package/skills/author-skill/SKILL.md +3 -3
- package/skills/author-skill/evals/cases.yaml +1 -1
- package/skills/author-skill/references/rsc-conventions.md +1 -1
- package/skills/clarify/SKILL.md +7 -14
- package/skills/clarify/evals/cases.yaml +2 -2
- package/skills/code-review/SKILL.md +1 -1
- package/skills/constitution/SKILL.md +10 -13
- package/skills/constitution/evals/cases.yaml +3 -3
- package/skills/continuous-learning/SKILL.md +1 -1
- package/skills/continuous-learning/evals/cases.yaml +1 -1
- package/skills/debug/SKILL.md +8 -12
- package/skills/debug/evals/cases.yaml +1 -1
- package/skills/decision-challenge/SKILL.md +1 -1
- package/skills/deprecation/SKILL.md +1 -1
- package/skills/ftd/SKILL.md +1 -3
- package/skills/ftd/evals/cases.yaml +1 -1
- package/skills/harness/SKILL.md +10 -9
- package/skills/harness/references/claude-md-template.md +1 -1
- package/skills/harness/references/wiki-protocol.md +5 -6
- package/skills/idea-refinement/SKILL.md +1 -1
- package/skills/implement/SKILL.md +14 -18
- package/skills/implement/evals/cases.yaml +1 -1
- package/skills/init/SKILL.md +36 -45
- package/skills/init/evals/README.md +3 -3
- package/skills/init/evals/cases.yaml +7 -7
- package/skills/init/references/accompaniment-and-profile.md +22 -40
- package/skills/init/references/discovery.md +2 -2
- package/skills/init/references/recommend-skills.md +5 -5
- package/skills/init/scripts/verify.sh +3 -7
- package/skills/knowledge-ops/SKILL.md +1 -1
- package/skills/orient/SKILL.md +52 -41
- package/skills/orient/evals/README.md +11 -9
- package/skills/orient/evals/cases.yaml +39 -29
- package/skills/orient/references/explain-ladder.md +93 -0
- package/skills/orient/references/orientation-contract.md +70 -18
- package/skills/parallel/SKILL.md +1 -1
- package/skills/parallel/evals/cases.yaml +2 -2
- package/skills/plan/SKILL.md +9 -13
- package/skills/review/SKILL.md +5 -8
- package/skills/roast-me/SKILL.md +1 -1
- package/skills/sdd/SKILL.md +10 -14
- package/skills/sdd/evals/README.md +2 -2
- package/skills/sdd/evals/cases.yaml +4 -4
- package/skills/sdd/references/model-routing.md +5 -12
- package/skills/sdd-init/SKILL.md +3 -3
- package/skills/ship/SKILL.md +6 -11
- package/skills/simplify-code/SKILL.md +1 -1
- package/skills/skill-scout/SKILL.md +1 -1
- package/skills/source-grounded-development/SKILL.md +1 -1
- package/skills/specify/SKILL.md +13 -14
- package/skills/specify/evals/cases.yaml +1 -1
- package/skills/specify/references/eliciting-requirements.md +5 -5
- package/skills/suggest/SKILL.md +3 -9
- package/skills/suggest/evals/cases.yaml +1 -1
- package/skills/tasks/SKILL.md +6 -7
- package/skills/tasks/evals/cases.yaml +1 -1
- package/skills/unslop/SKILL.md +39 -10
- package/skills/unslop/evals/README.md +7 -7
- package/skills/unslop/evals/cases.yaml +10 -9
- package/skills/verify/SKILL.md +5 -8
- package/skills/verify/evals/cases.yaml +1 -1
- package/skills/worktrees/SKILL.md +14 -18
- package/skills/worktrees/evals/cases.yaml +1 -1
- package/targets/_md-block.js +7 -1
- package/targets/auto-update.mjs +200 -0
- package/targets/claude.js +2 -0
- package/targets/clone-bootstrap.mjs +5 -2
- package/targets/opt-outs.js +1 -0
- package/targets/session-start.mjs +5 -78
- package/targets/update-wiring.js +153 -0
- package/skills/bro/SKILL.md +0 -76
- package/skills/bro/evals/README.md +0 -22
- package/skills/bro/evals/cases.yaml +0 -62
- package/skills/eli5/SKILL.md +0 -63
- package/skills/eli5/evals/README.md +0 -22
- package/skills/eli5/evals/cases.yaml +0 -54
- package/skills/show-me/SKILL.md +0 -140
- package/skills/show-me/evals/README.md +0 -23
- package/skills/show-me/evals/cases.yaml +0 -61
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@ericrisco/rsc)
|
|
8
8
|
[](https://www.npmjs.com/package/@ericrisco/rsc)
|
|
9
|
-
[](#the-catalog)
|
|
10
10
|
[](LICENSE)
|
|
11
11
|
[](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
|
|
46
|
-
|
|
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
|
|
70
|
-
|
|
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
|
|
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
|
|
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 **
|
|
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
|
-
|
|
256
|
+
270 skills · one CLI · zero bloat
|
|
239
257
|
|
|
240
|
-
How
|
|
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.
|
|
342
|
-
|
|
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
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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/) · [
|
|
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
|
|
449
|
-
>
|
|
450
|
-
>
|
|
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/) · [
|
|
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
|
|
2
|
+
"version": "2.2.0",
|
|
3
3
|
"counts": {
|
|
4
|
-
"skills":
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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
|
-
"
|
|
5273
|
-
"
|
|
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
|
|
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
|
}
|
package/scripts/consult.js
CHANGED
|
@@ -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: ['
|
|
47
|
-
humano: ['
|
|
48
|
-
humana: ['
|
|
49
|
-
huma: ['
|
|
50
|
-
natural: ['
|
|
51
|
-
robotico: ['
|
|
52
|
-
robotic: ['
|
|
53
|
-
jerga: ['
|
|
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/install-apply.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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. `
|
|
5
|
-
// but remains conditionally loaded: its description routes
|
|
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', '
|
|
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:
|
|
34
|
-
extra: [...installed].filter((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) => !
|
|
42
|
+
floorMissing: DEFAULT_SKILL_FLOOR.filter((id) => !declared.includes(id)),
|
|
39
43
|
};
|
|
40
44
|
}
|
package/scripts/lib/domains.js
CHANGED
|
@@ -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', '
|
|
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', '
|
|
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() {
|