@cortexkit/magic-context 0.23.1 → 0.24.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 (2) hide show
  1. package/dist/index.js +736 -414
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8265,9 +8265,6 @@ import { execFileSync, spawnSync } from "node:child_process";
8265
8265
  import { existsSync as existsSync4 } from "node:fs";
8266
8266
  import { homedir as homedir4 } from "node:os";
8267
8267
  import { join as join5 } from "node:path";
8268
- function getStaticModels() {
8269
- return [...STATIC_MODELS];
8270
- }
8271
8268
  function detectPiBinary() {
8272
8269
  const fromPath = findOnPath("pi");
8273
8270
  if (fromPath)
@@ -8314,17 +8311,26 @@ function parseModelListOutput(output) {
8314
8311
  const line = rawLine.trim().replace(/^[•*-]\s*/, "");
8315
8312
  if (!line || line.toLowerCase().includes("usage:"))
8316
8313
  continue;
8317
- const token = line.split(/\s+/)[0]?.trim().replace(/,$/, "");
8318
- if (!token?.includes("/"))
8314
+ const cols = line.split(/\s+/);
8315
+ const first = cols[0]?.replace(/,$/, "") ?? "";
8316
+ if (first.includes("/")) {
8317
+ if (!/^https?:\/\//.test(first))
8318
+ models.add(first);
8319
8319
  continue;
8320
- if (/^https?:\/\//.test(token))
8320
+ }
8321
+ const provider = first;
8322
+ const model = cols[1]?.replace(/,$/, "") ?? "";
8323
+ if (provider.toLowerCase() === "provider" && model.toLowerCase() === "model") {
8321
8324
  continue;
8322
- models.add(token);
8325
+ }
8326
+ if (PROVIDER_TOKEN.test(provider) && MODEL_TOKEN.test(model)) {
8327
+ models.add(`${provider}/${model}`);
8328
+ }
8323
8329
  }
8324
8330
  return [...models];
8325
8331
  }
8326
8332
  function getAvailableModels(piPath) {
8327
- const outputs = [runPi(piPath, ["models", "list"]), runPi(piPath, ["--list-models"])];
8333
+ const outputs = [runPi(piPath, ["--list-models"]), runPi(piPath, ["models", "list"])];
8328
8334
  for (const output of outputs) {
8329
8335
  if (!output)
8330
8336
  continue;
@@ -8332,95 +8338,13 @@ function getAvailableModels(piPath) {
8332
8338
  if (models.length > 0)
8333
8339
  return models;
8334
8340
  }
8335
- return getStaticModels();
8341
+ return [];
8336
8342
  }
8337
- function buildModelSelection(allModels, role) {
8338
- const result = [];
8339
- const added = new Set;
8340
- const addIfAvailable = (pattern, hint) => {
8341
- const matches = allModels.filter((m) => m === pattern || m.endsWith(`/${pattern}`));
8342
- for (const model of matches) {
8343
- if (added.has(model))
8344
- continue;
8345
- added.add(model);
8346
- result.push({
8347
- label: hint ? `${model} — ${hint}` : model,
8348
- value: model,
8349
- recommended: result.length === 0
8350
- });
8351
- }
8352
- };
8353
- if (role === "historian") {
8354
- addIfAvailable("anthropic/claude-haiku-4-5", "fast/cheap default");
8355
- addIfAvailable("github-copilot/claude-sonnet-4.6", "per-request billing");
8356
- addIfAvailable("anthropic/claude-sonnet-4-6");
8357
- addIfAvailable("github-copilot/gpt-5.4", "per-request billing");
8358
- addIfAvailable("openai/gpt-5.4");
8359
- addIfAvailable("opencode-go/minimax-m2.7");
8360
- addIfAvailable("opencode-go/glm-5");
8361
- } else if (role === "dreamer") {
8362
- for (const model of allModels.filter((m) => m.startsWith("ollama/"))) {
8363
- if (added.has(model))
8364
- continue;
8365
- added.add(model);
8366
- result.push({
8367
- label: `${model} — local`,
8368
- value: model,
8369
- recommended: result.length === 0
8370
- });
8371
- }
8372
- addIfAvailable("anthropic/claude-sonnet-4-6", "recommended quality default");
8373
- addIfAvailable("github-copilot/claude-sonnet-4.6", "per-request billing");
8374
- addIfAvailable("github-copilot/gemini-3-flash-preview", "fast/cheap");
8375
- addIfAvailable("opencode-go/glm-5");
8376
- addIfAvailable("opencode-go/minimax-m2.7");
8377
- } else {
8378
- for (const model of allModels.filter((m) => m.startsWith("cerebras/"))) {
8379
- if (added.has(model))
8380
- continue;
8381
- added.add(model);
8382
- result.push({
8383
- label: `${model} — fast`,
8384
- value: model,
8385
- recommended: result.length === 0
8386
- });
8387
- }
8388
- addIfAvailable("github-copilot/gemini-3-flash-preview", "fast");
8389
- addIfAvailable("github-copilot/gpt-5-mini", "fast");
8390
- addIfAvailable("openai/gpt-5.4-mini", "fast");
8391
- addIfAvailable("anthropic/claude-haiku-4-5", "fast");
8392
- }
8393
- for (const model of allModels) {
8394
- if (result.length >= 30)
8395
- break;
8396
- if (added.has(model))
8397
- continue;
8398
- added.add(model);
8399
- result.push({
8400
- label: model,
8401
- value: model,
8402
- recommended: result.length === 0
8403
- });
8404
- }
8405
- return result;
8406
- }
8407
- var PI_PACKAGE_SOURCE = "npm:@cortexkit/pi-magic-context", STATIC_MODELS;
8343
+ var PI_PACKAGE_SOURCE = "npm:@cortexkit/pi-magic-context", PROVIDER_TOKEN, MODEL_TOKEN;
8408
8344
  var init_pi_helpers = __esm(() => {
8409
8345
  init_find_on_path();
8410
- STATIC_MODELS = [
8411
- "anthropic/claude-haiku-4-5",
8412
- "anthropic/claude-sonnet-4-6",
8413
- "github-copilot/claude-sonnet-4.6",
8414
- "github-copilot/gpt-5.4",
8415
- "github-copilot/gpt-5-mini",
8416
- "github-copilot/gemini-3-flash-preview",
8417
- "openai/gpt-5.4",
8418
- "openai/gpt-5.4-mini",
8419
- "opencode-go/glm-5",
8420
- "opencode-go/minimax-m2.7",
8421
- "ollama/qwen2.5-coder:7b",
8422
- "cerebras/llama3.1-8b"
8423
- ];
8346
+ PROVIDER_TOKEN = /^[a-z0-9][a-z0-9._-]*$/i;
8347
+ MODEL_TOKEN = /^[a-z0-9][a-z0-9._:-]*$/i;
8424
8348
  });
8425
8349
 
8426
8350
  // src/adapters/pi.ts
@@ -9741,6 +9665,21 @@ import j2 from "node:process";
9741
9665
  function te() {
9742
9666
  return j2.platform !== "win32" ? j2.env.TERM !== "linux" : !!j2.env.CI || !!j2.env.WT_SESSION || !!j2.env.TERMINUS_SUBLIME || j2.env.ConEmuTask === "{cmd::Cmder}" || j2.env.TERM_PROGRAM === "Terminus-Sublime" || j2.env.TERM_PROGRAM === "vscode" || j2.env.TERM === "xterm-256color" || j2.env.TERM === "alacritty" || j2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
9743
9667
  }
9668
+ function Mt(t) {
9669
+ return t.label ?? String(t.value ?? "");
9670
+ }
9671
+ function Rt(t, r) {
9672
+ if (!t)
9673
+ return true;
9674
+ const s = (r.label ?? String(r.value ?? "")).toLowerCase(), i = (r.hint ?? "").toLowerCase(), u = String(r.value).toLowerCase(), n = t.toLowerCase();
9675
+ return s.includes(n) || i.includes(n) || u.includes(n);
9676
+ }
9677
+ function se(t, r) {
9678
+ const s = [];
9679
+ for (const i of r)
9680
+ t.includes(i.value) && s.push(i);
9681
+ return s;
9682
+ }
9744
9683
  var import_sisteransi2, tt2, at2 = () => process.env.CI === "true", w2 = (t, r) => tt2 ? t : r, _t, ot2, ut2, F2, lt, $2, E2, It, Et, z2, H2, et2, U2, J2, Gt, st2, ct, xt, $t, dt, Ot, ht2, pt, mt, gt, M = (t) => {
9745
9684
  switch (t) {
9746
9685
  case "initial":
@@ -9798,7 +9737,47 @@ var import_sisteransi2, tt2, at2 = () => process.env.CI === "true", w2 = (t, r)
9798
9737
  for (const G2 of b2)
9799
9738
  C2.push(G2);
9800
9739
  return g && C2.push(l), C2;
9801
- }, ue = (t) => {
9740
+ }, At = (t) => new Q({ options: t.options, initialValue: t.initialValue ? [t.initialValue] : undefined, initialUserInput: t.initialUserInput, placeholder: t.placeholder, filter: t.filter ?? ((r, s) => Rt(r, s)), signal: t.signal, input: t.input, output: t.output, validate: t.validate, render() {
9741
+ const r = t.withGuide ?? h.withGuide, s = r ? [`${e("gray", $2)}`, `${M(this.state)} ${t.message}`] : [`${M(this.state)} ${t.message}`], i = this.userInput, u = this.options, n = t.placeholder, o = i === "" && n !== undefined, c2 = (a, l) => {
9742
+ const d2 = Mt(a), y = a.hint && a.value === this.focusedValue ? e("dim", ` (${a.hint})`) : "";
9743
+ switch (l) {
9744
+ case "active":
9745
+ return `${e("green", z2)} ${d2}${y}`;
9746
+ case "inactive":
9747
+ return `${e("dim", H2)} ${e("dim", d2)}`;
9748
+ case "disabled":
9749
+ return `${e("gray", H2)} ${e(["strikethrough", "gray"], d2)}`;
9750
+ }
9751
+ };
9752
+ switch (this.state) {
9753
+ case "submit": {
9754
+ const a = se(this.selectedValues, u), l = a.length > 0 ? ` ${e("dim", a.map(Mt).join(", "))}` : "", d2 = r ? e("gray", $2) : "";
9755
+ return `${s.join(`
9756
+ `)}
9757
+ ${d2}${l}`;
9758
+ }
9759
+ case "cancel": {
9760
+ const a = i ? ` ${e(["strikethrough", "dim"], i)}` : "", l = r ? e("gray", $2) : "";
9761
+ return `${s.join(`
9762
+ `)}
9763
+ ${l}${a}`;
9764
+ }
9765
+ default: {
9766
+ const a = this.state === "error" ? "yellow" : "cyan", l = r ? `${e(a, $2)} ` : "", d2 = r ? e(a, E2) : "";
9767
+ let y = "";
9768
+ if (this.isNavigating || o) {
9769
+ const v2 = o ? n : i;
9770
+ y = v2 !== "" ? ` ${e("dim", v2)}` : "";
9771
+ } else
9772
+ y = ` ${this.userInputWithCursor}`;
9773
+ const p2 = this.filteredOptions.length !== u.length ? e("dim", ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? "" : "es"})`) : "", m = this.filteredOptions.length === 0 && i ? [`${l}${e("yellow", "No matches found")}`] : [], g = this.state === "error" ? [`${l}${e("yellow", this.error)}`] : [];
9774
+ r && s.push(`${l.trimEnd()}`), s.push(`${l}${e("dim", "Search:")}${y}${p2}`, ...m, ...g);
9775
+ const S2 = [`${e("dim", "↑/↓")} to select`, `${e("dim", "Enter:")} confirm`, `${e("dim", "Type:")} to search`], h2 = [`${l}${S2.join(" • ")}`, d2], f = this.filteredOptions.length === 0 ? [] : Y2({ cursor: this.cursor, options: this.filteredOptions, columnPadding: r ? 3 : 0, rowPadding: s.length + h2.length, style: (v2, T) => c2(v2, v2.disabled ? "disabled" : T ? "active" : "inactive"), maxItems: t.maxItems, output: t.output });
9776
+ return [...s, ...f.map((v2) => `${l}${v2}`), ...h2].join(`
9777
+ `);
9778
+ }
9779
+ }
9780
+ } }).prompt(), ue = (t) => {
9802
9781
  const r = t.active ?? "Yes", s = t.inactive ?? "No";
9803
9782
  return new X({ active: r, inactive: s, signal: t.signal, input: t.input, output: t.output, initialValue: t.initialValue ?? true, render() {
9804
9783
  const i = t.withGuide ?? h.withGuide, u = `${M(this.state)} `, n = i ? `${e("gray", $2)} ` : "", o = W(t.output, t.message, n, u), c2 = `${i ? `${e("gray", $2)}
@@ -10108,6 +10087,7 @@ __export(exports_prompts, {
10108
10087
  spinner: () => spinner,
10109
10088
  selectOne: () => selectOne,
10110
10089
  selectMany: () => selectMany,
10090
+ selectAutocomplete: () => selectAutocomplete,
10111
10091
  promptIO: () => promptIO,
10112
10092
  outro: () => outro,
10113
10093
  note: () => note,
@@ -10187,6 +10167,16 @@ async function selectMany(message, options, initial) {
10187
10167
  handleCancel(result);
10188
10168
  return result;
10189
10169
  }
10170
+ async function selectAutocomplete(message, options, opts = {}) {
10171
+ const result = await At({
10172
+ message,
10173
+ options: options.map(toClackOption),
10174
+ placeholder: opts.placeholder,
10175
+ maxItems: opts.maxItems ?? 10
10176
+ });
10177
+ handleCancel(result);
10178
+ return result;
10179
+ }
10190
10180
  var log, promptIO;
10191
10181
  var init_prompts = __esm(() => {
10192
10182
  init_dist4();
@@ -10219,7 +10209,8 @@ var init_prompts = __esm(() => {
10219
10209
  confirm,
10220
10210
  text,
10221
10211
  selectOne,
10222
- selectMany
10212
+ selectMany,
10213
+ selectAutocomplete
10223
10214
  };
10224
10215
  });
10225
10216
 
@@ -10785,6 +10776,58 @@ var init_conflict_fixer = __esm(() => {
10785
10776
  ];
10786
10777
  });
10787
10778
 
10779
+ // src/lib/model-picker.ts
10780
+ function sortModelsForPicker(models) {
10781
+ return [...new Set(models)].sort((a, b2) => a.localeCompare(b2));
10782
+ }
10783
+ function modelOptions(models) {
10784
+ return sortModelsForPicker(models).map((model) => ({ label: model, value: model }));
10785
+ }
10786
+ async function pickModel(prompts, allModels, role) {
10787
+ const copy = ROLE_COPY[role];
10788
+ prompts.note(copy.blurb, copy.title);
10789
+ const options = modelOptions(allModels);
10790
+ if (options.length === 0) {
10791
+ return (await prompts.text(`${copy.pickMessage} (type a provider/model id)`, {
10792
+ placeholder: "e.g. anthropic/claude-haiku-4-5",
10793
+ validate: (value) => value.trim().length === 0 ? "A model id is required" : undefined
10794
+ })).trim();
10795
+ }
10796
+ return prompts.selectAutocomplete(copy.pickMessage, options, {
10797
+ placeholder: copy.placeholder
10798
+ });
10799
+ }
10800
+ var ROLE_COPY;
10801
+ var init_model_picker = __esm(() => {
10802
+ ROLE_COPY = {
10803
+ historian: {
10804
+ title: "Historian",
10805
+ blurb: `The historian runs in the background and condenses older conversation into
10806
+ ` + `compact summaries, so your context never overflows. It works on one bounded
10807
+ ` + `chunk at a time and runs often — it does NOT need a frontier model. A smaller,
10808
+ ` + `cheaper, faster model (a mini / flash / haiku tier) works well here and keeps
10809
+ ` + "your costs down.",
10810
+ pickMessage: "Select a model for the historian",
10811
+ placeholder: "type to filter (e.g. haiku, flash, mini)…"
10812
+ },
10813
+ dreamer: {
10814
+ title: "Dreamer",
10815
+ blurb: `The dreamer runs periodically (typically overnight) to consolidate and maintain
10816
+ ` + `your project memories. It is not on the hot path and does NOT need a frontier
10817
+ ` + "model — a cheaper or local model is a good fit here.",
10818
+ pickMessage: "Select a model for the dreamer",
10819
+ placeholder: "type to filter (e.g. flash, local, glm)…"
10820
+ },
10821
+ sidekick: {
10822
+ title: "Sidekick",
10823
+ blurb: `The sidekick augments your prompt with relevant project context when you run
10824
+ ` + "/ctx-aug. Fast models are preferred here.",
10825
+ pickMessage: "Select a model for the sidekick",
10826
+ placeholder: "type to filter…"
10827
+ }
10828
+ };
10829
+ });
10830
+
10788
10831
  // src/lib/opencode-helpers.ts
10789
10832
  import { execSync } from "node:child_process";
10790
10833
  function isOpenCodeInstalled() {
@@ -10811,55 +10854,6 @@ function getAvailableModels2() {
10811
10854
  return [];
10812
10855
  }
10813
10856
  }
10814
- function buildModelSelection2(allModels, role) {
10815
- const result = [];
10816
- const added = new Set;
10817
- const addIfAvailable = (pattern, hint) => {
10818
- const matches = allModels.filter((m) => m === pattern || m.endsWith(`/${pattern}`));
10819
- for (const m of matches) {
10820
- if (!added.has(m)) {
10821
- added.add(m);
10822
- result.push({
10823
- label: hint ? `${m} — ${hint}` : m,
10824
- value: m,
10825
- recommended: result.length === 0
10826
- });
10827
- }
10828
- }
10829
- };
10830
- if (role === "historian") {
10831
- addIfAvailable("github-copilot/claude-sonnet-4.6", "per-request billing");
10832
- addIfAvailable("anthropic/claude-sonnet-4-6");
10833
- addIfAvailable("github-copilot/gpt-5.4", "per-request billing");
10834
- addIfAvailable("openai/gpt-5.4");
10835
- addIfAvailable("github-copilot/gemini-3.1-pro-preview", "per-request billing");
10836
- addIfAvailable("opencode-go/minimax-m2.7");
10837
- addIfAvailable("opencode-go/glm-5");
10838
- } else if (role === "dreamer") {
10839
- for (const m of allModels.filter((m2) => m2.startsWith("ollama/"))) {
10840
- if (!added.has(m)) {
10841
- added.add(m);
10842
- result.push({ label: `${m} — local`, value: m, recommended: result.length === 0 });
10843
- }
10844
- }
10845
- addIfAvailable("github-copilot/claude-sonnet-4.6", "per-request billing");
10846
- addIfAvailable("anthropic/claude-sonnet-4-6");
10847
- addIfAvailable("github-copilot/gemini-3-flash-preview", "per-request billing");
10848
- addIfAvailable("opencode-go/glm-5");
10849
- addIfAvailable("opencode-go/minimax-m2.7");
10850
- } else if (role === "sidekick") {
10851
- for (const m of allModels.filter((m2) => m2.startsWith("cerebras/"))) {
10852
- if (!added.has(m)) {
10853
- added.add(m);
10854
- result.push({ label: m, value: m, recommended: result.length === 0 });
10855
- }
10856
- }
10857
- addIfAvailable("opencode/gpt-5-nano");
10858
- addIfAvailable("github-copilot/gemini-3-flash-preview");
10859
- addIfAvailable("github-copilot/gpt-5-mini");
10860
- }
10861
- return result;
10862
- }
10863
10857
  var init_opencode_helpers = () => {};
10864
10858
 
10865
10859
  // src/commands/setup-opencode.ts
@@ -11008,8 +11002,11 @@ function writeMagicContextConfig(configPath, options) {
11008
11002
  writeFileSync4(configPath, `${import_comment_json4.stringify(config, null, 2)}
11009
11003
  `);
11010
11004
  }
11011
- async function runSetup() {
11005
+ async function runSetup(dryRun = false) {
11012
11006
  intro("Magic Context — Setup");
11007
+ if (dryRun) {
11008
+ log.warn("Dry run — no files will be written and no config will be changed.");
11009
+ }
11013
11010
  const s = spinner();
11014
11011
  s.start("Checking OpenCode installation");
11015
11012
  const installed = isOpenCodeInstalled();
@@ -11035,11 +11032,17 @@ async function runSetup() {
11035
11032
  }
11036
11033
  const paths = detectConfigPaths();
11037
11034
  const hadExistingSetup = paths.opencodeConfigFormat !== "none" || existsSync8(paths.magicContextConfig) || paths.tuiConfigFormat !== "none";
11038
- await resolveDcpConflictBeforeSetup(paths.opencodeConfig, paths.opencodeConfigFormat);
11039
- addPluginToOpenCodeConfig(paths.opencodeConfig, paths.opencodeConfigFormat);
11040
- log.success(`Plugin added to ${paths.opencodeConfig}`);
11041
- log.info("Disabled built-in compaction (auto=false, prune=false)");
11042
- log.message("Magic Context handles context management — built-in compaction would interfere");
11035
+ if (!dryRun) {
11036
+ await resolveDcpConflictBeforeSetup(paths.opencodeConfig, paths.opencodeConfigFormat);
11037
+ }
11038
+ if (dryRun) {
11039
+ log.message(`[dry-run] would add the plugin to ${paths.opencodeConfig} and disable compaction`);
11040
+ } else {
11041
+ addPluginToOpenCodeConfig(paths.opencodeConfig, paths.opencodeConfigFormat);
11042
+ log.success(`Plugin added to ${paths.opencodeConfig}`);
11043
+ log.info("Disabled built-in compaction (auto=false, prune=false)");
11044
+ log.message("Magic Context handles context management — built-in compaction would interfere");
11045
+ }
11043
11046
  if (hadExistingSetup) {
11044
11047
  const conflicts = detectConflicts(process.cwd());
11045
11048
  if (conflicts.hasConflict) {
@@ -11047,60 +11050,38 @@ async function runSetup() {
11047
11050
  for (const reason of conflicts.reasons) {
11048
11051
  log.message(` • ${reason}`);
11049
11052
  }
11050
- const shouldFixConflicts = await confirm("Apply automatic conflict fixes to your OpenCode and OMO config files?", true);
11051
- if (shouldFixConflicts) {
11052
- const actions = fixConflicts(process.cwd(), conflicts.conflicts);
11053
- if (actions.length > 0) {
11054
- for (const action of actions) {
11055
- log.success(action);
11053
+ if (dryRun) {
11054
+ log.message("[dry-run] would offer to apply automatic conflict fixes");
11055
+ } else {
11056
+ const shouldFixConflicts = await confirm("Apply automatic conflict fixes to your OpenCode and OMO config files?", true);
11057
+ if (shouldFixConflicts) {
11058
+ const actions = fixConflicts(process.cwd(), conflicts.conflicts);
11059
+ if (actions.length > 0) {
11060
+ for (const action of actions) {
11061
+ log.success(action);
11062
+ }
11063
+ } else {
11064
+ log.info("No additional conflict changes were needed");
11056
11065
  }
11057
11066
  } else {
11058
- log.info("No additional conflict changes were needed");
11067
+ log.warn("Skipped automatic conflict fixes — Magic Context may remain disabled");
11059
11068
  }
11060
- } else {
11061
- log.warn("Skipped automatic conflict fixes — Magic Context may remain disabled");
11062
11069
  }
11063
11070
  }
11064
11071
  }
11065
- let historianModel = null;
11066
- if (allModels.length > 0) {
11067
- const historianOptions = buildModelSelection2(allModels, "historian");
11068
- if (historianOptions.length > 0) {
11069
- historianModel = await selectOne("Select a model for historian (background context compressor)", historianOptions);
11070
- log.success(`Historian: ${historianModel}`);
11071
- } else {
11072
- log.info("No suitable historian models found — using built-in fallback chain");
11073
- }
11074
- } else {
11075
- log.info("Skipping model selection — using built-in fallback chain");
11076
- }
11077
- log.message("The dreamer runs overnight to consolidate and maintain project memories.");
11072
+ const historianModel = await pickModel(promptIO, allModels, "historian");
11073
+ log.success(`Historian: ${historianModel}`);
11078
11074
  const dreamerEnabled = await confirm("Enable dreamer?", true);
11079
11075
  let dreamerModel = null;
11080
- if (dreamerEnabled && allModels.length > 0) {
11081
- const dreamerOptions = buildModelSelection2(allModels, "dreamer");
11082
- if (dreamerOptions.length > 0) {
11083
- dreamerModel = await selectOne("Select a model for dreamer (runs in background, local LLMs ideal)", dreamerOptions);
11084
- log.success(`Dreamer: ${dreamerModel}`);
11085
- } else {
11086
- log.info("No suitable dreamer models — using built-in fallback chain");
11087
- }
11088
- } else if (dreamerEnabled) {
11089
- log.info("Using built-in fallback chain for dreamer");
11076
+ if (dreamerEnabled) {
11077
+ dreamerModel = await pickModel(promptIO, allModels, "dreamer");
11078
+ log.success(`Dreamer: ${dreamerModel}`);
11090
11079
  }
11091
- log.message("Sidekick augments prompts with project context via /ctx-aug command.");
11092
11080
  const sidekickEnabled = await confirm("Enable sidekick?", false);
11093
11081
  let sidekickModel = null;
11094
- if (sidekickEnabled && allModels.length > 0) {
11095
- const sidekickOptions = buildModelSelection2(allModels, "sidekick");
11096
- if (sidekickOptions.length > 0) {
11097
- sidekickModel = await selectOne("Select a model for sidekick (fast models preferred)", sidekickOptions);
11098
- log.success(`Sidekick: ${sidekickModel}`);
11099
- } else {
11100
- log.info("No suitable sidekick models — using built-in fallback chain");
11101
- }
11102
- } else if (sidekickEnabled) {
11103
- log.info("Using built-in fallback chain for sidekick");
11082
+ if (sidekickEnabled) {
11083
+ sidekickModel = await pickModel(promptIO, allModels, "sidekick");
11084
+ log.success(`Sidekick: ${sidekickModel}`);
11104
11085
  }
11105
11086
  const hasAnthropic = allModels.some((m) => m.startsWith("anthropic/"));
11106
11087
  let claudeMax = false;
@@ -11112,24 +11093,32 @@ async function runSetup() {
11112
11093
  log.success("Cache TTL set to 59m for Anthropic models");
11113
11094
  }
11114
11095
  }
11115
- writeMagicContextConfig(paths.magicContextConfig, {
11116
- historianModel,
11117
- dreamerEnabled,
11118
- dreamerModel,
11119
- sidekickEnabled,
11120
- sidekickModel,
11121
- claudeMax
11122
- });
11123
- log.success(`Config written to ${paths.magicContextConfig}`);
11124
- addPluginToTuiConfig(paths.tuiConfig, paths.tuiConfigFormat);
11125
- log.success("TUI sidebar plugin added to tui.json");
11096
+ if (dryRun) {
11097
+ log.message(`[dry-run] would write Magic Context config to ${paths.magicContextConfig}`);
11098
+ log.message("[dry-run] would add the TUI sidebar plugin to tui.json");
11099
+ } else {
11100
+ writeMagicContextConfig(paths.magicContextConfig, {
11101
+ historianModel,
11102
+ dreamerEnabled,
11103
+ dreamerModel,
11104
+ sidekickEnabled,
11105
+ sidekickModel,
11106
+ claudeMax
11107
+ });
11108
+ log.success(`Config written to ${paths.magicContextConfig}`);
11109
+ addPluginToTuiConfig(paths.tuiConfig, paths.tuiConfigFormat);
11110
+ log.success("TUI sidebar plugin added to tui.json");
11111
+ }
11126
11112
  if (paths.omoConfig && !hadExistingSetup) {
11127
11113
  log.warn(`Found oh-my-opencode config: ${paths.omoConfig}`);
11128
11114
  log.message(`These hooks may conflict:
11129
11115
  ` + ` • context-window-monitor
11130
11116
  ` + ` • preemptive-compaction
11131
11117
  ` + " • anthropic-context-window-limit-recovery");
11132
- const shouldDisable = await confirm("Disable these hooks in oh-my-opencode?", true);
11118
+ const shouldDisable = dryRun ? false : await confirm("Disable these hooks in oh-my-opencode?", true);
11119
+ if (dryRun) {
11120
+ log.message("[dry-run] would offer to disable conflicting oh-my-opencode hooks");
11121
+ }
11133
11122
  if (shouldDisable) {
11134
11123
  const actions = fixConflicts(process.cwd(), {
11135
11124
  compactionAuto: false,
@@ -11154,7 +11143,11 @@ async function runSetup() {
11154
11143
  sidekickEnabled ? `Sidekick: enabled${sidekickModel ? ` (${sidekickModel})` : ""}` : "Sidekick: disabled"
11155
11144
  ].join(`
11156
11145
  `);
11157
- note(summary, "Configuration");
11146
+ note(summary, dryRun ? "Configuration (dry run — not written)" : "Configuration");
11147
+ if (dryRun) {
11148
+ outro("Dry run complete — nothing was written.");
11149
+ return 0;
11150
+ }
11158
11151
  const shouldStar = await confirm("★ Star the repo on GitHub?", true);
11159
11152
  if (shouldStar) {
11160
11153
  try {
@@ -11177,6 +11170,7 @@ var init_setup_opencode = __esm(() => {
11177
11170
  init_conflict_detector();
11178
11171
  init_conflict_fixer();
11179
11172
  init_opencode();
11173
+ init_model_picker();
11180
11174
  init_opencode_helpers();
11181
11175
  init_paths();
11182
11176
  init_prompts();
@@ -11319,10 +11313,6 @@ function writeMagicContextConfig2(configPath, options) {
11319
11313
  writeFileSync5(configPath, `${import_comment_json5.stringify(config, null, 2)}
11320
11314
  `);
11321
11315
  }
11322
- async function chooseModel(prompts, allModels, role, message) {
11323
- const options = buildModelSelection(allModels, role);
11324
- return prompts.selectOne(message, options);
11325
- }
11326
11316
  async function chooseEmbedding(prompts) {
11327
11317
  const provider = await prompts.selectOne("Select embedding provider", [
11328
11318
  {
@@ -11383,18 +11373,25 @@ Run \`pi update --self\` (or \`npm install -g @earendil-works/pi-coding-agent@la
11383
11373
  spinner2.start("Fetching available Pi models");
11384
11374
  const allModels = env.getAvailableModels(pi.path);
11385
11375
  spinner2.stop(`Found ${allModels.length} model choices`);
11376
+ const dryRun = options.dryRun === true;
11377
+ if (dryRun) {
11378
+ prompts.log.warn("Dry run — no files will be written and no package will be registered.");
11379
+ }
11386
11380
  const settingsPath = env.paths.getPiUserExtensionsPath();
11387
11381
  const configPath = env.paths.getPiUserConfigPath();
11388
11382
  const configurePi = await prompts.confirm("Configure Pi to load Magic Context?", true);
11389
- let packageAdded = false;
11390
11383
  if (configurePi) {
11391
- packageAdded = writePiSettingsPackage(settingsPath);
11392
- prompts.log.success(packageAdded ? `Added ${PI_PACKAGE_SOURCE} to ${settingsPath}` : `Magic Context package already present in ${settingsPath}`);
11393
- prompts.log.message("This mirrors `pi install npm:@cortexkit/pi-magic-context` without running installs during setup verification.");
11384
+ if (dryRun) {
11385
+ prompts.log.message(`[dry-run] would add ${PI_PACKAGE_SOURCE} to ${settingsPath}`);
11386
+ } else {
11387
+ const packageAdded = writePiSettingsPackage(settingsPath);
11388
+ prompts.log.success(packageAdded ? `Added ${PI_PACKAGE_SOURCE} to ${settingsPath}` : `Magic Context package already present in ${settingsPath}`);
11389
+ prompts.log.message("This mirrors `pi install npm:@cortexkit/pi-magic-context` without running installs during setup verification.");
11390
+ }
11394
11391
  } else {
11395
11392
  prompts.log.warn("Skipped Pi package registration; install manually with `pi install npm:@cortexkit/pi-magic-context`.");
11396
11393
  }
11397
- const historianModel = await chooseModel(prompts, allModels, "historian", "Select a model for historian (background context compressor)");
11394
+ const historianModel = await pickModel(prompts, allModels, "historian");
11398
11395
  let historianThinkingLevel;
11399
11396
  if (historianModel.startsWith("github-copilot/")) {
11400
11397
  prompts.log.warn(`GitHub Copilot reasoning models require an explicit thinking level.
@@ -11414,36 +11411,41 @@ Run \`pi update --self\` (or \`npm install -g @earendil-works/pi-coding-agent@la
11414
11411
  ]);
11415
11412
  }
11416
11413
  const dreamerEnabled = await prompts.confirm("Enable dreamer for overnight memory maintenance?", true);
11417
- const dreamerModel = await chooseModel(prompts, allModels, "dreamer", "Select a model for dreamer (overnight memory maintenance)");
11414
+ const dreamerModel = dreamerEnabled ? await pickModel(prompts, allModels, "dreamer") : undefined;
11418
11415
  const sidekickEnabled = await prompts.confirm("Enable sidekick for /ctx-aug?", false);
11419
- const sidekickModel = sidekickEnabled ? await chooseModel(prompts, allModels, "sidekick", "Select a model for sidekick (fast models preferred)") : undefined;
11416
+ const sidekickModel = sidekickEnabled ? await pickModel(prompts, allModels, "sidekick") : undefined;
11420
11417
  const embedding = await chooseEmbedding(prompts);
11421
- writeMagicContextConfig2(configPath, {
11422
- historianModel,
11423
- historianThinkingLevel,
11424
- dreamerEnabled,
11425
- dreamerModel,
11426
- sidekickEnabled,
11427
- sidekickModel,
11428
- embedding
11429
- });
11430
- prompts.log.success(`Config written to ${configPath}`);
11418
+ if (dryRun) {
11419
+ prompts.log.message(`[dry-run] would write Magic Context config to ${configPath}`);
11420
+ } else {
11421
+ writeMagicContextConfig2(configPath, {
11422
+ historianModel,
11423
+ historianThinkingLevel,
11424
+ dreamerEnabled,
11425
+ dreamerModel,
11426
+ sidekickEnabled,
11427
+ sidekickModel,
11428
+ embedding
11429
+ });
11430
+ prompts.log.success(`Config written to ${configPath}`);
11431
+ }
11431
11432
  const thinkingLevelSuffix = historianThinkingLevel ? ` (thinking: ${historianThinkingLevel})` : "";
11432
11433
  const summary = [
11433
11434
  `Pi settings: ${configurePi ? settingsPath : "skipped"}`,
11434
11435
  `Magic Context config: ${configPath}`,
11435
11436
  `Historian: ${historianModel}${thinkingLevelSuffix}`,
11436
- `Dreamer: ${dreamerModel} (${dreamerEnabled ? "enabled" : "disabled"})`,
11437
+ `Dreamer: ${dreamerEnabled ? dreamerModel : "disabled"}`,
11437
11438
  sidekickEnabled ? `Sidekick: ${sidekickModel}` : "Sidekick: disabled",
11438
11439
  `Embedding: ${embedding.provider}${"model" in embedding ? ` (${embedding.model})` : ""}`
11439
11440
  ].join(`
11440
11441
  `);
11441
- prompts.note(summary, "Configuration");
11442
- prompts.outro("Start a Pi session and try /ctx-aug");
11442
+ prompts.note(summary, dryRun ? "Configuration (dry run — not written)" : "Configuration");
11443
+ prompts.outro(dryRun ? "Dry run complete — nothing was written." : "Start a Pi session and try /ctx-aug");
11443
11444
  return 0;
11444
11445
  }
11445
11446
  var import_comment_json5, DEFAULT_ENV;
11446
11447
  var init_setup_pi = __esm(() => {
11448
+ init_model_picker();
11447
11449
  init_paths();
11448
11450
  init_pi_helpers();
11449
11451
  init_pi_package_entry();
@@ -11466,7 +11468,8 @@ __export(exports_setup, {
11466
11468
  runSetup: () => runSetup3
11467
11469
  });
11468
11470
  async function runSetup3(argv) {
11469
- intro("Magic Context setup");
11471
+ const dryRun = argv.includes("--dry-run");
11472
+ intro(dryRun ? "Magic Context setup (dry run)" : "Magic Context setup");
11470
11473
  const adapters = await resolveAdaptersForCommand(argv, {
11471
11474
  allowMulti: true,
11472
11475
  verb: "setup"
@@ -11483,24 +11486,25 @@ async function runSetup3(argv) {
11483
11486
  anyFailure = true;
11484
11487
  continue;
11485
11488
  }
11486
- const code = await dispatchSetup(adapter);
11489
+ const code = await dispatchSetup(adapter, dryRun);
11487
11490
  if (code !== 0)
11488
11491
  anyFailure = true;
11489
- printNextSteps(adapter);
11492
+ if (!dryRun)
11493
+ printNextSteps(adapter);
11490
11494
  }
11491
11495
  if (anyFailure) {
11492
11496
  outro("Setup finished with warnings — see above.");
11493
11497
  return 1;
11494
11498
  }
11495
- outro("Done.");
11499
+ outro(dryRun ? "Dry run done — no changes were made." : "Done.");
11496
11500
  return 0;
11497
11501
  }
11498
- async function dispatchSetup(adapter) {
11502
+ async function dispatchSetup(adapter, dryRun) {
11499
11503
  switch (adapter.kind) {
11500
11504
  case "opencode":
11501
- return runSetup();
11505
+ return runSetup(dryRun);
11502
11506
  case "pi":
11503
- return runSetup2();
11507
+ return runSetup2({ dryRun });
11504
11508
  }
11505
11509
  }
11506
11510
  function printNextSteps(adapter) {
@@ -12224,6 +12228,7 @@ var init_storage_meta_shared = __esm(() => {
12224
12228
  "cachedM0Bytes",
12225
12229
  "cachedM1Bytes",
12226
12230
  "cachedM0ProjectMemoryEpoch",
12231
+ "cachedM0WorkspaceFingerprint",
12227
12232
  "cachedM0ProjectUserProfileVersion",
12228
12233
  "cachedM0MaxCompartmentSeq",
12229
12234
  "cachedM0MaxMemoryId",
@@ -145370,6 +145375,35 @@ function initializeDatabase(db) {
145370
145375
  );
145371
145376
  CREATE INDEX IF NOT EXISTS idx_compartments_session ON compartments(session_id);
145372
145377
 
145378
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
145379
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
145380
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
145381
+ session_id TEXT NOT NULL,
145382
+ project_path TEXT NOT NULL,
145383
+ harness TEXT NOT NULL DEFAULT 'opencode',
145384
+ window_index INTEGER NOT NULL DEFAULT 0,
145385
+ start_ordinal INTEGER NOT NULL,
145386
+ end_ordinal INTEGER NOT NULL,
145387
+ chunk_hash TEXT NOT NULL,
145388
+ model_id TEXT NOT NULL,
145389
+ dims INTEGER NOT NULL,
145390
+ vector BLOB NOT NULL,
145391
+ created_at INTEGER NOT NULL,
145392
+ UNIQUE(compartment_id, window_index)
145393
+ );
145394
+ CREATE INDEX IF NOT EXISTS idx_cce_session ON compartment_chunk_embeddings(session_id);
145395
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model ON compartment_chunk_embeddings(project_path, model_id);
145396
+
145397
+ CREATE TABLE IF NOT EXISTS session_projects (
145398
+ session_id TEXT NOT NULL,
145399
+ harness TEXT NOT NULL DEFAULT 'opencode',
145400
+ project_path TEXT NOT NULL,
145401
+ updated_at INTEGER NOT NULL,
145402
+ PRIMARY KEY(session_id, harness)
145403
+ );
145404
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
145405
+ ON session_projects(project_path);
145406
+
145373
145407
  CREATE TABLE IF NOT EXISTS compartment_events (
145374
145408
  id INTEGER PRIMARY KEY AUTOINCREMENT,
145375
145409
  session_id TEXT NOT NULL,
@@ -145555,6 +145589,25 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145555
145589
  rekeyed_at INTEGER NOT NULL
145556
145590
  );
145557
145591
 
145592
+ CREATE TABLE IF NOT EXISTS workspaces (
145593
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
145594
+ name TEXT NOT NULL UNIQUE,
145595
+ created_at INTEGER NOT NULL,
145596
+ updated_at INTEGER NOT NULL,
145597
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
145598
+ );
145599
+
145600
+ CREATE TABLE IF NOT EXISTS workspace_members (
145601
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
145602
+ project_path TEXT NOT NULL,
145603
+ display_name TEXT NOT NULL,
145604
+ display_path TEXT NOT NULL,
145605
+ added_at INTEGER NOT NULL,
145606
+ PRIMARY KEY (workspace_id, project_path)
145607
+ );
145608
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
145609
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
145610
+
145558
145611
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
145559
145612
  id INTEGER PRIMARY KEY AUTOINCREMENT,
145560
145613
  table_name TEXT NOT NULL,
@@ -145666,6 +145719,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145666
145719
  deferred_execute_state TEXT,
145667
145720
  cached_m0_bytes BLOB,
145668
145721
  cached_m0_project_memory_epoch INTEGER,
145722
+ cached_m0_workspace_fingerprint TEXT,
145669
145723
  cached_m0_project_user_profile_version INTEGER,
145670
145724
  cached_m0_max_compartment_seq INTEGER,
145671
145725
  cached_m0_max_memory_id INTEGER,
@@ -145824,6 +145878,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145824
145878
  ensureColumn(db, "session_meta", "cleared_reasoning_through_tag", "INTEGER DEFAULT 0");
145825
145879
  ensureColumn(db, "session_meta", "stripped_placeholder_ids", "TEXT DEFAULT ''");
145826
145880
  ensureColumn(db, "session_meta", "stale_reduce_stripped_ids", "TEXT DEFAULT ''");
145881
+ ensureColumn(db, "session_meta", "processed_image_stripped_ids", "TEXT DEFAULT ''");
145827
145882
  ensureColumn(db, "compartments", "start_message_id", "TEXT DEFAULT ''");
145828
145883
  ensureColumn(db, "compartments", "end_message_id", "TEXT DEFAULT ''");
145829
145884
  ensureColumn(db, "memory_embeddings", "model_id", "TEXT");
@@ -145877,6 +145932,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145877
145932
  ensureColumn(db, "memories", "importance", "INTEGER");
145878
145933
  ensureColumn(db, "session_meta", "cached_m0_bytes", "BLOB");
145879
145934
  ensureColumn(db, "session_meta", "cached_m0_project_memory_epoch", "INTEGER");
145935
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
145880
145936
  ensureColumn(db, "session_meta", "cached_m0_project_user_profile_version", "INTEGER");
145881
145937
  ensureColumn(db, "session_meta", "cached_m0_max_compartment_seq", "INTEGER");
145882
145938
  ensureColumn(db, "session_meta", "cached_m0_max_memory_id", "INTEGER");
@@ -145908,6 +145964,15 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145908
145964
  project_user_profile_version INTEGER NOT NULL DEFAULT 0,
145909
145965
  updated_at INTEGER NOT NULL DEFAULT 0
145910
145966
  );
145967
+ CREATE TABLE IF NOT EXISTS session_projects (
145968
+ session_id TEXT NOT NULL,
145969
+ harness TEXT NOT NULL DEFAULT 'opencode',
145970
+ project_path TEXT NOT NULL,
145971
+ updated_at INTEGER NOT NULL,
145972
+ PRIMARY KEY(session_id, harness)
145973
+ );
145974
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
145975
+ ON session_projects(project_path);
145911
145976
  CREATE TABLE IF NOT EXISTS m0_mutation_log (
145912
145977
  id INTEGER PRIMARY KEY AUTOINCREMENT,
145913
145978
  session_id TEXT NOT NULL,
@@ -145935,6 +146000,23 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145935
146000
  new_project_path TEXT NOT NULL,
145936
146001
  rekeyed_at INTEGER NOT NULL
145937
146002
  );
146003
+ CREATE TABLE IF NOT EXISTS workspaces (
146004
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
146005
+ name TEXT NOT NULL UNIQUE,
146006
+ created_at INTEGER NOT NULL,
146007
+ updated_at INTEGER NOT NULL,
146008
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
146009
+ );
146010
+ CREATE TABLE IF NOT EXISTS workspace_members (
146011
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
146012
+ project_path TEXT NOT NULL,
146013
+ display_name TEXT NOT NULL,
146014
+ display_path TEXT NOT NULL,
146015
+ added_at INTEGER NOT NULL,
146016
+ PRIMARY KEY (workspace_id, project_path)
146017
+ );
146018
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique ON workspace_members(project_path);
146019
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name ON workspace_members(workspace_id, display_name);
145938
146020
  CREATE TABLE IF NOT EXISTS v22_backfill_failures (
145939
146021
  id INTEGER PRIMARY KEY AUTOINCREMENT,
145940
146022
  table_name TEXT NOT NULL,
@@ -145956,6 +146038,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
145956
146038
  ensureColumn(db, "recomp_compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
145957
146039
  ensureColumn(db, "recomp_facts", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
145958
146040
  ensureColumn(db, "message_history_index", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
146041
+ ensureColumn(db, "workspaces", "share_categories", `TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
145959
146042
  }
145960
146043
  function healAllNullColumns(db) {
145961
146044
  healNullTextColumns(db);
@@ -145993,6 +146076,7 @@ function healNullTextColumns(db) {
145993
146076
  ["system_prompt_hash", ""],
145994
146077
  ["stripped_placeholder_ids", ""],
145995
146078
  ["stale_reduce_stripped_ids", ""],
146079
+ ["processed_image_stripped_ids", ""],
145996
146080
  ["memory_block_cache", ""],
145997
146081
  ["memory_block_ids", ""],
145998
146082
  ["compaction_marker_state", ""],
@@ -146037,7 +146121,7 @@ function healNullIntegerColumns(db) {
146037
146121
  }
146038
146122
  }
146039
146123
  function ensureColumn(db, table, column, definition) {
146040
- if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_'(),[\]\s]+$/i.test(definition)) {
146124
+ if (!/^[a-z][a-z0-9_]*$/.test(table) || !/^[a-z][a-z0-9_]*$/.test(column) || !/^[A-Z0-9_"'(),[\]\s]+$/i.test(definition)) {
146041
146125
  throw new Error(`Unsafe schema identifier: ${table}.${column} ${definition}`);
146042
146126
  }
146043
146127
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
@@ -146129,7 +146213,7 @@ function closeDatabase() {
146129
146213
  }
146130
146214
  }
146131
146215
  }
146132
- var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 32, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
146216
+ var databases, persistenceByDatabase, persistenceErrorByDatabase, lastSchemaFenceRejection = null, LATEST_SUPPORTED_VERSION = 36, sqlitePragmaConfig, CHANNEL2_CLAIM_TTL_MS = 120000;
146133
146217
  var init_storage_db = __esm(async () => {
146134
146218
  init_data_path();
146135
146219
  init_logger();
@@ -146149,10 +146233,263 @@ var init_storage_db = __esm(async () => {
146149
146233
  };
146150
146234
  });
146151
146235
 
146236
+ // ../plugin/src/features/magic-context/memory/constants.ts
146237
+ var V2_MEMORY_CATEGORIES, CATEGORY_PRIORITY, MEMORY_CATEGORY_ORDER_UNKNOWN = 99, MEMORY_CATEGORY_ORDER_PRIORITY, MEMORY_CATEGORY_ORDER_SQL, CATEGORY_DEFAULT_TTL;
146238
+ var init_constants = __esm(() => {
146239
+ V2_MEMORY_CATEGORIES = [
146240
+ "PROJECT_RULES",
146241
+ "ARCHITECTURE",
146242
+ "CONSTRAINTS",
146243
+ "CONFIG_VALUES",
146244
+ "NAMING"
146245
+ ];
146246
+ CATEGORY_PRIORITY = [
146247
+ "PROJECT_RULES",
146248
+ "ARCHITECTURE",
146249
+ "CONSTRAINTS",
146250
+ "CONFIG_VALUES",
146251
+ "NAMING",
146252
+ "USER_DIRECTIVES",
146253
+ "USER_PREFERENCES",
146254
+ "CONFIG_DEFAULTS",
146255
+ "ARCHITECTURE_DECISIONS",
146256
+ "ENVIRONMENT",
146257
+ "WORKFLOW_RULES",
146258
+ "KNOWN_ISSUES"
146259
+ ];
146260
+ MEMORY_CATEGORY_ORDER_PRIORITY = CATEGORY_PRIORITY.reduce((acc, category, index) => {
146261
+ acc[category] = index;
146262
+ return acc;
146263
+ }, {});
146264
+ MEMORY_CATEGORY_ORDER_SQL = `CASE category ${CATEGORY_PRIORITY.map((category, index) => `WHEN '${category}' THEN ${index}`).join(" ")} ELSE ${MEMORY_CATEGORY_ORDER_UNKNOWN} END`;
146265
+ CATEGORY_DEFAULT_TTL = {
146266
+ WORKFLOW_RULES: 90 * 24 * 60 * 60 * 1000,
146267
+ KNOWN_ISSUES: 30 * 24 * 60 * 60 * 1000
146268
+ };
146269
+ });
146270
+
146271
+ // ../plugin/src/features/magic-context/memory/project-identity.ts
146272
+ import { execFileSync as execFileSync3 } from "node:child_process";
146273
+ import { createHash } from "node:crypto";
146274
+ import { statSync as statSync3 } from "node:fs";
146275
+ import path3 from "node:path";
146276
+ function asError(error) {
146277
+ return error instanceof Error ? error : undefined;
146278
+ }
146279
+ function getErrorCode(error) {
146280
+ if (error === null || typeof error !== "object" || !("code" in error)) {
146281
+ return;
146282
+ }
146283
+ const code = error.code;
146284
+ return typeof code === "string" ? code : undefined;
146285
+ }
146286
+ function getErrorSignal(error) {
146287
+ if (error === null || typeof error !== "object" || !("signal" in error)) {
146288
+ return;
146289
+ }
146290
+ const signal = error.signal;
146291
+ return typeof signal === "string" ? signal : undefined;
146292
+ }
146293
+ function getErrorKilled(error) {
146294
+ if (error === null || typeof error !== "object" || !("killed" in error)) {
146295
+ return false;
146296
+ }
146297
+ return error.killed === true;
146298
+ }
146299
+ function getErrorStderr(error) {
146300
+ if (error === null || typeof error !== "object" || !("stderr" in error)) {
146301
+ return "";
146302
+ }
146303
+ const stderr = error.stderr;
146304
+ if (typeof stderr === "string") {
146305
+ return stderr;
146306
+ }
146307
+ if (Buffer.isBuffer(stderr)) {
146308
+ return stderr.toString("utf8");
146309
+ }
146310
+ return "";
146311
+ }
146312
+ function directoryFallback(directory) {
146313
+ const canonical = path3.resolve(directory);
146314
+ const hash = createHash("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
146315
+ return `dir:${hash}`;
146316
+ }
146317
+ function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
146318
+ try {
146319
+ const stat = statSync3(canonicalDirectory);
146320
+ if (!stat.isDirectory()) {
146321
+ throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
146322
+ }
146323
+ } catch (error) {
146324
+ if (error instanceof ProjectIdentityError) {
146325
+ throw error;
146326
+ }
146327
+ const code = getErrorCode(error);
146328
+ if (code === "EACCES" || code === "EPERM") {
146329
+ throw new ProjectIdentityError("permission_denied", rawDirectory, `Permission denied while accessing project directory: ${canonicalDirectory}`, asError(error));
146330
+ }
146331
+ throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error));
146332
+ }
146333
+ }
146334
+ function isGitTimeoutError(error) {
146335
+ const code = getErrorCode(error);
146336
+ const signal = getErrorSignal(error);
146337
+ return code === "ETIMEDOUT" || signal === "SIGTERM" || signal === "SIGKILL" || getErrorKilled(error);
146338
+ }
146339
+ function classifyGitError(error, rawDirectory) {
146340
+ if (isGitTimeoutError(error)) {
146341
+ return new ProjectIdentityError("git_timeout", rawDirectory, `git rev-list timed out after ${GIT_TIMEOUT_MS}ms`, asError(error));
146342
+ }
146343
+ const code = getErrorCode(error);
146344
+ if (code === "ENOENT") {
146345
+ return new ProjectIdentityError("git_missing", rawDirectory, "git binary is not available in PATH", asError(error));
146346
+ }
146347
+ if (code === "EACCES" || code === "EPERM") {
146348
+ return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
146349
+ }
146350
+ const stderr = getErrorStderr(error).toLowerCase();
146351
+ if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
146352
+ return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
146353
+ }
146354
+ return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
146355
+ }
146356
+ function resolveProjectIdentityStrict(directory) {
146357
+ const canonical = path3.resolve(directory);
146358
+ const cached = identityCache.get(canonical);
146359
+ if (cached !== undefined) {
146360
+ return cached;
146361
+ }
146362
+ assertDirectoryUsable(canonical, directory);
146363
+ let output;
146364
+ try {
146365
+ output = execFileSync3("git", ["rev-list", "--max-parents=0", "HEAD"], {
146366
+ cwd: canonical,
146367
+ encoding: "utf8",
146368
+ env: { ...process.env, LC_ALL: "C", LANG: "C" },
146369
+ stdio: ["ignore", "pipe", "pipe"],
146370
+ timeout: GIT_TIMEOUT_MS
146371
+ });
146372
+ } catch (error) {
146373
+ throw classifyGitError(error, directory);
146374
+ }
146375
+ const firstLine = output.split(`
146376
+ `)[0]?.trim() ?? "";
146377
+ const rootCommit = firstLine.slice(0, 64);
146378
+ if (rootCommit.length < 7) {
146379
+ throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
146380
+ }
146381
+ const identity = `git:${rootCommit}`;
146382
+ identityCache.set(canonical, identity);
146383
+ return identity;
146384
+ }
146385
+ function shouldUseDirectoryFallback(error) {
146386
+ return error.errorClass === "not_git_repo" || error.errorClass === "unknown" && error.message.startsWith("Unable to access project directory:");
146387
+ }
146388
+ function resolveProjectIdentity(directory) {
146389
+ const canonical = path3.resolve(directory);
146390
+ const cachedFallback = directoryFallbackCache.get(canonical);
146391
+ if (cachedFallback !== undefined) {
146392
+ if (!hasGitDir(canonical)) {
146393
+ return cachedFallback;
146394
+ }
146395
+ directoryFallbackCache.delete(canonical);
146396
+ }
146397
+ try {
146398
+ return resolveProjectIdentityStrict(directory);
146399
+ } catch (error) {
146400
+ if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
146401
+ const fallback = directoryFallback(canonical);
146402
+ if (!hasGitDir(canonical)) {
146403
+ directoryFallbackCache.set(canonical, fallback);
146404
+ }
146405
+ return fallback;
146406
+ }
146407
+ throw error;
146408
+ }
146409
+ }
146410
+ function hasGitDir(canonical) {
146411
+ try {
146412
+ statSync3(path3.join(canonical, ".git"));
146413
+ return true;
146414
+ } catch {
146415
+ return false;
146416
+ }
146417
+ }
146418
+ var GIT_TIMEOUT_MS = 5000, identityCache, directoryFallbackCache, ProjectIdentityError;
146419
+ var init_project_identity = __esm(() => {
146420
+ identityCache = new Map;
146421
+ directoryFallbackCache = new Map;
146422
+ ProjectIdentityError = class ProjectIdentityError extends Error {
146423
+ errorClass;
146424
+ rawDirectory;
146425
+ constructor(errorClass, rawDirectory, message, cause) {
146426
+ super(message);
146427
+ this.name = "ProjectIdentityError";
146428
+ this.errorClass = errorClass;
146429
+ this.rawDirectory = rawDirectory;
146430
+ if (cause) {
146431
+ this.cause = cause;
146432
+ }
146433
+ }
146434
+ };
146435
+ });
146436
+
146437
+ // ../plugin/src/features/magic-context/project-identity.ts
146438
+ var init_project_identity2 = __esm(() => {
146439
+ init_project_identity();
146440
+ });
146441
+
146442
+ // ../plugin/src/features/magic-context/workspaces.ts
146443
+ function uniqueSorted(values) {
146444
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
146445
+ }
146446
+ function isInTransaction(db) {
146447
+ const candidate = db;
146448
+ return candidate.inTransaction === true || candidate.isTransaction === true;
146449
+ }
146450
+ function bumpEpochRows(db, identities, now) {
146451
+ const stmt2 = db.prepare(`INSERT INTO project_state
146452
+ (project_path, project_memory_epoch, project_user_profile_version, updated_at)
146453
+ VALUES (?, 1, 0, ?)
146454
+ ON CONFLICT(project_path) DO UPDATE SET
146455
+ project_memory_epoch = project_memory_epoch + 1,
146456
+ updated_at = excluded.updated_at`);
146457
+ for (const identity of uniqueSorted(identities)) {
146458
+ stmt2.run(identity, now);
146459
+ }
146460
+ }
146461
+ function bumpEpochsForWorkspaceMemberSet(db, identities, now = Date.now()) {
146462
+ const run = () => bumpEpochRows(db, identities, now);
146463
+ if (isInTransaction(db)) {
146464
+ run();
146465
+ return;
146466
+ }
146467
+ db.exec("BEGIN IMMEDIATE");
146468
+ try {
146469
+ run();
146470
+ db.exec("COMMIT");
146471
+ } catch (error) {
146472
+ try {
146473
+ db.exec("ROLLBACK");
146474
+ } catch {}
146475
+ throw error;
146476
+ }
146477
+ }
146478
+ var VALID_SHARE_CATEGORIES;
146479
+ var init_workspaces = __esm(() => {
146480
+ init_constants();
146481
+ init_project_identity2();
146482
+ VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
146483
+ });
146484
+
146152
146485
  // ../plugin/src/features/magic-context/migrations.ts
146153
146486
  function tableExists(db, name2) {
146154
146487
  return Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?").get(name2));
146155
146488
  }
146489
+ function columnExists(db, table, column) {
146490
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
146491
+ return rows.some((row) => row.name === column);
146492
+ }
146156
146493
  function ensureMigrationsTable(db) {
146157
146494
  db.exec(`
146158
146495
  CREATE TABLE IF NOT EXISTS schema_migrations (
@@ -146221,6 +146558,7 @@ function runMigrations(db) {
146221
146558
  var MIGRATIONS, LATEST_MIGRATION_VERSION;
146222
146559
  var init_migrations = __esm(async () => {
146223
146560
  init_logger();
146561
+ init_workspaces();
146224
146562
  await init_storage_db();
146225
146563
  MIGRATIONS = [
146226
146564
  {
@@ -147018,6 +147356,156 @@ var init_migrations = __esm(async () => {
147018
147356
  db.prepare("UPDATE session_meta SET force_emergency_bypass_used = 0 WHERE force_emergency_bypass_used IS NULL").run();
147019
147357
  db.prepare("UPDATE session_meta SET last_usage_context_limit = 0 WHERE last_usage_context_limit IS NULL").run();
147020
147358
  }
147359
+ },
147360
+ {
147361
+ version: 33,
147362
+ description: "Compartment chunk embeddings for semantic message-history search",
147363
+ up: (db) => {
147364
+ db.exec(`
147365
+ CREATE TABLE IF NOT EXISTS compartment_chunk_embeddings (
147366
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
147367
+ compartment_id INTEGER NOT NULL REFERENCES compartments(id) ON DELETE CASCADE,
147368
+ session_id TEXT NOT NULL,
147369
+ project_path TEXT NOT NULL,
147370
+ harness TEXT NOT NULL DEFAULT 'opencode',
147371
+ window_index INTEGER NOT NULL DEFAULT 0,
147372
+ start_ordinal INTEGER NOT NULL,
147373
+ end_ordinal INTEGER NOT NULL,
147374
+ chunk_hash TEXT NOT NULL,
147375
+ model_id TEXT NOT NULL,
147376
+ dims INTEGER NOT NULL,
147377
+ vector BLOB NOT NULL,
147378
+ created_at INTEGER NOT NULL,
147379
+ UNIQUE(compartment_id, window_index)
147380
+ );
147381
+ CREATE INDEX IF NOT EXISTS idx_cce_session
147382
+ ON compartment_chunk_embeddings(session_id);
147383
+ CREATE INDEX IF NOT EXISTS idx_cce_project_model
147384
+ ON compartment_chunk_embeddings(project_path, model_id);
147385
+ `);
147386
+ }
147387
+ },
147388
+ {
147389
+ version: 34,
147390
+ description: "workspace tables and m[0] workspace fingerprint cache reset",
147391
+ up: (db) => {
147392
+ db.exec(`
147393
+ CREATE TABLE IF NOT EXISTS workspaces (
147394
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
147395
+ name TEXT NOT NULL UNIQUE,
147396
+ created_at INTEGER NOT NULL,
147397
+ updated_at INTEGER NOT NULL
147398
+ );
147399
+ CREATE TABLE IF NOT EXISTS workspace_members (
147400
+ workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
147401
+ project_path TEXT NOT NULL,
147402
+ display_name TEXT NOT NULL,
147403
+ display_path TEXT NOT NULL,
147404
+ added_at INTEGER NOT NULL,
147405
+ PRIMARY KEY (workspace_id, project_path)
147406
+ );
147407
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_unique
147408
+ ON workspace_members(project_path);
147409
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_member_name
147410
+ ON workspace_members(workspace_id, display_name);
147411
+ `);
147412
+ const hasSessionMeta = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='session_meta' LIMIT 1").get();
147413
+ if (!hasSessionMeta)
147414
+ return;
147415
+ ensureColumn(db, "session_meta", "cached_m0_workspace_fingerprint", "TEXT");
147416
+ const columns = new Set(db.prepare("PRAGMA table_info(session_meta)").all().map((column) => column.name));
147417
+ const clears = [
147418
+ ["cached_m0_bytes", null],
147419
+ ["cached_m1_bytes", null],
147420
+ ["cached_m0_project_memory_epoch", null],
147421
+ ["cached_m0_workspace_fingerprint", null],
147422
+ ["cached_m0_project_user_profile_version", null],
147423
+ ["cached_m0_max_compartment_seq", null],
147424
+ ["cached_m0_max_memory_id", null],
147425
+ ["cached_m0_max_mutation_id", null],
147426
+ ["cached_m0_max_memory_mutation_id", null],
147427
+ ["cached_m0_project_docs_hash", null],
147428
+ ["cached_m0_materialized_at", null],
147429
+ ["cached_m0_session_facts_version", null],
147430
+ ["cached_m0_upgrade_state", null],
147431
+ ["cached_m0_system_hash", null],
147432
+ ["cached_m0_tool_set_hash", null],
147433
+ ["cached_m0_model_key", null],
147434
+ ["cached_m0_last_baseline_end_message_id", null],
147435
+ ["memory_block_cache", ""],
147436
+ ["memory_block_ids", ""],
147437
+ ["memory_block_count", 0]
147438
+ ];
147439
+ const setClauses = [];
147440
+ const values = [];
147441
+ for (const [column, value] of clears) {
147442
+ if (!columns.has(column))
147443
+ continue;
147444
+ setClauses.push(`${column} = ?`);
147445
+ values.push(value);
147446
+ }
147447
+ if (setClauses.length > 0) {
147448
+ db.prepare(`UPDATE session_meta SET ${setClauses.join(", ")}`).run(...values);
147449
+ }
147450
+ }
147451
+ },
147452
+ {
147453
+ version: 35,
147454
+ description: "workspace per-category share defaults and epoch refresh",
147455
+ up: (db) => {
147456
+ db.exec(`
147457
+ CREATE TABLE IF NOT EXISTS workspaces (
147458
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
147459
+ name TEXT NOT NULL UNIQUE,
147460
+ created_at INTEGER NOT NULL,
147461
+ updated_at INTEGER NOT NULL,
147462
+ share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'
147463
+ );
147464
+ `);
147465
+ if (!columnExists(db, "workspaces", "share_categories")) {
147466
+ db.exec(`ALTER TABLE workspaces ADD COLUMN share_categories TEXT NOT NULL DEFAULT '["CONSTRAINTS"]'`);
147467
+ }
147468
+ db.prepare(`UPDATE workspaces
147469
+ SET share_categories = '["CONSTRAINTS"]'
147470
+ WHERE share_categories IS NULL OR share_categories = ''`).run();
147471
+ if (!tableExists(db, "workspace_members"))
147472
+ return;
147473
+ const rows = db.prepare(`SELECT DISTINCT project_path AS identity
147474
+ FROM workspace_members
147475
+ WHERE project_path IS NOT NULL AND project_path <> ''
147476
+ ORDER BY project_path ASC`).all();
147477
+ const identities = rows.map((row) => typeof row.identity === "string" ? row.identity : "").filter((identity) => identity.length > 0);
147478
+ if (identities.length > 0) {
147479
+ bumpEpochsForWorkspaceMemberSet(db, identities, Date.now());
147480
+ }
147481
+ }
147482
+ },
147483
+ {
147484
+ version: 36,
147485
+ description: "session project ownership map for compartment chunk backfill scoping",
147486
+ up: (db) => {
147487
+ db.exec(`
147488
+ CREATE TABLE IF NOT EXISTS session_projects (
147489
+ session_id TEXT NOT NULL,
147490
+ harness TEXT NOT NULL DEFAULT 'opencode',
147491
+ project_path TEXT NOT NULL,
147492
+ updated_at INTEGER NOT NULL,
147493
+ PRIMARY KEY(session_id, harness)
147494
+ );
147495
+ CREATE INDEX IF NOT EXISTS idx_session_projects_project
147496
+ ON session_projects(project_path);
147497
+ `);
147498
+ const hasChunkTable = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='compartment_chunk_embeddings'").get();
147499
+ if (hasChunkTable) {
147500
+ db.exec(`
147501
+ INSERT OR IGNORE INTO session_projects (session_id, harness, project_path, updated_at)
147502
+ SELECT session_id, harness, MIN(project_path), 0
147503
+ FROM compartment_chunk_embeddings
147504
+ GROUP BY session_id, harness
147505
+ HAVING COUNT(DISTINCT project_path) = 1;
147506
+ `);
147507
+ }
147508
+ }
147021
147509
  }
147022
147510
  ];
147023
147511
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -147030,177 +147518,6 @@ var init_project_docs_hash = __esm(() => {
147030
147518
  MAX_PROJECT_DOC_BYTES = 256 * 1024;
147031
147519
  docsCache = new Map;
147032
147520
  });
147033
-
147034
- // ../plugin/src/features/magic-context/memory/project-identity.ts
147035
- import { execFileSync as execFileSync3 } from "node:child_process";
147036
- import { createHash } from "node:crypto";
147037
- import { statSync as statSync3 } from "node:fs";
147038
- import path3 from "node:path";
147039
- function asError(error) {
147040
- return error instanceof Error ? error : undefined;
147041
- }
147042
- function getErrorCode(error) {
147043
- if (error === null || typeof error !== "object" || !("code" in error)) {
147044
- return;
147045
- }
147046
- const code = error.code;
147047
- return typeof code === "string" ? code : undefined;
147048
- }
147049
- function getErrorSignal(error) {
147050
- if (error === null || typeof error !== "object" || !("signal" in error)) {
147051
- return;
147052
- }
147053
- const signal = error.signal;
147054
- return typeof signal === "string" ? signal : undefined;
147055
- }
147056
- function getErrorKilled(error) {
147057
- if (error === null || typeof error !== "object" || !("killed" in error)) {
147058
- return false;
147059
- }
147060
- return error.killed === true;
147061
- }
147062
- function getErrorStderr(error) {
147063
- if (error === null || typeof error !== "object" || !("stderr" in error)) {
147064
- return "";
147065
- }
147066
- const stderr = error.stderr;
147067
- if (typeof stderr === "string") {
147068
- return stderr;
147069
- }
147070
- if (Buffer.isBuffer(stderr)) {
147071
- return stderr.toString("utf8");
147072
- }
147073
- return "";
147074
- }
147075
- function directoryFallback(directory) {
147076
- const canonical = path3.resolve(directory);
147077
- const hash = createHash("md5").update(canonical, "utf8").digest("hex").slice(0, 12);
147078
- return `dir:${hash}`;
147079
- }
147080
- function assertDirectoryUsable(canonicalDirectory, rawDirectory) {
147081
- try {
147082
- const stat = statSync3(canonicalDirectory);
147083
- if (!stat.isDirectory()) {
147084
- throw new ProjectIdentityError("unknown", rawDirectory, `Project path is not a directory: ${canonicalDirectory}`);
147085
- }
147086
- } catch (error) {
147087
- if (error instanceof ProjectIdentityError) {
147088
- throw error;
147089
- }
147090
- const code = getErrorCode(error);
147091
- if (code === "EACCES" || code === "EPERM") {
147092
- throw new ProjectIdentityError("permission_denied", rawDirectory, `Permission denied while accessing project directory: ${canonicalDirectory}`, asError(error));
147093
- }
147094
- throw new ProjectIdentityError("unknown", rawDirectory, `Unable to access project directory: ${canonicalDirectory}`, asError(error));
147095
- }
147096
- }
147097
- function isGitTimeoutError(error) {
147098
- const code = getErrorCode(error);
147099
- const signal = getErrorSignal(error);
147100
- return code === "ETIMEDOUT" || signal === "SIGTERM" || signal === "SIGKILL" || getErrorKilled(error);
147101
- }
147102
- function classifyGitError(error, rawDirectory) {
147103
- if (isGitTimeoutError(error)) {
147104
- return new ProjectIdentityError("git_timeout", rawDirectory, `git rev-list timed out after ${GIT_TIMEOUT_MS}ms`, asError(error));
147105
- }
147106
- const code = getErrorCode(error);
147107
- if (code === "ENOENT") {
147108
- return new ProjectIdentityError("git_missing", rawDirectory, "git binary is not available in PATH", asError(error));
147109
- }
147110
- if (code === "EACCES" || code === "EPERM") {
147111
- return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
147112
- }
147113
- const stderr = getErrorStderr(error).toLowerCase();
147114
- if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
147115
- return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
147116
- }
147117
- return new ProjectIdentityError("unknown", rawDirectory, "git rev-list failed while resolving project identity", asError(error));
147118
- }
147119
- function resolveProjectIdentityStrict(directory) {
147120
- const canonical = path3.resolve(directory);
147121
- const cached = identityCache.get(canonical);
147122
- if (cached !== undefined) {
147123
- return cached;
147124
- }
147125
- assertDirectoryUsable(canonical, directory);
147126
- let output;
147127
- try {
147128
- output = execFileSync3("git", ["rev-list", "--max-parents=0", "HEAD"], {
147129
- cwd: canonical,
147130
- encoding: "utf8",
147131
- env: { ...process.env, LC_ALL: "C", LANG: "C" },
147132
- stdio: ["ignore", "pipe", "pipe"],
147133
- timeout: GIT_TIMEOUT_MS
147134
- });
147135
- } catch (error) {
147136
- throw classifyGitError(error, directory);
147137
- }
147138
- const firstLine = output.split(`
147139
- `)[0]?.trim() ?? "";
147140
- const rootCommit = firstLine.slice(0, 64);
147141
- if (rootCommit.length < 7) {
147142
- throw new ProjectIdentityError("unknown", directory, "git rev-list returned no valid root commit hash");
147143
- }
147144
- const identity = `git:${rootCommit}`;
147145
- identityCache.set(canonical, identity);
147146
- return identity;
147147
- }
147148
- function shouldUseDirectoryFallback(error) {
147149
- return error.errorClass === "not_git_repo" || error.errorClass === "unknown" && error.message.startsWith("Unable to access project directory:");
147150
- }
147151
- function resolveProjectIdentity(directory) {
147152
- const canonical = path3.resolve(directory);
147153
- const cachedFallback = directoryFallbackCache.get(canonical);
147154
- if (cachedFallback !== undefined) {
147155
- if (!hasGitDir(canonical)) {
147156
- return cachedFallback;
147157
- }
147158
- directoryFallbackCache.delete(canonical);
147159
- }
147160
- try {
147161
- return resolveProjectIdentityStrict(directory);
147162
- } catch (error) {
147163
- if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
147164
- const fallback = directoryFallback(canonical);
147165
- if (!hasGitDir(canonical)) {
147166
- directoryFallbackCache.set(canonical, fallback);
147167
- }
147168
- return fallback;
147169
- }
147170
- throw error;
147171
- }
147172
- }
147173
- function hasGitDir(canonical) {
147174
- try {
147175
- statSync3(path3.join(canonical, ".git"));
147176
- return true;
147177
- } catch {
147178
- return false;
147179
- }
147180
- }
147181
- var GIT_TIMEOUT_MS = 5000, identityCache, directoryFallbackCache, ProjectIdentityError;
147182
- var init_project_identity = __esm(() => {
147183
- identityCache = new Map;
147184
- directoryFallbackCache = new Map;
147185
- ProjectIdentityError = class ProjectIdentityError extends Error {
147186
- errorClass;
147187
- rawDirectory;
147188
- constructor(errorClass, rawDirectory, message, cause) {
147189
- super(message);
147190
- this.name = "ProjectIdentityError";
147191
- this.errorClass = errorClass;
147192
- this.rawDirectory = rawDirectory;
147193
- if (cause) {
147194
- this.cause = cause;
147195
- }
147196
- }
147197
- };
147198
- });
147199
-
147200
- // ../plugin/src/features/magic-context/project-identity.ts
147201
- var init_project_identity2 = __esm(() => {
147202
- init_project_identity();
147203
- });
147204
147521
  // ../plugin/src/features/magic-context/storage-m0-mutation-log.ts
147205
147522
  var M0_MUTATION_TYPES;
147206
147523
  var init_storage_m0_mutation_log = __esm(() => {
@@ -147328,6 +147645,7 @@ var init_storage = __esm(async () => {
147328
147645
  init_storage_source();
147329
147646
  init_storage_tags();
147330
147647
  init_storage_v22_backfill_failures();
147648
+ init_workspaces();
147331
147649
  await __promiseAll([
147332
147650
  init_message_index(),
147333
147651
  init_migrations(),
@@ -162515,7 +162833,8 @@ var init_magic_context = __esm(() => {
162515
162833
  endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
162516
162834
  api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
162517
162835
  input_type: exports_external.string().optional().describe("Optional input_type sent in the embedding request body. Required by some openai-compatible providers (e.g. NVIDIA NIM expects 'query' or 'passage'). Omitted from the request when unset."),
162518
- truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset.")
162836
+ truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset."),
162837
+ max_input_tokens: exports_external.number().int().positive().optional().describe("Optional maximum input tokens for chunk embeddings. Defaults conservatively to 512 when omitted.")
162519
162838
  }).superRefine((data, ctx) => {
162520
162839
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
162521
162840
  ctx.addIssue({
@@ -162536,7 +162855,8 @@ var init_magic_context = __esm(() => {
162536
162855
  if (data.provider === "local") {
162537
162856
  return {
162538
162857
  provider: "local",
162539
- model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL
162858
+ model: data.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
162859
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
162540
162860
  };
162541
162861
  }
162542
162862
  if (data.provider === "openai-compatible") {
@@ -162549,7 +162869,8 @@ var init_magic_context = __esm(() => {
162549
162869
  endpoint: data.endpoint?.trim() ?? "",
162550
162870
  ...apiKey ? { api_key: apiKey } : {},
162551
162871
  ...inputType ? { input_type: inputType } : {},
162552
- ...truncate ? { truncate } : {}
162872
+ ...truncate ? { truncate } : {},
162873
+ ...data.max_input_tokens ? { max_input_tokens: data.max_input_tokens } : {}
162553
162874
  };
162554
162875
  }
162555
162876
  return { provider: "off" };
@@ -166500,6 +166821,7 @@ function printUsage() {
166500
166821
  console.log("");
166501
166822
  console.log(" Usage:");
166502
166823
  console.log(" npx @cortexkit/magic-context@latest setup");
166824
+ console.log(" # add --dry-run to preview the wizard without writing any files");
166503
166825
  console.log(" npx @cortexkit/magic-context@latest doctor");
166504
166826
  console.log(" npx @cortexkit/magic-context@latest doctor --issue");
166505
166827
  console.log(" npx @cortexkit/magic-context@latest doctor migrate \\");