diff --git a/.changeset/lively-foxes-roam.md b/.changeset/lively-foxes-roam.md new file mode 100644 index 000000000..7d0d6cd26 --- /dev/null +++ b/.changeset/lively-foxes-roam.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3584 +--- +**Runtime emitters now produce routable slash-command form** — `init manager` recommended actions, `phase add/insert` ROADMAP entries, `validate health` fix hints, `milestone complete` Operator Next Steps, `validate context` warnings, and `generate-claude-md` workflow blocks now emit `/gsd-` for skills-based runtimes (Claude/Cursor/OpenCode/Kilo/etc.) and `$gsd-` for Codex. The deprecated `/gsd:` colon form is no longer emitted — pasting a recommended-action command into Claude Code now routes successfully instead of failing with "Unknown command". diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index 317360c7a..6a47aa7f8 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -303,6 +303,7 @@ "roadmap-command-router.cjs", "roadmap.cjs", "runtime-homes.cjs", + "runtime-slash.cjs", "schema-detect.cjs", "secrets.cjs", "security.cjs", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index ee234fd0a..181c2beeb 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -361,7 +361,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t --- -## CLI Modules (63 shipped) +## CLI Modules (64 shipped) Full listing: `get-shit-done/bin/lib/*.cjs`. @@ -411,6 +411,7 @@ Full listing: `get-shit-done/bin/lib/*.cjs`. | `roadmap-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools roadmap` | | `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress | | `runtime-homes.cjs` | Canonical runtime → global config/skills directory mapping; first-class support for all 15 runtimes including Hermes nested layout and Cline rules-based exclusion (#3126) | +| `runtime-slash.cjs` | Runtime-aware slash-command formatter — single source of truth for emitting `/gsd-` (skills-based runtimes) and `$gsd-` (codex) in user-facing output and persisted artifacts (#3584) | | `schema-detect.cjs` | Schema-drift detection for ORM patterns (Prisma, Drizzle, etc.) | | `secrets.cjs` | Secret-config masking convention (`****`) for integration keys managed by `/gsd-config --integrations` — keeps plaintext out of `config-set` output | | `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON/shell helpers | diff --git a/get-shit-done/bin/lib/commands.cjs b/get-shit-done/bin/lib/commands.cjs index cc45dc9b7..b8233e192 100644 --- a/get-shit-done/bin/lib/commands.cjs +++ b/get-shit-done/bin/lib/commands.cjs @@ -8,6 +8,7 @@ const { loadConfig, isGitIgnored, normalizePhaseName, comparePhaseNum, getArchiv const { planningDir, planningPaths } = require('./planning-workspace.cjs'); const { extractFrontmatter } = require('./frontmatter.cjs'); const { MODEL_PROFILES } = require('./model-profiles.cjs'); +const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs'); /** * Determine phase status by checking plan/summary counts AND verification state. @@ -779,7 +780,7 @@ function cmdScaffold(cwd, type, options, raw) { switch (type) { case 'context': { filePath = path.join(phaseDir, `${padded}-CONTEXT.md`); - content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} — Context\n\n## Decisions\n\n_Decisions will be captured during /gsd:discuss-phase ${phase}_\n\n## Discretion Areas\n\n_Areas where the executor can use judgment_\n\n## Deferred Ideas\n\n_Ideas to consider later_\n`; + content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} — Context\n\n## Decisions\n\n_Decisions will be captured during ${formatGsdSlash('discuss-phase', resolveRuntime(cwd))} ${phase}_\n\n## Discretion Areas\n\n_Areas where the executor can use judgment_\n\n## Deferred Ideas\n\n_Ideas to consider later_\n`; break; } case 'uat': { diff --git a/get-shit-done/bin/lib/drift.cjs b/get-shit-done/bin/lib/drift.cjs index 084899f86..581f23879 100644 --- a/get-shit-done/bin/lib/drift.cjs +++ b/get-shit-done/bin/lib/drift.cjs @@ -253,8 +253,10 @@ function buildMessage(elements, affectedPaths, action) { if (action === 'auto-remap') { lines.push(`Auto-remap scheduled for paths: ${affectedPaths.join(', ')}`); } else { + const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs'); + const mapCmd = formatGsdSlash('map-codebase', resolveRuntime(null)); lines.push( - `Run /gsd:map-codebase --paths ${affectedPaths.join(',')} to refresh planning context.`, + `Run ${mapCmd} --paths ${affectedPaths.join(',')} to refresh planning context.`, ); } return lines.join('\n'); diff --git a/get-shit-done/bin/lib/gsd2-import.cjs b/get-shit-done/bin/lib/gsd2-import.cjs index 6b3a575de..175271538 100644 --- a/get-shit-done/bin/lib/gsd2-import.cjs +++ b/get-shit-done/bin/lib/gsd2-import.cjs @@ -421,7 +421,8 @@ function buildPreview(gsd2Data, artifacts) { lines.push(''); lines.push('Cannot migrate automatically:'); lines.push(' - GSD-2 cost/token ledger (no v1 equivalent)'); - lines.push(' - GSD-2 database state (rebuilt from files on first /gsd:health)'); + const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs'); + lines.push(` - GSD-2 database state (rebuilt from files on first ${formatGsdSlash('health', resolveRuntime(null))})`); lines.push(' - VS Code extension state'); return lines.join('\n'); diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index c5e2b709e..7f06825ec 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -10,6 +10,7 @@ const { planningPaths, planningDir, planningRoot } = require('./planning-workspa const { maskIfSecret } = require('./secrets.cjs'); const scanPhasePlans = require('./plan-scan.cjs'); const { stateExtractField } = require('./state-document.cjs'); +const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs'); // Accept all bold/colon variants of the Requirements header (#2769): // **Requirements:** / **Requirements**: / **Requirements** : render the @@ -1055,16 +1056,20 @@ function cmdInitMapCodebase(cwd, raw) { function cmdInitManager(cwd, raw) { const config = loadConfig(cwd); const milestone = getMilestoneInfo(cwd); + // Resolve the runtime once so every emitted slash-command reference uses + // the routable shape for this install (#3584). Hyphen form for skills-based + // runtimes, $gsd- shell-var for codex. + const _slashRuntime = resolveRuntime(cwd); // Use planningPaths for forward-compatibility with workstream scoping (#1268) const paths = planningPaths(cwd); // Validate prerequisites if (!fs.existsSync(paths.roadmap)) { - error('No ROADMAP.md found. Run /gsd:new-milestone first.'); + error(`No ROADMAP.md found. Run ${formatGsdSlash('new-milestone', _slashRuntime)} first.`); } if (!fs.existsSync(paths.state)) { - error('No STATE.md found. Run /gsd:new-milestone first.'); + error(`No STATE.md found. Run ${formatGsdSlash('new-milestone', _slashRuntime)} first.`); } const rawContent = fs.readFileSync(paths.roadmap, 'utf-8'); const content = extractCurrentMilestone(rawContent, cwd); @@ -1244,7 +1249,7 @@ function cmdInitManager(cwd, raw) { phase_name: phase.name, action: 'execute', reason: `${phase.plan_count} plans ready, dependencies met`, - command: `/gsd:execute-phase ${phase.number}`, + command: `${formatGsdSlash('execute-phase', _slashRuntime)} ${phase.number}`, }); } else if (phase.disk_status === 'discussed' || phase.disk_status === 'researched') { recommendedActions.push({ @@ -1252,7 +1257,7 @@ function cmdInitManager(cwd, raw) { phase_name: phase.name, action: 'plan', reason: 'Context gathered, ready for planning', - command: `/gsd:plan-phase ${phase.number}`, + command: `${formatGsdSlash('plan-phase', _slashRuntime)} ${phase.number}`, }); } else if ((phase.disk_status === 'empty' || phase.disk_status === 'no_directory') && phase.is_next_to_discuss) { recommendedActions.push({ @@ -1260,7 +1265,7 @@ function cmdInitManager(cwd, raw) { phase_name: phase.name, action: 'discuss', reason: 'Unblocked, ready to gather context', - command: `/gsd:discuss-phase ${phase.number}`, + command: `${formatGsdSlash('discuss-phase', _slashRuntime)} ${phase.number}`, }); } } diff --git a/get-shit-done/bin/lib/milestone.cjs b/get-shit-done/bin/lib/milestone.cjs index 674a88ee3..00d72aaab 100644 --- a/get-shit-done/bin/lib/milestone.cjs +++ b/get-shit-done/bin/lib/milestone.cjs @@ -9,6 +9,7 @@ const { platformWriteSync, platformEnsureDir } = require('./shell-command-projec const { planningPaths } = require('./planning-workspace.cjs'); const { extractFrontmatter } = require('./frontmatter.cjs'); const { writeStateMd, stateReplaceFieldWithFallback } = require('./state.cjs'); +const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs'); function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) { if (!reqIdsRaw || reqIdsRaw.length === 0) { @@ -228,10 +229,10 @@ function cmdMilestoneComplete(cwd, version, options, raw) { if (operatorPattern.test(stateContent)) { stateContent = stateContent.replace( operatorPattern, - `$1\n- Start the next milestone with /gsd:new-milestone\n\n`, + `$1\n- Start the next milestone with ${formatGsdSlash('new-milestone', resolveRuntime(cwd))}\n\n`, ); } else { - stateContent = `${stateContent.trimEnd()}\n\n## Operator Next Steps\n\n- Start the next milestone with /gsd:new-milestone\n`; + stateContent = `${stateContent.trimEnd()}\n\n## Operator Next Steps\n\n- Start the next milestone with ${formatGsdSlash('new-milestone', resolveRuntime(cwd))}\n`; } writeStateMd(statePath, stateContent, cwd); diff --git a/get-shit-done/bin/lib/phase.cjs b/get-shit-done/bin/lib/phase.cjs index 400aa6dcf..362b8ea5f 100644 --- a/get-shit-done/bin/lib/phase.cjs +++ b/get-shit-done/bin/lib/phase.cjs @@ -9,6 +9,7 @@ const { platformWriteSync, platformReadSync, platformEnsureDir } = require('./sh const { planningDir, withPlanningLock } = require('./planning-workspace.cjs'); const { extractFrontmatter } = require('./frontmatter.cjs'); const { writeStateMd, readModifyWriteStateMd, stateExtractField, stateReplaceField, stateReplaceFieldWithFallback, updatePerformanceMetricsSection } = require('./state.cjs'); +const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs'); // #2893 — strict canonical filter: `{padded_phase}-{NN}-PLAN.md` or `PLAN.md`. // Documented in agents/gsd-planner.md (write_phase_prompt step). The wider @@ -579,7 +580,7 @@ function cmdPhaseAdd(cwd, description, raw, customId) { // Build phase entry const dependsOn = config.phase_naming === 'custom' ? '' : `\n**Depends on:** Phase ${typeof _newPhaseId === 'number' ? _newPhaseId - 1 : 'TBD'}`; - const phaseEntry = `\n### Phase ${_newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${_newPhaseId} to break down)\n`; + const phaseEntry = `\n### Phase ${_newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${formatGsdSlash('plan-phase', resolveRuntime(cwd))} ${_newPhaseId} to break down)\n`; // Find insertion point: before last "---" or at end let updatedContent; @@ -656,7 +657,7 @@ function cmdPhaseAddBatch(cwd, descriptions, raw) { platformEnsureDir(dirPath); platformWriteSync(path.join(dirPath, '.gitkeep'), ''); const dependsOn = config.phase_naming === 'custom' ? '' : `\n**Depends on:** Phase ${typeof newPhaseId === 'number' ? newPhaseId - 1 : 'TBD'}`; - const phaseEntry = `\n### Phase ${newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${newPhaseId} to break down)\n`; + const phaseEntry = `\n### Phase ${newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${formatGsdSlash('plan-phase', resolveRuntime(cwd))} ${newPhaseId} to break down)\n`; const lastSeparator = rawContent.lastIndexOf('\n---'); rawContent = lastSeparator > 0 ? rawContent.slice(0, lastSeparator) + phaseEntry + rawContent.slice(lastSeparator) @@ -747,7 +748,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) { platformWriteSync(path.join(dirPath, '.gitkeep'), ''); // Build phase entry - const phaseEntry = `\n### Phase ${_decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${_decimalPhase} to break down)\n`; + const phaseEntry = `\n### Phase ${_decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${formatGsdSlash('plan-phase', resolveRuntime(cwd))} ${_decimalPhase} to break down)\n`; // Insert after the target phase section const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+${afterPhaseEscaped}:[^\\n]*\\n)`, 'i'); diff --git a/get-shit-done/bin/lib/profile-output.cjs b/get-shit-done/bin/lib/profile-output.cjs index 3ae39f9b5..fd9b2029a 100644 --- a/get-shit-done/bin/lib/profile-output.cjs +++ b/get-shit-done/bin/lib/profile-output.cjs @@ -15,6 +15,7 @@ const os = require('os'); const { output, error, loadConfig } = require('./core.cjs'); const { platformReadSync: safeReadFile, platformWriteSync, platformEnsureDir } = require('./shell-command-projection.cjs'); const { getGlobalSkillDir } = require('./runtime-homes.cjs'); +const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs'); // ─── Constants ──────────────────────────────────────────────────────────────── @@ -174,36 +175,46 @@ const CLAUDE_INSTRUCTIONS = { }, }; -const CLAUDE_MD_FALLBACKS = { - project: 'Project not yet initialized. Run /gsd:new-project to set up.', - stack: 'Technology stack not yet documented. Will populate after codebase mapping or first phase.', - conventions: 'Conventions not yet established. Will populate as patterns emerge during development.', - architecture: 'Architecture not yet mapped. Follow existing patterns found in the codebase.', - skills: 'No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, `.github/skills/`, or `.codex/skills/` with a `SKILL.md` index file.', -}; +// CLAUDE.md fallback / placeholder text — runtime-aware so emitted slash +// commands route correctly under the active install (#3584). The values must +// be computed per-call rather than at module load because the slash form +// depends on the runtime resolved from the project's config/env. +function buildClaudeMdFallbacks(runtime) { + return { + project: `Project not yet initialized. Run ${formatGsdSlash('new-project', runtime)} to set up.`, + stack: 'Technology stack not yet documented. Will populate after codebase mapping or first phase.', + conventions: 'Conventions not yet established. Will populate as patterns emerge during development.', + architecture: 'Architecture not yet mapped. Follow existing patterns found in the codebase.', + skills: 'No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, `.github/skills/`, or `.codex/skills/` with a `SKILL.md` index file.', + }; +} // Directories where project skills may live (checked in order) const SKILL_SEARCH_DIRS = ['.claude/skills', '.agents/skills', '.cursor/skills', '.github/skills', '.codex/skills']; -const CLAUDE_MD_WORKFLOW_ENFORCEMENT = [ - 'Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.', - '', - 'Use these entry points:', - '- `/gsd:quick` for small fixes, doc updates, and ad-hoc tasks', - '- `/gsd:debug` for investigation and bug fixing', - '- `/gsd:execute-phase` for planned phase work', - '', - 'Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.', -].join('\n'); +function buildClaudeMdWorkflowEnforcement(runtime) { + return [ + 'Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.', + '', + 'Use these entry points:', + `- \`${formatGsdSlash('quick', runtime)}\` for small fixes, doc updates, and ad-hoc tasks`, + `- \`${formatGsdSlash('debug', runtime)}\` for investigation and bug fixing`, + `- \`${formatGsdSlash('execute-phase', runtime)}\` for planned phase work`, + '', + 'Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.', + ].join('\n'); +} -const CLAUDE_MD_PROFILE_PLACEHOLDER = [ - '', - '## Developer Profile', - '', - '> Profile not yet configured. Run `/gsd:profile-user` to generate your developer profile.', - '> This section is managed by `generate-claude-profile` -- do not edit manually.', - '', -].join('\n'); +function buildClaudeMdProfilePlaceholder(runtime) { + return [ + '', + '## Developer Profile', + '', + `> Profile not yet configured. Run \`${formatGsdSlash('profile-user', runtime)}\` to generate your developer profile.`, + '> This section is managed by `generate-claude-profile` -- do not edit manually.', + '', + ].join('\n'); +} // ─── Helper Functions ───────────────────────────────────────────────────────── @@ -284,10 +295,11 @@ function extractMarkdownSection(content, sectionName) { // ─── CLAUDE.md Section Generators ───────────────────────────────────────────── function generateProjectSection(cwd) { + const fallbacks = buildClaudeMdFallbacks(resolveRuntime(cwd)); const projectPath = path.join(cwd, '.planning', 'PROJECT.md'); const content = safeReadFile(projectPath); if (!content) { - return { content: CLAUDE_MD_FALLBACKS.project, source: 'PROJECT.md', linkPath: null, hasFallback: true }; + return { content: fallbacks.project, source: 'PROJECT.md', linkPath: null, hasFallback: true }; } const parts = []; const h1Match = content.match(/^# (.+)$/m); @@ -308,12 +320,13 @@ function generateProjectSection(cwd) { if (body) parts.push(`### Constraints\n\n${body}`); } if (parts.length === 0) { - return { content: CLAUDE_MD_FALLBACKS.project, source: 'PROJECT.md', linkPath: null, hasFallback: true }; + return { content: fallbacks.project, source: 'PROJECT.md', linkPath: null, hasFallback: true }; } return { content: parts.join('\n\n'), source: 'PROJECT.md', linkPath: '.planning/PROJECT.md', hasFallback: false }; } function generateStackSection(cwd) { + const fallbacks = buildClaudeMdFallbacks(resolveRuntime(cwd)); const codebasePath = path.join(cwd, '.planning', 'codebase', 'STACK.md'); const researchPath = path.join(cwd, '.planning', 'research', 'STACK.md'); let content = safeReadFile(codebasePath); @@ -325,7 +338,7 @@ function generateStackSection(cwd) { linkPath = '.planning/research/STACK.md'; } if (!content) { - return { content: CLAUDE_MD_FALLBACKS.stack, source: 'STACK.md', linkPath: null, hasFallback: true }; + return { content: fallbacks.stack, source: 'STACK.md', linkPath: null, hasFallback: true }; } const lines = content.split('\n'); const summaryLines = []; @@ -344,10 +357,11 @@ function generateStackSection(cwd) { } function generateConventionsSection(cwd) { + const fallbacks = buildClaudeMdFallbacks(resolveRuntime(cwd)); const conventionsPath = path.join(cwd, '.planning', 'codebase', 'CONVENTIONS.md'); const content = safeReadFile(conventionsPath); if (!content) { - return { content: CLAUDE_MD_FALLBACKS.conventions, source: 'CONVENTIONS.md', linkPath: null, hasFallback: true }; + return { content: fallbacks.conventions, source: 'CONVENTIONS.md', linkPath: null, hasFallback: true }; } const lines = content.split('\n'); const summaryLines = []; @@ -360,10 +374,11 @@ function generateConventionsSection(cwd) { } function generateArchitectureSection(cwd) { + const fallbacks = buildClaudeMdFallbacks(resolveRuntime(cwd)); const architecturePath = path.join(cwd, '.planning', 'codebase', 'ARCHITECTURE.md'); const content = safeReadFile(architecturePath); if (!content) { - return { content: CLAUDE_MD_FALLBACKS.architecture, source: 'ARCHITECTURE.md', linkPath: null, hasFallback: true }; + return { content: fallbacks.architecture, source: 'ARCHITECTURE.md', linkPath: null, hasFallback: true }; } const lines = content.split('\n'); const summaryLines = []; @@ -375,9 +390,9 @@ function generateArchitectureSection(cwd) { return { content: summary, source: 'ARCHITECTURE.md', linkPath: '.planning/codebase/ARCHITECTURE.md', hasFallback: false }; } -function generateWorkflowSection() { +function generateWorkflowSection(cwd) { return { - content: CLAUDE_MD_WORKFLOW_ENFORCEMENT, + content: buildClaudeMdWorkflowEnforcement(resolveRuntime(cwd)), source: 'GSD defaults', linkPath: null, hasFallback: false, @@ -390,6 +405,7 @@ function generateWorkflowSection() { * agents know which skills are available at session startup (Layer 1 discovery). */ function generateSkillsSection(cwd) { + const fallbacks = buildClaudeMdFallbacks(resolveRuntime(cwd)); const discovered = []; for (const dir of SKILL_SEARCH_DIRS) { @@ -426,7 +442,7 @@ function generateSkillsSection(cwd) { } if (discovered.length === 0) { - return { content: CLAUDE_MD_FALLBACKS.skills, source: 'skills/', hasFallback: true }; + return { content: fallbacks.skills, source: 'skills/', hasFallback: true }; } const lines = ['| Skill | Description | Path |', '|-------|-------------|------|']; @@ -774,7 +790,7 @@ function cmdGenerateDevPreferences(cwd, options, raw) { let stackBlock; if (analysis.data_source === 'questionnaire') { - stackBlock = 'Stack preferences not available (questionnaire-only profile). Run `/gsd:profile-user --refresh` with session data to populate.'; + stackBlock = `Stack preferences not available (questionnaire-only profile). Run \`${formatGsdSlash('profile-user', resolveRuntime(cwd))} --refresh\` with session data to populate.`; } else if (options.stack) { stackBlock = options.stack; } else { @@ -881,7 +897,7 @@ function cmdGenerateClaudeProfile(cwd, options, raw) { '', '## Developer Profile', '', - `> Generated by GSD from ${dataSource}. Run \`/gsd:profile-user --refresh\` to update.`, + `> Generated by GSD from ${dataSource}. Run \`${formatGsdSlash('profile-user', resolveRuntime(cwd))} --refresh\` to update.`, '', '| Dimension | Rating | Confidence |', '|-----------|--------|------------|', @@ -1025,7 +1041,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) { sections.push(buildSectionContent(name, gen, heading)); } sections.push(''); - sections.push(CLAUDE_MD_PROFILE_PLACEHOLDER); + sections.push(buildClaudeMdProfilePlaceholder(resolveRuntime(cwd))); existingContent = sections.join('\n\n') + '\n'; action = 'created'; platformEnsureDir(path.dirname(outputPath)); @@ -1064,7 +1080,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) { } if (!options.auto && fileContent.indexOf('