test: coverage for the memorable bridge (remove-source regression for every KNOWN_HOOKS source)

- settings-hook: identity removal pinned for each source in KNOWN_HOOKS;
  list-items unknown flag and combined --owned-by/--command-regex
- gstack-memorable: enable/disable failure paths (lock give-up exit 5
  with the test-only lock timeout override, consent-write failures
  guarded by canRevokeWrites, canonical-version mismatch, no-bun status)
- hook: non-object JSON, missing cwd, non-ASCII bytes, held-open stdin,
  shim without bun, stripControl, resolveVendor, runExternal ENOENT
- egress-receipt: lockBudgetMs 0 and writeOutcome on garbage input
- uninstall: no memorable entry present reports nothing removed

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-09-09 02:50:32 +00:00
co-authored by Claude Fable 5.1
parent 45035b7f94
commit 77b7b31892
5 changed files with 332 additions and 6 deletions
+30
View File
@@ -352,6 +352,36 @@ describe('the Memorable bridge hook is removed by name and the kept config is le
}, 30000);
});
describe('the Memorable arm stays quiet when nothing of its is registered', () => {
test('no memorable entry -> no "Memorable UserPromptSubmit hook" in the summary, exit 0', () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-uninstall-memo-none-'));
try {
const mockHome = path.join(tmp, 'home');
const installRoot = path.join(mockHome, '.claude', 'skills', 'gstack');
const installBin = path.join(installRoot, 'bin');
fs.mkdirSync(installBin, { recursive: true });
for (const b of ['gstack-uninstall', 'gstack-settings-hook', 'gstack-session-update', 'gstack-config']) {
const dst = path.join(installBin, b);
fs.copyFileSync(path.join(ROOT, 'bin', b), dst);
fs.chmodSync(dst, 0o755);
}
const settingsFile = path.join(mockHome, '.claude', 'settings.json');
fs.writeFileSync(settingsFile, JSON.stringify({ hooks: { UserPromptSubmit: [{ hooks: [{ type: 'command', command: '/Users/me/my-own-hook' }] }] } }, null, 2));
fs.mkdirSync(path.join(mockHome, '.gstack'), { recursive: true });
const result = spawnSync('bash', [path.join(installBin, 'gstack-uninstall'), '--force', '--keep-state'], {
stdio: 'pipe', timeout: 30_000, encoding: 'utf-8', cwd: tmp,
env: { ...process.env, HOME: mockHome, GSTACK_SETTINGS_FILE: settingsFile, GSTACK_STATE_ROOT: path.join(mockHome, '.gstack') },
});
expect(result.status).toBe(0);
expect(result.stdout).not.toContain('Memorable UserPromptSubmit hook');
const s = JSON.parse(fs.readFileSync(settingsFile, 'utf-8'));
expect(s.hooks.UserPromptSubmit[0].hooks[0].command).toBe('/Users/me/my-own-hook');
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
}, 30000);
});
describe('hook cleanup under lock contention is loud, never silent (review-army)', () => {
test('a held foreign lock during uninstall surfaces the give-up warning on stderr', () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-uninstall-lock-'));