enh(#2876): retire the dead and pass-through exports from bin/install.js (#3615)

* 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:
Tom Boucher
2026-08-18 09:23:44 -04:00
committed by GitHub
parent bcefffc132
commit 682eaae3f0
31 changed files with 501 additions and 627 deletions

View File

@@ -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).