Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions packages/loopover-miner/lib/migrate-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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]";

Expand All @@ -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
Expand Down
11 changes: 10 additions & 1 deletion packages/loopover-miner/lib/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)],
Expand All @@ -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));
}
Expand Down
11 changes: 6 additions & 5 deletions test/unit/mcp-tool-rename-aliases.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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());
});
});
Expand Down
8 changes: 7 additions & 1 deletion test/unit/miner-migrate-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const STORE_NAMES = [
"claim-ledger",
"run-state",
"plan-store",
"governor-state",
"attempt-log",
"replay-snapshot",
"worktree-allocator",
];

afterEach(() => {
Expand All @@ -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");
Expand Down
13 changes: 13 additions & 0 deletions test/unit/miner-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down