@opengsd/gsd-core 1.20.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +33 -2
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +3 -3
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +265 -161
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/gsd-core/bin/gsd-tools.cjs +554 -823
- package/gsd-core/bin/lib/capability-activation.cjs +8 -2
- package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
- package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
- package/gsd-core/bin/lib/capability-lock.cjs +19 -27
- package/gsd-core/bin/lib/capability-registry.cjs +74 -71
- package/gsd-core/bin/lib/capability-validator.cjs +1 -0
- package/gsd-core/bin/lib/capability-writer.cjs +7 -4
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
- package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
- package/gsd-core/bin/lib/cli-surface.cjs +545 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
- package/gsd-core/bin/lib/commands.cjs +99 -2
- package/gsd-core/bin/lib/config-file-read.cjs +85 -0
- package/gsd-core/bin/lib/config-loader.cjs +23 -15
- package/gsd-core/bin/lib/config.cjs +139 -101
- package/gsd-core/bin/lib/configuration.cjs +41 -2
- package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
- package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
- package/gsd-core/bin/lib/gap-checker.cjs +12 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
- package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
- package/gsd-core/bin/lib/graphify.cjs +19 -2
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
- package/gsd-core/bin/lib/init.cjs +20 -18
- package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
- package/gsd-core/bin/lib/install-engine.cjs +20 -28
- package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
- package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
- package/gsd-core/bin/lib/install-profiles.cjs +4 -2
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
- package/gsd-core/bin/lib/markdown-table.cjs +39 -1
- package/gsd-core/bin/lib/milestone.cjs +8 -6
- package/gsd-core/bin/lib/model-resolver.cjs +8 -4
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
- package/gsd-core/bin/lib/phase-id.cjs +26 -9
- package/gsd-core/bin/lib/phase-locator.cjs +8 -3
- package/gsd-core/bin/lib/phase.cjs +170 -45
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
- package/gsd-core/bin/lib/planning-snapshot.cjs +10 -6
- package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
- package/gsd-core/bin/lib/profile-output.cjs +9 -6
- package/gsd-core/bin/lib/project-context.cjs +439 -0
- package/gsd-core/bin/lib/project-root.cjs +9 -230
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
- package/gsd-core/bin/lib/report-parser.cjs +16 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +357 -50
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
- package/gsd-core/bin/lib/roadmap.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +317 -134
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
- package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
- package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
- package/gsd-core/bin/lib/state-transition.cjs +103 -46
- package/gsd-core/bin/lib/state.cjs +448 -63
- package/gsd-core/bin/lib/surface.cjs +4 -7
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
- package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
- package/gsd-core/bin/lib/verify.cjs +49 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/autonomous-smart-discuss.md +99 -18
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/planner-guidance.md +11 -0
- package/gsd-core/references/planner-human-verify-mode.md +1 -1
- package/gsd-core/references/planner-reversibility.md +2 -0
- package/gsd-core/references/planner-verify-command-grounding.md +22 -6
- package/gsd-core/workflows/autonomous.md +4 -4
- package/gsd-core/workflows/code-review-fix.md +5 -7
- package/gsd-core/workflows/execute-phase.md +21 -7
- package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
- package/gsd-core/workflows/plan-phase.md +18 -19
- package/hooks/dist/gsd-agent-isolation-guard.js +4 -3
- package/hooks/dist/gsd-config-reload.js +2 -2
- package/hooks/dist/gsd-context-monitor.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +9 -6
- package/hooks/dist/gsd-read-injection-scanner.js +2 -2
- package/hooks/dist/gsd-secret-read-guard.js +141 -6
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/config-read.js +49 -0
- package/hooks/dist/lib/git-cmd.js +3 -1
- package/hooks/dist/lib/git-head-tree.js +474 -0
- package/hooks/dist/lib/isolation-sentinel.js +168 -13
- package/hooks/dist/lib/secret-read-exemptions.js +161 -0
- package/hooks/gsd-agent-isolation-guard.js +4 -3
- package/hooks/gsd-config-reload.js +2 -2
- package/hooks/gsd-context-monitor.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +9 -6
- package/hooks/gsd-read-injection-scanner.js +2 -2
- package/hooks/gsd-secret-read-guard.js +141 -6
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/lib/config-read.js +49 -0
- package/hooks/lib/git-cmd.js +3 -1
- package/hooks/lib/git-head-tree.js +474 -0
- package/hooks/lib/isolation-sentinel.js +168 -13
- package/hooks/lib/secret-read-exemptions.js +161 -0
- package/package.json +7 -2
- package/pi/package-agents/gsd-executor.md +1 -1
- package/pi/package-agents/gsd-plan-checker.md +1 -1
- package/pi/package-agents/gsd-planner.md +3 -3
- package/pi/package-agents/gsd-verifier.md +2 -2
- package/pi/package-agents/manifest.json +4 -4
- package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/scripts/changeset/cli.cjs +2 -2
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/changeset/serialize.cjs +12 -2
- package/scripts/ci-compact-v8-coverage.cjs +204 -0
- package/scripts/ci-fix-next-rerun.cjs +149 -0
- package/scripts/ci-next-health.cjs +52 -9
- package/scripts/ci-test-scope.cjs +77 -71
- package/scripts/command-contract-helpers.cjs +56 -7
- package/scripts/docs-guard-registry.cjs +3 -0
- package/scripts/gen-cli-surface-manifest.cjs +287 -0
- package/scripts/gen-platform-conformance-tier.cjs +258 -19
- package/scripts/lib/macos-conformance-tier.generated.cjs +59 -47
- package/scripts/lib/platform-conformance-tier.generated.cjs +23 -64
- package/scripts/lib/shellcheck-fetch.cjs +71 -4
- package/scripts/lint-removed-but-needed.cjs +562 -50
- package/scripts/merge-commit-pr.cjs +186 -0
- package/scripts/release-ancestry.cjs +86 -0
- package/scripts/release-notes/changelog-release-notes.cjs +345 -0
- package/scripts/release-notes/discord-release-summary.cjs +82 -1
- package/scripts/release-pipeline-pr.cjs +92 -0
- package/scripts/require-issue-link-policy.cjs +18 -9
- package/scripts/sync-next-version.cjs +11 -2
- package/scripts/timeout-evidence-from-ci.cjs +964 -0
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -1733,7 +1733,83 @@ function convertSlashCommandsToTraeSkillMentions(content) {
|
|
|
1733
1733
|
return `/gsd-${commandName}`;
|
|
1734
1734
|
});
|
|
1735
1735
|
}
|
|
1736
|
-
|
|
1736
|
+
/**
|
|
1737
|
+
* #5715: the texts of the install prefix a rewrite pass emitted, or `[]` when the prefix
|
|
1738
|
+
* must not be shielded. The prefix is the normalized (slash-less) form, which also covers
|
|
1739
|
+
* the trailing-slash form and the bare form; a `$HOME/...` prefix also yields its `~/...`
|
|
1740
|
+
* spelling, which the Claude tilde restore emits for `@` includes.
|
|
1741
|
+
*
|
|
1742
|
+
* A prefix that is nothing but the Claude config dir itself (`.claude`, `./.claude`, with
|
|
1743
|
+
* or without a trailing slash) is never shielded: it is indistinguishable from the
|
|
1744
|
+
* look-alike `.claude/` text the converters are meant to rewrite, so shielding it would
|
|
1745
|
+
* suppress every such conversion. Empty and `.` prefixes are likewise not shielded.
|
|
1746
|
+
*/
|
|
1747
|
+
function shieldedPrefixForms(pathPrefix) {
|
|
1748
|
+
if (typeof pathPrefix !== 'string')
|
|
1749
|
+
return [];
|
|
1750
|
+
const normalized = pathPrefix.replace(/\/+$/, '');
|
|
1751
|
+
const bare = normalized.replace(/^(?:\.\/)+/, '');
|
|
1752
|
+
if (bare === '' || bare === '.' || bare === '.claude')
|
|
1753
|
+
return [];
|
|
1754
|
+
return normalized.startsWith('$HOME/') ? [normalized, '~' + normalized.slice('$HOME'.length)] : [normalized];
|
|
1755
|
+
}
|
|
1756
|
+
/** Candidate sentinel namespaces tried by shieldEmittedPrefix before it gives up. */
|
|
1757
|
+
const SHIELD_SENTINEL_LIMIT = 256;
|
|
1758
|
+
const SHIELD_SENTINEL_OPEN = '\uE000';
|
|
1759
|
+
const SHIELD_SENTINEL_CLOSE = '\uE001';
|
|
1760
|
+
/**
|
|
1761
|
+
* #5715: run `convert` over `content` with every exact occurrence of the install prefix
|
|
1762
|
+
* the rewrite pass already emitted shielded from it. The agents, the gsd-core spec tree,
|
|
1763
|
+
* the OpenCode-family and the command-body converters all run AFTER the prefix is
|
|
1764
|
+
* substituted, so a config dir that contains a source form (`.claude/`, `$&`, `/gsd:x`,
|
|
1765
|
+
* `Bash(`) would otherwise be converted a second time inside its own output.
|
|
1766
|
+
*
|
|
1767
|
+
* Every prefix that occurs in the content is shielded, whatever it contains. A default
|
|
1768
|
+
* install is unaffected: the converters have nothing to rewrite inside a prefix that
|
|
1769
|
+
* holds no source form, so the output is byte-identical. The sentinel namespace is chosen
|
|
1770
|
+
* per call and checked absent from `content`; when none of the candidates is free this
|
|
1771
|
+
* throws instead of converting unshielded.
|
|
1772
|
+
*/
|
|
1773
|
+
function shieldEmittedPrefix(content, pathPrefix, convert) {
|
|
1774
|
+
const forms = shieldedPrefixForms(pathPrefix).filter((form) => content.includes(form));
|
|
1775
|
+
if (forms.length === 0)
|
|
1776
|
+
return convert(content);
|
|
1777
|
+
let namespace = 0;
|
|
1778
|
+
while (namespace < SHIELD_SENTINEL_LIMIT && content.includes(SHIELD_SENTINEL_OPEN + namespace + ':'))
|
|
1779
|
+
namespace += 1;
|
|
1780
|
+
if (namespace === SHIELD_SENTINEL_LIMIT) {
|
|
1781
|
+
throw new Error('shieldEmittedPrefix: no free sentinel namespace in the content; refusing to convert it unshielded');
|
|
1782
|
+
}
|
|
1783
|
+
const tokenOf = (index) => SHIELD_SENTINEL_OPEN + namespace + ':' + index + SHIELD_SENTINEL_CLOSE;
|
|
1784
|
+
let shielded = content;
|
|
1785
|
+
forms.forEach((form, index) => {
|
|
1786
|
+
shielded = shielded.split(form).join(tokenOf(index));
|
|
1787
|
+
});
|
|
1788
|
+
let converted = convert(shielded);
|
|
1789
|
+
forms.forEach((form, index) => {
|
|
1790
|
+
converted = converted.split(tokenOf(index)).join(form);
|
|
1791
|
+
});
|
|
1792
|
+
return converted;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* #2658 / #5715: the emitted `.trae/` prefix followed by the Claude instruction file names
|
|
1796
|
+
* the Trae rules file under that same prefix. The Body converter resolves it with a pattern
|
|
1797
|
+
* that reads the prefix text, which the shield hides from it, so the exact emitted prefix is
|
|
1798
|
+
* resolved here, before shielding, by literal replacement.
|
|
1799
|
+
*/
|
|
1800
|
+
function resolveTraeInstructionFileAtPrefix(content, pathPrefix) {
|
|
1801
|
+
let resolved = content;
|
|
1802
|
+
for (const form of shieldedPrefixForms(pathPrefix)) {
|
|
1803
|
+
if (!form.endsWith('.trae'))
|
|
1804
|
+
continue;
|
|
1805
|
+
resolved = resolved.split(form + '/CLAUDE.md').join(form + '/rules/rules.md');
|
|
1806
|
+
}
|
|
1807
|
+
return resolved;
|
|
1808
|
+
}
|
|
1809
|
+
function convertClaudeToTraeMarkdown(content, pathPrefix) {
|
|
1810
|
+
return shieldEmittedPrefix(resolveTraeInstructionFileAtPrefix(content, pathPrefix), pathPrefix, convertClaudeToTraeMarkdownBody);
|
|
1811
|
+
}
|
|
1812
|
+
function convertClaudeToTraeMarkdownBody(content) {
|
|
1737
1813
|
let converted = convertSlashCommandsToTraeSkillMentions(filterRuntimeNotesForTarget(content, 'trae'));
|
|
1738
1814
|
converted = converted.replace(/\bBash\(/g, 'Shell(');
|
|
1739
1815
|
converted = converted.replace(/\bEdit\(/g, 'StrReplace(');
|
|
@@ -1793,9 +1869,10 @@ function convertClaudeToTraeMarkdown(content) {
|
|
|
1793
1869
|
converted = converted.replace(/\.\/\.claude\//g, './.trae/');
|
|
1794
1870
|
converted = converted.replace(/\.claude\//g, '.trae/');
|
|
1795
1871
|
// Bare forms (no trailing slash) — after slash forms to avoid double-rewrite.
|
|
1796
|
-
// Use negative lookahead (?![\w-]) to preserve .claude-plugin and .
|
|
1797
|
-
|
|
1798
|
-
converted = converted.replace(
|
|
1872
|
+
// Use negative lookahead (?![\w-]|\.\w) to preserve .claude-plugin, .claudeignore and .claude.json.
|
|
1873
|
+
// #5715: an emitted prefix is shielded by convertClaudeToTraeMarkdown; the skills path runs this before the prefix pass, and the profile maps the own-dir forms.
|
|
1874
|
+
converted = converted.replace(/~\/\.claude(?![\w-]|\.\w)/g, '~/.trae');
|
|
1875
|
+
converted = converted.replace(/\$HOME\/\.claude(?![\w-]|\.\w)/g, () => '$HOME/.trae');
|
|
1799
1876
|
// Environment variable name rewrite
|
|
1800
1877
|
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'TRAE_CONFIG_DIR');
|
|
1801
1878
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
@@ -2702,9 +2779,10 @@ function convertClaudeAgentToCopilotAgent(content, isGlobal = false) {
|
|
|
2702
2779
|
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
2703
2780
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
2704
2781
|
const color = extractFrontmatterField(frontmatter, 'color');
|
|
2705
|
-
|
|
2706
|
-
//
|
|
2707
|
-
|
|
2782
|
+
// CONV-04 + CONV-05: Map tools, deduplicate, format as JSON array.
|
|
2783
|
+
// parseFrontmatterTools reads both the inline comma list and the YAML block
|
|
2784
|
+
// list (#5790).
|
|
2785
|
+
const claudeTools = parseFrontmatterTools(frontmatter);
|
|
2708
2786
|
const mappedTools = claudeTools.map(t => convertCopilotToolName(t));
|
|
2709
2787
|
const uniqueTools = [...new Set(mappedTools)];
|
|
2710
2788
|
const toolsArray = uniqueTools.length > 0
|
|
@@ -2794,8 +2872,8 @@ function convertClaudeAgentToAugmentAgent(content) {
|
|
|
2794
2872
|
const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
|
|
2795
2873
|
return `${cleanFrontmatter}\n${body}`;
|
|
2796
2874
|
}
|
|
2797
|
-
function convertClaudeAgentToTraeAgent(content) {
|
|
2798
|
-
const converted = convertClaudeToTraeMarkdown(content);
|
|
2875
|
+
function convertClaudeAgentToTraeAgent(content, _isGlobal, meta) {
|
|
2876
|
+
const converted = convertClaudeToTraeMarkdown(content, meta && meta.pathPrefix);
|
|
2799
2877
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
2800
2878
|
if (!frontmatter)
|
|
2801
2879
|
return converted;
|
|
@@ -3110,7 +3188,9 @@ function convertClaudeAgentToCodexAgent(content) {
|
|
|
3110
3188
|
return converted;
|
|
3111
3189
|
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
3112
3190
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
3113
|
-
|
|
3191
|
+
// parseFrontmatterTools reads both the inline comma list and the YAML block
|
|
3192
|
+
// list (#5790).
|
|
3193
|
+
const tools = parseFrontmatterTools(frontmatter).join(', ');
|
|
3114
3194
|
const roleHeader = `<codex_agent_role>
|
|
3115
3195
|
role: ${name}
|
|
3116
3196
|
tools: ${tools}
|
|
@@ -3278,17 +3358,19 @@ function retargetInstalledIncludes(content, behaviors, pathPrefix) {
|
|
|
3278
3358
|
return content;
|
|
3279
3359
|
if (!behaviors)
|
|
3280
3360
|
return content;
|
|
3281
|
-
|
|
3361
|
+
// #5715: every rule is an alternative of ONE pass, so the prefix a rule emits is never
|
|
3362
|
+
// rescanned by another (a prefix under `$HOME/<root>/` was rewritten a second time by
|
|
3363
|
+
// the `@$HOME/<root>/` rule after the `@~/<root>/` rule had emitted it), and the
|
|
3364
|
+
// replacement is a function result: a string replacement would expand `$&` in a prefix.
|
|
3365
|
+
const rules = [];
|
|
3282
3366
|
if (behaviors.retargetsGsdCoreProsePath === true) {
|
|
3283
|
-
|
|
3367
|
+
rules.push({ pattern: '@?GSD core\\/(?=[a-z])', replacement: `${pathPrefix}gsd-core/` });
|
|
3284
3368
|
}
|
|
3285
3369
|
const home = behaviors.defaultHomeIncludeRoot;
|
|
3286
3370
|
if (typeof home === 'string' && home.length > 0 && pathPrefix !== `$HOME/${home}/`) {
|
|
3287
|
-
|
|
3288
|
-
.split(`@~/${home}/`).join(`@${pathPrefix}`)
|
|
3289
|
-
.split(`@$HOME/${home}/`).join(`@${pathPrefix}`);
|
|
3371
|
+
rules.push({ pattern: '@~\\/' + (0, pattern_cjs_1.escapeRegex)(home) + '\\/', replacement: `@${pathPrefix}` }, { pattern: '@\\$HOME\\/' + (0, pattern_cjs_1.escapeRegex)(home) + '\\/', replacement: `@${pathPrefix}` });
|
|
3290
3372
|
}
|
|
3291
|
-
return
|
|
3373
|
+
return rewriteSourceFormsOnce(content, rules);
|
|
3292
3374
|
}
|
|
3293
3375
|
/** Return a safe project-relative prefix for a resolved install target. */
|
|
3294
3376
|
function projectRelativePrefixFromProjectRoot(projectRoot, resolvedTarget) {
|
|
@@ -3609,27 +3691,102 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
3609
3691
|
return _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal, attribution);
|
|
3610
3692
|
}
|
|
3611
3693
|
/**
|
|
3612
|
-
* #5501: rewrite `~/.claude
|
|
3613
|
-
*
|
|
3614
|
-
*
|
|
3615
|
-
*
|
|
3616
|
-
*
|
|
3694
|
+
* #5501 / #5715: rewrite config-dir source forms (`~/.claude/`, `$HOME/.claude`,
|
|
3695
|
+
* `./.claude/`, ...) in ONE pass. A sequential `.replace` chain re-scans the prefix an
|
|
3696
|
+
* earlier pass emitted, so a config dir nested under `~/.claude` or named
|
|
3697
|
+
* `.claude.d` was rewritten again inside its own output. Here every rule is an
|
|
3698
|
+
* alternative of one regex (earlier rules win at a position), so each source
|
|
3699
|
+
* occurrence is rewritten exactly once and emitted text is never rescanned. The
|
|
3700
|
+
* replacement is a FUNCTION result, never a string: a string replacement
|
|
3701
|
+
* interprets `$&`, `$`` and `$'` in a config dir.
|
|
3617
3702
|
*/
|
|
3618
3703
|
// The bare form ends at anything but a word character, `-`, or `.` + word character,
|
|
3619
3704
|
// so `.claude-plugin`, `.claudeignore`, `.claude.json` and `.claude.d` are not home forms.
|
|
3620
3705
|
const CLAUDE_HOME_BARE_BOUNDARY = '(?![\\w-]|\\.\\w)';
|
|
3621
|
-
const
|
|
3622
|
-
function
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3706
|
+
const CONFIG_DIR_REWRITE_RE_CACHE = new Map();
|
|
3707
|
+
function rewriteSourceFormsOnce(content, rules) {
|
|
3708
|
+
if (rules.length === 0)
|
|
3709
|
+
return content;
|
|
3710
|
+
const source = rules.map((rule) => '(' + rule.pattern + ')').join('|');
|
|
3711
|
+
let re = CONFIG_DIR_REWRITE_RE_CACHE.get(source);
|
|
3712
|
+
if (!re) {
|
|
3713
|
+
re = new RegExp(source, 'g');
|
|
3714
|
+
CONFIG_DIR_REWRITE_RE_CACHE.set(source, re);
|
|
3715
|
+
}
|
|
3716
|
+
return content.replace(re, (match, ...groups) => {
|
|
3717
|
+
for (let i = 0; i < rules.length; i++) {
|
|
3718
|
+
if (groups[i] !== undefined) {
|
|
3719
|
+
const replacement = rules[i].replacement;
|
|
3720
|
+
return typeof replacement === 'function' ? replacement(match) : replacement;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
return match;
|
|
3631
3724
|
});
|
|
3632
3725
|
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Rule builders for one install target. `slash` rewrites `~/D/` and `$HOME/D/` to the
|
|
3728
|
+
* prefix; `tilde` only `~/D/`; `bare` rewrites `~/D` and `$HOME/D` (no trailing slash)
|
|
3729
|
+
* to the normalized prefix; `localSlash` / `localBare` rewrite `./D/` and `./D`.
|
|
3730
|
+
* Bare `.claude` rules (home and `./`) take CLAUDE_HOME_BARE_BOUNDARY so Claude Code's
|
|
3731
|
+
* own `.claude`-prefixed files (`.claude.json`, `.claude-plugin`) stay unchanged;
|
|
3732
|
+
* every other bare rule takes the EXACT boundary its profile used before #5715 (a
|
|
3733
|
+
* lookahead or `\b`). Order the rules slash forms first: most bare boundaries also
|
|
3734
|
+
* accept a following `/`.
|
|
3735
|
+
*/
|
|
3736
|
+
function configDirRuleBuilders(pathPrefix, normalizedPathPrefix, dirName) {
|
|
3737
|
+
const dir = (d) => (0, pattern_cjs_1.escapeRegex)(d);
|
|
3738
|
+
const slashRule = (pattern) => ({ pattern, replacement: pathPrefix });
|
|
3739
|
+
const bareRule = (pattern, replacement) => ({
|
|
3740
|
+
pattern,
|
|
3741
|
+
replacement: replacement ?? normalizedPathPrefix,
|
|
3742
|
+
});
|
|
3743
|
+
return {
|
|
3744
|
+
slash: (d) => [slashRule('~\\/' + dir(d) + '\\/'), slashRule('\\$HOME\\/' + dir(d) + '\\/')],
|
|
3745
|
+
tilde: (d) => [slashRule('~\\/' + dir(d) + '\\/')],
|
|
3746
|
+
bare: (d, boundary, replacement) => [
|
|
3747
|
+
bareRule('~\\/' + dir(d) + boundary, replacement),
|
|
3748
|
+
bareRule('\\$HOME\\/' + dir(d) + boundary, replacement),
|
|
3749
|
+
],
|
|
3750
|
+
localSlash: (d, replacement = './' + dirName + '/') => [
|
|
3751
|
+
{ pattern: '\\.\\/' + dir(d) + '\\/', replacement },
|
|
3752
|
+
],
|
|
3753
|
+
localBare: (d, boundary) => [
|
|
3754
|
+
{ pattern: '\\.\\/' + dir(d) + boundary, replacement: './' + dirName },
|
|
3755
|
+
],
|
|
3756
|
+
};
|
|
3757
|
+
}
|
|
3758
|
+
/**
|
|
3759
|
+
* The gsd-core/ spec-tree copy (`copyWithPathReplacement` in bin/install.js): the
|
|
3760
|
+
* generic Claude / Qwen / Hermes source forms to the install prefix, in one pass.
|
|
3761
|
+
*/
|
|
3762
|
+
function rewriteSpecTreeConfigDirForms(content, pathPrefix, dirName) {
|
|
3763
|
+
const b = configDirRuleBuilders(pathPrefix, pathPrefix.replace(/\/$/, ''), dirName);
|
|
3764
|
+
return rewriteSourceFormsOnce(content, [
|
|
3765
|
+
...b.slash('.claude'),
|
|
3766
|
+
...b.localSlash('.claude'),
|
|
3767
|
+
...b.slash('.qwen'),
|
|
3768
|
+
...b.localSlash('.qwen'),
|
|
3769
|
+
...b.slash('.hermes'),
|
|
3770
|
+
...b.localSlash('.hermes'),
|
|
3771
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3772
|
+
...b.localBare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3773
|
+
]);
|
|
3774
|
+
}
|
|
3775
|
+
/**
|
|
3776
|
+
* The OpenCode / Kilo commands and skills path pass: `~/.claude/`, `$HOME/.claude/` and the
|
|
3777
|
+
* runtimes' own `~/.opencode/` / `~/.kilo/` to the install prefix, `./.claude/` to the
|
|
3778
|
+
* runtime's local dir, in ONE pass with literal replacement (a prefix is never rescanned,
|
|
3779
|
+
* and `$&` in a config dir is not expanded).
|
|
3780
|
+
*/
|
|
3781
|
+
function rewriteOpencodeFamilyConfigDirForms(content, pathPrefix, dirName) {
|
|
3782
|
+
const b = configDirRuleBuilders(pathPrefix, pathPrefix.replace(/\/$/, ''), dirName);
|
|
3783
|
+
return rewriteSourceFormsOnce(content, [
|
|
3784
|
+
...b.slash('.claude'),
|
|
3785
|
+
...b.localSlash('.claude'),
|
|
3786
|
+
...b.tilde('.opencode'),
|
|
3787
|
+
...b.tilde('.kilo'),
|
|
3788
|
+
]);
|
|
3789
|
+
}
|
|
3633
3790
|
function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
|
|
3634
3791
|
const dirName = getDirName(runtime);
|
|
3635
3792
|
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
|
|
@@ -3645,95 +3802,105 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
|
|
|
3645
3802
|
if (!rewriteBehaviors.skipRuntimeDefaultsStamp) {
|
|
3646
3803
|
content = _stampNonClaudeRuntimeDefaults(content, runtime);
|
|
3647
3804
|
}
|
|
3805
|
+
const b = configDirRuleBuilders(pathPrefix, normalizedPathPrefix, dirName);
|
|
3806
|
+
const LOOKAHEAD = '(?![\\w-])';
|
|
3807
|
+
const WORD_EDGE = '\\b';
|
|
3648
3808
|
switch (rewriteBehaviors.contentRewriteProfile) {
|
|
3649
3809
|
case 'slash-own-tilde':
|
|
3650
|
-
content = content
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3810
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3811
|
+
...b.slash('.claude'),
|
|
3812
|
+
...b.localSlash('.claude'),
|
|
3813
|
+
...b.tilde('.codex'),
|
|
3814
|
+
]);
|
|
3654
3815
|
// #1515 stamp moved to _stampNonClaudeRuntimeDefaults (#1521 generalisation).
|
|
3655
3816
|
content = processAttribution(content, attribution);
|
|
3656
3817
|
break;
|
|
3657
3818
|
case 'slash-bare-own-tilde':
|
|
3658
|
-
content = content
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
content = content.replace(/~\/\.cline\b/g, normalizedPathPrefix);
|
|
3666
|
-
content = content.replace(/\$HOME\/\.cline\b/g, normalizedPathPrefix);
|
|
3819
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3820
|
+
...b.slash('.claude'),
|
|
3821
|
+
...b.localSlash('.claude'),
|
|
3822
|
+
...b.slash('.cline'),
|
|
3823
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3824
|
+
...b.bare('.cline', WORD_EDGE),
|
|
3825
|
+
]);
|
|
3667
3826
|
content = processAttribution(content, attribution);
|
|
3668
3827
|
break;
|
|
3669
3828
|
case 'slash-lookahead-own-tilde':
|
|
3670
|
-
content = content
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3829
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3830
|
+
...b.slash('.claude'),
|
|
3831
|
+
...b.localSlash('.claude'),
|
|
3832
|
+
...b.tilde('.cursor'),
|
|
3833
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3834
|
+
...b.localBare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3835
|
+
]);
|
|
3677
3836
|
content = processAttribution(content, attribution);
|
|
3678
3837
|
break;
|
|
3679
3838
|
case 'slash-lookahead-nested-own-devin': {
|
|
3680
|
-
content = content
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3839
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3840
|
+
...b.slash('.claude'),
|
|
3841
|
+
...b.localSlash('.claude'),
|
|
3842
|
+
...b.tilde('.codeium/windsurf'),
|
|
3843
|
+
// The `~/` and `$HOME/` `.devin/skills/` forms become the prefix followed by
|
|
3844
|
+
// `skills/` (no stray `~/`, no doubled `$HOME/`). The `./` form keeps the text
|
|
3845
|
+
// before `.devin` as the pre-#5715 sequential rewrite left it. They sit before
|
|
3846
|
+
// the bare `.devin` rules so the longer match wins at the same position.
|
|
3847
|
+
...(isGlobal
|
|
3848
|
+
? [
|
|
3849
|
+
{ pattern: '~\\/\\.devin\\/skills\\/', replacement: pathPrefix + 'skills/' },
|
|
3850
|
+
{ pattern: '\\$HOME\\/\\.devin\\/skills\\/', replacement: pathPrefix + 'skills/' },
|
|
3851
|
+
{ pattern: '\\.\\/\\.devin\\/skills\\/', replacement: './' + pathPrefix + 'skills/' },
|
|
3852
|
+
{ pattern: '\\.devin\\/skills\\/', replacement: pathPrefix + 'skills/' },
|
|
3853
|
+
...b.localSlash('.devin', pathPrefix),
|
|
3854
|
+
]
|
|
3855
|
+
: []),
|
|
3856
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3857
|
+
...(isGlobal ? b.bare('.devin', LOOKAHEAD) : []),
|
|
3858
|
+
]);
|
|
3692
3859
|
content = processAttribution(content, attribution);
|
|
3693
3860
|
break;
|
|
3694
3861
|
}
|
|
3695
3862
|
case 'slash-lookahead-own-derived': {
|
|
3696
|
-
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
3697
|
-
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
3698
|
-
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
3699
|
-
content = content.replace(/~\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
3700
|
-
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, normalizedPathPrefix);
|
|
3701
|
-
content = content.replace(/\.\/\.claude(?![\w-])/g, `./${dirName}`);
|
|
3702
3863
|
// #2097: dot-dir self-references (~/.augment/…) → resolved prefix,
|
|
3703
3864
|
// dirName-derived (no runtime literal). getDirName('augment') resolves
|
|
3704
3865
|
// to '.augment', so this is byte-identical to the prior hardcoded regexes.
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3866
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3867
|
+
...b.slash('.claude'),
|
|
3868
|
+
...b.localSlash('.claude'),
|
|
3869
|
+
...b.slash(dirName),
|
|
3870
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3871
|
+
...b.localBare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3872
|
+
...b.bare(dirName, LOOKAHEAD),
|
|
3873
|
+
]);
|
|
3710
3874
|
content = processAttribution(content, attribution);
|
|
3711
3875
|
break;
|
|
3712
3876
|
}
|
|
3713
|
-
case 'slash-bare-own-derived':
|
|
3714
|
-
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
3715
|
-
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
3716
|
-
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
3717
|
-
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
|
3718
|
-
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
|
3719
|
-
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
3877
|
+
case 'slash-bare-own-derived': {
|
|
3720
3878
|
// #2094: descriptor-driven — dirName resolves to '.trae' via
|
|
3721
|
-
// getDirName()/localConfigDir, so
|
|
3722
|
-
// hardcoded
|
|
3723
|
-
|
|
3879
|
+
// getDirName()/localConfigDir, so the own-dir rule is built rather than
|
|
3880
|
+
// hardcoded. #5715: own-dir slash and bare forms (both `~/` and `$HOME/`
|
|
3881
|
+
// spellings) map to the install prefix, so a custom or local config dir is
|
|
3882
|
+
// honoured; default global output is unchanged.
|
|
3883
|
+
const keepDefaultOwnBare = (match) => (normalizedPathPrefix === '$HOME/' + dirName ? match : normalizedPathPrefix);
|
|
3884
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3885
|
+
...b.slash('.claude'),
|
|
3886
|
+
...b.localSlash('.claude'),
|
|
3887
|
+
...b.slash(dirName),
|
|
3888
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3889
|
+
...b.localBare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3890
|
+
...b.bare(dirName, CLAUDE_HOME_BARE_BOUNDARY, keepDefaultOwnBare),
|
|
3891
|
+
]);
|
|
3724
3892
|
content = processAttribution(content, attribution);
|
|
3725
3893
|
break;
|
|
3894
|
+
}
|
|
3726
3895
|
case 'slash-bare-own-tilde-bare':
|
|
3727
|
-
content = content
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
content = content.replace(/~\/\.codebuddy\b/g, normalizedPathPrefix);
|
|
3736
|
-
content = content.replace(/\$HOME\/\.codebuddy\b/g, normalizedPathPrefix);
|
|
3896
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3897
|
+
...b.slash('.claude'),
|
|
3898
|
+
...b.localSlash('.claude'),
|
|
3899
|
+
...b.slash('.codebuddy'),
|
|
3900
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3901
|
+
...b.localBare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3902
|
+
...b.bare('.codebuddy', WORD_EDGE),
|
|
3903
|
+
]);
|
|
3737
3904
|
content = processAttribution(content, attribution);
|
|
3738
3905
|
break;
|
|
3739
3906
|
case 'attribution-only':
|
|
@@ -3747,11 +3914,14 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
|
|
|
3747
3914
|
// #5501: the slash AND bare forms in ONE pass. The launcher default
|
|
3748
3915
|
// `${CLAUDE_CONFIG_DIR:-$HOME/.claude}` has no trailing slash, so a slash-only
|
|
3749
3916
|
// rewrite left a --config-dir install probing `~/.claude/gsd-core`. One
|
|
3750
|
-
//
|
|
3751
|
-
// rescanned
|
|
3752
|
-
//
|
|
3753
|
-
content =
|
|
3754
|
-
|
|
3917
|
+
// pass (#5715: also `./.claude/`) so a prefix the pass emits is never
|
|
3918
|
+
// rescanned. A default install keeps the source spelling of the bare form
|
|
3919
|
+
// (`~/.claude` stays `~/.claude`) so it stays byte-identical.
|
|
3920
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3921
|
+
...b.slash('.claude'),
|
|
3922
|
+
...b.localSlash('.claude'),
|
|
3923
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY, (match) => (normalizedPathPrefix === '$HOME/.claude' ? match : normalizedPathPrefix)),
|
|
3924
|
+
]);
|
|
3755
3925
|
// #3133 / #3544: restore @-file-reference lines to the tilde form
|
|
3756
3926
|
// Claude actually expands — see restoreClaudeGlobalAtRefTilde's doc
|
|
3757
3927
|
// comment above for why this must be a separate, @-anchored pass. Handed
|
|
@@ -3766,19 +3936,18 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
|
|
|
3766
3936
|
// (the gsd-surface skill) that need the negative-lookahead bare rewrites the
|
|
3767
3937
|
// claude profile deliberately does not have (it would change claude's bytes).
|
|
3768
3938
|
case 'slash-lookahead-tilde-restore':
|
|
3769
|
-
content = content
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3939
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3940
|
+
...b.slash('.claude'),
|
|
3941
|
+
...b.localSlash('.claude'),
|
|
3942
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3943
|
+
...b.localBare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3944
|
+
]);
|
|
3775
3945
|
content = restoreClaudeGlobalAtRefTilde(content, pathPrefix);
|
|
3776
3946
|
content = processAttribution(content, attribution);
|
|
3777
3947
|
break;
|
|
3778
3948
|
// Descriptor-driven brand literals (ADR-1239 / #2092): the qwen/hermes
|
|
3779
3949
|
// brand VALUES (CLAUDE.md/Claude Code/.claude/ replacements) now read from
|
|
3780
3950
|
// runtime.hostBehaviors.brandingRewrites instead of hardcoded literals.
|
|
3781
|
-
// EXACT regexes/order preserved — only the replacement values changed.
|
|
3782
3951
|
// qwen and hermes share this profile; their own dot-dir is derived from the
|
|
3783
3952
|
// descriptor's `localConfigDir` (`getDirName`) rather than spelled per host.
|
|
3784
3953
|
case 'branded-lookahead-own-derived': {
|
|
@@ -3787,35 +3956,32 @@ function _applyRuntimeRewritesInner(content, runtime, pathPrefix, isGlobal = fal
|
|
|
3787
3956
|
// brand-literal replacements, still apply the non-branding path
|
|
3788
3957
|
// rewrites below) instead of throwing on `_b['CLAUDE.md']`.
|
|
3789
3958
|
const _b = rewriteBehaviors.brandingRewrites;
|
|
3790
|
-
const _own = (0, pattern_cjs_1.escapeRegex)(dirName);
|
|
3791
3959
|
if (_b) {
|
|
3792
3960
|
content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
3793
3961
|
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
3794
3962
|
content = applyClaudeCodeBrandSwap(content, _b['Claude Code']);
|
|
3795
3963
|
}
|
|
3796
|
-
content = content
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
3808
|
-
content = content.replace(new RegExp('\\./' + _own + '/', 'g'), `./${dirName}/`);
|
|
3964
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3965
|
+
...b.slash('.claude'),
|
|
3966
|
+
...b.slash(dirName),
|
|
3967
|
+
...b.localSlash('.claude', _b ? './' + _b['.claude/'] : undefined),
|
|
3968
|
+
...b.localSlash(dirName),
|
|
3969
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3970
|
+
...b.bare(dirName, LOOKAHEAD),
|
|
3971
|
+
// Lowest priority: every other `.claude/` in the source takes the brand form.
|
|
3972
|
+
// In the SAME pass, so the prefix emitted above is never rescanned.
|
|
3973
|
+
...(_b ? [{ pattern: '\\.claude\\/', replacement: () => _b['.claude/'] }] : []),
|
|
3974
|
+
]);
|
|
3809
3975
|
content = processAttribution(content, attribution);
|
|
3810
3976
|
break;
|
|
3811
3977
|
}
|
|
3812
3978
|
case 'slash-bare':
|
|
3813
|
-
content = content
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3979
|
+
content = rewriteSourceFormsOnce(content, [
|
|
3980
|
+
...b.slash('.claude'),
|
|
3981
|
+
...b.localSlash('.claude'),
|
|
3982
|
+
...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3983
|
+
...b.localBare('.claude', CLAUDE_HOME_BARE_BOUNDARY),
|
|
3984
|
+
]);
|
|
3819
3985
|
content = processAttribution(content, attribution);
|
|
3820
3986
|
break;
|
|
3821
3987
|
default:
|
|
@@ -3887,7 +4053,8 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
|
|
|
3887
4053
|
// from runtime.hostBehaviors instead of a hardcoded runtime-name branch.
|
|
3888
4054
|
const _cmdConv = hostBehaviorsFor(runtime).commandBodyConverter;
|
|
3889
4055
|
if (_cmdConv && COMMAND_BODY_CONVERTERS[_cmdConv]) {
|
|
3890
|
-
|
|
4056
|
+
// #5715: the converter runs after the prefix pass, so the emitted prefix is shielded.
|
|
4057
|
+
content = shieldEmittedPrefix(content, pathPrefix, COMMAND_BODY_CONVERTERS[_cmdConv]);
|
|
3891
4058
|
}
|
|
3892
4059
|
installFs().writeFileSync(node_path_1.default.join(tempDir, entry.name), content);
|
|
3893
4060
|
}
|
|
@@ -4069,18 +4236,29 @@ function applyAgentPathRewrites(content, runtime, pathPrefix) {
|
|
|
4069
4236
|
}
|
|
4070
4237
|
return applyAgentPathRewritesInner(content, runtime, pathPrefix);
|
|
4071
4238
|
}
|
|
4239
|
+
/**
|
|
4240
|
+
* #5715: run an agent converter over content whose config-dir forms `applyAgentPathRewrites`
|
|
4241
|
+
* has already rewritten, with the emitted prefix shielded from the converter. A runtime that
|
|
4242
|
+
* declares `noPathRewrite` emitted no prefix, so nothing is shielded.
|
|
4243
|
+
*/
|
|
4244
|
+
function shieldAgentConverterPrefix(content, runtime, pathPrefix, convert) {
|
|
4245
|
+
if (hostBehaviorsFor(runtime).noPathRewrite === true)
|
|
4246
|
+
return convert(content);
|
|
4247
|
+
// The runtime whose own dot-dir is `.trae` resolves the instruction file at the emitted
|
|
4248
|
+
// prefix (see resolveTraeInstructionFileAtPrefix) before the prefix is hidden.
|
|
4249
|
+
const resolved = getDirName(runtime) === '.trae' ? resolveTraeInstructionFileAtPrefix(content, pathPrefix) : content;
|
|
4250
|
+
return shieldEmittedPrefix(resolved, pathPrefix, convert);
|
|
4251
|
+
}
|
|
4072
4252
|
function applyAgentPathRewritesInner(content, runtime, pathPrefix) {
|
|
4073
4253
|
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
|
|
4074
|
-
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
4075
|
-
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
4076
4254
|
// #3719 review (MAJOR): a bare `\b` is satisfied by ANY non-word character,
|
|
4077
4255
|
// including '-' — for a --config-dir whose name EXTENDS '.claude' (e.g.
|
|
4078
4256
|
// '.claude-work'), this re-matched the '.claude' PREFIX of the emitted
|
|
4079
|
-
// '.claude-work' path and corrupted it to '.claude-work-work'.
|
|
4080
|
-
//
|
|
4081
|
-
//
|
|
4082
|
-
|
|
4083
|
-
content = content.
|
|
4257
|
+
// '.claude-work' path and corrupted it to '.claude-work-work'. #5715: the
|
|
4258
|
+
// slash and bare forms are one single-pass rewrite, so no emitted prefix is
|
|
4259
|
+
// ever rescanned (a nested or `.claude.`-named config dir stays intact).
|
|
4260
|
+
const b = configDirRuleBuilders(pathPrefix, normalizedPathPrefix, '');
|
|
4261
|
+
content = rewriteSourceFormsOnce(content, [...b.slash('.claude'), ...b.bare('.claude', CLAUDE_HOME_BARE_BOUNDARY)]);
|
|
4084
4262
|
// #3719: the THIRD emit path that needed this restore. #3133 added it to the
|
|
4085
4263
|
// skill/command pipeline (`_applyRuntimeRewrites` case 'claude') and #3544 to
|
|
4086
4264
|
// bin/install.js's spec-tree copy; the agents pipeline never got it, so every
|
|
@@ -4379,6 +4557,11 @@ module.exports = {
|
|
|
4379
4557
|
_localIncludeDirName: localIncludeDirName,
|
|
4380
4558
|
_restoreClaudeGlobalAtRefTilde: restoreClaudeGlobalAtRefTilde,
|
|
4381
4559
|
_applyRuntimeRewrites,
|
|
4560
|
+
_rewriteSpecTreeConfigDirForms: rewriteSpecTreeConfigDirForms,
|
|
4561
|
+
_rewriteOpencodeFamilyConfigDirForms: rewriteOpencodeFamilyConfigDirForms,
|
|
4562
|
+
_shieldAgentConverterPrefix: shieldAgentConverterPrefix,
|
|
4563
|
+
_resolveTraeInstructionFileAtPrefix: resolveTraeInstructionFileAtPrefix,
|
|
4564
|
+
_shieldEmittedPrefix: shieldEmittedPrefix,
|
|
4382
4565
|
_stampNonClaudeRuntimeDefaults,
|
|
4383
4566
|
// #2652: registry-resolved dispatch isolation, mirroring routeDispatchIsolation
|
|
4384
4567
|
_negotiatedDispatchIsolation,
|