@adhisang/minecraft-modding-mcp 7.0.0-rc.3 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +3 -2
  3. package/dist/cache-registry.d.ts +16 -0
  4. package/dist/cache-registry.js +78 -10
  5. package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
  6. package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
  7. package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
  8. package/dist/entry-tools/batch-class-members-service.js +20 -6
  9. package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
  10. package/dist/entry-tools/batch-class-source-service.js +10 -0
  11. package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
  12. package/dist/entry-tools/compare-minecraft-service.js +65 -4
  13. package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
  14. package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
  15. package/dist/entry-tools/manage-cache-service.d.ts +2 -2
  16. package/dist/entry-tools/validate-project/cases/project-summary.js +71 -12
  17. package/dist/entry-tools/validate-project-service.d.ts +2 -2
  18. package/dist/index.js +37 -15
  19. package/dist/json-rpc-framing.d.ts +20 -0
  20. package/dist/json-rpc-framing.js +80 -7
  21. package/dist/mapping/loaders/tiny-maven.d.ts +9 -0
  22. package/dist/mapping/loaders/tiny-maven.js +10 -2
  23. package/dist/repo-downloader.js +13 -2
  24. package/dist/source/artifact-resolver.d.ts +14 -0
  25. package/dist/source/artifact-resolver.js +106 -12
  26. package/dist/source/class-source/members-builder.d.ts +7 -0
  27. package/dist/source/class-source/members-builder.js +4 -1
  28. package/dist/source/class-source.d.ts +9 -2
  29. package/dist/source/class-source.js +229 -26
  30. package/dist/source/indexer.js +69 -1
  31. package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
  32. package/dist/source/lifecycle/mapping-helpers.js +29 -3
  33. package/dist/source/lifecycle/runtime-check.d.ts +25 -0
  34. package/dist/source/lifecycle/runtime-check.js +68 -39
  35. package/dist/source/symbol-resolver.js +88 -0
  36. package/dist/source-jar-reader.d.ts +33 -0
  37. package/dist/source-jar-reader.js +58 -0
  38. package/dist/source-resolver.d.ts +7 -0
  39. package/dist/source-resolver.js +20 -5
  40. package/dist/source-service.d.ts +5 -0
  41. package/dist/source-service.js +7 -0
  42. package/dist/stdio-supervisor.js +193 -34
  43. package/dist/storage/db.d.ts +62 -2
  44. package/dist/storage/db.js +186 -21
  45. package/dist/storage/sqlite.d.ts +31 -1
  46. package/dist/storage/sqlite.js +125 -16
  47. package/dist/tool-guidance.js +4 -1
  48. package/dist/tool-schemas.d.ts +64 -52
  49. package/dist/tool-schemas.js +9 -7
  50. package/dist/types.d.ts +9 -0
  51. package/dist/v1-parity-schemas.js +36 -2
  52. package/dist/version-diff-service.d.ts +23 -0
  53. package/dist/version-diff-service.js +101 -0
  54. package/dist/version-service.d.ts +14 -0
  55. package/dist/version-service.js +45 -3
  56. package/dist/workspace-mapping-service.d.ts +8 -0
  57. package/dist/workspace-mapping-service.js +35 -7
  58. package/docs/README-ja.md +2 -0
  59. package/docs/tool-reference.md +55 -11
  60. package/package.json +1 -1
@@ -1928,8 +1928,10 @@ export class StdioSupervisor {
1928
1928
  message: detail
1929
1929
  });
1930
1930
  });
