@ericrisco/rsc 2.0.18 → 2.0.20
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 +1 -1
- package/manifest.json +1 -1
- package/package.json +9 -1
- package/scripts/doctor.js +13 -4
- package/scripts/rsc.js +2 -0
package/README.md
CHANGED
|
@@ -276,7 +276,7 @@ rsc consult "I want to launch a SaaS" # recommend only, no install
|
|
|
276
276
|
rsc registry refresh # write .rsc/skill-registry.{json,md}
|
|
277
277
|
rsc list # installed skills, agents and commands
|
|
278
278
|
rsc capabilities # installed/available surfaces + memory mode
|
|
279
|
-
rsc doctor # health, missing backing, hooks and local memory
|
|
279
|
+
rsc doctor # health, missing backing, hooks and local memory; exits 1 when unhealthy
|
|
280
280
|
rsc memory status # full / assisted / unsupported / degraded
|
|
281
281
|
rsc memory save --session handoff # force a deterministic local checkpoint
|
|
282
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.20",
|
|
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": {
|
|
@@ -101,6 +101,14 @@
|
|
|
101
101
|
{
|
|
102
102
|
"fixedIn": "2.0.18",
|
|
103
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"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"fixedIn": "2.0.20",
|
|
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"
|
|
104
112
|
}
|
|
105
113
|
]
|
|
106
114
|
}
|
package/scripts/doctor.js
CHANGED
|
@@ -65,6 +65,10 @@ function selloStatus(root) {
|
|
|
65
65
|
//
|
|
66
66
|
// Hookless targets get an empty list by construction: their always-on surface is a
|
|
67
67
|
// markdown block with no script behind it, so there is nothing that can go missing.
|
|
68
|
+
// A quoted span counts as a path only if it starts like one and holds no shell operator: a whole
|
|
69
|
+
// `sh -c '…'` snippet is not a path, and reading it as one reports a present script missing.
|
|
70
|
+
const SCRIPT_RE =
|
|
71
|
+
/"((?:[A-Za-z]:)?[\\/~][^"&;|]*[\\/]\.rsc[\\/][^"\s]+?\.mjs)"|'((?:[A-Za-z]:)?[\\/~][^'&;|]*[\\/]\.rsc[\\/][^'\s]+?\.mjs)'|([^"'\s]*[\\/]\.rsc[\\/][^"'\s]+\.mjs)/g;
|
|
68
72
|
export function missingHookScripts({ target, home = homedir(), cwd = process.cwd() } = {}) {
|
|
69
73
|
if (HOOKLESS_TARGETS.has(target)) return [];
|
|
70
74
|
const file = targetPaths(target, home, cwd).hookTarget;
|
|
@@ -84,8 +88,10 @@ export function missingHookScripts({ target, home = homedir(), cwd = process.cwd
|
|
|
84
88
|
const expand = (c) => c.split('${CLAUDE_PROJECT_DIR}').join(cwd);
|
|
85
89
|
for (const raw of commands) {
|
|
86
90
|
const cmd = expand(raw);
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
// Quoted forms first: a project path may hold a space, and stopping at whitespace kept only
|
|
92
|
+
// the tail (`entreno/.rsc/x.mjs`), which then resolved against the cwd and read as missing (#283).
|
|
93
|
+
for (const m of cmd.matchAll(SCRIPT_RE)) {
|
|
94
|
+
const script = m[1] ?? m[2] ?? m[3];
|
|
89
95
|
if (!existsSync(script)) seen.add(script);
|
|
90
96
|
}
|
|
91
97
|
}
|
|
@@ -201,7 +207,7 @@ export function doctor({ target, home, cwd }) {
|
|
|
201
207
|
const missingAgents = (state.agents || []).filter((id) => {
|
|
202
208
|
const path = agentPath(target, root, id);
|
|
203
209
|
return path && !existsSync(path);
|
|
204
|
-
}).map((id) => ({ id, action: 'Run `npx @ericrisco/rsc sync` to restore this managed agent.' }));
|
|
210
|
+
}).map((id) => ({ id, path: agentPath(target, root, id), action: 'Run `npx @ericrisco/rsc sync` to restore this managed agent.' }));
|
|
205
211
|
const report = {
|
|
206
212
|
target,
|
|
207
213
|
installed: Object.keys(state.skills),
|
|
@@ -251,9 +257,12 @@ export function doctor({ target, home, cwd }) {
|
|
|
251
257
|
commandOrphans,
|
|
252
258
|
commandCollisions: state.commandCollisions || [],
|
|
253
259
|
};
|
|
260
|
+
// #277 — the three "missing" lists share one shape, { id, path, action }, so whoever builds on top
|
|
261
|
+
// of doctor reads them the same way; and `healthy` is what the exit code says.
|
|
254
262
|
for (const [id, e] of Object.entries(state.skills)) {
|
|
255
|
-
for (const f of e.files) if (!existsSync(f)) report.missing.push(
|
|
263
|
+
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
264
|
}
|
|
265
|
+
report.healthy = report.hookWired && !report.missing.length && !missingAgents.length && !missingCommands.length;
|
|
257
266
|
return report;
|
|
258
267
|
}
|
|
259
268
|
|
package/scripts/rsc.js
CHANGED
|
@@ -758,6 +758,8 @@ async function main() {
|
|
|
758
758
|
}
|
|
759
759
|
case 'doctor': {
|
|
760
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;
|
|
761
763
|
if (argv.includes('--json')) return void say(JSON.stringify(report, null, 2));
|
|
762
764
|
printContextBudget(report.contextBudget);
|
|
763
765
|
return void say(JSON.stringify({ ...report, contextBudget: undefined }, null, 2));
|