@ericrisco/rsc 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,6 +56,58 @@ not a pile of whatever an agent happened to install.
56
56
 
57
57
  ---
58
58
 
59
+ ## 🛡️ New in 3.0: safe for teams by default
60
+
61
+ Day one with agents is easy. Day ninety is where harnesses break, especially with several people,
62
+ several assistants and several sessions on one project:
63
+
64
+ - every machine drifts, and what one person teaches their agent never reaches anyone else;
65
+ - two agents end up working in the same folder and switch branches under each other;
66
+ - someone's agent commits straight to `main` on a project in production;
67
+ - every request gets the same process, whether it is a typo or a new subsystem.
68
+
69
+ rsc 3.0 fixes all four, by default, with no new commands to learn:
70
+
71
+ | | What happens | Turn it off |
72
+ | --- | --- | --- |
73
+ | **Main is closed where it matters** | In a project that is complex or in production, the agent never commits on the default branch. It opens a branch on its own (`feat/…`, `fix/…`) and finishes with a pull request. In a project of scripts or research it works on `main`, because a branch there is only ceremony. | Say **«unlock main»**, or `rsc main unlock` |
74
+ | **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
+ | **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` |
77
+
78
+ **How does it know a project is "complex or in production"?** There are two layers. A hook counts
79
+ what anyone can check: a CI setup, a deployment file (`Dockerfile`, `vercel.json`, `fly.toml`…) or
80
+ at least two people in the last 50 commits. If any of those is there, a commit on the default branch
81
+ is refused before it runs. If none is, the agent decides change by change, using the same rule from
82
+ its skills. If the hook gets it wrong and the project really is simple, «unlock main» opens it for
83
+ the project. That decision is saved in `.rsc.json`, so the whole team gets it.
84
+
85
+ Every refusal says how to carry on: which branch to open, how to create the worktree, or how to
86
+ unlock. An agent is never left stuck. The rules only see what the **agent** runs; a person
87
+ committing in their own terminal is never touched.
88
+
89
+ **The first session after upgrading** does three things, once:
90
+
91
+ - It explains these changes in a few lines.
92
+ - If an agent left commits on a closed `main` that never reached the remote, it moves them to a
93
+ `rescue/main-<date>` branch and puts `main` back to what the remote has. Nothing is lost.
94
+ - It moves worktrees older versions created next to the project into `.worktrees/`.
95
+
96
+ ```bash
97
+ rsc main status # open or closed for the agent, and the signals behind it
98
+ rsc main unlock # this project is simple: the agent may work on main (saved in .rsc.json)
99
+ rsc main lock # back to the default
100
+ rsc isolation status # whether another session would send new work to a worktree
101
+ rsc isolation off # never use worktrees for parallel sessions in this project
102
+ ```
103
+
104
+ > **Limit, stated plainly.** The hook that refuses the commit or the branch switch runs in
105
+ > **Claude Code**, like the other rsc guards. In Codex, Gemini, Cursor and OpenCode the same rules
106
+ > are carried by the skills and by the session memory, which tells the agent that another session is
107
+ > active. They are followed, but no hook enforces them. Knowledge sync works in all five.
108
+
109
+ ---
110
+
59
111
  ## 🤖 For agents: "install this harness"
60
112
 
61
113
  If you're an AI coding agent (Claude Code, Cursor, Copilot, Codex, Gemini…) and your user
@@ -98,9 +150,9 @@ before anything is written.
98
150
 
99
151
  ---
100
152
 
101
- ## 🛣️ Three lanes, and the harness never picks the expensive one for you
153
+ ## 🛣️ Three lanes, and the agent picks the right one for you
102
154
 
103
- <img src="https://raw.githubusercontent.com/ericrisco/rsc-harness/main/site/lanes.svg" alt="The rsc lane decisor: a request is classified into answer (read-only), FTD (the default lane, one feature document) or SDD (the ten-phase chain, which only the user selects)." width="960">
155
+ <img src="https://raw.githubusercontent.com/ericrisco/rsc-harness/main/site/lanes.svg" alt="The rsc lane decisor: the agent classifies each request into answer (read-only), FTD (simple change, one feature document) or SDD (the ten-phase chain for big or complex work, entered by the agent; you approve the spec)." width="960">
104
156
 
105
157
  Every turn takes exactly one lane, and your agent names the one it took in a line.
106
158
 
@@ -111,11 +163,14 @@ Every turn takes exactly one lane, and your agent names the one it took in a lin
111
163
  2. **FTD — Fast-Track Development.** The request authorises a change. This is the default for
112
164
  ordinary work and needs no ceremony. One feature document per feature holds intent, scope, a
113
165
  checklist, the evidence and the next step. Tasks are checked off against observed proof, never
114
- against intention. A branch if the work writes code; nothing if it only touches docs or config.
115
- 3. **SDD — the ten-phase chain.** Unchanged, and **the harness never enters it alone.** It proposes
116
- the chain only when a durable spec, plan and task list would remove a *substantial* ambiguity —
117
- a test of usefulness, not of size — and enters it only when you ask for it or accept that
118
- proposal. Size, file count and perceived risk never select it on their own.
166
+ against intention. For the simple changes.
167
+ 3. **SDD — the ten-phase chain.** For the big or complex: several decisions that affect each other,
168
+ or doubts that change the result. **The agent enters it on its own** — you never have to decide
169
+ which method to use. Inside, you still decide *what* gets built: you approve the spec and answer
170
+ the clarifying questions, then choose to review every phase yourself or let it run on autopilot
171
+ to verified code. Publishing always asks.
172
+
173
+ The agent says which lane it took and why, in one line. Ask for the other one and it switches.
119
174
 
120
175
  **Isolation cleans itself up.** When a lane opens a branch or a worktree, you no longer have to
121
176
  remember to retire it: a `post-merge` hook does it the moment the work lands on the trunk, on both
@@ -124,10 +179,9 @@ landed and holds nothing unsaved; anything it refuses tells you why. The previou
124
179
  agent to run the cleanup at the end of a long phase, and that step was skipped on both features that
125
180
  reached it.
126
181
 
127
- Earlier versions routed everything that sounded like building into the chain. That contradicted the
128
- project's own first rule — friction is proportional to risk — and a harness that interrupts the 80%
129
- that is harmless gets switched off, which protects nothing. Both lanes are still here; what changed
130
- is which one you land in by default, and who decides.
182
+ Earlier versions asked you to accept a proposal before entering the chain. People do not want to
183
+ choose a method; they want the work done right. Since 3.0 the agent chooses, and you keep the part
184
+ that is genuinely yours: deciding what is built.
131
185
 
132
186
  ---
133
187
 
@@ -355,8 +409,9 @@ pinned version. It asks; it does not install anything on its own and it does not
355
409
  whatever you sat down to do. Say no and it stops asking on that machine.
356
410
 
357
411
  Not `@latest`, and the difference is the whole point of sharing: a teammate who clones in three
358
- months gets what you had, not what shipped since. Upgrading is something a person decides, writes
359
- into `.rsc.json`, and commits — and then it reaches everyone through git, like any other change.
412
+ months gets what you had, not what shipped since. Once it is built, the same rules as any project
413
+ apply: releases in the same major install themselves (see [Update](#update)), and the new
414
+ `catalogVersion` in `.rsc.json` reaches everyone when somebody commits it.
360
415
 
361
416
  When someone changes the harness and you `git pull`, `rsc doctor` tells you what no longer
362
417
  matches. **Nothing is ever written to your machine by a pull** — you are told, and you decide.
@@ -369,6 +424,59 @@ list somebody has to remember to update is a list that goes quietly stale. Decla
369
424
  `ownSkills` still works and still reports when someone is missing it.
370
425
 
371
426
 
427
+ ### Your wiki and tools stay in sync — on by default
428
+
429
+ `01-TOOLS/` and `02-DOCS/` are the team's knowledge, so rsc keeps them the same on every machine
430
+ without anybody thinking about git, **and without ever pushing to `main`**. Your `main` is
431
+ protected, or should be; rsc assumes it is.
432
+
433
+ The knowledge travels through one exchange branch, `rsc/knowledge`, that nobody works on by hand:
434
+
435
+ ```text
436
+ ana · feat/login edits 02-DOCS/wiki/api.md ──► rsc/knowledge (when her turn ends)
437
+ eric · feat/payments 📥 1 change from Ana ◄── rsc/knowledge (before his next message)
438
+ main · protected untouched — gets it inside the next merged pull request
439
+ new · clones main has it all after the first message
440
+ ```
441
+
442
+ - **When a turn ends**, your changes in those folders are committed as `📝 docs(auto): … [skip ci]`
443
+ and sent to `rsc/knowledge` on `origin`, from whatever branch you are on. If you are on a closed
444
+ `main`, nothing is committed locally: the snapshot is built aside and only goes to `rsc/knowledge`.
445
+ - **Before each message**, what teammates sent is brought into **the branch you are on**, and you are
446
+ told in one line. Only the knowledge folders are touched.
447
+ - **It reaches `main` the normal way.** Your feature branch now carries the team's knowledge, so it
448
+ arrives in `main` inside the pull request you were going to open anyway. There is no extra pull
449
+ request from `rsc/knowledge`, and nobody has to merge it.
450
+ - The first time, `rsc/knowledge` is created from the remote's default branch. If someone deletes it,
451
+ it is created again.
452
+
453
+ What it will not do, by design:
454
+
455
+ | | |
456
+ | --- | --- |
457
+ | Push to `main` | Never. Only `rsc/knowledge` receives anything. |
458
+ | Push your code or your unpushed commits | Only its own snapshot of the knowledge folders goes up. Your code waits for you. |
459
+ | Trigger CI or a deploy | Every automatic commit says `[skip ci]`. |
460
+ | Bring in anybody else's code | Only knowledge paths come down. Code, config and `.claude/` are never pulled. |
461
+ | Sync `02-DOCS/wiki/harness/user-profile.md` | Those are one person's dials. |
462
+ | Overwrite a file you are editing | It tells you, and your version stays. |
463
+ | Run where it has no business | No `origin`, no knowledge folders, a rebase in progress, a cloud agent: it stays quiet. |
464
+
465
+ It is wired into the assistant's own turn hooks (end of turn, new message) in Claude Code, Codex,
466
+ 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
467
+ it. It is kept apart from the [local session memory](#new-sessions-pick-up-the-latest-local-work),
468
+ which promises never to touch the network. The first turn says it is on. To turn it off for the
469
+ project:
470
+
471
+ ```bash
472
+ rsc knowledge-sync off # writes .rsc/.no-knowledge-sync and records it in .rsc.json — commit that
473
+ rsc knowledge-sync status # active or not, and why
474
+ ```
475
+
476
+ It is a **project** switch, not a personal one: if one person stopped sending, the rest of the team
477
+ would stop seeing their work. For the rest of the 3.0 team defaults, see
478
+ [New in 3.0](#️-new-in-30-safe-for-teams-by-default).
479
+
372
480
  ## 🩹 Something's off? One command
373
481
 
374
482
  Recognise any of these? They are all the same fix.
@@ -403,8 +511,26 @@ so rsc does not repair, move or delete them — not even when rebuilding from sc
403
511
 
404
512
  ## Update
405
513
 
406
- `rsc` is an npm package, so updating is two steps — bump the package, then
407
- re-sync what's already wired into your project:
514
+ **rsc updates itself.** When a session starts, rsc checks npm for a newer version:
515
+
516
+ - **Same major** (`2.1.0 → 2.1.1` or `2.2.0`): it installs that exact version in the background.
517
+ It takes effect in the next session, and that session tells you it happened.
518
+ - **New major** (`2.x → 3.0.0`): it can change how the harness works, so the assistant asks first.
519
+ - **Failed:** the assistant asks instead, and rsc retries once a day. The output is in
520
+ `.rsc/auto-update.log`.
521
+
522
+ A new install asks whether you want this (yes by default). To turn it off later, create
523
+ `.rsc/.no-auto-update`; every release then asks. It is a project decision: it travels in
524
+ `.rsc.json` like the other switches. The update can leave harness files changed in git; rsc never
525
+ commits them for you.
526
+
527
+ Where it runs on its own: **Claude Code, Codex, Gemini CLI, Cursor and OpenCode**, from a
528
+ session-start hook. Every other assistant has no such hook, so the always-on instructions ask the
529
+ agent to run `node .rsc/auto-update.mjs` on its first turn — same rules, but it depends on the agent
530
+ doing it (and some assistants ask your permission before running a command). Codex asks you to
531
+ trust new hooks once.
532
+
533
+ To update by hand, bump the package, then re-sync what's already wired into your project:
408
534
 
409
535
  ```bash
