diff --git a/src/resources/extensions/sf/commands-inspect.js b/src/resources/extensions/sf/commands-inspect.js index 400107f98..81a37dd8b 100644 --- a/src/resources/extensions/sf/commands-inspect.js +++ b/src/resources/extensions/sf/commands-inspect.js @@ -3,10 +3,8 @@ * * Contains: InspectData type, formatInspectOutput, handleInspect */ -import { existsSync } from "node:fs"; -import { join } from "node:path"; +import { ensureDbOpen } from "./bootstrap/dynamic-tools.js"; import { getErrorMessage } from "./error-utils.js"; -import { sfRoot } from "./paths.js"; import { logWarning } from "./workflow-logger.js"; export function formatInspectOutput(data) { const lines = []; @@ -34,19 +32,13 @@ export function formatInspectOutput(data) { } export async function handleInspect(ctx) { try { - const { isDbAvailable, _getAdapter, openDatabase } = await import( - "./sf-db.js" - ); - if (!isDbAvailable()) { - const sfDir = sfRoot(process.cwd()); - const dbPath = join(sfDir, "sf.db"); - if (!existsSync(sfDir) || !existsSync(dbPath) || !openDatabase(dbPath)) { - ctx.ui.notify( - "No SF database available. Run /sf autonomous to create one.", - "info", - ); - return; - } + const { isDbAvailable, _getAdapter } = await import("./sf-db.js"); + if (!(await ensureDbOpen(process.cwd())) || !isDbAvailable()) { + ctx.ui.notify( + "No SF database available. Run /sf autonomous to create one.", + "info", + ); + return; } const adapter = _getAdapter(); if (!adapter) { diff --git a/src/resources/extensions/sf/tests/commands-inspect-db.test.mjs b/src/resources/extensions/sf/tests/commands-inspect-db.test.mjs new file mode 100644 index 000000000..a73fd371d --- /dev/null +++ b/src/resources/extensions/sf/tests/commands-inspect-db.test.mjs @@ -0,0 +1,70 @@ +/** + * commands-inspect-db.test.mjs — `/sf inspect` database binding coverage. + * + * Purpose: prove inspect diagnostics read the current project SQLite DB instead + * of reusing a previously-open DB from another project. + */ +import assert from "node:assert/strict"; +import { mkdirSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, test } from "vitest"; +import { handleInspect } from "../commands-inspect.js"; +import { closeDatabase, insertDecision, openDatabase } from "../sf-db.js"; + +const tmpRoots = []; +const originalCwd = process.cwd(); + +afterEach(() => { + process.chdir(originalCwd); + closeDatabase(); + for (const root of tmpRoots.splice(0)) { + rmSync(root, { recursive: true, force: true }); + } +}); + +function makeProject() { + const root = mkdtempSync(join(tmpdir(), "sf-inspect-db-")); + mkdirSync(join(root, ".sf"), { recursive: true }); + tmpRoots.push(root); + return root; +} + +function makeCtx() { + const notifications = []; + return { + ctx: { + ui: { + notify(message, level = "info") { + notifications.push({ message, level }); + }, + }, + }, + notifications, + }; +} + +test("handleInspect_when_process_switches_projects_opens_current_project_db", async () => { + const first = makeProject(); + const second = makeProject(); + openDatabase(join(first, ".sf", "sf.db")); + insertDecision({ + id: "D-FIRST", + when_context: "test", + scope: "project", + decision: "First project decision", + choice: "first", + rationale: "seed wrong db", + revisable: 1, + superseded_by: null, + }); + process.chdir(second); + + const { ctx, notifications } = makeCtx(); + await handleInspect(ctx); + + assert.equal(notifications.length, 1); + assert.equal(notifications[0].level, "info"); + assert.match(notifications[0].message, /Decisions:\s+0/); + assert.doesNotMatch(notifications[0].message, /First project decision/); +});