* test(#2565): add failing regression for generate-claude-profile runtime target cmdGenerateClaudeProfile hardcodes .claude/CLAUDE.md (project + global), ignoring the runtime policy that #3163 wired into the sibling cmdGenerateClaudeMd handler. These tests pin the parity contract for project scope (codex -> AGENTS.md, env precedence, --output override, claude preserved), global scope (codex -> <CODEX_HOME>/AGENTS.md, claude preserved), and a divergence guard asserting both handlers agree on the project instruction path for the same runtime. Failing-first: all seven tests reproduce the bug on unmodified next. * fix(#2565): route generate-claude-profile target through runtime policy cmdGenerateClaudeProfile hardcoded .claude/CLAUDE.md for both project and global scope, ignoring the runtime-aware resolution that #3163 wired into the sibling cmdGenerateClaudeMd handler. The #3163 fix diverged when it did not propagate here, so /gsd-profile-user kept writing Claude instruction files on Codex installs (and other AGENTS-native runtimes: opencode, kilo, kimi, antigravity, copilot). Fix mirrors the proven #3163 pattern using existing policy primitives: - Project scope resolves through getProjectInstructionFile(runtime) and a non-claude runtime wins over a stale claude_md_path (AGENTS-native projects must never write to CLAUDE.md). - Global scope derives ~/.<config-home>/<instruction-basename> via getGlobalConfigDir + basename(getProjectInstructionFile), so codex lands at ~/.codex/AGENTS.md. Claude global is preserved byte-for-byte (no env-var drift beyond the prior hardcoded path). - GSD_RUNTIME env var takes precedence over config.runtime. A parity test asserts both handlers agree on the project instruction path for the same runtime, guarding against future re-divergence (CLAUDE.md 'Generative Fix Divergence' rule). * docs(#2565): add changeset fragment for generate-claude-profile runtime fix * docs(#2565): remove parenthetical product description from changeset The product-name purity guard (#1777) flags 'ProductName (description)' patterns in changeset fragments because the prose renders verbatim into CHANGELOG.md at release time. The original fragment had 'Codex (and other AGENTS-native runtimes)' in the bold header, which matched the banned pattern. Reworded to drop the parenthetical; also trimmed the per-runtime mapping (belongs in code comments, not changelog prose). * docs(#2565): backfill PR number in changeset fragment * test(#2565): isolate os.homedir() cross-platform in claude global test The 'global scope: claude runtime writes to ~/.claude/CLAUDE.md' test set only HOME to redirect os.homedir() at a tmpDir. On Windows, Node's os.homedir() reads USERPROFILE (not HOME), so the child process still resolved the real user profile and the path assertion failed (#2659 CI). Set USERPROFILE alongside HOME so the isolation holds on both POSIX and Windows. Production code is unchanged — it uses os.homedir() exactly as the prior hardcoded path did.
This commit is contained in:
@@ -1028,20 +1028,52 @@ function cmdGenerateClaudeProfile(cwd: string, options: CmdGenerateClaudeProfile
|
||||
|
||||
const sectionContent = sectionLines.join('\n');
|
||||
|
||||
// #2565: resolve target through effective runtime policy instead of
|
||||
// hardcoded .claude/CLAUDE.md. Mirrors the #3163 fix applied to
|
||||
// cmdGenerateClaudeMd above; that fix diverged when it didn't propagate
|
||||
// here, leaving /gsd-profile-user writing Claude files on Codex installs.
|
||||
// - Project scope: getProjectInstructionFile(runtime) is the single source
|
||||
// of truth (AGENTS.md for codex/opencode/kilo/kimi/unknown; GEMINI.md for
|
||||
// antigravity; .github/copilot-instructions.md for copilot).
|
||||
// - Global scope: ~/.<config-home>/<instruction-basename>, derived from
|
||||
// getGlobalConfigDir + basename(getProjectInstructionFile), so codex lands
|
||||
// at ~/.codex/AGENTS.md. Claude global is preserved byte-for-byte (no
|
||||
// env-var drift beyond the prior hardcoded path).
|
||||
// - GSD_RUNTIME env var takes precedence over config.runtime (mirrors the
|
||||
// #3163 env-precedence contract). Non-claude always wins over a stale
|
||||
// claude_md_path (#3163 rationale: an AGENTS-native project must never
|
||||
// write to CLAUDE.md even if a prior Claude setup left claude_md_path).
|
||||
let config: Record<string, unknown> = {};
|
||||
try {
|
||||
config = loadConfig(cwd);
|
||||
} catch { /* use defaults */ }
|
||||
const effectiveRuntime = resolveRuntimeNameFromCandidates(
|
||||
process.env['GSD_RUNTIME'],
|
||||
config['runtime'],
|
||||
);
|
||||
const isClaudeRuntime = !effectiveRuntime || effectiveRuntime === 'claude';
|
||||
|
||||
let targetPath: string;
|
||||
if (options.global) {
|
||||
targetPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
||||
if (isClaudeRuntime) {
|
||||
targetPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
||||
} else {
|
||||
targetPath = path.join(
|
||||
getGlobalConfigDir(effectiveRuntime),
|
||||
path.basename(getProjectInstructionFile(effectiveRuntime)),
|
||||
);
|
||||
}
|
||||
} else if (options.output) {
|
||||
targetPath = path.isAbsolute(options.output) ? options.output : path.join(cwd, options.output);
|
||||
} else {
|
||||
// Read claude_md_path from config; #1098 default is ./.claude/CLAUDE.md
|
||||
// Read claude_md_path from config; #1098 default is .claude/CLAUDE.md
|
||||
// (kept consistent with cmdGenerateClaudeMd so the profile section and the
|
||||
// managed sections land in the same file on a config-less project).
|
||||
let configClaudeMdPath = './.claude/CLAUDE.md';
|
||||
try {
|
||||
const config = loadConfig(cwd);
|
||||
if (config['claude_md_path']) configClaudeMdPath = config['claude_md_path'] as string;
|
||||
} catch { /* use default */ }
|
||||
let configClaudeMdPath = '.claude/CLAUDE.md';
|
||||
if (config['claude_md_path']) configClaudeMdPath = config['claude_md_path'] as string;
|
||||
if (!isClaudeRuntime) {
|
||||
configClaudeMdPath = getProjectInstructionFile(effectiveRuntime);
|
||||
}
|
||||
targetPath = path.isAbsolute(configClaudeMdPath) ? configClaudeMdPath : path.join(cwd, configClaudeMdPath);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user