diff --git a/packages/loopover-miner/lib/migrate-cli.js b/packages/loopover-miner/lib/migrate-cli.js index 437c9dbdec..4e0631e5cb 100644 --- a/packages/loopover-miner/lib/migrate-cli.js +++ b/packages/loopover-miner/lib/migrate-cli.js @@ -3,8 +3,8 @@ // whatever command happens to touch it first -- this command instead lets an operator PROACTIVELY bring every // known store's EXISTING on-disk file up to date in one pass (e.g. right after upgrading, or before starting a // fleet), without needing to guess which command happens to touch which store first. Mirrors status.js's -// storeIntegrityChecks [name, resolve*DbPath(env)] store list exactly (same seven stores `doctor` already -// covers), but actually OPENS each store (rather than a read-only integrity probe) so its real open/init +// storeIntegrityChecks [name, resolve*DbPath(env)] store list exactly (same eleven stores `doctor` already +// covers, #6768), but actually OPENS each store (rather than a read-only integrity probe) so its real open/init // function's migration path runs for real. A store file that does not exist yet is skipped, not created -- // "migrate" brings existing files up to date; it is not another way to bootstrap fresh state (that's `init`). import { existsSync } from "node:fs"; @@ -18,6 +18,10 @@ import { initPredictionLedger, resolvePredictionLedgerDbPath } from "./predictio import { initPortfolioQueueStore, resolvePortfolioQueueDbPath } from "./portfolio-queue.js"; import { initRunStateStore, resolveRunStateDbPath } from "./run-state.js"; import { openPlanStore, resolvePlanStoreDbPath } from "./plan-store.js"; +import { openGovernorState, resolveGovernorStateDbPath } from "./governor-state.js"; +import { initAttemptLog, resolveAttemptLogDbPath } from "./attempt-log.js"; +import { openReplaySnapshotStore, resolveReplaySnapshotDbPath } from "./replay-snapshot.js"; +import { openWorktreeAllocator, resolveWorktreeAllocatorDbPath } from "./worktree-allocator.js"; const MIGRATE_USAGE = "Usage: loopover-miner migrate [--json]"; @@ -29,6 +33,10 @@ const STORES = [ { name: "claim-ledger", resolveDbPath: resolveClaimLedgerDbPath, open: openClaimLedger }, { name: "run-state", resolveDbPath: resolveRunStateDbPath, open: initRunStateStore }, { name: "plan-store", resolveDbPath: resolvePlanStoreDbPath, open: openPlanStore }, + { name: "governor-state", resolveDbPath: resolveGovernorStateDbPath, open: openGovernorState }, + { name: "attempt-log", resolveDbPath: resolveAttemptLogDbPath, open: initAttemptLog }, + { name: "replay-snapshot", resolveDbPath: resolveReplaySnapshotDbPath, open: openReplaySnapshotStore }, + { name: "worktree-allocator", resolveDbPath: resolveWorktreeAllocatorDbPath, open: (dbPath) => openWorktreeAllocator({ dbPath }) }, ]; /** Read a store file's stamped schema version without ever creating it -- matches checkStoreIntegrity's diff --git a/packages/loopover-miner/lib/status.js b/packages/loopover-miner/lib/status.js index bdb4a4cd63..3a465684de 100644 --- a/packages/loopover-miner/lib/status.js +++ b/packages/loopover-miner/lib/status.js @@ -21,6 +21,10 @@ import { resolvePortfolioQueueDbPath } from "./portfolio-queue.js"; import { resolveClaimLedgerDbPath } from "./claim-ledger.js"; import { resolveRunStateDbPath } from "./run-state.js"; import { resolvePlanStoreDbPath } from "./plan-store.js"; +import { resolveGovernorStateDbPath } from "./governor-state.js"; +import { resolveAttemptLogDbPath } from "./attempt-log.js"; +import { resolveReplaySnapshotDbPath } from "./replay-snapshot.js"; +import { resolveWorktreeAllocatorDbPath } from "./worktree-allocator.js"; // Slim laptop-mode CLI commands (#2288): `status` (what's installed + where local state lives) and `doctor` (is // this laptop set up correctly). Both are read-only and 100% local — no repo-scanning, no coding-agent invocation, @@ -297,7 +301,8 @@ function checkStateDirWritable(stateDir) { } /** Per-store `PRAGMA integrity_check` sweep for `doctor` (#4834) — flags a corrupted store instead of probing - * only one with `SELECT 1`. A store file that does not exist yet is healthy by absence. */ + * only one with `SELECT 1`. A store file that does not exist yet is healthy by absence. Keep in sync with + * migrate-cli.js's `STORES` list (#6768): every durable local SQLite store using resolveLocalStoreDbPath. */ function storeIntegrityChecks(env) { const stores = [ ["event-ledger", resolveEventLedgerDbPath(env)], @@ -307,6 +312,10 @@ function storeIntegrityChecks(env) { ["claim-ledger", resolveClaimLedgerDbPath(env)], ["run-state", resolveRunStateDbPath(env)], ["plan-store", resolvePlanStoreDbPath(env)], + ["governor-state", resolveGovernorStateDbPath(env)], + ["attempt-log", resolveAttemptLogDbPath(env)], + ["replay-snapshot", resolveReplaySnapshotDbPath(env)], + ["worktree-allocator", resolveWorktreeAllocatorDbPath(env)], ]; return stores.map(([name, dbPath]) => checkStoreIntegrity(`store-integrity:${name}`, dbPath)); } diff --git a/test/unit/mcp-tool-rename-aliases.test.ts b/test/unit/mcp-tool-rename-aliases.test.ts index 28d8336071..3f7a6fa988 100644 --- a/test/unit/mcp-tool-rename-aliases.test.ts +++ b/test/unit/mcp-tool-rename-aliases.test.ts @@ -12,6 +12,7 @@ // (#6732 registered the loopover_monitor_open_prs CLI mirror, taking the count from 63 to 64.) // (#6752 registered the loopover_build_results_payload CLI mirror, taking the count from 67 to 68.) // (#6755 registered the loopover_intake_idea CLI mirror, taking the count from 68 to 69.) +// (#6915 registered the loopover_simulate_open_pr_pressure CLI mirror, taking the count from 69 to 70.) import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { mkdtempSync, rmSync } from "node:fs"; @@ -55,14 +56,14 @@ describe("MCP legacy alias retirement (#4777) — discovery invariants", () => { }); afterEach(disconnect); - it("lists exactly 69 loopover_ tools and zero gittensory_-prefixed aliases", async () => { + it("lists exactly 70 loopover_ tools and zero gittensory_-prefixed aliases", async () => { const { tools } = await client.listTools(); const names = tools.map((t) => t.name); const primary = names.filter((n) => n.startsWith("loopover_")); const legacy = names.filter((n) => n.startsWith("gittensory_")); - expect(primary.length).toBe(69); + expect(primary.length).toBe(70); expect(legacy.length).toBe(0); - expect(names.length).toBe(69); + expect(names.length).toBe(70); }); it("no loopover_ tool's description carries a stale deprecation notice", async () => { @@ -72,11 +73,11 @@ describe("MCP legacy alias retirement (#4777) — discovery invariants", () => { } }); - it("`loopover-mcp tools --json` reports the same 69-tool count the live server registers", async () => { + it("`loopover-mcp tools --json` reports the same 70-tool count the live server registers", async () => { const { tools } = await client.listTools(); const payload = JSON.parse(run(["tools", "--json"])) as { count: number; tools: Array<{ name: string }> }; expect(payload.count).toBe(tools.length); - expect(payload.count).toBe(69); + expect(payload.count).toBe(70); expect([...payload.tools.map((t) => t.name)].sort()).toEqual([...tools.map((t) => t.name)].sort()); }); }); diff --git a/test/unit/miner-migrate-cli.test.ts b/test/unit/miner-migrate-cli.test.ts index 631caa8617..2aa5f0c35d 100644 --- a/test/unit/miner-migrate-cli.test.ts +++ b/test/unit/miner-migrate-cli.test.ts @@ -24,6 +24,10 @@ const STORE_NAMES = [ "claim-ledger", "run-state", "plan-store", + "governor-state", + "attempt-log", + "replay-snapshot", + "worktree-allocator", ]; afterEach(() => { @@ -32,11 +36,13 @@ afterEach(() => { }); describe("loopover-miner migrate (#4871)", () => { - it("covers the exact same seven stores doctor's store-integrity sweep covers, in the same order, and skips every one when nothing has been created yet", () => { + it("covers the exact same eleven stores doctor's store-integrity sweep covers, in the same order, and skips every one when nothing has been created yet", () => { const env = tempEnv(); const results = runMigrateChecks(env); expect(results.map((result) => result.name)).toEqual(STORE_NAMES); + // REGRESSION (#6768): these four durable stores were previously omitted from both migrate and doctor. + expect(STORE_NAMES).toEqual(expect.arrayContaining(["governor-state", "attempt-log", "replay-snapshot", "worktree-allocator"])); for (const result of results) { expect(result.ok).toBe(true); expect(result.status).toBe("skipped"); diff --git a/test/unit/miner-status.test.ts b/test/unit/miner-status.test.ts index 1921d0b421..56f9ef5a19 100644 --- a/test/unit/miner-status.test.ts +++ b/test/unit/miner-status.test.ts @@ -125,7 +125,20 @@ describe("loopover-miner status/doctor (#2288)", () => { "store-integrity:claim-ledger", "store-integrity:run-state", "store-integrity:plan-store", + "store-integrity:governor-state", + "store-integrity:attempt-log", + "store-integrity:replay-snapshot", + "store-integrity:worktree-allocator", ]); + // REGRESSION (#6768): doctor previously omitted these four durable local stores from the integrity sweep. + expect(checks.map((check) => check.name)).toEqual( + expect.arrayContaining([ + "store-integrity:governor-state", + "store-integrity:attempt-log", + "store-integrity:replay-snapshot", + "store-integrity:worktree-allocator", + ]), + ); expect(runDoctor([], env, cwd)).toBe(0); expect(log).toHaveBeenCalled(); });