Files
msd-core/tests/bug-1817-sh-hook-guard.test.cjs
Tom Boucher f11462e58e refactor(#1067): phase 5f-1b — extract the settings-json hook block (applySettingsJsonHooks) — ADR-857/1016 (#1075)
* refactor(#1067): promote referencesHook to runtime-hooks-surface module scope

referencesHook was declared as a local function inside install() but also
called in finishInstall() (module scope), meaning JS hoisting was the only
thing making it work from finishInstall. Move it to src/runtime-hooks-surface.cts,
export it, and have both call sites in install.js use the module's copy.

This is the prerequisite for COMMIT 2 (applySettingsJsonHooks extraction)
per ADR-857 phase 5f-1b.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(#1067): extract applySettingsJsonHooks to runtime-hooks-surface module

ADR-857 phase 5f-1b: move the ~457-line settings.json hook-registration block
from install() into applySettingsJsonHooks(settings, opts) in
src/runtime-hooks-surface.cts. install() replaces the block with a single call.

Behavior-preserving: all runtime=== guards, isGemini/isQwen/isOpencode/isKilo
derivations, postToolEvent/preToolEvent dialect branches, idempotency checks,
fs.existsSync guards, and console.log/warn messages are verbatim.

Opts bag: 13 fields — runtime, isGlobal, targetDir, postToolEvent,
updateCheckCommand, contextMonitorCommand, promptGuardCommand, readGuardCommand,
readInjectionScannerCommand, configReloadCommand, hookOpts, localCmd,
localShellCmd. preToolEvent computed inside (from runtime). workflowGuardCommand
/ worktreePathGuardCommand / validateCommitCommand / graphifyUpdateCommand /
sessionStateCommand / phaseBoundaryCommand / contextMonitorFile also computed
inside. settings.hooks-only mutations confirmed.

5 source-scan tests updated to read runtime-hooks-surface.cts alongside
install.js (concatenated), so structural regression guards remain valid at
their new canonical location.

install.js: 12700 → 12254 lines (−446).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 19:17:06 -04:00

71 lines
2.9 KiB
JavaScript

/**
* Regression tests for bug #1817
*
* The installer must NOT register .sh hook entries in settings.json when the
* corresponding .sh file does not exist at the target path. The original bug:
* v1.32.0's npm package omitted the .sh files from hooks/dist/, so the copy
* step produced no files, yet the registration step ran unconditionally —
* leaving users with hook errors on every tool invocation.
*
* Defensive guard: before registering each .sh hook in settings.json,
* install.js must verify the target file exists. If it doesn't, skip
* registration and emit a warning.
*/
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const INSTALL_SRC = path.join(__dirname, '..', 'bin', 'install.js');
// ADR-857 phase 5f-1b: settings-json hook registration moved to runtime-hooks-surface.cts.
const HOOKS_SURFACE_SRC = path.join(__dirname, '..', 'src', 'runtime-hooks-surface.cts');
const SH_HOOKS = [
{ name: 'gsd-validate-commit.sh', settingsVar: 'validateCommitCommand' },
{ name: 'gsd-session-state.sh', settingsVar: 'sessionStateCommand' },
{ name: 'gsd-phase-boundary.sh', settingsVar: 'phaseBoundaryCommand' },
];
describe('bug #1817: .sh hook registration guards', () => {
let src;
// Read once — all tests in this suite share the same source snapshot.
// ADR-857 phase 5f-1b: hook registration moved to runtime-hooks-surface.cts.
// Concatenate both sources so structural assertions find patterns in either file.
try {
const installSrc = fs.readFileSync(INSTALL_SRC, 'utf-8');
let hooksSurfaceSrc = '';
try { hooksSurfaceSrc = fs.readFileSync(HOOKS_SURFACE_SRC, 'utf-8'); } catch { /* ok */ }
src = installSrc + '\n' + hooksSurfaceSrc;
} catch {
src = '';
}
for (const { name, settingsVar } of SH_HOOKS) {
describe(`${name} registration`, () => {
test(`install.js checks file existence before registering ${name}`, () => {
// Find the block where this .sh hook is registered.
// Each registration block is preceded by the command variable declaration
// and followed by the next hook or end of registration section.
const varIdx = src.indexOf(settingsVar);
assert.ok(varIdx !== -1, `${settingsVar} variable not found in install.js`);
// Extract ~900 chars around the variable to find the registration block
const blockStart = Math.max(0, varIdx - 50);
const blockEnd = Math.min(src.length, varIdx + 900);
const block = src.slice(blockStart, blockEnd);
assert.ok(
block.includes('fs.existsSync') || block.includes('existsSync'),
`install.js must call fs.existsSync on the target path before registering ${name} in settings.json. ` +
`Without this guard, hooks are registered even when the .sh file was never copied ` +
`(the root cause of #1817).`
);
});
});
}
});