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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.187",
3
+ "version": "0.2.188",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",