@ericrisco/rsc 3.0.6 → 3.0.8

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 CHANGED
@@ -31,6 +31,27 @@ npx @ericrisco/rsc@latest onboard
31
31
 
32
32
  </div>
33
33
 
34
+ ## 🐋 New: DeepSeek Harness
35
+
36
+ [DeepSeek Harness](https://www.npmjs.com/package/@deepseek-ai/dsh) (`dsh`) is now a supported
37
+ assistant, the 18th. Pick it in the wizard or pass `--target deepseek`:
38
+
39
+ ```bash
40
+ npx @ericrisco/rsc@latest onboard --target deepseek
41
+ ```
42
+
43
+ - **Skills** go to `.dsh/skills/`, where dsh finds them on its own.
44
+ - **Instructions** go in the rsc block of `AGENTS.md`, which dsh already reads.
45
+ - **Session memory, knowledge sync and the update notice** run from dsh's own hooks. dsh has no
46
+ hooks per project, so rsc adds one marked block to `~/.dsh/cordis.patch.yml` once per machine. That
47
+ block runs the hooks of whichever project the session is in. It works in `dsh web` with several
48
+ projects open, and in a project without rsc it does nothing. Restart dsh once after the first
49
+ install. To turn it off, delete the block.
50
+
51
+ Details: [targets table](#multi-target).
52
+
53
+ ---
54
+
34
55
  ## Your agent needs a harness. It should not improvise one.
35
56
 
36
57
  A model is only the brain. Real work also needs project memory, tools, domain knowledge, rules and
@@ -73,7 +94,7 @@ rsc 3.0 fixes all four, by default, with no new commands to learn:
73
94
  | **You choose how the project works** | The install asks once: straight on `main`, or branches and pull requests? Branches are recommended only for long-lived, complex code. With `main` the agent works on it and never asks about branches. With branches a commit on `main` is refused, and before each code change the agent asks: this branch, a new one, or unlock `main`? It never opens a branch on its own. | Say **«unlock main»** / **«lock main»**, or `rsc main unlock` / `lock` |
74
95
  | **One workspace per agent** | If another assistant session is working in the same folder, new work goes to a worktree under `.worktrees/<branch>/`. It stays inside the project, is never committed, and is removed once that branch is merged. | `rsc isolation off` |
75
96
  | **The agent picks the method** | A simple change takes Fast-Track (one document, proof for every task). Something big or complex takes the spec-driven chain. The agent decides and says why. You still approve *what* gets built: the spec and the clarifications, then step by step or autopilot. | Ask for the other lane |
76
- | **Knowledge reaches everyone** | `01-TOOLS/` and `02-DOCS/` travel through one exchange branch, `rsc/knowledge`. They go up when your turn ends and come down into whatever branch each person is on. They reach `main` inside your normal pull requests. | `rsc knowledge-sync off` |
97
+ | **Knowledge reaches everyone** | `01-TOOLS/`, `02-DOCS/wiki/` and `02-DOCS/attachments/` travel through one exchange branch, `rsc/knowledge`. They go up when your turn ends and come down into whatever branch each person is on. They reach `main` inside your normal pull requests. | `rsc knowledge-sync off` |
77
98
 
78
99
  **Commit, push and pull request without a prompt each time.** A harness installed from scratch lets
79
100
  the agent run `git commit`, `git push` and `gh pr create` without asking, because those are the steps
@@ -89,6 +110,7 @@ someone runs `rsc git-permissions on`.
89
110
  | Gemini | `.gemini/settings.json` → `tools.allowed` |
90
111
  | OpenCode | `opencode.json` → `permission.bash` (force-push → `ask`) |
91
112
  | Cursor | Not covered: its CLI matches only the first word, so allowing a push would allow every git command. |
113
+ | DeepSeek Harness | Not covered: dsh has no per-command allow list. Its own Permissions selector (`/permission`) sets approval for every command at once. |
92
114
 
93
115
  ```bash
94
116
  rsc git-permissions status # decided or not, and wired per assistant
@@ -241,8 +263,8 @@ no method deciding whether each piece belongs. rsc-harness keeps that constructi
241
263
  - **Specialists follow the stack.** The four base agents stay small; installing a
242
264
  supported stack adds only its reviewer and build resolver. `rsc add go`, for
243
265
  example, adds the Go pair without pulling reviewers for every other language.
244
- - **A new local session continues the old one.** Claude Code, Codex, Gemini CLI
245
- and OpenCode load a bounded checkpoint for the current branch and worktree at
266
+ - **A new local session continues the old one.** Claude Code, Codex, Gemini CLI,
267
+ OpenCode and DeepSeek Harness load a bounded checkpoint for the current branch and worktree at
246
268
  session start. Cursor desktop uses an assisted read-before-action fallback.
247
269
  - **Honestly good.** Every skill was built by a research → spec → implement →
248
270
  *adversarial review* pipeline and had to clear an objective rubric
@@ -262,7 +284,8 @@ When a new session opens in the same checkout, that state is injected before the
262
284
  first agent action. A completed edit is preserved even if the previous client
263
285
  closed before its normal session-end event.
264
286
 
265
- - **Full:** Claude Code, Codex, Gemini CLI and OpenCode. Codex asks you to inspect
287
+ - **Full:** Claude Code, Codex, Gemini CLI, OpenCode and DeepSeek Harness (through rsc's machine
288
+ bridge, see [targets](#multi-target)). Codex asks you to inspect
266
289
  and trust the project hook once with `/hooks`; until then `doctor` reports that
267
290
  trust is still required.
268
291
  - **Assisted:** Cursor desktop. Its start hook is fire-and-forget, so rsc also
@@ -448,8 +471,10 @@ list somebody has to remember to update is a list that goes quietly stale. Decla
448
471
 
449
472
  ### Your wiki and tools stay in sync — on by default
450
473
 
451
- `01-TOOLS/` and `02-DOCS/` are the team's knowledge, so rsc keeps them the same on every machine
452
- without anybody thinking about git, **and without ever pushing to `main`**. Your `main` is
474
+ `01-TOOLS/`, `02-DOCS/wiki/` and `02-DOCS/attachments/` are the team's knowledge, so rsc keeps them
475
+ the same on every machine without anybody thinking about git, **and without ever pushing to `main`**.
476
+ Exactly those three folders, minus rsc's own `01-TOOLS/_TEMPLATE/` and your personal
477
+ `02-DOCS/wiki/harness/user-profile.md`. Your `main` is
453
478
  protected, or should be; rsc assumes it is.
454
479
 
455
480
  The knowledge travels through one exchange branch, `rsc/knowledge`, that nobody works on by hand:
@@ -461,13 +486,22 @@ main · protected untouched — gets it inside the next merged pull reque
461
486
  new · clones main has it all after the first message
462
487
  ```
463
488
 
464
- - **When a turn ends**, your changes in those folders are committed as `📝 docs(auto): … [skip ci]`
465
- and sent to `rsc/knowledge` on `origin`, from whatever branch you are on. If you are on a closed
466
- `main`, nothing is committed locally: the snapshot is built aside and only goes to `rsc/knowledge`.
467
- Knowledge that you or the agent already **committed** on the branch during the turn goes up too —
468
- only its `01-TOOLS/` and `02-DOCS/` part, under a `docs(auto) … [skip ci]` message.
469
- - **Before each message**, what teammates sent is brought into **the branch you are on**, and you are
470
- told in one line. Only the knowledge folders are touched.
489
+ - **When a turn ends**, your changes in those folders are committed on your branch as
490
+ `📝 docs(auto): …` and sent to `rsc/knowledge` on `origin`, from whatever branch you are on. Only
491
+ the copy on `rsc/knowledge` carries `[skip ci]`; the commit on your branch does not, so your next
492
+ ordinary push still runs CI. Two cases commit nothing on your branch: a closed `main`, and code of
493
+ yours still uncommitted (a docs commit would land ahead of the code it describes). Then the snapshot
494
+ is built aside, only goes to `rsc/knowledge`, and the files stay modified for you to commit with
495
+ your work. Knowledge that you or the agent already **committed** on the branch goes up too — only
496
+ its knowledge part, under a `docs(auto) … [skip ci]` message. Commits that came in by merging
497
+ `main` are your teammates', not yours, and are not sent again.
498
+ - **Before each message**, what teammates sent is brought into **the branch you are on** as a
499
+ `📥 docs(auto): sync` commit, and you are told in one line. Only the knowledge folders are touched.
500
+ A doc that `main` on `origin` already has, identical, is left for your ordinary merge of `main`. On
501
+ a closed `main` nothing is written at all — an uncommitted file there would make your next
502
+ `git pull` refuse to run — you are told what is waiting, and it arrives with that pull or in the
503
+ next branch you open. When the last fetch is more than ten minutes old, the message fetches first
504
+ (three seconds at most), so even a one-message session sees fresh docs.
471
505
  A branch you open later catches up on its first message: everything on `rsc/knowledge` since it
472
506
  left `main` comes down, including what you wrote yourself on another branch.
473
507
  - **It reaches `main` the normal way.** Your feature branch now carries the team's knowledge, so it
@@ -482,14 +516,14 @@ What it will not do, by design:
482
516
  | --- | --- |
483
517
  | Push to `main` | Never. Only `rsc/knowledge` receives anything. |
484
518
  | Push your code or your unpushed commits | Only the knowledge folders go up, even from a commit that also has code. Your code waits for you. |
485
- | Trigger CI or a deploy | Every automatic commit says `[skip ci]`. |
519
+ | Trigger CI or a deploy | Every commit on `rsc/knowledge` says `[skip ci]`; the one on your branch does not, so it never skips the CI of your own push. |
486
520
  | Bring in anybody else's code | Only knowledge paths come down. Code, config and `.claude/` are never pulled. |
487
521
  | Sync `02-DOCS/wiki/harness/user-profile.md` | Those are one person's dials. |
488
522
  | Overwrite a file you are editing | It tells you, and your version stays. |
489
523
  | Run where it has no business | No `origin`, no knowledge folders, a rebase in progress, a cloud agent: it stays quiet. |
490
524
 
491
525
  It is wired into the assistant's own turn hooks (end of turn, new message) in Claude Code, Codex,
492
- Gemini, Cursor and OpenCode (tested end to end in Claude Code, Codex and OpenCode). The network part runs in the background, so a turn never waits for
526
+ Gemini, Cursor, OpenCode and DeepSeek Harness (tested end to end in Claude Code, Codex and OpenCode). The network part runs in the background, so a turn never waits for
493
527
  it. It is kept apart from the [local session memory](#new-sessions-pick-up-the-latest-local-work),
494
528
  which promises never to touch the network. The first turn says it is on. To turn it off for the
495
529
  project:
@@ -550,7 +584,7 @@ A new install asks whether you want this (yes by default). To turn it off later,
550
584
  `.rsc.json` like the other switches. The update can leave harness files changed in git; rsc never
551
585
  commits them for you.
552
586
 
553
- Where it runs on its own: **Claude Code, Codex, Gemini CLI, Cursor and OpenCode**, from a
587
+ Where it runs on its own: **Claude Code, Codex, Gemini CLI, Cursor, OpenCode and DeepSeek Harness**, from a
554
588
  session-start hook. Every other assistant has no such hook, so the always-on instructions ask the
555
589
  agent to run `node .rsc/auto-update.mjs` on its first turn — same rules, but it depends on the agent
556
590
  doing it (and some assistants ask your permission before running a command). Codex asks you to
@@ -772,14 +806,23 @@ duplicated. The wizard asks which ones; `--target a,b` does it non-interactively
772
806
  | `roo` | `.roo/rsc/<id>/` → symlink | rule in `.roo/rules/rsc-suggest.md` |
773
807
  | `amp` | `.amp/rsc/<id>/` → symlink | block in `AGENTS.md` |
774
808
  | `opencode` | `.opencode/rsc/<id>/` → symlink | block in `AGENTS.md` |
809
+ | `deepseek` | `.dsh/skills/<id>/` → symlink (DeepSeek Harness discovers it natively) | block in `AGENTS.md` |
775
810
  | `jules` | `.jules/rsc/<id>/` → symlink | block in `AGENTS.md` |
776
811
  | `junie` | `.junie/rsc/<id>/` → symlink | block in `.junie/guidelines.md` |
777
812
  | `kiro` | `.kiro/rsc/<id>/` → symlink | doc in `.kiro/steering/rsc-suggest.md` |
778
813
  | `aider` | `.aider/rsc/<id>/` → symlink | block in `CONVENTIONS.md` |
779
814
 
780
- > `codex`, `zed`, `amp`, `opencode` and `jules` all share the one root
815
+ > `codex`, `zed`, `amp`, `opencode`, `deepseek` and `jules` all share the one root
781
816
  > `AGENTS.md`; the block is idempotent, so picking several writes it once.
782
817
 
818
+ > **DeepSeek Harness** (`dsh`) has no project-level hook config: its plugins are per machine. So
819
+ > the `deepseek` target writes the project hooks to `.dsh/hooks.json` (Codex format, untracked) and,
820
+ > once per machine, adds one marked block to `~/.dsh/cordis.patch.yml` (or `$DSH_HOME`) that mounts
821
+ > dsh's Codex-hooks bridge on rsc's dispatcher in `~/.dsh/rsc/`. The dispatcher runs the hooks of
822
+ > whichever project the session is in, so `dsh web` serving several workspaces works too, and a
823
+ > project without rsc runs nothing. Restart dsh once after the first install. Delete the block to
824
+ > turn it off.
825
+
783
826
  The richer surfaces are intentionally narrower than skill support:
784
827
 
785
828
  | Targets | Stack agents | Native commands | Local session continuation |
@@ -788,6 +831,7 @@ The richer surfaces are intentionally narrower than skill support:
788
831
  | Codex | yes | no separate project-command surface | full after `/hooks` trust |
789
832
  | Cursor desktop | yes | yes | assisted |
790
833
  | Gemini CLI, OpenCode | yes | yes | full |
834
+ | DeepSeek Harness | unsupported | unsupported | full (through the machine bridge) |
791
835
  | GitHub Copilot | yes | yes | unsupported |
792
836
  | Junie, Kiro | yes | unsupported | unsupported |
793
837
  | Windsurf, Cline, Roo | unsupported | yes | unsupported |
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.0.6",
2
+ "version": "3.0.8",
3
3
  "counts": {
4
4
  "skills": 270,
5
5
  "agents": 33,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "3.0.6",
3
+ "version": "3.0.8",
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": {
@@ -46,6 +46,8 @@
46
46
  "claude-code",
47
47
  "cursor",
48
48
  "codex",
49
+ "deepseek",
50
+ "deepseek-harness",
49
51
  "gemini",
50
52
  "antigravity",
51
53
  "cli",
package/scripts/audit.js CHANGED
@@ -207,6 +207,17 @@ export function stampAudit(cwd, date = new Date().toISOString()) {
207
207
  writeFileSync(join(dir, 'audit.json'), JSON.stringify({ lastRun: date }, null, 2) + '\n');
208
208
  }
209
209
 
210
+ // The start of the cadence for a harness that has never been audited: written by onboarding so the
211
+ // first audit falls due STALE_DAYS after install, not in the first session. `baseline` and not
212
+ // `lastRun`, because no audit ran — the SessionStart hook reads either. Leaves an existing file alone.
213
+ export function recordAuditBaseline(cwd, date = new Date().toISOString()) {
214
+ const file = join(cwd, '.rsc', 'audit.json');
215
+ if (existsSync(file)) return false;
216
+ mkdirSync(join(cwd, '.rsc'), { recursive: true });
217
+ writeFileSync(file, JSON.stringify({ baseline: date }, null, 2) + '\n');
218
+ return true;
219
+ }
220
+
210
221
  // Write the report into the harness wiki when one exists; always stamp .rsc/audit.json.
211
222
  export function writeAuditReport(report, cwd = process.cwd()) {
212
223
  const written = [];
package/scripts/doctor.js CHANGED
@@ -1,6 +1,7 @@
1
- import { hooksDir, hooksDirIsOurs } from '../targets/worktree-reaper.mjs';
2
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
- import { join, dirname } from 'node:path';
1
+ import { hooksDir, hooksDirIsOurs, classifyWorktrees } from '../targets/worktree-reaper.mjs';
2
+ import { existsSync, readFileSync, readdirSync, statSync, realpathSync } from 'node:fs';
3
+ import { join, dirname, relative, sep } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
4
5
  import { homedir } from 'node:os';
5
6
  import { targetPaths, TARGET_IDS } from '../targets/index.js';
6
7
  import { readState } from './lib/state.js';
@@ -101,7 +102,18 @@ export function missingHookScripts({ target, home = homedir(), cwd = process.cwd
101
102
  // Is the post-merge trigger actually armed in this clone? Three states, because they need three
102
103
  // different answers: armed, absent (repair it), or someone else's (leave it — chaining is our job
103
104
  // at install time, and overwriting a husky hook to fix a convenience would be indefensible).
104
- function worktreeCleanupStatus(root) {
105
+ // The skills that read or write a design identity (02-DOCS/wiki/brand/) or a starting point. The
106
+ // state is always reported; the FIX — "run design-loop, then design-dna" — is advice, and advice needs
107
+ // evidence. A todo web app with none of these installed was told to run two skills it does not have
108
+ // and never asked for (E2E 2026-10-07).
109
+ const DESIGN_SKILLS = new Set(['design', 'design-loop', 'design-dna', 'brand-voice', 'content-engine', 'marketing', 'presentations', 'make']);
110
+ function withoutUnfoundedFix(summary, inPlay) {
111
+ if (inPlay || !summary || !summary.fix) return summary;
112
+ const { fix, ...rest } = summary;
113
+ return { ...rest, note: 'no design skill is installed here, so nothing is recommended' };
114
+ }
115
+
116
+ function worktreeCleanupStatus(root, policy) {
105
117
  if (!existsSync(join(root, '.git'))) return { state: 'not-a-repo' };
106
118
  if (existsSync(join(root, '.rsc', '.no-worktree-cleanup'))) return { state: 'off-by-choice' };
107
119
  // Ask git where hooks live instead of assuming `.git/hooks`. Until 2.0.1 this read the assumed
@@ -118,6 +130,13 @@ function worktreeCleanupStatus(root) {
118
130
  }
119
131
  const hook = join(dir, 'post-merge');
120
132
  if (!existsSync(hook)) {
133
+ // The merge hook is one of the code hooks, and the accepted plan can defer those. Recommending
134
+ // `repair` then is a loop with no exit — repair re-runs the same install, which honours the same
135
+ // plan and skips the hook again — and it contradicted `healthy: true` on every fresh small
136
+ // software install (E2E 2026-10-07). Said as what it is, with nothing to do.
137
+ if (policy?.codeHooks === false) {
138
+ return { state: 'not-in-plan', reason: 'the accepted plan defers code hooks, and the post-merge cleanup is one of them; `rsc reassess` revisits that.' };
139
+ }
121
140
  return { state: 'absent', action: 'Run `npx @ericrisco/rsc repair` to arm the post-merge cleanup in this clone.' };
122
141
  }
123
142
  try {
@@ -128,6 +147,27 @@ function worktreeCleanupStatus(root) {
128
147
  } catch { return { state: 'unreadable' }; }
129
148
  }
130
149
 
150
+ // The two things a team trips over with worktrees (team simulation D9): a `.worktrees/` git does not
151
+ // ignore — one `git add -A` away from committing a whole checkout — and landed worktrees still on
152
+ // disk, which look exactly like live ones. Reported with the way out, never blocking `healthy`: both
153
+ // cost tidiness, not work.
154
+ const realOf = (p) => { try { return realpathSync.native(p); } catch { return p; } };
155
+ function worktreeHygiene(root) {
156
+ if (!existsSync(join(root, '.git'))) return { state: 'not-a-repo' };
157
+ const probe = spawnSync('git', ['-C', root, 'check-ignore', '-q', '--no-index', '.worktrees/rsc-probe/file'], { encoding: 'utf8' });
158
+ const ignored = probe.status === 0;
159
+ let leftovers = [];
160
+ try {
161
+ leftovers = classifyWorktrees(root)
162
+ .filter((c) => c.verdict === 'safe' || c.verdict === 'ask')
163
+ .map((c) => ({ path: relative(realOf(root), c.path).split(sep).join('/') || c.path, branch: c.branch, verdict: c.verdict }));
164
+ } catch { /* unanswerable is not a finding */ }
165
+ const actions = [];
166
+ if (!ignored) actions.push('`.worktrees/` is not ignored by git here: run `npx @ericrisco/rsc sync` (it adds it to the rsc block of .gitignore) and commit .gitignore so every clone gets it.');
167
+ if (leftovers.length) actions.push(`${leftovers.length} landed worktree(s) are still on disk: \`npx @ericrisco/rsc worktrees\` lists them, \`npx @ericrisco/rsc worktrees reap\` removes the safe ones.`);
168
+ return { ignored, leftovers, ...(actions.length ? { action: actions.join(' ') } : {}) };
169
+ }
170
+
131
171
  // Every skill this catalog ships declares `origin: risco` in its own frontmatter — all 272 of them,
132
172
  // uniformly. So "is this ours?" is answerable from the file itself, and does not need the list in
133
173
  // `.rsc.json` that nobody ever filled: that list is parallel accounting (P3), it has been empty in
@@ -184,8 +224,8 @@ export function doctor({ target, home, cwd }) {
184
224
  try {
185
225
  memory.metrics = hasStore
186
226
  ? metricsSummary({ cwd: root })
187
- : { sessions: [], total: { cost: null, toolCalls: null }, knownTotal: { cost: 0, toolCalls: 0 }, unknown: { cost: 0, toolCalls: 0 } };
188
- } catch { memory.metrics = { sessions: [], total: { cost: null, toolCalls: null }, knownTotal: { cost: 0, toolCalls: 0 }, unknown: { cost: 0, toolCalls: 0 } }; }
227
+ : { sessions: [], total: { cost: null, toolCalls: null }, knownTotal: { cost: null, toolCalls: null }, unknown: { cost: 0, toolCalls: 0 } };
228
+ } catch { memory.metrics = { sessions: [], total: { cost: null, toolCalls: null }, knownTotal: { cost: null, toolCalls: null }, unknown: { cost: 0, toolCalls: 0 } }; }
189
229
  }
190
230
  const actualSkills = listSkills({ target, home, cwd: root }).map((entry) => entry.id);
191
231
  const actualAgents = listAgents({ target, home, cwd: root }).agents.map((entry) => entry.id);
@@ -208,6 +248,9 @@ export function doctor({ target, home, cwd }) {
208
248
  const path = agentPath(target, root, id);
209
249
  return path && !existsSync(path);
210
250
  }).map((id) => ({ id, path: agentPath(target, root, id), action: 'Run `npx @ericrisco/rsc sync` to restore this managed agent.' }));
251
+ // What THIS harness installed, not every skill visible from here: a design skill in the user's
252
+ // global scope is how a todo app's doctor came to recommend design-dna (E2E 2026-10-07).
253
+ const designInPlay = Object.keys(state.skills || {}).some((id) => DESIGN_SKILLS.has(id));
211
254
  const report = {
212
255
  target,
213
256
  installed: Object.keys(state.skills),
@@ -224,17 +267,18 @@ export function doctor({ target, home, cwd }) {
224
267
  // The worktree cleanup's trigger is a git hook, and `.git/hooks/` is not cloned. So a repo can
225
268
  // carry a perfectly healthy harness and still never clean up, on every machine but the one that
226
269
  // ran the installer. Reported, never nagged about: `repair` is what puts it back (P6).
227
- worktreeCleanup: worktreeCleanupStatus(root),
270
+ worktreeCleanup: worktreeCleanupStatus(root, state.policy),
271
+ worktreeHygiene: worktreeHygiene(root),
228
272
  ownSkills: ownSkillsIn(paths, readProjectManifest(root)?.ownSkills || []),
229
273
  sello: selloStatus(root),
230
274
  // Whether this harness has a design identity at all. `design` has always DECLARED that it
231
275
  // stops without one; until lib/design-identity.js nothing checked it (P2). Reported here,
232
276
  // never nagged about and never blocking: a missing identity is low risk (P7).
233
- designIdentity: designIdentity(root),
277
+ designIdentity: withoutUnfoundedFix(designIdentity(root), designInPlay),
234
278
  // The other half of the same question: an identity is what this project settled on, a starting
235
279
  // point is what it has to settle FROM. Three skills promise to propose one and none could look;
236
280
  // this is the look. One field, not a section — a report grows for every user who runs it (P5).
237
- designStartingPoint: startingPointSummary(root),
281
+ designStartingPoint: withoutUnfoundedFix(startingPointSummary(root), designInPlay),
238
282
  // An inert gate must never read as an armed one (the same rule the sello follows):
239
283
  // the gitmoji guard has a per-project kill switch, so say which state it is in.
240
284
  gitmojiGuard: state.policy?.gitmojiGuard === false
@@ -306,9 +350,17 @@ const bytesOf = (p) => { try { return statSync(p).size; } catch { return 0; } };
306
350
 
307
351
  // The always-on body a wired scope injects: the hook command embeds the exact SKILL.md path,
308
352
  // so read it from there rather than guessing the layout.
353
+ // The body the SessionStart hook prints is the `.md` file its command names — today the short
354
+ // `.rsc/suggest-always-on.md`, not the whole `suggest` SKILL.md. Reading only `…SKILL.md` out of the
355
+ // command meant the current wiring never matched and the fallback counted the full skill: doctor said
356
+ // 7.1 KB for a hook that injects 0.3 KB of body (E2E 2026-10-07). The command names the project
357
+ // through ${CLAUDE_PROJECT_DIR}, so that is resolved against the scope before measuring.
309
358
  function alwaysOnBytesFor(scopeRoot, settingsRaw) {
310
- const fromCommand = /"([^"]*suggest[^"]*SKILL\.md)"/.exec(settingsRaw || '');
311
- if (fromCommand && bytesOf(fromCommand[1])) return bytesOf(fromCommand[1]);
359
+ const raw = (settingsRaw || '').replace(/\\\\/g, '/');
360
+ const candidates = [...raw.matchAll(/\\?"([^"\\]*suggest[^"\\]*\.md)\\?"/g)].map((m) => m[1]
361
+ .replace(/\$\{CLAUDE_PROJECT_DIR\}|\$CLAUDE_PROJECT_DIR/g, scopeRoot)
362
+ .replace(/^~(?=\/)/, homedir()));
363
+ for (const file of candidates) if (bytesOf(file)) return bytesOf(file);
312
364
  return bytesOf(join(scopeRoot, '.rsc', 'skills', 'suggest', 'SKILL.md'));
313
365
  }
314
366