diff --git a/src/resources/extensions/gsd/doctor-engine-checks.ts b/src/resources/extensions/gsd/doctor-engine-checks.ts index 8b74dcac4..e7fc57540 100644 --- a/src/resources/extensions/gsd/doctor-engine-checks.ts +++ b/src/resources/extensions/gsd/doctor-engine-checks.ts @@ -13,6 +13,20 @@ export async function checkEngineHealth( issues: DoctorIssue[], fixesApplied: string[], ): Promise { + const dbPath = join(basePath, ".gsd", "gsd.db"); + + if (!isDbAvailable() && existsSync(dbPath)) { + issues.push({ + severity: "warning", + code: "db_unavailable", + scope: "project", + unitId: "project", + message: "Database unavailable — using filesystem state derivation (degraded mode). State queries may be slower and less reliable.", + file: ".gsd/gsd.db", + fixable: false, + }); + } + // ── DB constraint violation detection (full doctor only, not pre-dispatch per D-10) ── try { if (isDbAvailable()) { diff --git a/src/resources/extensions/gsd/doctor-format.ts b/src/resources/extensions/gsd/doctor-format.ts index 841f7ee13..a22d64e97 100644 --- a/src/resources/extensions/gsd/doctor-format.ts +++ b/src/resources/extensions/gsd/doctor-format.ts @@ -2,6 +2,7 @@ import type { DoctorIssue, DoctorIssueCode, DoctorReport, DoctorSummary } from " function matchesScope(unitId: string, scope?: string): boolean { if (!scope) return true; + if (unitId === "project" || unitId === "environment") return true; return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`); } diff --git a/src/resources/extensions/gsd/doctor-types.ts b/src/resources/extensions/gsd/doctor-types.ts index 8c804b3b8..309848048 100644 --- a/src/resources/extensions/gsd/doctor-types.ts +++ b/src/resources/extensions/gsd/doctor-types.ts @@ -78,6 +78,7 @@ export type DoctorIssueCode = | "db_orphaned_slice" | "db_done_task_no_summary" | "db_duplicate_id" + | "db_unavailable" | "projection_drift"; /** diff --git a/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts b/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts new file mode 100644 index 000000000..caeb403b5 --- /dev/null +++ b/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts @@ -0,0 +1,43 @@ +import { afterEach, test } from "node:test"; +import assert from "node:assert/strict"; +import { closeDatabase } from "../gsd-db.ts"; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; +import { filterDoctorIssues } from "../doctor-format.ts"; +import { checkEngineHealth } from "../doctor-engine-checks.ts"; + +afterEach(() => { + closeDatabase(); +}); + +test("filterDoctorIssues keeps project and environment issues in scoped reports", () => { + const issues = [ + { severity: "error", code: "env_dependencies", scope: "project", unitId: "environment", message: "node_modules missing", fixable: false }, + { severity: "warning", code: "db_unavailable", scope: "project", unitId: "project", message: "DB unavailable", fixable: false }, + { severity: "warning", code: "state_file_missing", scope: "slice", unitId: "M016/S01", message: "slice warning", fixable: false }, + ] as const; + + const filtered = filterDoctorIssues([...issues], { scope: "M016", includeWarnings: true }); + assert.deepEqual( + filtered.map((issue) => issue.unitId), + ["environment", "project", "M016/S01"], + ); +}); + +test("checkEngineHealth reports db_unavailable when gsd.db exists but the DB is closed", async (t) => { + const base = mkdtempSync(join(tmpdir(), "gsd-doctor-db-unavailable-")); + t.after(() => rmSync(base, { recursive: true, force: true })); + + const gsdDir = join(base, ".gsd"); + mkdirSync(gsdDir, { recursive: true }); + writeFileSync(join(gsdDir, "gsd.db"), ""); + + const issues: any[] = []; + await checkEngineHealth(base, issues, []); + + const dbIssue = issues.find((issue) => issue.code === "db_unavailable"); + assert.ok(dbIssue, "doctor should surface degraded DB mode when a DB file exists"); + assert.equal(dbIssue.unitId, "project"); + assert.equal(dbIssue.file, ".gsd/gsd.db"); +});