fix(#1477): write .gsd-source marker at install; resolve install-exports for the deployed layout

The Claude Code global skills layout ships gsd-core/{bin,contexts,references,
templates,workflows} but not the commands/gsd source tree, and
_runLegacyUninstallCleanup removes any commands/gsd/ for that scope. So at
runtime findInstallSourceRoot's walk-up from gsd-core/bin/lib has nothing to
find and /gsd-surface throws for every subcommand (list/status included). The
marker reader added in #1476 never fired because nothing wrote the marker.

Writer half (Failure 1): install() writes <configDir>/.gsd-source pointing at
the package's commands/gsd source (guarded on its presence so a half-published
package never leaves a dangling marker). findInstallSourceRoot already prefers
this marker over the walk-up.

Deployed install-exports (Failure 2): loadInstallExports' relative
'../../../bin/install.js' only resolves in the repo; in a deployed tree it
points at <configDir>/bin/install.js, which is never shipped, so the surface
write subcommands threw MODULE_NOT_FOUND. Derive install.js from the resolved
commands/gsd source root instead (its package root holds both commands/gsd and
bin/install.js), which is correct in the repo (walk-up) and in deployed installs
(marker) alike. No file relocation needed, so install.js' own internal requires
keep resolving. applySurface threads layout.configDir so the marker is honored.

Regression test reproduces the deployed global layout end-to-end: without the
marker the deployed walk-up throws, and getInstallExports under the old relative
path throws MODULE_NOT_FOUND; with both fixes list/status resolve and the
install-exports load. Adversarial marker cases (dangling, empty/whitespace)
fall through to walk-up.

Claude-Session: https://claude.ai/code/session_01XNT3SWgzjmEycNuweURDme
This commit is contained in:
Joe Slitzker
2026-06-20 09:15:59 -05:00
parent 67a3842776
commit ee6f3b70c5
4 changed files with 260 additions and 5 deletions

View File

@@ -10026,6 +10026,24 @@ function install(isGlobal, runtime = 'claude', options = {}) {
failures.push('gsd-core');
}
// Write the .gsd-source marker so runtime source resolution succeeds at
// runtime (#1477). The Claude-global skills layout ships gsd-core/{bin,
// contexts,references,templates,workflows} but NOT the commands/gsd source
// tree, and _runLegacyUninstallCleanup actively removes any commands/gsd/
// for that scope — so findInstallSourceRoot's walk-up has nothing to find
// and /gsd-surface (list/status and the write subcommands) throws. This is
// the writer half of the marker that runtime-artifact-layout.cjs's finders
// already read (the reader landed in #1476). It points at the package's own
// commands/gsd source, whose parent also holds bin/install.js — the path
// loadInstallExports derives the installer exports from. Guarded on source
// presence so a half-published package never writes a dangling marker.
const gsdSourceCommands = path.join(src, 'commands', 'gsd');
if (fs.existsSync(gsdSourceCommands)) {
try {
fs.writeFileSync(path.join(targetDir, '.gsd-source'), gsdSourceCommands + '\n', 'utf8');
} catch (_) { /* non-fatal: surface degrades to walk-up resolution */ }
}
// Copy shared manifests into the gsd-core payload
// at the co-located path that CJS modules resolve first:
// gsd-core/bin/shared/*.json