From a9c30841ce95897b6f360b6794ab3b38bf357536 Mon Sep 17 00:00:00 2001 From: Joe Slitzker Date: Sat, 20 Jun 2026 09:38:18 -0500 Subject: [PATCH] test(#1477): move regression cases into the owning module test file The lint-regression-test-names gate rejects new bug-NNNN-*.test.cjs files: a new regression case must live in its owning module's test file. Relocate the #1477 cases into tests/runtime-artifact-layout.test.cjs (the home of the findInstallSourceRoot / getInstallExports resolution seam at the heart of the bug) as a dedicated describe block, and delete the standalone file. Claude-Session: https://claude.ai/code/session_01XNT3SWgzjmEycNuweURDme --- tests/bug-1477-surface-source-marker.test.cjs | 210 ------------------ tests/runtime-artifact-layout.test.cjs | 192 +++++++++++++++- 2 files changed, 189 insertions(+), 213 deletions(-) delete mode 100644 tests/bug-1477-surface-source-marker.test.cjs diff --git a/tests/bug-1477-surface-source-marker.test.cjs b/tests/bug-1477-surface-source-marker.test.cjs deleted file mode 100644 index e0d594f42..000000000 --- a/tests/bug-1477-surface-source-marker.test.cjs +++ /dev/null @@ -1,210 +0,0 @@ -/** - * Regression test for #1477: Claude Code global install ships no commands/gsd - * source and never writes the .gsd-source marker, so /gsd-surface is fully - * non-functional (list/status throw at findInstallSourceRoot, and the write - * subcommands throw MODULE_NOT_FOUND at loadInstallExports). - * - * Repro (deployed Claude global layout): - * ~/.claude/gsd-core/{bin,contexts,references,templates,workflows} — no commands/gsd - * ~/.claude/.gsd-source — never written - * ~/.claude/bin/install.js — never shipped - * - * findInstallSourceRoot walks up from gsd-core/bin/lib looking for - * commands/gsd, finds nothing, and throws — killing list/status. The marker - * step that PR #1476 added (read side) never fires because nothing writes the - * marker (this issue is the write side). loadInstallExports' relative - * '../../../bin/install.js' resolves to ~/.claude/bin/install.js, which does - * not exist — killing profile/disable/enable/reset. - * - * Fix contract (both halves required): - * 1. bin/install.js writes /.gsd-source pointing at a resolvable - * commands/gsd source whose package root also holds bin/install.js. - * 2. runtime-artifact-layout.cjs derives bin/install.js from that resolved - * source root, so install-exports load in the deployed layout too. - */ - -'use strict'; - -process.env.GSD_TEST_MODE = '1'; - -const { describe, test, beforeEach, afterEach } = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); - -const REPO_ROOT = path.join(__dirname, '..'); -const { install } = require('../bin/install.js'); -const { createTempDir, cleanup } = require('./helpers.cjs'); - -// The repo-resident module — exercised directly for the adversarial marker-reader -// cases (its walk-up always finds the repo commands/gsd, so marker precedence and -// fall-through can be asserted without a full install). -const { - findInstallSourceRoot, -} = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs'); - -function silenceConsole(fn) { - const orig = { log: console.log, warn: console.warn, error: console.error }; - console.log = () => {}; - console.warn = () => {}; - console.error = () => {}; - try { - return fn(); - } finally { - console.log = orig.log; - console.warn = orig.warn; - console.error = orig.error; - } -} - -describe('bug #1477: .gsd-source marker provisioning + deployed install-exports resolution', () => { - let tmpRoot; - let savedHome; - let savedUserProfile; - let savedExplicitConfigDir; - - beforeEach(() => { - tmpRoot = createTempDir('gsd-1477-'); - savedHome = process.env.HOME; - // os.homedir() reads USERPROFILE on win32, HOME elsewhere; redirect both so - // install() targets the fixture regardless of platform. - savedUserProfile = process.env.USERPROFILE; - process.env.HOME = tmpRoot; - process.env.USERPROFILE = tmpRoot; - savedExplicitConfigDir = process.env.GSD_EXPLICIT_CONFIG_DIR; - delete process.env.GSD_EXPLICIT_CONFIG_DIR; - }); - - afterEach(() => { - if (savedHome === undefined) delete process.env.HOME; - else process.env.HOME = savedHome; - if (savedUserProfile === undefined) delete process.env.USERPROFILE; - else process.env.USERPROFILE = savedUserProfile; - if (savedExplicitConfigDir === undefined) delete process.env.GSD_EXPLICIT_CONFIG_DIR; - else process.env.GSD_EXPLICIT_CONFIG_DIR = savedExplicitConfigDir; - cleanup(tmpRoot); - }); - - // Guard against process.exit killing the runner mid-install. - function runInstall(isGlobal, runtime) { - const origExit = process.exit; - let exitCalled = false; - process.exit = (code) => { - exitCalled = true; - throw new Error(`process.exit(${code}) during install — should not happen`); - }; - try { - return silenceConsole(() => install(isGlobal, runtime)); - } catch (e) { - if (exitCalled) assert.fail(`install() called process.exit — unexpected: ${e.message}`); - throw e; - } finally { - process.exit = origExit; - } - } - - // ── Failure 1: the installer provisions a valid marker ────────────────────── - test('global claude install writes a .gsd-source marker pointing at a real commands/gsd', () => { - const claudeDir = path.join(tmpRoot, '.claude'); - fs.mkdirSync(claudeDir, { recursive: true }); - - runInstall(true /* isGlobal */, 'claude'); - - const markerPath = path.join(claudeDir, '.gsd-source'); - assert.ok(fs.existsSync(markerPath), `.gsd-source marker must be written at ${markerPath}`); - - const markerSrc = fs.readFileSync(markerPath, 'utf8').trim(); - assert.ok(path.isAbsolute(markerSrc), `marker must contain an absolute path, got: ${markerSrc}`); - assert.ok(fs.existsSync(markerSrc), `marker target must exist on disk: ${markerSrc}`); - assert.equal( - path.basename(markerSrc), 'gsd', - 'marker must point at a commands/gsd directory', - ); - assert.equal(path.basename(path.dirname(markerSrc)), 'commands'); - - // The package root that holds commands/gsd must also hold bin/install.js — - // this is what loadInstallExports derives the installer exports from. - const derivedInstallJs = path.resolve(markerSrc, '..', '..', 'bin', 'install.js'); - assert.ok( - fs.existsSync(derivedInstallJs), - `bin/install.js must be reachable from the marker's package root: ${derivedInstallJs}`, - ); - }); - - // ── Failures 1+2 end-to-end: resolution succeeds FROM the deployed tree ────── - // The deployed module's __dirname is /gsd-core/bin/lib, which has no - // commands/gsd ancestor (global skills layout). Only the marker rescues it. - test('deployed global layout resolves source root + install-exports via the marker', () => { - const claudeDir = path.join(tmpRoot, '.claude'); - fs.mkdirSync(claudeDir, { recursive: true }); - runInstall(true /* isGlobal */, 'claude'); - - // Sanity: the global layout genuinely ships no commands/gsd source tree. - assert.ok( - !fs.existsSync(path.join(claudeDir, 'commands', 'gsd')), - 'precondition: global claude install must not ship commands/gsd', - ); - - const deployedLayoutPath = path.join(claudeDir, 'gsd-core', 'bin', 'lib', 'runtime-artifact-layout.cjs'); - assert.ok(fs.existsSync(deployedLayoutPath), 'deployed runtime-artifact-layout.cjs must exist'); - delete require.cache[deployedLayoutPath]; - const deployed = require(deployedLayoutPath); - - // Negative proof that the bug condition exists: WITHOUT consulting the marker - // (no configDir argument), walk-up from the deployed tree has nothing to find. - assert.throws( - () => deployed.findInstallSourceRoot(), - /could not locate commands\/gsd/, - 'deployed walk-up must fail without the marker — this is the regression condition', - ); - - // With the marker (configDir provided), list/status resolution succeeds. - let resolved; - assert.doesNotThrow(() => { - resolved = deployed.findInstallSourceRoot(claudeDir); - }, 'findInstallSourceRoot must resolve via the .gsd-source marker'); - assert.equal(path.basename(resolved), 'gsd'); - assert.ok(fs.existsSync(resolved)); - - // The write-subcommand path: install-exports must load in the deployed layout. - const exportsObj = deployed.getInstallExports(claudeDir); - assert.equal(typeof exportsObj.computePathPrefix, 'function', - 'getInstallExports must expose computePathPrefix (used by applySurface)'); - assert.equal(typeof exportsObj.applyRuntimeContentRewritesInPlace, 'function', - 'getInstallExports must expose applyRuntimeContentRewritesInPlace (used by applySurface)'); - }); - - // ── Adversarial marker-reader cases (no full install needed) ───────────────── - describe('findInstallSourceRoot marker handling', () => { - let cfgDir; - beforeEach(() => { cfgDir = createTempDir('gsd-1477-marker-'); }); - afterEach(() => { cleanup(cfgDir); }); - - test('marker pointing at a valid commands/gsd takes precedence over walk-up', () => { - const fakeSrc = path.join(cfgDir, 'pkg', 'commands', 'gsd'); - fs.mkdirSync(fakeSrc, { recursive: true }); - fs.writeFileSync(path.join(cfgDir, '.gsd-source'), fakeSrc + '\n', 'utf8'); - - const resolved = findInstallSourceRoot(cfgDir); - assert.equal(path.resolve(resolved), path.resolve(fakeSrc), - 'marker target must win over the repo walk-up'); - }); - - test('marker pointing at a non-existent path is ignored (falls through to walk-up)', () => { - const ghost = path.join(cfgDir, 'does', 'not', 'exist', 'commands', 'gsd'); - fs.writeFileSync(path.join(cfgDir, '.gsd-source'), ghost + '\n', 'utf8'); - - // In-repo walk-up still resolves the real commands/gsd — no throw, and it is - // NOT the dangling marker target. - const resolved = findInstallSourceRoot(cfgDir); - assert.notEqual(path.resolve(resolved), path.resolve(ghost)); - assert.equal(path.resolve(resolved), path.resolve(REPO_ROOT, 'commands', 'gsd')); - }); - - test('empty / whitespace-only marker is ignored', () => { - fs.writeFileSync(path.join(cfgDir, '.gsd-source'), ' \n', 'utf8'); - const resolved = findInstallSourceRoot(cfgDir); - assert.equal(path.resolve(resolved), path.resolve(REPO_ROOT, 'commands', 'gsd')); - }); - }); -}); diff --git a/tests/runtime-artifact-layout.test.cjs b/tests/runtime-artifact-layout.test.cjs index 0df4ca2da..c3ba616e8 100644 --- a/tests/runtime-artifact-layout.test.cjs +++ b/tests/runtime-artifact-layout.test.cjs @@ -17,14 +17,17 @@ * runtime-artifact-layout-install-profiles.test.cjs — install-profiles seam */ -const { test, describe } = require('node:test'); +const { test, describe, beforeEach, afterEach } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('fs'); const path = require('path'); -const { resolveRuntimeArtifactLayout } = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs'); +const { resolveRuntimeArtifactLayout, findInstallSourceRoot } = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs'); const installProfiles = require('../gsd-core/bin/lib/install-profiles.cjs'); -const { cleanup } = require('./helpers.cjs'); +const { install } = require('../bin/install.js'); +const { createTempDir, cleanup } = require('./helpers.cjs'); + +const REPO_ROOT = path.join(__dirname, '..'); const FAKE_DIR = '/tmp/fake-config-dir'; @@ -621,3 +624,186 @@ describe('stage — cursor commands kind (#785)', () => { } }); }); + +// ─── #1477: .gsd-source marker provisioning + deployed install-exports ───────── +// +// Regression for #1477: the Claude Code global skills layout ships +// gsd-core/{bin,contexts,references,templates,workflows} but no commands/gsd +// source tree, and _runLegacyUninstallCleanup removes any commands/gsd/ for that +// scope. At runtime findInstallSourceRoot's walk-up from gsd-core/bin/lib had +// nothing to find and /gsd-surface threw for every subcommand (list/status +// included); the marker reader added in #1476 never fired because nothing wrote +// the marker. loadInstallExports' relative '../../../bin/install.js' also only +// resolved in the repo — in a deployed tree it pointed at /bin/ +// install.js, which is never shipped, so the surface write subcommands threw +// MODULE_NOT_FOUND. +// +// Fix (both halves): bin/install.js writes /.gsd-source pointing at a +// resolvable commands/gsd whose package root also holds bin/install.js, and +// runtime-artifact-layout derives bin/install.js from that resolved source root. + +describe('#1477 .gsd-source marker provisioning + deployed install-exports resolution', () => { + let tmpRoot; + let savedHome; + let savedUserProfile; + let savedExplicitConfigDir; + let savedTestMode; + + function silenceConsole(fn) { + const orig = { log: console.log, warn: console.warn, error: console.error }; + console.log = () => {}; + console.warn = () => {}; + console.error = () => {}; + try { + return fn(); + } finally { + console.log = orig.log; + console.warn = orig.warn; + console.error = orig.error; + } + } + + // Guard against process.exit killing the runner mid-install. + function runInstall(isGlobal, runtime) { + const origExit = process.exit; + let exitCalled = false; + process.exit = (code) => { + exitCalled = true; + throw new Error(`process.exit(${code}) during install — should not happen`); + }; + try { + return silenceConsole(() => install(isGlobal, runtime)); + } catch (e) { + if (exitCalled) assert.fail(`install() called process.exit — unexpected: ${e.message}`); + throw e; + } finally { + process.exit = origExit; + } + } + + beforeEach(() => { + tmpRoot = createTempDir('gsd-1477-'); + savedHome = process.env.HOME; + // os.homedir() reads USERPROFILE on win32, HOME elsewhere; redirect both so + // install() targets the fixture regardless of platform. + savedUserProfile = process.env.USERPROFILE; + process.env.HOME = tmpRoot; + process.env.USERPROFILE = tmpRoot; + savedExplicitConfigDir = process.env.GSD_EXPLICIT_CONFIG_DIR; + delete process.env.GSD_EXPLICIT_CONFIG_DIR; + savedTestMode = process.env.GSD_TEST_MODE; + process.env.GSD_TEST_MODE = '1'; + }); + + afterEach(() => { + if (savedHome === undefined) delete process.env.HOME; + else process.env.HOME = savedHome; + if (savedUserProfile === undefined) delete process.env.USERPROFILE; + else process.env.USERPROFILE = savedUserProfile; + if (savedExplicitConfigDir === undefined) delete process.env.GSD_EXPLICIT_CONFIG_DIR; + else process.env.GSD_EXPLICIT_CONFIG_DIR = savedExplicitConfigDir; + if (savedTestMode === undefined) delete process.env.GSD_TEST_MODE; + else process.env.GSD_TEST_MODE = savedTestMode; + cleanup(tmpRoot); + }); + + // ── Failure 1: the installer provisions a valid marker ────────────────────── + test('global claude install writes a .gsd-source marker pointing at a real commands/gsd', () => { + const claudeDir = path.join(tmpRoot, '.claude'); + fs.mkdirSync(claudeDir, { recursive: true }); + + runInstall(true /* isGlobal */, 'claude'); + + const markerPath = path.join(claudeDir, '.gsd-source'); + assert.ok(fs.existsSync(markerPath), `.gsd-source marker must be written at ${markerPath}`); + + const markerSrc = fs.readFileSync(markerPath, 'utf8').trim(); + assert.ok(path.isAbsolute(markerSrc), `marker must contain an absolute path, got: ${markerSrc}`); + assert.ok(fs.existsSync(markerSrc), `marker target must exist on disk: ${markerSrc}`); + assert.equal(path.basename(markerSrc), 'gsd', 'marker must point at a commands/gsd directory'); + assert.equal(path.basename(path.dirname(markerSrc)), 'commands'); + + // The package root that holds commands/gsd must also hold bin/install.js — + // this is what loadInstallExports derives the installer exports from. + const derivedInstallJs = path.resolve(markerSrc, '..', '..', 'bin', 'install.js'); + assert.ok( + fs.existsSync(derivedInstallJs), + `bin/install.js must be reachable from the marker's package root: ${derivedInstallJs}`, + ); + }); + + // ── Failures 1+2 end-to-end: resolution succeeds FROM the deployed tree ────── + // The deployed module's __dirname is /gsd-core/bin/lib, which has no + // commands/gsd ancestor (global skills layout). Only the marker rescues it. + test('deployed global layout resolves source root + install-exports via the marker', () => { + const claudeDir = path.join(tmpRoot, '.claude'); + fs.mkdirSync(claudeDir, { recursive: true }); + runInstall(true /* isGlobal */, 'claude'); + + // Sanity: the global layout genuinely ships no commands/gsd source tree. + assert.ok( + !fs.existsSync(path.join(claudeDir, 'commands', 'gsd')), + 'precondition: global claude install must not ship commands/gsd', + ); + + const deployedLayoutPath = path.join(claudeDir, 'gsd-core', 'bin', 'lib', 'runtime-artifact-layout.cjs'); + assert.ok(fs.existsSync(deployedLayoutPath), 'deployed runtime-artifact-layout.cjs must exist'); + delete require.cache[deployedLayoutPath]; + const deployed = require(deployedLayoutPath); + + // Negative proof that the bug condition exists: WITHOUT consulting the marker + // (no configDir argument), walk-up from the deployed tree has nothing to find. + assert.throws( + () => deployed.findInstallSourceRoot(), + /could not locate commands\/gsd/, + 'deployed walk-up must fail without the marker — this is the regression condition', + ); + + // With the marker (configDir provided), list/status resolution succeeds. + let resolved; + assert.doesNotThrow(() => { + resolved = deployed.findInstallSourceRoot(claudeDir); + }, 'findInstallSourceRoot must resolve via the .gsd-source marker'); + assert.equal(path.basename(resolved), 'gsd'); + assert.ok(fs.existsSync(resolved)); + + // The write-subcommand path: install-exports must load in the deployed layout. + const exportsObj = deployed.getInstallExports(claudeDir); + assert.equal(typeof exportsObj.computePathPrefix, 'function', + 'getInstallExports must expose computePathPrefix (used by applySurface)'); + assert.equal(typeof exportsObj.applyRuntimeContentRewritesInPlace, 'function', + 'getInstallExports must expose applyRuntimeContentRewritesInPlace (used by applySurface)'); + }); + + // ── Adversarial marker-reader cases (no full install needed) ───────────────── + describe('findInstallSourceRoot marker handling', () => { + let cfgDir; + beforeEach(() => { cfgDir = createTempDir('gsd-1477-marker-'); }); + afterEach(() => { cleanup(cfgDir); }); + + test('marker pointing at a valid commands/gsd takes precedence over walk-up', () => { + const fakeSrc = path.join(cfgDir, 'pkg', 'commands', 'gsd'); + fs.mkdirSync(fakeSrc, { recursive: true }); + fs.writeFileSync(path.join(cfgDir, '.gsd-source'), fakeSrc + '\n', 'utf8'); + + const resolved = findInstallSourceRoot(cfgDir); + assert.equal(path.resolve(resolved), path.resolve(fakeSrc), + 'marker target must win over the repo walk-up'); + }); + + test('marker pointing at a non-existent path is ignored (falls through to walk-up)', () => { + const ghost = path.join(cfgDir, 'does', 'not', 'exist', 'commands', 'gsd'); + fs.writeFileSync(path.join(cfgDir, '.gsd-source'), ghost + '\n', 'utf8'); + + const resolved = findInstallSourceRoot(cfgDir); + assert.notEqual(path.resolve(resolved), path.resolve(ghost)); + assert.equal(path.resolve(resolved), path.resolve(REPO_ROOT, 'commands', 'gsd')); + }); + + test('empty / whitespace-only marker is ignored', () => { + fs.writeFileSync(path.join(cfgDir, '.gsd-source'), ' \n', 'utf8'); + const resolved = findInstallSourceRoot(cfgDir); + assert.equal(path.resolve(resolved), path.resolve(REPO_ROOT, 'commands', 'gsd')); + }); + }); +});