fix(#1098): guard generate-claude-md against clobbering hand-crafted files; redirect default to .claude/CLAUDE.md (#1118)
/gsd-new-project wrote a repo-root CLAUDE.md full of broad project docs, overwriting/diluting a hand-crafted instruction file. --force was parsed but silently dropped, and nothing guarded an existing non-GSD file. - Guard: an existing instruction file with no `<!-- GSD:<section>-start` markers (hand-crafted) is left untouched; report action:"skipped". --force (now wired through CmdGenerateClaudeMdOptions) overwrites intentionally. The marker check uses /<!-- GSD:[a-z]+-start/ so a file merely documenting GSD syntax is safe. - Redirect: the Claude-family default output is now ./.claude/CLAUDE.md (a valid auto-loaded project-memory location) instead of repo-root ./CLAUDE.md. Aligned across the handler default, config-defaults.manifest.json, buildNewProjectConfig, the config template, new-project.md, and cmdGenerateClaudeProfile; advisory read-CLAUDE.md hints in plan-phase/quick/profile-user updated. Codex still writes AGENTS.md. Closes #1098 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -263,7 +263,7 @@ function buildNewProjectConfig(userChoices: Record<string, unknown>): Record<str
|
||||
project_code: null,
|
||||
phase_naming: 'sequential',
|
||||
agent_skills: {},
|
||||
claude_md_path: './CLAUDE.md',
|
||||
claude_md_path: './.claude/CLAUDE.md',
|
||||
plan_review: {
|
||||
source_grounding: true,
|
||||
source_grounding_authority: 'grep',
|
||||
|
||||
@@ -99,6 +99,10 @@ interface CmdGenerateClaudeProfileOptions {
|
||||
interface CmdGenerateClaudeMdOptions {
|
||||
output?: string;
|
||||
auto?: boolean;
|
||||
// #1098: overwrite an existing instruction file that has no GSD section
|
||||
// markers (a hand-crafted CLAUDE.md/AGENTS.md). Without it, such a file is
|
||||
// left untouched rather than clobbered.
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
// ─── Constants ────────────────────────────────────────────────────────────────
|
||||
@@ -1027,8 +1031,10 @@ function cmdGenerateClaudeProfile(cwd: string, options: CmdGenerateClaudeProfile
|
||||
} else if (options.output) {
|
||||
targetPath = path.isAbsolute(options.output) ? options.output : path.join(cwd, options.output);
|
||||
} else {
|
||||
// Read claude_md_path from config, default to ./CLAUDE.md
|
||||
let configClaudeMdPath = './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;
|
||||
@@ -1106,7 +1112,12 @@ function cmdGenerateClaudeMd(cwd: string, options: CmdGenerateClaudeMdOptions, r
|
||||
}
|
||||
|
||||
let assemblyConfig: Record<string, unknown> = {};
|
||||
let configClaudeMdPath = './CLAUDE.md';
|
||||
// #1098: default the Claude-family instruction file to the project-scoped
|
||||
// `.claude/CLAUDE.md` (a valid auto-loaded memory location) rather than a
|
||||
// repo-root `CLAUDE.md`, so generated GSD content does not land next to — or
|
||||
// pollute — a hand-crafted repo-root CLAUDE.md. An explicit `claude_md_path`
|
||||
// config value or `--output` still wins.
|
||||
let configClaudeMdPath = './.claude/CLAUDE.md';
|
||||
try {
|
||||
const config = loadConfig(cwd);
|
||||
if (config['claude_md_path']) configClaudeMdPath = config['claude_md_path'] as string;
|
||||
@@ -1160,6 +1171,25 @@ function cmdGenerateClaudeMd(cwd: string, options: CmdGenerateClaudeMdOptions, r
|
||||
action = 'created';
|
||||
platformEnsureDir(path.dirname(outputPath));
|
||||
platformWriteSync(outputPath, existingContent);
|
||||
} else if (!/<!-- GSD:[a-z]+-start/.test(existingContent) && !options.force) {
|
||||
// #1098: the target instruction file already exists and contains NO GSD
|
||||
// section markers — it is a hand-crafted CLAUDE.md/AGENTS.md, not a
|
||||
// GSD-managed one. Do NOT clobber it with generated project documentation
|
||||
// (broad project detail belongs in PROJECT.md / REQUIREMENTS.md, which GSD
|
||||
// already owns). Leave the file untouched and report a skip; `--force`
|
||||
// overwrites intentionally.
|
||||
output({
|
||||
claude_md_path: outputPath,
|
||||
action: 'skipped',
|
||||
reason: 'existing instruction file has no GSD markers (hand-crafted); not overwriting. Pass --force to overwrite.',
|
||||
sections_generated: [],
|
||||
sections_fallback: [],
|
||||
sections_skipped: MANAGED_SECTIONS,
|
||||
sections_total: MANAGED_SECTIONS.length,
|
||||
profile_status: 'skipped',
|
||||
message: `Left existing ${path.basename(outputPath)} untouched (no GSD markers found). Broad project context lives in PROJECT.md / REQUIREMENTS.md; pass --force to overwrite this file with GSD-managed sections.`,
|
||||
}, raw, undefined);
|
||||
return;
|
||||
} else {
|
||||
action = 'updated';
|
||||
let fileContent = existingContent;
|
||||
|
||||
Reference in New Issue
Block a user