* enh(#2876): retire the dead and pass-through exports from bin/install.js The installer exported 197 names and had zero production consumers - every non-test require of it repo-wide sits inside a comment. Its interface was shaped by test access, not by callers. Removes 9 dead exports and 61 pass-throughs, repointing their tests onto the extracted modules' own interfaces. 197 down to 127. Every count in the issue was wrong: 197 exports not 188, 9 dead not 12, 61 pass-throughs not 49, 44 test files not 42 - and the audit itself then missed 7 more consumer files. restoreUserArtifacts was on the dead list but ceased to exist in phase 6, and two _GSD_EFFORT_MANIFEST_* names listed as dead are now genuinely asserted, so acting on that list would have deleted live exports. 7 of the 9 dead names collide with an independent declaration that install.js delegates TO. Each removal was justified by which declaration a reference resolves to, never by whether the name appears somewhere. Coverage parity was the gate rather than test greenness: per-file counts were captured before any edit and diffed after. 44 of 45 files are byte-identical; the single delta is one added assertion, not a loss. The sweep for scattered require sites found two forms static grep misses - require(VARIABLE) and multi-line require() - plus tests asserting that install.js re-exports the SAME object, which now assert retirement instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#2876): close review findings — restore the duplicate-body guard, sweep orphaned code Both review engines found real defects in the first cut. The DEFECT.GENERATIVE-FIX single-owner guard from #1511 had been repointed from a reference-identity check to install.X === undefined. Those are not equivalent: the guard exists to catch a duplicate function body reintroduced into install.js, and the replacement passes cleanly if that duplicate is used internally and never exported. It now walks bin/install.js's real top-level bindings, so it catches a duplicate under either shape, exported or not - strictly stronger than the check it replaced. Proved by injecting a duplicate and watching it go red. That weakening survived the coverage-parity gate because the assertion count never moved. The gate compares counts, so an assertion that changes meaning rather than number is invisible to it. Removing the exports had orphaned their wrapper bodies: 14 dead wrappers, 9 consts and 9 destructure entries, several pre-existing and found by the same sweep. Dead code left in the file this phase exists to shrink. Three more comments claimed re-exports this phase removed, and tests were reading Cursor and Windsurf hook constants from install.js's local copy while calling functions from the hooks surface - equal today, with nothing holding them equal. The local consts now reference the owning module. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(#2876): backfill changeset pr number Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
5
.changeset/rapid-foxes-glide.md
Normal file
5
.changeset/rapid-foxes-glide.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3615
|
||||
---
|
||||
**The installer module no longer re-exports internals it does not own.** `bin/install.js` exported 197 names, 70 of which were either dead or plain pass-throughs to the modules that actually implement them — kept for "existing consumers" that turned out not to exist, since no production code has ever required the file. Those 70 are gone and their tests now import the owning modules directly. No installed output changes. (#2876)
|
||||
496
bin/install.js
496
bin/install.js
@@ -44,7 +44,10 @@ const { resolveScope } = require('../gsd-core/bin/lib/install-scope.cjs');
|
||||
// getDirName (runtime -> local config dir name) is relocated out of this
|
||||
// installer to the runtime-name-policy leaf (ADR-1508 / #1510 Phase 1) so the
|
||||
// conversion module's rewrite engine can consume it without importing
|
||||
// bin/install.js. Re-exported below for back-compat consumers/tests.
|
||||
// bin/install.js. Imported here for install.js's own internal call sites
|
||||
// (getConfigDirFromHome and the runtime-content-rewrite loops below) — #2876
|
||||
// retired the re-export; tests now import getDirName directly from
|
||||
// gsd-core/bin/lib/runtime-name-policy.cjs.
|
||||
const { getDirName, getRuntimeLabel, getGlobalConfigHomeFragment, runtimeFlags, getRuntimeNewProjectCommand } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
|
||||
const {
|
||||
applyWorktreeBaseRef,
|
||||
@@ -76,9 +79,14 @@ const { ensureCommonJsMarker, removeCommonJsMarker } = require('../gsd-core/bin/
|
||||
const { HOOKS_TO_COPY: _HOOKS_TO_COPY } = require('../scripts/build-hooks.js');
|
||||
const INSTALLED_HOOK_FILES = new Set(_HOOKS_TO_COPY);
|
||||
|
||||
// ADR-857 phase 5f-1: hook-surface writer functions extracted to a dedicated module.
|
||||
// bin/install.js re-exports everything from hooksSurface so existing callers
|
||||
// (require('../bin/install.js').writeCursorHooksJson etc.) continue to work.
|
||||
// ADR-857 phase 5f-1: hook-surface writer functions extracted to a dedicated
|
||||
// module. install.js used to re-export the whole hooksSurface surface so
|
||||
// existing callers (require('../bin/install.js').writeCursorHooksJson etc.)
|
||||
// kept working — #2876 found zero production/test consumers of any of those
|
||||
// re-exports (tests import runtime-hooks-surface.cjs directly) and retired
|
||||
// them from module.exports. install.js still requires hooksSurface below for
|
||||
// its own internal call sites (writeCursorHooksJson, writeClineArtifacts,
|
||||
// resolveNodeRunner, applySettingsJsonHooks, etc.).
|
||||
const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
|
||||
/**
|
||||
@@ -306,13 +314,26 @@ const GSD_COPILOT_SESSION_HOOK_PWSH =
|
||||
// subagentStart → gsd-cursor-subagent-start.js (subagent context injection)
|
||||
// subagentStop → gsd-cursor-subagent-stop.js (subagent completion reminder)
|
||||
// Cursor docs: https://cursor.com/docs/hooks
|
||||
const GSD_CURSOR_SESSION_HOOK_SCRIPT = 'gsd-cursor-session-start.js';
|
||||
const GSD_CURSOR_POST_TOOL_HOOK_SCRIPT = 'gsd-cursor-post-tool.js';
|
||||
const GSD_CURSOR_PRE_TOOL_HOOK_SCRIPT = 'gsd-cursor-pre-tool.js';
|
||||
const GSD_CURSOR_STOP_HOOK_SCRIPT = 'gsd-cursor-stop.js';
|
||||
const GSD_CURSOR_SUBAGENT_START_HOOK_SCRIPT = 'gsd-cursor-subagent-start.js';
|
||||
const GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT = 'gsd-cursor-subagent-stop.js';
|
||||
// All GSD-managed Cursor hook scripts (used by uninstall cleanup).
|
||||
//
|
||||
// These script-name/marker constants used to be independently re-declared
|
||||
// here with their own string literals — a second, unlinked copy of exactly
|
||||
// the values runtime-hooks-surface.cts also defines for its own internal use
|
||||
// (buildCursorHookEntry, writeCursorHooksJson, etc.). #2876's code review
|
||||
// found tests reading the constant from install.js's copy while calling
|
||||
// functions built from hooksSurface's copy, with nothing guarding the two
|
||||
// staying equal — the same unlinked-duplicate-value hazard the ADR-1508
|
||||
// dedup elsewhere in this file exists to prevent. Fixed at the root: these
|
||||
// are now bare references to hooksSurface's own exports, so there is exactly
|
||||
// one literal definition of each value, full stop.
|
||||
const GSD_CURSOR_SESSION_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_SESSION_HOOK_SCRIPT;
|
||||
const GSD_CURSOR_POST_TOOL_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_POST_TOOL_HOOK_SCRIPT;
|
||||
const GSD_CURSOR_PRE_TOOL_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_PRE_TOOL_HOOK_SCRIPT;
|
||||
const GSD_CURSOR_STOP_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_STOP_HOOK_SCRIPT;
|
||||
const GSD_CURSOR_SUBAGENT_START_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_SUBAGENT_START_HOOK_SCRIPT;
|
||||
const GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT;
|
||||
// All GSD-managed Cursor hook scripts (used by uninstall cleanup). Not
|
||||
// independently defined in hooksSurface — built here from the bare
|
||||
// references above, so it can never drift from them either.
|
||||
const GSD_CURSOR_HOOK_SCRIPTS = [
|
||||
GSD_CURSOR_SESSION_HOOK_SCRIPT,
|
||||
GSD_CURSOR_POST_TOOL_HOOK_SCRIPT,
|
||||
@@ -322,7 +343,7 @@ const GSD_CURSOR_HOOK_SCRIPTS = [
|
||||
GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT,
|
||||
];
|
||||
// Marker comment embedded in managed hook entries so GSD can find+remove them.
|
||||
const GSD_CURSOR_HOOK_MARKER = 'gsd-managed';
|
||||
const GSD_CURSOR_HOOK_MARKER = hooksSurface.GSD_CURSOR_HOOK_MARKER;
|
||||
|
||||
// #2100 Stage 2 — Windsurf/Cascade lifecycle hook constants.
|
||||
// Windsurf/Cascade reads hook configs from <project-root>/.windsurf/hooks.json
|
||||
@@ -338,13 +359,15 @@ const GSD_CURSOR_HOOK_MARKER = 'gsd-managed';
|
||||
// have no Windsurf counterpart and are deliberately NOT ported.
|
||||
// Cascade hooks docs (reference): https://docs.windsurf.com/llms-full.txt ,
|
||||
// https://docs.devin.ai/desktop/cascade/hooks
|
||||
const GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT = 'gsd-windsurf-pre-write.js';
|
||||
const GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT = 'gsd-windsurf-pre-command.js';
|
||||
//
|
||||
// Same #2876 fix as the Cursor block above: bare references to hooksSurface's
|
||||
// own exports instead of a second, unlinked literal copy.
|
||||
const GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT = hooksSurface.GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT;
|
||||
const GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT = hooksSurface.GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT;
|
||||
// All GSD-managed Windsurf hook scripts (used by uninstall cleanup).
|
||||
const GSD_WINDSURF_HOOK_SCRIPTS = [
|
||||
GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT,
|
||||
GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT,
|
||||
];
|
||||
// hooksSurface independently defines the same array — bare reference here so
|
||||
// the two can never drift.
|
||||
const GSD_WINDSURF_HOOK_SCRIPTS = hooksSurface.GSD_WINDSURF_HOOK_SCRIPTS;
|
||||
|
||||
// GSD-managed files under hooks/lib/ (helpers required by gsd-*.js hooks).
|
||||
// git-cmd.js does not start with "gsd-" (shared classifier for #3129), gsd-graphify-rebuild.sh does.
|
||||
@@ -708,26 +731,26 @@ const {
|
||||
// getCommitAttribution STAYS here (impure install-time config I/O); it is injected
|
||||
// into the engine functions via the resolveAttribution parameter at each call site.
|
||||
const installEngine = require(path.join(_gsdLibDir, 'install-engine.cjs'));
|
||||
// #2876: _copyStaged, convertClaudeCommandToOpencodeSkill, and
|
||||
// convertClaudeCommandToKiloSkill used to be destructured here too — all
|
||||
// three had no install.js internal caller and no export consumer (tests
|
||||
// import all three directly from gsd-core/bin/lib/install-engine.cjs), so
|
||||
// the retired bindings were dead code. applyOpencodeFamilyPathPrefix,
|
||||
// _runLegacyInstallMigrations, _runLegacyUninstallCleanup,
|
||||
// _removeGsdEntries, _restoreDir, and _removeHermesBareStemDirs were the
|
||||
// same — unused local bindings that were never part of this module's export
|
||||
// surface either — found and retired in the same sweep.
|
||||
const {
|
||||
installRuntimeArtifacts,
|
||||
uninstallRuntimeArtifacts,
|
||||
installOpencodeFamilySkills,
|
||||
installAgentsKindStandalone,
|
||||
_installNativePluginIfDeclared,
|
||||
_copyStaged,
|
||||
hasExistingSymlinkBetween,
|
||||
isSymlinkedDestOptIn,
|
||||
migrateLegacyDevPreferencesToSkill,
|
||||
applyOpencodeFamilyPathPrefix,
|
||||
convertClaudeCommandToOpencodeSkill,
|
||||
convertClaudeCommandToKiloSkill,
|
||||
USER_OWNED_ARTIFACTS,
|
||||
_runLegacyInstallMigrations,
|
||||
_runLegacyUninstallCleanup,
|
||||
_removeGsdEntries,
|
||||
_snapshotDir,
|
||||
_restoreDir,
|
||||
_removeHermesBareStemDirs,
|
||||
} = installEngine;
|
||||
|
||||
// #2875 (epic #2866 Phase 6): durable on-disk staging for USER_OWNED_ARTIFACTS
|
||||
@@ -965,7 +988,8 @@ Then re-run: npx ${pkg.name}@latest
|
||||
}
|
||||
|
||||
// getDirName (runtime -> local config dir name) now lives in
|
||||
// runtime-name-policy.cjs (ADR-1508 / #1510 Phase 1); imported + re-exported.
|
||||
// runtime-name-policy.cjs (ADR-1508 / #1510 Phase 1); imported above for
|
||||
// install.js's own internal use only — #2876 retired the re-export.
|
||||
|
||||
/**
|
||||
* Get the config directory path relative to home directory for a runtime
|
||||
@@ -1086,17 +1110,20 @@ if (hasHelp) {
|
||||
}
|
||||
|
||||
// computePathPrefix: implementation moved to runtimeArtifactConversion._computePathPrefix
|
||||
// (ADR-1508 / #1511 Phase 2 — single owner). The const binding above (~line 638)
|
||||
// re-exports it here for call sites and module.exports.
|
||||
// (ADR-1508 / #1511 Phase 2 — single owner). The const binding above re-binds
|
||||
// it here for install.js's own internal call sites only — #2876 retired the
|
||||
// module.exports entry (zero consumers found; tests import
|
||||
// runtimeArtifactConversion._computePathPrefix directly).
|
||||
// Original doc: Compute the path prefix used for `@file` references in installed
|
||||
// command/skill markdown. For global installs under $HOME uses $HOME/... form;
|
||||
// OpenCode always uses the absolute path (#2376 Windows, #2831 macOS/Linux).
|
||||
|
||||
// normalizeNodePath, resolveNodeRunner, resolveBashRunner, referencesHook are
|
||||
// now owned by the runtime-hooks-surface module. Import them here so
|
||||
// install.js callers continue to work and so there is a single implementation
|
||||
// of these helpers.
|
||||
const normalizeNodePath = hooksSurface.normalizeNodePath;
|
||||
// resolveNodeRunner, resolveBashRunner, referencesHook are now owned by the
|
||||
// runtime-hooks-surface module. Import them here so install.js callers
|
||||
// continue to work and so there is a single implementation of these helpers.
|
||||
// (normalizeNodePath was re-bound here too until #2876 found bin/install.js
|
||||
// had no internal caller and no export consumer for it — hooksSurface owns
|
||||
// the single implementation now, used internally by resolveNodeRunner there.)
|
||||
const resolveNodeRunner = hooksSurface.resolveNodeRunner;
|
||||
const resolveBashRunner = hooksSurface.resolveBashRunner;
|
||||
// referencesHook: pure predicate over hook entry objects, shared between
|
||||
@@ -1114,47 +1141,27 @@ const removeKimiHooksToml = hooksSurface.removeKimiHooksToml;
|
||||
// callers continue to work and there is a single implementation. (All call
|
||||
// sites are below this line, so the const binding has no TDZ hazard.)
|
||||
const processAttribution = runtimeArtifactConversion.processAttribution;
|
||||
// #2875 Part 2: descriptor-driven agent cross-cutting pieces, single-sourced
|
||||
// in runtimeArtifactConversion so the inline agent loop below and the
|
||||
// descriptor pipeline (stageAgentsForRuntimeWithConverter) resolve through
|
||||
// the SAME code — no drift between the two byte-parity-gated pipelines.
|
||||
const {
|
||||
deriveAgentName,
|
||||
applyAgentFrontmatterExtensions,
|
||||
applyAgentBrandingRewrites,
|
||||
} = runtimeArtifactConversion;
|
||||
// computePathPrefix / applyRuntimeContentRewritesInPlace / applyRuntimeContentRewritesForCommandsInPlace:
|
||||
// Single implementations now live in runtimeArtifactConversion (ADR-1508 / #1511 Phase 2).
|
||||
// Re-bound here so install.js call sites and exports continue to work unchanged.
|
||||
// Local bodies replaced by breadcrumb comments at their original locations.
|
||||
// All call sites are below this line → no TDZ hazard.
|
||||
const computePathPrefix = runtimeArtifactConversion._computePathPrefix;
|
||||
const applyRuntimeContentRewritesInPlace = runtimeArtifactConversion.applyRuntimeContentRewritesInPlace;
|
||||
const applyRuntimeContentRewritesForCommandsInPlace = runtimeArtifactConversion.applyRuntimeContentRewritesForCommandsInPlace;
|
||||
// #1675 (ADR-1508): the augment converter family is single-sourced in the
|
||||
// conversion module. install.js re-binds (does not re-define) these so there
|
||||
// is exactly one body — the generative-drift hazard the dedup removes. The two
|
||||
// private helpers (getAugmentSkillAdapterHeader, convertSlashCommandsToAugmentSkillMentions)
|
||||
// live only in the conversion module now; they are no longer duplicated here.
|
||||
// computePathPrefix: implementation lives in runtimeArtifactConversion
|
||||
// (ADR-1508 / #1511 Phase 2 — single owner). Re-bound here so install.js call
|
||||
// sites continue to work. #2876 retired the sibling
|
||||
// applyRuntimeContentRewritesInPlace / applyRuntimeContentRewritesForCommandsInPlace
|
||||
// re-bindings that used to sit alongside it, and the entire #1675 Augment
|
||||
// converter family re-binding (convertClaudeToAugmentMarkdown /
|
||||
// convertClaudeCommandToAugmentSkill / convertClaudeAgentToAugmentAgent) that
|
||||
// used to follow — bin/install.js had no internal caller for any of them (the
|
||||
// descriptor pipeline in runtimeArtifactConversion calls them directly).
|
||||
// (All call sites are below this line → no TDZ hazard.)
|
||||
const convertClaudeToAugmentMarkdown = runtimeArtifactConversion.convertClaudeToAugmentMarkdown;
|
||||
const convertClaudeCommandToAugmentSkill = runtimeArtifactConversion.convertClaudeCommandToAugmentSkill;
|
||||
const convertClaudeAgentToAugmentAgent = runtimeArtifactConversion.convertClaudeAgentToAugmentAgent;
|
||||
const computePathPrefix = runtimeArtifactConversion._computePathPrefix;
|
||||
// #2931 (ADR-1508): the windsurf converter family is single-sourced in the
|
||||
// conversion module, same pattern as the #1675 Augment dedup above. install.js
|
||||
// re-binds (does not re-define) these so there is exactly one body — the
|
||||
// generative-drift hazard the dedup removes. The two private helpers
|
||||
// (getWindsurfSkillAdapterHeader, convertSlashCommandsToWindsurfSkillMentions)
|
||||
// live only in the conversion module now; they are no longer duplicated here.
|
||||
// The reference-identity parity guard lives in
|
||||
// tests/install-runtime-artifacts.test.cjs (single-owner reference-identity
|
||||
// guard describe block), not tests/enh-1511-rewrite-engine-relocation.test.cjs
|
||||
// as the Augment comment above stated — that reference was stale.
|
||||
// conversion module. install.js re-binds (does not re-define) the one member
|
||||
// it still calls internally so there is exactly one body — the
|
||||
// generative-drift hazard the dedup removes. #2876 retired the sibling
|
||||
// convertClaudeCommandToWindsurfSkill / convertClaudeCommandToWindsurfWorkflow /
|
||||
// convertClaudeAgentToWindsurfAgent re-bindings — bin/install.js had no
|
||||
// internal caller for any of them (the descriptor pipeline in
|
||||
// runtimeArtifactConversion calls them directly).
|
||||
// (All call sites are below this line → no TDZ hazard.)
|
||||
const convertClaudeToWindsurfMarkdown = runtimeArtifactConversion.convertClaudeToWindsurfMarkdown;
|
||||
const convertClaudeCommandToWindsurfSkill = runtimeArtifactConversion.convertClaudeCommandToWindsurfSkill;
|
||||
const convertClaudeCommandToWindsurfWorkflow = runtimeArtifactConversion.convertClaudeCommandToWindsurfWorkflow;
|
||||
const convertClaudeAgentToWindsurfAgent = runtimeArtifactConversion.convertClaudeAgentToWindsurfAgent;
|
||||
// #2931 (ADR-1508): single-sourced in the conversion module — was a second,
|
||||
// unlinked verbatim copy here (used by the local Cursor/Trae/CodeBuddy/Cline
|
||||
// converters below), the exact drift class this PR exists to reduce. Verified
|
||||
@@ -1170,125 +1177,14 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
|
||||
return hooksSurface.rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts);
|
||||
}
|
||||
|
||||
// #3329: rewrite already-registered managed `.sh` hook commands to the shape
|
||||
// the current installer would generate (the #580/#3393 bash-runner-omission
|
||||
// migration the register-only-if-absent path never applied to existing
|
||||
// entries). Invoked inside applySettingsJsonHooks in the compiled surface
|
||||
// module; re-bound here for tests/consumers, mirroring the #2979 rewriter.
|
||||
function reconcileManagedShellHookCommands(settings, expected, opts) {
|
||||
return hooksSurface.reconcileManagedShellHookCommands(settings, expected, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the GSD-managed Codex SessionStart hook block for config.toml.
|
||||
*
|
||||
* Issue #3017: the previous shape inlined `command = "node ${path}"` which
|
||||
* fails under GUI/minimal-PATH runtimes where bare `node` doesn't resolve
|
||||
* (same failure mode as #2979 → fixed for settings.json by #3002, this
|
||||
* helper closes the gap for Codex's TOML hook surface).
|
||||
*
|
||||
* Returns null when `absoluteRunner` is null so callers can warn-and-skip
|
||||
* registration — emitting a broken bare-node hook is strictly worse than
|
||||
* not registering one (the user can re-run install once node is on PATH).
|
||||
*
|
||||
* @param {string} targetDir - Resolved absolute Codex config dir (e.g. ~/.codex).
|
||||
* @param {{ absoluteRunner: string|null, eol?: string }} opts
|
||||
* absoluteRunner: result of resolveNodeRunner() — a JSON-stringified
|
||||
* absolute node path with forward slashes (e.g. `"/usr/local/bin/node"`),
|
||||
* or null when process.execPath was unavailable.
|
||||
* eol: line ending to emit ('\n' or '\r\n') — caller passes
|
||||
* detectLineEnding(configContent) so existing CRLF files stay CRLF.
|
||||
* Defaults to '\n'.
|
||||
* @returns {string|null} The toml block to append, or null on missing runner.
|
||||
*/
|
||||
function buildCodexHookBlock(targetDir, opts) {
|
||||
return hooksSurface.buildCodexHookBlock(targetDir, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite legacy bare-`node` managed-hook command lines in a Codex
|
||||
* config.toml string to use the absolute Node runner. Mirror of
|
||||
* rewriteLegacyManagedNodeHookCommands but for the toml surface (#3017).
|
||||
*
|
||||
* Only rewrites entries whose script basename matches CODEX_MANAGED_HOOK_BASENAMES
|
||||
* (basename equality, not substring containment) — user-authored bare-node
|
||||
* hooks pointing at scripts outside the managed allowlist are left alone.
|
||||
*
|
||||
* @param {string} content - Current config.toml contents.
|
||||
* @param {string|null} absoluteRunner - Result of resolveNodeRunner().
|
||||
* @returns {{ content: string, changed: boolean }}
|
||||
*/
|
||||
function rewriteLegacyCodexHookBlock(content, absoluteRunner, opts) {
|
||||
return hooksSurface.rewriteLegacyCodexHookBlock(content, absoluteRunner, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic reconcile helper: ensure hooks.json contains exactly one managed GSD
|
||||
* hook entry for `eventName`, while preserving all user-owned entries.
|
||||
*
|
||||
* Supports both known hooks.json shapes:
|
||||
* 1) { "<EventName>": [...] }
|
||||
* 2) { "hooks": { "<EventName>": [...] } }
|
||||
*
|
||||
* @param {string} targetDir - Codex config dir (e.g. ~/.codex or <project>/.codex).
|
||||
* @param {string} eventName - Codex hook event name (e.g. 'SessionStart', 'Stop').
|
||||
* @param {{ managedCommand?: string|null, commandWindows?: string|null, matcher?: string|null, timeout?: number|null }} opts
|
||||
* managedCommand: POSIX hook command string to register, or null to remove.
|
||||
* commandWindows: Windows .cmd shim path to emit as `commandWindows` field
|
||||
* (#772). When provided, Codex uses this path on Windows and `managedCommand`
|
||||
* on POSIX without needing per-platform config regeneration.
|
||||
* matcher: optional Codex MatcherGroup pattern (e.g. 'Bash|Edit|Write').
|
||||
* timeout: optional timeout in seconds.
|
||||
* @returns {{ changed: boolean, wrote: boolean, path: string }}
|
||||
*/
|
||||
function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
||||
return hooksSurface.reconcileCodexHooksJsonEvent(targetDir, eventName, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconcile the GSD-managed SessionStart hook entry in hooks.json.
|
||||
* Delegates to the generic reconcileCodexHooksJsonEvent helper.
|
||||
*
|
||||
* @param {string} targetDir
|
||||
* @param {{ managedCommand?: string|null, commandWindows?: string|null }} opts
|
||||
* @returns {{ changed: boolean, wrote: boolean, path: string }}
|
||||
*/
|
||||
function reconcileCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
||||
return hooksSurface.reconcileCodexHooksJsonSessionStart(targetDir, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a typed IR for the Codex hook .cmd shim used on Windows (#3426).
|
||||
*
|
||||
* On Windows, Codex runs hook commands from a PowerShell/cmd execution
|
||||
* environment. The previous command format was:
|
||||
*
|
||||
* "C:/Program Files/nodejs/node.exe" "C:/path/.codex/hooks/gsd-check-update.js"
|
||||
*
|
||||
* This caused `bash.exe: bash.exe: cannot execute binary file` because
|
||||
* Codex's hook dispatch shell (Git Bash / MSYS) tried to POSIX-exec node.exe
|
||||
* (a Windows PE binary) via execvp(), which fails with ENOEXEC on Windows PE
|
||||
* binaries that the MSYS layer doesn't know how to fork-exec natively.
|
||||
*
|
||||
* Fix: write a .cmd shim (using the same CRLF .cmd shim pattern) whose
|
||||
* content is `@ECHO OFF / @SETLOCAL / @"node.exe" "script.js" %*`.
|
||||
* cmd.exe executes
|
||||
* .cmd natively via CreateProcess — no POSIX exec layer, no MSYS shebang
|
||||
* walk, no PE binary fork-exec failure.
|
||||
*
|
||||
* Returns the typed IR `{ invocation, cmdPath, hookCommand, render }` so
|
||||
* callers can assert on the structured shape (CONTRIBUTING.md L558–L565
|
||||
* IR-first discipline). Returns null when absoluteRunnerToken is null so
|
||||
* callers can warn-and-skip instead of writing a broken hook.
|
||||
*
|
||||
* @param {string} scriptAbsPath - Absolute path to the .js hook script.
|
||||
* @param {string|null} absoluteRunnerToken - JSON-quoted absolute node path
|
||||
* (result of resolveNodeRunner()), e.g. `"C:/Program Files/nodejs/node.exe"`.
|
||||
* @returns {{ invocation: { interpreter: string, target: string }, cmdPath: string, hookCommand: string, render: { cmd: () => string } }|null}
|
||||
*/
|
||||
function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
|
||||
return hooksSurface.buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken);
|
||||
}
|
||||
// #2876: reconcileManagedShellHookCommands, buildCodexHookBlock,
|
||||
// rewriteLegacyCodexHookBlock, reconcileCodexHooksJsonEvent, and
|
||||
// reconcileCodexHooksJsonSessionStart used to be re-bound here as one-line
|
||||
// delegates to the equivalent hooksSurface.* implementations. None had an
|
||||
// install.js internal caller or an export consumer (tests import all five
|
||||
// directly from gsd-core/bin/lib/runtime-hooks-surface.cjs), so the retired
|
||||
// bindings were dead code with no reachable body — removed rather than kept
|
||||
// as unreachable wrappers.
|
||||
|
||||
/**
|
||||
* Ensure Codex hooks.json contains exactly one managed SessionStart
|
||||
@@ -1507,10 +1403,10 @@ const {
|
||||
// (single source of truth with the descriptor pipeline's
|
||||
// applyAgentFrontmatterExtensions step, which now also owns disallowedTools
|
||||
// injection + the read-only agent deny-list internally — see its module doc
|
||||
// in src/runtime-artifact-conversion.cts). injectEffortFrontmatter is kept
|
||||
// bound here only because it is still part of this module's export surface
|
||||
// (tests reach it via require('../bin/install.js')).
|
||||
const { injectEffortFrontmatter } = runtimeArtifactConversion;
|
||||
// in src/runtime-artifact-conversion.cts). injectEffortFrontmatter used to be
|
||||
// re-bound here purely to stay on this module's export surface; #2876 found
|
||||
// no install.js internal caller either (tests import it directly from
|
||||
// gsd-core/bin/lib/runtime-artifact-conversion.cjs) and retired the binding.
|
||||
|
||||
// Cache for attribution settings (populated once per runtime during install)
|
||||
const attributionCache = new Map();
|
||||
@@ -6662,43 +6558,13 @@ function stripGsdFromCopilotInstructions(content) {
|
||||
const GSD_AGENTS_MD_MARKER = '<!-- GSD Configuration — managed by gsd-core installer -->';
|
||||
const GSD_AGENTS_MD_CLOSE_MARKER = '<!-- End GSD Configuration -->';
|
||||
|
||||
/**
|
||||
* The GSD instruction body shared by the Cline directory-form rules file and
|
||||
* the cross-tool AGENTS.md block. Self-contained — references only the gsd-core
|
||||
* engine layout, not the (separate) #782 Cline skills directory.
|
||||
*/
|
||||
function buildClineRulesBody() {
|
||||
return hooksSurface.buildClineRulesBody();
|
||||
}
|
||||
|
||||
/** AGENTS.md body for the cross-tool global instruction target (`~/.agents/AGENTS.md`). */
|
||||
function buildClineAgentsMdBody() {
|
||||
return hooksSurface.buildClineAgentsMdBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* The Cline PreToolUse hook script (issue #787).
|
||||
*
|
||||
* Cline invokes hooks as executable scripts named exactly after the event with
|
||||
* no extension, passing the operation context as JSON on stdin and reading a
|
||||
* JSON decision from stdout ({ cancel, errorMessage, contextModification }).
|
||||
*
|
||||
* This hook is a self-standing planning-artifact guard: it cancels write-class
|
||||
* tool calls that target `.planning/` (GSD-owned artifacts), and otherwise
|
||||
* allows the operation. It FAILS OPEN — any parse/IO error allows the call so a
|
||||
* hook bug can never wedge the user. No dependency on the #782 skills work.
|
||||
*/
|
||||
function buildClinePreToolUseHook() {
|
||||
return hooksSurface.buildClinePreToolUseHook();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the GSD AGENTS.md block into an existing file (or create it), preserving
|
||||
* any user content. Mirrors mergeCopilotInstructions: marker-delimited, idempotent.
|
||||
*/
|
||||
function mergeGsdAgentsMd(filePath, gsdContent) {
|
||||
return hooksSurface.mergeGsdAgentsMd(filePath, gsdContent);
|
||||
}
|
||||
// #2876: buildClineRulesBody, buildClineAgentsMdBody, buildClinePreToolUseHook,
|
||||
// and mergeGsdAgentsMd used to be re-bound here as one-line delegates to the
|
||||
// equivalent hooksSurface.* implementations. None had an install.js internal
|
||||
// caller or an export consumer (tests import all four directly from
|
||||
// gsd-core/bin/lib/runtime-hooks-surface.cjs), so the retired bindings were
|
||||
// dead code with no reachable body — removed rather than kept as unreachable
|
||||
// wrappers.
|
||||
|
||||
/**
|
||||
* Strip the GSD block from AGENTS.md content. Returns null if the file became
|
||||
@@ -6750,47 +6616,13 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
|
||||
//
|
||||
// References: https://cursor.com/docs/hooks
|
||||
|
||||
/**
|
||||
* Build a managed Cursor hook entry for a given hook script path.
|
||||
*
|
||||
* @param {string} scriptPath - Absolute path to the hook script
|
||||
* @returns {object} Cursor hook entry object
|
||||
*/
|
||||
function buildCursorHookEntry(scriptPath) {
|
||||
return hooksSurface.buildCursorHookEntry(scriptPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a Cursor hook entry is GSD-managed.
|
||||
* Detection: presence of the GSD_CURSOR_HOOK_MARKER sentinel field.
|
||||
*
|
||||
* @param {object} entry - A hooks array element from hooks.json
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isManagedCursorHookEntry(entry) {
|
||||
return hooksSurface.isManagedCursorHookEntry(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconcile the GSD-managed entries in a Cursor hooks.json file.
|
||||
*
|
||||
* Supports both known hooks.json shapes:
|
||||
* 1) { "version": 1, "hooks": { "sessionStart": [...], "postToolUse": [...] } }
|
||||
* 2) { "sessionStart": [...], "postToolUse": [...] } (no wrapper object)
|
||||
*
|
||||
* Managed entries (those with GSD_CURSOR_HOOK_MARKER) are removed then
|
||||
* re-added if managedEntries is non-null/non-empty. User-owned entries are
|
||||
* preserved. File is written atomically only when content changes.
|
||||
*
|
||||
* @param {string} hooksJsonPath - Absolute path to the hooks.json file
|
||||
* @param {{ sessionStart?: object|null, postToolUse?: object|null }|null} managedEntries
|
||||
* Map from event name to the new hook entry to register (or null to remove).
|
||||
* Pass null for the whole param to remove all managed entries.
|
||||
* @returns {{ changed: boolean, wrote: boolean, path: string }}
|
||||
*/
|
||||
function reconcileCursorHooksJson(hooksJsonPath, managedEntries) {
|
||||
return hooksSurface.reconcileCursorHooksJson(hooksJsonPath, managedEntries);
|
||||
}
|
||||
// #2876: buildCursorHookEntry, isManagedCursorHookEntry, and
|
||||
// reconcileCursorHooksJson used to be re-bound here as one-line delegates to
|
||||
// the equivalent hooksSurface.* implementations. None had an install.js
|
||||
// internal caller or an export consumer (tests import all three directly from
|
||||
// gsd-core/bin/lib/runtime-hooks-surface.cjs), so the retired bindings were
|
||||
// dead code with no reachable body — removed rather than kept as unreachable
|
||||
// wrappers.
|
||||
|
||||
/**
|
||||
* #777 — Write GSD-managed Cursor lifecycle hooks into <targetDir>/hooks.json.
|
||||
@@ -6850,23 +6682,13 @@ function removeWindsurfHooksJson(targetDir) {
|
||||
return hooksSurface.removeWindsurfHooksJson(targetDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* #786 — Build the GSD-managed GitHub Copilot lifecycle hook config object.
|
||||
*
|
||||
* Returns the verbatim JSON shape Copilot CLI expects:
|
||||
* { version: 1, hooks: { sessionStart: [ <hook entry> ] } }
|
||||
*
|
||||
* The sessionStart entry is a `command` hook whose `bash`/`powershell` bodies
|
||||
* run inline (no external script file), so the config can never reference a
|
||||
* hook script that the installer did not also install — it is self-contained
|
||||
* by construction. The command is advisory-only (always exits 0) and orients
|
||||
* the agent toward the project's GSD planning state at session start.
|
||||
*
|
||||
* @returns {object} Copilot hooks-configuration object
|
||||
*/
|
||||
function buildCopilotHookConfig() {
|
||||
return hooksSurface.buildCopilotHookConfig();
|
||||
}
|
||||
// #2876: buildCopilotHookConfig used to be re-bound here as a one-line
|
||||
// delegate to hooksSurface.buildCopilotHookConfig. It had no install.js
|
||||
// internal caller or export consumer (tests import it directly from
|
||||
// gsd-core/bin/lib/runtime-hooks-surface.cjs), so the retired binding was
|
||||
// dead code with no reachable body — removed rather than kept as an
|
||||
// unreachable wrapper. writeCopilotHookConfig below is unaffected — it still
|
||||
// has an internal caller (finishInstall).
|
||||
|
||||
/**
|
||||
* #786 — Write the GSD-managed Copilot lifecycle hook config under the runtime
|
||||
@@ -7395,10 +7217,14 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
|
||||
|
||||
// convertClaudeCommandToOpencodeFamilySkill, convertClaudeCommandToOpencodeSkill,
|
||||
// convertClaudeCommandToKiloSkill: moved to src/install-engine.cts (ADR-1239 Phase B).
|
||||
// Imported from installEngine above.
|
||||
// #2876 found no install.js internal caller for the latter two (tests import
|
||||
// them directly from gsd-core/bin/lib/install-engine.cjs) and retired the
|
||||
// destructured bindings above.
|
||||
|
||||
// applyOpencodeFamilyPathPrefix: moved to src/install-engine.cts (ADR-1239 Phase B).
|
||||
// Imported from installEngine above.
|
||||
// #2876 found no install.js internal caller (never had one either — it was
|
||||
// never part of this module's export surface) and retired the destructured
|
||||
// binding above.
|
||||
//
|
||||
// copyFlattenedCommands (OpenCode/Kilo flattened command/ writer): moved to
|
||||
// src/install-engine.cts as installOpencodeFamilyCommands (ADR-1239 / #2087).
|
||||
@@ -7500,12 +7326,13 @@ function writeHermesCategoryDescription(categoryDir) {
|
||||
* @param {boolean} isGlobal - Whether this is a global install
|
||||
*/
|
||||
|
||||
// USER_OWNED_ARTIFACTS, migrateLegacyDevPreferencesToSkill, _copyStaged, _removeGsdEntries,
|
||||
// _runLegacyInstallMigrations, _runLegacyUninstallCleanup, _snapshotDir,
|
||||
// _restoreDir, _removeHermesBareStemDirs, installRuntimeArtifacts,
|
||||
// installOpencodeFamilySkills, uninstallRuntimeArtifacts:
|
||||
// ALL moved to src/install-engine.cts (ADR-1239 Phase B).
|
||||
// Imported from installEngine above.
|
||||
// USER_OWNED_ARTIFACTS, migrateLegacyDevPreferencesToSkill, _snapshotDir,
|
||||
// installRuntimeArtifacts, installOpencodeFamilySkills, uninstallRuntimeArtifacts:
|
||||
// ALL moved to src/install-engine.cts (ADR-1239 Phase B). Imported from
|
||||
// installEngine above. _copyStaged, _removeGsdEntries,
|
||||
// _runLegacyInstallMigrations, _runLegacyUninstallCleanup, _restoreDir, and
|
||||
// _removeHermesBareStemDirs moved there too but #2876 found no install.js
|
||||
// internal caller for any of them and retired their destructured bindings.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase 2 — Layout-driven install/uninstall orchestrators (moved to engine)
|
||||
@@ -13547,8 +13374,6 @@ module.exports = {
|
||||
shouldNormalizeHyphenNamespaceInAgentBody,
|
||||
normalizeAgentBodyForRuntime,
|
||||
yamlIdentifier,
|
||||
computePathPrefix,
|
||||
applyRuntimeContentRewritesInPlace,
|
||||
getCodexSkillAdapterHeader,
|
||||
convertClaudeCommandToCursorSkill,
|
||||
convertClaudeAgentToCursorAgent,
|
||||
@@ -13562,7 +13387,6 @@ module.exports = {
|
||||
_resolveSkillsRootDir,
|
||||
codexBareAgentsHasOnlyKnownScalars,
|
||||
extractCodexUserAgentsScalars,
|
||||
spliceCodexAgentsScalars,
|
||||
CODEX_EXTENDED_HOOK_EVENTS,
|
||||
generateCodexConfigBlock,
|
||||
stripGsdFromCodexConfig,
|
||||
@@ -13573,13 +13397,6 @@ module.exports = {
|
||||
validateCodexConfigSchema,
|
||||
mergeCodexConfig,
|
||||
installCodexConfig,
|
||||
readGsdRuntimeProfileResolver,
|
||||
readGsdEffectiveModelOverrides,
|
||||
readGsdEffectiveEffortConfig,
|
||||
resolveInstallTimeEffort,
|
||||
injectEffortFrontmatter,
|
||||
get _GSD_EFFORT_MANIFEST_TIER_DEFAULTS() { return _getGsdEffortCatalog().EFFORT_MANIFEST_TIER_DEFAULTS; },
|
||||
get _GSD_EFFORT_MANIFEST_DEFAULT() { return _getGsdEffortCatalog().EFFORT_MANIFEST_DEFAULT; },
|
||||
install,
|
||||
installAllRuntimes,
|
||||
uninstall,
|
||||
@@ -13601,8 +13418,6 @@ module.exports = {
|
||||
buildKimiAgentArtifacts,
|
||||
convertClaudeToOpencodeFrontmatter,
|
||||
convertClaudeToKiloFrontmatter,
|
||||
convertClaudeCommandToOpencodeSkill,
|
||||
convertClaudeCommandToKiloSkill,
|
||||
configureOpencodePermissions,
|
||||
neutralizeAgentReferences,
|
||||
// #768 — Claude Code permissions pre-population
|
||||
@@ -13612,7 +13427,6 @@ module.exports = {
|
||||
GSD_CLAUDE_DENY_PERMISSIONS,
|
||||
GSD_CODEX_MARKER,
|
||||
CODEX_AGENT_SANDBOX,
|
||||
getDirName,
|
||||
getGlobalDir,
|
||||
getConfigDirFromHome,
|
||||
resolveKiloConfigPath,
|
||||
@@ -13634,20 +13448,11 @@ module.exports = {
|
||||
mergeCopilotInstructions,
|
||||
stripGsdFromCopilotInstructions,
|
||||
GSD_COPILOT_HOOK_FILE,
|
||||
buildCopilotHookConfig,
|
||||
writeCopilotHookConfig,
|
||||
convertClaudeToAntigravityContent,
|
||||
convertClaudeCommandToAntigravitySkill,
|
||||
convertClaudeAgentToAntigravityAgent,
|
||||
convertClaudeCommandToClaudeSkill,
|
||||
skillFrontmatterName,
|
||||
convertClaudeToWindsurfMarkdown,
|
||||
convertClaudeCommandToWindsurfSkill,
|
||||
convertClaudeCommandToWindsurfWorkflow,
|
||||
convertClaudeAgentToWindsurfAgent,
|
||||
convertClaudeToAugmentMarkdown,
|
||||
convertClaudeCommandToAugmentSkill,
|
||||
convertClaudeAgentToAugmentAgent,
|
||||
convertClaudeToTraeMarkdown,
|
||||
convertClaudeCommandToTraeSkill,
|
||||
convertClaudeAgentToTraeAgent,
|
||||
@@ -13658,8 +13463,6 @@ module.exports = {
|
||||
convertClaudeToCliineMarkdown,
|
||||
convertClaudeCommandToClineSkill,
|
||||
convertClaudeAgentToClineAgent,
|
||||
// #2284(b) — cross-cutting branding protected-region helper
|
||||
applyClaudeCodeBrandSwap,
|
||||
// #2284 — Hermes named-dispatch → delegate_task projection
|
||||
convertClaudeToHermesMarkdown,
|
||||
projectNamedDispatchToStructuralDelegate,
|
||||
@@ -13669,30 +13472,11 @@ module.exports = {
|
||||
maskStringLiterals,
|
||||
findDispatchCallSpans,
|
||||
_assertProjectionComplete,
|
||||
_normalizeDispatchCallSpan,
|
||||
buildClineRulesBody,
|
||||
buildClineAgentsMdBody,
|
||||
buildClinePreToolUseHook,
|
||||
writeClineArtifacts,
|
||||
mergeGsdAgentsMd,
|
||||
GSD_CURSOR_SESSION_HOOK_SCRIPT,
|
||||
GSD_CURSOR_POST_TOOL_HOOK_SCRIPT,
|
||||
GSD_CURSOR_PRE_TOOL_HOOK_SCRIPT,
|
||||
GSD_CURSOR_STOP_HOOK_SCRIPT,
|
||||
GSD_CURSOR_SUBAGENT_START_HOOK_SCRIPT,
|
||||
GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT,
|
||||
GSD_CURSOR_HOOK_SCRIPTS,
|
||||
GSD_CURSOR_HOOK_MARKER,
|
||||
buildCursorHookEntry,
|
||||
isManagedCursorHookEntry,
|
||||
reconcileCursorHooksJson,
|
||||
writeCursorHooksJson,
|
||||
removeCursorHooksJson,
|
||||
GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT,
|
||||
GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT,
|
||||
GSD_WINDSURF_HOOK_SCRIPTS,
|
||||
writeWindsurfHooksJson,
|
||||
removeWindsurfHooksJson,
|
||||
stripGsdFromAgentsMd,
|
||||
GSD_AGENTS_MD_MARKER,
|
||||
GSD_AGENTS_MD_CLOSE_MARKER,
|
||||
@@ -13700,13 +13484,7 @@ module.exports = {
|
||||
saveLocalPatches,
|
||||
reportLocalPatches,
|
||||
validateHookFields,
|
||||
migrateLegacyDevPreferencesToSkill,
|
||||
populatePristineDir,
|
||||
USER_OWNED_ARTIFACTS,
|
||||
stageUserArtifacts,
|
||||
restoreStagedUserArtifacts,
|
||||
discardStagedUserArtifacts,
|
||||
recoverOrphanedUserArtifacts,
|
||||
_resolveUserArtifactStagingRoot,
|
||||
_tryResolveUserArtifactStagingRoot,
|
||||
finishInstall,
|
||||
@@ -13723,35 +13501,11 @@ module.exports = {
|
||||
buildUpdateBannerPromptText,
|
||||
parseUpdateBannerInput,
|
||||
buildUpdateBannerHookEntry,
|
||||
buildHookCommand,
|
||||
normalizeNodePath,
|
||||
resolveNodeRunner,
|
||||
referencesHook,
|
||||
applySettingsJsonHooks,
|
||||
rewriteLegacyManagedNodeHookCommands,
|
||||
reconcileManagedShellHookCommands,
|
||||
buildCodexHookBlock,
|
||||
rewriteLegacyCodexHookBlock,
|
||||
buildCodexHookWindowsShimIR,
|
||||
ensureCodexHooksJsonSessionStart,
|
||||
ensureCodexHooksJsonEvent,
|
||||
removeCodexHooksJsonEvent,
|
||||
reconcileCodexHooksJsonEvent,
|
||||
readGsdCommandNames,
|
||||
installRuntimeArtifacts,
|
||||
installOpencodeFamilySkills,
|
||||
uninstallRuntimeArtifacts,
|
||||
parseConfigDirFromArgs,
|
||||
cleanupLegacyGsdCc,
|
||||
_applyRuntimeRewrites,
|
||||
// #1191 — exported so tests exercise the REAL readSettings, not a replica
|
||||
readSettings,
|
||||
stripJsonComments,
|
||||
// Compatibility relays retained after auditing the former broad
|
||||
// runtimeArtifactConversion spread (#1559).
|
||||
processAttribution,
|
||||
applyRuntimeContentRewritesForCommandsInPlace,
|
||||
_copyStaged,
|
||||
copyWithPathReplacement,
|
||||
};
|
||||
|
||||
|
||||
@@ -91,3 +91,37 @@ Two deferred follow-ups were spun out as **sub-issues of #1507** and have since
|
||||
- **#1676** (PR #1686) — added the `fast-check` property test (`$HOME`-collapse invariant + path-rewrite idempotency) promised in #1511's test scope.
|
||||
|
||||
Delivery verified by a Codex (`gpt-5.4`, high-effort, read-only) review against the epic's stated deliverables, cross-checked against the indexed code graph and live source.
|
||||
|
||||
## Amendment — 2026-08-17 (#2876): the re-export mandate's stated beneficiary was the test suite
|
||||
|
||||
This ADR's relocation slices, and ADR-857's before them, kept a re-export spine in `bin/install.js`
|
||||
so that *"existing consumers that do `require('../bin/install.js').writeCursorHooksJson` (etc.)
|
||||
continue to work unchanged"* — the wording carried in `src/runtime-hooks-surface.cts`'s own header.
|
||||
|
||||
**Epic #2866 Phase 7 measured the consumers. There are none.**
|
||||
|
||||
`bin/install.js` exported **197** names. Every reference to it outside `tests/` — in `src/`,
|
||||
`hooks/`, `scripts/` and `bin/` — sits **inside a comment**, including the one in
|
||||
`runtime-hooks-surface.cts` that named the case. Not one line of production code has ever required
|
||||
the file. The 44 consumers are test files.
|
||||
|
||||
So the pattern was real and the reasoning behind it was sound; the beneficiary was simply
|
||||
misidentified. The compatibility spine was preserving access for a caller that did not exist, while
|
||||
the actual dependency — the test suite reaching implementation through the installer rather than
|
||||
through the modules that own it — went unnamed and therefore unmanaged.
|
||||
|
||||
**What Phase 7 changed.** 9 dead exports and 61 pass-throughs are gone; their tests now import the
|
||||
extracted modules directly. `bin/install.js` exports **127** — 197 − 9 − 61 — and every one of those
|
||||
127 is substantive local implementation. They are not a compatibility wall; they are code with no
|
||||
other home yet, and extracting them is a different piece of work.
|
||||
|
||||
**What this does not change.** The dependency direction this ADR established — installer and layout
|
||||
import the conversion module, the conversion module imports nothing upward — is untouched, and so is
|
||||
ADR-857's capability-registry contract. Relocation still requires the moved symbol to keep working
|
||||
for its real callers; the correction is only about who those callers are.
|
||||
|
||||
**The generalizable point.** A back-compat re-export is a claim about consumers, and a claim about
|
||||
consumers is checkable. This one went four ADRs and several epics without being checked, which let
|
||||
an interface be shaped by test access while everyone believed it was shaped by callers. Before
|
||||
adding a compatibility layer, enumerate what it is compatible *for*; if the answer is "the tests",
|
||||
the honest fix is to point the tests at the owning module.
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
* BEHAVIOR-PRESERVING RELOCATION: all logic is copied verbatim from
|
||||
* bin/install.js. No behavior change, no descriptor reads, no new IO.
|
||||
*
|
||||
* bin/install.js re-exports every symbol from this module so existing
|
||||
* consumers that do require('../bin/install.js').writeCursorHooksJson
|
||||
* (etc.) continue to work unchanged.
|
||||
* #2876 (epic #2866 Phase 7): bin/install.js previously re-exported every
|
||||
* symbol from this module, but a repo-wide audit found zero production
|
||||
* consumers of those re-exports — no `require('../bin/install.js').
|
||||
* writeCursorHooksJson` (or any sibling) exists outside a doc comment
|
||||
* anywhere in the tree. The re-exports were test-suite-only pass-throughs;
|
||||
* tests now require this module directly instead.
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
|
||||
@@ -181,7 +181,9 @@ export const NO_LOCAL_CONFIG_DIR_SENTINEL = '(no-local-config-dir)';
|
||||
* Pure runtime-identity projection. Relocated from `bin/install.js` per
|
||||
* ADR-1508 (epic #1507, #1510 Phase 1) so the Runtime Artifact Conversion
|
||||
* Module's rewrite engine can consume it without importing the installer.
|
||||
* `bin/install.js` re-exports this same function for back-compat.
|
||||
* Consumers import it from here. `bin/install.js` used to re-export it for
|
||||
* back-compat; that re-export was retired in #2876, once an audit found the
|
||||
* compatibility spine had no production consumer at all.
|
||||
*/
|
||||
export function getDirName(runtime: string): string {
|
||||
if (!runtime) return '.claude';
|
||||
|
||||
@@ -15,7 +15,7 @@ const {
|
||||
convertClaudeCommandToAugmentSkill,
|
||||
convertClaudeAgentToAugmentAgent,
|
||||
convertClaudeToAugmentMarkdown,
|
||||
} = require('../bin/install.js');
|
||||
} = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
describe('convertClaudeCommandToAugmentSkill', () => {
|
||||
test('writes unquoted Augment skill name in frontmatter', () => {
|
||||
|
||||
@@ -34,21 +34,24 @@ const { PROBE_TIMEOUT_MS, INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs
|
||||
const INSTALL_SCRIPT = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
|
||||
const {
|
||||
getDirName,
|
||||
getConfigDirFromHome,
|
||||
convertClaudeToCliineMarkdown,
|
||||
install,
|
||||
finishInstall,
|
||||
uninstall,
|
||||
buildClineRulesBody,
|
||||
buildClinePreToolUseHook,
|
||||
buildClineAgentsMdBody,
|
||||
mergeGsdAgentsMd,
|
||||
stripGsdFromAgentsMd,
|
||||
GSD_AGENTS_MD_MARKER,
|
||||
GSD_AGENTS_MD_CLOSE_MARKER,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { getDirName } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
|
||||
const {
|
||||
buildClineRulesBody,
|
||||
buildClinePreToolUseHook,
|
||||
buildClineAgentsMdBody,
|
||||
mergeGsdAgentsMd,
|
||||
} = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
|
||||
const { getGlobalConfigDir } = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
|
||||
describe('Cline runtime directory mapping', () => {
|
||||
|
||||
@@ -13,7 +13,6 @@ const os = require('node:os');
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
|
||||
const {
|
||||
getDirName,
|
||||
getConfigDirFromHome,
|
||||
convertClaudeToCodebuddyMarkdown,
|
||||
convertClaudeCommandToCodebuddySkill,
|
||||
@@ -24,6 +23,7 @@ const {
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { installRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
const { getDirName } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
|
||||
|
||||
const { getGlobalConfigDir } = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
|
||||
|
||||
@@ -69,10 +69,10 @@ const {
|
||||
GSD_CODEX_MARKER,
|
||||
CODEX_AGENT_SANDBOX,
|
||||
parseTomlToObject,
|
||||
resolveNodeRunner,
|
||||
validateCodexConfigSchema,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { resolveNodeRunner } = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
const { resolveInstallPlan } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
|
||||
|
||||
function runCodexInstall(codexHome, cwd = path.join(__dirname, '..')) {
|
||||
@@ -5392,9 +5392,9 @@ const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const INSTALL = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
const HOOKS_SURFACE = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs'));
|
||||
const projection = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const { buildCodexHookBlock, rewriteLegacyCodexHookBlock, resolveNodeRunner } = INSTALL;
|
||||
const { buildCodexHookBlock, rewriteLegacyCodexHookBlock, resolveNodeRunner } = HOOKS_SURFACE;
|
||||
const { projectCodexHookTomlCommand } = projection;
|
||||
|
||||
/**
|
||||
@@ -6959,15 +6959,19 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const INSTALL = require('../bin/install.js');
|
||||
const HOOKS_SURFACE = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
const PROJECTION = require('../gsd-core/bin/lib/shell-command-projection.cjs');
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
|
||||
const {
|
||||
uninstall,
|
||||
} = INSTALL;
|
||||
|
||||
const {
|
||||
buildCodexHookWindowsShimIR,
|
||||
ensureCodexHooksJsonSessionStart,
|
||||
resolveNodeRunner,
|
||||
uninstall,
|
||||
} = INSTALL;
|
||||
} = HOOKS_SURFACE;
|
||||
|
||||
const { projectManagedHookCommand } = PROJECTION;
|
||||
|
||||
@@ -6991,12 +6995,12 @@ function hookHandlersForEvent(hooksJson, eventName) {
|
||||
describe('#3426 — export surface: buildCodexHookWindowsShimIR must be exported', () => {
|
||||
test('buildCodexHookWindowsShimIR is a function', () => {
|
||||
assert.equal(typeof buildCodexHookWindowsShimIR, 'function',
|
||||
'buildCodexHookWindowsShimIR must be exported from bin/install.js');
|
||||
'buildCodexHookWindowsShimIR must be exported from runtime-hooks-surface.cjs');
|
||||
});
|
||||
|
||||
test('ensureCodexHooksJsonSessionStart is a function', () => {
|
||||
assert.equal(typeof ensureCodexHooksJsonSessionStart, 'function',
|
||||
'ensureCodexHooksJsonSessionStart must be exported from bin/install.js');
|
||||
'ensureCodexHooksJsonSessionStart must be exported from runtime-hooks-surface.cjs');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9382,13 +9386,12 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const INSTALL = require('../bin/install.js');
|
||||
const {
|
||||
ensureCodexHooksJsonSessionStart,
|
||||
ensureCodexHooksJsonEvent,
|
||||
removeCodexHooksJsonEvent,
|
||||
reconcileCodexHooksJsonEvent,
|
||||
} = INSTALL;
|
||||
} = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
@@ -9430,17 +9433,17 @@ function stubHookFile(targetDir, hookName) {
|
||||
describe('enh-772: export surface — new functions are exported', () => {
|
||||
test('ensureCodexHooksJsonEvent is a function', () => {
|
||||
assert.strictEqual(typeof ensureCodexHooksJsonEvent, 'function',
|
||||
'ensureCodexHooksJsonEvent must be exported from bin/install.js');
|
||||
'ensureCodexHooksJsonEvent must be exported from runtime-hooks-surface.cjs');
|
||||
});
|
||||
|
||||
test('removeCodexHooksJsonEvent is a function', () => {
|
||||
assert.strictEqual(typeof removeCodexHooksJsonEvent, 'function',
|
||||
'removeCodexHooksJsonEvent must be exported from bin/install.js');
|
||||
'removeCodexHooksJsonEvent must be exported from runtime-hooks-surface.cjs');
|
||||
});
|
||||
|
||||
test('reconcileCodexHooksJsonEvent is a function', () => {
|
||||
assert.strictEqual(typeof reconcileCodexHooksJsonEvent, 'function',
|
||||
'reconcileCodexHooksJsonEvent must be exported from bin/install.js');
|
||||
'reconcileCodexHooksJsonEvent must be exported from runtime-hooks-surface.cjs');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ const {
|
||||
} = require('../gsd-core/bin/lib/host-integration.cjs');
|
||||
|
||||
const install = require('../bin/install.js');
|
||||
const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
|
||||
const CODEX_CAP = JSON.parse(
|
||||
@@ -201,9 +202,9 @@ test('upgrade 1 — codex registers all documented hooks.json lifecycle events,
|
||||
fs.writeFileSync(path.join(tmp, 'hooks', 'gsd-context-monitor.js'), '// stub');
|
||||
fs.writeFileSync(path.join(tmp, 'hooks', 'gsd-check-update.js'), '// stub');
|
||||
for (const ev of install.CODEX_EXTENDED_HOOK_EVENTS) {
|
||||
install.ensureCodexHooksJsonEvent(tmp, ev, { absoluteRunner: '/usr/bin/node', platform: 'linux' });
|
||||
hooksSurface.ensureCodexHooksJsonEvent(tmp, ev, { absoluteRunner: '/usr/bin/node', platform: 'linux' });
|
||||
}
|
||||
install.ensureCodexHooksJsonSessionStart(tmp, { absoluteRunner: '/usr/bin/node', platform: 'linux' });
|
||||
hooksSurface.ensureCodexHooksJsonSessionStart(tmp, { absoluteRunner: '/usr/bin/node', platform: 'linux' });
|
||||
const hooksJson = JSON.parse(fs.readFileSync(path.join(tmp, 'hooks.json'), 'utf8'));
|
||||
const registered = Object.keys(hooksJson.hooks || hooksJson || {});
|
||||
for (const ev of ['PreToolUse', 'PermissionRequest', 'PreCompact', 'PostCompact', 'SubagentStop', 'UserPromptSubmit']) {
|
||||
|
||||
@@ -28,7 +28,6 @@ const { parseFrontmatter, createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const { listAgentFiles } = require('./helpers/agent-roster.cjs');
|
||||
|
||||
const {
|
||||
getDirName,
|
||||
getConfigDirFromHome,
|
||||
claudeToCopilotTools,
|
||||
convertCopilotToolName,
|
||||
@@ -40,8 +39,6 @@ const {
|
||||
mergeCopilotInstructions,
|
||||
stripGsdFromCopilotInstructions,
|
||||
GSD_COPILOT_HOOK_FILE,
|
||||
buildCopilotHookConfig,
|
||||
writeCopilotHookConfig,
|
||||
writeManifest,
|
||||
reportLocalPatches,
|
||||
runtimeMap,
|
||||
@@ -51,6 +48,11 @@ const {
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { installRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
const { getDirName } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
|
||||
const {
|
||||
buildCopilotHookConfig,
|
||||
writeCopilotHookConfig,
|
||||
} = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
|
||||
const { getGlobalConfigDir } = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@ const assert = require('node:assert/strict');
|
||||
const {
|
||||
convertClaudeCommandToCursorSkill,
|
||||
convertClaudeAgentToCursorAgent,
|
||||
_applyRuntimeRewrites,
|
||||
} = require('../bin/install.js');
|
||||
const {
|
||||
_applyRuntimeRewrites,
|
||||
} = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
describe('convertClaudeCommandToCursorSkill', () => {
|
||||
test('writes unquoted Cursor skill name in frontmatter', () => {
|
||||
|
||||
@@ -25,10 +25,13 @@ const {
|
||||
} = require('../gsd-core/bin/lib/host-integration-adapters/imperative-hook-bus.cjs');
|
||||
|
||||
const {
|
||||
reconcileCursorHooksJson,
|
||||
GSD_CURSOR_HOOK_MARKER,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const {
|
||||
reconcileCursorHooksJson,
|
||||
} = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
|
||||
const CUR_CAP = JSON.parse(
|
||||
|
||||
@@ -43,15 +43,18 @@ const os = require('node:os');
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
|
||||
const {
|
||||
reconcileCursorHooksJson,
|
||||
isManagedCursorHookEntry,
|
||||
buildCursorHookEntry,
|
||||
removeCursorHooksJson,
|
||||
GSD_CURSOR_HOOK_MARKER,
|
||||
GSD_CURSOR_SESSION_HOOK_SCRIPT,
|
||||
GSD_CURSOR_POST_TOOL_HOOK_SCRIPT,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const {
|
||||
reconcileCursorHooksJson,
|
||||
isManagedCursorHookEntry,
|
||||
buildCursorHookEntry,
|
||||
removeCursorHooksJson,
|
||||
} = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
|
||||
const {
|
||||
resolveRuntimeConfigIntent,
|
||||
INSTALL_SURFACES,
|
||||
|
||||
@@ -29,13 +29,15 @@ const {
|
||||
maskStringLiterals,
|
||||
findDispatchCallSpans,
|
||||
_assertProjectionComplete,
|
||||
applyClaudeCodeBrandSwap,
|
||||
convertClaudeToWindsurfMarkdown,
|
||||
install,
|
||||
uninstall,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { installRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
const {
|
||||
applyClaudeCodeBrandSwap,
|
||||
convertClaudeToWindsurfMarkdown,
|
||||
} = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
const { parseFrontmatter, cleanup } = require('./helpers.cjs');
|
||||
const { nestedSkillPath } = require('./helpers/nested-layout.cjs');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
@@ -124,9 +124,12 @@ describe('hook file validation', () => {
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Typed import — no source-grep needed (#455)
|
||||
const { GSD_UNINSTALL_HOOKS, buildHookCommand } = require(
|
||||
const { GSD_UNINSTALL_HOOKS } = require(
|
||||
path.join(__dirname, '..', 'bin', 'install.js')
|
||||
);
|
||||
const { buildHookCommand } = require(
|
||||
path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs')
|
||||
);
|
||||
|
||||
describe('installer hook registration', () => {
|
||||
test('GSD_UNINSTALL_HOOKS includes all 3 opt-in bash hooks', () => {
|
||||
|
||||
@@ -1044,7 +1044,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs');
|
||||
const { applySettingsJsonHooks } = require('../bin/install.js');
|
||||
const { applySettingsJsonHooks } = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
|
||||
const JS_HOOKS = [
|
||||
'gsd-check-update.js',
|
||||
@@ -1167,7 +1167,7 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { createTempDir, cleanup, captureConsole } = require('./helpers.cjs');
|
||||
const { applySettingsJsonHooks } = require('../bin/install.js');
|
||||
const { applySettingsJsonHooks } = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
|
||||
const SH_HOOKS = [
|
||||
'gsd-validate-commit.sh',
|
||||
@@ -1292,7 +1292,7 @@ before(() => {
|
||||
ensureHooksDist();
|
||||
});
|
||||
|
||||
const { applySettingsJsonHooks } = require('../bin/install.js');
|
||||
const { applySettingsJsonHooks } = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -38,13 +38,19 @@ try {
|
||||
else process.env.GSD_TEST_MODE = savedTestMode;
|
||||
}
|
||||
|
||||
const { install, mergeClaudePermissions, GSD_CLAUDE_ALLOW_PERMISSIONS, GSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS, GSD_CLAUDE_DENY_PERMISSIONS, rewriteLegacyManagedNodeHookCommands, resolveNodeRunner, copyWithPathReplacement } = installExports || {};
|
||||
const { install, mergeClaudePermissions, GSD_CLAUDE_ALLOW_PERMISSIONS, GSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS, GSD_CLAUDE_DENY_PERMISSIONS, copyWithPathReplacement } = installExports || {};
|
||||
|
||||
const {
|
||||
installRuntimeArtifacts,
|
||||
uninstallRuntimeArtifacts,
|
||||
} = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
const {
|
||||
rewriteLegacyManagedNodeHookCommands,
|
||||
resolveNodeRunner,
|
||||
reconcileManagedShellHookCommands,
|
||||
} = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
|
||||
const INSTALL_SCRIPT = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
// #3145: class-norm timeout, not a per-suite value — see helpers/timeouts.cjs.
|
||||
const { INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
@@ -120,7 +126,7 @@ describe('Defect #1 regression (#3664 reversed by #947): bare-stem dirs removed,
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
assert.strictEqual(typeof installRuntimeArtifacts, 'function',
|
||||
'installRuntimeArtifacts must be exported from bin/install.js');
|
||||
'installRuntimeArtifacts must be exported from install-engine.cjs');
|
||||
|
||||
// Pre-create #3664-era bare-stem Hermes layout (no gsd- prefix, now stale).
|
||||
// Use real GSD command stems (help, quick) that readGsdCommandNames() knows about.
|
||||
@@ -307,7 +313,7 @@ describe('U1 (#2973): uninstallRuntimeArtifacts qwen migrates dev-preferences
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
assert.strictEqual(typeof uninstallRuntimeArtifacts, 'function',
|
||||
'uninstallRuntimeArtifacts must be exported from bin/install.js');
|
||||
'uninstallRuntimeArtifacts must be exported from install-engine.cjs');
|
||||
|
||||
const legacyDir = path.join(configDir, 'commands', 'gsd');
|
||||
fs.mkdirSync(legacyDir, { recursive: true });
|
||||
@@ -925,7 +931,7 @@ describe('#976 regression: installer does not duplicate managed hooks when regis
|
||||
|
||||
test('rewriteLegacyManagedNodeHookCommands leaves args-form launcher entries unchanged', () => {
|
||||
assert.strictEqual(typeof rewriteLegacyManagedNodeHookCommands, 'function',
|
||||
'rewriteLegacyManagedNodeHookCommands must be exported from bin/install.js');
|
||||
'rewriteLegacyManagedNodeHookCommands must be exported from runtime-hooks-surface.cjs');
|
||||
|
||||
const launcherCommand = '/usr/local/bin/node-launcher';
|
||||
const hookPath = '/Users/user/.claude/hooks/gsd-check-update.js';
|
||||
@@ -1343,27 +1349,15 @@ describe('#1924: profile-user.md backup path must be outside gsd-core/', () => {
|
||||
// guarantee than the retired helper ever gave, and one a plain in-process
|
||||
// round-trip would not prove.
|
||||
|
||||
describe('#1924: user artifacts survive a wipe via the durable staging path (install.js exports)', () => {
|
||||
test('install.js exports the staging primitives, and content staged through them survives a destDir wipe + crash', (t) => {
|
||||
// Set GSD_TEST_MODE so require() reaches the module.exports block
|
||||
const origMode = process.env.GSD_TEST_MODE;
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
let mod;
|
||||
try {
|
||||
mod = require(INSTALL_SCRIPT);
|
||||
} finally {
|
||||
if (origMode === undefined) {
|
||||
delete process.env.GSD_TEST_MODE;
|
||||
} else {
|
||||
process.env.GSD_TEST_MODE = origMode;
|
||||
}
|
||||
}
|
||||
describe('#1924: user artifacts survive a wipe via the durable staging path (user-artifact-staging.cjs exports)', () => {
|
||||
test('user-artifact-staging.cjs exports the staging primitives, and content staged through them survives a destDir wipe + crash', (t) => {
|
||||
const mod = require('../gsd-core/bin/lib/user-artifact-staging.cjs');
|
||||
|
||||
for (const name of ['stageUserArtifacts', 'restoreStagedUserArtifacts', 'discardStagedUserArtifacts', 'recoverOrphanedUserArtifacts']) {
|
||||
assert.strictEqual(
|
||||
typeof mod[name],
|
||||
'function',
|
||||
`install.js must export ${name} — the mechanism that now makes the #1924 durability property testable`,
|
||||
`user-artifact-staging.cjs must export ${name} — the mechanism that now makes the #1924 durability property testable`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1519,8 +1513,6 @@ describe('#3333 regression: copyWithPathReplacement tolerates a source file vani
|
||||
// user-authored hooks are untouched.
|
||||
|
||||
describe('#3329 regression: stale managed .sh hook commands are reconciled on install/update', () => {
|
||||
const { reconcileManagedShellHookCommands } = installExports || {};
|
||||
|
||||
const GLOBAL_EXPECTED = {
|
||||
'gsd-validate-commit.sh': '"C:/Users/u/.claude/hooks/gsd-validate-commit.sh"',
|
||||
'gsd-graphify-update.sh': '"C:/Users/u/.claude/hooks/gsd-graphify-update.sh"',
|
||||
@@ -1555,9 +1547,9 @@ describe('#3329 regression: stale managed .sh hook commands are reconciled on in
|
||||
return out;
|
||||
}
|
||||
|
||||
test('reconcileManagedShellHookCommands is exported from bin/install.js', () => {
|
||||
test('reconcileManagedShellHookCommands is exported from runtime-hooks-surface.cjs', () => {
|
||||
assert.strictEqual(typeof reconcileManagedShellHookCommands, 'function',
|
||||
'reconcileManagedShellHookCommands must be exported from bin/install.js (#3329)');
|
||||
'reconcileManagedShellHookCommands must be exported from runtime-hooks-surface.cjs (#3329)');
|
||||
});
|
||||
|
||||
test('win32+claude: bare `bash`-prefixed global entries are rewritten to the bare script path', () => {
|
||||
|
||||
@@ -25,6 +25,8 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
const os = require('node:os');
|
||||
const espree = require('espree');
|
||||
const { splitLines, joinLines } = require('../gsd-core/bin/lib/text-lines.cjs');
|
||||
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const { runNode } = require('./helpers/process-seam.cjs');
|
||||
@@ -40,6 +42,7 @@ const {
|
||||
const {
|
||||
installRuntimeArtifacts,
|
||||
installOpencodeFamilySkills,
|
||||
uninstallRuntimeArtifacts,
|
||||
_resolveUserArtifactStagingRoot,
|
||||
} = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
@@ -110,7 +113,7 @@ describe('installRuntimeArtifacts — consumes Runtime Artifact Install Plan Mod
|
||||
fs.writeFileSync(path.join(sourceDir, 'proof.md'), '# proof\n');
|
||||
fs.writeFileSync(path.join(cleanupDir, 'temp.md'), '# cleanup\n');
|
||||
let planArgs;
|
||||
const { installer, restore } = loadFreshInstallerWithInstallPlanStub((args) => {
|
||||
const { restore } = loadFreshInstallerWithInstallPlanStub((args) => {
|
||||
planArgs = args;
|
||||
return {
|
||||
ok: true,
|
||||
@@ -125,7 +128,12 @@ describe('installRuntimeArtifacts — consumes Runtime Artifact Install Plan Mod
|
||||
t.after(restore);
|
||||
|
||||
// Augment has a flat commands kind and prefixes proof.md as gsd-proof.md.
|
||||
installer.installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_CORE);
|
||||
// `installer` above only reloads bin/install.js (needed for OTHER stubs in
|
||||
// this file); installRuntimeArtifacts itself always lived in install-engine.cjs
|
||||
// (bin/install.js's #2876-retired re-export was an alias to the same
|
||||
// singleton), so calling the direct import exercises the identical,
|
||||
// already-monkeypatched module instance.
|
||||
installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_CORE);
|
||||
|
||||
assert.strictEqual(planArgs.layout.runtime, 'augment');
|
||||
assert.strictEqual(planArgs.layout.configDir, configDir);
|
||||
@@ -145,7 +153,7 @@ describe('installRuntimeArtifacts — consumes Runtime Artifact Install Plan Mod
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(cleanupDir, 'temp.md'), '# cleanup\n');
|
||||
const { installer, restore } = loadFreshInstallerWithInstallPlanStub(() => ({
|
||||
const { restore } = loadFreshInstallerWithInstallPlanStub(() => ({
|
||||
ok: false,
|
||||
kind: 'rewrite_failed',
|
||||
failedKind: 'commands',
|
||||
@@ -155,7 +163,7 @@ describe('installRuntimeArtifacts — consumes Runtime Artifact Install Plan Mod
|
||||
t.after(restore);
|
||||
|
||||
assert.throws(
|
||||
() => installer.installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE),
|
||||
() => installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE),
|
||||
/planned failure/,
|
||||
);
|
||||
assert.ok(!fs.existsSync(cleanupDir), 'failure cleanup dir must be removed');
|
||||
@@ -745,7 +753,7 @@ describe('uninstallRuntimeArtifacts — consumes Runtime Artifact Uninstall Plan
|
||||
fs.writeFileSync(path.join(commandsDir, 'user-custom.md'), '# keep\n');
|
||||
|
||||
let planLayout;
|
||||
const { installer, restore } = loadFreshInstallerWithPlanStubs({
|
||||
const { restore } = loadFreshInstallerWithPlanStubs({
|
||||
uninstallStub(layout) {
|
||||
planLayout = layout;
|
||||
return {
|
||||
@@ -757,7 +765,10 @@ describe('uninstallRuntimeArtifacts — consumes Runtime Artifact Uninstall Plan
|
||||
});
|
||||
t.after(restore);
|
||||
|
||||
installer.uninstallRuntimeArtifacts('augment', configDir, 'global');
|
||||
// uninstallRuntimeArtifacts always lived in install-engine.cjs (bin/install.js's
|
||||
// #2876-retired re-export was an alias to the same singleton) — the direct
|
||||
// top-level import exercises the identical, already-monkeypatched instance.
|
||||
uninstallRuntimeArtifacts('augment', configDir, 'global');
|
||||
|
||||
assert.strictEqual(planLayout.runtime, 'augment');
|
||||
assert.strictEqual(planLayout.configDir, configDir);
|
||||
@@ -774,7 +785,7 @@ describe('uninstallRuntimeArtifacts — removes gsd-owned entries, preserves for
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
const { uninstallRuntimeArtifacts } = require('../bin/install.js');
|
||||
const { uninstallRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
assert.strictEqual(typeof uninstallRuntimeArtifacts, 'function');
|
||||
|
||||
const layout = resolveRuntimeArtifactLayout(runtime, configDir, 'global');
|
||||
@@ -928,7 +939,7 @@ describe('installRuntimeArtifacts — legacy migrations run before layout copy',
|
||||
|
||||
describe('uninstallRuntimeArtifacts — legacy cleanup runs before layout removal', () => {
|
||||
test('hermes: both flat and nested layouts removed (#947: bare-stem dirs cleaned on uninstall)', (t) => {
|
||||
const { uninstallRuntimeArtifacts } = require('../bin/install.js');
|
||||
const { uninstallRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
const configDir = createTempDir('gsd-legacy-uninstall-hermes-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
@@ -957,7 +968,7 @@ describe('uninstallRuntimeArtifacts — legacy cleanup runs before layout remova
|
||||
});
|
||||
|
||||
test('claude: legacy commands/gsd/ cleaned AND new skills/ entries removed', (t) => {
|
||||
const { uninstallRuntimeArtifacts } = require('../bin/install.js');
|
||||
const { uninstallRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
const configDir = createTempDir('gsd-legacy-uninstall-claude-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
@@ -1283,11 +1294,14 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const {
|
||||
convertClaudeToWindsurfMarkdown,
|
||||
convertClaudeToTraeMarkdown,
|
||||
_applyRuntimeRewrites,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const {
|
||||
convertClaudeToWindsurfMarkdown,
|
||||
_applyRuntimeRewrites,
|
||||
} = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
// ─── Windsurf converter bare-form tests ─────────────────────────────────────
|
||||
|
||||
describe('convertClaudeToWindsurfMarkdown — bare ~/.claude and CLAUDE_CONFIG_DIR (#983)', () => {
|
||||
@@ -1634,10 +1648,10 @@ const {
|
||||
convertClaudeCommandToClineSkill,
|
||||
convertClaudeToCliineMarkdown,
|
||||
install,
|
||||
_applyRuntimeRewrites,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { installRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
const { _applyRuntimeRewrites } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
const {
|
||||
resolveRuntimeArtifactLayout,
|
||||
@@ -2694,9 +2708,9 @@ const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const {
|
||||
readGsdRuntimeProfileResolver,
|
||||
install,
|
||||
} = require('../bin/install.js');
|
||||
const { readGsdRuntimeProfileResolver } = require('../gsd-core/bin/lib/install-model-override-resolver.cjs');
|
||||
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const makeTmp = (prefix) => createTempDir(`gsd-2794-${prefix}-`);
|
||||
@@ -4232,8 +4246,10 @@ describe('#443 Source purity: agents/gsd-planner.md has no effort: key', () => {
|
||||
// convertClaudeToAugmentMarkdown duplicate dedup is deferred to Phase 2's cleanup
|
||||
// (entangled converter cluster; not required to unblock Phase 2).
|
||||
// These tests exercise the REAL relocated functions at their new home (the
|
||||
// generated .cjs) and assert install.js re-exports the SAME references
|
||||
// (Hyrum: existing consumers import these names from bin/install.js).
|
||||
// generated .cjs). #2876 (epic #2866 Phase 7) retired install.js's re-export
|
||||
// of both names — a repo-wide audit found zero production consumers of the
|
||||
// pass-through, so the "existing consumers" beneficiary ADR-1508 cited was
|
||||
// the test suite alone. The re-export absence is asserted below instead.
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
@@ -4276,8 +4292,8 @@ describe('getDirName (relocated to runtime-name-policy)', () => {
|
||||
assert.strictEqual(runtimeNamePolicy.getDirName(''), '.claude');
|
||||
});
|
||||
|
||||
test('bin/install.js re-exports the SAME getDirName reference (no drift)', () => {
|
||||
assert.strictEqual(installer.getDirName, runtimeNamePolicy.getDirName);
|
||||
test('bin/install.js no longer re-exports getDirName (#2876 retired the pass-through)', () => {
|
||||
assert.strictEqual(installer.getDirName, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4323,10 +4339,8 @@ describe('processAttribution (relocated to runtime-artifact-conversion)', () =>
|
||||
);
|
||||
});
|
||||
|
||||
test('bin/install.js re-exports the SAME processAttribution reference (no drift)', () => {
|
||||
// processAttribution remains an explicit installer compatibility relay, so
|
||||
// the export must keep pointing at the conversion module's implementation.
|
||||
assert.strictEqual(installer.processAttribution, conversion.processAttribution);
|
||||
test('bin/install.js no longer re-exports processAttribution (#2876 retired the pass-through)', () => {
|
||||
assert.strictEqual(installer.processAttribution, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4681,124 +4695,151 @@ describe('layout module no longer exports getInstallExports', () => {
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DEFECT.GENERATIVE-FIX: single-owner reference-identity guard (#1511)
|
||||
// Proves install.js binds to the conversion module's implementation, not a
|
||||
// duplicate local copy. If these fail, a duplicate body was re-introduced.
|
||||
// DEFECT.GENERATIVE-FIX: single-owner duplicate-body guard (#1511),
|
||||
// re-armed AST-based after #2876 (epic #2866 Phase 7) retired the exports
|
||||
// the original reference-identity check compared against.
|
||||
//
|
||||
// #1511 proved install.js bound to the conversion module's implementation
|
||||
// rather than a duplicate local copy — the single-owner property held via
|
||||
// `assert.strictEqual(install.X, conversion.X)` (reference identity). #2876
|
||||
// retired these names from bin/install.js's module.exports entirely because
|
||||
// nothing consumed the re-export. That broke the reference-identity form
|
||||
// (there is no `install.X` left to compare), and a same-shaped
|
||||
// `install.X === undefined` replacement is NOT an equivalent guard: it only
|
||||
// inspects the EXPORT surface, so a duplicate, unexported, re-implemented
|
||||
// copy of one of these functions/consts added directly to bin/install.js
|
||||
// would satisfy `install.X === undefined` trivially while still being
|
||||
// exactly the drift-hazard duplicate this guard exists to catch.
|
||||
//
|
||||
// Fix: parse bin/install.js's own top-level AST (espree) and assert directly
|
||||
// on what is actually declared there, instead of on what is exported.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('single-owner reference-identity guard (ADR-1508 / #1511 Phase 2)', () => {
|
||||
describe('single-owner duplicate-body guard (ADR-1508 / #1511 Phase 2, AST-based since #2876)', () => {
|
||||
let install;
|
||||
let conversionCjs;
|
||||
let installTopLevel;
|
||||
|
||||
// Collect every top-level `function <name>(...) {}` declaration and every
|
||||
// top-level `const/let/var <name> = <init>;` declarator in bin/install.js,
|
||||
// keyed by name. A duplicate body re-introduced under EITHER shape (a real
|
||||
// function, or a const bound to something other than a bare
|
||||
// runtimeArtifactConversion.<Y> reference) is visible here regardless of
|
||||
// whether it is ever exported.
|
||||
function collectTopLevelBindings(ast) {
|
||||
const functionNames = new Set();
|
||||
const variableInits = new Map();
|
||||
for (const node of ast.body) {
|
||||
if (node.type === 'FunctionDeclaration' && node.id) {
|
||||
functionNames.add(node.id.name);
|
||||
}
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
for (const decl of node.declarations) {
|
||||
if (decl.type === 'VariableDeclarator' && decl.id && decl.id.type === 'Identifier') {
|
||||
variableInits.set(decl.id.name, decl.init);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { functionNames, variableInits };
|
||||
}
|
||||
|
||||
// True iff `node` is exactly `<objectName>.<propertyName>` — a bare
|
||||
// single-hop member-expression reference, not a call, not a duplicated
|
||||
// function/object body.
|
||||
function isMemberReferenceTo(node, objectName, propertyName) {
|
||||
return (
|
||||
!!node &&
|
||||
node.type === 'MemberExpression' &&
|
||||
!node.computed &&
|
||||
node.object &&
|
||||
node.object.type === 'Identifier' &&
|
||||
node.object.name === objectName &&
|
||||
node.property &&
|
||||
node.property.type === 'Identifier' &&
|
||||
node.property.name === propertyName
|
||||
);
|
||||
}
|
||||
|
||||
before(() => {
|
||||
process.env['GSD_TEST_MODE'] = '1';
|
||||
install = require('../bin/install.js');
|
||||
conversionCjs = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
// bin/install.js opens with a `#!/usr/bin/env node` shebang line, which
|
||||
// is not valid top-level JS syntax for espree's parser — strip it before
|
||||
// parsing (mirrors how Node's own module loader strips it at runtime).
|
||||
// Uses splitLines() (src/text-lines.cts, the repo's sole `\r?\n` split
|
||||
// seam) rather than a readFileSync-content regex, per
|
||||
// local/no-unbounded-quantifier and local/no-crlf-fragile-split.
|
||||
const installSrcRaw = fs.readFileSync(INSTALL_SCRIPT, 'utf8');
|
||||
const installSrcLines = splitLines(installSrcRaw);
|
||||
if (installSrcLines[0] && installSrcLines[0].startsWith('#!')) {
|
||||
installSrcLines[0] = '';
|
||||
}
|
||||
const installSrc = joinLines(installSrcLines, '\n');
|
||||
const ast = espree.parse(installSrc, { ecmaVersion: 2022, sourceType: 'script', range: true, loc: true });
|
||||
installTopLevel = collectTopLevelBindings(ast);
|
||||
});
|
||||
|
||||
test('install.computePathPrefix === conversion._computePathPrefix (single implementation)', () => {
|
||||
assert.strictEqual(
|
||||
install.computePathPrefix,
|
||||
conversionCjs._computePathPrefix,
|
||||
'install.js must bind computePathPrefix from conversion (not a duplicate body)',
|
||||
);
|
||||
});
|
||||
// Names #2876 retired from bin/install.js entirely (no export, no internal
|
||||
// caller). Regression shape this guards against: someone re-adds one of
|
||||
// these as either a real `function NAME(...) {...}` OR a
|
||||
// `const NAME = <anything>;` — under a plain `install.X === undefined`
|
||||
// check, an unexported re-add of either shape passes silently.
|
||||
const RETIRED_NAMES = [
|
||||
['applyRuntimeContentRewritesInPlace', 'applyRuntimeContentRewritesInPlace'],
|
||||
['applyRuntimeContentRewritesForCommandsInPlace', 'applyRuntimeContentRewritesForCommandsInPlace'],
|
||||
['convertClaudeToAugmentMarkdown', 'convertClaudeToAugmentMarkdown'],
|
||||
['convertClaudeCommandToAugmentSkill', 'convertClaudeCommandToAugmentSkill'],
|
||||
['convertClaudeAgentToAugmentAgent', 'convertClaudeAgentToAugmentAgent'],
|
||||
['convertClaudeCommandToWindsurfSkill', 'convertClaudeCommandToWindsurfSkill'],
|
||||
['convertClaudeCommandToWindsurfWorkflow', 'convertClaudeCommandToWindsurfWorkflow'],
|
||||
['convertClaudeAgentToWindsurfAgent', 'convertClaudeAgentToWindsurfAgent'],
|
||||
];
|
||||
|
||||
test('install.applyRuntimeContentRewritesInPlace === conversion.applyRuntimeContentRewritesInPlace (single walk loop)', () => {
|
||||
assert.strictEqual(
|
||||
install.applyRuntimeContentRewritesInPlace,
|
||||
conversionCjs.applyRuntimeContentRewritesInPlace,
|
||||
'install.js must bind applyRuntimeContentRewritesInPlace from conversion (not a duplicate walk loop)',
|
||||
);
|
||||
});
|
||||
for (const [name, conversionProp] of RETIRED_NAMES) {
|
||||
test(`${name}: retired from bin/install.js with zero top-level presence (#2876); conversion.${conversionProp} remains the single implementation`, () => {
|
||||
assert.strictEqual(install[name], undefined, `bin/install.js must no longer export ${name}`);
|
||||
assert.strictEqual(
|
||||
installTopLevel.functionNames.has(name),
|
||||
false,
|
||||
`bin/install.js must not declare a top-level function named ${name} — a re-added unexported copy is the #1511/#2876 duplicate-body regression this guard exists to catch`
|
||||
);
|
||||
assert.strictEqual(
|
||||
installTopLevel.variableInits.has(name),
|
||||
false,
|
||||
`bin/install.js must not declare a top-level const/let/var named ${name} — a re-added unexported copy is the #1511/#2876 duplicate-body regression this guard exists to catch`
|
||||
);
|
||||
assert.strictEqual(typeof conversionCjs[conversionProp], 'function', `${conversionProp} remains available from the conversion module`);
|
||||
});
|
||||
}
|
||||
|
||||
test('install.applyRuntimeContentRewritesForCommandsInPlace === conversion.applyRuntimeContentRewritesForCommandsInPlace (single copy+rewrite loop)', () => {
|
||||
assert.strictEqual(
|
||||
install.applyRuntimeContentRewritesForCommandsInPlace,
|
||||
conversionCjs.applyRuntimeContentRewritesForCommandsInPlace,
|
||||
'install.js must bind applyRuntimeContentRewritesForCommandsInPlace from conversion (not a duplicate copy+rewrite loop)',
|
||||
);
|
||||
});
|
||||
// Names #2876 kept as a bare single-hop reference (`const X =
|
||||
// runtimeArtifactConversion.<Y>;`) because bin/install.js still calls them
|
||||
// internally (computePathPrefix, _applyRuntimeRewrites,
|
||||
// convertClaudeToWindsurfMarkdown, applyClaudeCodeBrandSwap — #2931). The
|
||||
// regression this half guards against: the reference gets replaced with an
|
||||
// actual re-implemented body instead of staying a pointer to the single
|
||||
// conversion-module owner.
|
||||
const REFERENCE_ONLY_NAMES = [
|
||||
['computePathPrefix', '_computePathPrefix'],
|
||||
['_applyRuntimeRewrites', '_applyRuntimeRewrites'],
|
||||
['convertClaudeToWindsurfMarkdown', 'convertClaudeToWindsurfMarkdown'],
|
||||
['applyClaudeCodeBrandSwap', 'applyClaudeCodeBrandSwap'],
|
||||
];
|
||||
|
||||
test('install._applyRuntimeRewrites === conversion._applyRuntimeRewrites (single switch engine)', () => {
|
||||
assert.strictEqual(
|
||||
install._applyRuntimeRewrites,
|
||||
conversionCjs._applyRuntimeRewrites,
|
||||
'install.js must bind _applyRuntimeRewrites from conversion (not a local shim)',
|
||||
);
|
||||
});
|
||||
|
||||
// #1675 (ADR-1508): the augment converter family is single-sourced in the
|
||||
// conversion module. install.js must re-bind (not re-define) these so there
|
||||
// is exactly one body — the generative-drift hazard the dedup removes.
|
||||
test('install.convertClaudeToAugmentMarkdown === conversion.convertClaudeToAugmentMarkdown (single converter)', () => {
|
||||
assert.strictEqual(
|
||||
install.convertClaudeToAugmentMarkdown,
|
||||
conversionCjs.convertClaudeToAugmentMarkdown,
|
||||
'install.js must bind convertClaudeToAugmentMarkdown from conversion (not a duplicate body)',
|
||||
);
|
||||
});
|
||||
|
||||
test('install.convertClaudeCommandToAugmentSkill === conversion.convertClaudeCommandToAugmentSkill (single converter)', () => {
|
||||
assert.strictEqual(
|
||||
install.convertClaudeCommandToAugmentSkill,
|
||||
conversionCjs.convertClaudeCommandToAugmentSkill,
|
||||
'install.js must bind convertClaudeCommandToAugmentSkill from conversion (not a duplicate body)',
|
||||
);
|
||||
});
|
||||
|
||||
test('install.convertClaudeAgentToAugmentAgent === conversion.convertClaudeAgentToAugmentAgent (single converter)', () => {
|
||||
assert.strictEqual(
|
||||
install.convertClaudeAgentToAugmentAgent,
|
||||
conversionCjs.convertClaudeAgentToAugmentAgent,
|
||||
'install.js must bind convertClaudeAgentToAugmentAgent from conversion (not a duplicate body)',
|
||||
);
|
||||
});
|
||||
|
||||
// #2931 (ADR-1508): the windsurf converter family is single-sourced in the
|
||||
// conversion module, same pattern as the #1675 Augment dedup above.
|
||||
test('installJsBindsWindsurfConvertersByReference — convertClaudeCommandToWindsurfWorkflow (single converter)', () => {
|
||||
assert.strictEqual(
|
||||
install.convertClaudeCommandToWindsurfWorkflow,
|
||||
conversionCjs.convertClaudeCommandToWindsurfWorkflow,
|
||||
'install.js must bind convertClaudeCommandToWindsurfWorkflow from conversion (not a duplicate body)',
|
||||
);
|
||||
});
|
||||
|
||||
test('installJsBindsEntireWindsurfFamilyByReference — every windsurf converter member', () => {
|
||||
assert.strictEqual(
|
||||
install.convertClaudeToWindsurfMarkdown,
|
||||
conversionCjs.convertClaudeToWindsurfMarkdown,
|
||||
'install.js must bind convertClaudeToWindsurfMarkdown from conversion (not a duplicate body)',
|
||||
);
|
||||
assert.strictEqual(
|
||||
install.convertClaudeCommandToWindsurfSkill,
|
||||
conversionCjs.convertClaudeCommandToWindsurfSkill,
|
||||
'install.js must bind convertClaudeCommandToWindsurfSkill from conversion (not a duplicate body)',
|
||||
);
|
||||
assert.strictEqual(
|
||||
install.convertClaudeCommandToWindsurfWorkflow,
|
||||
conversionCjs.convertClaudeCommandToWindsurfWorkflow,
|
||||
'install.js must bind convertClaudeCommandToWindsurfWorkflow from conversion (not a duplicate body)',
|
||||
);
|
||||
assert.strictEqual(
|
||||
install.convertClaudeAgentToWindsurfAgent,
|
||||
conversionCjs.convertClaudeAgentToWindsurfAgent,
|
||||
'install.js must bind convertClaudeAgentToWindsurfAgent from conversion (not a duplicate body)',
|
||||
);
|
||||
});
|
||||
|
||||
// #2931 (ADR-1508): applyClaudeCodeBrandSwap + RUNTIME_COMPATIBILITY_BLOCK_RE
|
||||
// were duplicated verbatim in install.js (used by the local Cursor/Trae/
|
||||
// CodeBuddy/Cline converters) alongside the conversion module's copy —
|
||||
// exactly the unlinked-duplicate-implementation class this guard exists to
|
||||
// catch. install.js now re-binds (does not re-define) it.
|
||||
test('install.applyClaudeCodeBrandSwap === conversion.applyClaudeCodeBrandSwap (single implementation)', () => {
|
||||
assert.strictEqual(
|
||||
install.applyClaudeCodeBrandSwap,
|
||||
conversionCjs.applyClaudeCodeBrandSwap,
|
||||
'install.js must bind applyClaudeCodeBrandSwap from conversion (not a duplicate body)',
|
||||
);
|
||||
});
|
||||
for (const [local, conversionProp] of REFERENCE_ONLY_NAMES) {
|
||||
test(`${local}: still a bare runtimeArtifactConversion.${conversionProp} reference, not a re-implemented body`, () => {
|
||||
assert.strictEqual(install[local], undefined, `bin/install.js must no longer export ${local}`);
|
||||
const init = installTopLevel.variableInits.get(local);
|
||||
assert.ok(init, `bin/install.js must still declare a top-level const named ${local} (has an internal caller)`);
|
||||
assert.ok(
|
||||
isMemberReferenceTo(init, 'runtimeArtifactConversion', conversionProp),
|
||||
`bin/install.js's ${local} binding must be a bare \`runtimeArtifactConversion.${conversionProp}\` reference — anything else (a real function/object body) is the #1511/#2876 duplicate-body regression this guard exists to catch`
|
||||
);
|
||||
assert.strictEqual(typeof conversionCjs[conversionProp], 'function');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -4995,7 +5036,7 @@ describe('Bug #2973: installer migrates existing legacy dev-preferences.md to sk
|
||||
// documented signature. End-to-end install testing is covered by
|
||||
// tests/install-*.test.cjs which already exercise legacy preservation.
|
||||
assert.equal(typeof inst.migrateLegacyDevPreferencesToSkill, 'function',
|
||||
'expected migrateLegacyDevPreferencesToSkill in install.js exports (#2973)');
|
||||
'expected migrateLegacyDevPreferencesToSkill in install-engine.cjs exports (#2973)');
|
||||
});
|
||||
|
||||
test('migration writes to skills/gsd-dev-preferences/SKILL.md when no skill exists yet', () => {
|
||||
@@ -6804,7 +6845,8 @@ describe('#2873 C1-C6 — install-time shadow report (spawned installer wiring)'
|
||||
// - Kilo mirrors OpenCode (static-frontmatter twin, #2093).
|
||||
{
|
||||
const { describe: __d3543, test: __t3543, beforeEach: __be3543, afterEach: __ae3543 } = require('node:test');
|
||||
const { install: __install3543, readGsdRuntimeProfileResolver: __resolver3543 } = require('../bin/install.js');
|
||||
const { install: __install3543 } = require('../bin/install.js');
|
||||
const { readGsdRuntimeProfileResolver: __resolver3543 } = require('../gsd-core/bin/lib/install-model-override-resolver.cjs');
|
||||
const { captureConsole: __capture3543 } = require('./helpers.cjs');
|
||||
|
||||
// Installer-written shape for a non-Claude runtime (writeNonClaudeDefaults).
|
||||
|
||||
@@ -22,12 +22,14 @@ process.env['GSD_TEST_MODE'] = '1';
|
||||
const {
|
||||
copyWithPathReplacement,
|
||||
installCodexConfig,
|
||||
_copyStaged,
|
||||
_resolveUserArtifactStagingRoot: _installJsResolveUserArtifactStagingRoot,
|
||||
_tryResolveUserArtifactStagingRoot: _installJsTryResolveUserArtifactStagingRoot,
|
||||
install,
|
||||
uninstall,
|
||||
} = require('../bin/install.js');
|
||||
const {
|
||||
_copyStaged,
|
||||
} = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
// #2875 (epic #2866 Phase 6): user-artifact-staging confinement rows (E1-E5).
|
||||
// Top-level (not inside any of the folded `__foldDescribe` sections below,
|
||||
|
||||
@@ -37,7 +37,6 @@ const pkg = require('../package.json');
|
||||
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
|
||||
const {
|
||||
getDirName,
|
||||
getConfigDirFromHome,
|
||||
install,
|
||||
uninstall,
|
||||
@@ -48,12 +47,14 @@ const {
|
||||
resolveKiloConfigPath,
|
||||
configureKiloPermissions,
|
||||
selectRuntimesFromArgs,
|
||||
normalizeNodePath,
|
||||
GSD_CHANGESET_FILES,
|
||||
GSD_SCRIPTS_LIB_FILES,
|
||||
installRuntimeArtifacts,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const { getDirName } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
|
||||
const { normalizeNodePath } = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
const { installRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
const { getGlobalConfigDir } = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
// #2874 AC3 exemplar (see the qwen install/uninstall group below): resolves
|
||||
// the SAME 'full' profile install(false, <runtime>) resolves by default
|
||||
@@ -2031,11 +2032,11 @@ const path = require('path');
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
const {
|
||||
readGsdEffectiveModelOverrides,
|
||||
generateCodexAgentToml,
|
||||
convertClaudeToOpencodeFrontmatter,
|
||||
getCodexSkillAdapterHeader,
|
||||
} = require('../bin/install.js');
|
||||
const { readGsdEffectiveModelOverrides } = require('../gsd-core/bin/lib/install-model-override-resolver.cjs');
|
||||
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const makeTmp = (prefix) => createTempDir(`gsd-2256-${prefix}-`);
|
||||
@@ -2203,8 +2204,8 @@ const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const INSTALL = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
const { normalizeNodePath, resolveNodeRunner, rewriteLegacyManagedNodeHookCommands } = INSTALL;
|
||||
const HOOKS_SURFACE = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs'));
|
||||
const { normalizeNodePath, resolveNodeRunner, rewriteLegacyManagedNodeHookCommands } = HOOKS_SURFACE;
|
||||
|
||||
// ─── normalizeNodePath ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -2499,8 +2500,7 @@ const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const INSTALL = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
const { normalizeNodePath, resolveNodeRunner } = INSTALL;
|
||||
const { normalizeNodePath, resolveNodeRunner } = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs'));
|
||||
|
||||
// ─── Synthetic paths used across tests ───────────────────────────────────────
|
||||
|
||||
@@ -2709,8 +2709,7 @@ const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const INSTALL = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
const { buildHookCommand, resolveNodeRunner } = INSTALL;
|
||||
const { buildHookCommand, resolveNodeRunner, rewriteLegacyManagedNodeHookCommands } = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs'));
|
||||
|
||||
/**
|
||||
* Parse a hook command string into { runner, hookPath } structured
|
||||
@@ -2876,8 +2875,6 @@ describe('Bug #2979: buildHookCommand for .sh hooks still uses bare "bash" (POSI
|
||||
|
||||
// ─── #3002 CR follow-up: legacy-bare-node migration ─────────────────────────
|
||||
|
||||
const { rewriteLegacyManagedNodeHookCommands } = INSTALL;
|
||||
|
||||
describe('Bug #2979 (#3002 CR): rewriteLegacyManagedNodeHookCommands rewrites bare-node managed hooks on reinstall', () => {
|
||||
test('exported as a function', () => {
|
||||
assert.equal(typeof rewriteLegacyManagedNodeHookCommands, 'function');
|
||||
@@ -3146,7 +3143,7 @@ describe('Bug #2979 (#3002 CR): resolveNodeRunner returns null when execPath una
|
||||
|
||||
// ─── #3002 CR follow-up #2: null-command guards in settings.json ──────────
|
||||
|
||||
const { validateHookFields } = INSTALL;
|
||||
const { validateHookFields } = require('../bin/install.js');
|
||||
|
||||
describe('Bug #2979 (#3002 CR follow-up): no command:null hook entries survive serialization', () => {
|
||||
// CR feedback: assert structurally on the resulting settings object, not by
|
||||
@@ -3369,13 +3366,12 @@ before(() => {
|
||||
conversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
});
|
||||
|
||||
describe('bin/install.js compatibility export audit (#1559)', () => {
|
||||
test('retains audited compatibility relays for shared rewrite helpers', () => {
|
||||
assert.strictEqual(installer.processAttribution, conversion.processAttribution);
|
||||
assert.strictEqual(
|
||||
installer.applyRuntimeContentRewritesForCommandsInPlace,
|
||||
conversion.applyRuntimeContentRewritesForCommandsInPlace,
|
||||
);
|
||||
describe('bin/install.js compatibility export audit (#1559, retired by #2876)', () => {
|
||||
test('the two previously-audited compatibility relays are retired — test consumers now import runtime-artifact-conversion.cjs directly', () => {
|
||||
assert.equal(typeof conversion.processAttribution, 'function', 'processAttribution remains available from the conversion module');
|
||||
assert.equal(installer.processAttribution, undefined, 'processAttribution is no longer re-exported by bin/install.js (#2876)');
|
||||
assert.equal(typeof conversion.applyRuntimeContentRewritesForCommandsInPlace, 'function', 'applyRuntimeContentRewritesForCommandsInPlace remains available from the conversion module');
|
||||
assert.equal(installer.applyRuntimeContentRewritesForCommandsInPlace, undefined, 'applyRuntimeContentRewritesForCommandsInPlace is no longer re-exported by bin/install.js (#2876)');
|
||||
});
|
||||
|
||||
test('does not leak unaudited conversion-module helpers through the installer', () => {
|
||||
@@ -3759,25 +3755,29 @@ describe('#2771: legacy manifest entries for USER_OWNED_ARTIFACTS are normalized
|
||||
// ─── Test 4: shared constant exists and is used by both call sites ────────────
|
||||
|
||||
describe('#2771: USER_OWNED_ARTIFACTS is a single source of truth', () => {
|
||||
test('install.js exports USER_OWNED_ARTIFACTS containing USER-PROFILE.md', () => {
|
||||
test('install-engine.cjs exports USER_OWNED_ARTIFACTS containing USER-PROFILE.md; bin/install.js no longer re-exports it (#2876)', () => {
|
||||
const origMode = process.env.GSD_TEST_MODE;
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
let mod;
|
||||
let engine;
|
||||
try {
|
||||
delete require.cache[require.resolve(INSTALL_SCRIPT)];
|
||||
mod = require(INSTALL_SCRIPT);
|
||||
engine = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
} finally {
|
||||
if (origMode === undefined) delete process.env.GSD_TEST_MODE;
|
||||
else process.env.GSD_TEST_MODE = origMode;
|
||||
}
|
||||
|
||||
assert.strictEqual(mod.USER_OWNED_ARTIFACTS, undefined, 'bin/install.js must no longer export USER_OWNED_ARTIFACTS (#2876)');
|
||||
|
||||
assert.ok(
|
||||
Array.isArray(mod.USER_OWNED_ARTIFACTS) || mod.USER_OWNED_ARTIFACTS instanceof Set,
|
||||
'install.js must export USER_OWNED_ARTIFACTS as a single source of truth'
|
||||
Array.isArray(engine.USER_OWNED_ARTIFACTS) || engine.USER_OWNED_ARTIFACTS instanceof Set,
|
||||
'install-engine.cjs must export USER_OWNED_ARTIFACTS as a single source of truth'
|
||||
);
|
||||
const list = Array.isArray(mod.USER_OWNED_ARTIFACTS)
|
||||
? mod.USER_OWNED_ARTIFACTS
|
||||
: Array.from(mod.USER_OWNED_ARTIFACTS);
|
||||
const list = Array.isArray(engine.USER_OWNED_ARTIFACTS)
|
||||
? engine.USER_OWNED_ARTIFACTS
|
||||
: Array.from(engine.USER_OWNED_ARTIFACTS);
|
||||
assert.ok(
|
||||
list.includes('USER-PROFILE.md'),
|
||||
'USER_OWNED_ARTIFACTS must include USER-PROFILE.md'
|
||||
@@ -6001,52 +6001,63 @@ const manifestPath = path.join(
|
||||
);
|
||||
const manifest = require(manifestPath);
|
||||
|
||||
// Load install.js exported values (executes the module, not text inspection)
|
||||
// Load install-effort-resolver.cjs's catalog (executes the module, not text
|
||||
// inspection). #2876 (epic #2866 Phase 7) retired bin/install.js's
|
||||
// `_GSD_EFFORT_MANIFEST_TIER_DEFAULTS` / `_GSD_EFFORT_MANIFEST_DEFAULT`
|
||||
// getter re-exports — install-effort-resolver.cjs's `_getGsdEffortCatalog()`
|
||||
// was always the single source; bin/install.js's getters merely relayed it.
|
||||
const installPath = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
const {
|
||||
_GSD_EFFORT_MANIFEST_TIER_DEFAULTS,
|
||||
_GSD_EFFORT_MANIFEST_DEFAULT,
|
||||
} = require(installPath);
|
||||
const { _getGsdEffortCatalog } = require(
|
||||
path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'install-effort-resolver.cjs')
|
||||
);
|
||||
const _GSD_EFFORT_MANIFEST_TIER_DEFAULTS = _getGsdEffortCatalog().EFFORT_MANIFEST_TIER_DEFAULTS;
|
||||
const _GSD_EFFORT_MANIFEST_DEFAULT = _getGsdEffortCatalog().EFFORT_MANIFEST_DEFAULT;
|
||||
|
||||
test('install.js _GSD_EFFORT_MANIFEST_TIER_DEFAULTS.light matches manifest effort.routing_tier_defaults.light', () => {
|
||||
test('bin/install.js no longer re-exports _GSD_EFFORT_MANIFEST_TIER_DEFAULTS / _GSD_EFFORT_MANIFEST_DEFAULT (#2876)', () => {
|
||||
const installExports = require(installPath);
|
||||
assert.strictEqual(installExports._GSD_EFFORT_MANIFEST_TIER_DEFAULTS, undefined);
|
||||
assert.strictEqual(installExports._GSD_EFFORT_MANIFEST_DEFAULT, undefined);
|
||||
});
|
||||
|
||||
test('install-effort-resolver.cjs _GSD_EFFORT_MANIFEST_TIER_DEFAULTS.light matches manifest effort.routing_tier_defaults.light', () => {
|
||||
assert.strictEqual(
|
||||
_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.light,
|
||||
manifest.effort.routing_tier_defaults.light,
|
||||
`install.js tier default for "light" (${_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.light}) differs from manifest (${manifest.effort.routing_tier_defaults.light})`
|
||||
`install-effort-resolver.cjs tier default for "light" (${_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.light}) differs from manifest (${manifest.effort.routing_tier_defaults.light})`
|
||||
);
|
||||
});
|
||||
|
||||
test('install.js _GSD_EFFORT_MANIFEST_TIER_DEFAULTS.standard matches manifest effort.routing_tier_defaults.standard', () => {
|
||||
test('install-effort-resolver.cjs _GSD_EFFORT_MANIFEST_TIER_DEFAULTS.standard matches manifest effort.routing_tier_defaults.standard', () => {
|
||||
assert.strictEqual(
|
||||
_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.standard,
|
||||
manifest.effort.routing_tier_defaults.standard,
|
||||
`install.js tier default for "standard" (${_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.standard}) differs from manifest (${manifest.effort.routing_tier_defaults.standard})`
|
||||
`install-effort-resolver.cjs tier default for "standard" (${_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.standard}) differs from manifest (${manifest.effort.routing_tier_defaults.standard})`
|
||||
);
|
||||
});
|
||||
|
||||
test('install.js _GSD_EFFORT_MANIFEST_TIER_DEFAULTS.heavy matches manifest effort.routing_tier_defaults.heavy', () => {
|
||||
test('install-effort-resolver.cjs _GSD_EFFORT_MANIFEST_TIER_DEFAULTS.heavy matches manifest effort.routing_tier_defaults.heavy', () => {
|
||||
assert.strictEqual(
|
||||
_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.heavy,
|
||||
manifest.effort.routing_tier_defaults.heavy,
|
||||
`install.js tier default for "heavy" (${_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.heavy}) differs from manifest (${manifest.effort.routing_tier_defaults.heavy})`
|
||||
`install-effort-resolver.cjs tier default for "heavy" (${_GSD_EFFORT_MANIFEST_TIER_DEFAULTS.heavy}) differs from manifest (${manifest.effort.routing_tier_defaults.heavy})`
|
||||
);
|
||||
});
|
||||
|
||||
test('install.js _GSD_EFFORT_MANIFEST_DEFAULT matches manifest effort.default', () => {
|
||||
test('install-effort-resolver.cjs _GSD_EFFORT_MANIFEST_DEFAULT matches manifest effort.default', () => {
|
||||
assert.strictEqual(
|
||||
_GSD_EFFORT_MANIFEST_DEFAULT,
|
||||
manifest.effort.default,
|
||||
`install.js effort default (${_GSD_EFFORT_MANIFEST_DEFAULT}) differs from manifest (${manifest.effort.default})`
|
||||
`install-effort-resolver.cjs effort default (${_GSD_EFFORT_MANIFEST_DEFAULT}) differs from manifest (${manifest.effort.default})`
|
||||
);
|
||||
});
|
||||
|
||||
test('install.js tier-defaults object has exactly the same keys as manifest effort.routing_tier_defaults', () => {
|
||||
test('install-effort-resolver.cjs tier-defaults object has exactly the same keys as manifest effort.routing_tier_defaults', () => {
|
||||
const installKeys = Object.keys(_GSD_EFFORT_MANIFEST_TIER_DEFAULTS).sort();
|
||||
const manifestKeys = Object.keys(manifest.effort.routing_tier_defaults).sort();
|
||||
assert.deepStrictEqual(
|
||||
installKeys,
|
||||
manifestKeys,
|
||||
`Key mismatch — install.js: [${installKeys.join(', ')}], manifest: [${manifestKeys.join(', ')}]`
|
||||
`Key mismatch — install-effort-resolver.cjs: [${installKeys.join(', ')}], manifest: [${manifestKeys.join(', ')}]`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2087,7 +2087,8 @@ const { throwIfFailed } = require('./helpers/git-fixture.cjs');
|
||||
|
||||
const installModule = require('../bin/install.js');
|
||||
const { readInstallState } = require('../gsd-core/bin/lib/installer-migrations.cjs');
|
||||
const { install, parseTomlToObject, reconcileCodexHooksJsonEvent } = installModule;
|
||||
const { install, parseTomlToObject } = installModule;
|
||||
const { reconcileCodexHooksJsonEvent } = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const HOOKS_DIST = path.join(__dirname, '..', 'hooks', 'dist');
|
||||
const BUILD_HOOKS_SCRIPT = path.join(__dirname, '..', 'scripts', 'build-hooks.js');
|
||||
|
||||
@@ -2354,7 +2354,7 @@ const {
|
||||
|
||||
const {
|
||||
injectEffortFrontmatter,
|
||||
} = require('../bin/install.js');
|
||||
} = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
function writeConfig(dir, config) {
|
||||
const planningDir = path.join(dir, '.planning');
|
||||
@@ -5512,9 +5512,10 @@ describe('issue #2517: install end-to-end — per-project config reaches Codex T
|
||||
const prevTestMode = process.env.GSD_TEST_MODE;
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
const installMod = require('../bin/install.js');
|
||||
const { readGsdRuntimeProfileResolver } = require('../gsd-core/bin/lib/install-model-override-resolver.cjs');
|
||||
if (prevTestMode === undefined) delete process.env.GSD_TEST_MODE;
|
||||
else process.env.GSD_TEST_MODE = prevTestMode;
|
||||
const { readGsdRuntimeProfileResolver, generateCodexAgentToml } = installMod;
|
||||
const { generateCodexAgentToml } = installMod;
|
||||
|
||||
let tmpDir;
|
||||
beforeEach(() => { isolateHome(); tmpDir = createTempProject(); resetRuntimeWarningCaches(); });
|
||||
|
||||
@@ -235,8 +235,8 @@ let computePathPrefix;
|
||||
before(() => {
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
// Re-require fresh in case other tests already loaded it.
|
||||
delete require.cache[require.resolve('../bin/install.js')];
|
||||
({ computePathPrefix } = require('../bin/install.js'));
|
||||
delete require.cache[require.resolve('../gsd-core/bin/lib/runtime-artifact-conversion.cjs')];
|
||||
({ _computePathPrefix: computePathPrefix } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs'));
|
||||
});
|
||||
|
||||
after(() => {
|
||||
@@ -244,7 +244,7 @@ after(() => {
|
||||
});
|
||||
|
||||
describe('bug-2376: OpenCode on Windows must use absolute path, not $HOME', () => {
|
||||
test('computePathPrefix is exported by install.js', () => {
|
||||
test('computePathPrefix (_computePathPrefix) is exported by runtime-artifact-conversion.cjs', () => {
|
||||
assert.equal(typeof computePathPrefix, 'function');
|
||||
});
|
||||
|
||||
@@ -316,8 +316,8 @@ let computePathPrefix;
|
||||
|
||||
before(() => {
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
delete require.cache[require.resolve('../bin/install.js')];
|
||||
({ computePathPrefix } = require('../bin/install.js'));
|
||||
delete require.cache[require.resolve('../gsd-core/bin/lib/runtime-artifact-conversion.cjs')];
|
||||
({ _computePathPrefix: computePathPrefix } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs'));
|
||||
});
|
||||
|
||||
after(() => {
|
||||
@@ -325,7 +325,7 @@ after(() => {
|
||||
});
|
||||
|
||||
describe('bug-2831: OpenCode pathPrefix uses absolute path on all platforms', () => {
|
||||
test('computePathPrefix is exported by install.js', () => {
|
||||
test('computePathPrefix (_computePathPrefix) is exported by runtime-artifact-conversion.cjs', () => {
|
||||
assert.equal(typeof computePathPrefix, 'function');
|
||||
});
|
||||
|
||||
|
||||
@@ -392,7 +392,7 @@ describe('applySurface', () => {
|
||||
// both paths must produce identical sets.
|
||||
test('applySurface command-dir filenames match a fresh install for every command runtime (#816 parity)', async (t) => {
|
||||
process.env.GSD_TEST_MODE = '1';
|
||||
const { installRuntimeArtifacts } = require('../bin/install.js');
|
||||
const { installRuntimeArtifacts } = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
|
||||
// Build the resolved profile once. Both install and surface sides must use
|
||||
|
||||
@@ -17,14 +17,17 @@ const {
|
||||
convertClaudeToOpencodeFrontmatter,
|
||||
convertClaudeToKiloFrontmatter,
|
||||
convertClaudeAgentToAntigravityAgent,
|
||||
convertClaudeCommandToOpencodeSkill,
|
||||
convertClaudeCommandToKiloSkill,
|
||||
convertClaudeCommandToTraeSkill,
|
||||
convertClaudeCommandToKimiSkill,
|
||||
buildKimiAgentArtifacts,
|
||||
neutralizeAgentReferences,
|
||||
} = require('../bin/install.js');
|
||||
|
||||
const {
|
||||
convertClaudeCommandToOpencodeSkill,
|
||||
convertClaudeCommandToKiloSkill,
|
||||
} = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
// Sample Claude agent frontmatter (matches actual GSD agent format)
|
||||
const SAMPLE_AGENT = `---
|
||||
name: gsd-executor
|
||||
|
||||
@@ -27,10 +27,10 @@ const assert = require('node:assert/strict');
|
||||
const path = require('path');
|
||||
|
||||
// buildHookCommand was extracted to gsd-core/bin/lib/runtime-hooks-surface.cjs
|
||||
// (ADR-857 phase 5f-1) and re-exported via install.js. Import through install.js
|
||||
// so the test exercises the same public surface that the rest of the codebase uses.
|
||||
const INSTALL = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
const { buildHookCommand } = INSTALL;
|
||||
// (ADR-857 phase 5f-1). #2876 (epic #2866 Phase 7) retired install.js's
|
||||
// pass-through re-export (zero production consumers) — import the module
|
||||
// directly.
|
||||
const { buildHookCommand } = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs'));
|
||||
|
||||
const SH_HOOKS = [
|
||||
{ name: 'gsd-validate-commit.sh' },
|
||||
|
||||
@@ -524,7 +524,7 @@ const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
|
||||
const projection = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||
const install = require(path.join(__dirname, '..', 'bin', 'install.js'));
|
||||
const hooksSurface = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'runtime-hooks-surface.cjs'));
|
||||
|
||||
const {
|
||||
hookCommandNeedsPowerShellCallOperator,
|
||||
@@ -535,7 +535,7 @@ const {
|
||||
projectLegacySettingsHookCommand,
|
||||
projectPortableHookBaseDir,
|
||||
} = projection;
|
||||
const { buildHookCommand, rewriteLegacyManagedNodeHookCommands } = install;
|
||||
const { buildHookCommand, rewriteLegacyManagedNodeHookCommands } = hooksSurface;
|
||||
|
||||
describe('bug #3413: Shell Command Projection Module uses runtime-aware hook policy', () => {
|
||||
test('#1928: no current runtime needs the PowerShell call operator (seam inert after gemini removal)', () => {
|
||||
|
||||
@@ -478,7 +478,8 @@ describe('stale-bake-guard parity with bin/install.js bake paths', () => {
|
||||
path.join(tmp, '.planning', 'config.json'),
|
||||
JSON.stringify({ model_overrides: { 'gsd-executor': 'opencode-go/flash', 'gsd-planner': 'openai/gpt-5' } }),
|
||||
);
|
||||
const resolved = install.readGsdEffectiveModelOverrides(tmp, { homedir: () => sandboxHome });
|
||||
const { readGsdEffectiveModelOverrides } = require(path.join(REPO_ROOT, 'gsd-core', 'bin', 'lib', 'install-model-override-resolver.cjs'));
|
||||
const resolved = readGsdEffectiveModelOverrides(tmp, { homedir: () => sandboxHome });
|
||||
assert.deepEqual(resolved, { 'gsd-executor': 'opencode-go/flash', 'gsd-planner': 'openai/gpt-5' });
|
||||
} finally {
|
||||
cleanup(tmp);
|
||||
|
||||
@@ -18,7 +18,7 @@ const {
|
||||
convertClaudeCommandToWindsurfWorkflow,
|
||||
convertClaudeAgentToWindsurfAgent,
|
||||
convertClaudeToWindsurfMarkdown,
|
||||
} = require('../bin/install.js');
|
||||
} = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
|
||||
// Mirrors WINDSURF_WORKFLOW_DESCRIPTION_MAX in
|
||||
// src/runtime-artifact-conversion.cts (not exported — the module's own
|
||||
|
||||
@@ -184,7 +184,7 @@ describe('atomic write temp-tracking — shared Set parity guard', () => {
|
||||
assert.ok(tmpsSet instanceof Set, '__atomicWrittenTmps must be a Set');
|
||||
const sizeBefore = tmpsSet.size;
|
||||
|
||||
const { writeCursorHooksJson } = require('../bin/install.js');
|
||||
const { writeCursorHooksJson } = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
writeCursorHooksJson(targetDir, srcDir, {});
|
||||
|
||||
// After the write, the Set must have grown: the hooks.json temp path
|
||||
@@ -227,7 +227,8 @@ describe('cursor hook registration — behavioral guard (module-owned hooks)', (
|
||||
fs.writeFileSync(path.join(srcHooksDir, 'gsd-cursor-session-start.js'), '// stub\n');
|
||||
fs.writeFileSync(path.join(srcHooksDir, 'gsd-cursor-post-tool.js'), '// stub\n');
|
||||
|
||||
const { writeCursorHooksJson, GSD_CURSOR_HOOK_MARKER } = require('../bin/install.js');
|
||||
const { GSD_CURSOR_HOOK_MARKER } = require('../bin/install.js');
|
||||
const { writeCursorHooksJson } = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
||||
const result = writeCursorHooksJson(targetDir, srcDir, {});
|
||||
|
||||
assert.ok(result, 'writeCursorHooksJson must return a result');
|
||||
|
||||
Reference in New Issue
Block a user