1931
+ const key = requestKey(entry.pending.id);
1932
+ let released = false;
1933
+ let attemptedRelease = false;
1931
1934
  this.runRecoveryStep("queue.dispatch_settle", () => {
1932
- const key = requestKey(entry.pending.id);
1933
1935
  // Only THIS instance may be settled here. A fault before forwardRequest
1934
1936
  // installed anything leaves the request re-queued by its own no-child
1935
1937
  // fallback (a later drain owns it) or already answered by the
@@ -1937,17 +1939,51 @@ export class StdioSupervisor {
1937
1939
  // different live request, which keeps its own guarantee.
1938
1940
  if (this.pendingRequests.get(key) !== entry.pending)
1939
1941
  return;
1940
- if (!this.releaseForwardedRequest(key, entry.pending))
1941
- return;
1942
- this.writeSyntheticReply(entry.pending, {
1943
- jsonrpc: "2.0",
1944
- id: entry.pending.id,
1945
- error: {
1946
- code: -32603,
1947
- message: `MCP supervisor failed to dispatch the queued request: ${detail}`
1948
- }
1949
- });
1942
+ attemptedRelease = true;
1943
+ released = this.releaseForwardedRequest(key, entry.pending);
1950
1944
  });
1945
+ if (attemptedRelease && !released) {
1946
+ // releaseForwardedRequest deletes the entry and records its tombstone
1947
+ // BEFORE it returns, so a throw part-way through (recordFinalityTombstone's
1948
+ // only throw site today is the debug-log call during tombstone
1949
+ // eviction) can still have taken the id away from its entry. `released`
1950
+ // cannot tell "declined" apart from "threw after mutating", so the id
1951
+ // is read back rather than assumed — mirrors
1952
+ // answerFaultedWorkerResponse's worker_message.release_verify.
1953
+ //
1954
+ // Gated on `attemptedRelease`, not merely `!released`: the identity
1955
+ // check above (`pendingRequests.get(key) !== entry.pending`) returns
1956
+ // early, WITHOUT calling releaseForwardedRequest, whenever this entry
1957
+ // was never installed into pendingRequests to begin with — which is
1958
+ // exactly what happens when forwardRequest's no-child fallback re-queues
1959
+ // this same entry (queue not full) or already answered it itself (queue
1960
+ // full) before throwing later in that same fallback (e.g. from
1961
+ // scheduleRestart). In either of those cases the id is either still
1962
+ // waiting for a real dispatch that will answer it for real later, or
1963
+ // already answered — and this verify step cannot distinguish "never
1964
+ // installed" from "installed, then removed by a throwing release" using
1965
+ // `pendingRequests.has(key)` alone. Running it anyway would record a
1966
+ // spurious tombstone and send a synthetic reply for a request that gets
1967
+ // (or already got) a real one, i.e. two replies for the same id.
1968
+ this.runRecoveryStep("queue.dispatch_settle_verify", () => {
1969
+ if (entry.pending.method === "initialize" || this.pendingRequests.has(key))
1970
+ return;
1971
+ this.recordFinalityTombstone(key, entry.pending.mode);
1972
+ released = true;
1973
+ });
1974
+ }
1975
+ if (released) {
1976
+ this.runRecoveryStep("queue.dispatch_reply", () => {
1977
+ this.writeSyntheticReply(entry.pending, {
1978
+ jsonrpc: "2.0",
1979
+ id: entry.pending.id,
1980
+ error: {
1981
+ code: -32603,
1982
+ message: `MCP supervisor failed to dispatch the queued request: ${detail}`
1983
+ }
1984
+ });
1985
+ });
1986
+ }
1951
1987
  return false;
1952
1988
  }
1953
1989
  }
@@ -1966,7 +2002,7 @@ export class StdioSupervisor {
1966
2002
  }
