convertClaudeCommandToClaudeSkill(content, skillName, runtime, cmdNames) uses
the runtime arg to gate Hermes/Qwen branding and version: frontmatter emission
(#2808, #3583). Previously the layout module called it with only 2 args so the
runtime-specific formatting was never applied.
Changes:
- skillsKind() gains a runtime param (5th arg after converterName).
- stage() computes cmdNames = readGsdCommandNames() once per call (perf: avoids
repeated fs.readdirSync in the converter) and wraps the real converter so all
4 args are forwarded.
- readGsdCommandNames added to bin/install.js GSD_TEST_MODE exports block so the
stage closure can call it without requiring the script separately.
- All switch arms updated to pass the canonical runtime string.
Converters that do not inspect runtime/cmdNames (Cursor, Codex, Copilot, etc.)
accept and ignore the extra arguments — no behaviour change for those runtimes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously runtime-artifact-layout.cjs set process.env.GSD_TEST_MODE at the
top level before requiring bin/install.js. Any caller that required this module
would silently inherit the GSD_TEST_MODE='1' side-effect for the lifetime of
the process.
Replace with a lazy loader (loadInstallExports / getInstallExports) that saves
the current GSD_TEST_MODE, sets it to '1' only if it was undefined, calls
require(), and restores the original value in a finally block. The exported
module.exports cache (_installExports) ensures the require is run at most once.
Converter names are now passed as strings to skillsKind; the stage closure
resolves them via getInstallExports()[converterName] at call time, so the
installer is not loaded until a stage() function is actually invoked.
Verification: node -e "require('./...runtime-artifact-layout.cjs'); console.log(process.env.GSD_TEST_MODE)"
prints undefined.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both findInstallSourceRoot and findAgentsSourceRoot now accept an optional
runtimeConfigDir. When provided, they first check <runtimeConfigDir>/.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 <noreply@anthropic.com>
Both findInstallSourceRoot and findAgentsSourceRoot previously returned a
path.join(__dirname, '..', '..', '..', ...) fallback when the walk-up loop
found nothing. Replace with throw per CLAUDE.md "No Relative Path Traversal"
policy: .. chains silently break on CWD changes; a throw with the failing
__dirname in the message is an unambiguous diagnostic.
The walk-up loop already uses path.dirname iteratively (no literal ..); only
the dead-end fallback used the banned pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>