410
536
  npm install -g @ericrisco/rsc@latest # global install: pull the newest catalog
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.1.0",
2
+ "version": "3.0.0",
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": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "A guided harness builder that turns your goal into a minimal, verified AI-agent setup using strict best practices.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,6 +22,8 @@ import {
22
22
  import {
23
23
  wireMemory, unwireMemory, memoryManagedPaths, memoryModeFor, memoryEnabledForProject, memoryArtifactsPresent,
24
24
  } from '../targets/memory.js';
25
+ import { wireUpdate, unwireUpdate, updateManagedPaths, updateArtifactsPresent } from '../targets/update-wiring.js';
26
+ import { wireKnowledge, unwireKnowledge, knowledgeManagedPaths, knowledgeArtifactsPresent } from '../targets/knowledge-wiring.js';
25
27
 
26
28
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
27
29
  const CLI_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version;
@@ -78,6 +80,10 @@ export function generatedHookFiles({ target, cwd, policy }) {
78
80
  join(cwd, '.rsc', 'worklog-checkpoint.mjs'),
79
81
  join(cwd, '.rsc', 'hook-once.mjs'),
80
82
  join(cwd, '.rsc', 'worktree-reaper.mjs'),
83
+ join(cwd, '.rsc', 'auto-update.mjs'),
84
+ // team-safe-default: the 3.0 start-up duties and the trunk policy (also read by branch-guard).
85
+ join(cwd, '.rsc', 'team-safe-start.mjs'),
86
+ join(cwd, '.rsc', 'trunk-policy.mjs'),
81
87
  ];
82
88
  // The danger guard is declared on its own terms (#273): present unless the plan says otherwise,
83
89
  // whether or not the code guards are.
@@ -85,6 +91,7 @@ export function generatedHookFiles({ target, cwd, policy }) {
85
91
  if (policy?.codeHooks === false) return [...lifecycle, ...danger, join(cwd, '.rsc', 'suggest-always-on.md')];
86
92
  return [...lifecycle, ...danger,
87
93
  join(cwd, '.rsc', 'ship-guard.mjs'),
94
+ join(cwd, '.rsc', 'branch-guard.mjs'),
88
95
  join(cwd, '.rsc', 'gitmoji-guard.mjs'), join(cwd, '.rsc', 'userprompt-gate.mjs'),
89
96
  join(cwd, '.rsc', 'sello.mjs')];
90
97
  }
@@ -95,6 +102,8 @@ export function managedPathsForInstall({ skillIds, agentIds = [], target, home,
95
102
  const out = [paths.stateFile, versionFile(cwd), baseVersionsFile(cwd)];
96
103
  if (policy?.context7 === false) out.push(join(cwd, '.rsc', '.no-context7'));
97
104
  if (policy?.memory !== false) out.push(...memoryManagedPaths(target, cwd));
105
+ out.push(...updateManagedPaths(target, cwd));
106
+ out.push(...knowledgeManagedPaths(target, cwd));
98
107
  if (targetHasAgents(target)) {
99
108
  const state = readState(paths.stateFile);
100
109
  const explicit = [...new Set([...(state.explicitAgents || readManifest(cwd)?.agents || []), ...agentIds])];
@@ -297,6 +306,12 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
297
306
  ? { mode: 'disabled', reason: 'onboarding-policy', paths: unwireMemory(target, cwd) }
298
307
  : wireMemory(target, cwd);
299
308
  state.memory = { mode: memoryResult.mode, reason: memoryResult.reason, paths: memoryResult.paths };
309
+ const updateResult = wireUpdate(target, cwd);
310
+ state.update = { mode: updateResult.mode, ...(updateResult.reason ? { reason: updateResult.reason } : {}) };
311
+ // Knowledge sync is its own decision (`.no-knowledge-sync`, checked at runtime) and never part of
312
+ // the memory, so the memory's policy does not decide it: wired wherever the assistant can run it.
313
+ const knowledgeResult = wireKnowledge(target, cwd);
314
+ state.knowledge = { mode: knowledgeResult.mode, reason: knowledgeResult.reason || null, paths: knowledgeResult.paths };
300
315
  const context7OptOut = join(cwd, '.rsc', '.no-context7');
301
316
  if (policy?.context7 === false) {
302
317
  mkdirSync(dirname(context7OptOut), { recursive: true });
@@ -596,6 +611,8 @@ export function removeTargetInstall({ target, home, cwd = process.cwd() }) {
596
611
  }
597
612
  unwireHook(target, paths);
598
613
  unwireMemory(target, cwd);
614
+ unwireUpdate(target, cwd);
615
+ unwireKnowledge(target, cwd);
599
616
  rmSync(paths.stateFile, { force: true });
600
617
  const gitignore = join(cwd, '.gitignore');
601
618
  if (existsSync(gitignore)) {
@@ -704,8 +721,12 @@ export async function purge({ home, cwd = process.cwd(), withDocs = false, dryRu
704
721
  if (!dryRun) {
705
722
  removed.push(...unwireHook(target, paths));
706
723
  removed.push(...unwireMemory(target, cwd));
724
+ removed.push(...unwireUpdate(target, cwd));
725
+ removed.push(...unwireKnowledge(target, cwd));
707
726
  } else {
708
727
  removed.push(...memoryArtifactsPresent(target, cwd));
728
+ removed.push(...updateArtifactsPresent(target, cwd));
729
+ removed.push(...knowledgeArtifactsPresent(target, cwd));
709
730
  }
710
731
  // A lost state file also loses proof of ownership. Leave same-named user files
711
732
  // behind rather than guessing from a catalog id and deleting their work.
package/scripts/rsc.js CHANGED
@@ -44,7 +44,7 @@ if (['--version', '-v', 'version'].includes(rawArgv[0])) {
44
44
  const GLOBAL_VALUE_FLAGS = new Set([
45
45
  '--target', '--technical-level', '--accompaniment', '--project-kind', '--goal', '--goal-base64', '--software-scope', '--accept-plan',
46
46
  ]);
47
- const COMMANDS = new Set(['onboard', 'reassess', 'add', 'install', 'consult', 'catalog', 'audit', 'list', 'doctor', 'memory', 'sync', 'backups', 'restore', 'upgrade', 'registry', 'worktrees', 'capabilities', 'sello', 'repair', 'uninstall', 'purge']);
47
+ const COMMANDS = new Set(['onboard', 'reassess', 'add', 'install', 'consult', 'catalog', 'audit', 'list', 'doctor', 'memory', 'knowledge-sync', 'main', 'isolation', 'sync', 'backups', 'restore', 'upgrade', 'registry', 'worktrees', 'capabilities', 'sello', 'repair', 'uninstall', 'purge']);
48
48
  function positionalTokens(input) {
49
49
  const out = [];
50
50
  for (let i = 0; i < input.length; i++) {
@@ -766,6 +766,72 @@ async function main() {
766
766
  printContextBudget(report.contextBudget);
767
767
  return void say(JSON.stringify({ ...report, contextBudget: undefined }, null, 2));
768
768
  }
769
+ case 'main':
770
+ case 'isolation': {
771
+ // team-safe-default: the two switches a person asks the agent for in plain words —
772
+ // «desbloquea main» / «bloquea main», «no uses worktrees» / «usa worktrees». Both are PROJECT
773
+ // decisions (recorded in .rsc.json optOuts by the sync below) because one person's answer is
774
+ // the team's: the default branch is open or closed for every agent working on the repo.
775
+ const isMain = cmd === 'main';
776
+ const sub = argv[1] || 'status';
777
+ const root = process.cwd();
778
+ const { existsSync, mkdirSync, writeFileSync, rmSync } = await import('node:fs');
779
+ const { join } = await import('node:path');
780
+ const marker = join(root, '.rsc', isMain ? '.no-trunk-guard' : '.no-worktree-isolation');
781
+ const OFF = isMain ? 'unlock' : 'off';
782
+ const ON = isMain ? 'lock' : 'on';
783
+ if (sub === OFF || sub === ON) {
784
+ mkdirSync(join(root, '.rsc'), { recursive: true });
785
+ if (sub === OFF) writeFileSync(marker, '');
786
+ else rmSync(marker, { force: true });
787
+ for (const t of targets) await syncInstalled({ target: t, cwd: root });
788
+ say(isMain
789
+ ? (sub === OFF
790
+ ? 'rsc main unlock: the agent may now work on the default branch in this project. Commit .rsc.json so the team gets the same decision.'
791
+ : 'rsc main lock: the default branch is closed for the agent again where the project looks complex or in production. Commit .rsc.json so the team gets the same decision.')
792
+ : (sub === OFF
793
+ ? 'rsc isolation off: sessions may share this folder without a worktree. Commit .rsc.json so the team gets the same decision.'
794
+ : 'rsc isolation on: with another session active in this folder, new work goes to .worktrees/<branch>/. Commit .rsc.json so the team gets the same decision.'));
795
+ return;
796
+ }
797
+ if (sub === 'status') {
798
+ if (isMain) {
799
+ const { trunkPolicy } = await import('../targets/trunk-policy.mjs');
800
+ return void say(JSON.stringify(trunkPolicy(root), null, 2));
801
+ }
802
+ const { otherActiveSessions } = await import('../targets/session-memory-core.mjs');
803
+ return void say(JSON.stringify({ isolation: existsSync(marker) ? 'off' : 'on', activeSessions: otherActiveSessions({ cwd: root }) }, null, 2));
804
+ }
805
+ say(`Use: npx @ericrisco/rsc ${cmd} ${ON}|${OFF}|status`);
806
+ process.exitCode = 2;
807
+ return;
808
+ }
809
+ case 'knowledge-sync': {
810
+ // On by default; the switch is a PROJECT decision (`.no-knowledge-sync`, recorded in `.rsc.json`
811
+ // optOuts by the sync below), because one person off means the team stops seeing their work.
812
+ const sub = argv[1] || 'status';
813
+ const root = process.cwd();
814
+ const { mkdirSync, writeFileSync, rmSync } = await import('node:fs');
815
+ const { join } = await import('node:path');
816
+ const marker = join(root, '.rsc', '.no-knowledge-sync');
817
+ if (sub === 'on' || sub === 'off') {
818
+ mkdirSync(join(root, '.rsc'), { recursive: true });
819
+ if (sub === 'off') writeFileSync(marker, '');
820
+ else rmSync(marker, { force: true });
821
+ for (const t of targets) await syncInstalled({ target: t, cwd: root });
822
+ say(sub === 'off'
823
+ ? 'rsc knowledge-sync off: 01-TOOLS/ and 02-DOCS/ no longer sync on their own in this project. Commit .rsc.json so the team gets the same decision.'
824
+ : 'rsc knowledge-sync on: 01-TOOLS/ and 02-DOCS/ go up when a turn ends and come down before each message. Commit .rsc.json so the team gets the same decision.');
825
+ return;
826
+ }
827
+ if (sub === 'status') {
828
+ const { knowledgeStatus } = await import('../targets/knowledge-sync.mjs');
829
+ return void say(JSON.stringify(knowledgeStatus(root), null, 2));
830
+ }
831
+ say('Use: npx @ericrisco/rsc knowledge-sync on|off|status');
832
+ process.exitCode = 2;
833
+ return;
834
+ }
769
835
  case 'memory': {
770
836
  const sub = argv[1] || 'status';
771
837
  const root = process.cwd();
@@ -53,10 +53,16 @@ the chain: a result asserted without evidence is treated as not done.
53
53
 
54
54
  ## Isolation
55
55
 
56
- If the work writes code, it runs on a branch — never on the default branch, so `origin` and the local
57
- tree stay clean. **No worktree**: this lane is short enough that setting one up and tearing it down
58
- costs more than it protects. Work that only touches documentation, the wiki or configuration needs no
59
- branch at all.
56
+ Where the work runs depends on the project, judged change by change (team-safe-default):
57
+
58
+ - **Complex or in production** (deploys, CI, several people committing, a protected default branch)
59
+ → a branch, never the default branch. Open it yourself, named by intent, and close it with a pull
60
+ request. Something unrelated to the current branch gets its own branch from the default one.
61
+ - **Simple** (scripts, research, personal) → the default branch is fine.
62
+ - **Another session working in this same folder** → a worktree in `.worktrees/<branch>/`, never a
63
+ branch switch under the other session. rsc tells you when that is the case.
64
+
65
+ A worktree only when another session is there: alone, setting one up costs more than it protects.
60
66
 
61
67
  `../worktrees/SKILL.md` owns the isolation mechanics if you need them. Once the branch lands, the
62
68
  cleanup is automatic and you do not run anything.
@@ -34,7 +34,7 @@ should_not_trigger:
34
34
 
35
35
  - prompt: "No tengo claro si los permisos van por rol o por recurso, y afecta al esquema, a la API y a la UI."
36
36
  route_to: "specify"
37
- why: "A substantial unresolved ambiguity that durable spec/plan/tasks would settle. The harness proposes the chain here; only an explicit request or an accepted proposal enters it."
37
+ why: "A substantial unresolved ambiguity that durable spec/plan/tasks would settle. Several interlocking decisions: the agent enters the chain itself and says why (team-safe-default)."
38
38
 
39
39
  - prompt: "Quiero especificar bien el sistema de permisos antes de tocar nada."
40
40
  route_to: "specify"
@@ -3,7 +3,7 @@
3
3
  "lanes": {
4
4
  "answer": "information only; writes nothing, creates no artifact",
5
5
  "ftd": "authorised change, ordinary; one feature document",
6
- "sdd": "the ten-phase chain, by explicit request or accepted proposal only"
6
+ "sdd": "the ten-phase chain, entered by the agent for big or complex work"
7
7
  },
8
8
  "cases": [
9
9
  { "request": "¿Por qué el login tarda tanto en producción?", "expect": "answer", "why": "investigation; nothing authorises a change" },
@@ -19,6 +19,6 @@
19
19
  { "request": "Migra los tres endpoints al nuevo formato de respuesta", "expect": "ftd", "why": "many files, but nothing a spec would settle that the code does not" },
20
20
  { "request": "Quiero especificar bien el sistema de permisos antes de tocar nada", "expect": "sdd", "why": "explicit request for the chain" },
21
21
  { "request": "Vamos a hacer una spec de esto", "expect": "sdd", "why": "explicit request, stated in the user's own words" },
22
- { "request": "No tengo claro si los permisos van por rol o por recurso, y afecta al esquema, a la API y a la UI", "expect": "sdd", "why": "propose the chain: durable artifacts would settle a substantial ambiguity, and only an accepted proposal enters it" }
22
+ { "request": "No tengo claro si los permisos van por rol o por recurso, y afecta al esquema, a la API y a la UI", "expect": "sdd", "why": "enter the chain: several interlocking decisions that durable artifacts would settle; the agent enters it and says why" }
23
23
  ]
24
24
  }
@@ -137,7 +137,7 @@ cargan al arrancar la sesión."* Full skill map and sample printouts → `refere
137
137
 
138
138
  ### Phase 4 — GROUND
139
139
 
140
- Four checks once the skills are in. The SessionStart hook nudges each of these too; doing them here
140
+ Five checks once the skills are in. The SessionStart hook nudges each of these too; doing them here
141
141
  means the user starts clean.
142
142
 
143
143
  1. **Version control.** No `.git/` → offer `git init`; the SDD chain and the ship guard assume it.
@@ -154,6 +154,15 @@ means the user starts clean.
154
154
  `WHERE`, `dd` to a device, `curl | bash` — and asks for a safer alternative. A fully `technical`
155
155
  user is never guarded. It turns off only if the user explicitly asks: `.rsc/.no-danger-guard`.
156
156
  Mention it when you record `non-technical`, so a later block is not a surprise.
157
+ 5. **Automatic updates.** Ask once, yes by default:
158
+ > *"Cuando salga una versión nueva de rsc, ¿la instalo sola? Las que cambian mucho (major) te las
159
+ > preguntaré siempre. (sí/no)"*
160
+
161
+ > *"When a new rsc version comes out, should I install it on my own? Big ones (major) I will
162
+ > always ask about. (yes/no)"*
163
+
164
+ Yes, or no clear answer → do nothing; it is on. No → create `.rsc/.no-auto-update`: every
165
+ release then asks. It is a project switch, recorded in `.rsc.json` on the next sync.
157
166
 
158
167
  ### Phase 5 — HANDOFF
159
168
 
@@ -93,6 +93,12 @@ This mirrors the harness "siempre 3 opciones" pattern. Gather the one fact that
93
93
  | **2. Pull request** | Push the branch, open a PR with a spec-linked body, let CI / a human gate the merge | `main` is protected; a team or CI must sign off; you want the change reviewable in the forge even if you self-merge |
94
94
  | **3. Park or discard** | Keep the branch un-merged (park) or delete it (discard) | The approach was superseded, the spike answered its question, or the work is paused — it should not land |
95
95
 
96
+ **When the default branch is closed for the agent** (team-safe-default: the project is complex or in
97
+ production — `npx @ericrisco/rsc main status` says `closed: true`), **option 1 is not offered**: a
98
+ local merge is a commit on the default branch. Present option 2 (the recommendation) and option 3,
99
+ and say in one line why option 1 is absent and that «desbloquea main» (`npx @ericrisco/rsc main
100
+ unlock`) would bring it back. The branch guard denies the local merge anyway.
101
+
96
102
  Recommend based on repo signals: protected `main` or an existing PR culture (look for `.github/`, prior PRs via `gh pr list`) → recommend **option 2**. A solo project with no protection and a passed review → **option 1** is honest and faster. Never default to a PR ceremony the repo doesn't use, and never force-merge a repo that gates `main`.
97
103
 
98
104
  ### Delivery strategy from SDD config
@@ -26,15 +26,15 @@ recommend. Read-only: write nothing, create no artifact, delegate no writer. Ask
26
26
  about* building something is still this lane; only asking to build authorises it. When change
27
27
  intent is ambiguous, ask one question and stay here — ambiguity slows the lane, never raises it.
28
28
 
29
- **FTD — Fast-Track Development** — the request authorises a change. The default for ordinary work,
30
- entered without ceremony. One feature document per feature in `02-DOCS`: intent, scope, checklist,
31
- evidence, next step. Tasks are checked off against observed proof, never intention. A branch if it
32
- writes code, none if it only touches docs, wiki or config; never a worktree. → `../ftd/SKILL.md`.
33
-
34
- **SDD** — the ten-phase chain, unchanged, and **never entered by the harness alone**. Propose it only
35
- when durable spec/plan/tasks would remove a *substantial* ambiguity — a test of usefulness, not of
36
- size — and enter it only on explicit request or an accepted proposal. Size, file count and perceived
37
- risk never select it. → `../sdd/SKILL.md`.
29
+ **You choose the lane; never hand that choice to the person.** Simple → **FTD**: one feature document
30
+ in `02-DOCS` (intent, scope, checklist, evidence, next step), tasks checked off against observed proof.
31
+ Big, or decisions that affect each other → **SDD**: enter the chain; the person approves the spec and
32
+ clarify, then picks manual or autopilot. Say which and why in one line; switch if asked.
33
+ → `../ftd/SKILL.md` · `../sdd/SKILL.md`.
34
+
35
+ **Where.** Complex or in production → never commit on the default branch: branch yourself, close with
36
+ a PR. Simple → the default branch. Another session in this checkout → `.worktrees/<branch>`. «desbloquea
37
+ main» = `rsc main unlock`; «no uses worktrees» = `rsc isolation off`.
38
38
 
39
39
  Judge the **meaning**, not the wording: the trigger is semantic in any language. A bug fix restoring
40
40
  intended behaviour is `debug`. Autopilot consent covers a whole run — advance without re-asking.
@@ -44,6 +44,7 @@ Both isolate history. A worktree also isolates the *filesystem*. Pick by what's
44
44
  | You'll run two streams of work at once (see the parallel pattern) | **worktree** | Each stream gets its own directory; no stash-juggling, no checkout thrash. |
45
45
  | The current tree is dirty and the user wants both the WIP and the new work | **worktree** | Stash-and-switch risks losing the WIP; a worktree sidesteps the stash entirely. |
46
46
  | You're an agent that may keep the main session running elsewhere | **worktree** | Filesystem isolation is the whole point — the parent session keeps its files. |
47
+ | **Another session is working in this same folder** (rsc says so on each message) | **worktree — mandatory** | Switching branches would change its files under it; rsc's branch guard denies the switch. Tell the person that `.worktrees/<branch>/` is where the project now runs. Off for the project: `npx @ericrisco/rsc isolation off`. |
47
48
 
48
49
  When in doubt, prefer a **worktree**: it is the strictly stronger isolation and the cost is one
49
50
  extra directory. The rest of this skill assumes a worktree; the branch path is the degenerate case
@@ -13,6 +13,12 @@ const MARK_END = '<!-- rsc-suggest:end -->';
13
13
  // The exact separator wireHook writes before an appended block, so unwireHook can take
14
14
  // back precisely what it gave and leave the rest of the file untouched.
15
15
  const SEAM = '\n\n';
16
+ // Most assistants that read a rules file have no session-start hook, so nothing checks for a new rsc
17
+ // on its own. The agent does it instead. Here and not in suggest's body: the body is paid on every
18
+ // turn by every assistant, Claude Code included, which already checks from its hook.
19
+ export const UPDATE_NOTE = '**rsc updates.** Claude Code, Codex, Gemini CLI, Cursor and OpenCode check for a new rsc on their '
20
+ + 'own. In any other assistant, run `node .rsc/auto-update.mjs` once on the first turn of a session and relay any '
21
+ + 'notice it prints in one line; "up to date" needs no mention.\n';
16
22
 
17
23
  export function writeSkill(id, fromDir, toPath) {
18
24
  return linkOrCopy(fromDir, toPath);
@@ -23,7 +29,7 @@ export function wireHook(paths, sourceMd, policy = {}) {
23
29
  const body = policy.codeHooks === false
24
30
  ? `# rsc-suggest — always-on operations layer\n\nRead \`02-DOCS/wiki/harness/user-profile.md\` before acting. Use \`orient\` to keep the user situated and \`suggest\` to offer a missing skill only when the current task needs it. Close with the configured orientation block.\n`
25
31
  : full;
26
- const block = `${MARK_START}\n${body}\n${MARK_END}`;
32
+ const block = `${MARK_START}\n${body}\n${UPDATE_NOTE}\n${MARK_END}`;
27
33
  let doc = existsSync(paths.hookTarget) ? readFileSync(paths.hookTarget, 'utf8') : '';
28
34
  if (doc.includes(MARK_START)) {
29
35
  doc = doc.replace(new RegExp(`${MARK_START}[\\s\\S]*?${MARK_END}`), block);