svamp-cli 0.2.187 → 0.2.188
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.
|
@@ -302,6 +302,40 @@ try {
|
|
|
302
302
|
ok(readState(d).phase !== 'done', 'loop B did not close as done at iteration 0');
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
// ---- Test 21: inject-loop self-heals a clobbered Stop gate (#0072) -------
|
|
306
|
+
console.log('Test 21: inject-loop re-installs a dropped Stop gate (shared-tree self-heal)');
|
|
307
|
+
{ const INJECT = resolve(HERE, '..', 'bin', 'inject-loop.mjs');
|
|
308
|
+
const d = newProject(); dirs.push(d);
|
|
309
|
+
const settingsPath = join(d, '.claude', 'settings.json');
|
|
310
|
+
const stopGate = join(d, '.svamp', SID, 'loop', 'bin', 'stop-gate.mjs');
|
|
311
|
+
const runInject = () => execFileSync(node, [INJECT], {
|
|
312
|
+
input: JSON.stringify({ hook_event_name: 'SessionStart', cwd: d }),
|
|
313
|
+
encoding: 'utf8', env: { ...process.env, SVAMP_SESSION_ID: SID, CLAUDE_PROJECT_DIR: d },
|
|
314
|
+
});
|
|
315
|
+
const hasStop = () => { const s = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
316
|
+
return Array.isArray(s.hooks?.Stop) && s.hooks.Stop.some((g) => Array.isArray(g?.hooks)
|
|
317
|
+
&& g.hooks.some((h) => typeof h?.command === 'string' && h.command.includes(stopGate))); };
|
|
318
|
+
ok(hasStop(), 'precondition: loop-init installed the Stop gate');
|
|
319
|
+
// Simulate a shared-tree clobber: another tool rewrites settings.json, dropping the Stop hook.
|
|
320
|
+
const clob = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
321
|
+
delete clob.hooks.Stop; clob.permissions = { allow: ['Bash(ls:*)'] };
|
|
322
|
+
writeFileSync(settingsPath, JSON.stringify(clob, null, 2));
|
|
323
|
+
ok(!hasStop(), 'precondition: Stop gate removed by the clobber');
|
|
324
|
+
runInject();
|
|
325
|
+
ok(hasStop(), 'inject-loop re-installed the missing Stop gate');
|
|
326
|
+
ok(JSON.parse(readFileSync(settingsPath, 'utf8')).permissions?.allow?.includes('Bash(ls:*)'),
|
|
327
|
+
'self-heal preserved unrelated settings (merge, not clobber)');
|
|
328
|
+
runInject();
|
|
329
|
+
ok(JSON.parse(readFileSync(settingsPath, 'utf8')).hooks.Stop.length === 1,
|
|
330
|
+
'self-heal is idempotent when the gate is intact');
|
|
331
|
+
// Inactive loop (cancelled) must NOT be resurrected by inject-loop.
|
|
332
|
+
const stateP = join(d, '.svamp', SID, 'loop', 'loop-state.json');
|
|
333
|
+
const st = JSON.parse(readFileSync(stateP, 'utf8')); st.active = false; writeFileSync(stateP, JSON.stringify(st));
|
|
334
|
+
const clob2 = JSON.parse(readFileSync(settingsPath, 'utf8')); delete clob2.hooks.Stop; writeFileSync(settingsPath, JSON.stringify(clob2, null, 2));
|
|
335
|
+
runInject();
|
|
336
|
+
ok(!hasStop(), 'inactive loop: inject-loop does not re-install (respects cancel)');
|
|
337
|
+
}
|
|
338
|
+
|
|
305
339
|
console.log(`\n${fail === 0 ? '✅' : '❌'} ${pass} passed, ${fail} failed`);
|
|
306
340
|
process.exit(fail === 0 ? 0 : 1);
|
|
307
341
|
} finally {
|