* 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>
71 lines
2.9 KiB
JavaScript
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).`
|
|
);
|
|
});
|
|
});
|
|
}
|
|
});
|