From 67dd09e509a94c87b0bc79af220d3bccf25d295e Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 17 May 2026 00:43:12 -0400 Subject: [PATCH] fix(3663): restore .gsd-source marker resolution in findInstallSourceRoot Both findInstallSourceRoot and findAgentsSourceRoot now accept an optional runtimeConfigDir. When provided, they first check /.gsd-source: read the stored path, verify it exists, and return it. Fall through to the path.dirname walk-up only when the marker is absent or points to a missing path. The factory functions (commandsKind, agentsKind, skillsKind) now receive configDir from the switch arms and pass it through to the finders. listSurface in surface.cjs passes its runtimeConfigDir argument to findInstallSourceRoot so installed-from-source layouts use the marker rather than the walk-up. Co-Authored-By: Claude Sonnet 4.6 --- .../bin/lib/runtime-artifact-layout.cjs | 101 ++++++++++++------ get-shit-done/bin/lib/surface.cjs | 2 +- 2 files changed, 70 insertions(+), 33 deletions(-) diff --git a/get-shit-done/bin/lib/runtime-artifact-layout.cjs b/get-shit-done/bin/lib/runtime-artifact-layout.cjs index ce62d827e..9721d1690 100644 --- a/get-shit-done/bin/lib/runtime-artifact-layout.cjs +++ b/get-shit-done/bin/lib/runtime-artifact-layout.cjs @@ -50,14 +50,29 @@ const { // --------------------------------------------------------------------------- /** - * Walk up from __dirname to find commands/gsd. Walk-up-only for Phase 1; - * the .gsd-source marker check (which requires runtimeConfigDir) is deferred. + * Locate the GSD commands/gsd source directory. * - * @param {string} [overrideRoot] optional override for testability + * Resolution order: + * 1. If runtimeConfigDir provided, check /.gsd-source marker. + * 2. Walk up from __dirname using path.dirname (no literal .. segments). + * 3. Throw a descriptive error if neither succeeds. + * + * @param {string} [runtimeConfigDir] optional runtime config directory * @returns {string} */ -function findInstallSourceRoot(overrideRoot) { - if (overrideRoot) return overrideRoot; +function findInstallSourceRoot(runtimeConfigDir) { + // Step 1: marker check + if (runtimeConfigDir) { + const markerPath = path.join(runtimeConfigDir, '.gsd-source'); + if (fs.existsSync(markerPath)) { + try { + const src = fs.readFileSync(markerPath, 'utf8').trim(); + if (src && fs.existsSync(src)) return src; + } catch { /* fall through */ } + } + } + + // Step 2: walk up from __dirname let dir = __dirname; for (let i = 0; i < 6; i++) { const candidate = path.join(dir, 'commands', 'gsd'); @@ -66,17 +81,38 @@ function findInstallSourceRoot(overrideRoot) { if (parent === dir) break; dir = parent; } + throw new Error(`findInstallSourceRoot: could not locate commands/gsd from ${__dirname}`); } /** - * Walk up from __dirname to find agents/. Walk-up-only for Phase 1. + * Locate the GSD agents source directory. * - * @param {string} [overrideRoot] optional override for testability - * @returns {string|null} + * Resolution order: + * 1. If runtimeConfigDir provided, check /.gsd-source marker. + * 2. Walk up from __dirname using path.dirname (no literal .. segments). + * 3. Throw a descriptive error if neither succeeds. + * + * @param {string} [runtimeConfigDir] optional runtime config directory + * @returns {string} */ -function findAgentsSourceRoot(overrideRoot) { - if (overrideRoot) return overrideRoot; +function findAgentsSourceRoot(runtimeConfigDir) { + // Step 1: marker check + if (runtimeConfigDir) { + const markerPath = path.join(runtimeConfigDir, '.gsd-source'); + if (fs.existsSync(markerPath)) { + try { + const src = fs.readFileSync(markerPath, 'utf8').trim(); + if (src && fs.existsSync(src)) { + // Marker points to commands/gsd; agents/ is a sibling of commands/ + const agentsCandidate = path.resolve(path.dirname(src), '..', 'agents'); + if (fs.existsSync(agentsCandidate)) return agentsCandidate; + } + } catch { /* fall through */ } + } + } + + // Step 2: walk up from __dirname let dir = __dirname; for (let i = 0; i < 6; i++) { const candidate = path.join(dir, 'agents'); @@ -85,6 +121,7 @@ function findAgentsSourceRoot(overrideRoot) { if (parent === dir) break; dir = parent; } + throw new Error(`findAgentsSourceRoot: could not locate agents/ from ${__dirname}`); } @@ -102,30 +139,30 @@ const ALLOWED_RUNTIMES = new Set([ // Layout table builders // --------------------------------------------------------------------------- -function commandsKind(destSubpath, prefix, srcOverride) { +function commandsKind(destSubpath, prefix, configDir) { return { kind: 'commands', destSubpath, prefix, - stage: (resolved) => stageSkillsForProfile(findInstallSourceRoot(srcOverride), resolved), + stage: (resolved) => stageSkillsForProfile(findInstallSourceRoot(configDir), resolved), }; } -function agentsKind(destSubpath, prefix, srcOverride) { +function agentsKind(destSubpath, prefix, configDir) { return { kind: 'agents', destSubpath, prefix, - stage: (resolved) => stageAgentsForProfile(findAgentsSourceRoot(srcOverride), resolved), + stage: (resolved) => stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved), }; } -function skillsKind(destSubpath, prefix, converter, srcOverride) { +function skillsKind(destSubpath, prefix, converter, configDir) { return { kind: 'skills', destSubpath, prefix, - stage: (resolved) => stageSkillsForRuntimeAsSkills(findInstallSourceRoot(srcOverride), resolved, converter, prefix), + stage: (resolved) => stageSkillsForRuntimeAsSkills(findInstallSourceRoot(configDir), resolved, converter, prefix), }; } @@ -157,56 +194,56 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') { case 'claude': if (scope === 'local') { kinds = [ - commandsKind('commands/gsd', 'gsd-'), - agentsKind('agents', 'gsd-'), + commandsKind('commands/gsd', 'gsd-', configDir), + agentsKind('agents', 'gsd-', configDir), ]; } else { - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToClaudeSkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToClaudeSkill, configDir)]; } break; case 'cursor': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToCursorSkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToCursorSkill, configDir)]; break; case 'gemini': - kinds = [commandsKind('commands/gsd', 'gsd-')]; + kinds = [commandsKind('commands/gsd', 'gsd-', configDir)]; break; case 'codex': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToCodexSkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToCodexSkill, configDir)]; break; case 'copilot': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToCopilotSkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToCopilotSkill, configDir)]; break; case 'antigravity': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToAntigravitySkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToAntigravitySkill, configDir)]; break; case 'windsurf': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToWindsurfSkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToWindsurfSkill, configDir)]; break; case 'augment': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToAugmentSkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToAugmentSkill, configDir)]; break; case 'trae': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToTraeSkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToTraeSkill, configDir)]; break; case 'qwen': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToClaudeSkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToClaudeSkill, configDir)]; break; case 'hermes': - kinds = [skillsKind('skills/gsd', '', convertClaudeCommandToClaudeSkill)]; + kinds = [skillsKind('skills/gsd', '', convertClaudeCommandToClaudeSkill, configDir)]; break; case 'codebuddy': - kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToCodebuddySkill)]; + kinds = [skillsKind('skills', 'gsd-', convertClaudeCommandToCodebuddySkill, configDir)]; break; case 'cline': @@ -214,11 +251,11 @@ function resolveRuntimeArtifactLayout(runtime, configDir, scope = 'global') { break; case 'opencode': - kinds = [commandsKind('command', 'gsd-')]; + kinds = [commandsKind('command', 'gsd-', configDir)]; break; case 'kilo': - kinds = [commandsKind('command', 'gsd-')]; + kinds = [commandsKind('command', 'gsd-', configDir)]; break; default: diff --git a/get-shit-done/bin/lib/surface.cjs b/get-shit-done/bin/lib/surface.cjs index 87986b855..08e88d9f6 100644 --- a/get-shit-done/bin/lib/surface.cjs +++ b/get-shit-done/bin/lib/surface.cjs @@ -327,7 +327,7 @@ function listSurface(runtimeConfigDir, manifest, clusterMap) { const disabled = allStems.filter(s => !enabledSet.has(s)).sort(); // Compute token cost by reading descriptions from the install source - const srcCommandsDir = findInstallSourceRoot(); + const srcCommandsDir = findInstallSourceRoot(runtimeConfigDir); let tokenCost = 0; for (const stem of enabled) { const filePath = path.join(srcCommandsDir, `${stem}.md`);