Files
msd-core/scripts/gen-golden-install-parity-zcode.cjs
Tom Boucher bd613566cb feat(#2100): drive Windsurf through the EoS descriptor + wire Cascade's blocking hook bus (ADR-1239)
Fold all 10 residual isWindsurf branches in bin/install.js onto descriptor-driven
hostBehaviors (byte-parity — no fold changes any install output):
- 2 dead destructures dropped (uninstall, finishInstall); the dead
  `else if (isWindsurf)` legacy agent-loop arm removed (windsurf ∈
  _DESCRIPTOR_AGENTS_RUNTIMES → unreachable).
- skipSharedHooksInstall:true folds the two `!isWindsurf` shared-hooks exclusions.
- legacyDevinSkillsCleanup:true folds the `.devin`→`.windsurf` one-time cleanup gate.
- installsCommandBodiesForWorkflowDelegation:true folds the #1629 command-body copy
  (workflow-delegation target — load-bearing; local-install verified intact).
- verificationStyle:"windsurf-workflows" folds the workflow-count report.
- Corrected stale _LEGACY_SCAN_SUBDIR_NAMES + hooks-json manifest comments (cursor + windsurf).
Zero live runtime==='windsurf'/isWindsurf branches remain across bin/install.js,
install-engine.cts, surface.cts, runtime-artifact-conversion.cts (AC2 guard scans all four).

UPGRADE (Cascade hook bus): wire GSD's write/command safety guards into Windsurf's
native hook bus. New hooksSurface 'windsurf-hooks-json' (VALID_HOOKS_SURFACES 7→8, GATE A
profile-marker-only allowlist, the HooksSurface union) + writeWindsurfHooksJson
(Cursor-templated, Cascade's flat {hooks:{<event>:[{command}]}} shape) writing
.windsurf/hooks.json with two BLOCKING pre-hooks:
- pre_write_code → gsd-windsurf-pre-write.js: blocks writes to a file outside the
  active git worktree / into .git internals.
- pre_run_command → gsd-windsurf-pre-command.js: conservative destructive-command
  deny-list (rm -rf of root/home incl. sudo/env/path-prefixed forms; fork bombs;
  force-push refspec forms — HEAD:main, +main, --force/-f — to main/master/next).
Both use Cascade's protocol (stdin JSON, exit 2 + stderr to block, exit 0 to allow,
fail-open on error/timeout). Tokenize-based classifier (no catastrophic-backtracking regex;
4096-char cap) with the fail-closed false-positives fixed post-review.

The 4 advisory GSD guards + pre_mcp_tool_use + 5 post_* logging events are deliberately
NOT wired: Cascade has no context-injection channel for advisory hooks and GSD has no MCP
guard — porting them would be non-functional padding (documented; codebuddy #2098 / copilot
#2099 faithful-subset precedent). extendedHookEvents stays [].

Golden: the 2 guard scripts ship in the shared hook bundle (HOOKS_TO_COPY + the shared
managed-hooks-registry), exactly like cursor's 6 gsd-cursor-*.js scripts — so the 8
shared-bundle runtimes' fixtures gain the 2 inert windsurf scripts + the registry hash
(functionally inert for non-windsurf; the established cursor pattern). No install-output
change beyond that (the folds are byte-parity; skip-bundle runtimes untouched). New scripts
registered in managed-hooks-registry + build-hooks + INVENTORY. Tests: declarative-reference-
windsurf (adapter/axes/fail-closed + AC2 guard) + windsurf-hooks-bridge (live exit-2 blocking
+ allow/fail-open + ReDoS-bound + writer/reconcile/remove idempotency); VALID_HOOKS_SURFACES
pin updated to 8. Matrix hookBus delta + changeset (Changed). capability-registry regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:04:24 -04:00

88 lines
4.0 KiB
JavaScript

#!/usr/bin/env node
'use strict';
/**
* Standalone golden-fixture generator for tests/golden-install-parity.
*
* This is a BUILD-TIME generation script — NOT a test run. It replicates the
* buildParityManifest logic from tests/golden-install-parity.test.cjs and
* captures the zcode fixture so the parity test (which the gsd-test gate runs)
* has a committed artifact to compare against. The authoritative test gate
* remains `gsd-test run`, never a local `node --test`.
*
* Usage: node scripts/gen-golden-install-parity-zcode.cjs
*/
const fs = require('node:fs');
const path = require('node:path');
const crypto = require('node:crypto');
const ROOT = path.resolve(__dirname, '..');
const { walk, runMinimalInstall, RUNTIME_META } = require(path.join(ROOT, 'tests', 'helpers', 'install-shared.cjs'));
const PKG_VERSION = require(path.join(ROOT, 'package.json')).version;
const FIXTURE_DIR = path.join(ROOT, 'tests', 'fixtures', 'golden-install-parity');
const VOLATILE_FILES = new Set([
'gsd-file-manifest.json',
'gsd-install-state.json',
'.gsd-source',
'gsd-core/CHANGELOG.md',
]);
// Must match tests/golden-install-parity.test.cjs exactly — settings.local.json
// (Claude LOCAL hook surface, #338/#2086) embeds the same platform-varying
// node-runner command and is excluded there; omitting it here mis-generated the
// claude-local fixture (#2100).
const HOOK_CONFIG_FILES = new Set(['settings.json', 'settings.local.json', 'hooks.json']);
// Kimi's native config.toml (#2095) — see tests/golden-install-parity.test.cjs'
// HOOK_CONFIG_RELATIVE_PATHS comment for why this is an exact relative-path
// exclusion rather than a HOOK_CONFIG_FILES basename entry (a basename entry
// would also blind Codex's stable, platform-independent config.toml fixture).
const HOOK_CONFIG_RELATIVE_PATHS = new Set(['.kimi/config.toml']);
const EXCLUDED_PREFIXES = ['gsd-core/bin/lib/'];
function buildParityManifest(configDir, root) {
const allFiles = walk(configDir);
const unsorted = {};
for (const full of allFiles) {
const rel = path.relative(configDir, full).split(path.sep).join('/');
if (VOLATILE_FILES.has(rel)) continue;
if (HOOK_CONFIG_FILES.has(path.basename(rel))) continue;
if (HOOK_CONFIG_RELATIVE_PATHS.has(rel)) continue;
if (EXCLUDED_PREFIXES.some((p) => rel.startsWith(p))) continue;
const content = fs.readFileSync(full);
const normalized = content.toString('utf8').split(root).join('<HOME>').split(PKG_VERSION).join('<VERSION>');
const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 16);
unsorted[rel] = hash;
}
const sorted = {};
for (const key of Object.keys(unsorted).sort()) sorted[key] = unsorted[key];
return sorted;
}
function cleanup(root) {
try { fs.rmSync(root, { recursive: true, force: true }); } catch { /* best effort */ }
}
// Regenerate the fixture for every runtime in RUNTIME_META. Needed when a
// SHARED gsd-core payload file (e.g. model-catalog.json, capability-registry)
// changes content — its hash appears in every runtime's manifest, so all
// fixtures must be recaptured together. Usage:
// node scripts/gen-golden-install-parity-zcode.cjs [runtime ...]
// With no args, regenerates ALL runtimes. With args, only the named runtimes.
const targets = process.argv.slice(2).length > 0 ? process.argv.slice(2) : Object.keys(RUNTIME_META);
fs.mkdirSync(FIXTURE_DIR, { recursive: true });
for (const runtime of targets) {
if (!Object.prototype.hasOwnProperty.call(RUNTIME_META, runtime)) {
process.stderr.write(`[gen] unknown runtime '${runtime}' (not in RUNTIME_META) — skipping\n`);
continue;
}
const { configDir, root } = runMinimalInstall({ runtime, scope: 'global' });
let actual;
try {
actual = buildParityManifest(configDir, root);
} finally {
cleanup(root);
}
const fixturePath = path.join(FIXTURE_DIR, `${runtime}.json`);
fs.writeFileSync(fixturePath, JSON.stringify(actual, null, 2) + '\n', 'utf8');
process.stdout.write(`[gen] ${runtime}: wrote ${Object.keys(actual).length} file hashes -> ${fixturePath}\n`);
}