fix: allow migration without ROADMAP.md (#93, #90)

ROADMAP.md was the only fatal requirement for .planning → .gsd migration,
but the transformer already had a null-roadmap fallback that infers
milestones from the phases/ directory. Downgrade to warning so partial
v1 projects can migrate successfully.

Closes #93
Closes #90

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson 2026-03-12 08:55:40 -06:00
parent 0607fba4dc
commit b28299bd60
5 changed files with 14 additions and 16 deletions

View file

@ -48,7 +48,7 @@ GSD v2 solves all of these because it's not a prompt framework anymore — it's
### Migrating from v1
> **Note:** A `ROADMAP.md` file is **required** for migration. If your project doesn't have one, you'll need to create it before running the migration command.
> **Note:** Migration works best with a `ROADMAP.md` file for milestone structure. Without one, milestones are inferred from the `phases/` directory.
If you have projects with `.planning` directories from the original Get Shit Done, you can migrate them to GSD-2's `.gsd` format:

View file

@ -99,7 +99,7 @@ export async function handleMigrate(
`Directory not found: ${sourcePath}\n\n` +
'Migration converts a .planning/ directory (from older GSD versions) into .gsd/ format.\n' +
'If you are starting a new project, use /gsd:new-project instead.\n' +
'If migrating, ensure the path contains a .planning/ directory with a ROADMAP.md file.',
'If migrating, ensure the path contains a .planning/ directory.',
"error",
);
return;

View file

@ -14,7 +14,7 @@ function issue(file: string, severity: ValidationSeverity, message: string): Val
/**
* Validate that a .planning directory has the minimum required structure.
* Returns structured issues with severity levels:
* - fatal: directory doesn't exist or ROADMAP.md missing (migration cannot proceed)
* - fatal: directory doesn't exist (migration cannot proceed)
* - warning: optional files missing (migration can proceed with reduced data)
*/
export async function validatePlanningDirectory(path: string): Promise<ValidationResult> {
@ -26,12 +26,10 @@ export async function validatePlanningDirectory(path: string): Promise<Validatio
return { valid: false, issues };
}
// ROADMAP.md is required (fatal if missing)
// ROADMAP.md — warn if missing (transformer falls back to filesystem phases)
if (!existsSync(join(path, 'ROADMAP.md'))) {
issues.push(issue('ROADMAP.md', 'fatal',
`ROADMAP.md is required for migration (expected at ${join(path, 'ROADMAP.md')}). ` +
'Migration converts a .planning/ directory into .gsd/ format. ' +
'If you are starting fresh, use /gsd:new-project instead.',
issues.push(issue('ROADMAP.md', 'warning',
'ROADMAP.md not found — milestone structure will be inferred from phases/ directory',
));
}

View file

@ -725,8 +725,8 @@ Another orphan.
}
}
// ─── Test 12: Validation — missing ROADMAP.md → fatal ─────────────────
console.log('\n=== Validation: missing ROADMAP.md → fatal ===');
// ─── Test 12: Validation — missing ROADMAP.md → warning (not fatal) ───
console.log('\n=== Validation: missing ROADMAP.md → warning (not fatal) ===');
{
const base = createFixtureBase();
try {
@ -736,10 +736,10 @@ Another orphan.
const result = await validatePlanningDirectory(planning);
assertEq(result.valid, false, 'no roadmap: validation fails');
assertEq(result.valid, true, 'no roadmap: validation still passes');
assert(
result.issues.some(i => i.severity === 'fatal' && i.file.includes('ROADMAP')),
'no roadmap: fatal issue mentions ROADMAP'
result.issues.some(i => i.severity === 'warning' && i.file.includes('ROADMAP')),
'no roadmap: warning issue mentions ROADMAP'
);
} finally {
cleanup(base);

View file

@ -211,15 +211,15 @@ async function main(): Promise<void> {
}
}
console.log('\n=== Validator: missing ROADMAP.md → fatal ===');
console.log('\n=== Validator: missing ROADMAP.md → warning (not fatal) ===');
{
const base = createFixtureBase();
try {
const planning = createPlanningDir(base);
writeFileSync(join(planning, 'PROJECT.md'), SAMPLE_PROJECT);
const result = await validatePlanningDirectory(planning);
assertEq(result.valid, false, 'no roadmap: validation fails');
assert(result.issues.some(i => i.severity === 'fatal' && i.file.includes('ROADMAP')), 'no roadmap: fatal issue mentions ROADMAP');
assertEq(result.valid, true, 'no roadmap: validation still passes');
assert(result.issues.some(i => i.severity === 'warning' && i.file.includes('ROADMAP')), 'no roadmap: warning issue mentions ROADMAP');
} finally {
cleanup(base);
}