fix(gsd): surface scoped doctor health warnings

This commit is contained in:
mastertyko 2026-04-09 18:39:16 +02:00
parent 4b671fba0f
commit 07da34dadb
4 changed files with 59 additions and 0 deletions

View file

@ -13,6 +13,20 @@ export async function checkEngineHealth(
issues: DoctorIssue[],
fixesApplied: string[],
): Promise<void> {
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()) {

View file

@ -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}`);
}

View file

@ -78,6 +78,7 @@ export type DoctorIssueCode =
| "db_orphaned_slice"
| "db_done_task_no_summary"
| "db_duplicate_id"
| "db_unavailable"
| "projection_drift";
/**

View file

@ -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");
});