Merge pull request #3540 from Tibsfox/fix/seed-requirements-from-markdown
fix(gsd): seed requirements table from REQUIREMENTS.md on first update
This commit is contained in:
commit
dcf41154b8
2 changed files with 93 additions and 4 deletions
|
|
@ -546,11 +546,35 @@ export async function updateRequirementInDb(
|
|||
try {
|
||||
const db = await import('./gsd-db.js');
|
||||
|
||||
const existing = db.getRequirementById(id);
|
||||
let existing = db.getRequirementById(id);
|
||||
|
||||
// If requirement doesn't exist in DB, seed the entire requirements table
|
||||
// from REQUIREMENTS.md first (#3346). This handles the standard workflow
|
||||
// where requirements are authored in markdown during discussion but never
|
||||
// imported into the database — making gsd_requirement_update always fail
|
||||
// with "not_found" at milestone completion.
|
||||
if (!existing) {
|
||||
const reqFilePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
|
||||
try {
|
||||
const content = readFileSync(reqFilePath, 'utf-8');
|
||||
const { parseRequirementsSections } = await import('./md-importer.js');
|
||||
const parsed = parseRequirementsSections(content);
|
||||
if (parsed.length > 0) {
|
||||
logWarning('manifest', `Seeding ${parsed.length} requirements from REQUIREMENTS.md into DB (first update triggers import)`, { fn: 'updateRequirementInDb' });
|
||||
for (const req of parsed) {
|
||||
// Only seed if not already in DB (avoid overwriting concurrent inserts)
|
||||
if (!db.getRequirementById(req.id)) {
|
||||
db.upsertRequirement(req);
|
||||
}
|
||||
}
|
||||
// Re-check after seeding
|
||||
existing = db.getRequirementById(id);
|
||||
}
|
||||
} catch {
|
||||
// REQUIREMENTS.md missing or unparseable — fall through to skeleton
|
||||
}
|
||||
}
|
||||
|
||||
// If requirement doesn't exist in DB, create a skeleton and merge updates.
|
||||
// This handles the case where requirements were written to REQUIREMENTS.md
|
||||
// but never imported into the database (see #2919).
|
||||
const base: Requirement = existing ?? {
|
||||
id,
|
||||
class: '',
|
||||
|
|
|
|||
|
|
@ -476,6 +476,71 @@ describe('db-writer', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('updateRequirementInDb — seeds from REQUIREMENTS.md when DB empty (#3346)', async () => {
|
||||
const tmpDir = makeTmpDir();
|
||||
const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
|
||||
openDatabase(dbPath);
|
||||
|
||||
try {
|
||||
// Write a REQUIREMENTS.md with real content (simulating discussion phase output)
|
||||
const reqContent = [
|
||||
'# Requirements',
|
||||
'',
|
||||
'## Active',
|
||||
'',
|
||||
'### R005 — User authentication',
|
||||
'- Class: functional',
|
||||
'- Why: Users need secure access',
|
||||
'- Source: user-research',
|
||||
'- Primary owner: M001/S02',
|
||||
'',
|
||||
'### R007 — API rate limiting',
|
||||
'- Class: non-functional',
|
||||
'- Why: Prevent abuse',
|
||||
'- Source: architecture',
|
||||
'- Primary owner: M001/S03',
|
||||
'',
|
||||
'## Validated',
|
||||
'',
|
||||
'### R001 — Database schema',
|
||||
'- Class: functional',
|
||||
'- Why: Foundation for storage',
|
||||
'- Source: design',
|
||||
'- Validation: S01 verified',
|
||||
].join('\n');
|
||||
fs.writeFileSync(path.join(tmpDir, '.gsd', 'REQUIREMENTS.md'), reqContent);
|
||||
|
||||
// DB is empty — no requirements seeded. Update R005 to "validated".
|
||||
// Before #3346 fix: this would create a skeleton with empty fields.
|
||||
// After fix: this seeds all 3 requirements from REQUIREMENTS.md first.
|
||||
await updateRequirementInDb('R005', {
|
||||
status: 'validated',
|
||||
validation: 'S02 — auth flow verified',
|
||||
}, tmpDir);
|
||||
|
||||
// R005 should have the update AND the original content from markdown
|
||||
const r005 = getRequirementById('R005');
|
||||
assert.ok(r005, 'R005 should exist');
|
||||
assert.equal(r005!.status, 'validated', 'status should be updated');
|
||||
assert.equal(r005!.validation, 'S02 — auth flow verified', 'validation should be updated');
|
||||
assert.equal(r005!.class, 'functional', 'class should be preserved from REQUIREMENTS.md');
|
||||
assert.ok(r005!.description?.includes('authentication') || r005!.full_content?.includes('authentication'),
|
||||
'original content should be preserved');
|
||||
|
||||
// R007 and R001 should also be seeded (not just the one being updated)
|
||||
const r007 = getRequirementById('R007');
|
||||
assert.ok(r007, 'R007 should be seeded from REQUIREMENTS.md');
|
||||
assert.equal(r007!.status, 'active', 'R007 status should be active');
|
||||
|
||||
const r001 = getRequirementById('R001');
|
||||
assert.ok(r001, 'R001 should be seeded from REQUIREMENTS.md');
|
||||
assert.equal(r001!.status, 'validated', 'R001 status should be validated (from section heading)');
|
||||
} finally {
|
||||
closeDatabase();
|
||||
cleanupDir(tmpDir);
|
||||
}
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// saveArtifactToDb Tests
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue