@ericrisco/rsc 2.0.13 → 2.0.15
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 +1 -1
- package/package.json +5 -1
- package/scripts/rsc.js +26 -14
- package/targets/session-memory-core.mjs +25 -2
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.15",
|
|
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": {
|
|
@@ -85,6 +85,10 @@
|
|
|
85
85
|
{
|
|
86
86
|
"fixedIn": "2.0.12",
|
|
87
87
|
"symptom": "Security: session continuation context could come from a different git worktree"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"fixedIn": "2.0.15",
|
|
91
|
+
"symptom": "The 'consider compacting' notice never appears in sessions that edit files through the terminal instead of the Edit tool"
|
|
88
92
|
}
|
|
89
93
|
]
|
|
90
94
|
}
|
package/scripts/rsc.js
CHANGED
|
@@ -501,7 +501,18 @@ function printContextBudget(b) {
|
|
|
501
501
|
say('════════════════════════════════════════════════\n');
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
-
async function
|
|
504
|
+
async function syncDeclared(targets, dry = false) {
|
|
505
|
+
for (const t of targets) {
|
|
506
|
+
const result = await syncInstalled({ target: t, dryRun: dry });
|
|
507
|
+
const verb = dry ? 'Would sync' : 'Synced';
|
|
508
|
+
say(`${verb} ${t}: ${result.synced.length ? result.synced.join(', ') : '(nothing to sync)'}`);
|
|
509
|
+
if (dry && result.paths?.length) {
|
|
510
|
+
for (const p of result.paths) say(` ${p}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function wizard(flagTargets, declaredTargets = []) {
|
|
505
516
|
const m = loadManifest();
|
|
506
517
|
await banner(m.counts.skills);
|
|
507
518
|
say(' the skill catalog for your assistant (Claude Code · Codex · Cursor · Gemini · Antigravity)\n');
|
|
@@ -511,12 +522,16 @@ async function wizard(flagTargets) {
|
|
|
511
522
|
const baseIds = skillsForProfile(m, 'minimal');
|
|
512
523
|
// Navigable loop: esc / "← Back" / "no" all return here instead of quitting.
|
|
513
524
|
for (;;) {
|
|
525
|
+
const declared = readManifest();
|
|
514
526
|
const choice = await select('What do you want to do?', [
|
|
527
|
+
// First, because it is what someone who already has a harness came here for nine times in ten.
|
|
528
|
+
...(declared ? [{ key: 'update', label: `Update this project — keeps its ${declared.skills.length} skills, refreshes hooks and content` }] : []),
|
|
515
529
|
{ key: 'base', label: `Base install — the essentials (${baseIds.length} skills)` },
|
|
516
530
|
{ key: 'sdd', label: 'Base + Spec-Driven Development — the specify → plan → implement → ship flow' },
|
|
517
531
|
{ key: 'manual', label: 'Pick skills by hand, by area' },
|
|
518
532
|
]);
|
|
519
533
|
if (choice === null) { say('\nOK — nothing installed. Anytime: npx @ericrisco/rsc'); return; }
|
|
534
|
+
if (choice === 'update') return syncDeclared(declaredTargets);
|
|
520
535
|
|
|
521
536
|
let ids;
|
|
522
537
|
if (choice === 'base') ids = baseIds;
|
|
@@ -621,7 +636,14 @@ async function main() {
|
|
|
621
636
|
const target = targets[0];
|
|
622
637
|
switch (cmd) {
|
|
623
638
|
case undefined:
|
|
624
|
-
|
|
639
|
+
if (!hasDeclaredHarness()) return runOnboarding(f ? targets : []);
|
|
640
|
+
// A project that already has a harness: the bare command means "bring it up to date". It is
|
|
641
|
+
// what every update notice, the README and our own replies to users tell people to run, with
|
|
642
|
+
// the promise "that reinstalls and refreshes". It used to open the install wizard instead, and
|
|
643
|
+
// with no terminal — which is how an agent runs it when it obeys the notice — it did nothing.
|
|
644
|
+
// Restoring what the project already declared is not a decision, so there is nothing to ask.
|
|
645
|
+
if (!isInteractive()) return syncDeclared(targets);
|
|
646
|
+
return wizard(f ? targets : null, targets);
|
|
625
647
|
case 'onboard':
|
|
626
648
|
return runOnboarding(f ? targets : []);
|
|
627
649
|
case 'reassess':
|
|
@@ -773,18 +795,8 @@ async function main() {
|
|
|
773
795
|
process.exitCode = 1;
|
|
774
796
|
return;
|
|
775
797
|
}
|
|
776
|
-
case 'sync':
|
|
777
|
-
|
|
778
|
-
for (const t of targets) {
|
|
779
|
-
const result = await syncInstalled({ target: t, dryRun: dry });
|
|
780
|
-
const verb = dry ? 'Would sync' : 'Synced';
|
|
781
|
-
say(`${verb} ${t}: ${result.synced.length ? result.synced.join(', ') : '(nothing to sync)'}`);
|
|
782
|
-
if (dry && result.paths?.length) {
|
|
783
|
-
for (const p of result.paths) say(` ${p}`);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
798
|
+
case 'sync':
|
|
799
|
+
return syncDeclared(targets, argv.includes('--dry-run'));
|
|
788
800
|
case 'backups': {
|
|
789
801
|
const backups = listBackups();
|
|
790
802
|
if (!backups.length) return void say('(no backups)');
|
|
@@ -319,7 +319,20 @@ export function capture(input = {}) {
|
|
|
319
319
|
if (input.event === 'start' && !existing) return { record: null, path: null, notice: null, compactionHint: false };
|
|
320
320
|
|
|
321
321
|
const repo = snapshot(here, anchor.baselineHead);
|
|
322
|
-
|
|
322
|
+
// Count what CHANGED, not which tool changed it. `editDelta` is +1 per Edit/Write tool event, and
|
|
323
|
+
// on its own it was blind to everything done through the shell — a heredoc, `sed -i`, a script, a
|
|
324
|
+
// formatter — which is how automated sessions work. The session that found this had touched dozens
|
|
325
|
+
// of files and its record said editCount=1, so the compaction hint never reached the long sessions
|
|
326
|
+
// it exists for.
|
|
327
|
+
//
|
|
328
|
+
// `snapshot` already fingerprints every modified file on every event. Comparing those fingerprints
|
|
329
|
+
// with the previous event's counts a change from any source. `max`, not `+`: an Edit that changed a
|
|
330
|
+
// file shows up in BOTH signals, and must count once.
|
|
331
|
+
const previousFingerprints = anchor.lastFingerprints || anchor.baselineFingerprints || {};
|
|
332
|
+
const observed = Object.entries(repo.fingerprints || {})
|
|
333
|
+
.filter(([path, fingerprint]) => fingerprint && previousFingerprints[path] !== fingerprint).length;
|
|
334
|
+
const editDelta = Math.max(finiteOrNull(input.editDelta, true) || 0, observed);
|
|
335
|
+
const editCount = Math.max(0, (existing?.editCount || 0) + editDelta);
|
|
323
336
|
const baselineFiles = new Set(anchor.baselineFiles || []);
|
|
324
337
|
const newDirtyPath = repo.files.some((path) => !baselineFiles.has(path));
|
|
325
338
|
const baselineFingerprints = anchor.baselineFingerprints || {};
|
|
@@ -368,7 +381,17 @@ export function capture(input = {}) {
|
|
|
368
381
|
// already written and `readRecords` would quietly drop the project's whole history.
|
|
369
382
|
let compactionHint = Boolean(config.compactionHint && editCount >= config.editThreshold);
|
|
370
383
|
if (compactionHint && anchor.compactionHintedAt) compactionHint = false;
|
|
371
|
-
|
|
384
|
+
// One anchor write per event at most, carrying both pieces of per-session state: the fingerprints
|
|
385
|
+
// the next event compares against, and the once-per-session hint mark. Written only when either
|
|
386
|
+
// actually changed, so a read-only shell command costs no extra disk.
|
|
387
|
+
const fingerprintsMoved = observed > 0 || !anchor.lastFingerprints;
|
|
388
|
+
if (compactionHint || fingerprintsMoved) {
|
|
389
|
+
atomicJson(anchorPath, {
|
|
390
|
+
...anchor,
|
|
391
|
+
lastFingerprints: repo.fingerprints || {},
|
|
392
|
+
...(compactionHint ? { compactionHintedAt: now } : {}),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
372
395
|
return {
|
|
373
396
|
record,
|
|
374
397
|
path: recordPath,
|