From fd2b97a52a1ec83d6d9cfec2f0cc5269902b8739 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 15 Aug 2026 09:31:59 -0400 Subject: [PATCH] fix(#3544): restore tilde form for at-refs in the global spec tree (#3551) * fix(#3544): restore tilde form for at-refs in the global spec tree A global claude install emitted @$HOME/.claude/gsd-core/references/*.md in its workflows and references. $HOME does not expand in a Claude Code @-import - only relative, absolute and ~ are documented, and a controlled /context test confirmed a $HOME import loads nothing - so 54 includes across 22 files silently resolved to nothing on a live install. This is a divergence, not a new bug. #3133 already applies exactly this correction to skill and command bodies through _applyRuntimeRewrites's claude case; copyWithPathReplacement, the spec-tree emit path, never had it. Both now call one exported helper, so the two surfaces cannot drift apart again. Deliberately narrower than changing computePathPrefix's return value: shipped markdown also carries double-quoted "$HOME/.claude/..." shell invocations, and ~ does not expand inside double quotes, so rewriting the prefix wholesale would regress #1284. Only @-prefixed references move. Refs #3544 * fix(#3544): derive the tilde restore from the resolved prefix Three review findings, one batch. The restore was hardcoded to the literal .claude directory, so a global install with --config-dir pointing anywhere else silently no-opped and reproduced the very defect this fixes. It now derives the tilde form from the resolved prefix, which also closes the same latent gap in #3133's original path since both call sites share the helper. The @-anchor is quote-aware, so a double-quoted shell path is never rewritten into a form the shell does not expand. Deliberately a lookbehind rather than a line-start anchor: @-references are documented to work mid-line, and anchoring would have traded a theoretical bug for a real one. Found while testing the above: the bare-form rewrites re-matched their own output whenever a config dir name extends .claude, emitting .claude-work-work. Guarded with the same negative-lookahead convention this file already uses to preserve .claude-plugin. The tests prove the emitted form, never that the host resolves it - no CI test can - and both the helper and the suite now say so, because an undocumented verification boundary is how this defect stayed green for its whole life. Refs #3544 * test(#3544): acknowledge the tilde-restore emitted drift The converter change moves 94 emitted paths that no source-file diff can explain, which is exactly the case the per-PR ack fragment exists for. Verified before acknowledging rather than after: both trees were built from real installs and every one of the 211 changed lines across all 94 paths is @$HOME becoming @~, with nothing outside that single kind. Nine spent entries were pruned from the #3151 and #2658 fragments. Those paths moved again here, and two ack sources naming one path is a hard duplicate error rather than last-wins, so the inert entries had to go before this one could land. Both fragments retain their remaining entries. Refs #3544 * chore(#3544): backfill changeset pr number --------- Co-authored-by: sim --- .changeset/sunny-deer-hum.md | 5 + bin/install.js | 27 +- src/runtime-artifact-conversion.cts | 92 +++++- .../2658-trae-instruction-file-path.json | 3 - .../3151-skill-effort-cache-invalidation.json | 18 -- .../3544-home-expansion-tilde-restore.json | 287 ++++++++++++++++++ tests/path-replacement.test.cjs | 278 +++++++++++++++++ 7 files changed, 675 insertions(+), 35 deletions(-) create mode 100644 .changeset/sunny-deer-hum.md create mode 100644 tests/emitted-drift-acks/3544-home-expansion-tilde-restore.json diff --git a/.changeset/sunny-deer-hum.md b/.changeset/sunny-deer-hum.md new file mode 100644 index 000000000..f93892079 --- /dev/null +++ b/.changeset/sunny-deer-hum.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3551 +--- +**Global Claude Code installs now load their referenced workflow context** — `gsd-core/workflows/*.md` and other spec-tree files previously emitted `@$HOME/.claude/...` `@`-file-references, a form Claude Code's `@`-import resolver silently drops (only `~/` and absolute paths resolve). Every such reference now resolves on `~/`, matching the already-working skill/command surface; double-quoted shell $HOME references are untouched. (#3544) diff --git a/bin/install.js b/bin/install.js index 7d743e38d..90a11d480 100755 --- a/bin/install.js +++ b/bin/install.js @@ -7952,8 +7952,20 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand content = content.replace(globalClaudeRegex, pathPrefix); content = content.replace(globalClaudeHomeRegex, pathPrefix); content = content.replace(localClaudeRegex, `./${dirName}/`); - content = content.replace(/~\/\.claude\b/g, pathPrefix.replace(/\/$/, '')); - content = content.replace(/\$HOME\/\.claude\b/g, pathPrefix.replace(/\/$/, '')); + // #3544 review (Finding 1 fallout): guarded with the SAME + // negative-lookahead convention already used at ~:2859-2860 below + // ("preserve .claude-plugin and .claudeignore"). A naive `\b` here + // is satisfied by ANY non-word character, including '-' — so for a + // --config-dir whose name EXTENDS '.claude' (e.g. '.claude-work', + // pathPrefix '$HOME/.claude-work/'), this pass re-matched the + // '$HOME/.claude' PREFIX of its own slash-form output (lines above) + // and re-appended the full prefix, corrupting every emitted path to + // '$HOME/.claude-work-work/...'. Harmless no-op for the literal + // default '.claude' (self-replace with an identical string), which + // is why this went undetected until a non-default config-dir name + // was exercised. + content = content.replace(/~\/\.claude(?![\w-])/g, pathPrefix.replace(/\/$/, '')); + content = content.replace(/\$HOME\/\.claude(?![\w-])/g, pathPrefix.replace(/\/$/, '')); content = content.replace(/\.\/\.claude\b/g, `./${dirName}`); content = content.replace(/~\/\.qwen\//g, pathPrefix); content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix); @@ -7961,6 +7973,17 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand content = content.replace(/~\/\.hermes\//g, pathPrefix); content = content.replace(/\$HOME\/\.hermes\//g, pathPrefix); content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`); + // #3544: restore @-file-reference lines to the tilde form Claude Code + // actually expands — the SAME correction #3133 already applies to + // skill/command bodies via _applyRuntimeRewrites's 'claude' case (see + // restoreClaudeGlobalAtRefTilde's doc comment in + // runtime-artifact-conversion.cts). This is the gsd-core/ spec-tree + // emit path, which never had it: every @~/.claude/gsd-core/… include + // in a global install's workflows/references tree silently resolved + // to nothing (54 includes across 22 files on a live install). + if (runtime === 'claude') { + content = runtimeArtifactConversion._restoreClaudeGlobalAtRefTilde(content, pathPrefix); + } } content = processAttribution(content, getCommitAttribution(runtime)); diff --git a/src/runtime-artifact-conversion.cts b/src/runtime-artifact-conversion.cts index 24a93afb2..36c35ac13 100644 --- a/src/runtime-artifact-conversion.cts +++ b/src/runtime-artifact-conversion.cts @@ -2785,6 +2785,81 @@ function _stampNonClaudeRuntimeDefaults(content: string, runtime: string): strin return content; } +/** + * #3544 (extending #3133's fix): restore `@$HOME` `@`-file-reference + * lines back to their tilde equivalent (`@~`) in Claude-emitted + * content whose pathPrefix is the `$HOME` form. This is a NARROW, + * context-sensitive correction layered on top of the blanket `~/.claude/` / + * `$HOME/.claude/` -> pathPrefix substitution every Claude emit path + * applies: that blanket substitution MUST keep emitting `$HOME` for global + * installs — shell commands embedded in workflow/command bodies (e.g. + * `node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"`) need it, since `~` does + * not expand inside double-quoted shell strings (#1284). But Claude Code's + * own `@`-import resolver does the opposite: it documents `~` expansion and + * does NOT expand `$HOME`. That is not merely undocumented — a controlled + * `/context` measurement showed an `@$HOME/…` import loading nothing (see + * .gsd/bug/fix-3544-home-expansion-spec-tree/10-diagnosis.md's ADDENDUM). No + * automated test can verify *resolution* inside a live Claude Code session + * (nothing in CI can spawn one and read `/context`); every test here — unit + * and spawned-installer alike — verifies only the emitted STRING takes the + * `~` form Claude Code documents as expanding. A single pathPrefix string + * cannot satisfy both the shell and the `@`-import consumer, so this runs as + * a second, `@`-anchored pass AFTER the blanket substitution. + * + * #3133 first applied this restore inline in `_applyRuntimeRewrites`'s + * `case 'claude'` below (the skill/command staging pipeline). #3544 found + * the identical defect in bin/install.js's `copyWithPathReplacement` — the + * `gsd-core/` spec-tree emit path, which never had the restore step, so + * every `@~/.claude/gsd-core/…` include in a global install's workflows/ + * references tree silently resolved to nothing (54 includes across 22 files + * on a live install, per the diagnosis). Both call sites now share this one + * implementation instead of drifting independently (DEFECT.GENERATIVE-FIX). + * + * No-op unless `pathPrefix` is the `$HOME` form — local installs already + * bake an absolute, `@`-resolvable pathPrefix and are unaffected, as are + * every non-Claude runtime (never called for them). + * + * #3544 review (2nd pass): the first cut of this function hardcoded the + * literal `.claude/` segment, so it silently no-opped for any global install + * under a non-default `--config-dir` (e.g. `~/.claude-work`) — reproducing + * the exact defect #3544 fixes, just one directory name later. This ALSO + * corrects the same latent gap in #3133's original path, since both call + * sites share this one implementation. Fixed by deriving the rewrite from + * `pathPrefix` itself rather than a hardcoded directory name: the tilde + * equivalent of any `$HOME`-form prefix is `'~' + pathPrefix.slice(5)` + * (`'$HOME'.length === 5`), so the transform generalizes to any config-dir + * name with no runtime-specific literal. + * + * #3544 review (2nd pass), quote-awareness: the anchor is a negative + * lookbehind for a preceding quote character, NOT a line-start anchor — + * Claude Code documents `@`-references as valid "anywhere in your + * CLAUDE.md" (e.g. `See @README for project overview`), so anchoring to + * line-start would miss a legitimate mid-line reference. The lookbehind + * instead guards the one demonstrated false-positive: a quoted shell string + * like `echo "@$HOME/.claude/x"`, where rewriting `$HOME` to `~` inside + * double quotes reintroduces the #1284 failure mode (`~` does not expand in + * double-quoted shell). Deliberately NOT fenced-code-block aware (unlike + * `resolveSpecRootReference`'s `scanFencedBlocks` use above): this pass + * targets genuine `@`-import lines and inline shell references across the + * whole emitted corpus, and today there are zero occurrences anywhere in the + * tree of an `@$HOME` sequence inside a fenced code block (the + * quote-guard already closes the one reachable false-positive class). + * Layering `scanFencedBlocks` on top would roughly double this function's + * size to guard an undemonstrated case — the opposite of the brief's + * "simpler, not more complex" direction. If a fenced example ever needs this + * literal sequence, add fence-awareness then, with a regression test proving + * the fence is real. + * + * @private — exported as `_restoreClaudeGlobalAtRefTilde` for tests and for + * bin/install.js's `copyWithPathReplacement`. + */ +function restoreClaudeGlobalAtRefTilde(content, pathPrefix) { + if (typeof pathPrefix !== 'string' || !pathPrefix.startsWith('$HOME')) return content; + const tildeEquivalent = '~' + pathPrefix.slice('$HOME'.length); + const atRefRe = new RegExp(`(? concrete file '.trae/rules/rules.md').", "gsd-core/templates/README.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/templates/claude-md.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", - "gsd-core/templates/codebase/structure.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", - "gsd-core/workflows/execute-plan.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/help/modes/full.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/milestone-summary.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", - "gsd-core/workflows/profile-user.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/progress.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/quick.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/workflows/sketch-wrap-up.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", diff --git a/tests/emitted-drift-acks/3151-skill-effort-cache-invalidation.json b/tests/emitted-drift-acks/3151-skill-effort-cache-invalidation.json index d4ac14c4c..f0ad1a888 100644 --- a/tests/emitted-drift-acks/3151-skill-effort-cache-invalidation.json +++ b/tests/emitted-drift-acks/3151-skill-effort-cache-invalidation.json @@ -1,15 +1,6 @@ { "version": 1, "paths": { - "skills/gsd-autonomous/SKILL.md": { - "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." - }, - "skills/gsd-execute-phase/SKILL.md": { - "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." - }, - "skills/gsd-next/SKILL.md": { - "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." - }, "skills/gsd-ns-manage/skills/stats/SKILL.md": { "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." }, @@ -28,15 +19,6 @@ "skills/gsd-ns-workflow/skills/progress/SKILL.md": { "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." }, - "skills/gsd-plan-phase/SKILL.md": { - "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." - }, - "skills/gsd-progress/SKILL.md": { - "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." - }, - "skills/gsd-stats/SKILL.md": { - "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." - }, "skills/gsd/gsd-ns-manage/skills/stats/SKILL.md": { "reason": "#3151: convertClaudeCommandToClaudeSkill no longer emits effort: into skill frontmatter (a static effort value invalidates the caller's prompt cache at both scope boundaries). The converter (src/runtime-artifact-conversion.cts + duplicate bin/install.js) changed, moving these emitted skill hashes without a source-command change; this ack covers that converter-driven emission drop across all runtime layouts (per ADR-2719/#3039 precedent)." }, diff --git a/tests/emitted-drift-acks/3544-home-expansion-tilde-restore.json b/tests/emitted-drift-acks/3544-home-expansion-tilde-restore.json new file mode 100644 index 000000000..e831f38ef --- /dev/null +++ b/tests/emitted-drift-acks/3544-home-expansion-tilde-restore.json @@ -0,0 +1,287 @@ +{ + "paths": { + "gsd-core/references/context-budget.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/edge-probe.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/honest-verifier.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/model-profile-resolution.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/planner-guidance.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/planner-mvp-mode.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/prohibition-probe.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/thinking-models-planning.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/verification-patterns.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/references/verifier-phase-gates.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/templates/codebase/structure.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/templates/phase-prompt.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/ai-integration-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/autonomous.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/discuss-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/discuss-phase/modes/power.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/eval-review.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/execute-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/execute-plan.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/explore.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/mvp-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/onboard.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/plan-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/plan-review-convergence.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/profile-user.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/resume-project.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/secure-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/sketch.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/spec-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/transition.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/ui-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/ui-review.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/undo.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/validate-phase.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/verify-work.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "gsd-core/workflows/verify-work/steps/mvp-uat-framing.md": { + "reason": "#3544: the gsd-core/ spec-tree emit path now runs restoreClaudeGlobalAtRefTilde over @-file-reference lines, rewriting every @$HOME/gsd-core/... include to the @~/gsd-core/... tilde form Claude Code actually expands (verified: @$HOME references silently resolved to nothing on a live global install, 54 includes across 22 files). This is the same correction #3133 already applies to skill/command bodies via _applyRuntimeRewrites's 'claude' case; this fragment covers the gsd-core/ workflows, references, and templates tree, which never had it. bin/install.js's bare-form '~/.claude'/'$HOME/.claude' substitution regexes were also tightened from a trailing \\b to (?![\\w-]), so a --config-dir extending '.claude' (e.g. '.claude-work') no longer gets its own emitted prefix re-mangled; that fix does not itself change any byte in this fragment's paths under the default global install measured here, but is part of the same commit and is named for completeness." + }, + "skills/gsd-add-tests/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-ai-integration-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-audit-fix/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-audit-milestone/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-audit-uat/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-autonomous/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-capture/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-cleanup/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-code-review/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-complete-milestone/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-config/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-debug/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-docs-update/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-eval-review/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-execute-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-explore/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-extract-learnings/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-fast/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-forensics/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-health/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-help/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-import/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-inbox/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-ingest-docs/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-manager/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-map-codebase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-milestone-summary/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-mvp-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-new-milestone/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-new-project/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-next/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-onboard/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-pause-work/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-plan-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-plan-review-convergence/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-pr-branch/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-profile-user/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-progress/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-quick/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-resume-work/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-review/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-secure-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-settings/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-ship/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-sketch/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-spec-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-spike/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-stats/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-thread/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-ui-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-ui-review/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-ultraplan-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-undo/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-update/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-validate-phase/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-verify-work/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + }, + "skills/gsd-workspace/SKILL.md": { + "reason": "#3544: the gsd-core/ spec-tree tilde-restore fix (see the workflows/references/templates entries in this same fragment) also applies inside command bodies converted into Claude skills - this SKILL.md embeds a @$HOME/gsd-core/... include from its source command that now emits as @~/gsd-core/... so Claude Code actually expands it, matching the tilde form #3133 already normalized elsewhere in the skill body. The bin/install.js bare-form '~/.claude'/'$HOME/.claude' guard (\\b -> (?![\\w-])) shipped in the same commit but does not itself move this file's bytes under the default global install." + } + }, + "version": 1 +} diff --git a/tests/path-replacement.test.cjs b/tests/path-replacement.test.cjs index e6cb0eed6..46ddeace9 100644 --- a/tests/path-replacement.test.cjs +++ b/tests/path-replacement.test.cjs @@ -549,3 +549,281 @@ describe('#3133: global Claude @-references resolve on tilde, not $HOME', () => assert.doesNotMatch(out, /@\$HOME/, `no @$HOME; got:\n${out}`); }); }); + +// ──────────────────────────────────────────────────────────────────────── +// #3544: `$HOME` does NOT expand in a Claude Code @-import (measured — see +// .gsd/bug/fix-3544-home-expansion-spec-tree/10-diagnosis.md's ADDENDUM). +// #3133 (above) already fixed this for the SKILL/command staging pipeline +// (_applyRuntimeRewrites's 'claude' case) by restoring @-anchored $HOME +// references to tilde AFTER the blanket $HOME substitution. #3544 found the +// identical defect in bin/install.js's copyWithPathReplacement — the +// gsd-core/ SPEC-TREE emit path — which never had that restore step: every +// `@~/.claude/gsd-core/…` include in a global install's workflows/references +// tree (e.g. gsd-core/workflows/plan-phase.md's own @-includes) silently +// resolved to nothing. +// +// The old test at the top of this file ('$HOME expands inside double-quoted +// shell commands') asserted only the raw computePathPrefix STRING, never +// that an @-reference actually resolves — which is exactly why the spec-tree +// defect survived undetected. That assertion is preserved above (unchanged: +// computePathPrefix's raw $HOME form is CORRECT and must stay — it is what +// keeps double-quoted shell commands working, #1284). What was missing is +// coverage of the @-anchored restore step. This section adds it, plus a +// real spawned-installer proof that the fix reaches the actual emit path +// (not just the pure function), plus negative-proof rows for OpenCode, +// local installs, and a second non-Claude global runtime (Cursor). +// ──────────────────────────────────────────────────────────────────────── +describe('#3544: _restoreClaudeGlobalAtRefTilde (spec-tree @-ref restore, unit)', () => { + const { _restoreClaudeGlobalAtRefTilde } = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs'); + + test('restores an @$HOME/.claude/ reference to @~/.claude/ when pathPrefix is the $HOME form', () => { + const src = '@$HOME/.claude/gsd-core/references/ui-brand.md\n'; + const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/'); + assert.strictEqual(out, '@~/.claude/gsd-core/references/ui-brand.md\n'); + }); + + test('leaves a non-@-anchored $HOME/.claude/ shell reference untouched (#1284)', () => { + const src = 'node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" query state\n'; + const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/'); + assert.strictEqual(out, src, 'a double-quoted shell $HOME reference must never be rewritten to ~ (breaks #1284)'); + }); + + test('is a no-op when pathPrefix is an absolute (local-install) path', () => { + const src = '@$HOME/.claude/gsd-core/references/ui-brand.md\n'; + const out = _restoreClaudeGlobalAtRefTilde(src, '/abs/projects/foo/.claude/'); + assert.strictEqual(out, src); + }); + + test('handles multiple @-references and preserves an interleaved shell reference', () => { + const src = + '@$HOME/.claude/gsd-core/references/a.md\n' + + 'node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"\n' + + '@$HOME/.claude/gsd-core/references/b.md\n'; + const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/'); + assert.strictEqual( + out, + '@~/.claude/gsd-core/references/a.md\n' + + 'node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"\n' + + '@~/.claude/gsd-core/references/b.md\n', + ); + }); + + // #3544 review (Finding 1): a custom --config-dir under $HOME produces a + // pathPrefix like '$HOME/.claude-work/' — the directory name is NOT + // '.claude'. The restore must derive the tilde form from pathPrefix itself + // rather than a hardcoded '.claude' literal, or it silently no-ops for any + // non-default config-dir name (reproducing the exact defect #3544 fixes). + test('generalizes to a non-default config-dir name (not hardcoded to .claude)', () => { + const src = '@$HOME/.claude-work/gsd-core/workflows/plan-phase.md\n'; + const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude-work/'); + assert.strictEqual(out, '@~/.claude-work/gsd-core/workflows/plan-phase.md\n'); + }); + + // #3544 review (Finding 2): quote-aware, not line-start-anchored. A + // double-quoted or single-quoted `@$HOME/...` sequence must be left alone + // (rewriting $HOME -> ~ inside a quoted shell string reintroduces #1284 — + // ~ does not expand in double quotes). A bare line-start `@$HOME/...` and a + // legitimate MID-LINE `@`-reference (Claude Code documents @-references as + // valid "anywhere in your CLAUDE.md") must both still be rewritten. + test('leaves a double-quoted @$HOME/.claude/ sequence untouched (quote-aware)', () => { + const src = 'echo "@$HOME/.claude/x"\n'; + const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/'); + assert.strictEqual(out, src, 'a double-quoted @$HOME sequence must not be rewritten'); + }); + + test('leaves a single-quoted @$HOME/.claude/ sequence untouched (quote-aware)', () => { + const src = "'@$HOME/.claude/x'\n"; + const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/'); + assert.strictEqual(out, src, 'a single-quoted @$HOME sequence must not be rewritten'); + }); + + test('rewrites a bare line-start @$HOME/.claude/ reference', () => { + const src = '@$HOME/.claude/x\n'; + const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/'); + assert.strictEqual(out, '@~/.claude/x\n'); + }); + + test('rewrites a legitimate mid-line @$HOME/.claude/ reference (not line-start-anchored)', () => { + const src = 'See @$HOME/.claude/x for details\n'; + const out = _restoreClaudeGlobalAtRefTilde(src, '$HOME/.claude/'); + assert.strictEqual(out, 'See @~/.claude/x for details\n'); + }); +}); + +// Verification boundary (#3544): this suite proves the emitted @-line takes +// the `~` STRING form Claude Code documents as expanding. It cannot prove +// the host actually resolves it — no automated test can spawn a live Claude +// Code session and read `/context` in CI. Read a green run here as "the +// installer emits the documented form", not as "the include loads". See +// restoreClaudeGlobalAtRefTilde's doc comment (runtime-artifact-conversion.cts) +// for the controlled measurement that established the documented form is the +// one that actually resolves. +describe('#3544: spawned installer — gsd-core/ spec tree @-refs resolve on tilde', () => { + const { before, after } = require('node:test'); + const { cleanup } = require('./helpers.cjs'); + const { runNode } = require('./helpers/process-seam.cjs'); + const { INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); + const { INSTALL_SCRIPT, installerEnv } = require('./helpers/install-shared.cjs'); + const { splitLines } = require('../gsd-core/bin/lib/text-lines.cjs'); + + const sandboxes = []; + function makeSandbox(prefix) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-3544-${prefix}-`)); + sandboxes.push(dir); + return dir; + } + + // No --config-dir: install.js must resolve the DEFAULT /.claude (or + // runtime equivalent) under the sandboxed HOME, so the emitted tree has the + // real `~/.claude/gsd-core/…` shape the diagnosis measured — a --config-dir + // run pins configDir to the sandbox root itself (target === home), which + // never reproduces that layout. + function spawnInstall(args, home) { + return runNode([INSTALL_SCRIPT, ...args], { + cwd: home, + env: installerEnv({ HOME: home, USERPROFILE: home }), + timeoutMs: INSTALL_TIMEOUT_MS, + }); + } + + function collectAtLines(rootDir) { + const out = []; + const walk = (dir) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.name.endsWith('.md')) { + const content = fs.readFileSync(full, 'utf8'); + for (const line of splitLines(content)) { + if (line.startsWith('@')) out.push({ file: full, line }); + } + } + } + }; + if (fs.existsSync(rootDir)) walk(rootDir); + return out; + } + + let claudeGlobalHome, claudeLocalHome, opencodeGlobalHome, cursorGlobalHome, claudeConfigDirHome, claudeConfigDirTarget; + + before(() => { + claudeGlobalHome = makeSandbox('claude-global'); + const g = spawnInstall(['--claude', '--global'], claudeGlobalHome); + assert.strictEqual(g.exitCode, 0, `claude --global install failed:\n${g.stdout}\n${g.stderr}`); + + claudeLocalHome = makeSandbox('claude-local'); + const l = spawnInstall(['--claude', '--local'], claudeLocalHome); + assert.strictEqual(l.exitCode, 0, `claude --local install failed:\n${l.stdout}\n${l.stderr}`); + + opencodeGlobalHome = makeSandbox('opencode-global'); + const o = spawnInstall(['--opencode', '--global'], opencodeGlobalHome); + assert.strictEqual(o.exitCode, 0, `opencode --global install failed:\n${o.stdout}\n${o.stderr}`); + + cursorGlobalHome = makeSandbox('cursor-global'); + const c = spawnInstall(['--cursor', '--global'], cursorGlobalHome); + assert.strictEqual(c.exitCode, 0, `cursor --global install failed:\n${c.stdout}\n${c.stderr}`); + + // #3544 review (Finding 1): a custom --config-dir name under $HOME + // (i.e. NOT the default .claude) — the target must be a subdirectory of + // HOME (not HOME itself) so computePathPrefix takes the $HOME-shorthand + // branch with a non-'.claude' suffix, the exact precondition the + // hardcoded-'.claude' bug required. + claudeConfigDirHome = makeSandbox('claude-config-dir-home'); + claudeConfigDirTarget = path.join(claudeConfigDirHome, '.claude-work'); + const cd = spawnInstall(['--claude', '--global', '--config-dir', claudeConfigDirTarget], claudeConfigDirHome); + assert.strictEqual(cd.exitCode, 0, `claude --global --config-dir install failed:\n${cd.stdout}\n${cd.stderr}`); + }); + + after(() => { + for (const dir of sandboxes) cleanup(dir); + }); + + test('claude --global: gsd-core/workflows/plan-phase.md @-lines use ~/, never @$HOME', () => { + const planPhase = path.join(claudeGlobalHome, '.claude', 'gsd-core', 'workflows', 'plan-phase.md'); + assert.ok(fs.existsSync(planPhase), `expected ${planPhase} to exist`); + const content = fs.readFileSync(planPhase, 'utf8'); + const atLines = splitLines(content).filter((l) => l.startsWith('@')); + assert.ok(atLines.length > 0, 'expected plan-phase.md to carry at least one @-line'); + for (const line of atLines) { + assert.ok(!/^@\$HOME/.test(line), `plan-phase.md @-line must not use @$HOME: ${line}`); + } + assert.ok( + atLines.some((l) => l.startsWith('@~/')), + `expected at least one @~/ line, got: ${JSON.stringify(atLines)}`, + ); + }); + + test('claude --global: zero ^@$HOME lines remain anywhere under the emitted gsd-core/ tree', () => { + const gsdCoreDir = path.join(claudeGlobalHome, '.claude', 'gsd-core'); + const homeLines = collectAtLines(gsdCoreDir).filter(({ line }) => /^@\$HOME/.test(line)); + assert.deepStrictEqual(homeLines, [], `found @$HOME lines: ${JSON.stringify(homeLines)}`); + }); + + // #3544 review (Finding 1): a real install under a non-default --config-dir + // name reproduces the exact defect #3544 fixes if the restore is hardcoded + // to '.claude/'. This must FAIL against the pre-fix restoreClaudeGlobalAtRefTilde. + test('claude --global --config-dir : zero @$HOME lines remain, and @~// appears', () => { + const gsdCoreDir = path.join(claudeConfigDirTarget, 'gsd-core'); + assert.ok(fs.existsSync(gsdCoreDir), `expected ${gsdCoreDir} to exist`); + const homeLines = collectAtLines(gsdCoreDir).filter(({ line }) => /^@\$HOME/.test(line)); + assert.deepStrictEqual(homeLines, [], `found @$HOME lines under a custom --config-dir: ${JSON.stringify(homeLines)}`); + const tildeLines = collectAtLines(gsdCoreDir).filter(({ line }) => line.startsWith('@~/.claude-work/')); + assert.ok( + tildeLines.length > 0, + `expected at least one @~/.claude-work/ line under a custom --config-dir, got: ${JSON.stringify(collectAtLines(gsdCoreDir))}`, + ); + }); + + test('claude --global: double-quoted shell $HOME references are preserved (#1284)', () => { + // At least one emitted file must still carry a double-quoted + // "$HOME/.claude/" shell reference — proving the restore step did NOT + // blanket-convert every $HOME occurrence, only @-anchored ones. + const gsdCoreDir = path.join(claudeGlobalHome, '.claude', 'gsd-core'); + let sawShellHome = false; + const walk = (dir) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.name.endsWith('.md') && fs.readFileSync(full, 'utf8').includes('"$HOME/.claude/')) { + sawShellHome = true; + } + } + }; + walk(gsdCoreDir); + assert.ok(sawShellHome, 'expected at least one emitted file to preserve a "$HOME/.claude/ shell reference'); + }); + + test('claude --local: pathPrefix is absolute — no @$HOME or bare @~ leak (negative proof)', () => { + const gsdCoreDir = path.join(claudeLocalHome, '.claude', 'gsd-core'); + assert.ok(fs.existsSync(gsdCoreDir), `expected ${gsdCoreDir} to exist`); + const badLines = collectAtLines(gsdCoreDir).filter( + ({ line }) => /^@\$HOME/.test(line) || /^@~\//.test(line), + ); + assert.deepStrictEqual(badLines, [], `local install must not emit @$HOME or @~/: ${JSON.stringify(badLines)}`); + }); + + test('opencode --global: unaffected — never emits an @~/.claude/ line (negative proof)', () => { + const gsdCoreDir = path.join(opencodeGlobalHome, '.config', 'opencode', 'gsd-core'); + assert.ok(fs.existsSync(gsdCoreDir), `expected ${gsdCoreDir} to exist`); + const leaked = collectAtLines(gsdCoreDir).filter(({ line }) => line.startsWith('@~/.claude/')); + assert.deepStrictEqual( + leaked, [], + `opencode must never emit a @~/.claude/ line (claude-only restore leaked): ${JSON.stringify(leaked)}`, + ); + }); + + test('cursor --global: unaffected — still emits @$HOME/.cursor/ (unrestored), never @~/.claude/ (negative proof)', () => { + const gsdCoreDir = path.join(cursorGlobalHome, '.cursor', 'gsd-core'); + assert.ok(fs.existsSync(gsdCoreDir), `expected ${gsdCoreDir} to exist`); + const atLines = collectAtLines(gsdCoreDir); + const leaked = atLines.filter(({ line }) => line.startsWith('@~/.claude/')); + assert.deepStrictEqual( + leaked, [], + `cursor must never emit a @~/.claude/ line (claude-only restore leaked): ${JSON.stringify(leaked)}`, + ); + assert.ok( + atLines.some(({ line }) => line.startsWith('@$HOME/.cursor/')), + `expected cursor's own $HOME-form @-refs to remain unrestored, got: ${JSON.stringify(atLines)}`, + ); + }); +});