fix(gsd): create empty DB for fresh projects with empty .gsd/ (#2510)

ensureDbOpen() and the auto-start DB lifecycle block both gated DB
creation on the presence of Markdown files (DECISIONS.md, REQUIREMENTS.md,
milestones/). In a brand new project, .gsd/ exists but contains no
Markdown yet, so gsd_decision_save returned db_unavailable and the
agent derailed.

Create an empty DB whenever .gsd/ exists, regardless of Markdown content.
Migration runs only when Markdown files are present.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson 2026-03-25 10:29:53 -06:00
parent 9490f2c45c
commit ae0029b49f
3 changed files with 18 additions and 11 deletions

View file

@ -549,17 +549,17 @@ export async function bootstrapAutoSession(
const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
const hasRequirements = existsSync(join(gsdDirPath, "REQUIREMENTS.md"));
const hasMilestones = existsSync(join(gsdDirPath, "milestones"));
if (hasDecisions || hasRequirements || hasMilestones) {
try {
const { openDatabase: openDb } = await import("./gsd-db.js");
try {
const { openDatabase: openDb } = await import("./gsd-db.js");
openDb(gsdDbPath);
if (hasDecisions || hasRequirements || hasMilestones) {
const { migrateFromMarkdown } = await import("./md-importer.js");
openDb(gsdDbPath);
migrateFromMarkdown(s.basePath);
} catch (err) {
process.stderr.write(
`gsd-migrate: auto-migration failed: ${(err as Error).message}\n`,
);
}
} catch (err) {
process.stderr.write(
`gsd-migrate: auto-migration failed: ${(err as Error).message}\n`,
);
}
}
if (existsSync(gsdDbPath) && !isDbAvailable()) {

View file

@ -67,6 +67,9 @@ export async function ensureDbOpen(): Promise<boolean> {
}
return opened;
}
// .gsd/ exists but has no Markdown content (fresh project) — create empty DB
return db.openDatabase(dbPath);
}
return false;

View file

@ -136,9 +136,10 @@ describe('ensure-db-open', () => {
// ensureDbOpen returns false for empty .gsd/ (no Markdown, no DB)
// ═══════════════════════════════════════════════════════════════════════════
test('ensureDbOpen: empty .gsd/ returns false', async () => {
test('ensureDbOpen: empty .gsd/ creates empty DB (#2510)', async () => {
const tmpDir = makeTmpDir();
fs.mkdirSync(path.join(tmpDir, '.gsd'), { recursive: true });
const gsdDir = path.join(tmpDir, '.gsd');
fs.mkdirSync(gsdDir, { recursive: true });
// .gsd/ exists but no DECISIONS.md, REQUIREMENTS.md, or milestones/
try { closeDatabase(); } catch { /* ok */ }
@ -148,9 +149,12 @@ describe('ensure-db-open', () => {
try {
const { ensureDbOpen } = await import('../bootstrap/dynamic-tools.ts');
const result = await ensureDbOpen();
assert.ok(result === false, 'ensureDbOpen should return false for empty .gsd/');
assert.ok(result === true, 'ensureDbOpen should create empty DB for fresh .gsd/');
assert.ok(fs.existsSync(path.join(gsdDir, 'gsd.db')), 'DB file should be created');
assert.ok(isDbAvailable(), 'DB should be available');
} finally {
process.cwd = origCwd;
closeDatabase();
cleanupDir(tmpDir);
}
});