@cortexkit/opencode-magic-context 0.15.7 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +41 -15
  2. package/dist/agents/magic-context-prompt.d.ts +2 -13
  3. package/dist/agents/magic-context-prompt.d.ts.map +1 -1
  4. package/dist/cli/diagnostics.d.ts.map +1 -1
  5. package/dist/cli/migrate.d.ts +70 -0
  6. package/dist/cli/migrate.d.ts.map +1 -0
  7. package/dist/cli.js +666 -29
  8. package/dist/config/schema/magic-context.d.ts +67 -4
  9. package/dist/config/schema/magic-context.d.ts.map +1 -1
  10. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  11. package/dist/features/magic-context/compaction.d.ts +1 -1
  12. package/dist/features/magic-context/compaction.d.ts.map +1 -1
  13. package/dist/features/magic-context/compartment-storage.d.ts +1 -1
  14. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  15. package/dist/features/magic-context/compression-depth-storage.d.ts +1 -1
  16. package/dist/features/magic-context/compression-depth-storage.d.ts.map +1 -1
  17. package/dist/features/magic-context/dreamer/lease.d.ts +1 -1
  18. package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
  19. package/dist/features/magic-context/dreamer/queue.d.ts +8 -3
  20. package/dist/features/magic-context/dreamer/queue.d.ts.map +1 -1
  21. package/dist/features/magic-context/dreamer/runner.d.ts +1 -1
  22. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  23. package/dist/features/magic-context/dreamer/scheduler.d.ts +1 -1
  24. package/dist/features/magic-context/dreamer/scheduler.d.ts.map +1 -1
  25. package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts +1 -1
  26. package/dist/features/magic-context/dreamer/storage-dream-runs.d.ts.map +1 -1
  27. package/dist/features/magic-context/dreamer/storage-dream-state.d.ts +1 -1
  28. package/dist/features/magic-context/dreamer/storage-dream-state.d.ts.map +1 -1
  29. package/dist/features/magic-context/git-commits/indexer.d.ts +1 -1
  30. package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -1
  31. package/dist/features/magic-context/git-commits/search-git-commits.d.ts +1 -1
  32. package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -1
  33. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +1 -1
  34. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -1
  35. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts +1 -1
  36. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -1
  37. package/dist/features/magic-context/key-files/identify-key-files.d.ts +1 -1
  38. package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -1
  39. package/dist/features/magic-context/key-files/read-stats.d.ts +1 -1
  40. package/dist/features/magic-context/key-files/read-stats.d.ts.map +1 -1
  41. package/dist/features/magic-context/key-files/storage-key-files.d.ts +1 -1
  42. package/dist/features/magic-context/key-files/storage-key-files.d.ts.map +1 -1
  43. package/dist/features/magic-context/memory/embedding-backfill.d.ts +1 -1
  44. package/dist/features/magic-context/memory/embedding-backfill.d.ts.map +1 -1
  45. package/dist/features/magic-context/memory/embedding-cache.d.ts +1 -1
  46. package/dist/features/magic-context/memory/embedding-cache.d.ts.map +1 -1
  47. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  48. package/dist/features/magic-context/memory/embedding.d.ts +1 -1
  49. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  50. package/dist/features/magic-context/memory/normalize-hash.d.ts.map +1 -1
  51. package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
  52. package/dist/features/magic-context/memory/promotion.d.ts +1 -1
  53. package/dist/features/magic-context/memory/promotion.d.ts.map +1 -1
  54. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +1 -1
  55. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  56. package/dist/features/magic-context/memory/storage-memory-fts.d.ts +1 -1
  57. package/dist/features/magic-context/memory/storage-memory-fts.d.ts.map +1 -1
  58. package/dist/features/magic-context/memory/storage-memory.d.ts +1 -1
  59. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  60. package/dist/features/magic-context/message-index.d.ts +1 -1
  61. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  62. package/dist/features/magic-context/migrations.d.ts +1 -1
  63. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  64. package/dist/features/magic-context/mock-database.d.ts +1 -1
  65. package/dist/features/magic-context/mock-database.d.ts.map +1 -1
  66. package/dist/features/magic-context/plugin-messages.d.ts +1 -1
  67. package/dist/features/magic-context/plugin-messages.d.ts.map +1 -1
  68. package/dist/features/magic-context/search.d.ts +1 -1
  69. package/dist/features/magic-context/search.d.ts.map +1 -1
  70. package/dist/features/magic-context/sidekick/agent.d.ts +2 -1
  71. package/dist/features/magic-context/sidekick/agent.d.ts.map +1 -1
  72. package/dist/features/magic-context/sidekick/core.d.ts +38 -0
  73. package/dist/features/magic-context/sidekick/core.d.ts.map +1 -0
  74. package/dist/features/magic-context/storage-db.d.ts +20 -1
  75. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  76. package/dist/features/magic-context/storage-meta-persisted.d.ts +1 -1
  77. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  78. package/dist/features/magic-context/storage-meta-session.d.ts +1 -1
  79. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  80. package/dist/features/magic-context/storage-meta-shared.d.ts +1 -1
  81. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  82. package/dist/features/magic-context/storage-notes.d.ts +1 -1
  83. package/dist/features/magic-context/storage-notes.d.ts.map +1 -1
  84. package/dist/features/magic-context/storage-ops.d.ts +1 -1
  85. package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
  86. package/dist/features/magic-context/storage-source.d.ts +1 -1
  87. package/dist/features/magic-context/storage-source.d.ts.map +1 -1
  88. package/dist/features/magic-context/storage-tags.d.ts +17 -1
  89. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  90. package/dist/features/magic-context/tagger.d.ts +1 -1
  91. package/dist/features/magic-context/tagger.d.ts.map +1 -1
  92. package/dist/features/magic-context/user-memory/review-user-memories.d.ts +1 -1
  93. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  94. package/dist/features/magic-context/user-memory/storage-user-memory.d.ts +1 -1
  95. package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -1
  96. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -1
  97. package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
  98. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  99. package/dist/hooks/magic-context/command-handler.d.ts +1 -1
  100. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  101. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +1 -1
  102. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  103. package/dist/hooks/magic-context/compartment-prompt.d.ts +1 -0
  104. package/dist/hooks/magic-context/compartment-prompt.d.ts.map +1 -1
  105. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts +1 -1
  106. package/dist/hooks/magic-context/compartment-runner-compressor.d.ts.map +1 -1
  107. package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts +1 -1
  108. package/dist/hooks/magic-context/compartment-runner-drop-queue.d.ts.map +1 -1
  109. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts +1 -0
  110. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  111. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  112. package/dist/hooks/magic-context/compartment-runner-types.d.ts +1 -1
  113. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  114. package/dist/hooks/magic-context/compartment-trigger.d.ts +1 -1
  115. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  116. package/dist/hooks/magic-context/execute-flush.d.ts +1 -1
  117. package/dist/hooks/magic-context/execute-flush.d.ts.map +1 -1
  118. package/dist/hooks/magic-context/execute-status.d.ts +1 -1
  119. package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
  120. package/dist/hooks/magic-context/historian-state-file.d.ts +29 -0
  121. package/dist/hooks/magic-context/historian-state-file.d.ts.map +1 -0
  122. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  123. package/dist/hooks/magic-context/inject-compartments.d.ts +1 -1
  124. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  125. package/dist/hooks/magic-context/note-nudger.d.ts +1 -1
  126. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  127. package/dist/hooks/magic-context/nudge-placement-store.d.ts +1 -1
  128. package/dist/hooks/magic-context/nudge-placement-store.d.ts.map +1 -1
  129. package/dist/hooks/magic-context/read-session-chunk.d.ts +39 -0
  130. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  131. package/dist/hooks/magic-context/read-session-db.d.ts +1 -1
  132. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  133. package/dist/hooks/magic-context/read-session-raw.d.ts +1 -1
  134. package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
  135. package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
  136. package/dist/hooks/magic-context/system-prompt-hash.d.ts +6 -5
  137. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  138. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  139. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  140. package/dist/index.js +8284 -8166
  141. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  142. package/dist/plugin/messages-transform.d.ts +1 -1
  143. package/dist/plugin/rpc-handlers.d.ts +4 -0
  144. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  145. package/dist/plugin/tool-registry.d.ts.map +1 -1
  146. package/dist/shared/conflict-detector.d.ts.map +1 -1
  147. package/dist/shared/data-path.d.ts +22 -0
  148. package/dist/shared/data-path.d.ts.map +1 -1
  149. package/dist/shared/harness.d.ts +43 -0
  150. package/dist/shared/harness.d.ts.map +1 -0
  151. package/dist/shared/rpc-notifications.d.ts +4 -2
  152. package/dist/shared/rpc-notifications.d.ts.map +1 -1
  153. package/dist/shared/sqlite-helpers.d.ts +16 -0
  154. package/dist/shared/sqlite-helpers.d.ts.map +1 -0
  155. package/dist/shared/sqlite.d.ts +55 -0
  156. package/dist/shared/sqlite.d.ts.map +1 -0
  157. package/dist/shared/subagent-runner.d.ts +202 -0
  158. package/dist/shared/subagent-runner.d.ts.map +1 -0
  159. package/dist/shared/tag-transcript.d.ts +66 -0
  160. package/dist/shared/tag-transcript.d.ts.map +1 -0
  161. package/dist/shared/transcript-opencode.d.ts +71 -0
  162. package/dist/shared/transcript-opencode.d.ts.map +1 -0
  163. package/dist/shared/transcript.d.ts +212 -0
  164. package/dist/shared/transcript.d.ts.map +1 -0
  165. package/dist/shared/tui-config.d.ts.map +1 -1
  166. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  167. package/dist/tools/ctx-memory/types.d.ts +13 -2
  168. package/dist/tools/ctx-memory/types.d.ts.map +1 -1
  169. package/dist/tools/ctx-note/tools.d.ts +8 -2
  170. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  171. package/dist/tools/ctx-reduce/tools.d.ts +1 -1
  172. package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
  173. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  174. package/dist/tools/ctx-search/types.d.ts +8 -2
  175. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  176. package/dist/tui/data/context-db.d.ts.map +1 -1
  177. package/package.json +7 -5
  178. package/src/shared/conflict-detector.test.ts +44 -1
  179. package/src/shared/conflict-detector.ts +24 -8
  180. package/src/shared/data-path.test.ts +53 -1
  181. package/src/shared/data-path.ts +28 -0
  182. package/src/shared/harness.ts +61 -0
  183. package/src/shared/rpc-notifications.ts +11 -5
  184. package/src/shared/sqlite-helpers.ts +27 -0
  185. package/src/shared/sqlite.ts +91 -0
  186. package/src/shared/subagent-runner.ts +206 -0
  187. package/src/shared/tag-transcript.ts +541 -0
  188. package/src/shared/transcript-opencode.ts +259 -0
  189. package/src/shared/transcript.ts +226 -0
  190. package/src/shared/tui-config.ts +34 -8
  191. package/src/tui/data/context-db.ts +5 -1