1967
2003
  catch (error) {
1968
2004
  log("error", "supervisor.worker_spawn_throw", {
1969
- message: error instanceof Error ? error.message : String(error)
2005
+ message: describeThrown(error)
1970
2006
  });
1971
2007
  this.handleStartupFailure(token, { code: null, signal: null });
1972
2008
  return;
@@ -2166,7 +2202,33 @@ export class StdioSupervisor {
2166
2202
  // cap allows. That successor arms a fresh startup watchdog and, on
2167
2203
  // ready, re-forwards the retained initialize (handleWorkerReady), so the
2168
2204
  // handshake gets a second chance instead of stalling.
2205
+ //
2206
+ // Replacing the generation does not by itself terminalize any OTHER
2207
+ // request forwarded to the old child: when its `exit` eventually fires,
2208
+ // `handleWorkerExit` finds `this.child` already pointing at the
2209
+ // successor and takes its early-return branch, skipping
2210
+ // failPendingRequestsOnWorkerExit entirely. So that call runs here
2211
+ // first, mirroring the precedent at handleWorkerProcessError, and BEFORE
2212
+ // recoverTimedOutWorker replaces the generation. It does not touch the
2213
+ // retained `initialize` itself: failPendingRequestsOnWorkerExit carves
2214
+ // out `this.initializeRequest`'s key, which is exactly the entry this
2215
+ // lifecycle is about to continue on the successor.
2216
+ //
2217
+ // Split into two independent recovery steps, deliberately: these are two
2218
+ // unrelated effects (terminalizing OTHER stranded requests, and replacing
2219
+ // the generation so the retained initialize gets a second chance), and
2220
+ // they must not share a fault boundary. Before failPendingRequestsOnWorkerExit
2221
+ // existed here, recoverTimedOutWorker was the only statement in this step
2222
+ // and ran unconditionally on any path that reached it. Running both in one
2223
+ // `runRecoveryStep` would let a throw inside failPendingRequestsOnWorkerExit
2224
+ // (e.g. its own timerClearer call faulting for some OTHER pending
2225
+ // request's deadline timer) silently swallow the call to
2226
+ // recoverTimedOutWorker that follows it in the same callback — silently
2227
+ // skipping the one guarantee this whole branch exists to provide.
2169
2228
  if (pending.method === "initialize") {
2229
+ this.runRecoveryStep("worker_message.initialize_recovery_fail_pending", () => {
2230
+ this.failPendingRequestsOnWorkerExit({ code: null, signal: null });
2231
+ });
2170
2232
  this.runRecoveryStep("worker_message.initialize_recovery", () => {
2171
2233
  this.recoverTimedOutWorker();
2172
2234
  });
@@ -2219,6 +2281,37 @@ export class StdioSupervisor {
2219
2281
  return;
2220
2282
  }
2221
2283
  log("warn", "supervisor.worker_stdin_error", { message: error.message });
2284
+ // This event fires only on child.stdin, which exists only once the child
2285
+ // has actually been spawned, but that does NOT collapse to a single
2286
+ // "always post-ready" case: handleWorkerReady's legacy-era replay of a
2287
+ // retained `initialize` (era === "legacy" with `this.initializeRequest`
2288
+ // set) forwards it to a freshly spawned successor WITHOUT calling
2289
+ // adoptActiveChild first — adoptActiveChild only runs once that
2290
+ // initialize's response actually comes back, or on the no-replay early
2291
+ // return. So a live child can have `child.stdin` while `this.childReady`
2292
+ // is still false, mirroring the fork handleWorkerProcessError already
2293
+ // makes on `wasReady`. Treating that window as post-ready would run
2294
+ // failPendingRequestsOnWorkerExit, which deliberately carves the retained
2295
+ // initialize's pending entry OUT of what it fails (so a later id reuse of
2296
+ // the completed initialize is not wrongly caught) — leaving the client's
2297
+ // `initialize` answered by nothing and re-replayed against every
2298
+ // successor forever if the stdin fault persists. Before this recovery
2299
+ // existed at all, a broken stdin left `this.child` pointing at a worker
2300
+ // nothing could ever write to again: scheduleRestart's own `this.child`
2301
+ // guard made every later restart attempt a permanent no-op, and any
2302
+ // request already forwarded to this child had nothing left that would
2303
+ // ever answer it.
2304
+ const wasReady = this.childReady;
2305
+ this.invalidateCurrentChild(child);
2306
+ this.beginTreeTermination(child);
2307
+ if (!wasReady) {
2308
+ this.handleStartupFailure(this.attemptToken, { code: null, signal: null });
2309
+ }
2310
+ else {
2311
+ this.consecutiveImmediateStandDowns = 0;
2312
+ this.failPendingRequestsOnWorkerExit({ code: null, signal: null });
2313
+ this.scheduleRestart(true);
2314
+ }
2222
2315
  }
2223
2316
  /**
2224
2317
  * Reassembles the worker's stderr into lines (the ready marker may be split
@@ -2379,13 +2472,17 @@ export class StdioSupervisor {
2379
2472
  if (this.isInitializationResponse(message)) {
2380
2473
  const id = getTrackedRequestId(message);
2381
2474
  let initializeMode;
2475
+ let initializeKey;
2476
+ let initializePending;
2382
2477
  if (id !== undefined) {
2383
- const key = requestKey(id);
2384
- initializeMode = this.pendingRequests.get(key)?.mode;
2385
- this.pendingRequests.delete(key);
2478
+ initializeKey = requestKey(id);
2479
+ initializePending = this.pendingRequests.get(initializeKey);
2480
+ initializeMode = initializePending?.mode;
2386
2481
  }
2387
2482
  initializeMode ??= this.modeForMessage(this.initializeRequest);
2388
2483
  if ("error" in message) {
2484
+ if (initializeKey !== undefined)
2485
+ this.pendingRequests.delete(initializeKey);
2389
2486
  if (!this.replayingInitialization && id !== undefined) {
2390
2487
  this.writeSyntheticReply({ id, era: this.era, mode: initializeMode }, buildLegacyJsonRpcError(id));
2391
2488
  const retainedIndex = this.queuedNotifications.findIndex((entry) => isRequest(entry) && requestKey(entry.id) === requestKey(id));
@@ -2401,6 +2498,8 @@ export class StdioSupervisor {
2401
2498
  return;
2402
2499
  }
2403
2500
  if (this.replayingInitialization) {
2501
+ if (initializeKey !== undefined)
2502
+ this.pendingRequests.delete(initializeKey);
2404
2503
  this.replayingInitialization = false;
2405
2504
  if (this.initializedNotification) {
2406
2505
  this.writeToWorker(child, this.initializedNotification);
@@ -2409,9 +2508,22 @@ export class StdioSupervisor {
2409
2508
  this.flushQueue();
2410
2509
  return;
2411
2510
  }
2511
+ // Mark the entry as settling and remove it BEFORE writeToClient, so a
2512
+ // throw there (or in the diagnostic write reporting that throw) still
2513
+ // leaves `settlingWorkerResponse` for `answerUndeliveredWorkerResponse`
2514
+ // to answer this id from later — mirrors the non-initialize response
2515
+ // path below, which does the same for every other successful reply.
2516
+ // Without this, the entry was gone and no marker recorded it, so a
2517
+ // faulted client write here permanently lost the reply to `initialize`.
2518
+ if (initializeKey !== undefined && initializePending) {
2519
+ this.settlingWorkerResponse = { key: initializeKey, snapshot: initializePending };
2520
+ }
2521
+ if (initializeKey !== undefined)
2522
+ this.pendingRequests.delete(initializeKey);
2412
2523
  this.clientInitialized = true;
2413
2524
  this.adoptActiveChild();
2414
2525
  this.writeToClient(message, initializeMode);
2526
+ this.settlingWorkerResponse = undefined;
2415
2527
  this.flushQueue();
2416
2528
  return;
2417
2529
  }
@@ -2446,13 +2558,19 @@ export class StdioSupervisor {
2446
2558
  // branch above, never here.
2447
2559
  this.settlingWorkerResponse = { key, snapshot: pending };
2448
2560
  }
2449
- if (pending?.deadlineTimer)
2450
- this.timerClearer(pending.deadlineTimer);
2561
+ // These two clears run BEFORE timerClearer: a validate-project id must
2562
+ // not stay "running" if the timer clear below throws. answerUndelivered-
2563
+ // WorkerResponse (which later answers this id via settlingWorkerResponse
2564
+ // on a writeToClient/drainQueue fault) never touches these two fields,
2565
+ // so leaving them ordered after a throwing call would strand the
2566
+ // barrier and every validate-project admitted behind it, permanently.
2451
2567
  if (pending?.toolName === "validate-project") {
2452
2568
  this.runningValidateKey = undefined;
2453
2569
  if (this.validateBarrierKey === key)
2454
2570
  this.validateBarrierKey = undefined;
2455
2571
  }
2572
+ if (pending?.deadlineTimer)
2573
+ this.timerClearer(pending.deadlineTimer);
2456
2574
  }
2457
2575
  }
2458
2576
  this.writeToClient(message, responseMode);
@@ -2558,7 +2676,12 @@ export class StdioSupervisor {
2558
2676
  // count to N and falsely escalate retryRecommendation to "report-bug".
2559
2677
  const pendingToolNames = [];
2560
2678
  for (const [key, pending] of this.pendingRequests.entries()) {
2561
- if (key === preservedInitializeKey)
2679
+ // Identity, not just id: a client may legally reuse `initialize`'s id
2680
+ // for a later request once initialize has completed. `initializeRequest`
2681
+ // (and so `preservedInitializeKey`) is retained past that point, so a
2682
+ // bare key match would treat the REUSED entry as the still-pending
2683
+ // initialize and skip it here — leaving it answered by nothing.
2684
+ if (key === preservedInitializeKey && pending.method === "initialize")
2562
2685
  continue;
2563
2686
  pendingToolNames.push(pending.toolName);
2564
2687
  }
@@ -2566,23 +2689,59 @@ export class StdioSupervisor {
2566
2689
  for (const [toolName, updated] of updatedByTool) {
2567
2690
  this.recentRestarts.set(toolName, updated);
2568
2691
  }
2692
+ // Each entry's cleanup and its reply run as two SEPARATE recovery steps,
2693
+ // not one bundled try around the whole loop body. Fault containment must
2694
+ // be per-entry, not just per-function: a throw from `this.timerClearer`
2695
+ // (or anything else in the cleanup half) for entry N must not abort the
2696
+ // `for` loop, or every entry after N in Map iteration order is left
2697
+ // completely unprocessed — not answered, and not cleared from
2698
+ // runningValidateKey/validateBarrierKey either. Since the old worker's
2699
+ // own `exit`/`error` handling short-circuits once `this.child` already
2700
+ // points at a successor generation, a request stranded that way here is
2701
+ // stranded permanently, and a stranded validate-project holding the
2702
+ // barrier keys would jam every later validate-project request behind it
2703
+ // forever. Splitting into two steps also means a fault in ONE entry's
2704
+ // cleanup cannot suppress that SAME entry's own reply.
2569
2705
  for (const [key, pending] of [...this.pendingRequests.entries()]) {
2570
- if (key === preservedInitializeKey)
2706
+ // See the identical guard above: a bare key match would also wrongly
2707
+ // skip a request that legally reused the completed initialize's id.
2708
+ if (key === preservedInitializeKey && pending.method === "initialize")
2571
2709
  continue;
2572
- if (pending.deadlineTimer)
2573
- this.timerClearer(pending.deadlineTimer);
2574
- if (this.runningValidateKey === key)
2575
- this.runningValidateKey = undefined;
2576
- if (this.validateBarrierKey === key)
2577
- this.validateBarrierKey = undefined;
2578
- // Forwarded entries stay in pendingRequests until writeSyntheticReply
2579
- // settles them (the FORWARDED pending is what entitles the id to a
2580
- // finality tombstone). Cancelled entries are already gone — the
2581
- // cancellation settled them terminally at admission.
2582
- const toolName = pending.toolName ?? "unknown";
2583
- const pruned = prunedByTool.get(toolName) ?? [];
2584
- const { reply } = buildWorkerRestartReply(pending, exit, now, pruned, { structuredRestartDisabled: STRUCTURED_RESTART_DISABLED });
2585
- this.writeSyntheticReply(pending, reply);
2710
+ this.runRecoveryStep("worker_exit.fail_pending_cleanup", () => {
2711
+ if (this.runningValidateKey === key)
2712
+ this.runningValidateKey = undefined;
2713
+ if (this.validateBarrierKey === key)
2714
+ this.validateBarrierKey = undefined;
2715
+ // Nil the field immediately after clearing, matching the convention
2716
+ // releaseForwardedRequest already uses. writeSyntheticReply (below,
2717
+ // in the SEPARATE fail_pending_reply step) also does
2718
+ // `if (pending.deadlineTimer) this.timerClearer(...)` before it
2719
+ // deletes the entry from pendingRequests. If this step cleared the
2720
+ // timer but left the field set, that second check would still be
2721
+ // true and writeSyntheticReply would attempt a REDUNDANT second
2722
+ // clear of the SAME already-cleared timer. A timerClearer that
2723
+ // faults on a repeat invocation for the same timer would then throw
2724
+ // BEFORE writeSyntheticReply's delete/tombstone — unlike a fault
2725
+ // strictly after deletion (which only loses that one reply), this
2726
+ // would leave the entry live in pendingRequests forever, with
2727
+ // nothing left to remove or answer it. Nilling here makes
2728
+ // writeSyntheticReply's own check false, so it never attempts that
2729
+ // second clear at all.
2730
+ if (pending.deadlineTimer) {
2731
+ this.timerClearer(pending.deadlineTimer);
2732
+ pending.deadlineTimer = undefined;
2733
+ }
2734
+ });
2735
+ this.runRecoveryStep("worker_exit.fail_pending_reply", () => {
2736
+ // Forwarded entries stay in pendingRequests until writeSyntheticReply
2737
+ // settles them (the FORWARDED pending is what entitles the id to a
2738
+ // finality tombstone). Cancelled entries are already gone — the
2739
+ // cancellation settled them terminally at admission.
2740
+ const toolName = pending.toolName ?? "unknown";
2741
+ const pruned = prunedByTool.get(toolName) ?? [];
2742
+ const { reply } = buildWorkerRestartReply(pending, exit, now, pruned, { structuredRestartDisabled: STRUCTURED_RESTART_DISABLED });
2743
+ this.writeSyntheticReply(pending, reply);
2744
+ });
2586
2745
  }
2587
2746
  }
2588
2747
  /**
@@ -2688,7 +2847,7 @@ export class StdioSupervisor {
2688
2847
  }
2689
2848
  catch (error) {
2690
2849
  this.eventWriter("warn", "supervisor.client_write_error", {
2691
- message: error instanceof Error ? error.message : String(error)
2850
+ message: describeThrown(error)
2692
2851
  });
2693
2852
  }
2694
2853
  }
@@ -1,5 +1,6 @@
1
- import Database from "./sqlite.js";
1
+ import Database, { isRawSqliteCorruptionError } from "./sqlite.js";
2
2
  import type { Config } from "../types.js";
3
+ export { isRawSqliteCorruptionError };
3
4
  type SqliteDatabase = InstanceType<typeof Database>;
4
5
  type Logger = {
5
6
  warn: (message: string, details?: Record<string, unknown>) => void;
@@ -11,5 +12,64 @@ export interface InitializedDatabase {
11
12
  schemaVersion: number;
12
13
  }
13
14
  type DatabaseConfig = Pick<Config, "sqlitePath"> & Partial<Pick<Config, "sqliteCacheKb" | "sqliteMmapSize">>;
15
+ /**
16
+ * Best-effort request that the NEXT `openDatabase` for `sqlitePath` run the
17
+ * full `PRAGMA integrity_check` instead of the default `quick_check`. Creates
18
+ * a new, uniquely-named request file (see the block comment above) with
19
+ * exclusive-create so it can never collide with or overwrite another
20
+ * pending request. Never throws: a failure to write the file only means the
21
+ * escalation is missed, which is logged rather than allowed to mask the
22
+ * caller's real error. Returns whether the file was actually written, so a
23
+ * caller that cannot schedule the follow-up check can say so instead of
24
+ * promising one.
25
+ */
26
+ export declare function requestFullIntegrityCheck(sqlitePath: string, logger?: Logger): boolean;
27
+ /**
28
+ * Lists the full paths of every currently pending full-integrity-check
29
+ * request file for `sqlitePath`. Best-effort: a missing or unreadable
30
+ * directory is treated as "no requests pending" rather than as an error,
31
+ * since the fallback (a quick_check) is always safe to run.
32
+ */
33
+ export declare function listIntegrityCheckRequestFiles(sqlitePath: string): string[];
34
+ /**
35
+ * Deletes EXACTLY the given request file paths - the ones an earlier
36
+ * `listIntegrityCheckRequestFiles` call returned, never a fresh listing - so
37
+ * a request file created after that decision point is never deleted by a
38
+ * clear that only ever meant to honor the requests it actually acted on.
39
+ * Best-effort per file: ENOENT (already gone) is ignored, anything else is
40
+ * logged, not thrown.
41
+ *
42
+ * Exported so a unit test can exercise the race guard directly:
43
+ * `openDatabase` lists-then-clears synchronously with no `await` in between,
44
+ * so there is no way to interleave a concurrent request file appearing
45
+ * between "decide" and "clear" from outside that call.
46
+ */
47
+ export declare function clearIntegrityCheckRequestFiles(requestFiles: string[], logger?: Logger): void;
48
+ /**
49
+ * Wires a Database instance's corruption observer (see sqlite.ts) so that a
50
+ * raw corruption error thrown by ANY later statement on this handle - no
51
+ * matter what catches and wraps it further up the call stack, including a
52
+ * tool-specific error wrapper that never reaches runTool's own catch block -
53
+ * still schedules the next open's full integrity_check. Exported so a unit
54
+ * test can attach the same wiring to a Database instance opened directly
55
+ * (bypassing openDatabase's own open-time check, which would otherwise catch
56
+ * a test's injected corruption before a "successfully opened" handle ever
57
+ * existed to query against).
58
+ */
59
+ export declare function attachRuntimeCorruptionObserver(db: SqliteDatabase, sqlitePath: string, logger?: Logger): void;
60
+ /**
61
+ * runTool catch-path helper: when `caughtError` is a raw SQLite corruption
62
+ * error observed while serving a tool call (not at open time - open-time
63
+ * corruption is already an AppError by the time it reaches here), this
64
+ * requests a full integrity check on the next open and returns a typed
65
+ * ERR_DB_FAILURE the caller should report instead of the raw error. Any other
66
+ * error is returned unchanged so non-corruption failures are unaffected.
67
+ *
68
+ * The marker request here is redundant with the Database corruption observer
69
+ * (attachRuntimeCorruptionObserver) for an UNWRAPPED raw error, since that
70
+ * observer already fired deeper in the call stack; it is kept because
71
+ * `convertRuntimeSqliteCorruption` is the only place with reliable access to
72
+ * whether scheduling succeeded, which decides which `nextAction` to publish.
73
+ */
74
+ export declare function convertRuntimeSqliteCorruption(error: unknown, sqlitePath: string, logger?: Logger): unknown;
14
75
  export declare function openDatabase(config: DatabaseConfig, logger?: Logger): InitializedDatabase;
15
- export {};