@cortexkit/magic-context 0.36.0 → 0.36.1

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 (2) hide show
  1. package/dist/index.js +2813 -560
  2. package/package.json +57 -56
package/dist/index.js CHANGED
@@ -11569,8 +11569,156 @@ var init_opencode = __esm(() => {
11569
11569
  import_comment_json = __toESM(require_src3(), 1);
11570
11570
  });
11571
11571
 
11572
- // src/lib/jsonc-config.ts
11572
+ // ../plugin/src/shared/jsonc-parser.ts
11573
11573
  import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
11574
+ function stripJsonComments(content) {
11575
+ let result = "";
11576
+ let inString = false;
11577
+ let escaped = false;
11578
+ let inLineComment = false;
11579
+ let inBlockComment = false;
11580
+ for (let index = 0;index < content.length; index += 1) {
11581
+ const char = content[index];
11582
+ const next = content[index + 1];
11583
+ if (inLineComment) {
11584
+ if (char === `
11585
+ `) {
11586
+ inLineComment = false;
11587
+ result += char;
11588
+ }
11589
+ continue;
11590
+ }
11591
+ if (inBlockComment) {
11592
+ if (char === "*" && next === "/") {
11593
+ inBlockComment = false;
11594
+ index += 1;
11595
+ }
11596
+ continue;
11597
+ }
11598
+ if (inString) {
11599
+ result += char;
11600
+ if (escaped) {
11601
+ escaped = false;
11602
+ } else if (char === "\\") {
11603
+ escaped = true;
11604
+ } else if (char === '"') {
11605
+ inString = false;
11606
+ }
11607
+ continue;
11608
+ }
11609
+ if (char === '"') {
11610
+ inString = true;
11611
+ result += char;
11612
+ continue;
11613
+ }
11614
+ if (char === "/" && next === "/") {
11615
+ inLineComment = true;
11616
+ index += 1;
11617
+ continue;
11618
+ }
11619
+ if (char === "/" && next === "*") {
11620
+ inBlockComment = true;
11621
+ index += 1;
11622
+ continue;
11623
+ }
11624
+ result += char;
11625
+ }
11626
+ return result;
11627
+ }
11628
+ function stripTrailingCommas(content) {
11629
+ let result = "";
11630
+ let inString = false;
11631
+ let escaped = false;
11632
+ for (let index = 0;index < content.length; index += 1) {
11633
+ const char = content[index];
11634
+ if (inString) {
11635
+ result += char;
11636
+ if (escaped) {
11637
+ escaped = false;
11638
+ } else if (char === "\\") {
11639
+ escaped = true;
11640
+ } else if (char === '"') {
11641
+ inString = false;
11642
+ }
11643
+ continue;
11644
+ }
11645
+ if (char === '"') {
11646
+ inString = true;
11647
+ result += char;
11648
+ continue;
11649
+ }
11650
+ if (char === ",") {
11651
+ let lookahead = index + 1;
11652
+ while (lookahead < content.length && /\s/.test(content[lookahead] ?? "")) {
11653
+ lookahead += 1;
11654
+ }
11655
+ const next = content[lookahead];
11656
+ if (next === "}" || next === "]") {
11657
+ continue;
11658
+ }
11659
+ }
11660
+ result += char;
11661
+ }
11662
+ return result;
11663
+ }
11664
+ function isPrototypePollutionKey(key) {
11665
+ return PROTOTYPE_POLLUTION_KEYS.has(key);
11666
+ }
11667
+ function sanitizeParsedJson(value, options = {}, path2 = []) {
11668
+ if (Array.isArray(value)) {
11669
+ return value.map((entry, index) => sanitizeParsedJson(entry, options, [...path2, index]));
11670
+ }
11671
+ if (value === null || typeof value !== "object")
11672
+ return value;
11673
+ const source = value;
11674
+ const sourcePrototype = Object.getPrototypeOf(source);
11675
+ if (sourcePrototype !== null && sourcePrototype !== Object.prototype) {
11676
+ options.onRejectedKey?.([...path2, "__proto__"]);
11677
+ }
11678
+ const sanitized = {};
11679
+ for (const key of Object.keys(source)) {
11680
+ if (isPrototypePollutionKey(key)) {
11681
+ options.onRejectedKey?.([...path2, key]);
11682
+ continue;
11683
+ }
11684
+ Object.defineProperty(sanitized, key, {
11685
+ value: sanitizeParsedJson(source[key], options, [...path2, key]),
11686
+ enumerable: true,
11687
+ configurable: true,
11688
+ writable: true
11689
+ });
11690
+ }
11691
+ return sanitized;
11692
+ }
11693
+ function parseJsonc2(content, options = {}) {
11694
+ const normalized = stripTrailingCommas(stripJsonComments(content));
11695
+ return sanitizeParsedJson(JSON.parse(normalized), options);
11696
+ }
11697
+ function readJsoncFile(filePath) {
11698
+ try {
11699
+ return parseJsonc2(readFileSync6(filePath, "utf-8"));
11700
+ } catch (_error) {
11701
+ return null;
11702
+ }
11703
+ }
11704
+ function detectConfigFile(basePath) {
11705
+ const jsoncPath = `${basePath}.jsonc`;
11706
+ const jsonPath = `${basePath}.json`;
11707
+ if (existsSync9(jsoncPath)) {
11708
+ return { format: "jsonc", path: jsoncPath };
11709
+ }
11710
+ if (existsSync9(jsonPath)) {
11711
+ return { format: "json", path: jsonPath };
11712
+ }
11713
+ return { format: "none", path: jsoncPath };
11714
+ }
11715
+ var PROTOTYPE_POLLUTION_KEYS;
11716
+ var init_jsonc_parser = __esm(() => {
11717
+ PROTOTYPE_POLLUTION_KEYS = new Set(["__proto__", "constructor", "prototype"]);
11718
+ });
11719
+
11720
+ // src/lib/jsonc-config.ts
11721
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
11574
11722
  function parseErrorLocation(content, error) {
11575
11723
  const lines = content.split(`
11576
11724
  `);
@@ -11586,11 +11734,17 @@ function parseErrorLocation(content, error) {
11586
11734
  return { line: messageLine ? Number.parseInt(messageLine, 10) : 1, column: 1 };
11587
11735
  }
11588
11736
  function readJsoncConfig(path2) {
11589
- if (!existsSync9(path2))
11737
+ if (!existsSync10(path2))
11590
11738
  return { kind: "missing" };
11591
- const content = readFileSync6(path2, "utf-8");
11739
+ const content = readFileSync7(path2, "utf-8");
11592
11740
  try {
11593
- const parsed = import_comment_json2.parse(content);
11741
+ const rejectedKeyPaths = [];
11742
+ const parsed = sanitizeParsedJson(import_comment_json2.parse(content), {
11743
+ onRejectedKey: (keyPath) => rejectedKeyPaths.push(keyPath.join("."))
11744
+ });
11745
+ if (rejectedKeyPaths.length > 0) {
11746
+ throw new Error(`unsafe prototype-pollution key at ${rejectedKeyPaths.join(", ")}`);
11747
+ }
11594
11748
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
11595
11749
  throw new Error("expected a JSON object at the document root");
11596
11750
  }
@@ -11616,6 +11770,7 @@ function assertJsoncConfigsParseable(paths) {
11616
11770
  }
11617
11771
  var import_comment_json2, ConfigParseError;
11618
11772
  var init_jsonc_config = __esm(() => {
11773
+ init_jsonc_parser();
11619
11774
  import_comment_json2 = __toESM(require_src3(), 1);
11620
11775
  ConfigParseError = class ConfigParseError extends Error {
11621
11776
  path;
@@ -11698,7 +11853,7 @@ function describePiPackageEntry(entry) {
11698
11853
  var PI_MAGIC_CONTEXT_PACKAGE_NAME = "@cortexkit/pi-magic-context";
11699
11854
 
11700
11855
  // src/adapters/pi.ts
11701
- import { existsSync as existsSync10 } from "node:fs";
11856
+ import { existsSync as existsSync11 } from "node:fs";
11702
11857
  import { dirname as dirname5 } from "node:path";
11703
11858
 
11704
11859
  class PiAdapter {
@@ -11835,7 +11990,7 @@ function writePiSettings(settings2) {
11835
11990
  }
11836
11991
  function ensureDir2(filePath) {
11837
11992
  const dir = dirname5(filePath);
11838
- if (!existsSync10(dir)) {
11993
+ if (!existsSync11(dir)) {
11839
11994
  const { mkdirSync: mkdirSync4 } = __require("node:fs");
11840
11995
  mkdirSync4(dir, { recursive: true });
11841
11996
  }
@@ -12248,10 +12403,10 @@ var init_dreamer_setup = __esm(() => {
12248
12403
  });
12249
12404
 
12250
12405
  // src/commands/setup-pi.ts
12251
- import { existsSync as existsSync11, mkdirSync as mkdirSync4 } from "node:fs";
12406
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4 } from "node:fs";
12252
12407
  import { dirname as dirname6 } from "node:path";
12253
12408
  function ensureDir3(path2) {
12254
- if (!existsSync11(path2))
12409
+ if (!existsSync12(path2))
12255
12410
  mkdirSync4(path2, { recursive: true });
12256
12411
  }
12257
12412
  async function getDefaultPrompts() {
@@ -12689,122 +12844,6 @@ Edit those files directly, then rerun setup.`);
12689
12844
  };
12690
12845
  });
12691
12846
 
12692
- // ../plugin/src/shared/jsonc-parser.ts
12693
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "node:fs";
12694
- function stripJsonComments(content) {
12695
- let result = "";
12696
- let inString = false;
12697
- let escaped = false;
12698
- let inLineComment = false;
12699
- let inBlockComment = false;
12700
- for (let index = 0;index < content.length; index += 1) {
12701
- const char = content[index];
12702
- const next = content[index + 1];
12703
- if (inLineComment) {
12704
- if (char === `
12705
- `) {
12706
- inLineComment = false;
12707
- result += char;
12708
- }
12709
- continue;
12710
- }
12711
- if (inBlockComment) {
12712
- if (char === "*" && next === "/") {
12713
- inBlockComment = false;
12714
- index += 1;
12715
- }
12716
- continue;
12717
- }
12718
- if (inString) {
12719
- result += char;
12720
- if (escaped) {
12721
- escaped = false;
12722
- } else if (char === "\\") {
12723
- escaped = true;
12724
- } else if (char === '"') {
12725
- inString = false;
12726
- }
12727
- continue;
12728
- }
12729
- if (char === '"') {
12730
- inString = true;
12731
- result += char;
12732
- continue;
12733
- }
12734
- if (char === "/" && next === "/") {
12735
- inLineComment = true;
12736
- index += 1;
12737
- continue;
12738
- }
12739
- if (char === "/" && next === "*") {
12740
- inBlockComment = true;
12741
- index += 1;
12742
- continue;
12743
- }
12744
- result += char;
12745
- }
12746
- return result;
12747
- }
12748
- function stripTrailingCommas(content) {
12749
- let result = "";
12750
- let inString = false;
12751
- let escaped = false;
12752
- for (let index = 0;index < content.length; index += 1) {
12753
- const char = content[index];
12754
- if (inString) {
12755
- result += char;
12756
- if (escaped) {
12757
- escaped = false;
12758
- } else if (char === "\\") {
12759
- escaped = true;
12760
- } else if (char === '"') {
12761
- inString = false;
12762
- }
12763
- continue;
12764
- }
12765
- if (char === '"') {
12766
- inString = true;
12767
- result += char;
12768
- continue;
12769
- }
12770
- if (char === ",") {
12771
- let lookahead = index + 1;
12772
- while (lookahead < content.length && /\s/.test(content[lookahead] ?? "")) {
12773
- lookahead += 1;
12774
- }
12775
- const next = content[lookahead];
12776
- if (next === "}" || next === "]") {
12777
- continue;
12778
- }
12779
- }
12780
- result += char;
12781
- }
12782
- return result;
12783
- }
12784
- function parseJsonc3(content) {
12785
- const normalized = stripTrailingCommas(stripJsonComments(content));
12786
- return JSON.parse(normalized);
12787
- }
12788
- function readJsoncFile(filePath) {
12789
- try {
12790
- return parseJsonc3(readFileSync7(filePath, "utf-8"));
12791
- } catch (_error) {
12792
- return null;
12793
- }
12794
- }
12795
- function detectConfigFile(basePath) {
12796
- const jsoncPath = `${basePath}.jsonc`;
12797
- const jsonPath = `${basePath}.json`;
12798
- if (existsSync12(jsoncPath)) {
12799
- return { format: "jsonc", path: jsoncPath };
12800
- }
12801
- if (existsSync12(jsonPath)) {
12802
- return { format: "json", path: jsonPath };
12803
- }
12804
- return { format: "none", path: jsoncPath };
12805
- }
12806
- var init_jsonc_parser = () => {};
12807
-
12808
12847
  // ../plugin/src/shared/logger.ts
12809
12848
  import * as fs from "node:fs";
12810
12849
  import * as path2 from "node:path";
@@ -27899,7 +27938,9 @@ function isValidPromptSurfaceModelKey(key) {
27899
27938
  if (key.length === 0 || key.trim() !== key)
27900
27939
  return false;
27901
27940
  const slash = key.indexOf("/");
27902
- if (slash <= 0 || slash === key.length - 1)
27941
+ if (slash < 0)
27942
+ return !key.includes("*");
27943
+ if (slash === 0 || slash === key.length - 1)
27903
27944
  return false;
27904
27945
  const provider = key.slice(0, slash);
27905
27946
  const modelID = key.slice(slash + 1);
@@ -27971,14 +28012,14 @@ var init_magic_context = __esm(() => {
27971
28012
  }).optional();
27972
28013
  PromptSurfacePresetSchema = exports_external.enum(["full", "light"]);
27973
28014
  PromptSurfaceModelKeySchema = exports_external.string().refine(isValidPromptSurfaceModelKey, {
27974
- message: "Use a non-empty provider/model key or the literal provider/* wildcard; model IDs may contain additional slashes and matching is case-sensitive."
28015
+ message: "Use a non-empty bare model key, provider/model key, or the literal provider/* wildcard; model IDs may contain additional slashes and matching is case-sensitive."
27975
28016
  });
27976
28017
  PromptSurfaceToolKeySchema = exports_external.string().refine((value) => value.trim().length > 0, {
27977
28018
  message: "tool description keys must not be empty or whitespace-only"
27978
28019
  });
27979
28020
  PromptSurfaceConfigSchema = exports_external.object({
27980
28021
  default: PromptSurfacePresetSchema.default("full").describe('Fallback prompt-surface preset ("full" or "light").'),
27981
- models: exports_external.record(PromptSurfaceModelKeySchema, PromptSurfacePresetSchema).optional().describe("Literal per-model routing. Keys are provider/model or provider/*; matching is case-sensitive and preserves additional slashes in model IDs."),
28022
+ models: exports_external.record(PromptSurfaceModelKeySchema, PromptSurfacePresetSchema).optional().describe("Literal per-model routing. Keys are bare model IDs, provider/model, or provider/*; matching is case-sensitive and preserves additional slashes in model IDs."),
27982
28023
  guidance_override_path: exports_external.string().refine((value) => value.trim().length > 0, {
27983
28024
  message: "guidance_override_path must not be empty or whitespace-only"
27984
28025
  }).optional().describe("USER-LEVEL ONLY path to a complete primary guidance section. Relative paths resolve from the user config file."),
@@ -28155,7 +28196,7 @@ var init_magic_context = __esm(() => {
28155
28196
  retina_handoff: exports_external.boolean().default(false).describe("When true, dreamer skips smart notes whose surface conditions compiled to retina provider configs at authoring time. Default false keeps both paths active until the retina consumer is deployed.")
28156
28197
  }).default({ retina_handoff: false }).describe("Smart-note ownership transition controls."),
28157
28198
  cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m").describe('Cache TTL: string (e.g. "5m", "1h", "30s") or per-model object ({ default: "5m", "model-id": "10m" }). Set to "never" for lanes kept warm by an external keepwarm proxy — disables the idle-TTL heuristic so MC never initiates a rebuild based on elapsed time.'),
28158
- prompt_surface: PromptSurfaceConfigSchema.default({ default: "full" }).describe("Prompt-surface presets: default is full; models use the literal provider/model or provider/* routing grammar. Guidance and tool-description overrides are user-level only. On OpenCode and Pi, per-model routing applies to the guidance block only: tool descriptions are registered once per process, so they follow the default preset (a v1 plugin-surface limitation; per-model tool descriptions are planned for the OpenCode v2 plugin API once the SDK stabilizes)."),
28199
+ prompt_surface: PromptSurfaceConfigSchema.default({ default: "full" }).describe("Prompt-surface presets: default is full; models use bare model IDs, provider/model, or provider/* routing keys. Guidance and tool-description overrides are user-level only. On OpenCode and Pi, per-model routing applies to the guidance block only: tool descriptions are registered once per process, so they follow the default preset (a v1 plugin-surface limitation; per-model tool descriptions are planned for the OpenCode v2 plugin API once the SDK stabilizes)."),
28159
28200
  output_reserve: exports_external.union([
28160
28201
  exports_external.number().min(0),
28161
28202
  exports_external.object({ default: exports_external.number().min(0) }).catchall(exports_external.number().min(0))
@@ -28780,10 +28821,15 @@ function loadConfigFileDetailed(configPath, source) {
28780
28821
  configPath,
28781
28822
  isProjectConfig: source === "project"
28782
28823
  });
28824
+ const rejectedKeyPaths = [];
28825
+ const config2 = parseJsonc2(substituted.text, {
28826
+ onRejectedKey: (path3) => rejectedKeyPaths.push(path3.join("."))
28827
+ });
28828
+ const unsafeKeyWarnings = rejectedKeyPaths.map((path3) => `Ignored unsafe config key "${path3}" (security: prototype-pollution keys are not allowed).`);
28783
28829
  return {
28784
- config: parseJsonc3(substituted.text),
28785
- warnings: substituted.warnings.map((w) => `${configPath}: ${w}`),
28786
- outcome: substituted.warnings.length > 0 ? "substitution-failure" : "ok",
28830
+ config: config2,
28831
+ warnings: [...substituted.warnings, ...unsafeKeyWarnings].map((warning) => `${configPath}: ${warning}`),
28832
+ outcome: rejectedKeyPaths.length > 0 ? "schema-recovery" : substituted.warnings.length > 0 ? "substitution-failure" : "ok",
28787
28833
  source
28788
28834
  };
28789
28835
  } catch (error51) {
@@ -28797,18 +28843,35 @@ function loadConfigFileDetailed(configPath, source) {
28797
28843
  };
28798
28844
  }
28799
28845
  }
28846
+ function defineOwnConfigValue(target, key, value) {
28847
+ Object.defineProperty(target, key, {
28848
+ value,
28849
+ enumerable: true,
28850
+ configurable: true,
28851
+ writable: true
28852
+ });
28853
+ }
28800
28854
  function deepMergeRawConfig(base, override) {
28801
- const result = { ...base };
28855
+ const result = {};
28856
+ for (const key of Object.keys(base)) {
28857
+ if (isPrototypePollutionKey(key))
28858
+ continue;
28859
+ defineOwnConfigValue(result, key, base[key]);
28860
+ }
28802
28861
  for (const key of Object.keys(override)) {
28803
- const baseVal = base[key];
28862
+ if (isPrototypePollutionKey(key))
28863
+ continue;
28864
+ const baseVal = Object.hasOwn(base, key) ? base[key] : undefined;
28804
28865
  const overrideVal = override[key];
28866
+ let mergedValue;
28805
28867
  if (baseVal !== null && typeof baseVal === "object" && !Array.isArray(baseVal) && overrideVal !== null && typeof overrideVal === "object" && !Array.isArray(overrideVal)) {
28806
- result[key] = deepMergeRawConfig(baseVal, overrideVal);
28868
+ mergedValue = deepMergeRawConfig(baseVal, overrideVal);
28807
28869
  } else if (key === "disabled_hooks" && Array.isArray(baseVal) && Array.isArray(overrideVal)) {
28808
- result[key] = [...new Set([...baseVal, ...overrideVal])];
28870
+ mergedValue = [...new Set([...baseVal, ...overrideVal])];
28809
28871
  } else {
28810
- result[key] = overrideVal;
28872
+ mergedValue = overrideVal;
28811
28873
  }
28874
+ defineOwnConfigValue(result, key, mergedValue);
28812
28875
  }
28813
28876
  return result;
28814
28877
  }
@@ -29313,30 +29376,242 @@ var init_conflict_detector = __esm(() => {
29313
29376
  OMO_PACKAGE_NAMES = new Set(["oh-my-opencode", "oh-my-openagent"]);
29314
29377
  });
29315
29378
 
29316
- // ../plugin/src/shared/conflict-fixer.ts
29317
- import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
29318
- import { homedir as homedir11 } from "node:os";
29319
- import { join as join12 } from "node:path";
29320
- function isRecord(value) {
29321
- return typeof value === "object" && value !== null && !Array.isArray(value);
29322
- }
29323
- function asStringArray(value) {
29324
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
29325
- }
29379
+ // ../plugin/src/shared/jsonc-edit.ts
29380
+ import {
29381
+ applyEdits,
29382
+ createScanner,
29383
+ findNodeAtLocation,
29384
+ getNodeValue,
29385
+ modify,
29386
+ parseTree
29387
+ } from "jsonc-parser";
29388
+ function parseDocument(text3) {
29389
+ const errors3 = [];
29390
+ const root = parseTree(text3, errors3, { allowTrailingComma: true });
29391
+ if (!root || errors3.length > 0) {
29392
+ throw new Error("Cannot edit invalid JSONC");
29393
+ }
29394
+ return root;
29395
+ }
29396
+ function findNode(text3, path3) {
29397
+ return findNodeAtLocation(parseDocument(text3), path3);
29398
+ }
29399
+ function findComma(text3, start, end) {
29400
+ const scanner = createScanner(text3, false);
29401
+ scanner.setPosition(start);
29402
+ for (;; ) {
29403
+ const kind = scanner.scan();
29404
+ const offset = scanner.getTokenOffset();
29405
+ if (kind === TOKEN_EOF || offset >= end)
29406
+ return;
29407
+ if (kind === TOKEN_COMMA) {
29408
+ return {
29409
+ offset,
29410
+ length: scanner.getTokenLength(),
29411
+ line: scanner.getTokenStartLine()
29412
+ };
29413
+ }
29414
+ }
29415
+ }
29416
+ function lineStart(text3, offset) {
29417
+ const previousNewline = text3.lastIndexOf(`
29418
+ `, offset - 1);
29419
+ return previousNewline === -1 ? 0 : previousNewline + 1;
29420
+ }
29421
+ function lineEnd(text3, offset) {
29422
+ const nextNewline = text3.indexOf(`
29423
+ `, offset);
29424
+ if (nextNewline === -1)
29425
+ return text3.length;
29426
+ return text3[nextNewline - 1] === "\r" ? nextNewline - 1 : nextNewline;
29427
+ }
29428
+ function inlineCommentEnd(text3, start, line, limit) {
29429
+ const scanner = createScanner(text3, false);
29430
+ scanner.setPosition(start);
29431
+ let end;
29432
+ for (;; ) {
29433
+ const kind = scanner.scan();
29434
+ const offset = scanner.getTokenOffset();
29435
+ if (kind === TOKEN_EOF || offset >= limit || scanner.getTokenStartLine() !== line) {
29436
+ return end;
29437
+ }
29438
+ if (kind === TOKEN_LINE_COMMENT || kind === TOKEN_BLOCK_COMMENT) {
29439
+ end = offset + scanner.getTokenLength();
29440
+ }
29441
+ }
29442
+ }
29443
+ function startAfterPrecedingInlineComments(text3, comma, entryOffset) {
29444
+ const commentEnd = inlineCommentEnd(text3, comma.offset + comma.length, comma.line, entryOffset);
29445
+ if (commentEnd === undefined)
29446
+ return comma.offset + comma.length;
29447
+ const nextNewline = text3.indexOf(`
29448
+ `, commentEnd);
29449
+ return nextNewline === -1 ? commentEnd : nextNewline + 1;
29450
+ }
29451
+ function endAfterFollowingInlineComments(text3, comma, nextEntryOffset) {
29452
+ const commentEnd = inlineCommentEnd(text3, comma.offset + comma.length, comma.line, nextEntryOffset);
29453
+ return commentEnd === undefined ? comma.offset + comma.length : lineEnd(text3, commentEnd);
29454
+ }
29455
+ function closingWhitespace(text3, entryEnd, closingBracket) {
29456
+ const trailing = text3.slice(entryEnd, closingBracket);
29457
+ const finalNewline = trailing.lastIndexOf(`
29458
+ `);
29459
+ if (finalNewline === -1)
29460
+ return "";
29461
+ const newlineStart = trailing[finalNewline - 1] === "\r" ? finalNewline - 1 : finalNewline;
29462
+ return trailing.slice(newlineStart);
29463
+ }
29464
+ function removeArrayEntry(text3, array2, index) {
29465
+ const entries = array2.children ?? [];
29466
+ const entry = entries[index];
29467
+ if (!entry)
29468
+ return text3;
29469
+ const closingBracket = array2.offset + array2.length - 1;
29470
+ if (entries.length === 1) {
29471
+ const start2 = array2.offset + 1;
29472
+ const replacement = closingWhitespace(text3, entry.offset + entry.length, closingBracket);
29473
+ return text3.slice(0, start2) + replacement + text3.slice(closingBracket);
29474
+ }
29475
+ if (index === entries.length - 1) {
29476
+ const previous = entries[index - 1];
29477
+ if (!previous)
29478
+ return text3;
29479
+ const comma = findComma(text3, previous.offset + previous.length, entry.offset);
29480
+ if (!comma)
29481
+ return text3;
29482
+ const replacement = closingWhitespace(text3, entry.offset + entry.length, closingBracket);
29483
+ return text3.slice(0, comma.offset) + replacement + text3.slice(closingBracket);
29484
+ }
29485
+ const next = entries[index + 1];
29486
+ if (!next)
29487
+ return text3;
29488
+ const followingComma = findComma(text3, entry.offset + entry.length, next.offset);
29489
+ if (!followingComma)
29490
+ return text3;
29491
+ const start = index === 0 ? array2.offset + 1 : (() => {
29492
+ const previous = entries[index - 1];
29493
+ if (!previous)
29494
+ return entry.offset;
29495
+ const precedingComma = findComma(text3, previous.offset + previous.length, entry.offset);
29496
+ return precedingComma ? startAfterPrecedingInlineComments(text3, precedingComma, entry.offset) : entry.offset;
29497
+ })();
29498
+ const end = endAfterFollowingInlineComments(text3, followingComma, next.offset);
29499
+ return text3.slice(0, start) + text3.slice(end);
29500
+ }
29501
+ function indentationAt(text3, offset) {
29502
+ const prefix = text3.slice(lineStart(text3, offset), offset);
29503
+ return /^[\t ]*$/.test(prefix) ? prefix : "";
29504
+ }
29505
+ function inferIndent(text3, array2) {
29506
+ const firstEntry = array2.children?.[0];
29507
+ if (firstEntry)
29508
+ return indentationAt(text3, firstEntry.offset);
29509
+ const closingBracket = array2.offset + array2.length - 1;
29510
+ const closingIndent = indentationAt(text3, closingBracket);
29511
+ return `${closingIndent}${closingIndent.includes("\t") ? "\t" : " "}`;
29512
+ }
29513
+ function appendArrayValue(text3, array2, value) {
29514
+ const entries = array2.children ?? [];
29515
+ const closingBracket = array2.offset + array2.length - 1;
29516
+ const serialized = JSON.stringify(value);
29517
+ if (entries.length === 0) {
29518
+ if (!text3.slice(array2.offset, closingBracket).includes(`
29519
+ `)) {
29520
+ return text3.slice(0, closingBracket) + serialized + text3.slice(closingBracket);
29521
+ }
29522
+ const indent = inferIndent(text3, array2);
29523
+ const closingLineStart2 = lineStart(text3, closingBracket);
29524
+ const eol2 = text3.includes(`\r
29525
+ `) ? `\r
29526
+ ` : `
29527
+ `;
29528
+ return text3.slice(0, closingLineStart2) + indent + serialized + eol2 + text3.slice(closingLineStart2);
29529
+ }
29530
+ const lastEntry = entries.at(-1);
29531
+ if (!lastEntry)
29532
+ return text3;
29533
+ const trailingComma = findComma(text3, lastEntry.offset + lastEntry.length, closingBracket);
29534
+ const isMultiline = text3.slice(array2.offset, closingBracket).includes(`
29535
+ `);
29536
+ if (!isMultiline) {
29537
+ return text3.slice(0, lastEntry.offset + lastEntry.length) + `,${serialized}` + text3.slice(lastEntry.offset + lastEntry.length);
29538
+ }
29539
+ const closingLineStart = lineStart(text3, closingBracket);
29540
+ const eol = text3.includes(`\r
29541
+ `) ? `\r
29542
+ ` : `
29543
+ `;
29544
+ const inserted = `${inferIndent(text3, array2)}${serialized}${trailingComma ? "," : ""}${eol}`;
29545
+ const withValue = text3.slice(0, closingLineStart) + inserted + text3.slice(closingLineStart);
29546
+ if (trailingComma)
29547
+ return withValue;
29548
+ return withValue.slice(0, lastEntry.offset + lastEntry.length) + "," + withValue.slice(lastEntry.offset + lastEntry.length);
29549
+ }
29550
+ function setJsoncValue(text3, path3, value) {
29551
+ const node = findNode(text3, path3);
29552
+ if (node) {
29553
+ if (Object.is(getNodeValue(node), value))
29554
+ return text3;
29555
+ const serialized = JSON.stringify(value);
29556
+ return text3.slice(0, node.offset) + serialized + text3.slice(node.offset + node.length);
29557
+ }
29558
+ return applyEdits(text3, modify(text3, path3, value, {}));
29559
+ }
29560
+ function removeJsoncArrayEntries(text3, path3, shouldRemove) {
29561
+ let nextText = text3;
29562
+ let removed = false;
29563
+ for (;; ) {
29564
+ const array2 = findNode(nextText, path3);
29565
+ if (array2?.type !== "array")
29566
+ return { text: nextText, removed };
29567
+ const index = (array2.children ?? []).findIndex((entry) => shouldRemove(getNodeValue(entry)));
29568
+ if (index === -1)
29569
+ return { text: nextText, removed };
29570
+ const updated = removeArrayEntry(nextText, array2, index);
29571
+ if (updated === nextText)
29572
+ return { text: nextText, removed };
29573
+ nextText = updated;
29574
+ removed = true;
29575
+ }
29576
+ }
29577
+ function appendJsoncArrayValues(text3, path3, values) {
29578
+ let nextText = text3;
29579
+ for (const value of values) {
29580
+ const array2 = findNode(nextText, path3);
29581
+ if (array2?.type !== "array") {
29582
+ return setJsoncValue(nextText, path3, values);
29583
+ }
29584
+ nextText = appendArrayValue(nextText, array2, value);
29585
+ }
29586
+ return nextText;
29587
+ }
29588
+ var TOKEN_COMMA = 5, TOKEN_LINE_COMMENT = 12, TOKEN_BLOCK_COMMENT = 13, TOKEN_EOF = 17;
29589
+ var init_jsonc_edit = () => {};
29590
+
29591
+ // ../plugin/src/shared/conflict-fixer.ts
29592
+ import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
29593
+ import { homedir as homedir11 } from "node:os";
29594
+ import { join as join12 } from "node:path";
29595
+ function isRecord(value) {
29596
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29597
+ }
29598
+ function asStringArray(value) {
29599
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
29600
+ }
29326
29601
  function readConfig(filePath) {
29327
29602
  if (!existsSync15(filePath)) {
29328
29603
  return null;
29329
29604
  }
29330
29605
  try {
29331
- const parsed = import_comment_json5.parse(readFileSync10(filePath, "utf-8"));
29332
- return isRecord(parsed) ? parsed : null;
29606
+ const text3 = readFileSync10(filePath, "utf-8");
29607
+ const parsed = import_comment_json5.parse(text3);
29608
+ return isRecord(parsed) ? { config: parsed, text: text3 } : null;
29333
29609
  } catch {
29334
29610
  return null;
29335
29611
  }
29336
29612
  }
29337
- function writeConfig(filePath, config2) {
29338
- writeFileSync4(filePath, `${import_comment_json5.stringify(config2, null, 2)}
29339
- `);
29613
+ function writeConfig(filePath, text3) {
29614
+ writeFileSync4(filePath, text3);
29340
29615
  }
29341
29616
  function resolveUserOpenCodeConfigPath() {
29342
29617
  const paths = getOpenCodeConfigPaths({ binary: "opencode" });
@@ -29395,21 +29670,24 @@ function isUnifiedOmoPath(configPath) {
29395
29670
  const basename2 = configPath.split("/").pop() ?? "";
29396
29671
  return basename2 === "omo.jsonc" || basename2 === "omo.json";
29397
29672
  }
29398
- function getOrCreateOmoV2Block(config2) {
29399
- const block2 = config2["[opencode]"];
29400
- if (isRecord(block2)) {
29401
- return block2;
29673
+ function disableCompactionFlags(text3, config2) {
29674
+ if (!isRecord(config2.compaction)) {
29675
+ return {
29676
+ text: setJsoncValue(text3, ["compaction"], { auto: false, prune: false }),
29677
+ changed: true
29678
+ };
29402
29679
  }
29403
- const newBlock = {};
29404
- config2["[opencode]"] = newBlock;
29405
- return newBlock;
29406
- }
29407
- function filterDcpPluginEntries(entries) {
29408
- const plugins = entries.filter((entry) => {
29409
- const name = extractPluginName(entry);
29410
- return name ? !matchesPackageName(name, DCP_PACKAGE_NAMES) : true;
29411
- });
29412
- return { plugins, removed: plugins.length !== entries.length };
29680
+ let updated = text3;
29681
+ let changed = false;
29682
+ if (config2.compaction.auto !== false) {
29683
+ updated = setJsoncValue(updated, ["compaction", "auto"], false);
29684
+ changed = true;
29685
+ }
29686
+ if (config2.compaction.prune !== false) {
29687
+ updated = setJsoncValue(updated, ["compaction", "prune"], false);
29688
+ changed = true;
29689
+ }
29690
+ return { text: updated, changed };
29413
29691
  }
29414
29692
  function fixConflicts(directory, conflicts, options) {
29415
29693
  const compactionEnabled = options?.compactionEnabled ?? true;
@@ -29420,36 +29698,33 @@ function fixConflicts(directory, conflicts, options) {
29420
29698
  const repairCompaction = compactionEnabled && (conflicts.compactionAuto || conflicts.compactionPrune);
29421
29699
  if (repairCompaction || conflicts.dcpPlugin) {
29422
29700
  for (const configPath of collectOpenCodeConfigPaths(directory)) {
29423
- const config2 = readConfig(configPath);
29424
- if (!config2) {
29701
+ const document = readConfig(configPath);
29702
+ if (!document) {
29425
29703
  continue;
29426
29704
  }
29705
+ let text3 = document.text;
29427
29706
  let changed = false;
29428
29707
  if (repairCompaction) {
29429
- const compaction = isRecord(config2.compaction) ? config2.compaction : {};
29430
- if (compaction.auto !== false) {
29431
- compaction.auto = false;
29432
- changed = true;
29433
- updatedCompaction = true;
29434
- }
29435
- if (compaction.prune !== false) {
29436
- compaction.prune = false;
29708
+ const result = disableCompactionFlags(text3, document.config);
29709
+ if (result.changed) {
29710
+ text3 = result.text;
29437
29711
  changed = true;
29438
29712
  updatedCompaction = true;
29439
29713
  }
29440
- config2.compaction = compaction;
29441
29714
  }
29442
29715
  if (conflicts.dcpPlugin) {
29443
- const plugins = Array.isArray(config2.plugin) ? config2.plugin : [];
29444
- const filtered = filterDcpPluginEntries(plugins);
29445
- if (filtered.removed) {
29446
- config2.plugin = filtered.plugins;
29716
+ const result = removeJsoncArrayEntries(text3, ["plugin"], (entry) => {
29717
+ const name = extractPluginName(entry);
29718
+ return name ? matchesPackageName(name, DCP_PACKAGE_NAMES) : false;
29719
+ });
29720
+ if (result.removed) {
29721
+ text3 = result.text;
29447
29722
  changed = true;
29448
29723
  removedDcpPlugin = true;
29449
29724
  }
29450
29725
  }
29451
29726
  if (changed) {
29452
- writeConfig(configPath, config2);
29727
+ writeConfig(configPath, text3);
29453
29728
  }
29454
29729
  }
29455
29730
  }
@@ -29465,26 +29740,19 @@ function fixConflicts(directory, conflicts, options) {
29465
29740
  hooksToDisable.add("anthropic-context-window-limit-recovery");
29466
29741
  }
29467
29742
  for (const configPath of collectOmoConfigPaths(directory)) {
29468
- const config2 = readConfig(configPath);
29469
- if (!config2) {
29743
+ const document = readConfig(configPath);
29744
+ if (!document) {
29470
29745
  continue;
29471
29746
  }
29472
- const target = isUnifiedOmoPath(configPath) ? getOrCreateOmoV2Block(config2) : config2;
29747
+ const unifiedPath = isUnifiedOmoPath(configPath);
29748
+ const target = unifiedPath && isRecord(document.config["[opencode]"]) ? document.config["[opencode]"] : unifiedPath ? {} : document.config;
29473
29749
  const disabledHooks = new Set(asStringArray(target.disabled_hooks));
29474
- let changed = false;
29475
- for (const hook of hooksToDisable) {
29476
- if (!disabledHooks.has(hook)) {
29477
- disabledHooks.add(hook);
29478
- changed = true;
29479
- disabledOmoHooks = true;
29480
- }
29481
- }
29482
- if (changed) {
29483
- target.disabled_hooks = [
29484
- ...CONFLICTING_OMO_HOOKS.filter((hook) => disabledHooks.has(hook)),
29485
- ...[...disabledHooks].filter((hook) => !CONFLICTING_OMO_HOOKS.includes(hook))
29486
- ];
29487
- writeConfig(configPath, config2);
29750
+ const hooksToAdd = CONFLICTING_OMO_HOOKS.filter((hook) => hooksToDisable.has(hook) && !disabledHooks.has(hook));
29751
+ if (hooksToAdd.length > 0) {
29752
+ const path3 = unifiedPath ? ["[opencode]", "disabled_hooks"] : ["disabled_hooks"];
29753
+ const text3 = Array.isArray(target.disabled_hooks) ? appendJsoncArrayValues(document.text, path3, hooksToAdd) : setJsoncValue(document.text, path3, hooksToAdd);
29754
+ writeConfig(configPath, text3);
29755
+ disabledOmoHooks = true;
29488
29756
  }
29489
29757
  }
29490
29758
  }
@@ -29502,6 +29770,7 @@ function fixConflicts(directory, conflicts, options) {
29502
29770
  var import_comment_json5, CONFLICTING_OMO_HOOKS, OMO_CONFIG_NAMES, OMO_UNIFIED_NAMES;
29503
29771
  var init_conflict_fixer = __esm(() => {
29504
29772
  init_conflict_detector();
29773
+ init_jsonc_edit();
29505
29774
  init_opencode_config_dir();
29506
29775
  import_comment_json5 = __toESM(require_src3(), 1);
29507
29776
  CONFLICTING_OMO_HOOKS = [
@@ -29572,7 +29841,7 @@ var OPENCODE_BINARY_ENV = "MAGIC_CONTEXT_OPENCODE_BINARY", OPENCODE_VERSION_PROB
29572
29841
  var init_opencode_helpers = () => {};
29573
29842
 
29574
29843
  // src/commands/setup-opencode.ts
29575
- import { existsSync as existsSync16, mkdirSync as mkdirSync6 } from "node:fs";
29844
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync11 } from "node:fs";
29576
29845
  import { basename as basename2, dirname as dirname9 } from "node:path";
29577
29846
  function resolveCompactionEnabledForWriter() {
29578
29847
  try {
@@ -29591,47 +29860,81 @@ function ensureDir5(dir) {
29591
29860
  function addPluginToOpenCodeConfig(configPath, _format, removeDcp = false, compactionEnabled = true) {
29592
29861
  const existsAtCommit = existsSync16(configPath);
29593
29862
  const existing = existsAtCommit ? readJsoncConfigForUpdate(configPath) : {};
29594
- if (!existsAtCommit)
29863
+ if (!existsAtCommit) {
29595
29864
  ensureDir5(dirname9(configPath));
29596
- let rawPlugins = Array.isArray(existing.plugin) ? existing.plugin : [];
29597
- if (removeDcp) {
29598
- rawPlugins = rawPlugins.filter((plugin) => !matchesPluginEntry(plugin, DCP_PLUGIN_NAME));
29865
+ const created = { plugin: [OPENCODE_PLUGIN_ENTRY_WITH_VERSION] };
29866
+ if (compactionEnabled) {
29867
+ created.compaction = { auto: false, prune: false };
29868
+ }
29869
+ writeFileAtomic(configPath, `${import_comment_json6.stringify(created, null, 2)}
29870
+ `);
29871
+ return;
29599
29872
  }
29600
- const hasNpmEntry = rawPlugins.some((p2) => matchesPluginEntry(p2, OPENCODE_PLUGIN_NAME));
29601
- const hasDevEntry = rawPlugins.some((p2) => isDevPathPluginEntry(p2));
29602
- if (rawPlugins.some((p2) => isLocalPathPluginEntry(p2) && String(p2).includes("magic-context") && !isDevPathPluginEntry(p2))) {
29873
+ let text3 = readFileSync11(configPath, "utf-8");
29874
+ let changed = false;
29875
+ const rawPlugins = Array.isArray(existing.plugin) ? existing.plugin : [];
29876
+ const retainedPlugins = removeDcp ? rawPlugins.filter((plugin) => !matchesPluginEntry(plugin, DCP_PLUGIN_NAME)) : rawPlugins;
29877
+ if (retainedPlugins.some((plugin) => isLocalPathPluginEntry(plugin) && String(plugin).includes("magic-context") && !isDevPathPluginEntry(plugin))) {
29603
29878
  log2.warn("An unverifiable local OpenCode plugin path was ignored; its package name is not Magic Context.");
29604
29879
  }
29605
- if (!hasNpmEntry && !hasDevEntry) {
29606
- rawPlugins.push(OPENCODE_PLUGIN_ENTRY_WITH_VERSION);
29880
+ if (Array.isArray(existing.plugin)) {
29881
+ if (removeDcp) {
29882
+ const result = removeJsoncArrayEntries(text3, ["plugin"], (plugin) => matchesPluginEntry(plugin, DCP_PLUGIN_NAME));
29883
+ if (result.removed) {
29884
+ text3 = result.text;
29885
+ changed = true;
29886
+ }
29887
+ }
29888
+ const hasNpmEntry = retainedPlugins.some((plugin) => matchesPluginEntry(plugin, OPENCODE_PLUGIN_NAME));
29889
+ const hasDevEntry = retainedPlugins.some((plugin) => isDevPathPluginEntry(plugin));
29890
+ if (!hasNpmEntry && !hasDevEntry) {
29891
+ text3 = appendJsoncArrayValues(text3, ["plugin"], [OPENCODE_PLUGIN_ENTRY_WITH_VERSION]);
29892
+ changed = true;
29893
+ }
29894
+ } else {
29895
+ text3 = setJsoncValue(text3, ["plugin"], [OPENCODE_PLUGIN_ENTRY_WITH_VERSION]);
29896
+ changed = true;
29607
29897
  }
29608
- existing.plugin = rawPlugins;
29609
29898
  if (compactionEnabled) {
29610
- const compaction = existing.compaction ?? {};
29611
- compaction.auto = false;
29612
- compaction.prune = false;
29613
- existing.compaction = compaction;
29899
+ const compaction = existing.compaction;
29900
+ const hasCompactionObject = typeof compaction === "object" && compaction !== null && !Array.isArray(compaction);
29901
+ if (!hasCompactionObject) {
29902
+ text3 = setJsoncValue(text3, ["compaction"], { auto: false, prune: false });
29903
+ changed = true;
29904
+ } else {
29905
+ const fields = compaction;
29906
+ if (fields.auto !== false) {
29907
+ text3 = setJsoncValue(text3, ["compaction", "auto"], false);
29908
+ changed = true;
29909
+ }
29910
+ if (fields.prune !== false) {
29911
+ text3 = setJsoncValue(text3, ["compaction", "prune"], false);
29912
+ changed = true;
29913
+ }
29914
+ }
29614
29915
  }
29615
- writeFileAtomic(configPath, `${import_comment_json6.stringify(existing, null, 2)}
29616
- `);
29916
+ if (changed)
29917
+ writeFileAtomic(configPath, text3);
29617
29918
  }
29618
29919
  function addPluginToTuiConfig(configPath, _format) {
29619
29920
  const existsAtCommit = existsSync16(configPath);
29620
29921
  const existing = existsAtCommit ? readJsoncConfigForUpdate(configPath) : {};
29621
- if (!existsAtCommit)
29922
+ if (!existsAtCommit) {
29622
29923
  ensureDir5(dirname9(configPath));
29924
+ writeFileAtomic(configPath, `${import_comment_json6.stringify({ plugin: [OPENCODE_PLUGIN_ENTRY_WITH_VERSION] }, null, 2)}
29925
+ `);
29926
+ return;
29927
+ }
29623
29928
  const rawPlugins = Array.isArray(existing.plugin) ? existing.plugin : [];
29624
- const hasNpmEntry = rawPlugins.some((p2) => matchesPluginEntry(p2, OPENCODE_PLUGIN_NAME));
29625
- const hasDevEntry = rawPlugins.some((p2) => isDevPathPluginEntry(p2));
29626
- if (rawPlugins.some((p2) => isLocalPathPluginEntry(p2) && String(p2).includes("magic-context") && !isDevPathPluginEntry(p2))) {
29929
+ if (rawPlugins.some((plugin) => isLocalPathPluginEntry(plugin) && String(plugin).includes("magic-context") && !isDevPathPluginEntry(plugin))) {
29627
29930
  log2.warn("An unverifiable local TUI plugin path was ignored; its package name is not Magic Context.");
29628
29931
  }
29629
- if (!hasNpmEntry && !hasDevEntry) {
29630
- rawPlugins.push(OPENCODE_PLUGIN_ENTRY_WITH_VERSION);
29631
- }
29632
- existing.plugin = rawPlugins;
29633
- writeFileAtomic(configPath, `${import_comment_json6.stringify(existing, null, 2)}
29634
- `);
29932
+ const hasNpmEntry = rawPlugins.some((plugin) => matchesPluginEntry(plugin, OPENCODE_PLUGIN_NAME));
29933
+ const hasDevEntry = rawPlugins.some((plugin) => isDevPathPluginEntry(plugin));
29934
+ if (hasNpmEntry || hasDevEntry)
29935
+ return;
29936
+ const text3 = Array.isArray(existing.plugin) ? appendJsoncArrayValues(readFileSync11(configPath, "utf-8"), ["plugin"], [OPENCODE_PLUGIN_ENTRY_WITH_VERSION]) : setJsoncValue(readFileSync11(configPath, "utf-8"), ["plugin"], [OPENCODE_PLUGIN_ENTRY_WITH_VERSION]);
29937
+ writeFileAtomic(configPath, text3);
29635
29938
  }
29636
29939
  function findDcpPluginIndexes(plugins) {
29637
29940
  return plugins.map((plugin, index) => matchesPluginEntry(plugin, DCP_PLUGIN_NAME) ? index : -1).filter((index) => index >= 0);
@@ -29920,6 +30223,7 @@ var init_setup_opencode = __esm(() => {
29920
30223
  init_agent_disable();
29921
30224
  init_conflict_detector();
29922
30225
  init_conflict_fixer();
30226
+ init_jsonc_edit();
29923
30227
  init_opencode();
29924
30228
  init_atomic_write();
29925
30229
  init_config_location_migration();
@@ -30412,7 +30716,8 @@ function prepareMirrorPageStatements(db) {
30412
30716
  deleteNote: db.prepare("DELETE FROM notes WHERE id = ?"),
30413
30717
  deleteNoteRevisions: db.prepare("DELETE FROM mirror_note_revisions WHERE module_project = ? AND module_row_id = ?"),
30414
30718
  updateNote: db.prepare(`UPDATE notes SET type = ?, status = ?, project_path = ?, session_id = ?, content = ?,
30415
- surface_condition = ?, ready_at = ?, ready_reason = ?, manifest_json = ?, compiled_check = ?,
30719
+ surface_condition = ?, compiled_provider = ?, compiled_config = ?, compiled_at = ?, compile_status = ?,
30720
+ ready_at = ?, ready_reason = ?, manifest_json = ?, compiled_check = ?,
30416
30721
  check_hash = ?, check_cron = ?, check_failure_count = ?, check_network_failure_count = ?,
30417
30722
  check_quarantined_until = ?, check_next_due_at = ?, check_compiled_at = ?, check_false_since_at = ?,
30418
30723
  check_last_liveness_at = ?, last_checked_at = ?, check_status = ?, check_version = ?,
@@ -30668,7 +30973,7 @@ function applyNoteRow(db, feed, statements) {
30668
30973
  }
30669
30974
  const moduleStatus = rowString(effectiveRow, "status", "active");
30670
30975
  const contextStatus = moduleStatus === "surfaced" || moduleStatus === "surfacing" ? "ready" : moduleStatus;
30671
- statements.updateNote.run(rowString(effectiveRow, "type", "smart"), contextStatus, moduleProject, rowNullableString(effectiveRow, "session_id"), rowString(effectiveRow, "content"), rowNullableString(effectiveRow, "surface_condition"), typeof effectiveRow.ready_at === "number" ? effectiveRow.ready_at : null, rowNullableString(effectiveRow, "ready_reason"), rowNullableString(effectiveRow, "manifest_json"), rowNullableString(effectiveRow, "compiled_check"), rowNullableString(effectiveRow, "check_hash"), rowNullableString(effectiveRow, "check_cron"), rowNumber(effectiveRow, "check_failure_count"), rowNumber(effectiveRow, "check_network_failure_count"), typeof effectiveRow.check_quarantined_until === "number" ? effectiveRow.check_quarantined_until : null, typeof effectiveRow.check_next_due_at === "number" ? effectiveRow.check_next_due_at : null, typeof effectiveRow.check_compiled_at === "number" ? effectiveRow.check_compiled_at : null, typeof effectiveRow.check_false_since_at === "number" ? effectiveRow.check_false_since_at : null, typeof effectiveRow.check_last_liveness_at === "number" ? effectiveRow.check_last_liveness_at : null, typeof effectiveRow.last_checked_at === "number" ? effectiveRow.last_checked_at : null, rowString(effectiveRow, "check_status", "uncompiled"), rowNumber(effectiveRow, "check_version"), rowNumber(effectiveRow, "policy_version", 1), rowNullableString(effectiveRow, "anchor_block_id"), typeof effectiveRow.anchor_ordinal === "number" ? effectiveRow.anchor_ordinal : null, rowNumber(effectiveRow, "created_at_ms"), rowNumber(effectiveRow, "updated_at_ms"), contextId);
30976
+ statements.updateNote.run(rowString(effectiveRow, "type", "smart"), contextStatus, moduleProject, rowNullableString(effectiveRow, "session_id"), rowString(effectiveRow, "content"), rowNullableString(effectiveRow, "surface_condition"), rowNullableString(effectiveRow, "compiled_provider"), rowNullableString(effectiveRow, "compiled_config"), typeof effectiveRow.compiled_at === "number" ? effectiveRow.compiled_at : null, rowNullableString(effectiveRow, "compile_status"), typeof effectiveRow.ready_at === "number" ? effectiveRow.ready_at : null, rowNullableString(effectiveRow, "ready_reason"), rowNullableString(effectiveRow, "manifest_json"), rowNullableString(effectiveRow, "compiled_check"), rowNullableString(effectiveRow, "check_hash"), rowNullableString(effectiveRow, "check_cron"), rowNumber(effectiveRow, "check_failure_count"), rowNumber(effectiveRow, "check_network_failure_count"), typeof effectiveRow.check_quarantined_until === "number" ? effectiveRow.check_quarantined_until : null, typeof effectiveRow.check_next_due_at === "number" ? effectiveRow.check_next_due_at : null, typeof effectiveRow.check_compiled_at === "number" ? effectiveRow.check_compiled_at : null, typeof effectiveRow.check_false_since_at === "number" ? effectiveRow.check_false_since_at : null, typeof effectiveRow.check_last_liveness_at === "number" ? effectiveRow.check_last_liveness_at : null, typeof effectiveRow.last_checked_at === "number" ? effectiveRow.last_checked_at : null, rowString(effectiveRow, "check_status", "uncompiled"), rowNumber(effectiveRow, "check_version"), rowNumber(effectiveRow, "policy_version", 1), rowNullableString(effectiveRow, "anchor_block_id"), typeof effectiveRow.anchor_ordinal === "number" ? effectiveRow.anchor_ordinal : null, rowNumber(effectiveRow, "created_at_ms"), rowNumber(effectiveRow, "updated_at_ms"), contextId);
30672
30977
  statements.upsertNoteRevision.run(moduleProject, feed.module_row_id, contextId, rowNumber(effectiveRow, "status_version"));
30673
30978
  }
30674
30979
  function applyMirrorPage(args) {
@@ -33368,10 +33673,13 @@ class SubcModuleTransport {
33368
33673
  routeSessionPrefix;
33369
33674
  client = null;
33370
33675
  routes = new Map;
33676
+ routeOpenings = new Map;
33371
33677
  canonicalRootCache = new Map;
33372
- activeSession = null;
33678
+ sessionLanes = new Map;
33679
+ queuedLaneWaiters = 0;
33680
+ wrapupSessions = new Map;
33373
33681
  nextProbeMs = 0;
33374
- laneReleaseCallbacks = [];
33682
+ connectionPromise = null;
33375
33683
  authorityProjectRoot = "";
33376
33684
  authorityBindRoot = "";
33377
33685
  backoffMs = CONNECT_BACKOFF_INITIAL_MS;
@@ -33417,6 +33725,11 @@ class SubcModuleTransport {
33417
33725
  laneTimeoutError() {
33418
33726
  return this.deadlineError("while queued");
33419
33727
  }
33728
+ connectionChangedError(detail) {
33729
+ const error51 = new Error(detail);
33730
+ error51.code = "ECONNRESET";
33731
+ return error51;
33732
+ }
33420
33733
  async beforeDeadline(operation, deadlineMs, detail) {
33421
33734
  const remainingMs = deadlineMs - Date.now();
33422
33735
  if (remainingMs <= 0)
@@ -33434,22 +33747,30 @@ class SubcModuleTransport {
33434
33747
  clearTimeout(timer);
33435
33748
  }
33436
33749
  }
33437
- laneRelease() {
33750
+ cleanupLane(sessionId, lane) {
33751
+ if (!lane.active && lane.waiters.length === 0 && this.sessionLanes.get(sessionId) === lane) {
33752
+ this.sessionLanes.delete(sessionId);
33753
+ }
33754
+ }
33755
+ laneRelease(sessionId, lane) {
33438
33756
  let released = false;
33439
33757
  return () => {
33440
33758
  if (released)
33441
33759
  return;
33442
33760
  released = true;
33443
- this.activeSession = null;
33444
- this.dispatchNextLaneWaiter();
33761
+ lane.active = false;
33762
+ this.dispatchNextLaneWaiter(sessionId, lane);
33445
33763
  };
33446
33764
  }
33447
- dispatchNextLaneWaiter() {
33448
- if (this.activeSession !== null)
33765
+ dispatchNextLaneWaiter(sessionId, lane) {
33766
+ if (lane.active)
33449
33767
  return;
33450
- while (this.laneReleaseCallbacks.length > 0) {
33451
- const waiter = this.laneReleaseCallbacks.shift();
33452
- if (!waiter || waiter.settled)
33768
+ while (lane.waiters.length > 0) {
33769
+ const waiter = lane.waiters.shift();
33770
+ if (!waiter)
33771
+ continue;
33772
+ this.queuedLaneWaiters = Math.max(0, this.queuedLaneWaiters - 1);
33773
+ if (waiter.settled)
33453
33774
  continue;
33454
33775
  waiter.settled = true;
33455
33776
  clearTimeout(waiter.timer);
@@ -33462,10 +33783,16 @@ class SubcModuleTransport {
33462
33783
  waiter.reject(this.laneTimeoutError());
33463
33784
  continue;
33464
33785
  }
33465
- this.activeSession = waiter.sessionId;
33466
- waiter.resolve(this.laneRelease());
33786
+ lane.active = true;
33787
+ waiter.resolve(this.laneRelease(sessionId, lane));
33467
33788
  return;
33468
33789
  }
33790
+ this.cleanupLane(sessionId, lane);
33791
+ }
33792
+ queueFullError() {
33793
+ const error51 = new Error("module transport queue is full");
33794
+ error51.code = "EBUSY";
33795
+ return error51;
33469
33796
  }
33470
33797
  acquireCorrectnessLane(sessionId, signal, deadlineMs) {
33471
33798
  if (signal?.aborted) {
@@ -33474,14 +33801,14 @@ class SubcModuleTransport {
33474
33801
  if (deadlineMs - Date.now() < SERIAL_LANE_MIN_REMAINING_MS) {
33475
33802
  return Promise.reject(this.laneTimeoutError());
33476
33803
  }
33477
- if (this.activeSession === null && this.laneReleaseCallbacks.length === 0) {
33478
- this.activeSession = sessionId;
33479
- return Promise.resolve(this.laneRelease());
33804
+ const lane = this.sessionLanes.get(sessionId) ?? { active: false, waiters: [] };
33805
+ this.sessionLanes.set(sessionId, lane);
33806
+ if (!lane.active && lane.waiters.length === 0) {
33807
+ lane.active = true;
33808
+ return Promise.resolve(this.laneRelease(sessionId, lane));
33480
33809
  }
33481
- if (this.laneReleaseCallbacks.length >= SERIAL_LANE_MAX_WAITERS) {
33482
- const error51 = new Error("module transport queue is full");
33483
- error51.code = "EBUSY";
33484
- return Promise.reject(error51);
33810
+ if (this.queuedLaneWaiters >= SERIAL_LANE_MAX_WAITERS || lane.waiters.length >= SERIAL_LANE_MAX_WAITERS_PER_SESSION) {
33811
+ return Promise.reject(this.queueFullError());
33485
33812
  }
33486
33813
  return new Promise((resolve5, reject) => {
33487
33814
  const waiter = {};
@@ -33491,14 +33818,14 @@ class SubcModuleTransport {
33491
33818
  waiter.settled = true;
33492
33819
  clearTimeout(waiter.timer);
33493
33820
  signal?.removeEventListener("abort", waiter.onAbort);
33494
- const index = this.laneReleaseCallbacks.indexOf(waiter);
33495
- if (index >= 0)
33496
- this.laneReleaseCallbacks.splice(index, 1);
33821
+ const index = lane.waiters.indexOf(waiter);
33822
+ if (index >= 0) {
33823
+ lane.waiters.splice(index, 1);
33824
+ this.queuedLaneWaiters = Math.max(0, this.queuedLaneWaiters - 1);
33825
+ }
33497
33826
  reject(error51);
33498
- if (this.activeSession === null)
33499
- this.dispatchNextLaneWaiter();
33827
+ this.cleanupLane(sessionId, lane);
33500
33828
  };
33501
- waiter.sessionId = sessionId;
33502
33829
  waiter.signal = signal;
33503
33830
  waiter.deadlineMs = deadlineMs;
33504
33831
  waiter.resolve = resolve5;
@@ -33507,17 +33834,40 @@ class SubcModuleTransport {
33507
33834
  waiter.onAbort = () => removeAndReject(signal?.reason ?? new Error("module transport call aborted"));
33508
33835
  waiter.timer = setTimeout(() => removeAndReject(this.laneTimeoutError()), Math.max(0, deadlineMs - Date.now()));
33509
33836
  signal?.addEventListener("abort", waiter.onAbort, { once: true });
33510
- this.laneReleaseCallbacks.push(waiter);
33837
+ lane.waiters.push(waiter);
33838
+ this.queuedLaneWaiters += 1;
33511
33839
  });
33512
33840
  }
33513
33841
  async call(args) {
33514
- const attemptTimeoutMs = args.timeoutMs ?? (args.method === "transform" ? Math.min(this.requestTimeoutMs, TRANSFORM_SEND_TIMEOUT_MS) : this.requestTimeoutMs);
33842
+ const wrapupInFlight = (this.wrapupSessions.get(args.sessionId) ?? 0) > 0;
33843
+ const attemptTimeoutMs = args.timeoutMs ?? (args.method === "session.wrapup" || args.method === "session.status" && wrapupInFlight ? MAX_WRAPUP_REQUEST_BUDGET_MS : args.method === "transform" ? Math.min(this.requestTimeoutMs, TRANSFORM_SEND_TIMEOUT_MS) : this.requestTimeoutMs);
33844
+ const tracksWrapup = args.method === "session.wrapup";
33845
+ if (tracksWrapup) {
33846
+ this.wrapupSessions.set(args.sessionId, (this.wrapupSessions.get(args.sessionId) ?? 0) + 1);
33847
+ }
33848
+ const finishWrapupTracking = () => {
33849
+ if (!tracksWrapup)
33850
+ return;
33851
+ const remaining = (this.wrapupSessions.get(args.sessionId) ?? 1) - 1;
33852
+ if (remaining > 0)
33853
+ this.wrapupSessions.set(args.sessionId, remaining);
33854
+ else
33855
+ this.wrapupSessions.delete(args.sessionId);
33856
+ };
33515
33857
  const laneDeadlineMs = Date.now() + attemptTimeoutMs;
33516
- const releaseLane = await this.acquireCorrectnessLane(args.sessionId, args.signal, laneDeadlineMs);
33517
- const onAbort = () => this.invalidateConnection();
33858
+ let releaseLane;
33859
+ try {
33860
+ releaseLane = await this.acquireCorrectnessLane(args.sessionId, args.signal, laneDeadlineMs);
33861
+ } catch (error51) {
33862
+ finishWrapupTracking();
33863
+ throw error51;
33864
+ }
33865
+ let activeAttemptClient = null;
33866
+ const onAbort = () => this.invalidateConnection(activeAttemptClient ?? this.client);
33518
33867
  args.signal?.addEventListener("abort", onAbort, { once: true });
33519
33868
  try {
33520
33869
  for (let attempt = 0;attempt < 2; attempt += 1) {
33870
+ activeAttemptClient = null;
33521
33871
  let ensuredRoute = null;
33522
33872
  try {
33523
33873
  if (args.signal?.aborted) {
@@ -33525,22 +33875,35 @@ class SubcModuleTransport {
33525
33875
  }
33526
33876
  const attemptDeadlineMs = Date.now() + attemptTimeoutMs;
33527
33877
  ensuredRoute = await this.ensureRoute(args.sessionId, args.projectRoot, attemptDeadlineMs);
33878
+ activeAttemptClient = ensuredRoute.client;
33528
33879
  if (args.signal?.aborted) {
33529
33880
  throw args.signal.reason ?? new Error("module transport call aborted");
33530
33881
  }
33531
- return await this.beforeDeadline(ensuredRoute.client.request(ensuredRoute.route, args.body, {
33882
+ const response = await this.beforeDeadline(ensuredRoute.client.request(ensuredRoute.route, args.body, {
33532
33883
  priority: Priority.Background,
33533
33884
  admissionClass: AdmissionClass.Normal,
33534
33885
  timeoutMs: Math.max(1, attemptDeadlineMs - Date.now())
33535
33886
  }), attemptDeadlineMs, "waiting for the module response");
33887
+ if (this.client !== ensuredRoute.client || this.connectionGeneration !== ensuredRoute.generation) {
33888
+ throw this.connectionChangedError("subc connection changed while awaiting the module response");
33889
+ }
33890
+ return response;
33536
33891
  } catch (error51) {
33537
33892
  if (isConnectionFailure(error51)) {
33893
+ const previousGeneration = ensuredRoute?.generation ?? this.connectionGeneration;
33538
33894
  if (ensuredRoute) {
33539
33895
  this.dropRoute(ensuredRoute.routeKey, ensuredRoute.route);
33540
33896
  this.invalidateConnection(ensuredRoute.client);
33541
33897
  } else {
33542
33898
  this.invalidateConnection();
33543
33899
  }
33900
+ if (args.generationSensitive && !args.signal?.aborted) {
33901
+ return {
33902
+ transport_status: "connection_generation_changed",
33903
+ previous_generation: previousGeneration,
33904
+ current_generation: this.connectionGeneration
33905
+ };
33906
+ }
33544
33907
  if (attempt === 0 && !args.signal?.aborted)
33545
33908
  continue;
33546
33909
  }
@@ -33550,6 +33913,7 @@ class SubcModuleTransport {
33550
33913
  throw new Error("module transport route retry exhausted");
33551
33914
  } finally {
33552
33915
  args.signal?.removeEventListener("abort", onAbort);
33916
+ finishWrapupTracking();
33553
33917
  releaseLane();
33554
33918
  }
33555
33919
  }
@@ -33640,7 +34004,7 @@ class SubcModuleTransport {
33640
34004
  });
33641
34005
  }
33642
34006
  }
33643
- if (routes.length === 0 && this.activeSession === sessionId) {
34007
+ if (routes.length === 0 && this.sessionLanes.get(sessionId)?.active) {
33644
34008
  this.invalidateConnection(client);
33645
34009
  }
33646
34010
  }
@@ -33655,23 +34019,36 @@ class SubcModuleTransport {
33655
34019
  }
33656
34020
  if (existing)
33657
34021
  this.routes.delete(routeKey2);
33658
- const target = { kind: "tool_provider", module_id: this.moduleId };
33659
- const identity = {
33660
- project_root: projectRoot,
33661
- harness: getHarness(),
33662
- session: `${this.routeSessionPrefix}${sessionId}`
33663
- };
33664
- const route = await this.beforeDeadline(client.routeOpen(target, identity), deadlineMs, "opening the module route");
33665
- if (this.client !== client || generation !== this.connectionGeneration) {
33666
- await client.closeRoute(route).catch(() => {
33667
- return;
33668
- });
33669
- const error51 = new Error("subc connection changed while opening module route");
33670
- error51.code = "ECONNRESET";
33671
- throw error51;
34022
+ const opening = this.routeOpenings.get(routeKey2);
34023
+ if (opening?.client === client && opening.generation === generation) {
34024
+ return await opening.promise;
34025
+ }
34026
+ const promise2 = (async () => {
34027
+ const target = { kind: "tool_provider", module_id: this.moduleId };
34028
+ const identity = {
34029
+ project_root: projectRoot,
34030
+ harness: getHarness(),
34031
+ session: `${this.routeSessionPrefix}${sessionId}`
34032
+ };
34033
+ const route = await this.beforeDeadline(client.routeOpen(target, identity), deadlineMs, "opening the module route");
34034
+ if (this.client !== client || generation !== this.connectionGeneration) {
34035
+ await client.closeRoute(route).catch(() => {
34036
+ return;
34037
+ });
34038
+ throw this.connectionChangedError("subc connection changed while opening module route");
34039
+ }
34040
+ this.routes.set(routeKey2, { route, generation });
34041
+ return { client, route, routeKey: routeKey2, generation };
34042
+ })();
34043
+ const routeOpening = { client, generation, promise: promise2 };
34044
+ this.routeOpenings.set(routeKey2, routeOpening);
34045
+ try {
34046
+ return await promise2;
34047
+ } finally {
34048
+ if (this.routeOpenings.get(routeKey2) === routeOpening) {
34049
+ this.routeOpenings.delete(routeKey2);
34050
+ }
33672
34051
  }
33673
- this.routes.set(routeKey2, { route, generation });
33674
- return { client, route, routeKey: routeKey2, generation };
33675
34052
  }
33676
34053
  dropRoute(routeKey2, route) {
33677
34054
  const existing = this.routes.get(routeKey2);
@@ -33699,9 +34076,17 @@ class SubcModuleTransport {
33699
34076
  }
33700
34077
  return resolved;
33701
34078
  }
34079
+ connectClient() {
34080
+ return SubcClient.connect({
34081
+ connectionFile: this.connectionFile,
34082
+ handshakeTimeoutMs: HANDSHAKE_TIMEOUT_MS
34083
+ });
34084
+ }
33702
34085
  async ensureConnected() {
33703
34086
  if (this.client)
33704
34087
  return this.client;
34088
+ if (this.connectionPromise)
34089
+ return await this.connectionPromise;
33705
34090
  const now = Date.now();
33706
34091
  if (now < this.nextProbeMs) {
33707
34092
  const error51 = new Error(`subc connection backoff active until ${this.nextProbeMs}`);
@@ -33709,49 +34094,55 @@ class SubcModuleTransport {
33709
34094
  throw error51;
33710
34095
  }
33711
34096
  const generation = this.connectionGeneration;
33712
- let candidate = null;
33713
- try {
33714
- candidate = await SubcClient.connect({
33715
- connectionFile: this.connectionFile,
33716
- handshakeTimeoutMs: HANDSHAKE_TIMEOUT_MS
33717
- });
33718
- if (generation !== this.connectionGeneration) {
33719
- candidate.close();
33720
- const error51 = new Error("subc connection attempt was superseded");
33721
- error51.code = "ECONNRESET";
34097
+ const connecting = (async () => {
34098
+ let candidate = null;
34099
+ try {
34100
+ candidate = await this.connectClient();
34101
+ if (generation !== this.connectionGeneration) {
34102
+ candidate.close();
34103
+ throw this.connectionChangedError("subc connection attempt was superseded");
34104
+ }
34105
+ this.client = candidate;
34106
+ this.routes.clear();
34107
+ this.backoffMs = CONNECT_BACKOFF_INITIAL_MS;
34108
+ this.nextProbeMs = 0;
34109
+ return candidate;
34110
+ } catch (error51) {
34111
+ candidate?.close();
34112
+ if (generation === this.connectionGeneration)
34113
+ this.invalidateConnection();
34114
+ this.nextProbeMs = Date.now() + this.backoffMs;
34115
+ this.backoffMs = Math.min(this.backoffMs * 2, CONNECT_BACKOFF_MAX_MS);
33722
34116
  throw error51;
33723
34117
  }
33724
- this.client = candidate;
33725
- this.routes.clear();
33726
- this.backoffMs = CONNECT_BACKOFF_INITIAL_MS;
33727
- this.nextProbeMs = 0;
33728
- return candidate;
33729
- } catch (error51) {
33730
- candidate?.close();
33731
- this.invalidateConnection();
33732
- this.nextProbeMs = Date.now() + this.backoffMs;
33733
- this.backoffMs = Math.min(this.backoffMs * 2, CONNECT_BACKOFF_MAX_MS);
33734
- throw error51;
34118
+ })();
34119
+ this.connectionPromise = connecting;
34120
+ try {
34121
+ return await connecting;
34122
+ } finally {
34123
+ if (this.connectionPromise === connecting)
34124
+ this.connectionPromise = null;
33735
34125
  }
33736
34126
  }
33737
34127
  invalidateConnection(client = this.client) {
33738
- if (client && this.client && client !== this.client)
34128
+ if (client && this.client !== client)
33739
34129
  return;
33740
34130
  this.connectionGeneration += 1;
33741
34131
  this.invalidateStateSyncCapabilities();
33742
34132
  this.client = null;
33743
34133
  this.routes.clear();
34134
+ this.routeOpenings.clear();
33744
34135
  client?.close();
33745
34136
  }
33746
34137
  }
33747
- var DEFAULT_MODULE_ID = "magic-context", CONNECT_BACKOFF_INITIAL_MS = 1000, CONNECT_BACKOFF_MAX_MS = 30000, HANDSHAKE_TIMEOUT_MS = 2000, MODULE_SEND_TIMEOUT_MS = 15000, TRANSFORM_SEND_TIMEOUT_MS = 5000, SERIAL_LANE_MAX_WAITERS = 16, SERIAL_LANE_MIN_REMAINING_MS = 25, CANONICAL_ROOT_CACHE_MAX_ENTRIES = 256;
34138
+ var DEFAULT_MODULE_ID = "magic-context", CONNECT_BACKOFF_INITIAL_MS = 1000, CONNECT_BACKOFF_MAX_MS = 30000, HANDSHAKE_TIMEOUT_MS = 2000, MODULE_SEND_TIMEOUT_MS = 15000, TRANSFORM_SEND_TIMEOUT_MS = 5000, MAX_WRAPUP_REQUEST_BUDGET_MS = 3800000, SERIAL_LANE_MAX_WAITERS = 16, SERIAL_LANE_MAX_WAITERS_PER_SESSION = 8, SERIAL_LANE_MIN_REMAINING_MS = 25, CANONICAL_ROOT_CACHE_MAX_ENTRIES = 256;
33748
34139
  var init_module_transport = __esm(() => {
33749
34140
  init_dist5();
33750
34141
  init_data_path();
33751
34142
  });
33752
34143
  // ../plugin/src/shared/rpc-utils.ts
33753
34144
  import { execFileSync as execFileSync4 } from "node:child_process";
33754
- import { readFileSync as readFileSync11 } from "node:fs";
34145
+ import { readFileSync as readFileSync12 } from "node:fs";
33755
34146
  function isPidAlive(pid) {
33756
34147
  if (!Number.isInteger(pid) || pid <= 0)
33757
34148
  return false;
@@ -33832,6 +34223,49 @@ function isPidIdentityPlausible(record2) {
33832
34223
  return true;
33833
34224
  return commandLooksLikeOpenCode(command);
33834
34225
  }
34226
+ function commandLooksLikePi(command) {
34227
+ const normalized = command.trim().toLowerCase().replaceAll("\\", "/");
34228
+ const tokens = normalized.split(/\s+/).filter(Boolean);
34229
+ const executableName = (token) => (token ?? "").split("/").at(-1) ?? "";
34230
+ const first = executableName(tokens[0]).replace(/\.exe$/, "");
34231
+ if (["pi", "pi.cmd", "omp", "oh-my-pi"].includes(first))
34232
+ return true;
34233
+ if (["node", "bun", "deno"].includes(first)) {
34234
+ const script = executableName(tokens[1]);
34235
+ return ["pi", "pi.js", "pi.mjs", "pi.cjs"].includes(script) || normalized.includes("pi-coding-agent");
34236
+ }
34237
+ return false;
34238
+ }
34239
+ function inspectLivePiProcesses() {
34240
+ if (false) {}
34241
+ try {
34242
+ const output = String(rpcProcessListExecFileSync("ps", ["-axo", "pid=,command="], {
34243
+ encoding: "utf8",
34244
+ timeout: PS_PROBE_TIMEOUT_MS
34245
+ }));
34246
+ const pids = new Set;
34247
+ for (const line of output.split(/\r?\n/)) {
34248
+ const match = /^\s*(\d+)\s+(.+)$/.exec(line);
34249
+ if (!match)
34250
+ continue;
34251
+ const pid = Number(match[1]);
34252
+ if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid)
34253
+ continue;
34254
+ if (commandLooksLikePi(match[2]))
34255
+ pids.add(pid);
34256
+ }
34257
+ return { state: "known", processIds: [...pids].sort((left, right) => left - right) };
34258
+ } catch (error51) {
34259
+ return {
34260
+ state: "unreadable",
34261
+ processIds: [],
34262
+ error: error51 instanceof Error ? error51.message : String(error51)
34263
+ };
34264
+ }
34265
+ }
34266
+ function discoverLivePiProcessIds() {
34267
+ return inspectLivePiProcesses().processIds;
34268
+ }
33835
34269
  function parseRpcPortFile(content, fallbackPid = 0) {
33836
34270
  const trimmed = content.trim();
33837
34271
  if (!trimmed)
@@ -33863,11 +34297,12 @@ function parseRpcPortFile(content, fallbackPid = 0) {
33863
34297
  function isValidPort(port) {
33864
34298
  return Number.isInteger(port) && port > 0 && port <= 65535;
33865
34299
  }
33866
- var RPC_IDENTITY_SKEW_TOLERANCE_MS = 120000, LINUX_CLOCK_TICKS_PER_SECOND = 100, PS_PROBE_TIMEOUT_MS = 1000, OPEN_CODE_COMMAND_MARKERS, rpcIdentityReadFileSync, rpcIdentityExecFileSync, rpcIdentityPlatform, rpcIdentityNowMs = () => Date.now();
34300
+ var RPC_IDENTITY_SKEW_TOLERANCE_MS = 120000, LINUX_CLOCK_TICKS_PER_SECOND = 100, PS_PROBE_TIMEOUT_MS = 1000, OPEN_CODE_COMMAND_MARKERS, rpcIdentityReadFileSync, rpcIdentityExecFileSync, rpcProcessListExecFileSync, rpcIdentityPlatform, rpcIdentityNowMs = () => Date.now();
33867
34301
  var init_rpc_utils = __esm(() => {
33868
34302
  OPEN_CODE_COMMAND_MARKERS = ["opencode", "node", "bun", "electron"];
33869
- rpcIdentityReadFileSync = readFileSync11;
34303
+ rpcIdentityReadFileSync = readFileSync12;
33870
34304
  rpcIdentityExecFileSync = execFileSync4;
34305
+ rpcProcessListExecFileSync = execFileSync4;
33871
34306
  rpcIdentityPlatform = process.platform;
33872
34307
  });
33873
34308
  // ../plugin/src/hooks/magic-context/compartment-parser.ts
@@ -34227,6 +34662,14 @@ var init_workspaces = __esm(() => {
34227
34662
  });
34228
34663
 
34229
34664
  // ../plugin/src/features/magic-context/migrations.ts
34665
+ function isSqliteLockError(error51) {
34666
+ if (!error51 || typeof error51 !== "object")
34667
+ return false;
34668
+ const candidate = error51;
34669
+ if (candidate.code === "SQLITE_BUSY" || candidate.code === "SQLITE_LOCKED")
34670
+ return true;
34671
+ return typeof candidate.message === "string" && /database is locked|sqlite_(busy|locked)/i.test(candidate.message);
34672
+ }
34230
34673
  function tableExists(db, name) {
34231
34674
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name));
34232
34675
  }
@@ -34330,11 +34773,106 @@ function installLatestAuthorityTriggers(db) {
34330
34773
  `);
34331
34774
  }
34332
34775
  }
34333
- var FORK_MIGRATION_VERSION_FLOOR = 1e4, MIGRATIONS, LATEST_MIGRATION_VERSION;
34776
+ function ensureMigrationsTable(db) {
34777
+ db.exec(`
34778
+ CREATE TABLE IF NOT EXISTS schema_migrations (
34779
+ version INTEGER PRIMARY KEY,
34780
+ description TEXT NOT NULL,
34781
+ applied_at INTEGER NOT NULL
34782
+ )
34783
+ `);
34784
+ }
34785
+ function getCurrentVersion(db) {
34786
+ const row = db.prepare("SELECT COALESCE(MAX(version), 0) AS version FROM schema_migrations WHERE version < ?").get(FORK_MIGRATION_VERSION_FLOOR);
34787
+ return row?.version ?? 0;
34788
+ }
34789
+ function isMigrationApplied(db, version2) {
34790
+ return db.prepare("SELECT 1 FROM schema_migrations WHERE version = ?").get(version2) != null;
34791
+ }
34792
+ function isSiblingMigrationConflict(db, error51, version2) {
34793
+ if (!(error51 instanceof Error))
34794
+ return false;
34795
+ const msg = error51.message;
34796
+ if (!msg.includes("schema_migrations"))
34797
+ return false;
34798
+ if (!msg.toLowerCase().includes("version"))
34799
+ return false;
34800
+ const confirmed = db.prepare("SELECT 1 FROM schema_migrations WHERE version = ?").get(version2);
34801
+ return confirmed != null;
34802
+ }
34803
+ function runMigrations(db) {
34804
+ try {
34805
+ ensureMigrationsTable(db);
34806
+ } catch (error51) {
34807
+ if (isSqliteLockError(error51)) {
34808
+ throw new MigrationLockBusyError(`failed to prepare migration lock: ${error51 instanceof Error ? error51.message : String(error51)}`);
34809
+ }
34810
+ throw error51;
34811
+ }
34812
+ let loggedPlan = false;
34813
+ let touchedLegacyAuthorityBatch = false;
34814
+ while (true) {
34815
+ let migration;
34816
+ let currentVersion = 0;
34817
+ try {
34818
+ const applied = db.transaction(() => {
34819
+ currentVersion = getCurrentVersion(db);
34820
+ migration = MIGRATIONS.find((candidate) => candidate.version > currentVersion && !isMigrationApplied(db, candidate.version));
34821
+ if (!migration)
34822
+ return false;
34823
+ if (!loggedPlan) {
34824
+ const pendingCount = MIGRATIONS.filter((candidate) => candidate.version > currentVersion && !isMigrationApplied(db, candidate.version)).length;
34825
+ log3(`[migrations] current upstream migration lane: ${currentVersion}, applying ${pendingCount} migration(s)`);
34826
+ loggedPlan = true;
34827
+ }
34828
+ migration.up(db);
34829
+ db.prepare("INSERT INTO schema_migrations (version, description, applied_at) VALUES (?, ?, ?)").run(migration.version, migration.description, Date.now());
34830
+ return true;
34831
+ }).immediate();
34832
+ if (!applied || !migration)
34833
+ break;
34834
+ if (migration.version <= 61)
34835
+ touchedLegacyAuthorityBatch = true;
34836
+ log3(`[migrations] applied v${migration.version}: ${migration.description}`);
34837
+ } catch (error51) {
34838
+ if (!migration && isSqliteLockError(error51)) {
34839
+ throw new MigrationLockBusyError(`failed to acquire migration write lock: ${error51 instanceof Error ? error51.message : String(error51)}`);
34840
+ }
34841
+ if (migration && isSiblingMigrationConflict(db, error51, migration.version)) {
34842
+ log3(`[migrations] v${migration.version} already applied by sibling instance — resuming with re-read version`);
34843
+ const reReadVersion = getCurrentVersion(db);
34844
+ if (reReadVersion > currentVersion)
34845
+ continue;
34846
+ throw new Error(`Migration v${migration.version} failed: sibling conflict reported but version did not advance. Database may need manual repair.`);
34847
+ }
34848
+ const version2 = migration?.version ?? currentVersion + 1;
34849
+ const description = migration?.description ?? "acquire migration write lock";
34850
+ log3(`[migrations] FAILED v${version2}: ${description} — ${error51 instanceof Error ? error51.message : String(error51)}`);
34851
+ throw new Error(`Migration v${version2} failed: ${error51 instanceof Error ? error51.message : String(error51)}. Database may need manual repair.`);
34852
+ }
34853
+ }
34854
+ if (touchedLegacyAuthorityBatch) {
34855
+ try {
34856
+ db.transaction(() => installLatestAuthorityTriggers(db)).immediate();
34857
+ } catch (error51) {
34858
+ throw new Error(`Migration authority-trigger postcondition failed: ${error51 instanceof Error ? error51.message : String(error51)}. Database may need manual repair.`);
34859
+ }
34860
+ }
34861
+ if (loggedPlan) {
34862
+ log3(`[migrations] upstream migration lane now: ${MIGRATIONS[MIGRATIONS.length - 1].version}`);
34863
+ }
34864
+ }
34865
+ var FORK_MIGRATION_VERSION_FLOOR = 1e4, MigrationLockBusyError, MIGRATIONS, LATEST_MIGRATION_VERSION;
34334
34866
  var init_migrations = __esm(() => {
34335
34867
  init_compartment_parser();
34336
34868
  init_logger();
34337
34869
  init_workspaces();
34870
+ MigrationLockBusyError = class MigrationLockBusyError extends Error {
34871
+ constructor(message) {
34872
+ super(message);
34873
+ this.name = "MigrationLockBusyError";
34874
+ }
34875
+ };
34338
34876
  MIGRATIONS = [
34339
34877
  {
34340
34878
  version: 1,
@@ -36289,11 +36827,8 @@ var init_commit_detection = __esm(() => {
36289
36827
  COMMIT_HASH_TEST_PATTERN = new RegExp(`\\b${HASH_HEX}\\b`, "i");
36290
36828
  });
36291
36829
  // ../plugin/src/hooks/magic-context/read-session-formatting.ts
36292
- import { createRequire as createRequire2 } from "node:module";
36293
- var requireFromThisModule;
36294
36830
  var init_read_session_formatting = __esm(() => {
36295
36831
  init_commit_detection();
36296
- requireFromThisModule = createRequire2(import.meta.url);
36297
36832
  });
36298
36833
  // ../plugin/src/features/magic-context/tool-definition-tokens.ts
36299
36834
  var measurements, fingerprints;
@@ -36320,7 +36855,7 @@ import {
36320
36855
  existsSync as existsSync18,
36321
36856
  mkdirSync as mkdirSync7,
36322
36857
  readdirSync as readdirSync2,
36323
- readFileSync as readFileSync12,
36858
+ readFileSync as readFileSync13,
36324
36859
  statSync as statSync7,
36325
36860
  unlinkSync as unlinkSync2
36326
36861
  } from "node:fs";
@@ -36448,11 +36983,1113 @@ function inspectRpcServerDiscovery(storageDir) {
36448
36983
  }
36449
36984
  function getLiveMigrationBlockingProcesses(storageDir) {
36450
36985
  const discovery = inspectRpcServerDiscovery(storageDir);
36451
- if (discovery.state !== "live")
36452
- return [];
36453
- return discovery.serverPids.map((pid) => ({ harness: "OpenCode server", pid }));
36986
+ const openCode = discovery.state === "live" ? discovery.serverPids.map((pid) => ({ harness: "OpenCode server", pid })) : [];
36987
+ const pi = discoverLivePiProcessIds().map((pid) => ({ harness: "Pi harness", pid }));
36988
+ return [...openCode, ...pi];
36454
36989
  }
36455
- var databases, pendingAsyncOpens, persistenceByDatabase, persistenceErrorByDatabase, pathByDatabase, LATEST_SUPPORTED_VERSION = 76, PERMISSIONS_ENFORCEABLE, RPC_DISCOVERY_PARSE_GRACE_MS, defaultRpcDiscoveryFs, rpcDiscoveryFs;
36990
+ function applySqliteTuningPragmas(db) {
36991
+ db.exec(`PRAGMA cache_size=-${Math.round(sqlitePragmaConfig.cacheSizeMb * 1024)}`);
36992
+ db.exec(`PRAGMA mmap_size=${Math.round(sqlitePragmaConfig.mmapSizeMb * 1024 * 1024)}`);
36993
+ db.exec("PRAGMA analysis_limit=400");
36994
+ }
36995
+ function initializeDatabase(db) {
36996
+ db.exec("PRAGMA busy_timeout=5000");
36997
+ db.exec("PRAGMA foreign_keys=ON");
36998
+ db.exec("PRAGMA journal_mode=WAL");
36999
+ applySqliteTuningPragmas(db);
37000
+ db.exec(`
37001
+ CREATE TABLE IF NOT EXISTS tags (
37002
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37003
+ session_id TEXT,
37004
+ message_id TEXT,
37005
+ type TEXT,
37006
+ status TEXT DEFAULT 'active',
37007
+ byte_size INTEGER,
37008
+ tag_number INTEGER,
37009
+ harness TEXT NOT NULL DEFAULT 'opencode',
37010
+ entry_fingerprint TEXT,
37011
+ token_count INTEGER,
37012
+ input_token_count INTEGER,
37013
+ reasoning_token_count INTEGER,
37014
+ UNIQUE(session_id, tag_number)
37015
+ );
37016
+
37017
+ CREATE TABLE IF NOT EXISTS pending_ops (
37018
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37019
+ session_id TEXT,
37020
+ tag_id INTEGER,
37021
+ operation TEXT,
37022
+ queued_at INTEGER,
37023
+ harness TEXT NOT NULL DEFAULT 'opencode'
37024
+ );
37025
+
37026
+ CREATE TABLE IF NOT EXISTS source_contents (
37027
+ tag_id INTEGER,
37028
+ session_id TEXT,
37029
+ content TEXT,
37030
+ created_at INTEGER,
37031
+ harness TEXT NOT NULL DEFAULT 'opencode',
37032
+ PRIMARY KEY(session_id, tag_id)
37033
+ );
37034
+
37035
+ CREATE TABLE IF NOT EXISTS compartments (
37036
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37037
+ session_id TEXT NOT NULL,
37038
+ sequence INTEGER NOT NULL,
37039
+ start_message INTEGER NOT NULL,
37040
+ end_message INTEGER NOT NULL,
37041
+ start_message_id TEXT DEFAULT '',
37042
+ end_message_id TEXT DEFAULT '',
37043
+ title TEXT NOT NULL,
37044
+ content TEXT NOT NULL,
37045
+ p1 TEXT,
37046
+ p2 TEXT,
37047
+ p3 TEXT,
37048
+ p4 TEXT,
37049
+ importance INTEGER NOT NULL DEFAULT 50,
37050
+ episode_type TEXT,
37051
+ p1_embedding BLOB,
37052
+ p1_embedding_model_id TEXT,
37053
+ legacy INTEGER NOT NULL DEFAULT 0,
37054
+ created_at INTEGER NOT NULL,
37055
+ harness TEXT NOT NULL DEFAULT 'opencode',
37056
+ UNIQUE(session_id, sequence)
37057
+ );
37058
+ CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
37059
+
37060
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
37061
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37062
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
37063
+ session_id TEXT NOT NULL,
37064
+ project_path TEXT NOT NULL,
37065
+ harness TEXT NOT NULL DEFAULT 'opencode',
37066
+ window_index INTEGER NOT NULL DEFAULT 0,
37067
+ start_ordinal INTEGER NOT NULL,
37068
+ end_ordinal INTEGER NOT NULL,
37069
+ chunk_hash TEXT NOT NULL,
37070
+ model_id TEXT NOT NULL,
37071
+ dims INTEGER NOT NULL,
37072
+ vector BLOB NOT NULL,
37073
+ created_at INTEGER NOT NULL,
37074
+ UNIQUE(compartment_id, model_id, window_index)
37075
+ );
37076
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
37077
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
37078
+
37079
+ CREATE TABLE IF NOT EXISTS session_projects (
37080
+ session_id TEXT NOT NULL,
37081
+ harness TEXT NOT NULL DEFAULT 'opencode',
37082
+ project_path TEXT NOT NULL,
37083
+ updated_at INTEGER NOT NULL,
37084
+ PRIMARY KEY(session_id, harness)
37085
+ );
37086
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
37087
+ ON session_projects(project_path);
37088
+
37089
+ CREATE TABLE IF NOT EXISTS compartment_events (
37090
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37091
+ session_id TEXT NOT NULL,
37092
+ compartment_id INTEGER,
37093
+ kind TEXT NOT NULL,
37094
+ at_compartment INTEGER,
37095
+ fields_json TEXT NOT NULL DEFAULT '{}',
37096
+ created_at INTEGER NOT NULL,
37097
+ harness TEXT NOT NULL DEFAULT 'opencode'
37098
+ );
37099
+ CREATE INDEX IF NOT EXISTS idx_compartment_events_session
37100
+ ON compartment_events(session_id);
37101
+
37102
+ CREATE TABLE IF NOT EXISTS compartment_state_lease (
37103
+ session_id TEXT PRIMARY KEY NOT NULL,
37104
+ holder_id TEXT NOT NULL,
37105
+ acquired_at INTEGER NOT NULL,
37106
+ expires_at INTEGER NOT NULL
37107
+ );
37108
+ CREATE INDEX IF NOT EXISTS idx_compartment_state_lease_expires
37109
+ ON compartment_state_lease(expires_at);
37110
+
37111
+ CREATE TABLE IF NOT EXISTS compression_depth (
37112
+ session_id TEXT NOT NULL,
37113
+ message_ordinal INTEGER NOT NULL,
37114
+ depth INTEGER NOT NULL DEFAULT 0,
37115
+ harness TEXT NOT NULL DEFAULT 'opencode',
37116
+ PRIMARY KEY(session_id, message_ordinal)
37117
+ );
37118
+ CREATE INDEX IF NOT EXISTS idx_compression_depth_session ON compression_depth(session_id);
37119
+
37120
+ CREATE TABLE IF NOT EXISTS session_facts (
37121
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37122
+ session_id TEXT NOT NULL,
37123
+ category TEXT NOT NULL,
37124
+ content TEXT NOT NULL,
37125
+ created_at INTEGER NOT NULL,
37126
+ updated_at INTEGER NOT NULL,
37127
+ harness TEXT NOT NULL DEFAULT 'opencode'
37128
+ );
37129
+
37130
+ CREATE TABLE IF NOT EXISTS primer_candidates (
37131
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37132
+ project_path TEXT NOT NULL,
37133
+ harness TEXT NOT NULL DEFAULT 'opencode',
37134
+ session_id TEXT NOT NULL,
37135
+ question TEXT NOT NULL,
37136
+ normalized_question TEXT NOT NULL,
37137
+ source_compartment_start INTEGER,
37138
+ source_compartment_end INTEGER,
37139
+ source_start_message_id TEXT NOT NULL DEFAULT '',
37140
+ source_end_message_id TEXT NOT NULL DEFAULT '',
37141
+ source_message_time INTEGER NOT NULL,
37142
+ question_embedding BLOB,
37143
+ question_embedding_model_id TEXT,
37144
+ created_at INTEGER NOT NULL,
37145
+ UNIQUE(project_path, harness, session_id, source_start_message_id, source_end_message_id)
37146
+ );
37147
+ CREATE INDEX IF NOT EXISTS idx_primer_candidates_project_time
37148
+ ON primer_candidates(project_path, source_message_time);
37149
+ CREATE INDEX IF NOT EXISTS idx_primer_candidates_session
37150
+ ON primer_candidates(session_id, harness);
37151
+ CREATE INDEX IF NOT EXISTS idx_primer_candidates_embedding_model
37152
+ ON primer_candidates(project_path, question_embedding_model_id);
37153
+
37154
+ CREATE TABLE IF NOT EXISTS primers (
37155
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37156
+ project_path TEXT NOT NULL,
37157
+ question TEXT NOT NULL,
37158
+ question_embedding BLOB,
37159
+ question_embedding_model_id TEXT,
37160
+ answer TEXT NOT NULL DEFAULT '',
37161
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'archived')),
37162
+ total_support INTEGER NOT NULL DEFAULT 0,
37163
+ last_observed_at INTEGER,
37164
+ answer_refreshed_at INTEGER,
37165
+ source_candidate_ids TEXT NOT NULL DEFAULT '[]',
37166
+ created_at INTEGER NOT NULL,
37167
+ updated_at INTEGER NOT NULL
37168
+ );
37169
+ CREATE INDEX IF NOT EXISTS idx_primers_project_status_observed
37170
+ ON primers(project_path, status, last_observed_at DESC);
37171
+ CREATE INDEX IF NOT EXISTS idx_primers_embedding_model
37172
+ ON primers(project_path, question_embedding_model_id);
37173
+
37174
+ CREATE VIRTUAL TABLE IF NOT EXISTS primers_fts USING fts5(
37175
+ question,
37176
+ answer,
37177
+ project_path UNINDEXED,
37178
+ content='primers',
37179
+ content_rowid='id',
37180
+ tokenize='porter unicode61'
37181
+ );
37182
+
37183
+ CREATE TRIGGER IF NOT EXISTS primers_ai AFTER INSERT ON primers BEGIN
37184
+ INSERT INTO primers_fts(rowid, question, answer, project_path)
37185
+ VALUES (new.id, new.question, new.answer, new.project_path);
37186
+ END;
37187
+
37188
+ CREATE TRIGGER IF NOT EXISTS primers_ad AFTER DELETE ON primers BEGIN
37189
+ INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
37190
+ VALUES ('delete', old.id, old.question, old.answer, old.project_path);
37191
+ END;
37192
+
37193
+ CREATE TRIGGER IF NOT EXISTS primers_au AFTER UPDATE ON primers BEGIN
37194
+ INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
37195
+ VALUES ('delete', old.id, old.question, old.answer, old.project_path);
37196
+ INSERT INTO primers_fts(rowid, question, answer, project_path)
37197
+ VALUES (new.id, new.question, new.answer, new.project_path);
37198
+ END;
37199
+
37200
+ -- session_notes and smart_notes were merged into the unified notes table
37201
+ -- by migration v1 (see features/magic-context/migrations.ts). The old tables
37202
+ -- are never recreated; fresh DBs create only notes, upgraded DBs have
37203
+ -- their old tables migrated and dropped by the migration runner.
37204
+
37205
+ CREATE TABLE IF NOT EXISTS memories (
37206
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37207
+ project_path TEXT NOT NULL,
37208
+ category TEXT NOT NULL,
37209
+ content TEXT NOT NULL,
37210
+ normalized_hash TEXT NOT NULL,
37211
+ importance INTEGER,
37212
+ scope TEXT NOT NULL DEFAULT 'project',
37213
+ shareable INTEGER NOT NULL DEFAULT 0,
37214
+ source_session_id TEXT,
37215
+ source_type TEXT DEFAULT 'historian',
37216
+ seen_count INTEGER DEFAULT 1,
37217
+ retrieval_count INTEGER DEFAULT 0,
37218
+ first_seen_at INTEGER NOT NULL,
37219
+ created_at INTEGER NOT NULL,
37220
+ updated_at INTEGER NOT NULL,
37221
+ last_seen_at INTEGER NOT NULL,
37222
+ last_retrieved_at INTEGER,
37223
+ status TEXT DEFAULT 'active',
37224
+ expires_at INTEGER,
37225
+ verification_status TEXT DEFAULT 'unverified',
37226
+ verified_at INTEGER,
37227
+ classified_at INTEGER,
37228
+ superseded_by_memory_id INTEGER,
37229
+ merged_from TEXT,
37230
+ metadata_json TEXT,
37231
+ mural_cue TEXT,
37232
+ mural_cue_hash TEXT,
37233
+ mural_cue_at INTEGER,
37234
+ mural_cue_rejection_count INTEGER NOT NULL DEFAULT 0,
37235
+ UNIQUE(project_path, category, normalized_hash)
37236
+ );
37237
+
37238
+ CREATE TABLE IF NOT EXISTS memory_embeddings (
37239
+ -- FK-cascade audit (v12): memory_embeddings.memory_id -> memories.id
37240
+ -- uses ON DELETE CASCADE, so SQLite PRAGMA foreign_keys must be ON on
37241
+ -- every connection and v12 cleans historical orphan rows.
37242
+ memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
37243
+ embedding BLOB NOT NULL,
37244
+ model_id TEXT NOT NULL,
37245
+ PRIMARY KEY(memory_id, model_id)
37246
+ );
37247
+
37248
+ CREATE TABLE IF NOT EXISTS embedding_identity_active (
37249
+ project_path TEXT NOT NULL,
37250
+ scope TEXT NOT NULL CHECK(scope IN ('memory', 'commit', 'chunk')),
37251
+ model_id TEXT NOT NULL,
37252
+ last_active_at INTEGER NOT NULL,
37253
+ PRIMARY KEY(project_path, scope, model_id)
37254
+ );
37255
+
37256
+ CREATE TABLE IF NOT EXISTS embedding_registrations (
37257
+ project_path TEXT PRIMARY KEY,
37258
+ provider_identity TEXT NOT NULL DEFAULT '',
37259
+ model_id TEXT NOT NULL DEFAULT '',
37260
+ chunk_model_id TEXT NOT NULL DEFAULT '',
37261
+ fingerprint TEXT NOT NULL DEFAULT '',
37262
+ table_epoch INTEGER NOT NULL DEFAULT 0,
37263
+ dims INTEGER NOT NULL DEFAULT 0,
37264
+ provenance_json TEXT NOT NULL DEFAULT '{}',
37265
+ generation INTEGER NOT NULL DEFAULT 0,
37266
+ updated_at INTEGER NOT NULL DEFAULT 0
37267
+ );
37268
+
37269
+ CREATE TABLE IF NOT EXISTS synapse_batch_ledger (
37270
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37271
+ session_id TEXT NOT NULL,
37272
+ project_path TEXT NOT NULL DEFAULT '',
37273
+ scope TEXT NOT NULL DEFAULT '',
37274
+ manifest_json TEXT NOT NULL DEFAULT '{}',
37275
+ request_key TEXT NOT NULL DEFAULT '',
37276
+ job_id TEXT,
37277
+ cursor TEXT,
37278
+ status TEXT NOT NULL DEFAULT 'pending',
37279
+ created_at INTEGER NOT NULL DEFAULT 0,
37280
+ updated_at INTEGER NOT NULL DEFAULT 0,
37281
+ UNIQUE(session_id, request_key)
37282
+ );
37283
+ CREATE INDEX IF NOT EXISTS idx_synapse_batch_ledger_session
37284
+ ON synapse_batch_ledger(session_id, updated_at);
37285
+
37286
+ CREATE TABLE IF NOT EXISTS shadow_embedding_registrations (
37287
+ project_path TEXT NOT NULL,
37288
+ scope TEXT NOT NULL CHECK(scope IN ('memory', 'commit', 'chunk')),
37289
+ model_id TEXT NOT NULL,
37290
+ generation INTEGER NOT NULL DEFAULT 0,
37291
+ fingerprint TEXT NOT NULL DEFAULT '',
37292
+ table_epoch INTEGER NOT NULL DEFAULT 0,
37293
+ dims INTEGER NOT NULL DEFAULT 0,
37294
+ provenance_json TEXT NOT NULL DEFAULT '{}',
37295
+ updated_at INTEGER NOT NULL DEFAULT 0,
37296
+ PRIMARY KEY(project_path, scope, model_id)
37297
+ );
37298
+
37299
+ CREATE TABLE IF NOT EXISTS embedding_measurement_corpus (
37300
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37301
+ session_id TEXT NOT NULL,
37302
+ project_path TEXT NOT NULL DEFAULT '',
37303
+ dedup_key TEXT NOT NULL DEFAULT '',
37304
+ cohort_key TEXT NOT NULL DEFAULT '',
37305
+ query_text_hash TEXT NOT NULL DEFAULT '',
37306
+ primary_result_ids_json TEXT NOT NULL DEFAULT '[]',
37307
+ shadow_result_ids_json TEXT NOT NULL DEFAULT '[]',
37308
+ primary_latency_ms INTEGER,
37309
+ shadow_latency_ms INTEGER,
37310
+ primary_failed INTEGER NOT NULL DEFAULT 0,
37311
+ shadow_failed INTEGER NOT NULL DEFAULT 0,
37312
+ primary_model_id TEXT NOT NULL DEFAULT '',
37313
+ shadow_model_id TEXT NOT NULL DEFAULT '',
37314
+ primary_fingerprint TEXT NOT NULL DEFAULT '',
37315
+ shadow_fingerprint TEXT NOT NULL DEFAULT '',
37316
+ primary_epoch INTEGER NOT NULL DEFAULT 0,
37317
+ shadow_epoch INTEGER NOT NULL DEFAULT 0,
37318
+ corpus_hash TEXT NOT NULL DEFAULT '',
37319
+ coverage_json TEXT NOT NULL DEFAULT '{}',
37320
+ created_at INTEGER NOT NULL DEFAULT 0,
37321
+ UNIQUE(dedup_key, cohort_key)
37322
+ );
37323
+ CREATE INDEX IF NOT EXISTS idx_embedding_measurement_session
37324
+ ON embedding_measurement_corpus(session_id, created_at);
37325
+
37326
+ CREATE TABLE IF NOT EXISTS memory_verifications (
37327
+ memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
37328
+ file_path TEXT NOT NULL,
37329
+ -- verified_at=0 means "mapped (files known) but not yet content-verified".
37330
+ -- map-memories sets mapped_at + verified_at=0; verify sets verified_at=now.
37331
+ verified_at INTEGER NOT NULL,
37332
+ mapped_at INTEGER NOT NULL DEFAULT 0,
37333
+ PRIMARY KEY (memory_id, file_path)
37334
+ );
37335
+ CREATE INDEX IF NOT EXISTS idx_memory_verifications_memory ON memory_verifications(memory_id);
37336
+
37337
+ CREATE TABLE IF NOT EXISTS memory_mutation_log (
37338
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37339
+ project_path TEXT NOT NULL,
37340
+ mutation_type TEXT NOT NULL CHECK (mutation_type IN ('archive', 'delete', 'update', 'superseded')),
37341
+ target_memory_id INTEGER NOT NULL,
37342
+ superseded_by_id INTEGER,
37343
+ category TEXT,
37344
+ new_content TEXT,
37345
+ queued_at INTEGER NOT NULL
37346
+ );
37347
+ CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_project
37348
+ ON memory_mutation_log(project_path, id);
37349
+ CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_visibility
37350
+ ON memory_mutation_log(project_path, category, id, target_memory_id);
37351
+ CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_target
37352
+ ON memory_mutation_log(project_path, target_memory_id, id);
37353
+
37354
+ CREATE TABLE IF NOT EXISTS dream_state (
37355
+ key TEXT PRIMARY KEY,
37356
+ value TEXT NOT NULL
37357
+ );
37358
+
37359
+ CREATE TABLE IF NOT EXISTS dream_queue (
37360
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37361
+ project_path TEXT NOT NULL,
37362
+ reason TEXT NOT NULL,
37363
+ enqueued_at INTEGER NOT NULL,
37364
+ started_at INTEGER,
37365
+ retry_count INTEGER DEFAULT 0
37366
+ );
37367
+ CREATE INDEX IF NOT EXISTS idx_dream_queue_project ON dream_queue(project_path);
37368
+ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, enqueued_at);
37369
+
37370
+ CREATE TABLE IF NOT EXISTS dream_runs (
37371
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37372
+ project_path TEXT NOT NULL,
37373
+ started_at INTEGER NOT NULL,
37374
+ finished_at INTEGER NOT NULL,
37375
+ holder_id TEXT NOT NULL,
37376
+ tasks_json TEXT NOT NULL,
37377
+ tasks_succeeded INTEGER NOT NULL DEFAULT 0,
37378
+ tasks_failed INTEGER NOT NULL DEFAULT 0,
37379
+ smart_notes_surfaced INTEGER NOT NULL DEFAULT 0,
37380
+ smart_notes_pending INTEGER NOT NULL DEFAULT 0,
37381
+ memory_changes_json TEXT,
37382
+ parent_session_id TEXT
37383
+ );
37384
+ CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
37385
+
37386
+ CREATE TABLE IF NOT EXISTS task_schedule_state (
37387
+ project_path TEXT NOT NULL,
37388
+ task TEXT NOT NULL,
37389
+ last_run_at INTEGER,
37390
+ next_due_at INTEGER,
37391
+ schedule TEXT,
37392
+ last_status TEXT,
37393
+ last_error TEXT,
37394
+ last_checked_commit TEXT,
37395
+ last_broad_run_at INTEGER,
37396
+ retrospective_watermark_ms INTEGER,
37397
+ retry_count INTEGER NOT NULL DEFAULT 0,
37398
+ PRIMARY KEY (project_path, task)
37399
+ );
37400
+ CREATE INDEX IF NOT EXISTS idx_task_schedule_due ON task_schedule_state(next_due_at);
37401
+
37402
+ CREATE TABLE IF NOT EXISTS retrospective_processed_windows (
37403
+ project_path TEXT NOT NULL,
37404
+ window_key TEXT NOT NULL,
37405
+ processed_at INTEGER NOT NULL,
37406
+ PRIMARY KEY (project_path, window_key)
37407
+ );
37408
+
37409
+ CREATE TABLE IF NOT EXISTS project_key_files (
37410
+ project_path TEXT NOT NULL,
37411
+ path TEXT NOT NULL,
37412
+ content TEXT NOT NULL,
37413
+ content_hash TEXT NOT NULL,
37414
+ local_token_estimate INTEGER NOT NULL,
37415
+ generated_at INTEGER NOT NULL,
37416
+ generated_by_model TEXT,
37417
+ generation_config_hash TEXT NOT NULL,
37418
+ stale_reason TEXT,
37419
+ PRIMARY KEY (project_path, path)
37420
+ );
37421
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_project ON project_key_files(project_path);
37422
+ CREATE INDEX IF NOT EXISTS idx_project_key_files_generated_at ON project_key_files(project_path, generated_at);
37423
+
37424
+ CREATE TABLE IF NOT EXISTS project_key_files_version (
37425
+ project_path TEXT PRIMARY KEY,
37426
+ version INTEGER NOT NULL DEFAULT 0
37427
+ );
37428
+
37429
+ CREATE TABLE IF NOT EXISTS schema_migrations_meta (
37430
+ key TEXT PRIMARY KEY,
37431
+ value TEXT NOT NULL
37432
+ );
37433
+
37434
+ CREATE TABLE IF NOT EXISTS project_state (
37435
+ project_path TEXT PRIMARY KEY,
37436
+ project_memory_epoch INTEGER NOT NULL DEFAULT 0,
37437
+ project_user_profile_version INTEGER NOT NULL DEFAULT 0,
37438
+ updated_at INTEGER NOT NULL DEFAULT 0
37439
+ );
37440
+
37441
+ CREATE TABLE IF NOT EXISTS git_sweep_coordinator (
37442
+ project_path TEXT PRIMARY KEY,
37443
+ lease_holder TEXT,
37444
+ lease_expires_at INTEGER,
37445
+ last_swept_at INTEGER
37446
+ );
37447
+ CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_lease_expires
37448
+ ON git_sweep_coordinator(lease_expires_at);
37449
+ CREATE INDEX IF NOT EXISTS idx_git_sweep_coordinator_last_swept
37450
+ ON git_sweep_coordinator(last_swept_at);
37451
+
37452
+ CREATE TABLE IF NOT EXISTS m0_mutation_log (
37453
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37454
+ session_id TEXT NOT NULL,
37455
+ mutation_type TEXT NOT NULL CHECK (mutation_type IN (
37456
+ 'compartment_delete', 'compartment_merge', 'recomp_boundary_change', 'compartment_upgrade'
37457
+ )),
37458
+ target_id INTEGER,
37459
+ queued_at INTEGER NOT NULL
37460
+ );
37461
+ CREATE INDEX IF NOT EXISTS idx_m0_mutation_log_session ON m0_mutation_log(session_id);
37462
+
37463
+ CREATE TABLE IF NOT EXISTS v22_identity_rekey_map (
37464
+ old_project_path TEXT PRIMARY KEY,
37465
+ new_project_path TEXT NOT NULL,
37466
+ rekeyed_at INTEGER NOT NULL
37467
+ );
37468
+
37469
+ CREATE TABLE IF NOT EXISTS workspaces (
37470
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37471
+ name TEXT NOT NULL UNIQUE,
37472
+ created_at INTEGER NOT NULL,
37473
+ updated_at INTEGER NOT NULL,
37474
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
37475
+ );
37476
+
37477
+ CREATE TABLE IF NOT EXISTS workspace_members (
37478
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
37479
+ project_path TEXT NOT NULL,
37480
+ display_name TEXT NOT NULL,
37481
+ display_path TEXT NOT NULL,
37482
+ added_at INTEGER NOT NULL,
37483
+ PRIMARY KEY (workspace_id, project_path)
37484
+ );
37485
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
37486
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
37487
+
37488
+ CREATE TABLE IF NOT EXISTS v22_backfill_failures (
37489
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37490
+ table_name TEXT NOT NULL,
37491
+ row_id INTEGER NOT NULL,
37492
+ raw_project_path TEXT NOT NULL,
37493
+ error_class TEXT NOT NULL CHECK (error_class IN ('not_git_repo', 'git_missing', 'git_timeout', 'permission_denied', 'unknown')),
37494
+ error_message TEXT,
37495
+ failed_at INTEGER NOT NULL,
37496
+ UNIQUE(table_name, row_id)
37497
+ );
37498
+
37499
+ -- (smart_notes: see note above; merged into unified notes table by migration v1)
37500
+
37501
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
37502
+ content,
37503
+ category,
37504
+ content='memories',
37505
+ content_rowid='id',
37506
+ tokenize='porter unicode61'
37507
+ );
37508
+
37509
+ CREATE VIRTUAL TABLE IF NOT EXISTS message_history_fts USING fts5(
37510
+ session_id UNINDEXED,
37511
+ message_ordinal UNINDEXED,
37512
+ message_id UNINDEXED,
37513
+ role,
37514
+ content,
37515
+ tokenize='porter unicode61'
37516
+ );
37517
+
37518
+ CREATE TABLE IF NOT EXISTS message_history_index (
37519
+ session_id TEXT PRIMARY KEY,
37520
+ last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
37521
+ dirty_floor_ordinal INTEGER NOT NULL DEFAULT 0,
37522
+ updated_at INTEGER NOT NULL,
37523
+ harness TEXT NOT NULL DEFAULT 'opencode'
37524
+ );
37525
+ CREATE INDEX IF NOT EXISTS idx_message_history_index_orphan_sweep
37526
+ ON message_history_index(harness, session_id, updated_at);
37527
+
37528
+ CREATE TABLE IF NOT EXISTS message_history_source (
37529
+ session_id TEXT NOT NULL,
37530
+ message_id TEXT NOT NULL,
37531
+ message_ordinal INTEGER NOT NULL,
37532
+ source_version TEXT NOT NULL,
37533
+ normalized_content_hash TEXT NOT NULL,
37534
+ role TEXT NOT NULL,
37535
+ harness TEXT NOT NULL DEFAULT 'opencode',
37536
+ updated_at INTEGER NOT NULL,
37537
+ PRIMARY KEY(session_id, message_id)
37538
+ );
37539
+ CREATE INDEX IF NOT EXISTS idx_message_history_source_session_ordinal
37540
+ ON message_history_source(session_id, message_ordinal);
37541
+
37542
+ CREATE TABLE IF NOT EXISTS pending_session_cleanup (
37543
+ session_id TEXT PRIMARY KEY,
37544
+ harness TEXT NOT NULL DEFAULT 'opencode',
37545
+ requested_at INTEGER NOT NULL,
37546
+ last_attempt_at INTEGER
37547
+ );
37548
+
37549
+ CREATE TABLE IF NOT EXISTS message_history_orphan_sweep (
37550
+ harness TEXT PRIMARY KEY,
37551
+ cursor_session_id TEXT NOT NULL DEFAULT '',
37552
+ last_swept_at INTEGER
37553
+ );
37554
+
37555
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
37556
+ INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
37557
+ END;
37558
+
37559
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
37560
+ INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
37561
+ END;
37562
+
37563
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
37564
+ INSERT INTO memories_fts(memories_fts, rowid, content, category) VALUES ('delete', old.id, old.content, old.category);
37565
+ INSERT INTO memories_fts(rowid, content, category) VALUES (new.id, new.content, new.category);
37566
+ END;
37567
+
37568
+ CREATE TABLE IF NOT EXISTS session_meta (
37569
+ session_id TEXT PRIMARY KEY,
37570
+ harness TEXT NOT NULL DEFAULT 'opencode',
37571
+ last_response_time INTEGER,
37572
+ cache_ttl TEXT,
37573
+ counter INTEGER DEFAULT 0,
37574
+ last_nudge_tokens INTEGER DEFAULT 0,
37575
+ last_nudge_band TEXT DEFAULT '',
37576
+ last_nudge_undropped INTEGER DEFAULT 0,
37577
+ last_nudge_level TEXT DEFAULT '',
37578
+ channel2_nudge_state TEXT DEFAULT '',
37579
+ channel2_nudge_claimed_at INTEGER DEFAULT 0,
37580
+ channel2_nudge_claim_token TEXT DEFAULT '',
37581
+ last_emergency_input_sample INTEGER DEFAULT 0,
37582
+ last_transform_error TEXT DEFAULT '',
37583
+ nudge_anchor_message_id TEXT DEFAULT '',
37584
+ nudge_anchor_text TEXT DEFAULT '',
37585
+ sticky_turn_reminder_text TEXT DEFAULT '',
37586
+ sticky_turn_reminder_message_id TEXT DEFAULT '',
37587
+ note_nudge_trigger_pending INTEGER DEFAULT 0,
37588
+ note_nudge_trigger_message_id TEXT DEFAULT '',
37589
+ note_nudge_sticky_text TEXT DEFAULT '',
37590
+ note_nudge_sticky_message_id TEXT DEFAULT '',
37591
+ note_nudge_anchors TEXT NOT NULL DEFAULT '[]',
37592
+ auto_search_hint_decisions TEXT NOT NULL DEFAULT '[]',
37593
+ last_todo_state TEXT DEFAULT '',
37594
+ todo_permission_denied INTEGER NOT NULL DEFAULT 2,
37595
+ todo_synthetic_call_id TEXT DEFAULT '',
37596
+ todo_synthetic_anchor_message_id TEXT DEFAULT '',
37597
+ todo_synthetic_state_json TEXT DEFAULT '',
37598
+ is_subagent INTEGER DEFAULT 0,
37599
+ last_context_percentage REAL DEFAULT 0,
37600
+ last_input_tokens INTEGER DEFAULT 0,
37601
+ detected_context_limit_provenance TEXT NOT NULL DEFAULT 'unknown',
37602
+ observed_safe_input_tokens INTEGER NOT NULL DEFAULT 0,
37603
+ cache_alert_sent INTEGER NOT NULL DEFAULT 0,
37604
+ times_execute_threshold_reached INTEGER DEFAULT 0,
37605
+ compartment_in_progress INTEGER DEFAULT 0,
37606
+ historian_failure_count INTEGER DEFAULT 0,
37607
+ historian_last_error TEXT DEFAULT NULL,
37608
+ historian_last_failure_at INTEGER DEFAULT NULL,
37609
+ system_prompt_hash TEXT DEFAULT '',
37610
+ memory_block_cache TEXT DEFAULT '',
37611
+ memory_block_count INTEGER DEFAULT 0,
37612
+ memory_block_ids TEXT DEFAULT '',
37613
+ -- pending_compaction_marker_state: intentionally NULLABLE without a
37614
+ -- default. Absence of a deferred marker is SQL NULL; presence is a
37615
+ -- valid JSON blob written via setPendingCompactionMarkerState.
37616
+ -- Excluded from the healAllNullColumns fallback list. Readers filter
37617
+ -- IS NOT NULL AND != empty-string defensively. Plan v6 section 3.
37618
+ pending_compaction_marker_state TEXT,
37619
+ -- Target OpenCode message id used to inject the current compaction marker.
37620
+ -- Nullable for legacy persisted markers; repaired on the next marker move.
37621
+ compaction_marker_target_end_message_id TEXT,
37622
+ -- pending_pi_compaction_marker_state: intentionally NULLABLE without a
37623
+ -- default. Absence of a deferred Pi-native marker is SQL NULL; presence
37624
+ -- is a valid JSON blob written via setPendingPiCompactionMarkerState.
37625
+ -- Excluded from the healAllNullColumns fallback list.
37626
+ pending_pi_compaction_marker_state TEXT,
37627
+ new_work_tokens INTEGER NOT NULL DEFAULT 0,
37628
+ total_input_tokens INTEGER NOT NULL DEFAULT 0,
37629
+ -- deferred_execute_state: intentionally NULLABLE without a default.
37630
+ -- Absence is SQL NULL; presence is a JSON blob written via
37631
+ -- setDeferredExecutePendingIfAbsent. Excluded from the
37632
+ -- healAllNullColumns fallback list.
37633
+ deferred_execute_state TEXT,
37634
+ cached_m0_bytes BLOB,
37635
+ cached_m0_project_memory_epoch INTEGER,
37636
+ cached_m0_workspace_fingerprint TEXT,
37637
+ cached_m0_project_user_profile_version INTEGER,
37638
+ cached_m0_max_compartment_seq INTEGER,
37639
+ cached_m0_max_memory_id INTEGER,
37640
+ cached_m0_max_mutation_id INTEGER,
37641
+ cached_m0_max_memory_mutation_id INTEGER,
37642
+ cached_m0_project_docs_hash TEXT,
37643
+ cached_m1_bytes BLOB,
37644
+ last_observed_model_key TEXT,
37645
+ last_usage_context_limit INTEGER NOT NULL DEFAULT 0,
37646
+ prior_boundary_ordinal INTEGER NOT NULL DEFAULT 1,
37647
+ protected_tail_policy_version INTEGER NOT NULL DEFAULT 0,
37648
+ protected_tail_drain_window_started_at INTEGER NOT NULL DEFAULT 0,
37649
+ protected_tail_drain_tokens INTEGER NOT NULL DEFAULT 0,
37650
+ recovery_no_eligible_head_count INTEGER NOT NULL DEFAULT 0,
37651
+ force_emergency_bypass_window_start INTEGER NOT NULL DEFAULT 0,
37652
+ force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
37653
+ emergency_drain_active INTEGER NOT NULL DEFAULT 0,
37654
+ historian_drain_failure_at INTEGER NOT NULL DEFAULT 0,
37655
+ wrapup_in_progress_state TEXT,
37656
+ compaction_mode_record TEXT,
37657
+ cached_m0_materialized_at INTEGER,
37658
+ cached_m0_session_facts_version INTEGER,
37659
+ cached_m0_upgrade_state TEXT,
37660
+ cached_m0_system_hash TEXT,
37661
+ cached_m0_tool_set_hash TEXT,
37662
+ cached_m0_model_key TEXT,
37663
+ cached_m0_project_identity TEXT,
37664
+ cached_m0_last_baseline_end_message_id TEXT,
37665
+ upgrade_reminded_at INTEGER,
37666
+ pi_stable_id_scheme INTEGER
37667
+ );
37668
+
37669
+ CREATE TABLE IF NOT EXISTS tool_owner_backfill_state (
37670
+ session_id TEXT PRIMARY KEY,
37671
+ status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'skipped')),
37672
+ started_at INTEGER,
37673
+ lease_expires_at INTEGER,
37674
+ completed_at INTEGER,
37675
+ last_error TEXT
37676
+ );
37677
+ CREATE INDEX IF NOT EXISTS idx_tool_owner_backfill_state_status
37678
+ ON tool_owner_backfill_state(status);
37679
+
37680
+ CREATE TABLE IF NOT EXISTS subagent_invocations (
37681
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37682
+ session_id TEXT NOT NULL,
37683
+ harness TEXT NOT NULL,
37684
+ subagent TEXT NOT NULL,
37685
+ task TEXT,
37686
+ provider_id TEXT,
37687
+ model_id TEXT,
37688
+ started_at INTEGER NOT NULL,
37689
+ ended_at INTEGER,
37690
+ status TEXT NOT NULL,
37691
+ input_tokens INTEGER NOT NULL DEFAULT 0,
37692
+ output_tokens INTEGER NOT NULL DEFAULT 0,
37693
+ cache_read_tokens INTEGER NOT NULL DEFAULT 0,
37694
+ cache_write_tokens INTEGER NOT NULL DEFAULT 0,
37695
+ error TEXT,
37696
+ parent_invocation_id INTEGER
37697
+ );
37698
+ CREATE INDEX IF NOT EXISTS idx_sai_session_started
37699
+ ON subagent_invocations(session_id, started_at DESC);
37700
+ CREATE INDEX IF NOT EXISTS idx_sai_subagent
37701
+ ON subagent_invocations(subagent, started_at DESC);
37702
+
37703
+ CREATE TABLE IF NOT EXISTS historian_runs (
37704
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37705
+ session_id TEXT NOT NULL,
37706
+ harness TEXT NOT NULL DEFAULT 'opencode',
37707
+ subagent_invocation_id INTEGER,
37708
+ run_kind TEXT NOT NULL,
37709
+ status TEXT NOT NULL,
37710
+ failure_reason TEXT,
37711
+ chunk_start_ordinal INTEGER,
37712
+ chunk_end_ordinal INTEGER,
37713
+ unprocessed_from INTEGER,
37714
+ compartments_produced INTEGER NOT NULL DEFAULT 0,
37715
+ compartment_id_min INTEGER,
37716
+ compartment_id_max INTEGER,
37717
+ facts_emitted INTEGER NOT NULL DEFAULT 0,
37718
+ facts_by_category_json TEXT,
37719
+ events_emitted INTEGER NOT NULL DEFAULT 0,
37720
+ importance_min INTEGER,
37721
+ importance_max INTEGER,
37722
+ importance_avg REAL,
37723
+ discarded_last INTEGER NOT NULL DEFAULT 0,
37724
+ legacy INTEGER NOT NULL DEFAULT 0,
37725
+ created_at INTEGER NOT NULL
37726
+ );
37727
+ CREATE INDEX IF NOT EXISTS idx_historian_runs_session
37728
+ ON historian_runs(session_id, created_at DESC);
37729
+ CREATE INDEX IF NOT EXISTS idx_historian_runs_status
37730
+ ON historian_runs(status, created_at DESC);
37731
+
37732
+ CREATE TABLE IF NOT EXISTS transform_decisions (
37733
+ session_id TEXT NOT NULL,
37734
+ harness TEXT NOT NULL DEFAULT 'opencode',
37735
+ message_id TEXT NOT NULL,
37736
+ ts_ms INTEGER NOT NULL,
37737
+ decision TEXT NOT NULL,
37738
+ materialized INTEGER NOT NULL DEFAULT 0,
37739
+ materialize_reason TEXT,
37740
+ emergency INTEGER NOT NULL DEFAULT 0,
37741
+ dropped_tokens INTEGER NOT NULL DEFAULT 0,
37742
+ dropped_count INTEGER NOT NULL DEFAULT 0,
37743
+ input_tokens INTEGER NOT NULL DEFAULT 0,
37744
+ PRIMARY KEY (session_id, harness, message_id)
37745
+ );
37746
+ CREATE INDEX IF NOT EXISTS idx_transform_decisions_session_harness
37747
+ ON transform_decisions(session_id, harness);
37748
+
37749
+ CREATE INDEX IF NOT EXISTS idx_tags_session_tag_number ON tags(session_id, tag_number);
37750
+ CREATE INDEX IF NOT EXISTS idx_tags_session_message_id ON tags(session_id, message_id);
37751
+ CREATE INDEX IF NOT EXISTS idx_pending_ops_session ON pending_ops(session_id);
37752
+ CREATE INDEX IF NOT EXISTS idx_pending_ops_session_tag_id ON pending_ops(session_id, tag_id);
37753
+ CREATE INDEX IF NOT EXISTS idx_source_contents_session ON source_contents(session_id);
37754
+
37755
+ CREATE TABLE IF NOT EXISTS recomp_compartments (
37756
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37757
+ session_id TEXT NOT NULL,
37758
+ sequence INTEGER NOT NULL,
37759
+ start_message INTEGER NOT NULL,
37760
+ end_message INTEGER NOT NULL,
37761
+ start_message_id TEXT DEFAULT '',
37762
+ end_message_id TEXT DEFAULT '',
37763
+ title TEXT NOT NULL,
37764
+ content TEXT NOT NULL,
37765
+ p1 TEXT,
37766
+ p2 TEXT,
37767
+ p3 TEXT,
37768
+ p4 TEXT,
37769
+ importance INTEGER NOT NULL DEFAULT 50,
37770
+ episode_type TEXT,
37771
+ pass_number INTEGER NOT NULL,
37772
+ created_at INTEGER NOT NULL,
37773
+ harness TEXT NOT NULL DEFAULT 'opencode',
37774
+ UNIQUE(session_id, sequence)
37775
+ );
37776
+
37777
+ CREATE TABLE IF NOT EXISTS recomp_facts (
37778
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37779
+ session_id TEXT NOT NULL,
37780
+ category TEXT NOT NULL,
37781
+ content TEXT NOT NULL,
37782
+ pass_number INTEGER NOT NULL,
37783
+ created_at INTEGER NOT NULL,
37784
+ harness TEXT NOT NULL DEFAULT 'opencode'
37785
+ );
37786
+
37787
+ CREATE INDEX IF NOT EXISTS idx_session_facts_session ON session_facts(session_id);
37788
+ CREATE INDEX IF NOT EXISTS idx_recomp_compartments_session ON recomp_compartments(session_id);
37789
+ CREATE INDEX IF NOT EXISTS idx_recomp_facts_session ON recomp_facts(session_id);
37790
+ CREATE INDEX IF NOT EXISTS idx_memories_project_status_category ON memories(project_path, status, category);
37791
+ CREATE INDEX IF NOT EXISTS idx_memories_project_status_expires ON memories(project_path, status, expires_at);
37792
+ CREATE INDEX IF NOT EXISTS idx_memories_project_category_hash ON memories(project_path, category, normalized_hash);
37793
+ CREATE INDEX IF NOT EXISTS idx_message_history_index_updated_at ON message_history_index(updated_at);
37794
+ `);
37795
+ ensureColumn(db, "primer_candidates", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
37796
+ ensureColumn(db, "primer_candidates", "source_start_message_id", "TEXT NOT NULL DEFAULT ''");
37797
+ ensureColumn(db, "primer_candidates", "source_end_message_id", "TEXT NOT NULL DEFAULT ''");
37798
+ ensureColumn(db, "primer_candidates", "question_embedding", "BLOB");
37799
+ ensureColumn(db, "primer_candidates", "question_embedding_model_id", "TEXT");
37800
+ ensureColumn(db, "primers", "question_embedding_model_id", "TEXT");
37801
+ db.exec(`
37802
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_primer_candidates_occurrence
37803
+ ON primer_candidates(project_path, harness, session_id, source_start_message_id, source_end_message_id);
37804
+ CREATE INDEX IF NOT EXISTS idx_primer_candidates_project_time
37805
+ ON primer_candidates(project_path, source_message_time);
37806
+ CREATE INDEX IF NOT EXISTS idx_primer_candidates_session
37807
+ ON primer_candidates(session_id, harness);
37808
+ CREATE INDEX IF NOT EXISTS idx_primer_candidates_embedding_model
37809
+ ON primer_candidates(project_path, question_embedding_model_id);
37810
+ CREATE INDEX IF NOT EXISTS idx_primers_project_status_observed
37811
+ ON primers(project_path, status, last_observed_at DESC);
37812
+ CREATE INDEX IF NOT EXISTS idx_primers_embedding_model
37813
+ ON primers(project_path, question_embedding_model_id);
37814
+ CREATE VIRTUAL TABLE IF NOT EXISTS primers_fts USING fts5(
37815
+ question,
37816
+ answer,
37817
+ project_path UNINDEXED,
37818
+ content='primers',
37819
+ content_rowid='id',
37820
+ tokenize='porter unicode61'
37821
+ );
37822
+ CREATE TRIGGER IF NOT EXISTS primers_ai AFTER INSERT ON primers BEGIN
37823
+ INSERT INTO primers_fts(rowid, question, answer, project_path)
37824
+ VALUES (new.id, new.question, new.answer, new.project_path);
37825
+ END;
37826
+ CREATE TRIGGER IF NOT EXISTS primers_ad AFTER DELETE ON primers BEGIN
37827
+ INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
37828
+ VALUES ('delete', old.id, old.question, old.answer, old.project_path);
37829
+ END;
37830
+ CREATE TRIGGER IF NOT EXISTS primers_au AFTER UPDATE ON primers BEGIN
37831
+ INSERT INTO primers_fts(primers_fts, rowid, question, answer, project_path)
37832
+ VALUES ('delete', old.id, old.question, old.answer, old.project_path);
37833
+ INSERT INTO primers_fts(rowid, question, answer, project_path)
37834
+ VALUES (new.id, new.question, new.answer, new.project_path);
37835
+ END;
37836
+ `);
37837
+ ensureColumn(db, "session_meta", "last_nudge_band", "TEXT DEFAULT ''");
37838
+ ensureColumn(db, "session_meta", "last_nudge_undropped", "INTEGER DEFAULT 0");
37839
+ ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
37840
+ ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
37841
+ ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
37842
+ ensureColumn(db, "session_meta", "channel2_nudge_claim_token", "TEXT DEFAULT ''");
37843
+ ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
37844
+ ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
37845
+ ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
37846
+ ensureColumn(db, "session_meta", "nudge_anchor_text", "TEXT DEFAULT ''");
37847
+ ensureColumn(db, "session_meta", "sticky_turn_reminder_text", "TEXT DEFAULT ''");
37848
+ ensureColumn(db, "session_meta", "sticky_turn_reminder_message_id", "TEXT DEFAULT ''");
37849
+ ensureColumn(db, "session_meta", "note_nudge_trigger_pending", "INTEGER DEFAULT 0");
37850
+ ensureColumn(db, "session_meta", "note_nudge_trigger_message_id", "TEXT DEFAULT ''");
37851
+ ensureColumn(db, "session_meta", "note_nudge_sticky_text", "TEXT DEFAULT ''");
37852
+ ensureColumn(db, "session_meta", "note_nudge_sticky_message_id", "TEXT DEFAULT ''");
37853
+ ensureColumn(db, "session_meta", "note_nudge_anchors", "TEXT NOT NULL DEFAULT '[]'");
37854
+ ensureColumn(db, "session_meta", "auto_search_hint_decisions", "TEXT NOT NULL DEFAULT '[]'");
37855
+ ensureColumn(db, "session_meta", "last_todo_state", "TEXT DEFAULT ''");
37856
+ ensureColumn(db, "session_meta", "todo_permission_denied", "INTEGER NOT NULL DEFAULT 2");
37857
+ ensureColumn(db, "session_meta", "todo_synthetic_call_id", "TEXT DEFAULT ''");
37858
+ ensureColumn(db, "session_meta", "todo_synthetic_anchor_message_id", "TEXT DEFAULT ''");
37859
+ ensureColumn(db, "session_meta", "todo_synthetic_state_json", "TEXT DEFAULT ''");
37860
+ ensureColumn(db, "session_meta", "note_last_read_at", "INTEGER DEFAULT 0");
37861
+ ensureColumn(db, "session_meta", "times_execute_threshold_reached", "INTEGER DEFAULT 0");
37862
+ ensureColumn(db, "session_meta", "observed_safe_input_tokens", "INTEGER NOT NULL DEFAULT 0");
37863
+ ensureColumn(db, "session_meta", "cache_alert_sent", "INTEGER NOT NULL DEFAULT 0");
37864
+ ensureColumn(db, "session_meta", "compartment_in_progress", "INTEGER DEFAULT 0");
37865
+ ensureColumn(db, "session_meta", "historian_failure_count", "INTEGER DEFAULT 0");
37866
+ ensureColumn(db, "session_meta", "historian_last_error", "TEXT DEFAULT NULL");
37867
+ ensureColumn(db, "session_meta", "historian_last_failure_at", "INTEGER DEFAULT NULL");
37868
+ ensureColumn(db, "session_meta", "system_prompt_hash", "TEXT DEFAULT ''");
37869
+ ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
37870
+ ensureColumn(db, "session_meta", "tool_reclaim_watermark", "INTEGER DEFAULT 0");
37871
+ ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
37872
+ ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
37873
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
37874
+ ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
37875
+ ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
37876
+ ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
37877
+ ensureColumn(db, "session_meta", "memory_block_cache", "TEXT DEFAULT ''");
37878
+ ensureColumn(db, "session_meta", "memory_block_count", "INTEGER DEFAULT 0");
37879
+ ensureColumn(db, "session_meta", "pi_stable_id_scheme", "INTEGER");
37880
+ ensureColumn(db, "session_meta", "memory_block_ids", "TEXT DEFAULT ''");
37881
+ ensureColumn(db, "dream_queue", "retry_count", "INTEGER DEFAULT 0");
37882
+ ensureColumn(db, "tags", "reasoning_byte_size", "INTEGER DEFAULT 0");
37883
+ ensureColumn(db, "tags", "drop_mode", "TEXT DEFAULT 'full'");
37884
+ ensureColumn(db, "tags", "tool_name", "TEXT");
37885
+ ensureColumn(db, "tags", "input_byte_size", "INTEGER DEFAULT 0");
37886
+ ensureColumn(db, "tags", "caveman_depth", "INTEGER DEFAULT 0");
37887
+ ensureColumn(db, "tags", "tool_owner_message_id", "TEXT DEFAULT NULL");
37888
+ ensureColumn(db, "tags", "entry_fingerprint", "TEXT");
37889
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_adopt
37890
+ ON tags(session_id, entry_fingerprint)
37891
+ WHERE type='message' AND entry_fingerprint IS NOT NULL`);
37892
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_tags_pi_fallback_tool_owner
37893
+ ON tags(session_id, tool_owner_message_id)
37894
+ WHERE type='tool'`);
37895
+ ensureColumn(db, "tags", "token_count", "INTEGER");
37896
+ ensureColumn(db, "tags", "input_token_count", "INTEGER");
37897
+ ensureColumn(db, "tags", "reasoning_token_count", "INTEGER");
37898
+ ensureColumn(db, "task_schedule_state", "schedule", "TEXT");
37899
+ ensureColumn(db, "task_schedule_state", "last_checked_commit", "TEXT");
37900
+ ensureColumn(db, "task_schedule_state", "last_broad_run_at", "INTEGER");
37901
+ ensureColumn(db, "task_schedule_state", "retrospective_watermark_ms", "INTEGER");
37902
+ ensureColumn(db, "dream_runs", "parent_session_id", "TEXT");
37903
+ ensureColumn(db, "session_meta", "system_prompt_tokens", "INTEGER DEFAULT 0");
37904
+ ensureColumn(db, "session_meta", "compaction_marker_state", "TEXT DEFAULT ''");
37905
+ ensureColumn(db, "session_meta", "compaction_marker_target_end_message_id", "TEXT");
37906
+ ensureColumn(db, "session_meta", "key_files", "TEXT DEFAULT ''");
37907
+ ensureColumn(db, "session_meta", "conversation_tokens", "INTEGER DEFAULT 0");
37908
+ ensureColumn(db, "session_meta", "tool_call_tokens", "INTEGER DEFAULT 0");
37909
+ ensureColumn(db, "session_meta", "recomp_partial_range_start", "INTEGER DEFAULT 0");
37910
+ ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
37911
+ ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
37912
+ ensureColumn(db, "session_meta", "detected_context_limit_model_key", "TEXT");
37913
+ ensureColumn(db, "session_meta", "detected_context_limit_provenance", "TEXT NOT NULL DEFAULT 'unknown'");
37914
+ ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
37915
+ ensureColumn(db, "session_meta", "emergency_recovery_origin", "TEXT DEFAULT ''");
37916
+ ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
37917
+ ensureColumn(db, "session_meta", "pending_pi_compaction_marker_state", "TEXT");
37918
+ ensureColumn(db, "session_meta", "new_work_tokens", "INTEGER NOT NULL DEFAULT 0");
37919
+ ensureColumn(db, "session_meta", "total_input_tokens", "INTEGER NOT NULL DEFAULT 0");
37920
+ ensureColumn(db, "session_meta", "deferred_execute_state", "TEXT");
37921
+ ensureColumn(db, "compartments", "p1", "TEXT");
37922
+ ensureColumn(db, "compartments", "p2", "TEXT");
37923
+ ensureColumn(db, "compartments", "p3", "TEXT");
37924
+ ensureColumn(db, "compartments", "p4", "TEXT");
37925
+ ensureColumn(db, "compartments", "importance", "INTEGER NOT NULL DEFAULT 50");
37926
+ ensureColumn(db, "compartments", "episode_type", "TEXT");
37927
+ ensureColumn(db, "compartments", "p1_embedding", "BLOB");
37928
+ ensureColumn(db, "compartments", "p1_embedding_model_id", "TEXT");
37929
+ ensureColumn(db, "compartments", "legacy", "INTEGER NOT NULL DEFAULT 0");
37930
+ ensureColumn(db, "recomp_compartments", "p1", "TEXT");
37931
+ ensureColumn(db, "recomp_compartments", "p2", "TEXT");
37932
+ ensureColumn(db, "recomp_compartments", "p3", "TEXT");
37933
+ ensureColumn(db, "recomp_compartments", "p4", "TEXT");
37934
+ ensureColumn(db, "recomp_compartments", "importance", "INTEGER NOT NULL DEFAULT 50");
37935
+ ensureColumn(db, "recomp_compartments", "episode_type", "TEXT");
37936
+ ensureColumn(db, "memories", "importance", "INTEGER");
37937
+ ensureColumn(db, "memories", "classified_at", "INTEGER");
37938
+ ensureColumn(db, "memories", "mural_cue", "TEXT");
37939
+ ensureColumn(db, "memories", "mural_cue_hash", "TEXT");
37940
+ ensureColumn(db, "memories", "mural_cue_at", "INTEGER");
37941
+ ensureColumn(db, "memory_verifications", "mapped_at", "INTEGER NOT NULL DEFAULT 0");
37942
+ ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
37943
+ ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
37944
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
37945
+ ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
37946
+ ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
37947
+ ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
37948
+ ensureColumn(db, "session_meta", "cached_m0_max_mutation_id", "INTEGER");
37949
+ ensureColumn(db, "session_meta", "cached_m0_max_memory_mutation_id", "INTEGER");
37950
+ ensureColumn(db, "session_meta", "cached_m0_project_docs_hash", "TEXT");
37951
+ ensureColumn(db, "session_meta", "cached_m1_bytes", "BLOB");
37952
+ ensureColumn(db, "session_meta", "last_observed_model_key", "TEXT");
37953
+ ensureColumn(db, "session_meta", "last_usage_context_limit", "INTEGER NOT NULL DEFAULT 0");
37954
+ ensureColumn(db, "session_meta", "prior_boundary_ordinal", "INTEGER NOT NULL DEFAULT 1");
37955
+ ensureColumn(db, "session_meta", "protected_tail_policy_version", "INTEGER NOT NULL DEFAULT 0");
37956
+ ensureColumn(db, "session_meta", "protected_tail_drain_window_started_at", "INTEGER NOT NULL DEFAULT 0");
37957
+ ensureColumn(db, "session_meta", "protected_tail_drain_tokens", "INTEGER NOT NULL DEFAULT 0");
37958
+ ensureColumn(db, "session_meta", "recovery_no_eligible_head_count", "INTEGER NOT NULL DEFAULT 0");
37959
+ ensureColumn(db, "session_meta", "force_emergency_bypass_window_start", "INTEGER NOT NULL DEFAULT 0");
37960
+ ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
37961
+ ensureColumn(db, "session_meta", "emergency_drain_active", "INTEGER NOT NULL DEFAULT 0");
37962
+ ensureColumn(db, "session_meta", "historian_drain_failure_at", "INTEGER NOT NULL DEFAULT 0");
37963
+ ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
37964
+ ensureColumn(db, "session_meta", "compaction_mode_record", "TEXT");
37965
+ ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
37966
+ ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
37967
+ ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
37968
+ ensureColumn(db, "session_meta", "cached_m0_system_hash", "TEXT");
37969
+ ensureColumn(db, "session_meta", "cached_m0_tool_set_hash", "TEXT");
37970
+ ensureColumn(db, "session_meta", "cached_m0_model_key", "TEXT");
37971
+ ensureColumn(db, "session_meta", "cached_m0_project_identity", "TEXT");
37972
+ ensureColumn(db, "session_meta", "cached_m0_last_baseline_end_message_id", "TEXT");
37973
+ ensureColumn(db, "session_meta", "upgrade_reminded_at", "INTEGER");
37974
+ ensureColumn(db, "session_meta", "upgrade_reminder_last_sent_at", "INTEGER");
37975
+ ensureColumn(db, "session_meta", "upgrade_reminder_count", "INTEGER NOT NULL DEFAULT 0");
37976
+ ensureColumn(db, "session_meta", "cached_m0_mural_data_url", "TEXT");
37977
+ ensureColumn(db, "session_meta", "cached_m0_mural_hash", "TEXT");
37978
+ db.exec(`
37979
+ CREATE TABLE IF NOT EXISTS project_state (
37980
+ project_path TEXT PRIMARY KEY,
37981
+ project_memory_epoch INTEGER NOT NULL DEFAULT 0,
37982
+ project_user_profile_version INTEGER NOT NULL DEFAULT 0,
37983
+ updated_at INTEGER NOT NULL DEFAULT 0
37984
+ );
37985
+ CREATE TABLE IF NOT EXISTS session_projects (
37986
+ session_id TEXT NOT NULL,
37987
+ harness TEXT NOT NULL DEFAULT 'opencode',
37988
+ project_path TEXT NOT NULL,
37989
+ updated_at INTEGER NOT NULL,
37990
+ PRIMARY KEY(session_id, harness)
37991
+ );
37992
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
37993
+ ON session_projects(project_path);
37994
+ CREATE TABLE IF NOT EXISTS m0_mutation_log (
37995
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37996
+ session_id TEXT NOT NULL,
37997
+ mutation_type TEXT NOT NULL CHECK (mutation_type IN (
37998
+ 'compartment_delete', 'compartment_merge', 'recomp_boundary_change', 'compartment_upgrade'
37999
+ )),
38000
+ target_id INTEGER,
38001
+ queued_at INTEGER NOT NULL
38002
+ );
38003
+ CREATE INDEX IF NOT EXISTS idx_m0_mutation_log_session ON m0_mutation_log(session_id);
38004
+ CREATE TABLE IF NOT EXISTS memory_mutation_log (
38005
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38006
+ project_path TEXT NOT NULL,
38007
+ mutation_type TEXT NOT NULL CHECK (mutation_type IN ('archive', 'delete', 'update', 'superseded')),
38008
+ target_memory_id INTEGER NOT NULL,
38009
+ superseded_by_id INTEGER,
38010
+ category TEXT,
38011
+ new_content TEXT,
38012
+ queued_at INTEGER NOT NULL
38013
+ );
38014
+ CREATE INDEX IF NOT EXISTS idx_memory_mutation_log_project
38015
+ ON memory_mutation_log(project_path, id);
38016
+ CREATE TABLE IF NOT EXISTS v22_identity_rekey_map (
38017
+ old_project_path TEXT PRIMARY KEY,
38018
+ new_project_path TEXT NOT NULL,
38019
+ rekeyed_at INTEGER NOT NULL
38020
+ );
38021
+ CREATE TABLE IF NOT EXISTS identity_merge_log (
38022
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38023
+ from_identity TEXT NOT NULL,
38024
+ to_identity TEXT NOT NULL,
38025
+ table_name TEXT NOT NULL,
38026
+ row_id TEXT NOT NULL,
38027
+ action TEXT NOT NULL,
38028
+ target_row_id TEXT,
38029
+ merged_at INTEGER NOT NULL
38030
+ );
38031
+ CREATE INDEX IF NOT EXISTS idx_identity_merge_log_identities
38032
+ ON identity_merge_log(from_identity, to_identity, merged_at);
38033
+ CREATE INDEX IF NOT EXISTS idx_identity_merge_log_table_row
38034
+ ON identity_merge_log(table_name, row_id);
38035
+ CREATE TABLE IF NOT EXISTS workspaces (
38036
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38037
+ name TEXT NOT NULL UNIQUE,
38038
+ created_at INTEGER NOT NULL,
38039
+ updated_at INTEGER NOT NULL,
38040
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
38041
+ );
38042
+ CREATE TABLE IF NOT EXISTS workspace_members (
38043
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
38044
+ project_path TEXT NOT NULL,
38045
+ display_name TEXT NOT NULL,
38046
+ display_path TEXT NOT NULL,
38047
+ added_at INTEGER NOT NULL,
38048
+ PRIMARY KEY (workspace_id, project_path)
38049
+ );
38050
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
38051
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
38052
+ CREATE TABLE IF NOT EXISTS v22_backfill_failures (
38053
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38054
+ table_name TEXT NOT NULL,
38055
+ row_id INTEGER NOT NULL,
38056
+ raw_project_path TEXT NOT NULL,
38057
+ error_class TEXT NOT NULL CHECK (error_class IN ('not_git_repo', 'git_missing', 'git_timeout', 'permission_denied', 'unknown')),
38058
+ error_message TEXT,
38059
+ failed_at INTEGER NOT NULL,
38060
+ UNIQUE(table_name, row_id)
38061
+ );
38062
+ CREATE TABLE IF NOT EXISTS transform_decisions (
38063
+ session_id TEXT NOT NULL,
38064
+ harness TEXT NOT NULL DEFAULT 'opencode',
38065
+ message_id TEXT NOT NULL,
38066
+ ts_ms INTEGER NOT NULL,
38067
+ decision TEXT NOT NULL,
38068
+ materialized INTEGER NOT NULL DEFAULT 0,
38069
+ materialize_reason TEXT,
38070
+ emergency INTEGER NOT NULL DEFAULT 0,
38071
+ dropped_tokens INTEGER NOT NULL DEFAULT 0,
38072
+ dropped_count INTEGER NOT NULL DEFAULT 0,
38073
+ input_tokens INTEGER NOT NULL DEFAULT 0,
38074
+ PRIMARY KEY (session_id, harness, message_id)
38075
+ );
38076
+ CREATE INDEX IF NOT EXISTS idx_transform_decisions_session_harness
38077
+ ON transform_decisions(session_id, harness);
38078
+ `);
38079
+ ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38080
+ ensureColumn(db, "message_history_index", "dirty_floor_ordinal", "INTEGER NOT NULL DEFAULT 0");
38081
+ ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38082
+ ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38083
+ ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38084
+ ensureColumn(db, "compression_depth", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38085
+ ensureColumn(db, "session_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38086
+ ensureColumn(db, "session_meta", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38087
+ ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38088
+ ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38089
+ ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
38090
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
38091
+ }
38092
+ var databases, pendingAsyncOpens, persistenceByDatabase, persistenceErrorByDatabase, pathByDatabase, LATEST_SUPPORTED_VERSION = 76, PERMISSIONS_ENFORCEABLE, RPC_DISCOVERY_PARSE_GRACE_MS, defaultRpcDiscoveryFs, rpcDiscoveryFs, sqlitePragmaConfig;
36456
38093
  var init_storage_db = __esm(async () => {
36457
38094
  init_data_path();
36458
38095
  init_logger();
@@ -36473,11 +38110,15 @@ var init_storage_db = __esm(async () => {
36473
38110
  RPC_DISCOVERY_PARSE_GRACE_MS = 10 * 60 * 1000;
36474
38111
  defaultRpcDiscoveryFs = {
36475
38112
  readdirSync: (path4, options) => options?.withFileTypes ? readdirSync2(path4, { withFileTypes: true }) : readdirSync2(path4),
36476
- readFileSync: (path4, encoding) => String(readFileSync12(path4, encoding)),
38113
+ readFileSync: (path4, encoding) => String(readFileSync13(path4, encoding)),
36477
38114
  statSync: (path4) => ({ mtimeMs: statSync7(path4).mtimeMs }),
36478
38115
  unlinkSync: (path4) => unlinkSync2(path4)
36479
38116
  };
36480
38117
  rpcDiscoveryFs = defaultRpcDiscoveryFs;
38118
+ sqlitePragmaConfig = {
38119
+ cacheSizeMb: 64,
38120
+ mmapSizeMb: 0
38121
+ };
36481
38122
  });
36482
38123
 
36483
38124
  // src/lib/database-access.ts
@@ -36784,13 +38425,39 @@ function mergeMemoryRow(db, row, fromIdentity, toIdentity, mergedAt) {
36784
38425
  const sourceId = row.id;
36785
38426
  if (typeof sourceId !== "number")
36786
38427
  return false;
36787
- const collision = db.prepare(`SELECT id, seen_count
38428
+ const collision = db.prepare(`SELECT *
36788
38429
  FROM memories
36789
38430
  WHERE project_path = ? AND category = ? AND normalized_hash = ? AND id <> ?
36790
38431
  LIMIT 1`).get(toIdentity, row.category, row.normalized_hash, sourceId);
36791
38432
  if (collision && typeof collision.id === "number") {
38433
+ const targetId = collision.id;
36792
38434
  const mergedSeen = Math.max(Number(collision.seen_count ?? 1), Number(row.seen_count ?? 1));
36793
- db.prepare("UPDATE memories SET seen_count = ?, status = COALESCE(status, 'active') WHERE id = ?").run(mergedSeen, collision.id);
38435
+ const sourceClassifiedAt = Number(row.classified_at ?? 0);
38436
+ const targetClassifiedAt = Number(collision.classified_at ?? 0);
38437
+ if (sourceClassifiedAt > targetClassifiedAt) {
38438
+ db.prepare(`UPDATE memories
38439
+ SET importance = ?, scope = ?, shareable = ?, classified_at = ?
38440
+ WHERE id = ?`).run(row.importance, row.scope, row.shareable, row.classified_at, targetId);
38441
+ }
38442
+ const sourceHasCue = typeof row.mural_cue === "string" && row.mural_cue.length > 0;
38443
+ const targetHasCue = typeof collision.mural_cue === "string" && collision.mural_cue.length > 0;
38444
+ const sourceCueAt = Number(row.mural_cue_at ?? 0);
38445
+ const targetCueAt = Number(collision.mural_cue_at ?? 0);
38446
+ if (sourceHasCue && (!targetHasCue || sourceCueAt > targetCueAt)) {
38447
+ db.prepare(`UPDATE memories
38448
+ SET mural_cue = ?, mural_cue_hash = ?, mural_cue_at = ?,
38449
+ mural_cue_rejection_count = ?
38450
+ WHERE id = ?`).run(row.mural_cue, row.mural_cue_hash, row.mural_cue_at, row.mural_cue_rejection_count, targetId);
38451
+ }
38452
+ db.prepare(`INSERT INTO memory_verifications (memory_id, file_path, verified_at, mapped_at)
38453
+ SELECT ?, file_path, verified_at, mapped_at
38454
+ FROM memory_verifications
38455
+ WHERE memory_id = ?
38456
+ ON CONFLICT(memory_id, file_path) DO UPDATE SET
38457
+ verified_at = MAX(memory_verifications.verified_at, excluded.verified_at),
38458
+ mapped_at = MAX(memory_verifications.mapped_at, excluded.mapped_at)`).run(targetId, sourceId);
38459
+ db.prepare("DELETE FROM memory_verifications WHERE memory_id = ?").run(sourceId);
38460
+ db.prepare("UPDATE memories SET seen_count = ?, status = COALESCE(status, 'active') WHERE id = ?").run(mergedSeen, targetId);
36794
38461
  db.prepare(`UPDATE memories
36795
38462
  SET status = 'archived',
36796
38463
  superseded_by_memory_id = ?,
@@ -36799,11 +38466,11 @@ function mergeMemoryRow(db, row, fromIdentity, toIdentity, mergedAt) {
36799
38466
  ELSE merged_from || ',' || ?
36800
38467
  END,
36801
38468
  updated_at = ?
36802
- WHERE id = ? AND project_path = ?`).run(collision.id, String(sourceId), "identity-merge", mergedAt, sourceId, fromIdentity);
38469
+ WHERE id = ? AND project_path = ?`).run(targetId, String(sourceId), "identity-merge", mergedAt, sourceId, fromIdentity);
36803
38470
  db.prepare(`INSERT INTO memory_mutation_log
36804
38471
  (project_path, mutation_type, target_memory_id, superseded_by_id, category, queued_at)
36805
- VALUES (?, 'superseded', ?, ?, ?, ?)`).run(fromIdentity, sourceId, collision.id, row.category, mergedAt);
36806
- logRow(db, fromIdentity, toIdentity, "memories", String(sourceId), "superseded", String(collision.id), mergedAt);
38472
+ VALUES (?, 'superseded', ?, ?, ?, ?)`).run(fromIdentity, sourceId, targetId, row.category, mergedAt);
38473
+ logRow(db, fromIdentity, toIdentity, "memories", String(sourceId), "superseded", String(targetId), mergedAt);
36807
38474
  return true;
36808
38475
  }
36809
38476
  const result = db.prepare("UPDATE memories SET project_path = ? WHERE id = ? AND project_path = ?").run(toIdentity, sourceId, fromIdentity);
@@ -36812,10 +38479,30 @@ function mergeMemoryRow(db, row, fromIdentity, toIdentity, mergedAt) {
36812
38479
  logRow(db, fromIdentity, toIdentity, "memories", String(sourceId), "rekeyed", null, mergedAt);
36813
38480
  return true;
36814
38481
  }
38482
+ function nullableMaximum(left, right) {
38483
+ const values = [left, right].filter((value) => typeof value === "number");
38484
+ return values.length > 0 ? Math.max(...values) : null;
38485
+ }
38486
+ function oldestOpenCycleStart(left, right) {
38487
+ const values = [left, right].filter((value) => typeof value === "number" && value > 0);
38488
+ return values.length > 0 ? Math.min(...values) : null;
38489
+ }
38490
+ function reconcileTaskScheduleCollision(db, source, target) {
38491
+ const sourceIsNewer = typeof source.last_run_at === "number" && (typeof target.last_run_at !== "number" || source.last_run_at > target.last_run_at);
38492
+ const latest = sourceIsNewer ? source : target;
38493
+ db.prepare(`UPDATE task_schedule_state
38494
+ SET last_run_at = ?, next_due_at = ?, schedule = ?, last_status = ?,
38495
+ last_error = ?, retry_count = ?, last_checked_commit = ?,
38496
+ last_broad_run_at = ?, retrospective_watermark_ms = ?
38497
+ WHERE project_path = ? AND task = ?`).run(nullableMaximum(source.last_run_at, target.last_run_at), latest.next_due_at, latest.schedule, latest.last_status, latest.last_error, latest.retry_count, latest.last_checked_commit, oldestOpenCycleStart(source.last_broad_run_at, target.last_broad_run_at), nullableMaximum(source.retrospective_watermark_ms, target.retrospective_watermark_ms), target.project_path, target.task);
38498
+ }
36815
38499
  function rekeyGenericRow(db, table, row, fromIdentity, toIdentity, mergedAt) {
36816
38500
  const rowId = rowKey(db, table.name, row);
36817
38501
  const collision = findUniqueCollision(db, table, row, fromIdentity, toIdentity);
36818
38502
  if (collision) {
38503
+ if (table.name === "task_schedule_state") {
38504
+ reconcileTaskScheduleCollision(db, row, collision);
38505
+ }
36819
38506
  const sourcePredicate = rowPredicate(db, table.name, row);
36820
38507
  const result2 = db.prepare(`DELETE FROM ${quoteIdentifier(table.name)} WHERE ${sourcePredicate.sql}`).run(...sourcePredicate.values);
36821
38508
  if ((result2.changes ?? 0) === 0)
@@ -37010,12 +38697,522 @@ var init_doctor_merge_identity = __esm(async () => {
37010
38697
  await init_database_access();
37011
38698
  });
37012
38699
 
38700
+ // src/commands/doctor-repair-db.ts
38701
+ var exports_doctor_repair_db = {};
38702
+ __export(exports_doctor_repair_db, {
38703
+ runRepairDbCli: () => runRepairDbCli,
38704
+ runRepairDb: () => runRepairDb,
38705
+ defaultSqliteExecutable: () => defaultSqliteExecutable,
38706
+ REPAIR_DB_EXIT: () => REPAIR_DB_EXIT
38707
+ });
38708
+ import { spawnSync as spawnSync3 } from "node:child_process";
38709
+ import {
38710
+ chmodSync as chmodSync3,
38711
+ closeSync as closeSync2,
38712
+ constants as constants2,
38713
+ copyFileSync as copyFileSync2,
38714
+ existsSync as existsSync20,
38715
+ openSync as openSync2,
38716
+ renameSync as renameSync3,
38717
+ rmSync as rmSync2,
38718
+ statSync as statSync8
38719
+ } from "node:fs";
38720
+ import { dirname as dirname11, join as join16 } from "node:path";
38721
+ function defaultInspectHolders(storageDir) {
38722
+ const rpc = inspectRpcServerDiscovery(storageDir);
38723
+ if (rpc.state === "unreadable") {
38724
+ const arm = rpc.unreadableArm === "parse" ? "could not be parsed" : "could not be read";
38725
+ return {
38726
+ safe: false,
38727
+ blockers: [],
38728
+ uncertainty: `RPC discovery ${rpc.unreadableFile ?? join16(storageDir, "rpc")} ${arm}`
38729
+ };
38730
+ }
38731
+ const blockers = rpc.state === "live" ? rpc.serverPids.map((pid) => `OpenCode server (PID ${pid})`) : [];
38732
+ const pi = inspectLivePiProcesses();
38733
+ if (pi.state === "unreadable") {
38734
+ return {
38735
+ safe: false,
38736
+ blockers,
38737
+ uncertainty: `Pi/OMP process liveness could not be determined: ${pi.error ?? "process list unavailable"}`
38738
+ };
38739
+ }
38740
+ blockers.push(...pi.processIds.map((pid) => `Pi/OMP harness (PID ${pid})`));
38741
+ return { safe: blockers.length === 0, blockers };
38742
+ }
38743
+ function defaultSqliteExecutable() {
38744
+ return process.env.MAGIC_CONTEXT_SQLITE3 ?? "sqlite3";
38745
+ }
38746
+ function timestamp(date5) {
38747
+ return date5.toISOString().replaceAll("-", "").replaceAll(":", "").replace(".", "");
38748
+ }
38749
+ function uniqueBase(preferred) {
38750
+ if (!DATABASE_SUFFIXES.some((suffix) => existsSync20(`${preferred}${suffix}`)))
38751
+ return preferred;
38752
+ for (let attempt = 1;attempt < 1e4; attempt++) {
38753
+ const candidate = `${preferred}-${attempt}`;
38754
+ if (!DATABASE_SUFFIXES.some((suffix) => existsSync20(`${candidate}${suffix}`)))
38755
+ return candidate;
38756
+ }
38757
+ throw new Error(`Could not allocate a unique recovery path beside ${preferred}`);
38758
+ }
38759
+ function copyBackupBundle(dbPath, stamp) {
38760
+ const basePath = uniqueBase(`${dbPath}.corrupt-backup-${stamp}`);
38761
+ const copiedPaths = copyDatabaseBundle(dbPath, basePath);
38762
+ return { basePath, copiedPaths };
38763
+ }
38764
+ function copyDatabaseBundle(sourceBase, destinationBase) {
38765
+ const copiedPaths = [];
38766
+ for (const suffix of DATABASE_SUFFIXES) {
38767
+ const source = `${sourceBase}${suffix}`;
38768
+ if (!existsSync20(source))
38769
+ continue;
38770
+ const destination = `${destinationBase}${suffix}`;
38771
+ copyFileSync2(source, destination, constants2.COPYFILE_EXCL);
38772
+ copiedPaths.push(destination);
38773
+ }
38774
+ return copiedPaths;
38775
+ }
38776
+ function readRowCounts(path4) {
38777
+ const counts = Object.fromEntries(ROW_COUNT_TABLES.map((table) => [table, null]));
38778
+ let db = null;
38779
+ try {
38780
+ db = new Database(path4, { readonly: true });
38781
+ for (const table of ROW_COUNT_TABLES) {
38782
+ try {
38783
+ const row = db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get();
38784
+ counts[table] = typeof row?.count === "number" ? row.count : null;
38785
+ } catch {
38786
+ counts[table] = null;
38787
+ }
38788
+ }
38789
+ } catch {} finally {
38790
+ db?.close();
38791
+ }
38792
+ return counts;
38793
+ }
38794
+ function reportCounts(prompts, label, counts) {
38795
+ prompts.log.info(`Row counts ${label} recovery:`);
38796
+ for (const table of ROW_COUNT_TABLES) {
38797
+ prompts.log.info(` ${table}=${counts[table] ?? "n/a"}`);
38798
+ }
38799
+ }
38800
+ function reportSchemaTransition(prompts, result) {
38801
+ prompts.log.info(`Schema migration: v${result.schemaVersionBefore ?? "unknown"} → v${result.schemaVersionAfter ?? "unknown"}`);
38802
+ }
38803
+ function reportSalvageRates(prompts, before, after) {
38804
+ prompts.log.info("Salvage rates (readable rows before → rows after):");
38805
+ for (const table of ROW_COUNT_TABLES) {
38806
+ const beforeCount = before[table];
38807
+ const afterCount = after[table];
38808
+ if (beforeCount === null || afterCount === null) {
38809
+ prompts.log.info(` ${table}: n/a`);
38810
+ continue;
38811
+ }
38812
+ const rate = beforeCount === 0 ? afterCount === 0 ? 100 : 0 : afterCount / beforeCount * 100;
38813
+ const lost = Math.max(0, beforeCount - afterCount);
38814
+ prompts.log.info(` ${table}: ${beforeCount} → ${afterCount} (${rate.toFixed(1)}%, lost ${lost})`);
38815
+ }
38816
+ }
38817
+ function integrityErrors(db) {
38818
+ const rows = db.prepare("PRAGMA integrity_check").all();
38819
+ return rows.map((row) => String(row.integrity_check ?? "unknown")).filter((result) => result !== "ok");
38820
+ }
38821
+ function sqliteError(result) {
38822
+ if (result.error)
38823
+ return result.error.message;
38824
+ const stderr = String(result.stderr ?? "").trim();
38825
+ if (stderr)
38826
+ return stderr;
38827
+ return `sqlite3 exited with status ${String(result.status)}`;
38828
+ }
38829
+ function isRecoverCapabilityMissing(errorDetail) {
38830
+ return RECOVER_UNAVAILABLE_PATTERNS.some((pattern) => pattern.test(errorDetail));
38831
+ }
38832
+ function runRecoverShell(sqliteExecutable, sourcePath, recoveredPath, dumpPath) {
38833
+ let dumpWriteFd = null;
38834
+ try {
38835
+ dumpWriteFd = openSync2(dumpPath, "wx", 384);
38836
+ const recovered = spawnSync3(sqliteExecutable, [sourcePath, ".recover"], {
38837
+ stdio: ["ignore", dumpWriteFd, "pipe"],
38838
+ encoding: "utf8",
38839
+ windowsHide: true
38840
+ });
38841
+ closeSync2(dumpWriteFd);
38842
+ dumpWriteFd = null;
38843
+ if (recovered.error || recovered.status !== 0) {
38844
+ const errorDetail = sqliteError(recovered);
38845
+ const attempted = recovered.error === undefined;
38846
+ return {
38847
+ ok: false,
38848
+ detail: `.recover failed: ${errorDetail}`,
38849
+ attempted,
38850
+ unavailable: attempted && isRecoverCapabilityMissing(errorDetail)
38851
+ };
38852
+ }
38853
+ const dumpReadFd = openSync2(dumpPath, "r");
38854
+ try {
38855
+ const replayed = spawnSync3(sqliteExecutable, [recoveredPath], {
38856
+ stdio: [dumpReadFd, "ignore", "pipe"],
38857
+ encoding: "utf8",
38858
+ windowsHide: true
38859
+ });
38860
+ if (replayed.error || replayed.status !== 0) {
38861
+ return {
38862
+ ok: false,
38863
+ detail: `replaying .recover output failed: ${sqliteError(replayed)}`,
38864
+ attempted: true,
38865
+ unavailable: false
38866
+ };
38867
+ }
38868
+ } finally {
38869
+ closeSync2(dumpReadFd);
38870
+ }
38871
+ return { ok: true };
38872
+ } catch (error51) {
38873
+ return {
38874
+ ok: false,
38875
+ detail: error51 instanceof Error ? error51.message : String(error51),
38876
+ attempted: false,
38877
+ unavailable: false
38878
+ };
38879
+ } finally {
38880
+ if (dumpWriteFd !== null)
38881
+ closeSync2(dumpWriteFd);
38882
+ rmSync2(dumpPath, { force: true });
38883
+ }
38884
+ }
38885
+ function migrateAndCheckRecoveredDatabase(path4) {
38886
+ let db = null;
38887
+ try {
38888
+ db = new Database(path4);
38889
+ const recognized = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name IN (${RECOGNIZABLE_TABLES.map(() => "?").join(",")})`).all(...RECOGNIZABLE_TABLES);
38890
+ if (recognized.length === 0) {
38891
+ return {
38892
+ ok: false,
38893
+ detail: ".recover found no recognizable Magic Context schema or user tables"
38894
+ };
38895
+ }
38896
+ const schemaVersionBefore = getPersistedSchemaVersion(db);
38897
+ initializeDatabase(db);
38898
+ runMigrations(db);
38899
+ ensureContextStoreUuid(db);
38900
+ const schemaVersionAfter = getPersistedSchemaVersion(db);
38901
+ const errors3 = integrityErrors(db);
38902
+ if (errors3.length > 0) {
38903
+ return {
38904
+ ok: false,
38905
+ detail: `recovered database integrity_check failed: ${errors3.slice(0, 3).join("; ")}`
38906
+ };
38907
+ }
38908
+ const afterCounts = readCountsFromOpenDatabase(db);
38909
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
38910
+ db.exec("PRAGMA journal_mode=DELETE");
38911
+ return { ok: true, afterCounts, schemaVersionBefore, schemaVersionAfter };
38912
+ } catch (error51) {
38913
+ return {
38914
+ ok: false,
38915
+ detail: error51 instanceof Error ? error51.message : String(error51)
38916
+ };
38917
+ } finally {
38918
+ db?.close();
38919
+ }
38920
+ }
38921
+ function readCountsFromOpenDatabase(db) {
38922
+ const counts = Object.fromEntries(ROW_COUNT_TABLES.map((table) => [table, null]));
38923
+ for (const table of ROW_COUNT_TABLES) {
38924
+ try {
38925
+ const row = db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get();
38926
+ counts[table] = typeof row?.count === "number" ? row.count : null;
38927
+ } catch {
38928
+ counts[table] = null;
38929
+ }
38930
+ }
38931
+ return counts;
38932
+ }
38933
+ function prepareFreshDatabase(path4) {
38934
+ let db = null;
38935
+ try {
38936
+ db = new Database(path4);
38937
+ const schemaVersionBefore = getPersistedSchemaVersion(db);
38938
+ initializeDatabase(db);
38939
+ runMigrations(db);
38940
+ ensureContextStoreUuid(db);
38941
+ const schemaVersionAfter = getPersistedSchemaVersion(db);
38942
+ const errors3 = integrityErrors(db);
38943
+ if (errors3.length > 0) {
38944
+ return {
38945
+ ok: false,
38946
+ detail: `fresh database integrity_check failed: ${errors3.join("; ")}`
38947
+ };
38948
+ }
38949
+ const afterCounts = readCountsFromOpenDatabase(db);
38950
+ db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
38951
+ db.exec("PRAGMA journal_mode=DELETE");
38952
+ return { ok: true, afterCounts, schemaVersionBefore, schemaVersionAfter };
38953
+ } catch (error51) {
38954
+ return { ok: false, detail: error51 instanceof Error ? error51.message : String(error51) };
38955
+ } finally {
38956
+ db?.close();
38957
+ }
38958
+ }
38959
+ function removeRecoveryBundle(basePath) {
38960
+ for (const suffix of DATABASE_SUFFIXES)
38961
+ rmSync2(`${basePath}${suffix}`, { force: true });
38962
+ }
38963
+ function activateReplacement(dbPath, replacementPath, originalAsidePath) {
38964
+ const movedOriginals = [];
38965
+ try {
38966
+ for (const suffix of DATABASE_SUFFIXES) {
38967
+ const from = `${dbPath}${suffix}`;
38968
+ if (!existsSync20(from))
38969
+ continue;
38970
+ const to = `${originalAsidePath}${suffix}`;
38971
+ renameSync3(from, to);
38972
+ movedOriginals.push({ from, to });
38973
+ }
38974
+ renameSync3(replacementPath, dbPath);
38975
+ return movedOriginals.map(({ to }) => to);
38976
+ } catch (error51) {
38977
+ for (const moved of movedOriginals.reverse()) {
38978
+ if (existsSync20(moved.to) && !existsSync20(moved.from))
38979
+ renameSync3(moved.to, moved.from);
38980
+ }
38981
+ throw error51;
38982
+ }
38983
+ }
38984
+ function reportSafetyRefusal(prompts, dbPath, inspection, backupBase) {
38985
+ prompts.log.error(`Refusing to repair the live database: ${dbPath}`);
38986
+ if (inspection.blockers.length > 0) {
38987
+ prompts.log.error(`Active database holder(s): ${inspection.blockers.join(", ")}`);
38988
+ }
38989
+ if (inspection.uncertainty)
38990
+ prompts.log.error(inspection.uncertainty);
38991
+ prompts.log.info("Close every OpenCode, Pi, and OMP process, then run the command again.");
38992
+ if (backupBase)
38993
+ prompts.log.info(`Backup base: ${backupBase}`);
38994
+ else
38995
+ prompts.log.info("Backup: not created (repair refused before database access)");
38996
+ prompts.outro("Database repair refused; the original database files were not modified");
38997
+ return REPAIR_DB_EXIT.refused;
38998
+ }
38999
+ function printHelp() {
39000
+ console.log("Usage: magic-context doctor repair-db");
39001
+ console.log("");
39002
+ console.log("Back up and salvage the shared context.db using SQLite .recover.");
39003
+ console.log("If salvage is impossible, an empty reset is offered with a separate confirmation.");
39004
+ console.log("Salvage needs a sqlite3 shell built with SQLITE_ENABLE_DBPAGE_VTAB; without one, the command backs up and stops without modifying the database.");
39005
+ }
39006
+ async function runRepairDb(options = {}) {
39007
+ const prompts = options.prompts ?? promptIO;
39008
+ const deps = { ...DEFAULT_DEPS, ...options.deps };
39009
+ const storageDir = options.storageDir ?? dirname11(options.dbPath ?? join16(getMagicContextStorageDir(), "context.db"));
39010
+ const dbPath = options.dbPath ?? join16(storageDir, "context.db");
39011
+ const stamp = timestamp(deps.now());
39012
+ prompts.intro("Magic Context — Repair shared database");
39013
+ prompts.log.info(`Database: ${dbPath}`);
39014
+ if (!existsSync20(dbPath)) {
39015
+ prompts.log.error(`Database not found: ${dbPath}`);
39016
+ prompts.log.info("Backup: not created (database does not exist)");
39017
+ prompts.outro("Database repair failed");
39018
+ return REPAIR_DB_EXIT.failed;
39019
+ }
39020
+ const initialInspection = deps.inspectHolders(storageDir);
39021
+ if (!initialInspection.safe)
39022
+ return reportSafetyRefusal(prompts, dbPath, initialInspection);
39023
+ let backup;
39024
+ try {
39025
+ backup = copyBackupBundle(dbPath, stamp);
39026
+ } catch (error51) {
39027
+ prompts.log.error(`Could not complete the required backup: ${error51 instanceof Error ? error51.message : String(error51)}`);
39028
+ prompts.log.info(`Backup base: ${dbPath}.corrupt-backup-${stamp}`);
39029
+ prompts.outro("Database repair stopped before salvage");
39030
+ return REPAIR_DB_EXIT.failed;
39031
+ }
39032
+ for (const path4 of backup.copiedPaths)
39033
+ prompts.log.info(`Backup: ${path4}`);
39034
+ const workingBase = uniqueBase(`${dbPath}.recovering-${stamp}-${process.pid}`);
39035
+ const countSourcePath = `${workingBase}.count-source`;
39036
+ const recoverSourcePath = `${workingBase}.recover-source`;
39037
+ try {
39038
+ copyDatabaseBundle(backup.basePath, countSourcePath);
39039
+ } catch (error51) {
39040
+ removeRecoveryBundle(countSourcePath);
39041
+ prompts.log.error(`Could not prepare the isolated row-count snapshot: ${error51 instanceof Error ? error51.message : String(error51)}`);
39042
+ prompts.log.info(`Database remains unchanged: ${dbPath}`);
39043
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39044
+ prompts.outro("Database repair stopped before salvage");
39045
+ return REPAIR_DB_EXIT.failed;
39046
+ }
39047
+ const beforeCounts = readRowCounts(countSourcePath);
39048
+ removeRecoveryBundle(countSourcePath);
39049
+ reportCounts(prompts, "BEFORE", beforeCounts);
39050
+ try {
39051
+ copyDatabaseBundle(backup.basePath, recoverSourcePath);
39052
+ } catch (error51) {
39053
+ removeRecoveryBundle(recoverSourcePath);
39054
+ prompts.log.error(`Could not prepare the isolated salvage snapshot: ${error51 instanceof Error ? error51.message : String(error51)}`);
39055
+ prompts.log.info(`Database remains unchanged: ${dbPath}`);
39056
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39057
+ prompts.outro("Database repair stopped before salvage");
39058
+ return REPAIR_DB_EXIT.failed;
39059
+ }
39060
+ const recoveredPath = `${workingBase}.db`;
39061
+ const dumpPath = `${workingBase}.sql`;
39062
+ prompts.log.info(`Attempting SQLite .recover into fresh database: ${recoveredPath}`);
39063
+ const salvage = runRecoverShell(deps.sqliteExecutable, recoverSourcePath, recoveredPath, dumpPath);
39064
+ removeRecoveryBundle(recoverSourcePath);
39065
+ if (!salvage.ok && !salvage.attempted) {
39066
+ removeRecoveryBundle(recoveredPath);
39067
+ const unavailableAfter2 = Object.fromEntries(ROW_COUNT_TABLES.map((table) => [table, null]));
39068
+ reportCounts(prompts, "AFTER", unavailableAfter2);
39069
+ reportSalvageRates(prompts, beforeCounts, unavailableAfter2);
39070
+ prompts.log.error(`SQLite .recover could not be started: ${salvage.detail}`);
39071
+ prompts.log.info("Install the SQLite command-line shell (sqlite3), then rerun this command. Reset was not offered because salvage did not run.");
39072
+ prompts.log.info(`Database remains unchanged: ${dbPath}`);
39073
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39074
+ prompts.outro("Database repair stopped before salvage could run");
39075
+ return REPAIR_DB_EXIT.failed;
39076
+ }
39077
+ if (!salvage.ok && salvage.unavailable) {
39078
+ removeRecoveryBundle(recoveredPath);
39079
+ const unavailableAfter2 = Object.fromEntries(ROW_COUNT_TABLES.map((table) => [table, null]));
39080
+ reportCounts(prompts, "AFTER", unavailableAfter2);
39081
+ reportSalvageRates(prompts, beforeCounts, unavailableAfter2);
39082
+ prompts.log.error(`SQLite .recover is unavailable on this machine: ${salvage.detail}`);
39083
+ prompts.log.info("This sqlite3 was built without SQLITE_ENABLE_DBPAGE_VTAB, the compile-time option .recover needs. That is a limitation of the tool, not a verdict about your data: the database may be perfectly salvageable with a full sqlite3 build. Install the official SQLite command-line binary (sqlite.org/download.html) or a distro package built with SQLITE_ENABLE_DBPAGE_VTAB, then rerun this command. Reset was not offered because salvage did not run.");
39084
+ prompts.log.info(`Database remains unchanged: ${dbPath}`);
39085
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39086
+ prompts.outro("Database repair stopped: this sqlite3 cannot run .recover");
39087
+ return REPAIR_DB_EXIT.failed;
39088
+ }
39089
+ let salvageResult;
39090
+ if (salvage.ok) {
39091
+ salvageResult = migrateAndCheckRecoveredDatabase(recoveredPath);
39092
+ } else {
39093
+ salvageResult = salvage;
39094
+ }
39095
+ if (salvageResult.ok && salvageResult.afterCounts) {
39096
+ const finalInspection = deps.inspectHolders(storageDir);
39097
+ if (!finalInspection.safe) {
39098
+ removeRecoveryBundle(recoveredPath);
39099
+ return reportSafetyRefusal(prompts, dbPath, finalInspection, backup.basePath);
39100
+ }
39101
+ try {
39102
+ const originalMode = statSync8(dbPath).mode & 511;
39103
+ chmodSync3(recoveredPath, originalMode);
39104
+ const originalAsidePath = uniqueBase(`${dbPath}.corrupt-original-${stamp}`);
39105
+ const moved = activateReplacement(dbPath, recoveredPath, originalAsidePath);
39106
+ reportSchemaTransition(prompts, salvageResult);
39107
+ reportCounts(prompts, "AFTER", salvageResult.afterCounts);
39108
+ reportSalvageRates(prompts, beforeCounts, salvageResult.afterCounts);
39109
+ for (const path4 of moved)
39110
+ prompts.log.info(`Corrupt original preserved: ${path4}`);
39111
+ prompts.log.success(`Salvaged database installed: ${dbPath}`);
39112
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39113
+ prompts.outro("Database salvage complete; restart your harness");
39114
+ return REPAIR_DB_EXIT.salvaged;
39115
+ } catch (error51) {
39116
+ salvageResult = {
39117
+ ok: false,
39118
+ detail: `could not install recovered database: ${error51 instanceof Error ? error51.message : String(error51)}`
39119
+ };
39120
+ }
39121
+ }
39122
+ removeRecoveryBundle(recoveredPath);
39123
+ const unavailableAfter = Object.fromEntries(ROW_COUNT_TABLES.map((table) => [table, null]));
39124
+ reportCounts(prompts, "AFTER", unavailableAfter);
39125
+ reportSalvageRates(prompts, beforeCounts, unavailableAfter);
39126
+ prompts.log.error(`SQLite salvage was unsuccessful: ${salvageResult.detail ?? "unknown error"}`);
39127
+ prompts.log.info(`Database remains unchanged: ${dbPath}`);
39128
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39129
+ const confirmed = await prompts.confirm("Salvage failed. Move the corrupt database aside and create a fresh empty database? This discards all unrecovered data from the active database.", false);
39130
+ if (!confirmed) {
39131
+ prompts.log.info("Reset declined. The corrupt database remains in place.");
39132
+ prompts.outro("Database is unsalvageable by this command; backup retained");
39133
+ return REPAIR_DB_EXIT.unsalvageable;
39134
+ }
39135
+ const resetInspection = deps.inspectHolders(storageDir);
39136
+ if (!resetInspection.safe) {
39137
+ return reportSafetyRefusal(prompts, dbPath, resetInspection, backup.basePath);
39138
+ }
39139
+ const freshPath = `${workingBase}.fresh.db`;
39140
+ const fresh = prepareFreshDatabase(freshPath);
39141
+ if (!fresh.ok || !fresh.afterCounts) {
39142
+ removeRecoveryBundle(freshPath);
39143
+ prompts.log.error(`Could not create a fresh database: ${fresh.detail ?? "unknown error"}`);
39144
+ prompts.log.info(`Database remains unchanged: ${dbPath}`);
39145
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39146
+ prompts.outro("Database reset failed");
39147
+ return REPAIR_DB_EXIT.failed;
39148
+ }
39149
+ try {
39150
+ const originalMode = statSync8(dbPath).mode & 511;
39151
+ chmodSync3(freshPath, originalMode);
39152
+ const originalAsidePath = uniqueBase(`${dbPath}.corrupt-original-${stamp}`);
39153
+ const moved = activateReplacement(dbPath, freshPath, originalAsidePath);
39154
+ reportSchemaTransition(prompts, fresh);
39155
+ reportCounts(prompts, "AFTER", fresh.afterCounts);
39156
+ reportSalvageRates(prompts, beforeCounts, fresh.afterCounts);
39157
+ for (const path4 of moved)
39158
+ prompts.log.info(`Corrupt original preserved: ${path4}`);
39159
+ prompts.log.success(`Fresh database installed after explicit reset confirmation: ${dbPath}`);
39160
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39161
+ prompts.outro("Database reset complete; restart your harness");
39162
+ return REPAIR_DB_EXIT.salvaged;
39163
+ } catch (error51) {
39164
+ prompts.log.error(`Could not install the fresh database: ${error51 instanceof Error ? error51.message : String(error51)}`);
39165
+ prompts.log.info(`Database remains unchanged: ${dbPath}`);
39166
+ prompts.log.info(`Backup base: ${backup.basePath}`);
39167
+ prompts.outro("Database reset failed");
39168
+ return REPAIR_DB_EXIT.failed;
39169
+ }
39170
+ }
39171
+ async function runRepairDbCli(args, options = {}) {
39172
+ if (args.includes("--help") || args.includes("-h")) {
39173
+ printHelp();
39174
+ return REPAIR_DB_EXIT.salvaged;
39175
+ }
39176
+ return runRepairDb(options);
39177
+ }
39178
+ var ROW_COUNT_TABLES, DATABASE_SUFFIXES, RECOGNIZABLE_TABLES, REPAIR_DB_EXIT, DEFAULT_DEPS, RECOVER_UNAVAILABLE_PATTERNS;
39179
+ var init_doctor_repair_db = __esm(async () => {
39180
+ init_migrations();
39181
+ init_data_path();
39182
+ init_rpc_utils();
39183
+ init_prompts();
39184
+ await __promiseAll([
39185
+ init_context_authority(),
39186
+ init_storage_db(),
39187
+ init_sqlite()
39188
+ ]);
39189
+ ROW_COUNT_TABLES = ["tags", "compartments", "memories", "notes", "dream_runs"];
39190
+ DATABASE_SUFFIXES = ["", "-wal", "-shm"];
39191
+ RECOGNIZABLE_TABLES = [...ROW_COUNT_TABLES, "schema_migrations"];
39192
+ REPAIR_DB_EXIT = {
39193
+ salvaged: 0,
39194
+ failed: 1,
39195
+ unsalvageable: 2,
39196
+ refused: 3
39197
+ };
39198
+ DEFAULT_DEPS = {
39199
+ now: () => new Date,
39200
+ sqliteExecutable: defaultSqliteExecutable(),
39201
+ inspectHolders: defaultInspectHolders
39202
+ };
39203
+ RECOVER_UNAVAILABLE_PATTERNS = [
39204
+ /no such table: sqlite_dbpage/i,
39205
+ /no such module: sqlite_dbpage/i,
39206
+ /unknown command[^\n]*\.recover/i
39207
+ ];
39208
+ });
39209
+
37013
39210
  // src/lib/migration-paths.ts
37014
39211
  import { homedir as homedir12 } from "node:os";
37015
- import { join as join16 } from "node:path";
39212
+ import { join as join17 } from "node:path";
37016
39213
  function getOpenCodeDatabasePath() {
37017
39214
  const dataHome = process.env.XDG_DATA_HOME?.trim();
37018
- return join16(dataHome || join16(homedir12(), ".local", "share"), "opencode", "opencode.db");
39215
+ return join17(dataHome || join17(homedir12(), ".local", "share"), "opencode", "opencode.db");
37019
39216
  }
37020
39217
  function projectPathToPiSessionSlug(projectPath, platform = process.platform) {
37021
39218
  const separators = platform === "win32" ? /[:\\/]+/g : /\/+/g;
@@ -37038,12 +39235,12 @@ __export(exports_migrate, {
37038
39235
  });
37039
39236
  import { randomBytes as randomBytes2 } from "node:crypto";
37040
39237
  import { unlinkSync as unlinkSync3 } from "node:fs";
37041
- import { join as join17 } from "node:path";
39238
+ import { join as join18 } from "node:path";
37042
39239
  function defaultOpenCodeDbPath() {
37043
39240
  return getOpenCodeDatabasePath();
37044
39241
  }
37045
39242
  function defaultCortexkitDbPath() {
37046
- return join17(getMagicContextStorageDir(), "context.db");
39243
+ return join18(getMagicContextStorageDir(), "context.db");
37047
39244
  }
37048
39245
  function defaultPiSessionsRoot() {
37049
39246
  return getPiSessionsRoot();
@@ -37138,11 +39335,11 @@ function tokensToPiUsage(tokens) {
37138
39335
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
37139
39336
  };
37140
39337
  }
37141
- function makeMessageEntry(role, text3, timestamp, parentId, usage) {
39338
+ function makeMessageEntry(role, text3, timestamp2, parentId, usage) {
37142
39339
  const message = {
37143
39340
  role,
37144
39341
  content: [{ type: "text", text: text3 }],
37145
- timestamp: Date.parse(timestamp)
39342
+ timestamp: Date.parse(timestamp2)
37146
39343
  };
37147
39344
  if (role === "assistant") {
37148
39345
  message.usage = usage;
@@ -37151,30 +39348,30 @@ function makeMessageEntry(role, text3, timestamp, parentId, usage) {
37151
39348
  type: "message",
37152
39349
  id: shortId(),
37153
39350
  parentId,
37154
- timestamp,
39351
+ timestamp: timestamp2,
37155
39352
  message
37156
39353
  };
37157
39354
  }
37158
- function makeThinkingEntry(text3, timestamp, parentId, usage) {
39355
+ function makeThinkingEntry(text3, timestamp2, parentId, usage) {
37159
39356
  return {
37160
39357
  type: "message",
37161
39358
  id: shortId(),
37162
39359
  parentId,
37163
- timestamp,
39360
+ timestamp: timestamp2,
37164
39361
  message: {
37165
39362
  role: "assistant",
37166
39363
  content: [{ type: "thinking", thinking: text3, thinkingSignature: null }],
37167
- timestamp: Date.parse(timestamp),
39364
+ timestamp: Date.parse(timestamp2),
37168
39365
  usage
37169
39366
  }
37170
39367
  };
37171
39368
  }
37172
- function makeToolCallEntry(tool, timestamp, parentId, usage) {
39369
+ function makeToolCallEntry(tool, timestamp2, parentId, usage) {
37173
39370
  return {
37174
39371
  type: "message",
37175
39372
  id: shortId(),
37176
39373
  parentId,
37177
- timestamp,
39374
+ timestamp: timestamp2,
37178
39375
  message: {
37179
39376
  role: "assistant",
37180
39377
  content: [
@@ -37185,24 +39382,24 @@ function makeToolCallEntry(tool, timestamp, parentId, usage) {
37185
39382
  arguments: tool.input ?? {}
37186
39383
  }
37187
39384
  ],
37188
- timestamp: Date.parse(timestamp),
39385
+ timestamp: Date.parse(timestamp2),
37189
39386
  usage
37190
39387
  }
37191
39388
  };
37192
39389
  }
37193
- function makeToolResultEntry(tool, timestamp, parentId) {
39390
+ function makeToolResultEntry(tool, timestamp2, parentId) {
37194
39391
  return {
37195
39392
  type: "message",
37196
39393
  id: shortId(),
37197
39394
  parentId,
37198
- timestamp,
39395
+ timestamp: timestamp2,
37199
39396
  message: {
37200
39397
  role: "toolResult",
37201
39398
  toolCallId: tool.callId,
37202
39399
  toolName: tool.name,
37203
39400
  content: [{ type: "text", text: textFromUnknown(tool.output) }],
37204
39401
  isError: false,
37205
- timestamp: Date.parse(timestamp)
39402
+ timestamp: Date.parse(timestamp2)
37206
39403
  }
37207
39404
  };
37208
39405
  }
@@ -37277,7 +39474,7 @@ The following conversation was migrated from a different harness. Reasoning cont
37277
39474
  const role = roleFromMessage(message);
37278
39475
  if (!role)
37279
39476
  continue;
37280
- const timestamp = isoFromMs(message.time_created, params.now);
39477
+ const timestamp2 = isoFromMs(message.time_created, params.now);
37281
39478
  const tokens = tokensFromMessage(message);
37282
39479
  const usage = tokensToPiUsage(tokens);
37283
39480
  let lastEntryIdForMessage = null;
@@ -37285,7 +39482,7 @@ The following conversation was migrated from a different harness. Reasoning cont
37285
39482
  const newEntries = convertPartToEntries({
37286
39483
  role,
37287
39484
  row: part,
37288
- timestamp,
39485
+ timestamp: timestamp2,
37289
39486
  parentId,
37290
39487
  usage
37291
39488
  });
@@ -37509,7 +39706,7 @@ function migrateOpenCodeSessionToPi(opts) {
37509
39706
  const provider = opts.provider ?? model.provider;
37510
39707
  const modelId = opts.modelId ?? model.modelId;
37511
39708
  const cwd = session.directory ?? session.path ?? process.cwd();
37512
- const outputDir = join17(piSessionsRoot, projectPathToPiDirSlug(cwd));
39709
+ const outputDir = join18(piSessionsRoot, projectPathToPiDirSlug(cwd));
37513
39710
  const buildResult = buildPiEntries({
37514
39711
  session,
37515
39712
  messages,
@@ -37538,7 +39735,7 @@ function migrateOpenCodeSessionToPi(opts) {
37538
39735
  commitMagicContextState = plan.commit;
37539
39736
  }
37540
39737
  const compactionMarker = insertCompactionMarker(buildResult.entries, copyResult.lastCompartmentEndPiEntryId);
37541
- const outputPath = join17(outputDir, `${formatPiFilenameTimestamp(now)}_${buildResult.piSessionId}.jsonl`);
39738
+ const outputPath = join18(outputDir, `${formatPiFilenameTimestamp(now)}_${buildResult.piSessionId}.jsonl`);
37542
39739
  const jsonl = `${buildResult.entries.map((entry) => JSON.stringify(entry)).join(`
37543
39740
  `)}
37544
39741
  `;
@@ -37784,9 +39981,9 @@ __export(exports_migrate_session, {
37784
39981
  planMigrateSession: () => planMigrateSession,
37785
39982
  applyMigrateSession: () => applyMigrateSession
37786
39983
  });
37787
- import { existsSync as existsSync20, realpathSync as realpathSync4 } from "node:fs";
39984
+ import { existsSync as existsSync21, realpathSync as realpathSync4 } from "node:fs";
37788
39985
  import { homedir as homedir13 } from "node:os";
37789
- import path4, { join as join18 } from "node:path";
39986
+ import path4, { join as join19 } from "node:path";
37790
39987
  function existingSessionColumns(db) {
37791
39988
  const rows = db.prepare("PRAGMA table_info(session)").all();
37792
39989
  return new Set(rows.map((r2) => r2.name).filter((n2) => typeof n2 === "string"));
@@ -37940,14 +40137,14 @@ function defaultOpenCodeDbPath2() {
37940
40137
  return getOpenCodeDatabasePath();
37941
40138
  }
37942
40139
  function defaultContextDbPath() {
37943
- return join18(getMagicContextStorageDir(), "context.db");
40140
+ return join19(getMagicContextStorageDir(), "context.db");
37944
40141
  }
37945
40142
  function realDeps(opencodeDb, contextDb) {
37946
40143
  return {
37947
40144
  opencodeDb,
37948
40145
  contextDb,
37949
40146
  resolveIdentity: resolveProjectIdentity,
37950
- hasGitDir: (dir) => existsSync20(join18(dir, ".git")),
40147
+ hasGitDir: (dir) => existsSync21(join19(dir, ".git")),
37951
40148
  realpath: (p2) => realpathSync4(p2)
37952
40149
  };
37953
40150
  }
@@ -38025,8 +40222,8 @@ async function runMigrateSessionCli(args) {
38025
40222
  printMigrateSessionHelp();
38026
40223
  return 1;
38027
40224
  }
38028
- const expandedTo = toDir.startsWith("~") ? join18(homedir13(), toDir.slice(1).replace(/^[/\\]/, "")) : toDir;
38029
- if (!existsSync20(expandedTo)) {
40225
+ const expandedTo = toDir.startsWith("~") ? join19(homedir13(), toDir.slice(1).replace(/^[/\\]/, "")) : toDir;
40226
+ if (!existsSync21(expandedTo)) {
38030
40227
  console.error(`Target directory does not exist: ${expandedTo}`);
38031
40228
  return 1;
38032
40229
  }
@@ -38383,7 +40580,7 @@ var init_v22_backfill_commands = __esm(async () => {
38383
40580
  });
38384
40581
 
38385
40582
  // ../pi-plugin/src/config/index.ts
38386
- import { existsSync as existsSync21, readFileSync as readFileSync13 } from "node:fs";
40583
+ import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
38387
40584
  function getProjectConfigPaths(cwd) {
38388
40585
  const basePath = cortexKitProjectConfigBasePath(cwd);
38389
40586
  return [`${basePath}.jsonc`, `${basePath}.json`];
@@ -38393,25 +40590,28 @@ function getUserConfigPaths() {
38393
40590
  return [`${basePath}.jsonc`, `${basePath}.json`];
38394
40591
  }
38395
40592
  function resolveFirstExisting(paths) {
38396
- return paths.find((path6) => existsSync21(path6));
40593
+ return paths.find((path6) => existsSync22(path6));
38397
40594
  }
38398
40595
  function resolvePiLegacyFallback(sources) {
38399
- return sources.find((source) => existsSync21(source.path)) ?? null;
40596
+ return sources.find((source) => existsSync22(source.path)) ?? null;
38400
40597
  }
38401
40598
  function loadConfigFile(path6, scope) {
38402
40599
  try {
38403
- const rawText = readFileSync13(path6, "utf-8");
40600
+ const rawText = readFileSync14(path6, "utf-8");
38404
40601
  const substituted = substituteConfigVariables({
38405
40602
  text: rawText,
38406
40603
  configPath: path6,
38407
40604
  isProjectConfig: scope === "project"
38408
40605
  });
40606
+ const rejectedKeyPaths = [];
40607
+ const config2 = sanitizeParsedJson(import_comment_json7.parse(substituted.text), { onRejectedKey: (keyPath) => rejectedKeyPaths.push(keyPath.join(".")) });
40608
+ const unsafeKeyWarnings = rejectedKeyPaths.map((keyPath) => `Ignored unsafe config key "${keyPath}" (security: prototype-pollution keys are not allowed).`);
38409
40609
  return {
38410
40610
  path: path6,
38411
40611
  scope,
38412
- config: import_comment_json7.parse(substituted.text),
38413
- warnings: substituted.warnings.map((warning) => `${path6}: ${warning}`),
38414
- loadOutcome: substituted.warnings.length > 0 ? "substitution-failure" : "ok"
40612
+ config: config2,
40613
+ warnings: [...substituted.warnings, ...unsafeKeyWarnings].map((warning) => `${path6}: ${warning}`),
40614
+ loadOutcome: rejectedKeyPaths.length > 0 ? "schema-recovery" : substituted.warnings.length > 0 ? "substitution-failure" : "ok"
38415
40615
  };
38416
40616
  } catch (error51) {
38417
40617
  const message = error51 instanceof Error ? error51.message : String(error51);
@@ -38449,11 +40649,28 @@ function redactConfigValue2(value) {
38449
40649
  function isPlainObject4(value) {
38450
40650
  return typeof value === "object" && value !== null && !Array.isArray(value);
38451
40651
  }
40652
+ function defineOwnConfigValue2(target, key, value) {
40653
+ Object.defineProperty(target, key, {
40654
+ value,
40655
+ enumerable: true,
40656
+ configurable: true,
40657
+ writable: true
40658
+ });
40659
+ }
38452
40660
  function mergeRawConfigs(base, override) {
38453
- const merged = { ...base };
38454
- for (const [key, overrideValue] of Object.entries(override)) {
38455
- const baseValue = merged[key];
38456
- merged[key] = isPlainObject4(baseValue) && isPlainObject4(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
40661
+ const merged = {};
40662
+ for (const key of Object.keys(base)) {
40663
+ if (isPrototypePollutionKey(key))
40664
+ continue;
40665
+ defineOwnConfigValue2(merged, key, base[key]);
40666
+ }
40667
+ for (const key of Object.keys(override)) {
40668
+ if (isPrototypePollutionKey(key))
40669
+ continue;
40670
+ const overrideValue = override[key];
40671
+ const baseValue = Object.hasOwn(base, key) ? base[key] : undefined;
40672
+ const mergedValue = isPlainObject4(baseValue) && isPlainObject4(overrideValue) ? mergeRawConfigs(baseValue, overrideValue) : overrideValue;
40673
+ defineOwnConfigValue2(merged, key, mergedValue);
38457
40674
  }
38458
40675
  return merged;
38459
40676
  }
@@ -38533,7 +40750,7 @@ function loadPiConfig(opts = {}) {
38533
40750
  if (loaded)
38534
40751
  loadedFiles.push(loaded);
38535
40752
  }
38536
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync21(source.path));
40753
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync22(source.path));
38537
40754
  const userPath = resolveFirstExisting(getUserConfigPaths());
38538
40755
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
38539
40756
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -38542,7 +40759,7 @@ function loadPiConfig(opts = {}) {
38542
40759
  if (loaded)
38543
40760
  loadedFiles.push(loaded);
38544
40761
  }
38545
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync21(source.path));
40762
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync22(source.path));
38546
40763
  if (userLegacyFallback) {
38547
40764
  warnings.push(`[user config] reading legacy config from ${userLegacyFallback.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`);
38548
40765
  } else if (legacyUserUnmigrated) {
@@ -38602,10 +40819,17 @@ var init_config2 = __esm(() => {
38602
40819
  init_project_security();
38603
40820
  init_magic_context();
38604
40821
  init_variable();
40822
+ init_jsonc_parser();
38605
40823
  init_models_dev_cache();
38606
40824
  import_comment_json7 = __toESM(require_src3(), 1);
38607
40825
  });
38608
40826
 
40827
+ // src/lib/database-repair-guidance.ts
40828
+ function formatDatabaseRepairGuidance(dbPath) {
40829
+ return `Database: ${dbPath}. Recovery: run \`${DATABASE_REPAIR_COMMAND}\` (salvage needs a sqlite3 shell built with SQLITE_ENABLE_DBPAGE_VTAB; without one, the command backs up and stops without modifying the database).`;
40830
+ }
40831
+ var DATABASE_REPAIR_COMMAND = "bunx @cortexkit/magic-context@latest doctor repair-db";
40832
+
38609
40833
  // ../plugin/src/shared/redaction.ts
38610
40834
  import { homedir as homedir14, userInfo } from "node:os";
38611
40835
  function escapeRegex2(value) {
@@ -38815,10 +41039,10 @@ var init_redaction2 = __esm(() => {
38815
41039
  });
38816
41040
 
38817
41041
  // src/commands/doctor-omp.ts
38818
- import { execFileSync as execFileSync5, spawnSync as spawnSync3 } from "node:child_process";
38819
- import { existsSync as existsSync22, mkdirSync as mkdirSync8, readFileSync as readFileSync14 } from "node:fs";
38820
- import { createRequire as createRequire3 } from "node:module";
38821
- import { dirname as dirname11, join as join19, resolve as resolve5 } from "node:path";
41042
+ import { execFileSync as execFileSync5, spawnSync as spawnSync4 } from "node:child_process";
41043
+ import { existsSync as existsSync23, mkdirSync as mkdirSync8, readFileSync as readFileSync15 } from "node:fs";
41044
+ import { createRequire as createRequire2 } from "node:module";
41045
+ import { dirname as dirname12, join as join20, resolve as resolve5 } from "node:path";
38822
41046
  function parseSemver(value) {
38823
41047
  const match = value?.match(/(\d+)\.(\d+)\.(\d+)/);
38824
41048
  return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
@@ -38849,7 +41073,7 @@ function printResult(prompts, result) {
38849
41073
  prompts.log.error(line);
38850
41074
  }
38851
41075
  function selfVersion() {
38852
- const req = createRequire3(import.meta.url);
41076
+ const req = createRequire2(import.meta.url);
38853
41077
  for (const path6 of ["../../package.json", "../package.json"]) {
38854
41078
  try {
38855
41079
  const value = req(path6);
@@ -38860,10 +41084,10 @@ function selfVersion() {
38860
41084
  return "unknown";
38861
41085
  }
38862
41086
  function readConfig2(path6) {
38863
- if (!existsSync22(path6))
41087
+ if (!existsSync23(path6))
38864
41088
  return {};
38865
41089
  try {
38866
- const parsed = import_comment_json8.parse(readFileSync14(path6, "utf-8"));
41090
+ const parsed = import_comment_json8.parse(readFileSync15(path6, "utf-8"));
38867
41091
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? {} : { error: "top level is not an object" };
38868
41092
  } catch (error51) {
38869
41093
  return { error: error51 instanceof Error ? error51.message : String(error51) };
@@ -38873,7 +41097,7 @@ function pluginDeclaresOmp(path6) {
38873
41097
  if (!path6)
38874
41098
  return null;
38875
41099
  try {
38876
- const pkg = JSON.parse(readFileSync14(join19(path6, "package.json"), "utf-8"));
41100
+ const pkg = JSON.parse(readFileSync15(join20(path6, "package.json"), "utf-8"));
38877
41101
  return Array.isArray(pkg.omp?.extensions) || Array.isArray(pkg.pi?.extensions);
38878
41102
  } catch {
38879
41103
  return null;
@@ -38954,7 +41178,7 @@ async function runHealthChecks(options) {
38954
41178
  }
38955
41179
  }
38956
41180
  const userConfigPath = getOmpUserConfigPath();
38957
- if (!existsSync22(userConfigPath)) {
41181
+ if (!existsSync23(userConfigPath)) {
38958
41182
  add(results, "warn", `No Magic Context user config at ${userConfigPath}`);
38959
41183
  repairPlan.writeUserConfig = true;
38960
41184
  } else {
@@ -38970,8 +41194,8 @@ async function runHealthChecks(options) {
38970
41194
  else
38971
41195
  for (const warning of loaded.warnings.slice(0, 5))
38972
41196
  add(results, "warn", warning);
38973
- const dbPath = join19(getMagicContextStorageDir(), "context.db");
38974
- if (!existsSync22(dbPath))
41197
+ const dbPath = join20(getMagicContextStorageDir(), "context.db");
41198
+ if (!existsSync23(dbPath))
38975
41199
  add(results, "info", `Shared context DB will be created at ${dbPath}`);
38976
41200
  else {
38977
41201
  let db = null;
@@ -38981,9 +41205,11 @@ async function runHealthChecks(options) {
38981
41205
  if (integrity?.integrity_check === "ok")
38982
41206
  add(results, "pass", "SQLite integrity_check: ok");
38983
41207
  else
38984
- add(results, "fail", `SQLite integrity_check: ${String(integrity?.integrity_check)}`);
41208
+ add(results, "fail", `SQLite integrity_check: ${String(integrity?.integrity_check)}
41209
+ ${formatDatabaseRepairGuidance(dbPath)}`);
38985
41210
  } catch (error51) {
38986
- add(results, "fail", `Could not inspect shared DB: ${String(error51)}`);
41211
+ add(results, "fail", `Could not inspect shared DB: ${String(error51)}
41212
+ ${formatDatabaseRepairGuidance(dbPath)}`);
38987
41213
  } finally {
38988
41214
  db?.close();
38989
41215
  }
@@ -38998,7 +41224,7 @@ async function runHealthChecks(options) {
38998
41224
  add(results, "info", `OMP non-global config: ${source}`);
38999
41225
  }
39000
41226
  const logPath = getMagicContextLogPath2("pi");
39001
- add(results, "info", `Pi-compatible runtime log: ${logPath}${existsSync22(logPath) ? "" : " (not created yet)"}`);
41227
+ add(results, "info", `Pi-compatible runtime log: ${logPath}${existsSync23(logPath) ? "" : " (not created yet)"}`);
39002
41228
  if (!options.quiet)
39003
41229
  for (const result of results)
39004
41230
  printResult(options.prompts, result);
@@ -39011,7 +41237,7 @@ async function runHealthChecks(options) {
39011
41237
  };
39012
41238
  }
39013
41239
  function writeDefaultConfig(path6) {
39014
- mkdirSync8(dirname11(path6), { recursive: true });
41240
+ mkdirSync8(dirname12(path6), { recursive: true });
39015
41241
  const config2 = {
39016
41242
  $schema: "https://raw.githubusercontent.com/cortexkit/magic-context/master/assets/magic-context.schema.json",
39017
41243
  ...MagicContextConfigSchema.parse({})
@@ -39021,7 +41247,7 @@ function writeDefaultConfig(path6) {
39021
41247
  }
39022
41248
  async function repair(plan, deps, prompts, cwd) {
39023
41249
  let fixed = 0;
39024
- if (plan.writeUserConfig && !existsSync22(getOmpUserConfigPath())) {
41250
+ if (plan.writeUserConfig && !existsSync23(getOmpUserConfigPath())) {
39025
41251
  writeDefaultConfig(getOmpUserConfigPath());
39026
41252
  prompts.log.success(`Wrote default Magic Context config to ${getOmpUserConfigPath()}`);
39027
41253
  fixed += 1;
@@ -39057,7 +41283,7 @@ async function repair(plan, deps, prompts, cwd) {
39057
41283
  }
39058
41284
  return fixed;
39059
41285
  }
39060
- function timestamp(date5) {
41286
+ function timestamp2(date5) {
39061
41287
  return date5.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
39062
41288
  }
39063
41289
  async function runIssueFlow(options) {
@@ -39079,7 +41305,7 @@ async function runIssueFlow(options) {
39079
41305
  ...report.results.map((result) => `- ${result.status.toUpperCase()}: ${sanitizeDiagnosticText(result.message)}`)
39080
41306
  ].join(`
39081
41307
  `);
39082
- const path6 = join19(options.cwd, `magic-context-omp-issue-${timestamp(options.deps.now())}.md`);
41308
+ const path6 = join20(options.cwd, `magic-context-omp-issue-${timestamp2(options.deps.now())}.md`);
39083
41309
  writeFileAtomic(path6, `${body}
39084
41310
  `);
39085
41311
  options.prompts.log.success(`Sanitized report written to ${path6}`);
@@ -39115,8 +41341,8 @@ async function runIssueFlow(options) {
39115
41341
  }
39116
41342
  async function runDoctor(options = {}) {
39117
41343
  const deps = {
39118
- ...DEFAULT_DEPS,
39119
- prompts: options.prompts ?? DEFAULT_DEPS.prompts,
41344
+ ...DEFAULT_DEPS2,
41345
+ prompts: options.prompts ?? DEFAULT_DEPS2.prompts,
39120
41346
  ...options.deps
39121
41347
  };
39122
41348
  const prompts = options.prompts ?? deps.prompts;
@@ -39142,7 +41368,7 @@ async function runDoctor(options = {}) {
39142
41368
  prompts.log.message(`Summary: PASS ${second.pass} / WARN ${second.warn} / FAIL ${second.fail}`);
39143
41369
  return second.fail === 0 ? 0 : 1;
39144
41370
  }
39145
- var import_comment_json8, MIN_OMP_VERSION = "17.1.7", DEFAULT_DEPS;
41371
+ var import_comment_json8, MIN_OMP_VERSION = "17.1.7", DEFAULT_DEPS2;
39146
41372
  var init_doctor_omp = __esm(async () => {
39147
41373
  init_magic_context();
39148
41374
  init_data_path();
@@ -39156,7 +41382,7 @@ var init_doctor_omp = __esm(async () => {
39156
41382
  init_redaction2();
39157
41383
  await init_database_access();
39158
41384
  import_comment_json8 = __toESM(require_src3(), 1);
39159
- DEFAULT_DEPS = {
41385
+ DEFAULT_DEPS2 = {
39160
41386
  prompts: promptIO,
39161
41387
  detectOmpBinary,
39162
41388
  getOmpVersion,
@@ -39166,7 +41392,7 @@ var init_doctor_omp = __esm(async () => {
39166
41392
  openExistingContextDatabase,
39167
41393
  now: () => new Date,
39168
41394
  execFileSync: execFileSync5,
39169
- spawnSync: spawnSync3
41395
+ spawnSync: spawnSync4
39170
41396
  };
39171
41397
  });
39172
41398
 
@@ -39283,15 +41509,15 @@ var DEFAULT_TIMEOUT_MS = 1e4, MAX_PREVIEW_CHARS = 240;
39283
41509
 
39284
41510
  // ../plugin/src/shared/tui-config.ts
39285
41511
  import {
39286
- chmodSync as chmodSync3,
39287
- existsSync as existsSync23,
41512
+ chmodSync as chmodSync4,
41513
+ existsSync as existsSync24,
39288
41514
  mkdirSync as mkdirSync9,
39289
- readFileSync as readFileSync15,
39290
- renameSync as renameSync3,
39291
- statSync as statSync8,
41515
+ readFileSync as readFileSync16,
41516
+ renameSync as renameSync4,
41517
+ statSync as statSync9,
39292
41518
  writeFileSync as writeFileSync6
39293
41519
  } from "node:fs";
39294
- import { dirname as dirname12, join as join20 } from "node:path";
41520
+ import { dirname as dirname13, join as join21 } from "node:path";
39295
41521
  function pluginEntryId(entry) {
39296
41522
  if (typeof entry === "string")
39297
41523
  return entry;
@@ -39324,19 +41550,19 @@ function writeTuiConfigAtomic(configPath, config2) {
39324
41550
  const tmpPath = `${configPath}.tmp`;
39325
41551
  writeFileSync6(tmpPath, body);
39326
41552
  try {
39327
- if (statSync8(configPath, { throwIfNoEntry: false })?.isFile()) {
39328
- chmodSync3(tmpPath, statSync8(configPath).mode & 511);
41553
+ if (statSync9(configPath, { throwIfNoEntry: false })?.isFile()) {
41554
+ chmodSync4(tmpPath, statSync9(configPath).mode & 511);
39329
41555
  }
39330
41556
  } catch {}
39331
- renameSync3(tmpPath, configPath);
41557
+ renameSync4(tmpPath, configPath);
39332
41558
  }
39333
41559
  function resolveTuiConfigPath(configDirOverride) {
39334
41560
  const configDir = configDirOverride ?? getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
39335
- const jsoncPath = join20(configDir, "tui.jsonc");
39336
- const jsonPath = join20(configDir, "tui.json");
39337
- if (existsSync23(jsoncPath))
41561
+ const jsoncPath = join21(configDir, "tui.jsonc");
41562
+ const jsonPath = join21(configDir, "tui.json");
41563
+ if (existsSync24(jsoncPath))
39338
41564
  return jsoncPath;
39339
- if (existsSync23(jsonPath))
41565
+ if (existsSync24(jsonPath))
39340
41566
  return jsonPath;
39341
41567
  return jsoncPath;
39342
41568
  }
@@ -39344,8 +41570,8 @@ function ensureTuiPluginEntry(options = {}) {
39344
41570
  try {
39345
41571
  const configPath = resolveTuiConfigPath(options.configDir);
39346
41572
  let config2 = {};
39347
- if (existsSync23(configPath)) {
39348
- const raw = readFileSync15(configPath, "utf-8");
41573
+ if (existsSync24(configPath)) {
41574
+ const raw = readFileSync16(configPath, "utf-8");
39349
41575
  config2 = import_comment_json9.parse(raw) ?? {};
39350
41576
  }
39351
41577
  const plugins = Array.isArray(config2.plugin) ? [...config2.plugin] : [];
@@ -39379,7 +41605,7 @@ function ensureTuiPluginEntry(options = {}) {
39379
41605
  }
39380
41606
  }
39381
41607
  config2.plugin = plugins;
39382
- mkdirSync9(dirname12(configPath), { recursive: true });
41608
+ mkdirSync9(dirname13(configPath), { recursive: true });
39383
41609
  writeTuiConfigAtomic(configPath, config2);
39384
41610
  log3(`[magic-context] updated TUI plugin entry in ${configPath}`);
39385
41611
  return true;
@@ -39397,12 +41623,12 @@ var init_tui_config = __esm(() => {
39397
41623
  });
39398
41624
 
39399
41625
  // src/lib/diagnostics-opencode.ts
39400
- import { existsSync as existsSync24, readdirSync as readdirSync3, readFileSync as readFileSync16, statSync as statSync9 } from "node:fs";
39401
- import { createRequire as createRequire4 } from "node:module";
41626
+ import { existsSync as existsSync25, readdirSync as readdirSync3, readFileSync as readFileSync17, statSync as statSync10 } from "node:fs";
41627
+ import { createRequire as createRequire3 } from "node:module";
39402
41628
  import { homedir as homedir15 } from "node:os";
39403
- import { join as join21 } from "node:path";
41629
+ import { join as join22 } from "node:path";
39404
41630
  function getSelfVersion() {
39405
- const require2 = createRequire4(import.meta.url);
41631
+ const require2 = createRequire3(import.meta.url);
39406
41632
  for (const relPath of ["../../package.json", "../package.json"]) {
39407
41633
  try {
39408
41634
  const pkg = require2(relPath);
@@ -39418,8 +41644,8 @@ function getPluginCacheInfo() {
39418
41644
  let cached2;
39419
41645
  for (const installedPkgPath of getOpenCodePluginPackageJsonPaths()) {
39420
41646
  try {
39421
- if (existsSync24(installedPkgPath)) {
39422
- const pkg = JSON.parse(readFileSync16(installedPkgPath, "utf-8"));
41647
+ if (existsSync25(installedPkgPath)) {
41648
+ const pkg = JSON.parse(readFileSync17(installedPkgPath, "utf-8"));
39423
41649
  cached2 = typeof pkg.version === "string" ? pkg.version : undefined;
39424
41650
  if (cached2)
39425
41651
  break;
@@ -39431,12 +41657,12 @@ function getPluginCacheInfo() {
39431
41657
  return { path: path6, cached: cached2, latest: getSelfVersion() };
39432
41658
  }
39433
41659
  function getStorageDir() {
39434
- const dataHome = process.env.XDG_DATA_HOME || join21(homedir15(), ".local", "share");
39435
- return join21(dataHome, "cortexkit", "magic-context");
41660
+ const dataHome = process.env.XDG_DATA_HOME || join22(homedir15(), ".local", "share");
41661
+ return join22(dataHome, "cortexkit", "magic-context");
39436
41662
  }
39437
41663
  function fileSize(path6) {
39438
41664
  try {
39439
- return existsSync24(path6) ? statSync9(path6).size : 0;
41665
+ return existsSync25(path6) ? statSync10(path6).size : 0;
39440
41666
  } catch {
39441
41667
  return 0;
39442
41668
  }
@@ -39448,10 +41674,10 @@ function sanitizeValue(value) {
39448
41674
  return sanitizeConfigValue(value);
39449
41675
  }
39450
41676
  function readConfig3(path6) {
39451
- if (!existsSync24(path6))
41677
+ if (!existsSync25(path6))
39452
41678
  return { value: null };
39453
41679
  try {
39454
- const raw = readFileSync16(path6, "utf-8");
41680
+ const raw = readFileSync17(path6, "utf-8");
39455
41681
  const value = import_comment_json10.parse(raw);
39456
41682
  return { value };
39457
41683
  } catch (error51) {
@@ -39476,7 +41702,7 @@ function configHasPluginEntry(config2) {
39476
41702
  }
39477
41703
  function parseHistorianDumpMeta(path6) {
39478
41704
  try {
39479
- const xml = readFileSync16(path6, "utf-8");
41705
+ const xml = readFileSync17(path6, "utf-8");
39480
41706
  const parsed = parseCompartmentOutput(xml);
39481
41707
  const factCountByCategory = {};
39482
41708
  for (const fact of parsed.facts) {
@@ -39509,11 +41735,11 @@ function parseHistorianDumpMeta(path6) {
39509
41735
  }
39510
41736
  }
39511
41737
  function listDumpsInDir(dir, limit) {
39512
- if (!existsSync24(dir))
41738
+ if (!existsSync25(dir))
39513
41739
  return { count: 0, recent: [] };
39514
41740
  try {
39515
41741
  const entries = readdirSync3(dir).filter((name) => name.endsWith(".xml")).map((name) => {
39516
- const stat = statSync9(join21(dir, name));
41742
+ const stat = statSync10(join22(dir, name));
39517
41743
  return {
39518
41744
  name,
39519
41745
  mtime: stat.mtimeMs,
@@ -39522,7 +41748,7 @@ function listDumpsInDir(dir, limit) {
39522
41748
  }).sort((a3, b2) => b2.mtime - a3.mtime);
39523
41749
  const now = Date.now();
39524
41750
  const recent = entries.slice(0, limit).map((entry) => {
39525
- const meta3 = parseHistorianDumpMeta(join21(dir, entry.name));
41751
+ const meta3 = parseHistorianDumpMeta(join22(dir, entry.name));
39526
41752
  const summary = {
39527
41753
  name: entry.name,
39528
41754
  ageMinutes: Math.round((now - entry.mtime) / 60000),
@@ -39577,9 +41803,9 @@ function collectHistorianDumps(recentSessions) {
39577
41803
  };
39578
41804
  }
39579
41805
  async function collectRecentSessions() {
39580
- const dataHome = process.env.XDG_DATA_HOME || join21(process.env.HOME || homedir15(), ".local", "share");
39581
- const opencodeDbPath = join21(dataHome, "opencode", "opencode.db");
39582
- if (!existsSync24(opencodeDbPath))
41806
+ const dataHome = process.env.XDG_DATA_HOME || join22(process.env.HOME || homedir15(), ".local", "share");
41807
+ const opencodeDbPath = join22(dataHome, "opencode", "opencode.db");
41808
+ if (!existsSync25(opencodeDbPath))
39583
41809
  return [];
39584
41810
  if (typeof globalThis.Bun === "undefined") {
39585
41811
  return [];
@@ -39613,8 +41839,8 @@ async function collectRecentSessions() {
39613
41839
  }
39614
41840
  }
39615
41841
  async function collectHistorianFailures(storageDirPath) {
39616
- const contextDbPath = join21(storageDirPath, "context.db");
39617
- if (!existsSync24(contextDbPath))
41842
+ const contextDbPath = join22(storageDirPath, "context.db");
41843
+ if (!existsSync25(contextDbPath))
39618
41844
  return [];
39619
41845
  if (typeof globalThis.Bun === "undefined") {
39620
41846
  return [];
@@ -39647,8 +41873,8 @@ async function collectHistorianFailures(storageDirPath) {
39647
41873
  }
39648
41874
  }
39649
41875
  async function collectHistorianRuns(storageDirPath) {
39650
- const contextDbPath = join21(storageDirPath, "context.db");
39651
- if (!existsSync24(contextDbPath))
41876
+ const contextDbPath = join22(storageDirPath, "context.db");
41877
+ if (!existsSync25(contextDbPath))
39652
41878
  return [];
39653
41879
  if (typeof globalThis.Bun === "undefined")
39654
41880
  return [];
@@ -39717,9 +41943,9 @@ async function collectDiagnostics() {
39717
41943
  const tuiConfig = readConfig3(configPaths.tuiConfig);
39718
41944
  const magicContextConfig = readConfig3(configPaths.magicContextConfig);
39719
41945
  const storageDirPath = getStorageDir();
39720
- const contextDbPath = join21(storageDirPath, "context.db");
41946
+ const contextDbPath = join22(storageDirPath, "context.db");
39721
41947
  const logPath = getMagicContextLogPath2("opencode");
39722
- const logFileSize = existsSync24(logPath) ? statSync9(logPath).size : 0;
41948
+ const logFileSize = existsSync25(logPath) ? statSync10(logPath).size : 0;
39723
41949
  let compactionEnabled = false;
39724
41950
  try {
39725
41951
  compactionEnabled = isCompactionEnabled(loadPluginConfig(process.cwd()));
@@ -39747,14 +41973,14 @@ async function collectDiagnostics() {
39747
41973
  opencodeConfigHasPlugin: configHasPluginEntry(opencodeConfig.value),
39748
41974
  tuiConfigHasPlugin: configHasPluginEntry(tuiConfig.value),
39749
41975
  magicContextConfig: {
39750
- exists: existsSync24(configPaths.magicContextConfig),
41976
+ exists: existsSync25(configPaths.magicContextConfig),
39751
41977
  ...magicContextConfig.error ? { parseError: magicContextConfig.error } : {},
39752
41978
  flags: sanitizeValue(magicContextConfig.value ?? {}) ?? {}
39753
41979
  },
39754
41980
  pluginCache: getPluginCacheInfo(),
39755
41981
  storageDir: {
39756
41982
  path: storageDirPath,
39757
- exists: existsSync24(storageDirPath),
41983
+ exists: existsSync25(storageDirPath),
39758
41984
  contextDbSizeBytes: fileSize(contextDbPath)
39759
41985
  },
39760
41986
  conflicts: {
@@ -39765,7 +41991,7 @@ async function collectDiagnostics() {
39765
41991
  },
39766
41992
  logFile: {
39767
41993
  path: logPath,
39768
- exists: existsSync24(logPath),
41994
+ exists: existsSync25(logPath),
39769
41995
  sizeKb: Math.round(logFileSize / 1024)
39770
41996
  },
39771
41997
  recentSessions,
@@ -39917,17 +42143,17 @@ var init_diagnostics_opencode = __esm(() => {
39917
42143
  });
39918
42144
 
39919
42145
  // src/lib/embedding-runtime.ts
39920
- import { spawnSync as spawnSync4 } from "node:child_process";
39921
- import { existsSync as existsSync25 } from "node:fs";
39922
- import { createRequire as createRequire5 } from "node:module";
39923
- import { dirname as dirname13, join as join22, sep } from "node:path";
42146
+ import { spawnSync as spawnSync5 } from "node:child_process";
42147
+ import { existsSync as existsSync26 } from "node:fs";
42148
+ import { createRequire as createRequire4 } from "node:module";
42149
+ import { dirname as dirname14, join as join23, sep } from "node:path";
39924
42150
  function describeError(error51) {
39925
42151
  const message = error51 instanceof Error ? error51.message : String(error51 ?? "unknown error");
39926
42152
  const code = error51?.code;
39927
42153
  return typeof code === "string" && code.length > 0 ? `${code}: ${message}` : message;
39928
42154
  }
39929
42155
  function runOnnxRuntimeNodeLoadProbeChild(packageDir) {
39930
- return spawnSync4(process.execPath, ["-e", ONNX_RUNTIME_NODE_LOAD_PROBE_SCRIPT], {
42156
+ return spawnSync5(process.execPath, ["-e", ONNX_RUNTIME_NODE_LOAD_PROBE_SCRIPT], {
39931
42157
  encoding: "utf8",
39932
42158
  env: { ...process.env, [ONNX_LOAD_PROBE_PACKAGE_DIR_ENV]: packageDir },
39933
42159
  stdio: ["ignore", "pipe", "pipe"],
@@ -40029,25 +42255,25 @@ function expectedBinaryRelPath(platform, arch) {
40029
42255
  const supportedArch = arch === "x64" || arch === "arm64";
40030
42256
  if (!supportedPlatform || !supportedArch)
40031
42257
  return null;
40032
- return join22("bin", "napi-v6", platform, arch, "onnxruntime_binding.node");
42258
+ return join23("bin", "napi-v6", platform, arch, "onnxruntime_binding.node");
40033
42259
  }
40034
42260
  function checkLocalEmbeddingRuntimeAt(installRoot, platform = process.platform, arch = process.arch) {
40035
- const packageDir = join22(installRoot, "node_modules", "onnxruntime-node");
40036
- if (!existsSync25(join22(packageDir, "package.json"))) {
42261
+ const packageDir = join23(installRoot, "node_modules", "onnxruntime-node");
42262
+ if (!existsSync26(join23(packageDir, "package.json"))) {
40037
42263
  return { state: "package-missing", packageDir };
40038
42264
  }
40039
42265
  const rel = expectedBinaryRelPath(platform, arch);
40040
42266
  if (rel === null) {
40041
42267
  return probeOnnxRuntimeNodeLoad(packageDir) ?? { state: "ok", binaryPath: packageDir };
40042
42268
  }
40043
- const binaryPath = join22(packageDir, rel);
40044
- if (!existsSync25(binaryPath)) {
42269
+ const binaryPath = join23(packageDir, rel);
42270
+ if (!existsSync26(binaryPath)) {
40045
42271
  return { state: "binary-missing", packageDir, expectedBinary: binaryPath };
40046
42272
  }
40047
42273
  return probeOnnxRuntimeNodeLoad(packageDir) ?? { state: "ok", binaryPath };
40048
42274
  }
40049
42275
  function checkLocalEmbeddingRuntime(installRoots, platform = process.platform, arch = process.arch) {
40050
- const existing = installRoots.filter((root) => existsSync25(root));
42276
+ const existing = installRoots.filter((root) => existsSync26(root));
40051
42277
  if (existing.length === 0) {
40052
42278
  return {
40053
42279
  state: "unknown",
@@ -40067,22 +42293,22 @@ function checkLocalEmbeddingRuntime(installRoots, platform = process.platform, a
40067
42293
  function packageDirFromResolved(resolvedPath, packageName) {
40068
42294
  const marker = `node_modules${sep}${packageName.split("/").join(sep)}`;
40069
42295
  const idx = resolvedPath.indexOf(marker);
40070
- return idx >= 0 ? resolvedPath.slice(0, idx + marker.length) : dirname13(resolvedPath);
42296
+ return idx >= 0 ? resolvedPath.slice(0, idx + marker.length) : dirname14(resolvedPath);
40071
42297
  }
40072
42298
  function checkLocalEmbeddingRuntimeByResolution(pluginDir, platform = process.platform, arch = process.arch) {
40073
- if (!existsSync25(join22(pluginDir, "package.json"))) {
42299
+ if (!existsSync26(join23(pluginDir, "package.json"))) {
40074
42300
  return { state: "unknown", reason: "plugin package dir not found" };
40075
42301
  }
40076
42302
  let onnxDir = null;
40077
42303
  let resolveError;
40078
42304
  try {
40079
- const reqPlugin = createRequire5(join22(pluginDir, "package.json"));
42305
+ const reqPlugin = createRequire4(join23(pluginDir, "package.json"));
40080
42306
  try {
40081
42307
  onnxDir = packageDirFromResolved(reqPlugin.resolve("onnxruntime-node"), "onnxruntime-node");
40082
42308
  } catch {
40083
42309
  const tfResolved = reqPlugin.resolve("@huggingface/transformers");
40084
42310
  const tfDir = packageDirFromResolved(tfResolved, "@huggingface/transformers");
40085
- const reqTf = createRequire5(join22(tfDir, "package.json"));
42311
+ const reqTf = createRequire4(join23(tfDir, "package.json"));
40086
42312
  onnxDir = packageDirFromResolved(reqTf.resolve("onnxruntime-node"), "onnxruntime-node");
40087
42313
  }
40088
42314
  } catch (error51) {
@@ -40090,7 +42316,7 @@ function checkLocalEmbeddingRuntimeByResolution(pluginDir, platform = process.pl
40090
42316
  if (resolveError === "ERR_MODULE_NOT_FOUND" || resolveError === "MODULE_NOT_FOUND") {
40091
42317
  return {
40092
42318
  state: "package-missing",
40093
- packageDir: join22(pluginDir, "node_modules", "onnxruntime-node")
42319
+ packageDir: join23(pluginDir, "node_modules", "onnxruntime-node")
40094
42320
  };
40095
42321
  }
40096
42322
  return {
@@ -40105,8 +42331,8 @@ function checkLocalEmbeddingRuntimeByResolution(pluginDir, platform = process.pl
40105
42331
  if (rel === null) {
40106
42332
  return probeOnnxRuntimeNodeLoad(onnxDir) ?? { state: "ok", binaryPath: onnxDir };
40107
42333
  }
40108
- const binaryPath = join22(onnxDir, rel);
40109
- if (!existsSync25(binaryPath)) {
42334
+ const binaryPath = join23(onnxDir, rel);
42335
+ if (!existsSync26(binaryPath)) {
40110
42336
  return { state: "binary-missing", packageDir: onnxDir, expectedBinary: binaryPath };
40111
42337
  }
40112
42338
  return probeOnnxRuntimeNodeLoad(onnxDir) ?? { state: "ok", binaryPath };
@@ -40235,8 +42461,8 @@ var init_issue_body = __esm(() => {
40235
42461
  });
40236
42462
 
40237
42463
  // src/lib/logs-opencode.ts
40238
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync7 } from "node:fs";
40239
- import { join as join23 } from "node:path";
42464
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync7 } from "node:fs";
42465
+ import { join as join24 } from "node:path";
40240
42466
  function sanitizeLogContent(content) {
40241
42467
  return sanitizeDiagnosticText(content);
40242
42468
  }
@@ -40278,7 +42504,7 @@ function filterLogLinesBySession(lines, sessionId) {
40278
42504
  }
40279
42505
  async function bundleIssueReport(report, description, title, sessionFilter = null) {
40280
42506
  const LOG_TAIL_LINES = 400;
40281
- const allLogLines = report.logFile.exists ? readFileSync17(report.logFile.path, "utf-8").split(/\r?\n/) : [];
42507
+ const allLogLines = report.logFile.exists ? readFileSync18(report.logFile.path, "utf-8").split(/\r?\n/) : [];
40282
42508
  const logLines = filterLogLinesBySession(allLogLines, sessionFilter);
40283
42509
  const recentLog = sanitizeLogContent(logLines.slice(-LOG_TAIL_LINES).join(`
40284
42510
  `)).trim();
@@ -40329,7 +42555,7 @@ async function bundleIssueReport(report, description, title, sessionFilter = nul
40329
42555
  ].join(`
40330
42556
  `);
40331
42557
  const bodyMarkdown = capBodyToGithubLimit(rawBodyMarkdown);
40332
- const path6 = join23(process.cwd(), `magic-context-issue-${formatTimestamp(new Date)}.md`);
42558
+ const path6 = join24(process.cwd(), `magic-context-issue-${formatTimestamp(new Date)}.md`);
40333
42559
  writeFileSync7(path6, `${bodyMarkdown}
40334
42560
  `);
40335
42561
  return { path: path6, bodyMarkdown };
@@ -40701,22 +42927,22 @@ var init_storage_versions = __esm(async () => {
40701
42927
  });
40702
42928
 
40703
42929
  // src/commands/doctor-opencode-cache.ts
40704
- import { existsSync as existsSync26, readFileSync as readFileSync18, rmSync as rmSync2 } from "node:fs";
42930
+ import { existsSync as existsSync27, readFileSync as readFileSync19, rmSync as rmSync3 } from "node:fs";
40705
42931
  function readCachedPluginVersion(pluginCacheDir) {
40706
42932
  try {
40707
42933
  const installedPkgPath = getOpenCodePluginPackageJsonPath(pluginCacheDir);
40708
- if (!existsSync26(installedPkgPath))
42934
+ if (!existsSync27(installedPkgPath))
40709
42935
  return;
40710
- const pkg = JSON.parse(readFileSync18(installedPkgPath, "utf-8"));
42936
+ const pkg = JSON.parse(readFileSync19(installedPkgPath, "utf-8"));
40711
42937
  return typeof pkg.version === "string" ? pkg.version : undefined;
40712
42938
  } catch {
40713
42939
  return;
40714
42940
  }
40715
42941
  }
40716
42942
  async function clearPluginCache(options = {}, deps = {}) {
40717
- const remove = deps.remove ?? ((path6) => rmSync2(path6, { recursive: true, force: true }));
42943
+ const remove = deps.remove ?? ((path6) => rmSync3(path6, { recursive: true, force: true }));
40718
42944
  const pluginCacheRoots = getOpenCodePluginCacheRoots();
40719
- const existingRoots = pluginCacheRoots.filter((root) => existsSync26(root));
42945
+ const existingRoots = pluginCacheRoots.filter((root) => existsSync27(root));
40720
42946
  if (existingRoots.length === 0) {
40721
42947
  return { action: "not_found", path: pluginCacheRoots[0] ?? "" };
40722
42948
  }
@@ -40783,11 +43009,11 @@ var init_doctor_opencode_cache = __esm(() => {
40783
43009
  });
40784
43010
 
40785
43011
  // src/commands/doctor-opencode.ts
40786
- import { execSync as execSync2, spawnSync as spawnSync5 } from "node:child_process";
40787
- import { existsSync as existsSync27, readFileSync as readFileSync19, statSync as statSync10 } from "node:fs";
40788
- import { createRequire as createRequire6 } from "node:module";
43012
+ import { execSync as execSync2, spawnSync as spawnSync6 } from "node:child_process";
43013
+ import { existsSync as existsSync28, readFileSync as readFileSync20, statSync as statSync11 } from "node:fs";
43014
+ import { createRequire as createRequire5 } from "node:module";
40789
43015
  import { homedir as homedir16 } from "node:os";
40790
- import { join as join24 } from "node:path";
43016
+ import { join as join25 } from "node:path";
40791
43017
  function resolveCompactionEnabledForDoctor() {
40792
43018
  try {
40793
43019
  const config2 = loadPluginConfig(process.cwd());
@@ -40866,7 +43092,7 @@ async function fetchNpmLatest(pkg, timeoutMs = 5000) {
40866
43092
  }
40867
43093
  }
40868
43094
  function getSelfVersion2() {
40869
- const req = createRequire6(import.meta.url);
43095
+ const req = createRequire5(import.meta.url);
40870
43096
  for (const relPath of ["../../package.json", "../package.json"]) {
40871
43097
  try {
40872
43098
  const pkg = req(relPath);
@@ -40886,15 +43112,15 @@ function getUserNpmrcPath() {
40886
43112
  if (custom2)
40887
43113
  return custom2;
40888
43114
  const home = process.env.HOME?.trim();
40889
- return join24(home || homedir16(), ".npmrc");
43115
+ return join25(home || homedir16(), ".npmrc");
40890
43116
  }
40891
43117
  function collectNpmReleaseAgeWarnings() {
40892
43118
  const ageWarnings = [];
40893
43119
  const npmrcPath = getUserNpmrcPath();
40894
- if (!existsSync27(npmrcPath))
43120
+ if (!existsSync28(npmrcPath))
40895
43121
  return ageWarnings;
40896
43122
  try {
40897
- const npmrc = readFileSync19(npmrcPath, "utf-8");
43123
+ const npmrc = readFileSync20(npmrcPath, "utf-8");
40898
43124
  for (const line of npmrc.split(`
40899
43125
  `)) {
40900
43126
  const trimmed = line.trim();
@@ -40935,15 +43161,15 @@ function isGhInstalled() {
40935
43161
  function openBrowser(url2) {
40936
43162
  try {
40937
43163
  if (process.platform === "darwin") {
40938
- const child = spawnSync5("open", [url2], { stdio: "ignore" });
43164
+ const child = spawnSync6("open", [url2], { stdio: "ignore" });
40939
43165
  if (child.status === 0)
40940
43166
  return;
40941
43167
  } else if (process.platform === "linux") {
40942
- const child = spawnSync5("xdg-open", [url2], { stdio: "ignore" });
43168
+ const child = spawnSync6("xdg-open", [url2], { stdio: "ignore" });
40943
43169
  if (child.status === 0)
40944
43170
  return;
40945
43171
  } else if (process.platform === "win32") {
40946
- const child = spawnSync5("cmd", ["/c", "start", "", url2], { stdio: "ignore" });
43172
+ const child = spawnSync6("cmd", ["/c", "start", "", url2], { stdio: "ignore" });
40947
43173
  if (child.status === 0)
40948
43174
  return;
40949
43175
  }
@@ -40987,7 +43213,7 @@ async function runIssueFlow2() {
40987
43213
  s.stop(`Report written to ${bundled.path}`);
40988
43214
  const shouldSubmit = await confirm2("Submit this issue on GitHub now?", true);
40989
43215
  if (shouldSubmit && isGhInstalled()) {
40990
- const result = spawnSync5("gh", [
43216
+ const result = spawnSync6("gh", [
40991
43217
  "issue",
40992
43218
  "create",
40993
43219
  "-R",
@@ -41033,12 +43259,12 @@ function checkLocalEmbeddingRuntimeForDoctor() {
41033
43259
  return { issues: 0 };
41034
43260
  }
41035
43261
  async function checkEmbeddingConfig(magicContextConfigPath) {
41036
- if (!existsSync27(magicContextConfigPath)) {
43262
+ if (!existsSync28(magicContextConfigPath)) {
41037
43263
  return checkLocalEmbeddingRuntimeForDoctor();
41038
43264
  }
41039
43265
  let rawText;
41040
43266
  try {
41041
- rawText = readFileSync19(magicContextConfigPath, "utf-8");
43267
+ rawText = readFileSync20(magicContextConfigPath, "utf-8");
41042
43268
  } catch {
41043
43269
  log2.warn("Could not read magic-context.jsonc for embedding check");
41044
43270
  return { issues: 1 };
@@ -41161,7 +43387,7 @@ async function runDoctor2(options = {}) {
41161
43387
  const v22Result = await runV22BackfillCommands({
41162
43388
  name: "OpenCode",
41163
43389
  openDatabase: (readonly2 = true) => {
41164
- const dbPath2 = join24(getMagicContextStorageDir(), "context.db");
43390
+ const dbPath2 = join25(getMagicContextStorageDir(), "context.db");
41165
43391
  v22Db = readonly2 ? openExistingContextDatabase(dbPath2, { readonly: true }) : openExistingContextDatabaseForMutation(dbPath2);
41166
43392
  return v22Db;
41167
43393
  },
@@ -41193,7 +43419,7 @@ async function runDoctor2(options = {}) {
41193
43419
  failCount++;
41194
43420
  issues++;
41195
43421
  };
41196
- const authorityDbPath = join24(getMagicContextStorageDir(), "context.db");
43422
+ const authorityDbPath = join25(getMagicContextStorageDir(), "context.db");
41197
43423
  let authorityDb = null;
41198
43424
  try {
41199
43425
  authorityDb = openExistingContextDatabase(authorityDbPath, { readonly: true });
@@ -41244,10 +43470,10 @@ async function runDoctor2(options = {}) {
41244
43470
  } else {
41245
43471
  pass(`OpenCode config: ${paths.opencodeConfig}`);
41246
43472
  }
41247
- if (existsSync27(paths.magicContextConfig)) {
43473
+ if (existsSync28(paths.magicContextConfig)) {
41248
43474
  pass(`Magic Context config: ${paths.magicContextConfig}`);
41249
43475
  try {
41250
- const raw = readFileSync19(paths.magicContextConfig, "utf-8");
43476
+ const raw = readFileSync20(paths.magicContextConfig, "utf-8");
41251
43477
  const substituted = substituteConfigVariables({
41252
43478
  text: raw,
41253
43479
  configPath: paths.magicContextConfig
@@ -41272,9 +43498,9 @@ async function runDoctor2(options = {}) {
41272
43498
  warn(`No magic-context.jsonc found — using defaults`);
41273
43499
  log2.info(" Run 'setup' to create one with model recommendations");
41274
43500
  }
41275
- if (existsSync27(paths.magicContextConfig)) {
43501
+ if (existsSync28(paths.magicContextConfig)) {
41276
43502
  try {
41277
- const mcRaw = readFileSync19(paths.magicContextConfig, "utf-8");
43503
+ const mcRaw = readFileSync20(paths.magicContextConfig, "utf-8");
41278
43504
  const mcConfig = import_comment_json11.parse(mcRaw);
41279
43505
  let mcChanged = false;
41280
43506
  const experimental = mcConfig.experimental;
@@ -41392,7 +43618,7 @@ async function runDoctor2(options = {}) {
41392
43618
  }
41393
43619
  if (paths.opencodeConfigFormat !== "none") {
41394
43620
  try {
41395
- const raw = readFileSync19(paths.opencodeConfig, "utf-8");
43621
+ const raw = readFileSync20(paths.opencodeConfig, "utf-8");
41396
43622
  const config2 = import_comment_json11.parse(raw);
41397
43623
  const rawPlugins = Array.isArray(config2?.plugin) ? config2.plugin : [];
41398
43624
  const existingIdx = rawPlugins.findIndex((entry) => matchesPluginEntry(entry, OPENCODE_PLUGIN_NAME) || isDevPathPluginEntry(entry));
@@ -41476,9 +43702,9 @@ async function runDoctor2(options = {}) {
41476
43702
  pass("Added TUI sidebar plugin to tui.json");
41477
43703
  warn("Restart OpenCode to see the sidebar");
41478
43704
  fixed++;
41479
- } else if (existsSync27(paths.tuiConfig)) {
43705
+ } else if (existsSync28(paths.tuiConfig)) {
41480
43706
  try {
41481
- const tuiRaw = readFileSync19(paths.tuiConfig, "utf-8");
43707
+ const tuiRaw = readFileSync20(paths.tuiConfig, "utf-8");
41482
43708
  const tuiConfig = import_comment_json11.parse(tuiRaw);
41483
43709
  const tuiRawPlugins = Array.isArray(tuiConfig?.plugin) ? tuiConfig.plugin : [];
41484
43710
  const tuiIdx = tuiRawPlugins.findIndex((entry) => matchesPluginEntry(entry, OPENCODE_PLUGIN_NAME) || isDevPathPluginEntry(entry));
@@ -41527,9 +43753,9 @@ async function runDoctor2(options = {}) {
41527
43753
  } else {
41528
43754
  fail("Could not create or verify the TUI sidebar config");
41529
43755
  }
41530
- if (existsSync27(paths.magicContextConfig)) {
43756
+ if (existsSync28(paths.magicContextConfig)) {
41531
43757
  try {
41532
- const mcRaw = readFileSync19(paths.magicContextConfig, "utf-8");
43758
+ const mcRaw = readFileSync20(paths.magicContextConfig, "utf-8");
41533
43759
  const mcConfig = import_comment_json11.parse(mcRaw);
41534
43760
  const warning = checkUserMemoriesDreamerCompatibility(mcConfig);
41535
43761
  if (warning) {
@@ -41546,8 +43772,8 @@ async function runDoctor2(options = {}) {
41546
43772
  warnCount++;
41547
43773
  else
41548
43774
  passCount++;
41549
- const dbPath = join24(getMagicContextStorageDir(), "context.db");
41550
- if (!existsSync27(dbPath)) {
43775
+ const dbPath = join25(getMagicContextStorageDir(), "context.db");
43776
+ if (!existsSync28(dbPath)) {
41551
43777
  log2.info(`Shared context DB not yet created at ${dbPath} (will be created on first run)`);
41552
43778
  } else {
41553
43779
  log2.info(`Shared context DB exists at ${dbPath}`);
@@ -41573,9 +43799,11 @@ async function runDoctor2(options = {}) {
41573
43799
  if (result === "ok")
41574
43800
  pass("SQLite integrity_check: ok");
41575
43801
  else
41576
- fail(`SQLite integrity_check reported: ${result}`);
43802
+ fail(`SQLite integrity_check reported: ${result}
43803
+ ${formatDatabaseRepairGuidance(dbPath)}`);
41577
43804
  } catch (err) {
41578
- fail(`SQLite integrity_check failed: ${err instanceof Error ? err.message : String(err)}`);
43805
+ fail(`SQLite integrity_check failed: ${err instanceof Error ? err.message : String(err)}
43806
+ ${formatDatabaseRepairGuidance(dbPath)}`);
41579
43807
  }
41580
43808
  try {
41581
43809
  const counts = {};
@@ -41606,7 +43834,8 @@ async function runDoctor2(options = {}) {
41606
43834
  plugin_supported_version: err.supportedVersion
41607
43835
  }).message);
41608
43836
  } else {
41609
- fail(`Could not open shared DB: ${err instanceof Error ? err.message : String(err)}`);
43837
+ fail(`Could not open shared DB: ${err instanceof Error ? err.message : String(err)}
43838
+ ${formatDatabaseRepairGuidance(dbPath)}`);
41610
43839
  }
41611
43840
  }
41612
43841
  }
@@ -41651,8 +43880,8 @@ async function runDoctor2(options = {}) {
41651
43880
  }
41652
43881
  }
41653
43882
  const logPath = getMagicContextLogPath2("opencode");
41654
- if (existsSync27(logPath)) {
41655
- const logStat = statSync10(logPath);
43883
+ if (existsSync28(logPath)) {
43884
+ const logStat = statSync11(logPath);
41656
43885
  const sizeKb = (logStat.size / 1024).toFixed(0);
41657
43886
  log2.info(`Log file: ${logPath} (${sizeKb} KB)`);
41658
43887
  } else {
@@ -41732,12 +43961,12 @@ var init_doctor_opencode = __esm(async () => {
41732
43961
 
41733
43962
  // src/lib/diagnostics-pi.ts
41734
43963
  import { createHash as createHash4 } from "node:crypto";
41735
- import { existsSync as existsSync28, readdirSync as readdirSync4, readFileSync as readFileSync20, statSync as statSync11 } from "node:fs";
41736
- import { createRequire as createRequire7 } from "node:module";
43964
+ import { existsSync as existsSync29, readdirSync as readdirSync4, readFileSync as readFileSync21, statSync as statSync12 } from "node:fs";
43965
+ import { createRequire as createRequire6 } from "node:module";
41737
43966
  import { homedir as homedir17, userInfo as userInfo2 } from "node:os";
41738
- import { join as join25 } from "node:path";
43967
+ import { join as join26 } from "node:path";
41739
43968
  function getSelfVersion3() {
41740
- const req = createRequire7(import.meta.url);
43969
+ const req = createRequire6(import.meta.url);
41741
43970
  for (const relPath of ["../../package.json", "../package.json"]) {
41742
43971
  try {
41743
43972
  const pkg = req(relPath);
@@ -41750,7 +43979,7 @@ function getSelfVersion3() {
41750
43979
  }
41751
43980
  function fileSize2(path6) {
41752
43981
  try {
41753
- return existsSync28(path6) ? statSync11(path6).size : 0;
43982
+ return existsSync29(path6) ? statSync12(path6).size : 0;
41754
43983
  } catch {
41755
43984
  return 0;
41756
43985
  }
@@ -41802,11 +44031,11 @@ function sanitizeValue2(value, key = "") {
41802
44031
  return value;
41803
44032
  }
41804
44033
  function readJsonc(path6) {
41805
- if (!existsSync28(path6))
44034
+ if (!existsSync29(path6))
41806
44035
  return { value: {} };
41807
44036
  try {
41808
44037
  return {
41809
- value: import_comment_json12.parse(readFileSync20(path6, "utf-8"))
44038
+ value: import_comment_json12.parse(readFileSync21(path6, "utf-8"))
41810
44039
  };
41811
44040
  } catch (error51) {
41812
44041
  return {
@@ -41822,7 +44051,7 @@ function readConfigDiagnostic(path6) {
41822
44051
  const parsed = readJsonc(path6);
41823
44052
  return {
41824
44053
  path: path6,
41825
- exists: existsSync28(path6),
44054
+ exists: existsSync29(path6),
41826
44055
  ...parsed.parseError ? { parseError: parsed.parseError } : {},
41827
44056
  flags: sanitizeValue2(parsed.value)
41828
44057
  };
@@ -41840,7 +44069,7 @@ function reverseSlugToDirectory(slug) {
41840
44069
  }
41841
44070
  function collectPiRecentSessions() {
41842
44071
  const sessionsRoot = getPiSessionsRoot();
41843
- if (!existsSync28(sessionsRoot))
44072
+ if (!existsSync29(sessionsRoot))
41844
44073
  return [];
41845
44074
  try {
41846
44075
  const slugs = readdirSync4(sessionsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -41849,7 +44078,7 @@ function collectPiRecentSessions() {
41849
44078
  const directory = reverseSlugToDirectory(slug);
41850
44079
  if (!directory)
41851
44080
  continue;
41852
- const slugDir = join25(sessionsRoot, slug);
44081
+ const slugDir = join26(sessionsRoot, slug);
41853
44082
  let files;
41854
44083
  try {
41855
44084
  files = readdirSync4(slugDir).filter((name) => name.endsWith(".jsonl"));
@@ -41858,7 +44087,7 @@ function collectPiRecentSessions() {
41858
44087
  }
41859
44088
  for (const file2 of files) {
41860
44089
  try {
41861
- const mtime = statSync11(join25(slugDir, file2)).mtimeMs;
44090
+ const mtime = statSync12(join26(slugDir, file2)).mtimeMs;
41862
44091
  const sessionId = file2.replace(/\.jsonl$/, "");
41863
44092
  candidates.push({ sessionId, directory, mtime });
41864
44093
  } catch {}
@@ -41876,7 +44105,7 @@ function collectPiRecentSessions() {
41876
44105
  }
41877
44106
  function parseHistorianDumpMeta2(path6) {
41878
44107
  try {
41879
- const xml = readFileSync20(path6, "utf-8");
44108
+ const xml = readFileSync21(path6, "utf-8");
41880
44109
  const parsed = parseCompartmentOutput(xml);
41881
44110
  const factCountByCategory = {};
41882
44111
  for (const fact of parsed.facts) {
@@ -41909,11 +44138,11 @@ function parseHistorianDumpMeta2(path6) {
41909
44138
  }
41910
44139
  }
41911
44140
  function listDumpsInDir2(dir, limit) {
41912
- if (!existsSync28(dir))
44141
+ if (!existsSync29(dir))
41913
44142
  return { count: 0, recent: [] };
41914
44143
  try {
41915
44144
  const entries = readdirSync4(dir).filter((name) => name.endsWith(".xml")).map((name) => {
41916
- const stat = statSync11(join25(dir, name));
44145
+ const stat = statSync12(join26(dir, name));
41917
44146
  return {
41918
44147
  name,
41919
44148
  mtime: stat.mtimeMs,
@@ -41922,7 +44151,7 @@ function listDumpsInDir2(dir, limit) {
41922
44151
  }).sort((a3, b2) => b2.mtime - a3.mtime);
41923
44152
  const now = Date.now();
41924
44153
  const recent = entries.slice(0, limit).map((entry) => {
41925
- const meta3 = parseHistorianDumpMeta2(join25(dir, entry.name));
44154
+ const meta3 = parseHistorianDumpMeta2(join26(dir, entry.name));
41926
44155
  const summary = {
41927
44156
  name: entry.name,
41928
44157
  ageMinutes: Math.round((now - entry.mtime) / 60000),
@@ -41985,9 +44214,9 @@ async function collectDiagnostics2(cwd = process.cwd()) {
41985
44214
  const projectConfigPath = getProjectConfigPath(cwd);
41986
44215
  const loaded = loadPiConfig({ cwd });
41987
44216
  const storageDirPath = getMagicContextStorageDir();
41988
- const dbPath = join25(storageDirPath, "context.db");
44217
+ const dbPath = join26(storageDirPath, "context.db");
41989
44218
  const logPath = getMagicContextLogPath2("pi");
41990
- const logFileSize = existsSync28(logPath) ? statSync11(logPath).size : 0;
44219
+ const logFileSize = existsSync29(logPath) ? statSync12(logPath).size : 0;
41991
44220
  const otherPiExtensions = packages.filter((entry) => !isPiMagicContextPackageEntry(entry)).map(describePiPackageEntry);
41992
44221
  const recentSessions = collectPiRecentSessions();
41993
44222
  const historianDumps = collectPiHistorianDumps(recentSessions);
@@ -42002,7 +44231,7 @@ async function collectDiagnostics2(cwd = process.cwd()) {
42002
44231
  piVersion: pi ? getPiVersion(pi.path) : null,
42003
44232
  settings: {
42004
44233
  path: settingsPath,
42005
- exists: existsSync28(settingsPath),
44234
+ exists: existsSync29(settingsPath),
42006
44235
  ...settingsParsed.parseError ? { parseError: settingsParsed.parseError } : {},
42007
44236
  hasMagicContextPackage: hasPiMagicContextPackage(packages),
42008
44237
  packages: sanitizeValue2(packages)
@@ -42018,7 +44247,7 @@ async function collectDiagnostics2(cwd = process.cwd()) {
42018
44247
  loadWarnings: loaded.warnings.map(sanitizeString2),
42019
44248
  storageDir: {
42020
44249
  path: storageDirPath,
42021
- exists: existsSync28(storageDirPath),
44250
+ exists: existsSync29(storageDirPath),
42022
44251
  contextDbSizeBytes: fileSize2(dbPath)
42023
44252
  },
42024
44253
  conflicts: {
@@ -42027,7 +44256,7 @@ async function collectDiagnostics2(cwd = process.cwd()) {
42027
44256
  },
42028
44257
  logFile: {
42029
44258
  path: logPath,
42030
- exists: existsSync28(logPath),
44259
+ exists: existsSync29(logPath),
42031
44260
  sizeKb: Math.round(logFileSize / 1024)
42032
44261
  },
42033
44262
  recentSessions,
@@ -42121,8 +44350,8 @@ var init_diagnostics_pi = __esm(() => {
42121
44350
  });
42122
44351
 
42123
44352
  // src/lib/logs-pi.ts
42124
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync8 } from "node:fs";
42125
- import { join as join26 } from "node:path";
44353
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync8 } from "node:fs";
44354
+ import { join as join27 } from "node:path";
42126
44355
  function sanitizeLogContent2(content) {
42127
44356
  return sanitizeString2(content);
42128
44357
  }
@@ -42151,7 +44380,7 @@ function filterLogLinesBySession2(lines, sessionId) {
42151
44380
  }
42152
44381
  async function bundleIssueReport2(report, description, title, options = {}) {
42153
44382
  const LOG_TAIL_LINES = 400;
42154
- const allLogLines = report.logFile.exists ? readFileSync21(report.logFile.path, "utf-8").split(/\r?\n/) : [];
44383
+ const allLogLines = report.logFile.exists ? readFileSync22(report.logFile.path, "utf-8").split(/\r?\n/) : [];
42155
44384
  const logLines = filterLogLinesBySession2(allLogLines, options.sessionFilter ?? null);
42156
44385
  const recentLog = sanitizeLogContent2(logLines.slice(-LOG_TAIL_LINES).join(`
42157
44386
  `)).trim();
@@ -42187,7 +44416,7 @@ async function bundleIssueReport2(report, description, title, options = {}) {
42187
44416
  `);
42188
44417
  const bodyMarkdown = capBodyToGithubLimit(rawBodyMarkdown);
42189
44418
  const cwd = options.cwd ?? process.cwd();
42190
- const path6 = join26(cwd, `magic-context-pi-issue-${formatTimestamp2(options.now ?? new Date)}.md`);
44419
+ const path6 = join27(cwd, `magic-context-pi-issue-${formatTimestamp2(options.now ?? new Date)}.md`);
42191
44420
  writeFileSync8(path6, `${bodyMarkdown}
42192
44421
  `);
42193
44422
  return { path: path6, bodyMarkdown };
@@ -42198,15 +44427,15 @@ var init_logs_pi = __esm(() => {
42198
44427
  });
42199
44428
 
42200
44429
  // src/commands/doctor-pi.ts
42201
- import { execFileSync as execFileSync6, spawnSync as spawnSync6 } from "node:child_process";
42202
- import { existsSync as existsSync29, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync22, rmSync as rmSync3, statSync as statSync12 } from "node:fs";
42203
- import { createRequire as createRequire8 } from "node:module";
44430
+ import { execFileSync as execFileSync6, spawnSync as spawnSync7 } from "node:child_process";
44431
+ import { existsSync as existsSync30, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync23, rmSync as rmSync4, statSync as statSync13 } from "node:fs";
44432
+ import { createRequire as createRequire7 } from "node:module";
42204
44433
  import { homedir as homedir18 } from "node:os";
42205
- import { dirname as dirname14, isAbsolute as isAbsolute4, join as join27 } from "node:path";
44434
+ import { dirname as dirname15, isAbsolute as isAbsolute4, join as join28 } from "node:path";
42206
44435
  function depsFrom(options) {
42207
44436
  return {
42208
- ...DEFAULT_DEPS2,
42209
- prompts: options.prompts ?? DEFAULT_DEPS2.prompts,
44437
+ ...DEFAULT_DEPS3,
44438
+ prompts: options.prompts ?? DEFAULT_DEPS3.prompts,
42210
44439
  ...options.deps
42211
44440
  };
42212
44441
  }
@@ -42226,7 +44455,7 @@ function printDoctorHelp() {
42226
44455
  console.log("");
42227
44456
  }
42228
44457
  function selfVersion2() {
42229
- const req = createRequire8(import.meta.url);
44458
+ const req = createRequire7(import.meta.url);
42230
44459
  for (const relPath of ["../../package.json", "../package.json"]) {
42231
44460
  try {
42232
44461
  const pkg = req(relPath);
@@ -42292,7 +44521,7 @@ function summarize(results) {
42292
44521
  function readJsonc2(path6) {
42293
44522
  try {
42294
44523
  return {
42295
- value: import_comment_json13.parse(readFileSync22(path6, "utf-8"))
44524
+ value: import_comment_json13.parse(readFileSync23(path6, "utf-8"))
42296
44525
  };
42297
44526
  } catch (error51) {
42298
44527
  return {
@@ -42311,27 +44540,34 @@ function piPluginDirCandidates(packages, cwd) {
42311
44540
  const spec = typeof entry === "string" ? entry.trim() : "";
42312
44541
  if (!spec || spec.startsWith("npm:"))
42313
44542
  continue;
42314
- const resolved = isAbsolute4(spec) ? spec : join27(agentDir, spec);
44543
+ const resolved = isAbsolute4(spec) ? spec : join28(agentDir, spec);
42315
44544
  dirs.push(resolved);
42316
44545
  }
42317
- dirs.push(join27(agentDir, "npm", "node_modules", PACKAGE_NAME));
42318
- dirs.push(join27(cwd, ".pi", "npm", "node_modules", PACKAGE_NAME));
42319
- return dirs.filter((dir) => existsSync29(join27(dir, "package.json")));
44546
+ dirs.push(join28(agentDir, "npm", "node_modules", PACKAGE_NAME));
44547
+ dirs.push(join28(cwd, ".pi", "npm", "node_modules", PACKAGE_NAME));
44548
+ return dirs.filter((dir) => existsSync30(join28(dir, "package.json")));
42320
44549
  }
42321
44550
  function projectConfigPath(cwd) {
42322
44551
  return resolveCortexKitProjectConfigPath(cwd);
42323
44552
  }
42324
44553
  function readConfigForEmbedding(path6, isProjectConfig) {
42325
- if (!existsSync29(path6))
44554
+ if (!existsSync30(path6))
42326
44555
  return null;
42327
44556
  try {
42328
- const rawText = readFileSync22(path6, "utf-8");
44557
+ const rawText = readFileSync23(path6, "utf-8");
42329
44558
  const substituted = substituteConfigVariables({
42330
44559
  text: rawText,
42331
44560
  configPath: path6,
42332
44561
  isProjectConfig
42333
44562
  });
42334
- return import_comment_json13.parse(substituted.text);
44563
+ const rejectedKeyPaths = [];
44564
+ const parsed = sanitizeParsedJson(import_comment_json13.parse(substituted.text), {
44565
+ onRejectedKey: (keyPath) => rejectedKeyPaths.push(keyPath.join("."))
44566
+ });
44567
+ if (rejectedKeyPaths.length > 0) {
44568
+ throw new Error("unsafe prototype-pollution key in config");
44569
+ }
44570
+ return parsed;
42335
44571
  } catch {
42336
44572
  return null;
42337
44573
  }
@@ -42396,35 +44632,35 @@ function pinnedVersionFromPackageSpecifier(specifier) {
42396
44632
  }
42397
44633
  function cacheRoots(cwd) {
42398
44634
  const home = process.env.HOME?.trim();
42399
- const cacheHome = process.env.XDG_CACHE_HOME || join27(home || homedir18(), ".cache");
44635
+ const cacheHome = process.env.XDG_CACHE_HOME || join28(home || homedir18(), ".cache");
42400
44636
  const piCacheRoot = getPiCacheRoot();
42401
44637
  return [
42402
- join27(piCacheRoot, "extensions"),
42403
- join27(piCacheRoot, "packages"),
42404
- join27(cacheHome, "pi", "extensions"),
42405
- join27(cacheHome, "pi", "packages"),
42406
- join27(getPiAgentConfigDir(), "cache", "extensions"),
42407
- join27(getPiAgentConfigDir(), "npm", "node_modules", PACKAGE_NAME),
42408
- join27(cwd, ".pi", "npm", "node_modules", PACKAGE_NAME)
44638
+ join28(piCacheRoot, "extensions"),
44639
+ join28(piCacheRoot, "packages"),
44640
+ join28(cacheHome, "pi", "extensions"),
44641
+ join28(cacheHome, "pi", "packages"),
44642
+ join28(getPiAgentConfigDir(), "cache", "extensions"),
44643
+ join28(getPiAgentConfigDir(), "npm", "node_modules", PACKAGE_NAME),
44644
+ join28(cwd, ".pi", "npm", "node_modules", PACKAGE_NAME)
42409
44645
  ];
42410
44646
  }
42411
44647
  function findPiMagicContextCacheDirs(cwd, expectedVersion, force = false) {
42412
44648
  const found = new Map;
42413
44649
  const visit = (path6, depth) => {
42414
- if (depth < 0 || !existsSync29(path6))
44650
+ if (depth < 0 || !existsSync30(path6))
42415
44651
  return;
42416
44652
  let stat;
42417
44653
  try {
42418
- stat = statSync12(path6);
44654
+ stat = statSync13(path6);
42419
44655
  } catch {
42420
44656
  return;
42421
44657
  }
42422
44658
  if (!stat.isDirectory())
42423
44659
  return;
42424
- const packageJson = join27(path6, "package.json");
42425
- if (existsSync29(packageJson)) {
44660
+ const packageJson = join28(path6, "package.json");
44661
+ if (existsSync30(packageJson)) {
42426
44662
  try {
42427
- const pkg = JSON.parse(readFileSync22(packageJson, "utf-8"));
44663
+ const pkg = JSON.parse(readFileSync23(packageJson, "utf-8"));
42428
44664
  const name = typeof pkg.name === "string" ? pkg.name : "";
42429
44665
  const version2 = typeof pkg.version === "string" ? pkg.version : undefined;
42430
44666
  if (name === PACKAGE_NAME) {
@@ -42444,7 +44680,7 @@ function findPiMagicContextCacheDirs(cwd, expectedVersion, force = false) {
42444
44680
  return;
42445
44681
  }
42446
44682
  for (const entry of entries)
42447
- visit(join27(path6, entry), depth - 1);
44683
+ visit(join28(path6, entry), depth - 1);
42448
44684
  };
42449
44685
  for (const root of cacheRoots(cwd))
42450
44686
  visit(root, 5);
@@ -42489,7 +44725,7 @@ async function runHealthChecks2(options) {
42489
44725
  }
42490
44726
  const settingsPath = getPiUserExtensionsPath();
42491
44727
  let packages = [];
42492
- if (!existsSync29(settingsPath)) {
44728
+ if (!existsSync30(settingsPath)) {
42493
44729
  add2(results, "fail", `Pi settings not found at ${settingsPath}`);
42494
44730
  repairPlan.addPackageEntry = true;
42495
44731
  } else {
@@ -42513,7 +44749,7 @@ async function runHealthChecks2(options) {
42513
44749
  ["user", userConfigPath, true],
42514
44750
  ["project", projectPath, false]
42515
44751
  ]) {
42516
- if (!existsSync29(path6)) {
44752
+ if (!existsSync30(path6)) {
42517
44753
  if (required2) {
42518
44754
  add2(results, "warn", `No ${label} magic-context.jsonc found at ${path6}`);
42519
44755
  if (userConfigMigrationRefused) {
@@ -42550,8 +44786,8 @@ async function runHealthChecks2(options) {
42550
44786
  add2(results, "pass", `historian.model "${historianModel}" has thinking_level "${historianThinkingLevel}" configured`);
42551
44787
  }
42552
44788
  const storageDir = getMagicContextStorageDir();
42553
- const dbPath = join27(storageDir, "context.db");
42554
- const existedBeforeOpen = existsSync29(dbPath);
44789
+ const dbPath = join28(storageDir, "context.db");
44790
+ const existedBeforeOpen = existsSync30(dbPath);
42555
44791
  if (existedBeforeOpen)
42556
44792
  add2(results, "pass", `Shared context DB exists at ${dbPath}`);
42557
44793
  else
@@ -42574,8 +44810,9 @@ async function runHealthChecks2(options) {
42574
44810
  if (integrity?.integrity_check === "ok")
42575
44811
  add2(results, "pass", "SQLite integrity_check: ok");
42576
44812
  else
42577
- add2(results, "fail", `SQLite integrity_check: ${String(integrity?.integrity_check ?? "unknown")}`);
42578
- const counts = ROW_COUNT_TABLES.map((table) => `${table}=${countTable(db, table) ?? "n/a"}`).join(", ");
44813
+ add2(results, "fail", `SQLite integrity_check: ${String(integrity?.integrity_check ?? "unknown")}
44814
+ ${formatDatabaseRepairGuidance(dbPath)}`);
44815
+ const counts = ROW_COUNT_TABLES2.map((table) => `${table}=${countTable(db, table) ?? "n/a"}`).join(", ");
42579
44816
  add2(results, "info", `Shared DB row counts: ${counts}`);
42580
44817
  }
42581
44818
  } catch (error51) {
@@ -42585,7 +44822,8 @@ async function runHealthChecks2(options) {
42585
44822
  plugin_supported_version: error51.supportedVersion
42586
44823
  }).message);
42587
44824
  } else {
42588
- add2(results, "fail", `Could not open shared context DB: ${error51 instanceof Error ? error51.message : String(error51)}`);
44825
+ add2(results, "fail", `Could not open shared context DB: ${error51 instanceof Error ? error51.message : String(error51)}
44826
+ ${formatDatabaseRepairGuidance(dbPath)}`);
42589
44827
  }
42590
44828
  } finally {
42591
44829
  db?.close();
@@ -42600,7 +44838,16 @@ async function runHealthChecks2(options) {
42600
44838
  for (const config2 of [userRaw, projectRaw]) {
42601
44839
  const embedding = config2?.embedding;
42602
44840
  if (embedding && typeof embedding === "object" && !Array.isArray(embedding)) {
42603
- Object.assign(mergedEmbedding, embedding);
44841
+ for (const key of Object.keys(embedding)) {
44842
+ if (isPrototypePollutionKey(key))
44843
+ continue;
44844
+ Object.defineProperty(mergedEmbedding, key, {
44845
+ value: embedding[key],
44846
+ enumerable: true,
44847
+ configurable: true,
44848
+ writable: true
44849
+ });
44850
+ }
42604
44851
  }
42605
44852
  }
42606
44853
  if (projectRaw) {
@@ -42677,10 +44924,10 @@ async function runHealthChecks2(options) {
42677
44924
  add2(results, "pass", "Pi extension cache clean (no stale cached package found)");
42678
44925
  }
42679
44926
  const logPath = getMagicContextLogPath2("pi");
42680
- if (existsSync29(logPath)) {
42681
- const stat = statSync12(logPath);
44927
+ if (existsSync30(logPath)) {
44928
+ const stat = statSync13(logPath);
42682
44929
  const sizeKb = (stat.size / 1024).toFixed(0);
42683
- const lines = readFileSync22(logPath, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
44930
+ const lines = readFileSync23(logPath, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
42684
44931
  add2(results, "info", `Log file: ${logPath} (${sizeKb} KB)`);
42685
44932
  const lastLine = lines.at(-1);
42686
44933
  add2(results, "info", `Last plugin log line: ${lastLine ? sanitizeDiagnosticText(lastLine) : "<empty log>"}`);
@@ -42715,7 +44962,7 @@ async function runHealthChecks2(options) {
42715
44962
  return { results, repairPlan, ...summarize(results) };
42716
44963
  }
42717
44964
  function writeDefaultMagicContextConfig(path6) {
42718
- mkdirSync10(dirname14(path6), { recursive: true });
44965
+ mkdirSync10(dirname15(path6), { recursive: true });
42719
44966
  const config2 = {
42720
44967
  $schema: "https://raw.githubusercontent.com/cortexkit/magic-context/master/assets/magic-context.schema.json",
42721
44968
  ...MagicContextConfigSchema.parse({})
@@ -42737,7 +44984,7 @@ function repair2(plan, prompts) {
42737
44984
  }
42738
44985
  if (plan.writeUserConfig) {
42739
44986
  const configPath = getPiUserConfigPath();
42740
- if (!existsSync29(configPath)) {
44987
+ if (!existsSync30(configPath)) {
42741
44988
  try {
42742
44989
  writeDefaultMagicContextConfig(configPath);
42743
44990
  prompts.log.success(`Wrote default Magic Context config to ${configPath}`);
@@ -42749,7 +44996,7 @@ function repair2(plan, prompts) {
42749
44996
  }
42750
44997
  for (const path6 of plan.clearCachePaths) {
42751
44998
  try {
42752
- rmSync3(path6, { recursive: true, force: true });
44999
+ rmSync4(path6, { recursive: true, force: true });
42753
45000
  prompts.log.success(`Cleared stale Pi extension cache: ${path6}`);
42754
45001
  fixed += 1;
42755
45002
  } catch (error51) {
@@ -42857,7 +45104,7 @@ async function runDoctor3(options = {}) {
42857
45104
  const v22Result = await runV22BackfillCommands({
42858
45105
  name: "Pi",
42859
45106
  openDatabase: (readonly2 = true) => {
42860
- const dbPath = join27(getMagicContextStorageDir(), "context.db");
45107
+ const dbPath = join28(getMagicContextStorageDir(), "context.db");
42861
45108
  v22Db = readonly2 ? openExistingContextDatabase(dbPath, { readonly: true }) : openExistingContextDatabaseForMutation(dbPath);
42862
45109
  return v22Db;
42863
45110
  },
@@ -42922,13 +45169,14 @@ function parseDoctorArgs(args) {
42922
45169
  function doctor(args = []) {
42923
45170
  return runDoctor3(parseDoctorArgs(args));
42924
45171
  }
42925
- var import_comment_json13, PACKAGE_NAME = "@cortexkit/pi-magic-context", MIN_PI_VERSION = "0.74.0", ROW_COUNT_TABLES, DEFAULT_DEPS2;
45172
+ var import_comment_json13, PACKAGE_NAME = "@cortexkit/pi-magic-context", MIN_PI_VERSION = "0.74.0", ROW_COUNT_TABLES2, DEFAULT_DEPS3;
42926
45173
  var init_doctor_pi = __esm(async () => {
42927
45174
  init_migrate_config_location();
42928
45175
  init_project_security();
42929
45176
  init_magic_context();
42930
45177
  init_variable();
42931
45178
  init_data_path();
45179
+ init_jsonc_parser();
42932
45180
  init_config2();
42933
45181
  init_atomic_write();
42934
45182
  init_config_location_migration();
@@ -42947,8 +45195,8 @@ var init_doctor_pi = __esm(async () => {
42947
45195
  init_v22_backfill_commands()
42948
45196
  ]);
42949
45197
  import_comment_json13 = __toESM(require_src3(), 1);
42950
- ROW_COUNT_TABLES = ["tags", "compartments", "memories", "notes", "dream_runs"];
42951
- DEFAULT_DEPS2 = {
45198
+ ROW_COUNT_TABLES2 = ["tags", "compartments", "memories", "notes", "dream_runs"];
45199
+ DEFAULT_DEPS3 = {
42952
45200
  prompts: promptIO,
42953
45201
  collectDiagnostics: collectDiagnostics2,
42954
45202
  detectPiBinary,
@@ -42959,7 +45207,7 @@ var init_doctor_pi = __esm(async () => {
42959
45207
  openExistingContextDatabase,
42960
45208
  now: () => new Date,
42961
45209
  execFileSync: execFileSync6,
42962
- spawnSync: spawnSync6
45210
+ spawnSync: spawnSync7
42963
45211
  };
42964
45212
  });
42965
45213
 
@@ -42968,8 +45216,8 @@ var exports_doctor = {};
42968
45216
  __export(exports_doctor, {
42969
45217
  runDoctor: () => runDoctor4
42970
45218
  });
42971
- import { existsSync as existsSync30, rmSync as rmSync4 } from "node:fs";
42972
- import { join as join28 } from "node:path";
45219
+ import { existsSync as existsSync31, rmSync as rmSync5 } from "node:fs";
45220
+ import { join as join29 } from "node:path";
42973
45221
  async function runDoctor4(options) {
42974
45222
  if (options.clear)
42975
45223
  return runClear();
@@ -42987,7 +45235,7 @@ async function runDoctor4(options) {
42987
45235
  const result = await runV22BackfillCommands({
42988
45236
  name: "Magic Context",
42989
45237
  openDatabase: (readonly2 = true) => {
42990
- const dbPath = join28(getMagicContextStorageDir(), "context.db");
45238
+ const dbPath = join29(getMagicContextStorageDir(), "context.db");
42991
45239
  v22Db = readonly2 ? openExistingContextDatabase(dbPath, { readonly: true }) : openExistingContextDatabaseForMutation(dbPath);
42992
45240
  return v22Db;
42993
45241
  },
@@ -43076,8 +45324,8 @@ async function runClear() {
43076
45324
  const s = spinner2();
43077
45325
  s.start(`Clearing ${item.path}`);
43078
45326
  try {
43079
- if (existsSync30(item.path)) {
43080
- rmSync4(item.path, { recursive: true, force: true });
45327
+ if (existsSync31(item.path)) {
45328
+ rmSync5(item.path, { recursive: true, force: true });
43081
45329
  }
43082
45330
  s.stop(`Cleared ${item.path}`);
43083
45331
  } catch (err) {
@@ -43114,7 +45362,7 @@ var init_doctor = __esm(async () => {
43114
45362
 
43115
45363
  // src/index.ts
43116
45364
  init_prompts();
43117
- import { createRequire as createRequire9 } from "node:module";
45365
+ import { createRequire as createRequire8 } from "node:module";
43118
45366
 
43119
45367
  // src/lib/sqlite-preflight.ts
43120
45368
  async function probeSqliteBackend() {
@@ -43142,7 +45390,7 @@ async function runSqlitePreflight(probe = probeSqliteBackend, report = (message)
43142
45390
 
43143
45391
  // src/index.ts
43144
45392
  function getVersion() {
43145
- const req = createRequire9(import.meta.url);
45393
+ const req = createRequire8(import.meta.url);
43146
45394
  for (const relPath of ["../../package.json", "../package.json"]) {
43147
45395
  try {
43148
45396
  const pkg = req(relPath);
@@ -43180,6 +45428,7 @@ function printUsage() {
43180
45428
  console.log(" doctor migrate Migrate OpenCode session to Pi or OMP JSONL");
43181
45429
  console.log(" doctor migrate-session Re-home an OpenCode session to another directory");
43182
45430
  console.log(" doctor merge-identity Merge project rows (--from ID --to ID [--dry-run] [--yes])");
45431
+ console.log(" doctor repair-db Back up and salvage a corrupted shared database");
43183
45432
  console.log("");
43184
45433
  console.log(" Harness selection:");
43185
45434
  console.log(" --harness opencode Target OpenCode only");
@@ -43221,17 +45470,21 @@ async function main(argv = process.argv.slice(2)) {
43221
45470
  console.error("Usage: magic-context doctor drain-authority <project>");
43222
45471
  return 1;
43223
45472
  }
43224
- const [{ runDoctorDrainAuthority: runDoctorDrainAuthority2 }, { getMagicContextStorageDir: getMagicContextStorageDir2 }, { join: join29 }] = await Promise.all([
45473
+ const [{ runDoctorDrainAuthority: runDoctorDrainAuthority2 }, { getMagicContextStorageDir: getMagicContextStorageDir2 }, { join: join30 }] = await Promise.all([
43225
45474
  init_doctor_authority().then(() => exports_doctor_authority),
43226
45475
  Promise.resolve().then(() => (init_data_path(), exports_data_path)),
43227
45476
  import("node:path")
43228
45477
  ]);
43229
- return runDoctorDrainAuthority2(projectRoot, join29(getMagicContextStorageDir2(), "context.db"));
45478
+ return runDoctorDrainAuthority2(projectRoot, join30(getMagicContextStorageDir2(), "context.db"));
43230
45479
  }
43231
45480
  if (rest[0] === "merge-identity") {
43232
45481
  const { runMergeIdentityCli: runMergeIdentityCli2 } = await init_doctor_merge_identity().then(() => exports_doctor_merge_identity);
43233
45482
  return runMergeIdentityCli2(rest.slice(1));
43234
45483
  }
45484
+ if (rest[0] === "repair-db") {
45485
+ const { runRepairDbCli: runRepairDbCli2 } = await init_doctor_repair_db().then(() => exports_doctor_repair_db);
45486
+ return runRepairDbCli2(rest.slice(1));
45487
+ }
43235
45488
  if (rest[0] === "migrate") {
43236
45489
  const { runMigrateCli: runMigrateCli2 } = await init_migrate().then(() => exports_migrate);
43237
45490
  return runMigrateCli2(rest.slice(1));