package/dist/cli.js CHANGED
@@ -8260,8 +8260,24 @@ function matchesPackageName(entry, canonicalNames) {
8260
8260
  const nameOnly = lastAt > 0 ? entry.slice(0, lastAt) : entry;
8261
8261
  return canonicalNames.has(nameOnly);
8262
8262
  }
8263
+ function extractPluginName(entry) {
8264
+ if (typeof entry === "string")
8265
+ return entry;
8266
+ if (Array.isArray(entry) && typeof entry[0] === "string")
8267
+ return entry[0];
8268
+ return null;
8269
+ }
8263
8270
  function collectPluginEntries(directory) {
8264
8271
  const plugins = [];
8272
+ const pushFrom = (entries) => {
8273
+ if (!entries)
8274
+ return;
8275
+ for (const entry of entries) {
8276
+ const name = extractPluginName(entry);
8277
+ if (name)
8278
+ plugins.push(name);
8279
+ }
8280
+ };
8265
8281
  for (const configPath of [
8266
8282
  join2(directory, ".opencode", "opencode.jsonc"),
8267
8283
  join2(directory, ".opencode", "opencode.json"),
@@ -8269,17 +8285,13 @@ function collectPluginEntries(directory) {
8269
8285
  join2(directory, "opencode.json")
8270
8286
  ]) {
8271
8287
  const config = readJsoncFile(configPath);
8272
- if (config?.plugin) {
8273
- plugins.push(...config.plugin);
8274
- }
8288
+ pushFrom(config?.plugin);
8275
8289
  }
8276
8290
  try {
8277
8291
  const paths = getOpenCodeConfigPaths({ binary: "opencode" });
8278
8292
  for (const configPath of [paths.configJsonc, paths.configJson]) {
8279
8293
  const config = readJsoncFile(configPath);
8280
- if (config?.plugin) {
8281
- plugins.push(...config.plugin);
8282
- }
8294
+ pushFrom(config?.plugin);
8283
8295
  }
8284
8296
  } catch {}
8285
8297
  return plugins;
@@ -8495,6 +8507,12 @@ function fixConflicts(directory, conflicts) {
8495
8507
  // src/shared/data-path.ts
8496
8508
  import * as os from "node:os";
8497
8509
  import * as path from "node:path";
8510
+ function getDataDir() {
8511
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
8512
+ }
8513
+ function getMagicContextStorageDir() {
8514
+ return path.join(getDataDir(), "cortexkit", "magic-context");
8515
+ }
8498
8516
  function getCacheDir() {
8499
8517
  return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
8500
8518
  }
@@ -8561,6 +8579,17 @@ if (!isTestEnv) {
8561
8579
  // src/shared/tui-config.ts
8562
8580
  var PLUGIN_NAME = "@cortexkit/opencode-magic-context";
8563
8581
  var PLUGIN_ENTRY = `${PLUGIN_NAME}@latest`;
8582
+ function isMagicContextEntry(entry) {
8583
+ if (!entry)
8584
+ return false;
8585
+ if (entry === PLUGIN_NAME)
8586
+ return true;
8587
+ if (entry.startsWith(`${PLUGIN_NAME}@`))
8588
+ return true;
8589
+ if (entry.includes("opencode-magic-context"))
8590
+ return true;
8591
+ return false;
8592
+ }
8564
8593
  function resolveTuiConfigPath() {
8565
8594
  const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
8566
8595
  const jsoncPath = join6(configDir, "tui.jsonc");
@@ -8580,12 +8609,12 @@ function ensureTuiPluginEntry() {
8580
8609
  config = import_comment_json.parse(raw) ?? {};
8581
8610
  }
8582
8611
  const plugins = Array.isArray(config.plugin) ? config.plugin.filter((p) => typeof p === "string") : [];
8583
- const existingIdx = plugins.findIndex((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`));
8612
+ const existingIdx = plugins.findIndex(isMagicContextEntry);
8584
8613
  if (existingIdx >= 0) {
8585
- if (plugins[existingIdx] === PLUGIN_ENTRY) {
8614
+ const existing = plugins[existingIdx];
8615
+ if (existing === PLUGIN_ENTRY) {
8586
8616
  return false;
8587
8617
  }
8588
- const existing = plugins[existingIdx];
8589
8618
  if (existing === PLUGIN_NAME) {
8590
8619
  plugins[existingIdx] = PLUGIN_ENTRY;
8591
8620
  } else {
@@ -8835,7 +8864,7 @@ function getPluginCacheInfo() {
8835
8864
  }
8836
8865
  function getStorageDir() {
8837
8866
  const dataHome = process.env.XDG_DATA_HOME || join8(homedir5(), ".local", "share");
8838
- return join8(dataHome, "opencode", "storage", "plugin", "magic-context");
8867
+ return join8(dataHome, "cortexkit", "magic-context");
8839
8868
  }
8840
8869
  function fileSize(path3) {
8841
8870
  try {
@@ -10509,7 +10538,7 @@ async function runIssueFlow() {
10509
10538
  "issue",
10510
10539
  "create",
10511
10540
  "-R",
10512
- "cortexkit/opencode-magic-context",
10541
+ "cortexkit/magic-context",
10513
10542
  "--title",
10514
10543
  title,
10515
10544
  "--body-file",
@@ -10524,7 +10553,7 @@ async function runIssueFlow() {
10524
10553
  } else if (shouldSubmit && !isGhInstalled()) {
10525
10554
  R2.warn("gh CLI not found — falling back to browser");
10526
10555
  }
10527
- const url = `https://github.com/cortexkit/opencode-magic-context/issues/new?title=${encodeURIComponent(title)}&template=bug_report.yml`;
10556
+ const url = `https://github.com/cortexkit/magic-context/issues/new?title=${encodeURIComponent(title)}&template=bug_report.yml`;
10528
10557
  R2.info(`Open this URL and paste the contents of ${bundled.path} into the Diagnostics field:`);
10529
10558
  R2.info(url);
10530
10559
  openBrowser(url);
@@ -10965,15 +10994,614 @@ async function runDoctor(options = {}) {
10965
10994
  return 0;
10966
10995
  }
10967
10996
 
10997
+ // src/cli/migrate.ts
10998
+ import { randomBytes } from "node:crypto";
10999
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "node:fs";
11000
+ import { homedir as homedir8 } from "node:os";
11001
+ import { join as join11 } from "node:path";
11002
+
11003
+ // src/shared/sqlite.ts
11004
+ var isBun = typeof process !== "undefined" && typeof process.versions?.bun === "string";
11005
+ var bunSpec = "bun:" + "sqlite";
11006
+ var betterSpec = "better-" + "sqlite3";
11007
+ var sqliteModule = isBun ? await import(bunSpec) : await import(betterSpec);
11008
+ var DatabaseImpl = isBun ? sqliteModule.Database : sqliteModule.default;
11009
+ var Database = DatabaseImpl;
11010
+
11011
+ // src/cli/migrate.ts
11012
+ var DEFAULT_PROVIDER = "openai-codex";
11013
+ var DEFAULT_MODEL = "gpt-5.5";
11014
+ var MIGRATION_COMPACTION_SUMMARY = "Magic Context compacted prior conversation. See <session-history> block for the structured summary.";
11015
+ function defaultOpenCodeDbPath() {
11016
+ return join11(homedir8(), ".local", "share", "opencode", "opencode.db");
11017
+ }
11018
+ function defaultCortexkitDbPath() {
11019
+ return join11(getMagicContextStorageDir(), "context.db");
11020
+ }
11021
+ function defaultPiSessionsRoot() {
11022
+ return join11(homedir8(), ".pi", "agent", "sessions");
11023
+ }
11024
+ function defaultFs() {
11025
+ return { existsSync: existsSync8, mkdirSync: mkdirSync3, writeFileSync: writeFileSync5 };
11026
+ }
11027
+ function stmt(db, sql) {
11028
+ return db.prepare(sql);
11029
+ }
11030
+ function projectPathToPiDirSlug(projectPath) {
11031
+ return `--${projectPath.replace(/^\/+|\/+$/g, "").replaceAll("/", "-")}--`;
11032
+ }
11033
+ function formatPiFilenameTimestamp(date) {
11034
+ return date.toISOString().replaceAll(":", "-").replace(".", "-");
11035
+ }
11036
+ function generateUuidV7(date = new Date) {
11037
+ const bytes = randomBytes(16);
11038
+ let ms = BigInt(date.getTime());
11039
+ for (let i = 5;i >= 0; i--) {
11040
+ bytes[i] = Number(ms & 0xffn);
11041
+ ms >>= 8n;
11042
+ }
11043
+ bytes[6] = bytes[6] & 15 | 112;
11044
+ bytes[8] = bytes[8] & 63 | 128;
11045
+ const hex = bytes.toString("hex");
11046
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
11047
+ }
11048
+ function shortId() {
11049
+ return randomBytes(4).toString("hex");
11050
+ }
11051
+ function parseJsonObject(text2) {
11052
+ const parsed = JSON.parse(text2);
11053
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
11054
+ throw new Error("Expected JSON object");
11055
+ }
11056
+ return parsed;
11057
+ }
11058
+ function isoFromMs(ms, fallback) {
11059
+ return new Date(typeof ms === "number" && Number.isFinite(ms) ? ms : fallback.getTime()).toISOString();
11060
+ }
11061
+ function textFromUnknown(value) {
11062
+ if (value === undefined || value === null)
11063
+ return "";
11064
+ if (typeof value === "string")
11065
+ return value;
11066
+ return JSON.stringify(value, null, 2);
11067
+ }
11068
+ function roleFromMessage(row) {
11069
+ const data = parseJsonObject(row.data);
11070
+ return data.role === "user" || data.role === "assistant" ? data.role : undefined;
11071
+ }
11072
+ function tokensFromMessage(row) {
11073
+ try {
11074
+ const data = parseJsonObject(row.data);
11075
+ return data.tokens ?? {};
11076
+ } catch {
11077
+ return {};
11078
+ }
11079
+ }
11080
+ function extractModel(rows) {
11081
+ for (const row of rows) {
11082
+ try {
11083
+ const data = parseJsonObject(row.data);
11084
+ const provider = data.providerID ?? data.model?.providerID;
11085
+ const modelId = data.modelID ?? data.model?.modelID;
11086
+ if (provider && modelId)
11087
+ return { provider, modelId };
11088
+ } catch {}
11089
+ }
11090
+ return { provider: DEFAULT_PROVIDER, modelId: DEFAULT_MODEL };
11091
+ }
11092
+ function normalizeOpenCodeTool(part) {
11093
+ const callId = part.callID ?? part.call_id ?? part.toolCallId ?? part.tool_call_id ?? `migrated_${shortId()}`;
11094
+ const name = part.tool ?? part.tool_name ?? part.name ?? part.state?.title ?? "unknown_tool";
11095
+ const input = part.input ?? part.state?.input ?? {};
11096
+ const output = part.output ?? part.state?.output ?? part.state?.metadata?.output ?? "";
11097
+ return { callId, name, input, output };
11098
+ }
11099
+ function tokensToPiUsage(tokens) {
11100
+ const input = tokens?.input ?? 0;
11101
+ const output = tokens?.output ?? 0;
11102
+ const cacheRead = tokens?.cache?.read ?? 0;
11103
+ const cacheWrite = tokens?.cache?.write ?? 0;
11104
+ const total = tokens?.total ?? input + output + cacheRead + cacheWrite;
11105
+ return {
11106
+ input,
11107
+ output,
11108
+ cacheRead,
11109
+ cacheWrite,
11110
+ totalTokens: total,
11111
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
11112
+ };
11113
+ }
11114
+ function makeMessageEntry(role, text2, timestamp, parentId, usage) {
11115
+ const message = {
11116
+ role,
11117
+ content: [{ type: "text", text: text2 }],
11118
+ timestamp: Date.parse(timestamp)
11119
+ };
11120
+ if (role === "assistant") {
11121
+ message.usage = usage;
11122
+ }
11123
+ return {
11124
+ type: "message",
11125
+ id: shortId(),
11126
+ parentId,
11127
+ timestamp,
11128
+ message
11129
+ };
11130
+ }
11131
+ function makeThinkingEntry(text2, timestamp, parentId, usage) {
11132
+ return {
11133
+ type: "message",
11134
+ id: shortId(),
11135
+ parentId,
11136
+ timestamp,
11137
+ message: {
11138
+ role: "assistant",
11139
+ content: [{ type: "thinking", thinking: text2, thinkingSignature: null }],
11140
+ timestamp: Date.parse(timestamp),
11141
+ usage
11142
+ }
11143
+ };
11144
+ }
11145
+ function makeToolCallEntry(tool, timestamp, parentId, usage) {
11146
+ return {
11147
+ type: "message",
11148
+ id: shortId(),
11149
+ parentId,
11150
+ timestamp,
11151
+ message: {
11152
+ role: "assistant",
11153
+ content: [
11154
+ {
11155
+ type: "toolCall",
11156
+ id: tool.callId,
11157
+ name: tool.name,
11158
+ arguments: tool.input ?? {}
11159
+ }
11160
+ ],
11161
+ timestamp: Date.parse(timestamp),
11162
+ usage
11163
+ }
11164
+ };
11165
+ }
11166
+ function makeToolResultEntry(tool, timestamp, parentId) {
11167
+ return {
11168
+ type: "message",
11169
+ id: shortId(),
11170
+ parentId,
11171
+ timestamp,
11172
+ message: {
11173
+ role: "toolResult",
11174
+ toolCallId: tool.callId,
11175
+ toolName: tool.name,
11176
+ content: [{ type: "text", text: textFromUnknown(tool.output) }],
11177
+ isError: false,
11178
+ timestamp: Date.parse(timestamp)
11179
+ }
11180
+ };
11181
+ }
11182
+ function convertPartToEntries(ctx) {
11183
+ const part = parseJsonObject(ctx.row.data);
11184
+ switch (part.type) {
11185
+ case "step-start":
11186
+ case "step-finish":
11187
+ case "patch":
11188
+ return [];
11189
+ case "text":
11190
+ return part.text ? [makeMessageEntry(ctx.role, part.text, ctx.timestamp, ctx.parentId, ctx.usage)] : [];
11191
+ case "reasoning":
11192
+ return part.text ? [makeThinkingEntry(part.text, ctx.timestamp, ctx.parentId, ctx.usage)] : [];
11193
+ case "tool": {
11194
+ const tool = normalizeOpenCodeTool(part);
11195
+ const call = makeToolCallEntry(tool, ctx.timestamp, ctx.parentId, ctx.usage);
11196
+ const result = makeToolResultEntry(tool, ctx.timestamp, call.id);
11197
+ return [call, result];
11198
+ }
11199
+ case "file": {
11200
+ const name = part.filename ?? part.name ?? "attachment";
11201
+ return [
11202
+ makeMessageEntry(ctx.role, `<file omitted: ${name}>`, ctx.timestamp, ctx.parentId, ctx.usage)
11203
+ ];
11204
+ }
11205
+ default:
11206
+ return [];
11207
+ }
11208
+ }
11209
+ function buildPiEntries(params) {
11210
+ const sessionUuid = generateUuidV7(params.now);
11211
+ const nowIso = params.now.toISOString();
11212
+ const entries = [
11213
+ {
11214
+ type: "session",
11215
+ version: 3,
11216
+ id: sessionUuid,
11217
+ timestamp: nowIso,
11218
+ cwd: params.session.directory ?? params.session.path ?? process.cwd()
11219
+ },
11220
+ {
11221
+ type: "model_change",
11222
+ id: shortId(),
11223
+ parentId: null,
11224
+ timestamp: nowIso,
11225
+ provider: params.provider,
11226
+ modelId: params.modelId
11227
+ }
11228
+ ];
11229
+ const boundary = makeMessageEntry("user", `<!-- migrated from OpenCode session ${params.session.id} at ${nowIso} -->
11230
+
11231
+ The following conversation was migrated from a different harness. Reasoning context from prior turns may be incomplete; tool calls reference tools that may not exist in this environment.`, nowIso, null, {
11232
+ input: 0,
11233
+ output: 0,
11234
+ cacheRead: 0,
11235
+ cacheWrite: 0,
11236
+ totalTokens: 0,
11237
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
11238
+ });
11239
+ entries.push(boundary);
11240
+ const partsByMessage = new Map;
11241
+ for (const part of params.parts) {
11242
+ const list = partsByMessage.get(part.message_id) ?? [];
11243
+ list.push(part);
11244
+ partsByMessage.set(part.message_id, list);
11245
+ }
11246
+ const messageIdToLastPiEntryId = new Map;
11247
+ const orderedSourceMessageIds = [];
11248
+ let parentId = boundary.id;
11249
+ for (const message of params.messages) {
11250
+ const role = roleFromMessage(message);
11251
+ if (!role)
11252
+ continue;
11253
+ const timestamp = isoFromMs(message.time_created, params.now);
11254
+ const tokens = tokensFromMessage(message);
11255
+ const usage = tokensToPiUsage(tokens);
11256
+ let lastEntryIdForMessage = null;
11257
+ for (const part of partsByMessage.get(message.id) ?? []) {
11258
+ const newEntries = convertPartToEntries({
11259
+ role,
11260
+ row: part,
11261
+ timestamp,
11262
+ parentId,
11263
+ usage
11264
+ });
11265
+ for (const entry of newEntries) {
11266
+ if (entry.parentId === undefined || entry.parentId === parentId)
11267
+ entry.parentId = parentId;
11268
+ entries.push(entry);
11269
+ parentId = entry.id;
11270
+ lastEntryIdForMessage = parentId;
11271
+ }
11272
+ }
11273
+ if (lastEntryIdForMessage !== null) {
11274
+ messageIdToLastPiEntryId.set(message.id, lastEntryIdForMessage);
11275
+ orderedSourceMessageIds.push(message.id);
11276
+ }
11277
+ }
11278
+ return {
11279
+ entries,
11280
+ piSessionId: sessionUuid,
11281
+ messageIdToLastPiEntryId,
11282
+ orderedSourceMessageIds
11283
+ };
11284
+ }
11285
+ function fetchRows(db, sessionId, maxMessages) {
11286
+ const session = stmt(db, "SELECT id, title, directory, path, time_created FROM session WHERE id = ?").get(sessionId);
11287
+ if (!session)
11288
+ throw new Error(`OpenCode session not found: ${sessionId}`);
11289
+ const sourceMessageCount = stmt(db, "SELECT COUNT(*) AS count FROM message WHERE session_id = ?").get(sessionId)?.count ?? 0;
11290
+ const limitClause = maxMessages ? "LIMIT ?" : "";
11291
+ const params = maxMessages ? [sessionId, maxMessages] : [sessionId];
11292
+ const newestFirst = stmt(db, `SELECT id, time_created, data FROM message WHERE session_id = ? ORDER BY time_created DESC, id DESC ${limitClause}`).all(...params);
11293
+ const messages = newestFirst.reverse();
11294
+ const ids = messages.map((row) => row.id);
11295
+ const parts = ids.length ? stmt(db, `SELECT id, message_id, time_created, data FROM part WHERE message_id IN (${ids.map(() => "?").join(",")}) ORDER BY time_created, id`).all(...ids) : [];
11296
+ return { session, sourceMessageCount, messages, parts };
11297
+ }
11298
+ function remapBoundaryId(openCodeMessageId, messageIdToLastPiEntryId, orderedSourceMessageIds) {
11299
+ const direct = messageIdToLastPiEntryId.get(openCodeMessageId);
11300
+ if (direct !== undefined)
11301
+ return { piEntryId: direct, exact: true };
11302
+ let nearestAtOrBefore;
11303
+ for (const id of orderedSourceMessageIds) {
11304
+ if (id <= openCodeMessageId) {
11305
+ nearestAtOrBefore = id;
11306
+ } else {
11307
+ break;
11308
+ }
11309
+ }
11310
+ if (nearestAtOrBefore === undefined)
11311
+ return;
11312
+ const piEntryId = messageIdToLastPiEntryId.get(nearestAtOrBefore);
11313
+ if (piEntryId === undefined)
11314
+ return;
11315
+ return { piEntryId, exact: false };
11316
+ }
11317
+ function insertCompactionMarker(entries, boundaryEntryId) {
11318
+ if (boundaryEntryId === undefined)
11319
+ return { written: false };
11320
+ const boundaryIndex = entries.findIndex((entry) => entry.id === boundaryEntryId);
11321
+ if (boundaryIndex < 0)
11322
+ return { written: false };
11323
+ const firstKept = entries[boundaryIndex + 1];
11324
+ if (!firstKept?.id)
11325
+ return { written: false };
11326
+ const compactedPrefixChars = entries.slice(0, boundaryIndex + 1).reduce((total, entry) => total + JSON.stringify(entry.message ?? "").length, 0);
11327
+ const compactionId = shortId();
11328
+ const marker = {
11329
+ type: "compaction",
11330
+ id: compactionId,
11331
+ parentId: boundaryEntryId,
11332
+ timestamp: String(entries[boundaryIndex].timestamp),
11333
+ summary: MIGRATION_COMPACTION_SUMMARY,
11334
+ firstKeptEntryId: firstKept.id,
11335
+ tokensBefore: Math.ceil(compactedPrefixChars / 4),
11336
+ fromHook: true
11337
+ };
11338
+ firstKept.parentId = compactionId;
11339
+ entries.splice(boundaryIndex + 1, 0, marker);
11340
+ return {
11341
+ written: true,
11342
+ boundaryEntryId,
11343
+ firstKeptEntryId: firstKept.id
11344
+ };
11345
+ }
11346
+ function copyMagicContextState(args) {
11347
+ const sourceCompartments = stmt(args.cortexkitDb, `SELECT sequence, start_message, end_message, start_message_id, end_message_id,
11348
+ title, content, created_at
11349
+ FROM compartments
11350
+ WHERE session_id = ? AND harness = 'opencode'
11351
+ ORDER BY sequence ASC`).all(args.sourceSessionId);
11352
+ const sourceFacts = stmt(args.cortexkitDb, `SELECT category, content, created_at, updated_at
11353
+ FROM session_facts
11354
+ WHERE session_id = ? AND harness = 'opencode'
11355
+ ORDER BY category ASC, id ASC`).all(args.sourceSessionId);
11356
+ let boundariesApproximated = 0;
11357
+ const remappedCompartments = [];
11358
+ for (const c of sourceCompartments) {
11359
+ const startRemap = remapBoundaryId(c.start_message_id, args.messageIdToLastPiEntryId, args.orderedSourceMessageIds);
11360
+ const endRemap = remapBoundaryId(c.end_message_id, args.messageIdToLastPiEntryId, args.orderedSourceMessageIds);
11361
+ if (!startRemap || !endRemap)
11362
+ continue;
11363
+ if (!startRemap.exact || !endRemap.exact)
11364
+ boundariesApproximated++;
11365
+ remappedCompartments.push({
11366
+ sequence: c.sequence,
11367
+ start_message: c.start_message,
11368
+ end_message: c.end_message,
11369
+ start_message_id: startRemap.piEntryId,
11370
+ end_message_id: endRemap.piEntryId,
11371
+ title: c.title,
11372
+ content: c.content
11373
+ });
11374
+ }
11375
+ if (args.dryRun) {
11376
+ return {
11377
+ compartmentsCopied: remappedCompartments.length,
11378
+ factsCopied: sourceFacts.length,
11379
+ boundariesApproximated,
11380
+ lastCompartmentEndPiEntryId: remappedCompartments.at(-1)?.end_message_id
11381
+ };
11382
+ }
11383
+ const insertCompartment = stmt(args.cortexkitDb, `INSERT INTO compartments (
11384
+ session_id, sequence, start_message, end_message,
11385
+ start_message_id, end_message_id, title, content,
11386
+ created_at, harness
11387
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pi')`);
11388
+ for (const c of remappedCompartments) {
11389
+ insertCompartment.run(args.piSessionId, c.sequence, c.start_message, c.end_message, c.start_message_id, c.end_message_id, c.title, c.content, args.now);
11390
+ }
11391
+ const insertFact = stmt(args.cortexkitDb, `INSERT INTO session_facts (
11392
+ session_id, category, content, created_at, updated_at, harness
11393
+ ) VALUES (?, ?, ?, ?, ?, 'pi')`);
11394
+ for (const f of sourceFacts) {
11395
+ insertFact.run(args.piSessionId, f.category, f.content, f.created_at, f.updated_at);
11396
+ }
11397
+ return {
11398
+ compartmentsCopied: remappedCompartments.length,
11399
+ factsCopied: sourceFacts.length,
11400
+ boundariesApproximated,
11401
+ lastCompartmentEndPiEntryId: remappedCompartments.at(-1)?.end_message_id
11402
+ };
11403
+ }
11404
+ function ensureValidOptions(opts) {
11405
+ if (!opts.from)
11406
+ throw new Error("Missing required flag: --from <opencode>");
11407
+ if (!opts.to)
11408
+ throw new Error("Missing required flag: --to <pi>");
11409
+ if (opts.from !== "opencode" || opts.to !== "pi") {
11410
+ if (opts.from === "pi" && opts.to === "opencode") {
11411
+ throw new Error("Migration pi → opencode is not yet supported (V1 supports only opencode → pi)");
11412
+ }
11413
+ throw new Error(`Unsupported migration: ${opts.from} → ${opts.to} (V1 supports only opencode → pi)`);
11414
+ }
11415
+ if (!opts.session)
11416
+ throw new Error("Missing required flag: --session <id>");
11417
+ if (opts.maxMessages !== undefined && (!Number.isInteger(opts.maxMessages) || opts.maxMessages <= 0)) {
11418
+ throw new Error("--max-messages must be a positive integer");
11419
+ }
11420
+ }
11421
+ function migrateOpenCodeSessionToPi(opts) {
11422
+ const fs2 = opts.fs ?? defaultFs();
11423
+ const now = opts.now ?? new Date;
11424
+ const opencodeDbPath = opts.opencodeDbPath ?? defaultOpenCodeDbPath();
11425
+ const piSessionsRoot = opts.piSessionsRoot ?? defaultPiSessionsRoot();
11426
+ const ownsDb = !opts.db;
11427
+ const db = opts.db ?? new Database(opencodeDbPath, { readonly: true });
11428
+ let cortexkitDb;
11429
+ let ownsCortexkitDb = false;
11430
+ if (opts.cortexkitDb === null) {
11431
+ cortexkitDb = null;
11432
+ } else if (opts.cortexkitDb !== undefined) {
11433
+ cortexkitDb = opts.cortexkitDb;
11434
+ } else {
11435
+ try {
11436
+ cortexkitDb = new Database(defaultCortexkitDbPath());
11437
+ ownsCortexkitDb = true;
11438
+ } catch {
11439
+ cortexkitDb = null;
11440
+ }
11441
+ }
11442
+ try {
11443
+ const { session, sourceMessageCount, messages, parts } = fetchRows(db, opts.sessionId, opts.maxMessages);
11444
+ const model = extractModel(messages);
11445
+ const provider = opts.provider ?? model.provider;
11446
+ const modelId = opts.modelId ?? model.modelId;
11447
+ const cwd = session.directory ?? session.path ?? process.cwd();
11448
+ const outputDir = join11(piSessionsRoot, projectPathToPiDirSlug(cwd));
11449
+ const buildResult = buildPiEntries({
11450
+ session,
11451
+ messages,
11452
+ parts,
11453
+ now,
11454
+ provider,
11455
+ modelId
11456
+ });
11457
+ let copyResult = {
11458
+ compartmentsCopied: 0,
11459
+ factsCopied: 0,
11460
+ boundariesApproximated: 0
11461
+ };
11462
+ if (cortexkitDb !== null) {
11463
+ copyResult = copyMagicContextState({
11464
+ cortexkitDb,
11465
+ sourceSessionId: session.id,
11466
+ piSessionId: buildResult.piSessionId,
11467
+ messageIdToLastPiEntryId: buildResult.messageIdToLastPiEntryId,
11468
+ orderedSourceMessageIds: buildResult.orderedSourceMessageIds,
11469
+ now: now.getTime(),
11470
+ dryRun: Boolean(opts.dryRun)
11471
+ });
11472
+ }
11473
+ const compactionMarker = insertCompactionMarker(buildResult.entries, copyResult.lastCompartmentEndPiEntryId);
11474
+ const outputPath = join11(outputDir, `${formatPiFilenameTimestamp(now)}_${buildResult.piSessionId}.jsonl`);
11475
+ const jsonl = `${buildResult.entries.map((entry) => JSON.stringify(entry)).join(`
11476
+ `)}
11477
+ `;
11478
+ if (!opts.dryRun) {
11479
+ if (!fs2.existsSync(outputDir))
11480
+ fs2.mkdirSync(outputDir, { recursive: true });
11481
+ fs2.writeFileSync(outputPath, jsonl);
11482
+ }
11483
+ return {
11484
+ outputPath,
11485
+ piSessionId: buildResult.piSessionId,
11486
+ messageCount: buildResult.entries.length - 2,
11487
+ byteCount: Buffer.byteLength(jsonl, "utf8"),
11488
+ sourceMessageCount,
11489
+ compartmentsCopied: copyResult.compartmentsCopied,
11490
+ factsCopied: copyResult.factsCopied,
11491
+ boundariesApproximated: copyResult.boundariesApproximated,
11492
+ compactionMarkerWritten: compactionMarker.written,
11493
+ compactionBoundaryEntryId: compactionMarker.boundaryEntryId,
11494
+ compactionFirstKeptEntryId: compactionMarker.firstKeptEntryId,
11495
+ dryRun: Boolean(opts.dryRun)
11496
+ };
11497
+ } finally {
11498
+ if (ownsDb)
11499
+ db.close();
11500
+ if (ownsCortexkitDb && cortexkitDb !== null)
11501
+ cortexkitDb.close();
11502
+ }
11503
+ }
11504
+ function parseMigrateArgs(args) {
11505
+ const opts = {};
11506
+ for (let i = 0;i < args.length; i++) {
11507
+ const arg = args[i];
11508
+ const readValue = (flag) => {
11509
+ const value = args[++i];
11510
+ if (!value || value.startsWith("--"))
11511
+ throw new Error(`Missing value for ${flag}`);
11512
+ return value;
11513
+ };
11514
+ if (arg === "--from")
11515
+ opts.from = readValue(arg);
11516
+ else if (arg === "--to")
11517
+ opts.to = readValue(arg);
11518
+ else if (arg === "--session")
11519
+ opts.session = readValue(arg);
11520
+ else if (arg === "--max-messages")
11521
+ opts.maxMessages = Number(readValue(arg));
11522
+ else if (arg === "--dry-run")
11523
+ opts.dryRun = true;
11524
+ else if (arg === "--help" || arg === "-h")
11525
+ throw new Error("HELP");
11526
+ else
11527
+ throw new Error(`Unknown migrate flag: ${arg}`);
11528
+ }
11529
+ return opts;
11530
+ }
11531
+ function printMigrateHelp() {
11532
+ console.log(`
11533
+ Magic Context doctor migrate
11534
+ ─────────────────────────────
11535
+
11536
+ Copy OpenCode session message content into a new Pi JSONL session,
11537
+ PLUS the source session's Magic Context state (compartments + facts)
11538
+ into the shared cortexkit database under the new Pi session id.
11539
+
11540
+ Supported pairs (V1):
11541
+ --from opencode --to pi
11542
+
11543
+ Usage:
11544
+ bunx --bun @cortexkit/opencode-magic-context@latest doctor migrate \\
11545
+ --from opencode --to pi --session ses_xxx [--max-messages N] [--dry-run]
11546
+
11547
+ Fidelity:
11548
+ - text, reasoning text, tool calls, and tool results are preserved
11549
+ - assistant 'usage' fields carry real input/output/cache token counts
11550
+ from the source so Pi's getContextUsage() reports realistic numbers
11551
+ - reasoning signatures are stripped; step-start/step-finish are skipped
11552
+ - file bytes are replaced with <file omitted: name> markers
11553
+ - compartments + session_facts are copied to the new Pi session_id;
11554
+ compartment boundary message IDs are remapped to the corresponding
11555
+ Pi entry IDs (nearest-at-or-before for boundaries that don't have
11556
+ a direct message-level Pi entry)
11557
+ `);
11558
+ }
11559
+ async function runMigrateCli(args) {
11560
+ try {
11561
+ const parsed = parseMigrateArgs(args);
11562
+ ensureValidOptions(parsed);
11563
+ const result = migrateOpenCodeSessionToPi({
11564
+ sessionId: parsed.session,
11565
+ maxMessages: parsed.maxMessages,
11566
+ dryRun: parsed.dryRun
11567
+ });
11568
+ const action = result.dryRun ? "Would write" : "Wrote";
11569
+ console.log(`${action} Pi session JSONL:`);
11570
+ console.log(` path: ${result.outputPath}`);
11571
+ console.log(` pi session id: ${result.piSessionId}`);
11572
+ console.log(` source messages: ${result.sourceMessageCount}`);
11573
+ console.log(` migrated entries: ${result.messageCount}`);
11574
+ console.log(` bytes: ${result.byteCount}`);
11575
+ console.log(` compartments copied: ${result.compartmentsCopied}`);
11576
+ console.log(` session facts copied: ${result.factsCopied}`);
11577
+ console.log(` compaction marker: ${result.compactionMarkerWritten ? "yes" : "no"}${result.compactionMarkerWritten ? ` (boundary: ${result.compactionBoundaryEntryId}, first kept: ${result.compactionFirstKeptEntryId})` : ""}`);
11578
+ if (result.boundariesApproximated > 0) {
11579
+ console.log(` boundaries approximated: ${result.boundariesApproximated} (nearest-at-or-before)`);
11580
+ }
11581
+ if (!result.dryRun) {
11582
+ console.log("Pi may need to be restarted to pick up the new session file.");
11583
+ }
11584
+ return 0;
11585
+ } catch (error) {
11586
+ if (error instanceof Error && error.message === "HELP") {
11587
+ printMigrateHelp();
11588
+ return 0;
11589
+ }
11590
+ console.error(error instanceof Error ? error.message : String(error));
11591
+ console.error("Run `doctor migrate --help` for usage.");
11592
+ return 1;
11593
+ }
11594
+ }
11595
+
10968
11596
  // src/cli/setup.ts
10969
11597
  var import_comment_json4 = __toESM(require_src2(), 1);
10970
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
11598
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
10971
11599
  import { dirname as dirname4 } from "node:path";
10972
11600
  var PLUGIN_NAME4 = "@cortexkit/opencode-magic-context";
10973
11601
  var PLUGIN_ENTRY2 = "@cortexkit/opencode-magic-context@latest";
10974
11602
  function ensureDir(dir) {
10975
- if (!existsSync8(dir)) {
10976
- mkdirSync3(dir, { recursive: true });
11603
+ if (!existsSync9(dir)) {
11604
+ mkdirSync4(dir, { recursive: true });
10977
11605
  }
10978
11606
  }
10979
11607
  function readJsonc(path3) {
@@ -10992,7 +11620,7 @@ function addPluginToOpenCodeConfig(configPath, format) {
10992
11620
  plugin: [PLUGIN_ENTRY2],
10993
11621
  compaction: { auto: false, prune: false }
10994
11622
  };
10995
- writeFileSync5(configPath, `${import_comment_json4.stringify(config, null, 2)}
11623
+ writeFileSync6(configPath, `${import_comment_json4.stringify(config, null, 2)}
10996
11624
  `);
10997
11625
  return;
10998
11626
  }
@@ -11011,13 +11639,13 @@ function addPluginToOpenCodeConfig(configPath, format) {
11011
11639
  compaction.auto = false;
11012
11640
  compaction.prune = false;
11013
11641
  existing.compaction = compaction;
11014
- writeFileSync5(configPath, `${import_comment_json4.stringify(existing, null, 2)}
11642
+ writeFileSync6(configPath, `${import_comment_json4.stringify(existing, null, 2)}
11015
11643
  `);
11016
11644
  }
11017
11645
  function addPluginToTuiConfig(configPath, format) {
11018
11646
  ensureDir(dirname4(configPath));
11019
11647
  if (format === "none") {
11020
- writeFileSync5(configPath, `${import_comment_json4.stringify({ plugin: [PLUGIN_ENTRY2] }, null, 2)}
11648
+ writeFileSync6(configPath, `${import_comment_json4.stringify({ plugin: [PLUGIN_ENTRY2] }, null, 2)}
11021
11649
  `);
11022
11650
  return;
11023
11651
  }
@@ -11032,13 +11660,13 @@ function addPluginToTuiConfig(configPath, format) {
11032
11660
  plugins.push(PLUGIN_ENTRY2);
11033
11661
  }
11034
11662
  existing.plugin = plugins;
11035
- writeFileSync5(configPath, `${import_comment_json4.stringify(existing, null, 2)}
11663
+ writeFileSync6(configPath, `${import_comment_json4.stringify(existing, null, 2)}
11036
11664
  `);
11037
11665
  }
11038
11666
  function writeMagicContextConfig(configPath, options) {
11039
- const config = (existsSync8(configPath) ? readJsonc(configPath) : null) ?? {};
11667
+ const config = (existsSync9(configPath) ? readJsonc(configPath) : null) ?? {};
11040
11668
  if (!config.$schema) {
11041
- config.$schema = "https://raw.githubusercontent.com/cortexkit/opencode-magic-context/master/assets/magic-context.schema.json";
11669
+ config.$schema = "https://raw.githubusercontent.com/cortexkit/magic-context/master/assets/magic-context.schema.json";
11042
11670
  }
11043
11671
  if (options.historianModel) {
11044
11672
  const historian = config.historian ?? {};
@@ -11073,7 +11701,7 @@ function writeMagicContextConfig(configPath, options) {
11073
11701
  cacheTtl["anthropic/claude-opus-4-6"] = "59m";
11074
11702
  config.cache_ttl = cacheTtl;
11075
11703
  }
11076
- writeFileSync5(configPath, `${import_comment_json4.stringify(config, null, 2)}
11704
+ writeFileSync6(configPath, `${import_comment_json4.stringify(config, null, 2)}
11077
11705
  `);
11078
11706
  }
11079
11707
  async function runSetup() {
@@ -11102,7 +11730,7 @@ async function runSetup() {
11102
11730
  R2.warn("You can configure models manually in magic-context.jsonc later");
11103
11731
  }
11104
11732
  const paths = detectConfigPaths();
11105
- const hadExistingSetup = paths.opencodeConfigFormat !== "none" || existsSync8(paths.magicContextConfig) || paths.tuiConfigFormat !== "none";
11733
+ const hadExistingSetup = paths.opencodeConfigFormat !== "none" || existsSync9(paths.magicContextConfig) || paths.tuiConfigFormat !== "none";
11106
11734
  addPluginToOpenCodeConfig(paths.opencodeConfig, paths.opencodeConfigFormat);
11107
11735
  R2.success(`Plugin added to ${paths.opencodeConfig}`);
11108
11736
  R2.info("Disabled built-in compaction (auto=false, prune=false)");
@@ -11120,7 +11748,7 @@ async function runSetup() {
11120
11748
  if (shouldRemove) {
11121
11749
  plugins.splice(dcpIndex, 1);
11122
11750
  ocConfig.plugin = plugins;
11123
- writeFileSync5(paths.opencodeConfig, `${import_comment_json4.stringify(ocConfig, null, 2)}
11751
+ writeFileSync6(paths.opencodeConfig, `${import_comment_json4.stringify(ocConfig, null, 2)}
11124
11752
  `);
11125
11753
  R2.success("Removed opencode-dcp from plugin list");
11126
11754
  } else {
@@ -11248,11 +11876,14 @@ async function runSetup() {
11248
11876
  if (shouldStar) {
11249
11877
  try {
11250
11878
  const { execSync: execSync3 } = await import("node:child_process");
11251
- execSync3("gh api --silent --method PUT /user/starred/cortexkit/opencode-magic-context", { stdio: "ignore", timeout: 1e4 });
11879
+ execSync3("gh api --silent --method PUT /user/starred/cortexkit/magic-context", {
11880
+ stdio: "ignore",
11881
+ timeout: 1e4
11882
+ });
11252
11883
  R2.success("Thanks for starring! ★");
11253
11884
  } catch {
11254
11885
  R2.info(`Couldn't star automatically. You can star manually:
11255
- https://github.com/cortexkit/opencode-magic-context`);
11886
+ https://github.com/cortexkit/magic-context`);
11256
11887
  }
11257
11888
  }
11258
11889
  Gt("Run 'opencode' to start!");
@@ -11264,9 +11895,13 @@ var command = process.argv[2];
11264
11895
  if (command === "setup") {
11265
11896
  runSetup().then((code) => process.exit(code));
11266
11897
  } else if (command === "doctor") {
11267
- const force = process.argv.includes("--force");
11268
- const issue = process.argv.includes("--issue");
11269
- runDoctor({ force, issue }).then((code) => process.exit(code));
11898
+ if (process.argv[3] === "migrate") {
11899
+ runMigrateCli(process.argv.slice(4)).then((code) => process.exit(code));
11900
+ } else {
11901
+ const force = process.argv.includes("--force");
11902
+ const issue = process.argv.includes("--issue");
11903
+ runDoctor({ force, issue }).then((code) => process.exit(code));
11904
+ }
11270
11905
  } else {
11271
11906
  console.log("");
11272
11907
  console.log(" Magic Context CLI");
@@ -11277,11 +11912,13 @@ if (command === "setup") {
11277
11912
  console.log(" doctor Check and fix configuration issues");
11278
11913
  console.log(" doctor --force Force clear plugin cache (fixes broken dependencies)");
11279
11914
  console.log(" doctor --issue Collect diagnostics and open a GitHub issue");
11915
+ console.log(" doctor migrate Migrate OpenCode session content to Pi JSONL");
11280
11916
  console.log("");
11281
11917
  console.log(" Usage:");
11282
11918
  console.log(" bunx --bun @cortexkit/opencode-magic-context@latest setup");
11283
11919
  console.log(" bunx --bun @cortexkit/opencode-magic-context@latest doctor");
11284
11920
  console.log(" bunx --bun @cortexkit/opencode-magic-context@latest doctor --issue");
11921
+ console.log(" bunx --bun @cortexkit/opencode-magic-context@latest doctor migrate --from opencode --to pi --session ses_xxx --dry-run");
11285
11922
  console.log("");
11286
11923
  process.exit(command ? 1 : 0);
11287
11924
  }