@ericrisco/rsc 2.0.17 → 2.0.19
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 +4 -1
- package/manifest.json +1 -1
- package/package.json +9 -1
- package/scripts/doctor.js +5 -2
- package/scripts/install-apply.js +8 -0
- package/scripts/lib/backups.js +13 -0
- package/scripts/rsc.js +19 -2
package/README.md
CHANGED
|
@@ -71,6 +71,9 @@ accompaniment, project kind and goal **before it writes anything**, reads only t
|
|
|
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.
|
|
74
|
+
If the project already has a skill of its own with a catalog name, the plan lists it under
|
|
75
|
+
**Your own skills this plan replaces** — tell the user before accepting; renaming their folder and
|
|
76
|
+
regenerating the plan keeps theirs.
|
|
74
77
|
|
|
75
78
|
For a non-interactive agent, collect the answers and preview the same plan explicitly:
|
|
76
79
|
|
|
@@ -273,7 +276,7 @@ rsc consult "I want to launch a SaaS" # recommend only, no install
|
|
|
273
276
|
rsc registry refresh # write .rsc/skill-registry.{json,md}
|
|
274
277
|
rsc list # installed skills, agents and commands
|
|
275
278
|
rsc capabilities # installed/available surfaces + memory mode
|
|
276
|
-
rsc doctor # health, missing backing, hooks and local memory
|
|
279
|
+
rsc doctor # health, missing backing, hooks and local memory; exits 1 when unhealthy
|
|
277
280
|
rsc memory status # full / assisted / unsupported / degraded
|
|
278
281
|
rsc memory save --session handoff # force a deterministic local checkpoint
|
|
279
282
|
rsc memory resume # print this branch/worktree continuation
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ericrisco/rsc",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.19",
|
|
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": {
|
|
@@ -97,6 +97,14 @@
|
|
|
97
97
|
{
|
|
98
98
|
"fixedIn": "2.0.17",
|
|
99
99
|
"symptom": "Re-accepting an onboarding plan erased what init had learned about you, reset your explanation level, or dropped an assistant you added later"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"fixedIn": "2.0.18",
|
|
103
|
+
"symptom": "Onboarding silently replaced a skill you wrote yourself that had the same name as one of rsc's (review, plan, debug…)"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"fixedIn": "2.0.19",
|
|
107
|
+
"symptom": "rsc doctor reported missing pieces or an unwired hook but still exited with 0, so a CI or an editor extension could not tell"
|
|
100
108
|
}
|
|
101
109
|
]
|
|
102
110
|
}
|
package/scripts/doctor.js
CHANGED
|
@@ -201,7 +201,7 @@ export function doctor({ target, home, cwd }) {
|
|
|
201
201
|
const missingAgents = (state.agents || []).filter((id) => {
|
|
202
202
|
const path = agentPath(target, root, id);
|
|
203
203
|
return path && !existsSync(path);
|
|
204
|
-
}).map((id) => ({ id, action: 'Run `npx @ericrisco/rsc sync` to restore this managed agent.' }));
|
|
204
|
+
}).map((id) => ({ id, path: agentPath(target, root, id), action: 'Run `npx @ericrisco/rsc sync` to restore this managed agent.' }));
|
|
205
205
|
const report = {
|
|
206
206
|
target,
|
|
207
207
|
installed: Object.keys(state.skills),
|
|
@@ -251,9 +251,12 @@ export function doctor({ target, home, cwd }) {
|
|
|
251
251
|
commandOrphans,
|
|
252
252
|
commandCollisions: state.commandCollisions || [],
|
|
253
253
|
};
|
|
254
|
+
// #277 — the three "missing" lists share one shape, { id, path, action }, so whoever builds on top
|
|
255
|
+
// of doctor reads them the same way; and `healthy` is what the exit code says.
|
|
254
256
|
for (const [id, e] of Object.entries(state.skills)) {
|
|
255
|
-
for (const f of e.files) if (!existsSync(f)) report.missing.push(
|
|
257
|
+
for (const f of e.files) if (!existsSync(f)) report.missing.push({ id, path: f, action: 'Run `npx @ericrisco/rsc sync` to restore this managed skill.' });
|
|
256
258
|
}
|
|
259
|
+
report.healthy = report.hookWired && !report.missing.length && !missingAgents.length && !missingCommands.length;
|
|
257
260
|
return report;
|
|
258
261
|
}
|
|
259
262
|
|
package/scripts/install-apply.js
CHANGED
|
@@ -422,6 +422,14 @@ export function collisions({ cwd = process.cwd(), target, home, skillIds = [] })
|
|
|
422
422
|
return skillIds.filter((id) => !managed.has(id) && existsSync(paths.skillDir(id)));
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
// The same test as `collisions`, across every target of a plan, as the paths the user would recognise.
|
|
426
|
+
// `onboard` puts these in the plan itself, so the id the user accepts is an id that names them.
|
|
427
|
+
export function ownSkillPaths({ cwd = process.cwd(), targets = [], skillIds = [], home }) {
|
|
428
|
+
const found = targets.flatMap((target) => collisions({ cwd, target, home, skillIds })
|
|
429
|
+
.map((id) => relative(cwd, targetPaths(target, home, cwd).skillDir(id)).split(sep).join('/')));
|
|
430
|
+
return [...new Set(found)].sort();
|
|
431
|
+
}
|
|
432
|
+
|
|
425
433
|
export function listInstalled({ target, home, cwd = process.cwd() }) {
|
|
426
434
|
const paths = targetPaths(target, home, cwd);
|
|
427
435
|
return Object.keys(readState(paths.stateFile).skills);
|
package/scripts/lib/backups.js
CHANGED
|
@@ -40,6 +40,19 @@ export function createBackup({ cwd = process.cwd(), operation, target, paths, cl
|
|
|
40
40
|
return manifest;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Where the newest snapshot kept a path, relative to the project — what to tell someone whose own work
|
|
44
|
+
// was just replaced, so recovering it is a copy they can find rather than a restore they never heard of.
|
|
45
|
+
// A transaction snapshot is the fallback: it is the install's rollback, not the user's copy.
|
|
46
|
+
export function whereKept({ cwd = process.cwd(), relPath }) {
|
|
47
|
+
const backups = listBackups({ cwd });
|
|
48
|
+
const ordered = [...backups.filter((b) => !/transaction/.test(b.operation)), ...backups.filter((b) => /transaction/.test(b.operation))];
|
|
49
|
+
for (const b of ordered) {
|
|
50
|
+
const kept = join(backupsDir(cwd), b.id, 'files', relPath);
|
|
51
|
+
if (existsSync(kept)) return relative(cwd, kept).split(sep).join('/');
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
export function listBackups({ cwd = process.cwd() } = {}) {
|
|
44
57
|
const dir = backupsDir(cwd);
|
|
45
58
|
if (!existsSync(dir)) return [];
|
package/scripts/rsc.js
CHANGED
|
@@ -4,14 +4,14 @@ import { detectTarget, installedTargets, resolveTargets, TARGETS } from '../targ
|
|
|
4
4
|
import { detectRepo } from './detect-repo.js';
|
|
5
5
|
import { rank } from './consult.js';
|
|
6
6
|
import { expandRecommends, toOutcomes, hasOutcome } from './lib/recommend.js';
|
|
7
|
-
import { applyInstall, listInstalled, listInstalledAgents, listInstalledCommands, uninstall, syncInstalled, purge, collisions } from './install-apply.js';
|
|
7
|
+
import { applyInstall, listInstalled, listInstalledAgents, listInstalledCommands, uninstall, syncInstalled, purge, collisions, ownSkillPaths } from './install-apply.js';
|
|
8
8
|
import { stackAgentNames } from '../targets/agents.js';
|
|
9
9
|
import { doctor } from './doctor.js';
|
|
10
10
|
import { ask, say, select, pickFrom, banner, confirm, isInteractive } from './lib/ui.js';
|
|
11
11
|
import { refreshRegistry, registryStatus } from './lib/registry.js';
|
|
12
12
|
import { audit, writeAuditReport } from './audit.js';
|
|
13
13
|
import { DOMAINS } from './lib/domains.js';
|
|
14
|
-
import { listBackups, restoreBackup } from './lib/backups.js';
|
|
14
|
+
import { listBackups, restoreBackup, whereKept } from './lib/backups.js';
|
|
15
15
|
import { runUpgrade } from './lib/upgrade.js';
|
|
16
16
|
import { diagnose, repair } from './lib/repair.js';
|
|
17
17
|
import { DEFAULT_SKILL_FLOOR, withDefaultSkillFloor } from './lib/default-skill-floor.js';
|
|
@@ -142,6 +142,12 @@ function renderPlan(plan, planId) {
|
|
|
142
142
|
for (const decision of plan.decisions.filter((d) => d.state === 'excluded')) say(` × ${decision.kind}/${decision.id} — ${decision.reason}`);
|
|
143
143
|
say('Managed paths:');
|
|
144
144
|
for (const path of plan.governedPaths) say(` ${path}`);
|
|
145
|
+
if (plan.replacesUserSkills?.length) {
|
|
146
|
+
say('Your own skills this plan replaces:');
|
|
147
|
+
for (const path of plan.replacesUserSkills) say(` ! ${path} — already here and not put by rsc; the catalog skill of the same name takes its place, and yours is kept in .rsc/backups/`);
|
|
148
|
+
say(' To keep yours, rename its folder (for example review → review-own) and generate the plan again.');
|
|
149
|
+
say(' AGENT: name each of these to the user before accepting, and ask whether to replace or rename.');
|
|
150
|
+
}
|
|
145
151
|
if (plan.evidence.parentHarness) say(`Parent harness detected at ${plan.evidence.parentHarness}; it is not inherited by this plan.`);
|
|
146
152
|
const pieces = [
|
|
147
153
|
'npx @ericrisco/rsc@latest onboard',
|
|
@@ -201,6 +207,11 @@ async function runOnboarding(targets) {
|
|
|
201
207
|
return;
|
|
202
208
|
}
|
|
203
209
|
const plan = buildOnboardingPlan(record, scanProject(process.cwd()));
|
|
210
|
+
// #275 — a folder where a catalog skill goes that rsc did not put is the user's own work. It goes
|
|
211
|
+
// INTO the plan, so the id they accept names it: an id made before that folder existed no longer
|
|
212
|
+
// applies, and accepting is never consent to something the plan did not show.
|
|
213
|
+
const replacesUserSkills = ownSkillPaths({ cwd: process.cwd(), targets: plan.policy.targets, skillIds: plan.policy.skills });
|
|
214
|
+
if (replacesUserSkills.length) plan.replacesUserSkills = replacesUserSkills;
|
|
204
215
|
const planId = identifyPlan(plan);
|
|
205
216
|
const accepted = flag('accept-plan');
|
|
206
217
|
if (!accepted) {
|
|
@@ -229,6 +240,10 @@ async function runOnboarding(targets) {
|
|
|
229
240
|
} catch (error) {
|
|
230
241
|
say(`RSC_SKELETON_FAILED ${error.message}`);
|
|
231
242
|
}
|
|
243
|
+
for (const path of plan.replacesUserSkills || []) {
|
|
244
|
+
const kept = whereKept({ cwd: process.cwd(), relPath: path });
|
|
245
|
+
if (kept) say(`Your previous ${path} is kept in ${kept}`);
|
|
246
|
+
}
|
|
232
247
|
emitHarnessReadiness(plan, planId);
|
|
233
248
|
}
|
|
234
249
|
|
|
@@ -743,6 +758,8 @@ async function main() {
|
|
|
743
758
|
}
|
|
744
759
|
case 'doctor': {
|
|
745
760
|
const report = doctor({ target });
|
|
761
|
+
// #277 — an automation on top of rsc reads the exit code, not the prose.
|
|
762
|
+
if (!report.healthy) process.exitCode = 1;
|
|
746
763
|
if (argv.includes('--json')) return void say(JSON.stringify(report, null, 2));
|
|
747
764
|
printContextBudget(report.contextBudget);
|
|
748
765
|
return void say(JSON.stringify({ ...report, contextBudget: undefined }, null, 2));
|