Merge branch 'next' into fix/1857-test-gate-watch-mode-timeout

This commit is contained in:
Tom Boucher
2026-07-07 12:58:57 -04:00
committed by GitHub
6 changed files with 103 additions and 52 deletions

View File

@@ -380,29 +380,7 @@
"gsd-core/workflows/validate-phase.md": "557e3251e3b9349a",
"gsd-core/workflows/verify-phase.md": "e0957e153788a222",
"gsd-core/workflows/verify-work.md": "b68ac37f6301a3b5",
"hooks/gsd-check-update-worker.js": "c992bbad91d0e994",
"hooks/gsd-check-update.js": "fdd77abe7ef26a2d",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "2caaaf96d39fe742",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "3ce09b366839d324",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "7792c420f1d72f11",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "a055020378003805",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "cb8b86e39a5d49e9",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "8a9f7633c6fe0ea0",
"kilo.json": "13151e97ff23c1aa",
"package.json": "dbf8353f77358bc1",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",

View File

@@ -380,28 +380,6 @@
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
"gsd-core/workflows/verify-phase.md": "22f18492581f1da5",
"gsd-core/workflows/verify-work.md": "34e980a6950cd83c",
"hooks/gsd-check-update-worker.js": "6660bcf03ed849a0",
"hooks/gsd-check-update.js": "901ea3bd75fd5bbc",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "9f969c172d1614a9",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-ensure-canonical-path.js": "84e6b584da9805c4",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "7d0344c1ef6dd0ab",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "c1056568acf6dfd2",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "635cbdc5cde5277b",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "49f1e3ea9e332097",
"package.json": "dbf8353f77358bc1",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",

View File

@@ -54,6 +54,7 @@ const {
EXPECTED_SH_HOOKS,
EXPECTED_ALL_HOOKS,
SKILL_RUNTIMES,
walk,
simulateHookCopy,
installerEnv,
runMinimalInstall,
@@ -640,6 +641,82 @@ describe('#1755: .sh hooks are copied and executable after install', () => {
});
});
// ─── #1821: Kilo/ZCode (hooksSurface:none, no plugin) receive no dead hooks ────
//
// #1821 reported dead hook scripts staged for runtimes with hooksSurface:'none'.
// OpenCode ALSO declares hooksSurface:'none', but its #1914 native plugin adapter
// (plugins/gsd-core.js) spawns the staged hooks/*.js via OpenCode's event bus —
// so for OpenCode the hooks are LIVE and must keep being copied. Kilo and ZCode
// have no plugin surface, so their staged hooks are genuinely dead: this is the
// case the fix removes. These tests assert the split: Kilo/ZCode get no hooks;
// OpenCode (and Claude) still do.
describe('#1821: Kilo/ZCode receive no dead hook files; OpenCode/Claude keep their hooks', () => {
function gsdHookFilesUnder(configDir) {
const hooksDir = path.join(configDir, 'hooks');
if (!fs.existsSync(hooksDir)) return [];
return walk(hooksDir).filter((f) => {
const base = path.basename(f);
return /^gsd-.*\.(js|sh)$/.test(base);
});
}
function installAndCollect(runtime) {
const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-1821-${runtime}-`));
try {
const result = spawnSync(
process.execPath,
[INSTALL_SCRIPT, `--${runtime}`, '--global', '--config-dir', targetDir],
{ encoding: 'utf8', env: installerEnv() },
);
assert.strictEqual(result.status, 0,
`installer exited with status ${result.status} for --${runtime} --global\nstdout: ${result.stdout}\nstderr: ${result.stderr}`);
// Collect results while targetDir still exists — cleanup() below removes it.
return {
hookFiles: gsdHookFilesUnder(targetDir),
hooksLibExists: fs.existsSync(path.join(targetDir, 'hooks', 'lib')),
pluginExists: fs.existsSync(path.join(targetDir, 'plugins', 'gsd-core.js')),
};
} finally {
cleanup(targetDir);
}
}
// Kilo and ZCode both declare hooksSurface:'none' with no plugin surface, so
// their staged hooks are dead weight (#1821).
for (const runtime of ['kilo', 'zcode']) {
test(`${runtime} --global install creates no gsd-*.js/.sh hook files or hooks/lib`, () => {
const { hookFiles, hooksLibExists } = installAndCollect(runtime);
assert.deepStrictEqual(hookFiles, [], `${runtime} install must not copy any gsd-*.js/.sh hook files, found: ${hookFiles.join(', ')}`);
assert.ok(!hooksLibExists, `${runtime} install must not create hooks/lib/`);
});
}
// Regression guard for #1914: OpenCode's plugin adapter spawns the staged
// hooks, so excluding OpenCode from the hook copy would break it. OpenCode
// must KEEP its hooks and receive the plugin.
test('opencode --global install still copies hooks and installs the #1914 plugin', () => {
const { hookFiles, pluginExists } = installAndCollect('opencode');
const basenames = hookFiles.map((f) => path.basename(f));
assert.ok(
basenames.includes('gsd-context-monitor.js'),
`opencode install must still copy gsd-*.js hooks (spawned by the #1914 plugin), found: ${basenames.join(', ')}`,
);
assert.ok(pluginExists, 'opencode install must install plugins/gsd-core.js (#1914 hook bridge)');
});
// Positive control: guards against over-exclusion breaking runtimes that
// legitimately need hooks (hooksSurface !== 'none').
test('claude --global install still copies gsd-*.js hooks', () => {
const { hookFiles } = installAndCollect('claude');
const basenames = hookFiles.map((f) => path.basename(f));
assert.ok(
basenames.includes('gsd-context-monitor.js'),
`claude install must still copy gsd-context-monitor.js, found: ${basenames.join(', ')}`,
);
});
});
// Migrated (#455): uses typed export GSD_UNINSTALL_HOOKS instead of
// source-grep assertions on bin/install.js for the uninstall hook list tests.
describe('install.js uninstall hooks registry (typed assertions)', () => {

View File

@@ -35,12 +35,16 @@ const RUNTIME_INSTALL_CONTRACTS = {
gemini: { surface: 'commands-gsd', settings: true, packageJson: true },
hermes: { surface: 'hermes-skills', settings: true, packageJson: true },
kimi: { surface: 'kimi-skills-agents', settings: false, packageJson: false },
kilo: { surface: 'flat-command', settings: false, packageJson: true },
// #1821: Kilo (hooksSurface:none, no plugin surface) no longer receives the
// dead hook scripts or the CommonJS package.json marker.
kilo: { surface: 'flat-command', settings: false, packageJson: false },
opencode: { surface: 'flat-command', settings: true, packageJson: true },
qwen: { surface: 'flat-skills', settings: true, packageJson: true },
trae: { surface: 'flat-skills', settings: false, packageJson: false },
windsurf: { surface: 'global-artifacts-noop', settings: false, packageJson: false },
zcode: { surface: 'flat-skills', settings: false, packageJson: true },
// #1821: ZCode (hooksSurface:none, no plugin surface) no longer receives the
// dead hook scripts or the CommonJS package.json marker.
zcode: { surface: 'flat-skills', settings: false, packageJson: false },
};
function sha256(content) {