@ericrisco/rsc 2.0.0 → 2.0.1
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/manifest.json
CHANGED
package/package.json
CHANGED
package/scripts/doctor.js
CHANGED
|
@@ -445,6 +445,18 @@ export function contextBudget({ target, home = homedir(), cwd = process.cwd() }
|
|
|
445
445
|
});
|
|
446
446
|
}
|
|
447
447
|
const drift = divergence({ cwd, target, home });
|
|
448
|
+
// A separate finding, not a fourth clause of the one below: that one says this checkout
|
|
449
|
+
// is behind its own team, and the fix is to align with the declaration. This one says the
|
|
450
|
+
// declaration itself is behind the catalog, so aligning with it changes nothing. Folded
|
|
451
|
+
// together, the report would name the right skill and the wrong reason.
|
|
452
|
+
if (drift.floorMissing?.length) {
|
|
453
|
+
findings.push({
|
|
454
|
+
id: 'floor-missing',
|
|
455
|
+
severity: 'high',
|
|
456
|
+
summary: `.rsc.json predates a skill the catalog now requires: ${drift.floorMissing.join(', ')}. The always-on body routes work to it, so that route currently answers nowhere.`,
|
|
457
|
+
action: 'Run `npx @ericrisco/rsc sync` — it adds what the catalog requires and touches nothing else.',
|
|
458
|
+
});
|
|
459
|
+
}
|
|
448
460
|
if (drift.missing.length || drift.extra.length || drift.ownMissing.length) {
|
|
449
461
|
// The day-two case: a teammate changed the harness and this checkout has not caught
|
|
450
462
|
// up. Reported always, even after someone declines to align — the divergence does not
|
package/scripts/install-apply.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
resolveAgentNames, agentByName, allAgentNames, readDeveloperTier,
|
|
11
11
|
} from '../targets/agents.js';
|
|
12
12
|
import { readState, writeState } from './lib/state.js';
|
|
13
|
+
import { withDefaultSkillFloor } from './lib/default-skill-floor.js';
|
|
13
14
|
import { readManifest, writeManifest } from './lib/manifest-file.js';
|
|
14
15
|
import { createBackup } from './lib/backups.js';
|
|
15
16
|
import {
|
|
@@ -534,9 +535,16 @@ export async function syncInstalled({ target, home, cwd = process.cwd(), dryRun
|
|
|
534
535
|
// then rebuilt from a governed list that predated it and pruned the difference, recursively.
|
|
535
536
|
// Reported by a user and reproduced 2026-09-17. The receipt stays untouched, because it is
|
|
536
537
|
// hash-checked against what the user accepted and is not a log of what happened since.
|
|
537
|
-
const
|
|
538
|
+
const declaredRaw = governedSkills
|
|
538
539
|
? [...new Set([...governedSkills, ...(manifest?.skills || [])])].sort()
|
|
539
540
|
: (ids.length ? ids : (manifest?.skills || []));
|
|
541
|
+
// A declaration is frozen at the moment it was written, so a skill the catalog later makes
|
|
542
|
+
// mandatory never reaches an existing harness: `add` and `install` apply the floor, sync did
|
|
543
|
+
// not, and sync is the only one an upgrade runs. 2.0.0 shipped the three-lane decisor inside
|
|
544
|
+
// `suggest` — already declared, so already upgraded — routing ordinary work to an `ftd` that
|
|
545
|
+
// sync had no reason to fetch. Applied only to a harness that already declares something: a
|
|
546
|
+
// directory declaring nothing is not a harness missing its floor.
|
|
547
|
+
const declared = declaredRaw.length ? withDefaultSkillFloor(declaredRaw) : declaredRaw;
|
|
540
548
|
const declaredAgents = state.explicitAgents?.length ? state.explicitAgents : (manifest?.agents || []);
|
|
541
549
|
if (!declared.length && !declaredAgents.length) return dryRun ? { dryRun: true, synced: [], syncedAgents: [], paths: [] } : { synced: [], syncedAgents: [], backup: null };
|
|
542
550
|
if (dryRun) {
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
// `orient` and `suggest` are runtime always-on. `bro` is installed by default
|
|
5
5
|
// but remains conditionally loaded: its description routes human-language asks
|
|
6
6
|
// without paying for its body on unrelated turns.
|
|
7
|
-
|
|
7
|
+
//
|
|
8
|
+
// `ftd` joined in 2.0.1 by the same criterion as `suggest`: the always-on decisor
|
|
9
|
+
// routes ordinary work to `../ftd/SKILL.md`, so a harness without it has a default
|
|
10
|
+
// lane that points at nothing. Membership of every profile was not enough — a
|
|
11
|
+
// declaration written before the skill existed never gains it (see `syncInstalled`).
|
|
12
|
+
export const DEFAULT_SKILL_FLOOR = Object.freeze(['orient', 'suggest', 'bro', 'ftd']);
|
|
8
13
|
|
|
9
14
|
export function withDefaultSkillFloor(skillIds = []) {
|
|
10
15
|
return [...new Set([...DEFAULT_SKILL_FLOOR, ...skillIds])];
|
|
@@ -2,6 +2,7 @@ import { readManifest } from './manifest-file.js';
|
|
|
2
2
|
import { readState } from './state.js';
|
|
3
3
|
import { targetPaths } from '../../targets/index.js';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
|
+
import { DEFAULT_SKILL_FLOOR } from './default-skill-floor.js';
|
|
5
6
|
|
|
6
7
|
// What the team declared versus what is on this machine.
|
|
7
8
|
//
|
|
@@ -15,10 +16,13 @@ import { existsSync } from 'node:fs';
|
|
|
15
16
|
// ownMissing — declared team skill, absent from the repo. Aligning says so and writes
|
|
16
17
|
// nothing: it comes from the repo, not from us.
|
|
17
18
|
// extra — installed BY RSC, no longer declared.
|
|
19
|
+
// floorMissing — a skill the catalog makes mandatory that this declaration predates.
|
|
20
|
+
// Not drift between teammates: drift between the harness and the catalog,
|
|
21
|
+
// and the only bucket that survives everyone running the same `git pull`.
|
|
18
22
|
// A hand-written skill that nobody declared is in none of them. Not declaring is a valid
|
|
19
23
|
// way to keep things of your own, not an oversight to correct.
|
|
20
24
|
export function divergence({ cwd = process.cwd(), target, home } = {}) {
|
|
21
|
-
const empty = { missing: [], extra: [], ownMissing: [] };
|
|
25
|
+
const empty = { missing: [], extra: [], ownMissing: [], floorMissing: [] };
|
|
22
26
|
const manifest = readManifest(cwd);
|
|
23
27
|
if (!manifest) return empty;
|
|
24
28
|
|
|
@@ -29,5 +33,8 @@ export function divergence({ cwd = process.cwd(), target, home } = {}) {
|
|
|
29
33
|
missing: (manifest.skills || []).filter((id) => !installed.has(id)),
|
|
30
34
|
extra: [...installed].filter((id) => !(manifest.skills || []).includes(id)),
|
|
31
35
|
ownMissing: (manifest.ownSkills || []).filter((name) => !existsSync(paths.skillDir(name))),
|
|
36
|
+
// Read off the declaration, not off the disk: a skill installed by hand into a harness
|
|
37
|
+
// that still does not declare it is one sync away from being pruned again.
|
|
38
|
+
floorMissing: DEFAULT_SKILL_FLOOR.filter((id) => !(manifest.skills || []).includes(id)),
|
|
32
39
|
};
|
|
33
40
|
}
|