@orquentra/codebuddy 2.15.1 → 2.15.3

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 (78) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/dist/{chunk-VBDM7HC5.js → chunk-3L5IR6OW.js} +2 -2
  3. package/dist/{chunk-VBDM7HC5.js.map → chunk-3L5IR6OW.js.map} +1 -1
  4. package/dist/{chunk-AFXWCSQ4.js → chunk-3TZNQJTH.js} +3 -3
  5. package/dist/{chunk-RKF4HBBQ.js → chunk-4LMBZWIF.js} +47 -12
  6. package/dist/chunk-4LMBZWIF.js.map +1 -0
  7. package/dist/{chunk-RGIVHKBH.js → chunk-4TKKLQLH.js} +2 -2
  8. package/dist/{chunk-OO7Q7XZF.js → chunk-5ZLNHYJH.js} +30 -14
  9. package/dist/chunk-5ZLNHYJH.js.map +1 -0
  10. package/dist/{chunk-VF7DKRIU.js → chunk-6KSSJJZO.js} +8 -7
  11. package/dist/chunk-6KSSJJZO.js.map +1 -0
  12. package/dist/{chunk-MNSMW4MZ.js → chunk-7IXPARDM.js} +3 -3
  13. package/dist/{chunk-K4XNTYV7.js → chunk-FEFOZ2ZW.js} +14 -3
  14. package/dist/chunk-FEFOZ2ZW.js.map +1 -0
  15. package/dist/{chunk-7JPUMXFE.js → chunk-FGJ2KPPN.js} +53 -26
  16. package/dist/chunk-FGJ2KPPN.js.map +1 -0
  17. package/dist/{chunk-GNV4X67F.js → chunk-KGDRGA2L.js} +10 -10
  18. package/dist/{chunk-4DTO2E4Y.js → chunk-LZTYPJ5I.js} +35 -17
  19. package/dist/chunk-LZTYPJ5I.js.map +1 -0
  20. package/dist/{chunk-BNZDEU5K.js → chunk-R5E2YGLE.js} +13 -7
  21. package/dist/chunk-R5E2YGLE.js.map +1 -0
  22. package/dist/{chunk-6OQKL35B.js → chunk-R5JRKMEP.js} +1 -1
  23. package/dist/chunk-R5JRKMEP.js.map +1 -0
  24. package/dist/{chunk-PZ4EBFIA.js → chunk-SPQU2422.js} +4 -4
  25. package/dist/{chunk-NNJSAYHY.js → chunk-SXVH74NE.js} +36 -6
  26. package/dist/chunk-SXVH74NE.js.map +1 -0
  27. package/dist/{chunk-IM5RFEW4.js → chunk-TIMS3PB7.js} +2 -2
  28. package/dist/cli/index.js +4 -4
  29. package/dist/{config-S2BDMT4B.js → config-32X53LWY.js} +3 -3
  30. package/dist/{expand-TONFVT5D.js → expand-VFYOWNZS.js} +4 -4
  31. package/dist/{file-vectors-VREL75MA.js → file-vectors-UBOKGYXX.js} +3 -2
  32. package/dist/{index-facts-MBJKBYA3.js → index-facts-ZTUANWBF.js} +6 -6
  33. package/dist/{index-pass-RP23G2CR.js → index-pass-S6NC25P5.js} +5 -5
  34. package/dist/index.js +8 -7
  35. package/dist/{install-MCJIJMFF.js → install-ZTE3H7QM.js} +7 -6
  36. package/dist/{lean-CBHVW6JM.js → lean-5N6BOONS.js} +4 -3
  37. package/dist/{local-TH5XCLVJ.js → local-5EYLEBEP.js} +4 -2
  38. package/dist/{local-llm-GZREB5DF.js → local-llm-ESMVP5LE.js} +3 -3
  39. package/dist/{main-QF6Z552J.js → main-B3HZCGVE.js} +45 -31
  40. package/dist/main-B3HZCGVE.js.map +1 -0
  41. package/dist/{memory-LSZKSF53.js → memory-CI7HIGHN.js} +4 -4
  42. package/dist/{operations-BPI633VN.js → operations-MW52HIQ3.js} +3 -3
  43. package/dist/{rerank-EPCTSEVP.js → rerank-O4S5BC45.js} +4 -4
  44. package/dist/{runtime-W5ACTDFS.js → runtime-SZAWIXEB.js} +5 -4
  45. package/dist/{summaries-W2J5FCLG.js → summaries-GCHZGIIA.js} +10 -8
  46. package/dist/{zvec-LSA2IRAU.js → zvec-V34P24WF.js} +10 -5
  47. package/package.json +1 -1
  48. package/dist/chunk-4DTO2E4Y.js.map +0 -1
  49. package/dist/chunk-6OQKL35B.js.map +0 -1
  50. package/dist/chunk-7JPUMXFE.js.map +0 -1
  51. package/dist/chunk-BNZDEU5K.js.map +0 -1
  52. package/dist/chunk-K4XNTYV7.js.map +0 -1
  53. package/dist/chunk-NNJSAYHY.js.map +0 -1
  54. package/dist/chunk-OO7Q7XZF.js.map +0 -1
  55. package/dist/chunk-RKF4HBBQ.js.map +0 -1
  56. package/dist/chunk-VF7DKRIU.js.map +0 -1
  57. package/dist/main-QF6Z552J.js.map +0 -1
  58. /package/dist/{chunk-AFXWCSQ4.js.map → chunk-3TZNQJTH.js.map} +0 -0
  59. /package/dist/{chunk-RGIVHKBH.js.map → chunk-4TKKLQLH.js.map} +0 -0
  60. /package/dist/{chunk-MNSMW4MZ.js.map → chunk-7IXPARDM.js.map} +0 -0
  61. /package/dist/{chunk-GNV4X67F.js.map → chunk-KGDRGA2L.js.map} +0 -0
  62. /package/dist/{chunk-PZ4EBFIA.js.map → chunk-SPQU2422.js.map} +0 -0
  63. /package/dist/{chunk-IM5RFEW4.js.map → chunk-TIMS3PB7.js.map} +0 -0
  64. /package/dist/{config-S2BDMT4B.js.map → config-32X53LWY.js.map} +0 -0
  65. /package/dist/{expand-TONFVT5D.js.map → expand-VFYOWNZS.js.map} +0 -0
  66. /package/dist/{file-vectors-VREL75MA.js.map → file-vectors-UBOKGYXX.js.map} +0 -0
  67. /package/dist/{index-facts-MBJKBYA3.js.map → index-facts-ZTUANWBF.js.map} +0 -0
  68. /package/dist/{index-pass-RP23G2CR.js.map → index-pass-S6NC25P5.js.map} +0 -0
  69. /package/dist/{install-MCJIJMFF.js.map → install-ZTE3H7QM.js.map} +0 -0
  70. /package/dist/{lean-CBHVW6JM.js.map → lean-5N6BOONS.js.map} +0 -0
  71. /package/dist/{local-TH5XCLVJ.js.map → local-5EYLEBEP.js.map} +0 -0
  72. /package/dist/{local-llm-GZREB5DF.js.map → local-llm-ESMVP5LE.js.map} +0 -0
  73. /package/dist/{memory-LSZKSF53.js.map → memory-CI7HIGHN.js.map} +0 -0
  74. /package/dist/{operations-BPI633VN.js.map → operations-MW52HIQ3.js.map} +0 -0
  75. /package/dist/{rerank-EPCTSEVP.js.map → rerank-O4S5BC45.js.map} +0 -0
  76. /package/dist/{runtime-W5ACTDFS.js.map → runtime-SZAWIXEB.js.map} +0 -0
  77. /package/dist/{summaries-W2J5FCLG.js.map → summaries-GCHZGIIA.js.map} +0 -0
  78. /package/dist/{zvec-LSA2IRAU.js.map → zvec-V34P24WF.js.map} +0 -0
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  withCardLock
3
- } from "./chunk-6OQKL35B.js";
3
+ } from "./chunk-R5JRKMEP.js";
4
4
  import {
5
5
  resolveLlmConfig
6
- } from "./chunk-RGIVHKBH.js";
6
+ } from "./chunk-4TKKLQLH.js";
7
7
  import {
8
8
  loadRuntimeConfig
9
9
  } from "./chunk-Y4QQML24.js";
@@ -14,7 +14,7 @@ async function runIndexModelPass(root, options = {}) {
14
14
  const config = await resolveLlmConfig(root);
15
15
  if (!config.enabled) return { cards: null, facts: null, cardVectors: 0 };
16
16
  const signal = options.signal ? { signal: options.signal } : {};
17
- const { summarizeRepository } = await import("./summaries-W2J5FCLG.js");
17
+ const { summarizeRepository } = await import("./summaries-GCHZGIIA.js");
18
18
  const cards = config.features.summaries ? await summarizeRepository(root, {
19
19
  config,
20
20
  pace: config.provider !== "local",
@@ -23,13 +23,13 @@ async function runIndexModelPass(root, options = {}) {
23
23
  }) : null;
24
24
  if (cards?.stoppedBy === "locked")
25
25
  return { cards: { ...cards, lockedBy: cards.lockedBy ?? null }, facts: null, cardVectors: 0 };
26
- const { extractIndexFacts } = await import("./index-facts-MBJKBYA3.js");
26
+ const { extractIndexFacts } = await import("./index-facts-ZTUANWBF.js");
27
27
  const locked = await withCardLock(root, () => extractIndexFacts(root, { config, ...signal }));
28
28
  const facts = locked.ok ? locked.value : null;
29
29
  let cardVectors = 0;
30
30
  const local = (await loadRuntimeConfig(root, { requireDatabase: false }).catch(() => null))?.provider.type === "local";
31
31
  if (local && cards && cards.summarized > 0 && !options.signal?.aborted) {
32
- const { syncFileVectors } = await import("./file-vectors-VREL75MA.js");
32
+ const { syncFileVectors } = await import("./file-vectors-UBOKGYXX.js");
33
33
  cardVectors = (await syncFileVectors(root, VECTOR_BUDGET_MS))?.cards ?? 0;
34
34
  }
35
35
  return {
@@ -37,7 +37,8 @@ async function runIndexModelPass(root, options = {}) {
37
37
  summarized: cards.summarized,
38
38
  timedOut: cards.timedOut,
39
39
  remaining: cards.remaining,
40
- stoppedBy: cards.stoppedBy
40
+ stoppedBy: cards.stoppedBy,
41
+ ...cards.reason ? { reason: cards.reason } : {}
41
42
  },
42
43
  facts: !facts || facts.stoppedBy === "disabled" ? null : { written: facts.written, removed: facts.removed },
43
44
  cardVectors
@@ -47,4 +48,4 @@ async function runIndexModelPass(root, options = {}) {
47
48
  export {
48
49
  runIndexModelPass
49
50
  };
50
- //# sourceMappingURL=chunk-VF7DKRIU.js.map
51
+ //# sourceMappingURL=chunk-6KSSJJZO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/llm/index-pass.ts"],"sourcesContent":["import { loadRuntimeConfig } from \"../core/config-file.js\";\nimport { withCardLock } from \"./card-lock.js\";\nimport { resolveLlmConfig } from \"./config.js\";\n\nexport type IndexModelPass = {\n cards: {\n summarized: number;\n timedOut: number;\n remaining: number;\n stoppedBy: string;\n lockedBy?: number | null;\n reason?: string;\n } | null;\n facts: { written: number; removed: number } | null;\n cardVectors: number;\n};\n\n/** One file vector budget per pass: card vectors are one short text per file. */\nconst VECTOR_BUDGET_MS = 5 * 60_000;\n\n/**\n * The model-backed part of indexing, always off the request path: intent\n * cards for new or changed source files, index facts for changed sources,\n * then the card vectors. Resumable (everything is cached by content hash) and\n * a no-op returning nulls when no model layer is enabled. One writer per\n * repository: while another process holds the card lock this pass does nothing.\n */\nexport async function runIndexModelPass(\n root: string,\n options: { signal?: AbortSignal; onProgress?: (done: number, total: number) => void } = {},\n): Promise<IndexModelPass> {\n const config = await resolveLlmConfig(root);\n if (!config.enabled) return { cards: null, facts: null, cardVectors: 0 };\n const signal = options.signal ? { signal: options.signal } : {};\n const { summarizeRepository } = await import(\"./summaries.js\");\n const cards = config.features.summaries\n ? await summarizeRepository(root, {\n config,\n pace: config.provider !== \"local\",\n ...signal,\n ...(options.onProgress ? { onProgress: options.onProgress } : {}),\n })\n : null;\n if (cards?.stoppedBy === \"locked\")\n return { cards: { ...cards, lockedBy: cards.lockedBy ?? null }, facts: null, cardVectors: 0 };\n const { extractIndexFacts } = await import(\"./index-facts.js\");\n const locked = await withCardLock(root, () => extractIndexFacts(root, { config, ...signal }));\n const facts = locked.ok ? locked.value : null;\n let cardVectors = 0;\n // Only the local embedder: a hosted one would bill every card.\n const local =\n (await loadRuntimeConfig(root, { requireDatabase: false }).catch(() => null))?.provider.type ===\n \"local\";\n if (local && cards && cards.summarized > 0 && !options.signal?.aborted) {\n const { syncFileVectors } = await import(\"../context/file-vectors.js\");\n cardVectors = (await syncFileVectors(root, VECTOR_BUDGET_MS))?.cards ?? 0;\n }\n return {\n cards: cards && {\n summarized: cards.summarized,\n timedOut: cards.timedOut,\n remaining: cards.remaining,\n stoppedBy: cards.stoppedBy,\n ...(cards.reason ? { reason: cards.reason } : {}),\n },\n facts:\n !facts || facts.stoppedBy === \"disabled\"\n ? null\n : { written: facts.written, removed: facts.removed },\n cardVectors,\n };\n}\n"],"mappings":";;;;;;;;;;;AAkBA,IAAM,mBAAmB,IAAI;AAS7B,eAAsB,kBACpB,MACA,UAAwF,CAAC,GAChE;AACzB,QAAM,SAAS,MAAM,iBAAiB,IAAI;AAC1C,MAAI,CAAC,OAAO,QAAS,QAAO,EAAE,OAAO,MAAM,OAAO,MAAM,aAAa,EAAE;AACvE,QAAM,SAAS,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAC9D,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,yBAAgB;AAC7D,QAAM,QAAQ,OAAO,SAAS,YAC1B,MAAM,oBAAoB,MAAM;AAAA,IAC9B;AAAA,IACA,MAAM,OAAO,aAAa;AAAA,IAC1B,GAAG;AAAA,IACH,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;AAAA,EACjE,CAAC,IACD;AACJ,MAAI,OAAO,cAAc;AACvB,WAAO,EAAE,OAAO,EAAE,GAAG,OAAO,UAAU,MAAM,YAAY,KAAK,GAAG,OAAO,MAAM,aAAa,EAAE;AAC9F,QAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,2BAAkB;AAC7D,QAAM,SAAS,MAAM,aAAa,MAAM,MAAM,kBAAkB,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC;AAC5F,QAAM,QAAQ,OAAO,KAAK,OAAO,QAAQ;AACzC,MAAI,cAAc;AAElB,QAAM,SACH,MAAM,kBAAkB,MAAM,EAAE,iBAAiB,MAAM,CAAC,EAAE,MAAM,MAAM,IAAI,IAAI,SAAS,SACxF;AACF,MAAI,SAAS,SAAS,MAAM,aAAa,KAAK,CAAC,QAAQ,QAAQ,SAAS;AACtE,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,4BAA4B;AACrE,mBAAe,MAAM,gBAAgB,MAAM,gBAAgB,IAAI,SAAS;AAAA,EAC1E;AACA,SAAO;AAAA,IACL,OAAO,SAAS;AAAA,MACd,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM;AAAA,MACjB,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,IACjD;AAAA,IACA,OACE,CAAC,SAAS,MAAM,cAAc,aAC1B,OACA,EAAE,SAAS,MAAM,SAAS,SAAS,MAAM,QAAQ;AAAA,IACvD;AAAA,EACF;AACF;","names":[]}
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  redactLlmKeys
3
- } from "./chunk-RGIVHKBH.js";
3
+ } from "./chunk-4TKKLQLH.js";
4
4
  import {
5
5
  writeAtomic
6
6
  } from "./chunk-CG5B3LYG.js";
7
7
  import {
8
8
  localChatJson
9
- } from "./chunk-NNJSAYHY.js";
9
+ } from "./chunk-SXVH74NE.js";
10
10
 
11
11
  // src/llm/budget.ts
12
12
  import { mkdir, readFile } from "fs/promises";
@@ -224,4 +224,4 @@ export {
224
224
  coolingDown,
225
225
  chatJson
226
226
  };
227
- //# sourceMappingURL=chunk-MNSMW4MZ.js.map
227
+ //# sourceMappingURL=chunk-7IXPARDM.js.map
@@ -13,10 +13,12 @@ import {
13
13
  localModelPath,
14
14
  localRuntimeAvailable,
15
15
  removeLocalModels
16
- } from "./chunk-NNJSAYHY.js";
16
+ } from "./chunk-SXVH74NE.js";
17
17
 
18
18
  // src/cli/commands/local-llm.ts
19
19
  import { spawn } from "child_process";
20
+ import { stat } from "fs/promises";
21
+ import { join } from "path";
20
22
  import { confirm, isCancel, spinner } from "@clack/prompts";
21
23
  import pc from "picocolors";
22
24
  var ok = (text) => `${pc.green("\u2713")} ${text}`;
@@ -118,13 +120,22 @@ async function printLocalLlmStatus(cards) {
118
120
  console.log(row("status", warn("disabled in .codebuddy/config.json")));
119
121
  if (!cards) return;
120
122
  const pass = cards.lastPass;
121
- const detail = cards.writer ? `being written by pid ${cards.writer}` : pass && `last pass: ${pass.summarized} written${pass.timedOut ? `, ${pass.timedOut} timed out` : ""}, ${pass.remaining} left (${pass.stoppedBy})`;
123
+ const detail = cards.writer ? `being written by pid ${cards.writer}` : pass && `last pass: ${pass.summarized} written${pass.timedOut ? `, ${pass.timedOut} timed out` : ""}, ${pass.remaining} left (${pass.reason ? `${pass.stoppedBy}: ${pass.reason}` : pass.stoppedBy})`;
122
124
  console.log(
123
125
  row(
124
126
  "cards",
125
127
  `${cards.carded} / ${cards.eligible} source files${detail ? pc.dim(` (${detail})`) : ""}`
126
128
  )
127
129
  );
130
+ if (cards.carded === 0) return;
131
+ const { cardsDir } = await import("./zvec-V34P24WF.js");
132
+ const built = await stat(join(cardsDir(process.cwd()), ".zvec-grep")).catch(() => null);
133
+ console.log(
134
+ row(
135
+ "card index",
136
+ built ? ok("built (zvec-grep)") : pc.dim("not built (needs zvec-grep; next card pass)")
137
+ )
138
+ );
128
139
  }
129
140
 
130
141
  export {
@@ -133,4 +144,4 @@ export {
133
144
  removeLocalLlm,
134
145
  printLocalLlmStatus
135
146
  };
136
- //# sourceMappingURL=chunk-K4XNTYV7.js.map
147
+ //# sourceMappingURL=chunk-FEFOZ2ZW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/local-llm.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { confirm, isCancel, spinner } from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { readConfigFile, setConfigBlock } from \"../../core/config-file.js\";\nimport {\n checkLocalModelResources,\n downloadLocalModel,\n formatBytes,\n freeMemory,\n installedLocalModel,\n LLAMA_RUNTIME,\n LOCAL_MODELS,\n type LocalModelSize,\n localModelPath,\n localRuntimeAvailable,\n removeLocalModels,\n} from \"../../llm/local.js\";\nimport type { cardProgress } from \"../../llm/summaries.js\";\n\nconst ok = (text: string) => `${pc.green(\"✓\")} ${text}`;\nconst warn = (text: string) => `${pc.yellow(\"!\")} ${text}`;\nconst row = (label: string, value: string) => ` ${label.padEnd(10)} ${value}`;\n\nexport const LOCAL_LLM_SIZES = Object.keys(LOCAL_MODELS) as LocalModelSize[];\n\n/**\n * `codebuddy models add local-llm`: the explicit opt-in for the local small\n * model. Shows the size, checks disk and RAM, installs the node-llama-cpp\n * runtime when missing (after confirmation), downloads the pinned GGUF with a\n * checksum check, and records the size in `.codebuddy/config.json`.\n */\nexport async function addLocalLlm(\n options: { size?: LocalModelSize; yes?: boolean; interactive?: boolean } = {},\n): Promise<boolean> {\n const spec = LOCAL_MODELS[options.size ?? \"default\"];\n const interactive = options.interactive ?? process.stdin.isTTY === true;\n console.log(\n `${pc.bold(\"Local LLM\")} ${spec.id} ${pc.dim(`(${formatBytes(spec.bytes)} download, ~${formatBytes(spec.ramBytes)} RAM while carding)`)}`,\n );\n const resources = await checkLocalModelResources(spec);\n if (!resources.ok) {\n console.log(warn(resources.reason));\n return false;\n }\n if (freeMemory() < spec.ramBytes) {\n console.log(warn(`only ${formatBytes(freeMemory())} RAM free right now; carding may be slow`));\n }\n if (!(await localRuntimeAvailable())) {\n const install = options.yes\n ? true\n : interactive\n ? await confirm({\n message: `Install the local runtime (${LLAMA_RUNTIME}, ~20–35 MB prebuilt) with npm -g?`,\n initialValue: true,\n })\n : false;\n if (isCancel(install) || !install) {\n console.log(warn(`Runtime missing. Install it with: npm install -g ${LLAMA_RUNTIME}`));\n return false;\n }\n const code = await new Promise<number>((resolve) => {\n const child = spawn(\"npm\", [\"install\", \"-g\", LLAMA_RUNTIME], { stdio: \"inherit\" });\n child.on(\"error\", () => resolve(1));\n child.on(\"close\", (exit) => resolve(exit ?? 1));\n });\n if (code !== 0 || !(await localRuntimeAvailable())) {\n console.log(warn(`Runtime install failed. Run: npm install -g ${LLAMA_RUNTIME}`));\n return false;\n }\n }\n if ((await installedLocalModel(spec.size))?.id !== spec.id) {\n const progress = spinner();\n progress.start(`Downloading ${spec.file}`);\n let shown = 0;\n try {\n await downloadLocalModel(spec, (received, total) => {\n const percent = Math.floor((received / total) * 100);\n if (percent !== shown) {\n shown = percent;\n progress.message(`Downloading ${spec.file} ${percent}%`);\n }\n });\n progress.stop(ok(`${spec.id} verified ${pc.dim(localModelPath(spec))}`));\n } catch (error) {\n progress.stop(warn((error as Error).message));\n return false;\n }\n } else {\n console.log(ok(`${spec.id} already installed`));\n }\n const llm = (await readConfigFile()).llm;\n // A BYOK block stays as it is (the local model is then unused until it is switched).\n if (llm === undefined || llm.provider === \"local\" || llm.enabled === false) {\n await setConfigBlock(\"llm\", { ...llm, enabled: true, provider: \"local\", size: spec.size });\n } else {\n console.log(\n pc.dim(\n ` .codebuddy/config.json uses ${llm.provider}; set llm.provider to \"local\" to use it.`,\n ),\n );\n }\n console.log(\n pc.dim(\n \" Cards are written in the background by `codebuddy serve` or `codebuddy index --cards`.\",\n ),\n );\n return true;\n}\n\nexport async function removeLocalLlm(): Promise<void> {\n const removed = await removeLocalModels();\n const llm = (await readConfigFile()).llm;\n if (llm?.provider === \"local\") await setConfigBlock(\"llm\", { ...llm, enabled: false });\n console.log(\n removed.length > 0\n ? ok(`removed ${removed.join(\", \")}; CodeBuddy falls back to 2.14 behaviour`)\n : pc.dim(\"no local LLM installed\"),\n );\n}\n\nexport async function printLocalLlmStatus(\n cards: Awaited<ReturnType<typeof cardProgress>> | null,\n): Promise<void> {\n const llm = (await readConfigFile()).llm;\n const installed = await installedLocalModel(llm?.size);\n console.log(pc.bold(\"Local LLM\"));\n if (!installed) {\n console.log(row(\"model\", pc.dim(\"not installed (optional: codebuddy models add local-llm)\")));\n return;\n }\n const runtime = await localRuntimeAvailable();\n console.log(row(\"model\", ok(`${installed.id} ${pc.dim(formatBytes(installed.bytes))}`)));\n console.log(\n row(\"runtime\", runtime ? ok(LLAMA_RUNTIME) : warn(`missing: npm install -g ${LLAMA_RUNTIME}`)),\n );\n if (llm?.enabled === false)\n console.log(row(\"status\", warn(\"disabled in .codebuddy/config.json\")));\n if (!cards) return;\n const pass = cards.lastPass;\n const detail = cards.writer\n ? `being written by pid ${cards.writer}`\n : pass &&\n `last pass: ${pass.summarized} written${pass.timedOut ? `, ${pass.timedOut} timed out` : \"\"}, ${pass.remaining} left (${pass.reason ? `${pass.stoppedBy}: ${pass.reason}` : pass.stoppedBy})`;\n console.log(\n row(\n \"cards\",\n `${cards.carded} / ${cards.eligible} source files${detail ? pc.dim(` (${detail})`) : \"\"}`,\n ),\n );\n if (cards.carded === 0) return;\n const { cardsDir } = await import(\"../../retrieval/zvec.js\");\n const built = await stat(join(cardsDir(process.cwd()), \".zvec-grep\")).catch(() => null);\n console.log(\n row(\n \"card index\",\n built ? ok(\"built (zvec-grep)\") : pc.dim(\"not built (needs zvec-grep; next card pass)\"),\n ),\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,14 +1,11 @@
1
1
  import {
2
2
  chatJson
3
- } from "./chunk-MNSMW4MZ.js";
3
+ } from "./chunk-7IXPARDM.js";
4
4
  import {
5
- cardLockHolder,
6
- withCardLock
7
- } from "./chunk-6OQKL35B.js";
8
- import {
9
- CARDS_DIR,
5
+ LEGACY_CARDS_DIR,
6
+ cardsDir,
10
7
  refreshZvecCardIndex
11
- } from "./chunk-RKF4HBBQ.js";
8
+ } from "./chunk-4LMBZWIF.js";
12
9
  import {
13
10
  CHANGELOG_PATH,
14
11
  NON_SOURCE_PATH,
@@ -16,24 +13,28 @@ import {
16
13
  loadFreshIndex,
17
14
  loadProjectArchitectureMap,
18
15
  readBoundedSource
19
- } from "./chunk-4DTO2E4Y.js";
16
+ } from "./chunk-LZTYPJ5I.js";
17
+ import {
18
+ cardLockHolder,
19
+ withCardLock
20
+ } from "./chunk-R5JRKMEP.js";
20
21
  import {
21
22
  redactSensitive,
22
23
  scanSensitive
23
24
  } from "./chunk-AL6VBTNA.js";
24
25
  import {
25
26
  resolveLlmConfig
26
- } from "./chunk-RGIVHKBH.js";
27
+ } from "./chunk-4TKKLQLH.js";
27
28
  import {
28
29
  writeAtomic
29
30
  } from "./chunk-CG5B3LYG.js";
30
31
  import {
31
- waitForForeground,
32
- warmLocalModel
33
- } from "./chunk-NNJSAYHY.js";
32
+ loadLocalModel,
33
+ waitForForeground
34
+ } from "./chunk-SXVH74NE.js";
34
35
 
35
36
  // src/llm/summaries.ts
36
- import { mkdir, readdir, readFile, rm } from "fs/promises";
37
+ import { mkdir, readdir, readFile, rm, stat } from "fs/promises";
37
38
  import { dirname, join } from "path";
38
39
  import { z } from "zod";
39
40
  var SUMMARY_PROMPT_VERSION = 2;
@@ -199,8 +200,11 @@ async function summarizeLocked(root, config, options) {
199
200
  let index = 0;
200
201
  let stoppedBy = "done";
201
202
  if (pending.length > 0) options.onProgress?.(0, pending.length);
202
- if (local && pending.length > 0 && config.local && !await warmLocalModel(config.local)) {
203
- console.error("[cards] local model failed to load");
203
+ let reason;
204
+ const loadError = local && pending.length > 0 && config.local ? await loadLocalModel(config.local) : null;
205
+ if (loadError) {
206
+ console.error(`[cards] ${loadError}`);
207
+ reason = loadError;
204
208
  index = pending.length;
205
209
  stoppedBy = "error";
206
210
  }
@@ -271,7 +275,7 @@ async function summarizeLocked(root, config, options) {
271
275
  timeoutRun = 0;
272
276
  for (const { entry } of batch) {
273
277
  const answer = result.value.files.find((file) => file.path === entry.path) ?? (batch.length === 1 && result.value.files.length === 1 ? result.value.files[0] : void 0);
274
- if (!answer?.summary.trim()) {
278
+ if (!answer?.summary.trim() || brokenCardText(answer.summary.trim().slice(0, 160))) {
275
279
  skipped++;
276
280
  continue;
277
281
  }
@@ -291,15 +295,34 @@ async function summarizeLocked(root, config, options) {
291
295
  }
292
296
  }
293
297
  await writeStore(root, store);
294
- await pruneCardFiles(root, store, manifest);
295
- if (summarized > 0)
296
- await refreshZvecCardIndex(root, options.signal ? { signal: options.signal } : {});
297
- return { summarized, skipped, timedOut, remaining: pending.length - index, stoppedBy };
298
+ const written = await pruneCardFiles(root, store, manifest);
299
+ if (summarized + written > 0 || !await stat(join(cardsDir(root), ".zvec-grep")).catch(() => null)) {
300
+ const cardIndex = await refreshZvecCardIndex(
301
+ root,
302
+ options.signal ? { signal: options.signal } : {}
303
+ );
304
+ if (cardIndex.status !== "ok" && cardIndex.status !== "disabled")
305
+ console.error(`[cards] ${cardIndex.reason}`);
306
+ }
307
+ return {
308
+ summarized,
309
+ skipped,
310
+ timedOut,
311
+ // A load failure leaves every pending file for the next pass.
312
+ remaining: loadError ? pending.length : pending.length - index,
313
+ stoppedBy,
314
+ ...reason ? { reason } : {}
315
+ };
316
+ }
317
+ function brokenCardText(text) {
318
+ return text.length > 160 || /[{}[\]]{2,}|"\s*:/.test(text) || /[^\P{L}\p{Script=Latin}]/u.test(text);
298
319
  }
299
320
  function toCard(entry, model, answer) {
300
321
  const known = new Set((entry.symbolTable ?? []).map((symbol) => symbol.name));
301
322
  const clean = (items, count, chars) => [
302
- ...new Set(items.map((item) => redactSensitive(item.trim()).slice(0, chars)).filter(Boolean))
323
+ ...new Set(
324
+ items.map((item) => item.trim()).filter((item) => item && !brokenCardText(item)).map((item) => redactSensitive(item).slice(0, chars))
325
+ )
303
326
  ].slice(0, count);
304
327
  return {
305
328
  hash: entry.hash,
@@ -309,7 +332,7 @@ function toCard(entry, model, answer) {
309
332
  symptoms: clean(answer.symptoms, LIMITS.symptoms, LIMITS.symptomChars),
310
333
  terms: clean(answer.terms, LIMITS.terms, LIMITS.termChars),
311
334
  symbols: Object.fromEntries(
312
- answer.symbols.filter((symbol) => known.has(symbol.name)).slice(0, LIMITS.symbols).map((symbol) => [symbol.name, symbol.summary.trim().slice(0, LIMITS.symbolChars)])
335
+ answer.symbols.filter((symbol) => known.has(symbol.name) && !brokenCardText(symbol.summary.trim())).slice(0, LIMITS.symbols).map((symbol) => [symbol.name, symbol.summary.trim().slice(0, LIMITS.symbolChars)])
313
336
  )
314
337
  };
315
338
  }
@@ -317,7 +340,7 @@ async function writeCardFiles(root, store, paths) {
317
340
  for (const path of paths) {
318
341
  const card = store[path];
319
342
  if (!card) continue;
320
- const target = join(root, CARDS_DIR, `${path}.md`);
343
+ const target = join(cardsDir(root), `${path}.md`);
321
344
  await mkdir(dirname(target), { recursive: true });
322
345
  await writeAtomic(
323
346
  target,
@@ -340,7 +363,8 @@ async function writeCardFiles(root, store, paths) {
340
363
  }
341
364
  }
342
365
  async function pruneCardFiles(root, store, manifest) {
343
- const directory = join(root, CARDS_DIR);
366
+ const directory = cardsDir(root);
367
+ await rm(join(root, LEGACY_CARDS_DIR), { recursive: true, force: true });
344
368
  const current = new Set(
345
369
  Object.entries(store).filter(([path, card]) => manifest.entries[path]?.hash === card.hash && card.symptoms).map(([path]) => `${path}.md`)
346
370
  );
@@ -351,11 +375,13 @@ async function pruneCardFiles(root, store, manifest) {
351
375
  await rm(join(directory, name), { force: true });
352
376
  }
353
377
  }
378
+ const missing = [...current].filter((name) => !present.has(name));
354
379
  await writeCardFiles(
355
380
  root,
356
381
  store,
357
- [...current].filter((name) => !present.has(name)).map((name) => name.slice(0, -3))
382
+ missing.map((name) => name.slice(0, -3))
358
383
  );
384
+ return missing.length;
359
385
  }
360
386
  async function cardProgress(root) {
361
387
  const { manifest } = await loadFreshIndex(root);
@@ -408,6 +434,7 @@ export {
408
434
  cardWords,
409
435
  loadLlmSummaries,
410
436
  summarizeRepository,
437
+ brokenCardText,
411
438
  cardProgress
412
439
  };
413
- //# sourceMappingURL=chunk-7JPUMXFE.js.map
440
+ //# sourceMappingURL=chunk-FGJ2KPPN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/llm/summaries.ts"],"sourcesContent":["import { mkdir, readdir, readFile, rm, stat } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { z } from \"zod\";\nimport {\n CHANGELOG_PATH,\n loadProjectArchitectureMap,\n NON_SOURCE_PATH,\n readBoundedSource,\n TEST_PATH,\n} from \"../context/discovery.js\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { redactSensitive, scanSensitive } from \"../core/sensitive.js\";\nimport { loadFreshIndex } from \"../indexer/engine.js\";\nimport type { IndexEntry, IndexManifest } from \"../indexer/types.js\";\nimport { cardsDir, LEGACY_CARDS_DIR, refreshZvecCardIndex } from \"../retrieval/zvec.js\";\nimport { cardLockHolder, withCardLock } from \"./card-lock.js\";\nimport { chatJson } from \"./client.js\";\nimport { type EnabledLlmConfig, resolveLlmConfig } from \"./config.js\";\nimport { loadLocalModel, waitForForeground } from \"./local.js\";\n\n/**\n * Intent cards: for each source file, a plain-English summary, the symptoms a\n * bug report would use, the code terms a search should hit, and one-line\n * descriptions of its main symbols. Written off the prompt path by the local\n * model (`provider: \"local\"`) or the BYOK model (`codebuddy index --cards`,\n * `codebuddy serve`, `watch`, `codebuddy llm summarize`) and cached by content\n * hash. Retrieval uses them three ways: a card vector per file\n * (`file-vectors.ts`), a card workspace in zvec-grep (`cardsDir`), and\n * down-weighted card words in the content index. A card alone never admits a\n * file: card evidence counts when another dense signal agrees (see the\n * agreement rule in `context/discovery.ts`).\n */\nexport const SUMMARY_PROMPT_VERSION = 2;\nconst LIMITS = {\n fileChars: 200,\n symbolChars: 100,\n symptomChars: 80,\n termChars: 40,\n symptoms: 6,\n terms: 10,\n symbols: 8,\n signatures: 12,\n neighbours: 5,\n /** ~600 tokens per file: long-line (minified) files are cut, never sent whole. */\n headChars: 1_600,\n signatureChars: 160,\n batchTokens: 5_000,\n batchFiles: 8,\n outputTokensPerFile: 320,\n /** The local grammar bounds every field, so one card always fits. */\n localOutputTokens: 700,\n tokensPerMinute: 6_000,\n timeoutMs: 20_000,\n /** One file per call for the local model: a 0.6B model loses track of batches. */\n localTimeoutMs: 30_000,\n /** This many local timeouts in a row means the machine is overloaded: stop the pass. */\n localTimeoutRun: 5,\n} as const;\n\nexport type LlmSummary = {\n hash: string;\n model: string;\n version: number;\n summary: string;\n symbols: Record<string, string>;\n /** Card fields (prompt version ≥ 2). */\n symptoms?: string[];\n terms?: string[];\n};\n\n/**\n * Source files that get a card: no tests, docs, config, changelogs, vendored,\n * generated, minified or lock files (secret-bearing ones are skipped when read).\n */\nexport function cardEligible(path: string): boolean {\n return (\n !TEST_PATH.test(path) &&\n !NON_SOURCE_PATH.test(path) &&\n !CHANGELOG_PATH.test(path) &&\n !GENERATED_PATH.test(path)\n );\n}\nconst GENERATED_PATH =\n /(^|\\/)(vendor|vendored|third[_-]?party|node_modules|dist|build|generated|__generated__)\\/|\\.(min|bundle|generated|pb)\\.[a-z]+$|_pb2\\.py$|(^|\\/)[^/]*lock[^/]*$/i;\n\n/** The text embedded as a file's card vector (empty for a summary without card fields). */\nexport function cardText(card: LlmSummary): string {\n return [\n card.summary,\n ...(card.symptoms ?? []),\n ...Object.entries(card.symbols).map(([name, text]) => `${name}: ${text}`),\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\n/** Words the content index adds (down-weighted) for a card: terms and symptoms. */\nexport function cardWords(card: LlmSummary): string {\n return [...(card.terms ?? []), ...(card.symptoms ?? [])].join(\" \");\n}\n\ntype Store = Record<string, LlmSummary>;\nconst storePath = (root: string) => join(root, \".codebuddy\", \"cache\", \"llm-summaries.json\");\n\nasync function readStore(root: string): Promise<Store> {\n try {\n return JSON.parse(await readFile(storePath(root), \"utf8\")) as Store;\n } catch {\n return {};\n }\n}\n\nasync function writeStore(root: string, store: Store): Promise<void> {\n await mkdir(join(root, \".codebuddy\", \"cache\"), { recursive: true });\n await writeAtomic(storePath(root), `${JSON.stringify(store)}\\n`);\n}\n\n/** Summaries whose content hash still matches the current index. */\nexport async function loadLlmSummaries(\n root: string,\n manifest: IndexManifest,\n): Promise<Map<string, LlmSummary>> {\n const store = await readStore(root);\n return new Map(\n Object.entries(store).filter(([path, entry]) => manifest.entries[path]?.hash === entry.hash),\n );\n}\n\nconst outputSchema = z.object({\n files: z.array(\n z.object({\n path: z.string(),\n summary: z.string(),\n symptoms: z.array(z.string()).default([]),\n terms: z.array(z.string()).default([]),\n symbols: z.array(z.object({ name: z.string(), summary: z.string() })).default([]),\n }),\n ),\n});\n\n/**\n * Grammar bounds for the local model: without them a 0.6B model writes\n * paragraph summaries and endless arrays until it hits the token cap and the\n * JSON is cut off. Zod still validates (and trims) every answer.\n */\nconst boundedString = (maxLength: number) => ({ type: \"string\", maxLength });\nconst CARD_GRAMMAR = {\n type: \"object\",\n properties: {\n files: {\n type: \"array\",\n minItems: 1,\n maxItems: 1,\n items: {\n type: \"object\",\n properties: {\n path: boundedString(300),\n summary: boundedString(LIMITS.fileChars),\n symptoms: {\n type: \"array\",\n items: boundedString(LIMITS.symptomChars),\n maxItems: LIMITS.symptoms,\n },\n terms: { type: \"array\", items: boundedString(LIMITS.termChars), maxItems: LIMITS.terms },\n symbols: {\n type: \"array\",\n maxItems: LIMITS.symbols,\n items: {\n type: \"object\",\n properties: { name: boundedString(80), summary: boundedString(LIMITS.symbolChars) },\n },\n },\n },\n },\n },\n },\n};\n\nconst SYSTEM = [\n \"You write search cards for source files so that a plain-English bug report or task finds the right file.\",\n 'Answer with JSON only: {\"files\":[{\"path\":\"...\",\"summary\":\"...\",\"symptoms\":[\"...\"],\"terms\":[\"...\"],\"symbols\":[{\"name\":\"...\",\"summary\":\"...\"}]}]}.',\n \"For each input file:\",\n `summary: one plain-English sentence (max ${LIMITS.fileChars} chars) saying what the file does;`,\n `symptoms: 3 to ${LIMITS.symptoms} short phrases a user or bug report would use to describe a problem caused by this file, in everyday words without code identifiers;`,\n `terms: 5 to ${LIMITS.terms} code identifiers and domain words from the file that a search should match;`,\n `symbols: up to ${LIMITS.symbols} main symbols (only names listed in \"signatures\"), each with one short phrase.`,\n \"Symptoms describe what goes wrong for a user of this specific file (a wrong value, an error, nothing happening), never generic phrases.\",\n \"Never invent paths or symbol names.\",\n].join(\" \");\n\nexport type SummarizeResult = {\n summarized: number;\n skipped: number;\n /** Local files that ran past the deadline (counted apart from `skipped`). */\n timedOut: number;\n remaining: number;\n stoppedBy:\n | \"done\"\n | \"limit\"\n | \"budget\"\n | \"cooldown\"\n | \"disabled\"\n | \"aborted\"\n | \"error\"\n | \"timeout\"\n | \"locked\";\n /** Set with `stoppedBy: \"locked\"`: the PID already writing cards (null if unknown). */\n lockedBy?: number | null;\n /** Why the pass stopped early with `stoppedBy: \"error\"`, when known. */\n reason?: string;\n};\n\n/** The last finished pass, for `codebuddy models status`. */\nexport type CardPassRecord = Omit<SummarizeResult, \"lockedBy\"> & { at: string };\nconst passPath = (root: string) => join(root, \".codebuddy\", \"cache\", \"cards-pass.json\");\n\nexport async function summarizeRepository(\n root: string,\n options: {\n limit?: number;\n onProgress?: (done: number, total: number) => void;\n signal?: AbortSignal;\n /** Injected config (tests, CLI); defaults to `resolveLlmConfig`. */\n config?: EnabledLlmConfig;\n /** Wait between batches to stay under the free tier's tokens/minute. */\n pace?: boolean;\n batchFiles?: number;\n /** Per-file deadline for the local model (tests). */\n localTimeoutMs?: number;\n } = {},\n): Promise<SummarizeResult> {\n const resolved = options.config ?? (await resolveLlmConfig(root));\n if (!resolved.enabled || !resolved.features.summaries) {\n return { summarized: 0, skipped: 0, timedOut: 0, remaining: 0, stoppedBy: \"disabled\" };\n }\n const config = resolved;\n const locked = await withCardLock(root, async () => {\n const result = await summarizeLocked(root, config, options);\n const record: CardPassRecord = { ...result, at: new Date().toISOString() };\n await writeAtomic(passPath(root), `${JSON.stringify(record)}\\n`).catch(() => {});\n return result;\n });\n if (locked.ok) return locked.value;\n console.error(`[cards] already being written by pid ${locked.holder ?? \"unknown\"}`);\n return {\n summarized: 0,\n skipped: 0,\n timedOut: 0,\n remaining: 0,\n stoppedBy: \"locked\",\n lockedBy: locked.holder,\n };\n}\n\nasync function summarizeLocked(\n root: string,\n config: EnabledLlmConfig,\n options: NonNullable<Parameters<typeof summarizeRepository>[1]>,\n): Promise<SummarizeResult> {\n const { manifest } = await loadFreshIndex(root);\n const store = await readStore(root);\n for (const path of Object.keys(store)) if (!manifest.entries[path]) delete store[path];\n\n const map = await loadProjectArchitectureMap(root, manifest).catch(() => null);\n const pending = orderByImportance(\n map,\n Object.values(manifest.entries).filter((entry) => {\n if (!cardEligible(entry.path)) return false;\n const done = store[entry.path];\n return !(\n done?.hash === entry.hash &&\n done.model === config.model &&\n done.version === SUMMARY_PROMPT_VERSION\n );\n }),\n );\n const local = config.provider === \"local\";\n const batchFiles = local ? 1 : (options.batchFiles ?? LIMITS.batchFiles);\n const limit = options.limit ?? Number.POSITIVE_INFINITY;\n const localTimeoutMs = options.localTimeoutMs ?? LIMITS.localTimeoutMs;\n let summarized = 0;\n let skipped = 0;\n let timedOut = 0;\n let timeoutRun = 0;\n let index = 0;\n let stoppedBy: SummarizeResult[\"stoppedBy\"] = \"done\";\n if (pending.length > 0) options.onProgress?.(0, pending.length);\n // Load once, outside the per-file deadline: a cold load under memory\n // pressure must not time out (and skip) the first files.\n let reason: string | undefined;\n const loadError =\n local && pending.length > 0 && config.local ? await loadLocalModel(config.local) : null;\n if (loadError) {\n console.error(`[cards] ${loadError}`);\n reason = loadError;\n index = pending.length;\n stoppedBy = \"error\";\n }\n\n while (index < pending.length) {\n if (options.signal?.aborted) {\n stoppedBy = \"aborted\";\n break;\n }\n if (summarized >= limit) {\n stoppedBy = \"limit\";\n break;\n }\n const batch: Array<{ entry: IndexEntry; input: FileInput }> = [];\n let tokens = 0;\n while (index < pending.length && batch.length < batchFiles) {\n if (summarized + batch.length >= limit) break;\n const entry = pending[index] as IndexEntry;\n const input = await fileInput(root, entry, map);\n if (input === null) {\n skipped++;\n index++;\n continue;\n }\n const cost = Math.ceil(JSON.stringify(input).length / 4);\n if (batch.length > 0 && tokens + cost > LIMITS.batchTokens - 600) break;\n batch.push({ entry, input });\n tokens += cost;\n index++;\n }\n if (batch.length === 0) continue;\n\n // A prompt hook or tool call is running: let it have the machine first.\n if (local) await waitForForeground(root, options.signal);\n const started = Date.now();\n const ask = () =>\n chatJson(config, {\n system: SYSTEM,\n user: JSON.stringify({ files: batch.map(({ input }) => input) }),\n schema: outputSchema,\n jsonSchema: CARD_GRAMMAR,\n maxTokens: local ? LIMITS.localOutputTokens : LIMITS.outputTokensPerFile * batch.length,\n deadlineMs: local ? localTimeoutMs : LIMITS.timeoutMs,\n purpose: \"background\",\n repositoryRoot: root,\n });\n let result = await ask();\n // Invalid output gets one retry, then the file is skipped.\n if (!result.ok && result.reason === \"invalid\") result = await ask();\n if (!result.ok && local && result.reason === \"timeout\") {\n // One slow file is not an outage, but a run of them is an overloaded\n // machine: say so and stop instead of burning hours writing nothing.\n timedOut += batch.length;\n timeoutRun++;\n for (const { entry } of batch)\n console.error(\n `[cards] ${entry.path} timed out after ${Math.round(localTimeoutMs / 1000)} s`,\n );\n options.onProgress?.(index, pending.length);\n if (timeoutRun >= LIMITS.localTimeoutRun) {\n stoppedBy = \"timeout\";\n break;\n }\n continue;\n }\n if (!result.ok) {\n // A batch the provider rejects outright (bad output, or e.g. 413 for one\n // file) is skipped, not retried forever; limits and outages stop the run.\n if (result.reason === \"invalid\" || (result.reason === \"http\" && batch.length === 1)) {\n skipped += batch.length;\n continue;\n }\n index -= batch.length;\n stoppedBy =\n result.reason === \"budget\"\n ? \"budget\"\n : result.reason === \"cooldown\" || result.reason === \"rate-limited\"\n ? \"cooldown\"\n : \"error\";\n break;\n }\n timeoutRun = 0;\n for (const { entry } of batch) {\n // A one-file batch (the local model) may echo the path imperfectly.\n const answer =\n result.value.files.find((file) => file.path === entry.path) ??\n (batch.length === 1 && result.value.files.length === 1 ? result.value.files[0] : undefined);\n // A broken summary is no card (the file is retried by the next pass).\n if (!answer?.summary.trim() || brokenCardText(answer.summary.trim().slice(0, 160))) {\n skipped++;\n continue;\n }\n store[entry.path] = toCard(entry, config.model, answer);\n summarized++;\n }\n await writeStore(root, store);\n await writeCardFiles(\n root,\n store,\n batch.map(({ entry }) => entry.path),\n );\n options.onProgress?.(index, pending.length);\n if (options.pace !== false) {\n const wait = (result.tokens / LIMITS.tokensPerMinute) * 60_000 - (Date.now() - started);\n if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));\n }\n }\n await writeStore(root, store);\n const written = await pruneCardFiles(root, store, manifest);\n // Also after a migration or a missing card index: the cards exist, the index may not.\n if (\n summarized + written > 0 ||\n !(await stat(join(cardsDir(root), \".zvec-grep\")).catch(() => null))\n ) {\n const cardIndex = await refreshZvecCardIndex(\n root,\n options.signal ? { signal: options.signal } : {},\n );\n if (cardIndex.status !== \"ok\" && cardIndex.status !== \"disabled\")\n console.error(`[cards] ${cardIndex.reason}`);\n }\n return {\n summarized,\n skipped,\n timedOut,\n // A load failure leaves every pending file for the next pass.\n remaining: loadError ? pending.length : pending.length - index,\n stoppedBy,\n ...(reason ? { reason } : {}),\n };\n}\n\ntype CardAnswer = z.infer<typeof outputSchema>[\"files\"][number];\n\n/** Bounded, de-duplicated card fields; symbols only from the file's symbol table. */\n/**\n * Text a small model broke rather than wrote: JSON punctuation runs (`]}]}`),\n * letters outside the Latin script (reasoning leaking in another language),\n * or a run-on string far past any field's bound. Such strings are dropped.\n */\nexport function brokenCardText(text: string): boolean {\n return (\n text.length > 160 || /[{}[\\]]{2,}|\"\\s*:/.test(text) || /[^\\P{L}\\p{Script=Latin}]/u.test(text)\n );\n}\n\nfunction toCard(entry: IndexEntry, model: string, answer: CardAnswer): LlmSummary {\n const known = new Set((entry.symbolTable ?? []).map((symbol) => symbol.name));\n const clean = (items: string[], count: number, chars: number) =>\n [\n ...new Set(\n items\n .map((item) => item.trim())\n .filter((item) => item && !brokenCardText(item))\n .map((item) => redactSensitive(item).slice(0, chars)),\n ),\n ].slice(0, count);\n return {\n hash: entry.hash,\n model,\n version: SUMMARY_PROMPT_VERSION,\n summary: redactSensitive(answer.summary.trim()).slice(0, LIMITS.fileChars),\n symptoms: clean(answer.symptoms, LIMITS.symptoms, LIMITS.symptomChars),\n terms: clean(answer.terms, LIMITS.terms, LIMITS.termChars),\n symbols: Object.fromEntries(\n answer.symbols\n .filter((symbol) => known.has(symbol.name) && !brokenCardText(symbol.summary.trim()))\n .slice(0, LIMITS.symbols)\n .map((symbol) => [symbol.name, symbol.summary.trim().slice(0, LIMITS.symbolChars)]),\n ),\n };\n}\n\n/** One Markdown card per source file for the zvec-grep card workspace. */\nasync function writeCardFiles(root: string, store: Store, paths: string[]): Promise<void> {\n for (const path of paths) {\n const card = store[path];\n if (!card) continue;\n const target = join(cardsDir(root), `${path}.md`);\n await mkdir(dirname(target), { recursive: true });\n await writeAtomic(\n target,\n [\n \"---\",\n `source: ${JSON.stringify(path)}`,\n `hash: ${card.hash}`,\n \"---\",\n \"\",\n `# ${path}`,\n \"\",\n card.summary,\n \"\",\n ...(card.symptoms?.length ? [\"Symptoms:\", ...card.symptoms.map((s) => `- ${s}`), \"\"] : []),\n ...(card.terms?.length ? [`Terms: ${card.terms.join(\", \")}`, \"\"] : []),\n ...Object.entries(card.symbols).map(([name, text]) => `- ${name}: ${text}`),\n \"\",\n ].join(\"\\n\"),\n );\n }\n}\n\n/** Write missing cards and delete cards whose source is gone or no longer current. */\n/** Returns how many missing card files it wrote. */\nasync function pruneCardFiles(\n root: string,\n store: Store,\n manifest: IndexManifest,\n): Promise<number> {\n const directory = cardsDir(root);\n // Pre-2.15.2 in-repo cards: their zg index never worked (see `cardsDir`).\n await rm(join(root, LEGACY_CARDS_DIR), { recursive: true, force: true });\n const current = new Set(\n Object.entries(store)\n .filter(([path, card]) => manifest.entries[path]?.hash === card.hash && card.symptoms)\n .map(([path]) => `${path}.md`),\n );\n const existing = await readdir(directory, { recursive: true }).catch(() => [] as string[]);\n const present = new Set(existing.map((name) => name.split(\"\\\\\").join(\"/\")));\n for (const name of present) {\n if (name.endsWith(\".md\") && !name.startsWith(\".zvec-grep\") && !current.has(name)) {\n await rm(join(directory, name), { force: true });\n }\n }\n const missing = [...current].filter((name) => !present.has(name));\n await writeCardFiles(\n root,\n store,\n missing.map((name) => name.slice(0, -3)),\n );\n return missing.length;\n}\n\n/**\n * Card coverage for `stats`/`storage`/`models`: current cards vs eligible\n * source files, the live writer's PID and the last finished pass.\n */\nexport async function cardProgress(root: string): Promise<{\n carded: number;\n eligible: number;\n writer: number | null;\n lastPass: CardPassRecord | null;\n}> {\n const { manifest } = await loadFreshIndex(root);\n const store = await readStore(root);\n const eligible = Object.keys(manifest.entries).filter(cardEligible);\n const lastPass = await readFile(passPath(root), \"utf8\")\n .then((text) => JSON.parse(text) as CardPassRecord)\n .catch(() => null);\n return {\n writer: await cardLockHolder(root),\n lastPass,\n carded: eligible.filter(\n (path) =>\n store[path]?.hash === manifest.entries[path]?.hash &&\n store[path]?.version === SUMMARY_PROMPT_VERSION,\n ).length,\n eligible: eligible.length,\n };\n}\n\ntype FileInput = {\n path: string;\n language: string;\n signatures: string[];\n /** Files this one imports and files that import it (paths only). */\n neighbours: string[];\n head: string;\n};\n\ntype ImportMap = Awaited<ReturnType<typeof loadProjectArchitectureMap>> | null;\n\n/** Import lines say little about what a file does; the card reads past them. */\nconst IMPORT_LINE =\n /^\\s*(import\\b|from\\s+\\S+\\s+import\\b|export\\s+(\\*|\\{[^}]*\\})\\s+from\\b|(const|let|var)\\s+\\S+\\s*=\\s*require\\()|^\\s*$/;\n\n/** What is sent for one file; null for unreadable or secret-bearing files. */\nasync function fileInput(\n root: string,\n entry: IndexEntry,\n map: ImportMap,\n): Promise<FileInput | null> {\n const source = await readBoundedSource(root, entry.path);\n if (source === null || scanSensitive(source).sensitive) return null;\n const edges = map?.edges ?? [];\n return {\n path: entry.path,\n language: entry.language,\n // Not the deterministic summary: small models copy its \"ts • 80 lines\" form.\n signatures: (entry.symbolTable ?? [])\n .filter((symbol) => symbol.kind !== \"heading\")\n .slice(0, LIMITS.signatures)\n .map((symbol) => redactSensitive(symbol.signature.slice(0, LIMITS.signatureChars))),\n neighbours: [\n ...new Set([\n ...edges.filter((edge) => edge.from === entry.path).map((edge) => edge.to),\n ...edges.filter((edge) => edge.to === entry.path).map((edge) => edge.from),\n ]),\n ].slice(0, LIMITS.neighbours),\n // The module docstring and first definitions, never the whole file.\n head: redactSensitive(\n source\n .split(/\\r?\\n/)\n .filter((line) => !IMPORT_LINE.test(line))\n .join(\"\\n\")\n .slice(0, LIMITS.headChars),\n ),\n };\n}\n\n/** Most-imported source first. */\nfunction orderByImportance(map: ImportMap, entries: IndexEntry[]): IndexEntry[] {\n const fanIn = new Map<string, number>();\n for (const edge of map?.edges ?? []) fanIn.set(edge.to, (fanIn.get(edge.to) ?? 0) + 1);\n return [...entries].sort(\n (left, right) =>\n (fanIn.get(right.path) ?? 0) - (fanIn.get(left.path) ?? 0) ||\n left.path.localeCompare(right.path),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,SAAS,UAAU,IAAI,YAAY;AACnD,SAAS,SAAS,YAAY;AAC9B,SAAS,SAAS;AA8BX,IAAM,yBAAyB;AACtC,IAAM,SAAS;AAAA,EACb,WAAW;AAAA,EACX,aAAa;AAAA,EACb,cAAc;AAAA,EACd,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AAAA,EACP,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,YAAY;AAAA;AAAA,EAEZ,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,qBAAqB;AAAA;AAAA,EAErB,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,WAAW;AAAA;AAAA,EAEX,gBAAgB;AAAA;AAAA,EAEhB,iBAAiB;AACnB;AAiBO,SAAS,aAAa,MAAuB;AAClD,SACE,CAAC,UAAU,KAAK,IAAI,KACpB,CAAC,gBAAgB,KAAK,IAAI,KAC1B,CAAC,eAAe,KAAK,IAAI,KACzB,CAAC,eAAe,KAAK,IAAI;AAE7B;AACA,IAAM,iBACJ;AAGK,SAAS,SAAS,MAA0B;AACjD,SAAO;AAAA,IACL,KAAK;AAAA,IACL,GAAI,KAAK,YAAY,CAAC;AAAA,IACtB,GAAG,OAAO,QAAQ,KAAK,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,GAAG,IAAI,KAAK,IAAI,EAAE;AAAA,EAC1E,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AACd;AAGO,SAAS,UAAU,MAA0B;AAClD,SAAO,CAAC,GAAI,KAAK,SAAS,CAAC,GAAI,GAAI,KAAK,YAAY,CAAC,CAAE,EAAE,KAAK,GAAG;AACnE;AAGA,IAAM,YAAY,CAAC,SAAiB,KAAK,MAAM,cAAc,SAAS,oBAAoB;AAE1F,eAAe,UAAU,MAA8B;AACrD,MAAI;AACF,WAAO,KAAK,MAAM,MAAM,SAAS,UAAU,IAAI,GAAG,MAAM,CAAC;AAAA,EAC3D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,WAAW,MAAc,OAA6B;AACnE,QAAM,MAAM,KAAK,MAAM,cAAc,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AAClE,QAAM,YAAY,UAAU,IAAI,GAAG,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI;AACjE;AAGA,eAAsB,iBACpB,MACA,UACkC;AAClC,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,SAAO,IAAI;AAAA,IACT,OAAO,QAAQ,KAAK,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,MAAM,SAAS,QAAQ,IAAI,GAAG,SAAS,MAAM,IAAI;AAAA,EAC7F;AACF;AAEA,IAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,OAAO,EAAE;AAAA,IACP,EAAE,OAAO;AAAA,MACP,MAAM,EAAE,OAAO;AAAA,MACf,SAAS,EAAE,OAAO;AAAA,MAClB,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,MACxC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,MACrC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,IAClF,CAAC;AAAA,EACH;AACF,CAAC;AAOD,IAAM,gBAAgB,CAAC,eAAuB,EAAE,MAAM,UAAU,UAAU;AAC1E,IAAM,eAAe;AAAA,EACnB,MAAM;AAAA,EACN,YAAY;AAAA,IACV,OAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,MACV,UAAU;AAAA,MACV,OAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY;AAAA,UACV,MAAM,cAAc,GAAG;AAAA,UACvB,SAAS,cAAc,OAAO,SAAS;AAAA,UACvC,UAAU;AAAA,YACR,MAAM;AAAA,YACN,OAAO,cAAc,OAAO,YAAY;AAAA,YACxC,UAAU,OAAO;AAAA,UACnB;AAAA,UACA,OAAO,EAAE,MAAM,SAAS,OAAO,cAAc,OAAO,SAAS,GAAG,UAAU,OAAO,MAAM;AAAA,UACvF,SAAS;AAAA,YACP,MAAM;AAAA,YACN,UAAU,OAAO;AAAA,YACjB,OAAO;AAAA,cACL,MAAM;AAAA,cACN,YAAY,EAAE,MAAM,cAAc,EAAE,GAAG,SAAS,cAAc,OAAO,WAAW,EAAE;AAAA,YACpF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,SAAS;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,4CAA4C,OAAO,SAAS;AAAA,EAC5D,kBAAkB,OAAO,QAAQ;AAAA,EACjC,eAAe,OAAO,KAAK;AAAA,EAC3B,kBAAkB,OAAO,OAAO;AAAA,EAChC;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AA0BV,IAAM,WAAW,CAAC,SAAiB,KAAK,MAAM,cAAc,SAAS,iBAAiB;AAEtF,eAAsB,oBACpB,MACA,UAWI,CAAC,GACqB;AAC1B,QAAM,WAAW,QAAQ,UAAW,MAAM,iBAAiB,IAAI;AAC/D,MAAI,CAAC,SAAS,WAAW,CAAC,SAAS,SAAS,WAAW;AACrD,WAAO,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,WAAW;AAAA,EACvF;AACA,QAAM,SAAS;AACf,QAAM,SAAS,MAAM,aAAa,MAAM,YAAY;AAClD,UAAM,SAAS,MAAM,gBAAgB,MAAM,QAAQ,OAAO;AAC1D,UAAM,SAAyB,EAAE,GAAG,QAAQ,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE;AACzE,UAAM,YAAY,SAAS,IAAI,GAAG,GAAG,KAAK,UAAU,MAAM,CAAC;AAAA,CAAI,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC/E,WAAO;AAAA,EACT,CAAC;AACD,MAAI,OAAO,GAAI,QAAO,OAAO;AAC7B,UAAQ,MAAM,wCAAwC,OAAO,UAAU,SAAS,EAAE;AAClF,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,UAAU;AAAA,IACV,WAAW;AAAA,IACX,WAAW;AAAA,IACX,UAAU,OAAO;AAAA,EACnB;AACF;AAEA,eAAe,gBACb,MACA,QACA,SAC0B;AAC1B,QAAM,EAAE,SAAS,IAAI,MAAM,eAAe,IAAI;AAC9C,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,aAAW,QAAQ,OAAO,KAAK,KAAK,EAAG,KAAI,CAAC,SAAS,QAAQ,IAAI,EAAG,QAAO,MAAM,IAAI;AAErF,QAAM,MAAM,MAAM,2BAA2B,MAAM,QAAQ,EAAE,MAAM,MAAM,IAAI;AAC7E,QAAM,UAAU;AAAA,IACd;AAAA,IACA,OAAO,OAAO,SAAS,OAAO,EAAE,OAAO,CAAC,UAAU;AAChD,UAAI,CAAC,aAAa,MAAM,IAAI,EAAG,QAAO;AACtC,YAAM,OAAO,MAAM,MAAM,IAAI;AAC7B,aAAO,EACL,MAAM,SAAS,MAAM,QACrB,KAAK,UAAU,OAAO,SACtB,KAAK,YAAY;AAAA,IAErB,CAAC;AAAA,EACH;AACA,QAAM,QAAQ,OAAO,aAAa;AAClC,QAAM,aAAa,QAAQ,IAAK,QAAQ,cAAc,OAAO;AAC7D,QAAM,QAAQ,QAAQ,SAAS,OAAO;AACtC,QAAM,iBAAiB,QAAQ,kBAAkB,OAAO;AACxD,MAAI,aAAa;AACjB,MAAI,UAAU;AACd,MAAI,WAAW;AACf,MAAI,aAAa;AACjB,MAAI,QAAQ;AACZ,MAAI,YAA0C;AAC9C,MAAI,QAAQ,SAAS,EAAG,SAAQ,aAAa,GAAG,QAAQ,MAAM;AAG9D,MAAI;AACJ,QAAM,YACJ,SAAS,QAAQ,SAAS,KAAK,OAAO,QAAQ,MAAM,eAAe,OAAO,KAAK,IAAI;AACrF,MAAI,WAAW;AACb,YAAQ,MAAM,WAAW,SAAS,EAAE;AACpC,aAAS;AACT,YAAQ,QAAQ;AAChB,gBAAY;AAAA,EACd;AAEA,SAAO,QAAQ,QAAQ,QAAQ;AAC7B,QAAI,QAAQ,QAAQ,SAAS;AAC3B,kBAAY;AACZ;AAAA,IACF;AACA,QAAI,cAAc,OAAO;AACvB,kBAAY;AACZ;AAAA,IACF;AACA,UAAM,QAAwD,CAAC;AAC/D,QAAI,SAAS;AACb,WAAO,QAAQ,QAAQ,UAAU,MAAM,SAAS,YAAY;AAC1D,UAAI,aAAa,MAAM,UAAU,MAAO;AACxC,YAAM,QAAQ,QAAQ,KAAK;AAC3B,YAAM,QAAQ,MAAM,UAAU,MAAM,OAAO,GAAG;AAC9C,UAAI,UAAU,MAAM;AAClB;AACA;AACA;AAAA,MACF;AACA,YAAM,OAAO,KAAK,KAAK,KAAK,UAAU,KAAK,EAAE,SAAS,CAAC;AACvD,UAAI,MAAM,SAAS,KAAK,SAAS,OAAO,OAAO,cAAc,IAAK;AAClE,YAAM,KAAK,EAAE,OAAO,MAAM,CAAC;AAC3B,gBAAU;AACV;AAAA,IACF;AACA,QAAI,MAAM,WAAW,EAAG;AAGxB,QAAI,MAAO,OAAM,kBAAkB,MAAM,QAAQ,MAAM;AACvD,UAAM,UAAU,KAAK,IAAI;AACzB,UAAM,MAAM,MACV,SAAS,QAAQ;AAAA,MACf,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO,MAAM,IAAI,CAAC,EAAE,MAAM,MAAM,KAAK,EAAE,CAAC;AAAA,MAC/D,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,WAAW,QAAQ,OAAO,oBAAoB,OAAO,sBAAsB,MAAM;AAAA,MACjF,YAAY,QAAQ,iBAAiB,OAAO;AAAA,MAC5C,SAAS;AAAA,MACT,gBAAgB;AAAA,IAClB,CAAC;AACH,QAAI,SAAS,MAAM,IAAI;AAEvB,QAAI,CAAC,OAAO,MAAM,OAAO,WAAW,UAAW,UAAS,MAAM,IAAI;AAClE,QAAI,CAAC,OAAO,MAAM,SAAS,OAAO,WAAW,WAAW;AAGtD,kBAAY,MAAM;AAClB;AACA,iBAAW,EAAE,MAAM,KAAK;AACtB,gBAAQ;AAAA,UACN,WAAW,MAAM,IAAI,oBAAoB,KAAK,MAAM,iBAAiB,GAAI,CAAC;AAAA,QAC5E;AACF,cAAQ,aAAa,OAAO,QAAQ,MAAM;AAC1C,UAAI,cAAc,OAAO,iBAAiB;AACxC,oBAAY;AACZ;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI,CAAC,OAAO,IAAI;AAGd,UAAI,OAAO,WAAW,aAAc,OAAO,WAAW,UAAU,MAAM,WAAW,GAAI;AACnF,mBAAW,MAAM;AACjB;AAAA,MACF;AACA,eAAS,MAAM;AACf,kBACE,OAAO,WAAW,WACd,WACA,OAAO,WAAW,cAAc,OAAO,WAAW,iBAChD,aACA;AACR;AAAA,IACF;AACA,iBAAa;AACb,eAAW,EAAE,MAAM,KAAK,OAAO;AAE7B,YAAM,SACJ,OAAO,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,MAAM,IAAI,MACzD,MAAM,WAAW,KAAK,OAAO,MAAM,MAAM,WAAW,IAAI,OAAO,MAAM,MAAM,CAAC,IAAI;AAEnF,UAAI,CAAC,QAAQ,QAAQ,KAAK,KAAK,eAAe,OAAO,QAAQ,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,GAAG;AAClF;AACA;AAAA,MACF;AACA,YAAM,MAAM,IAAI,IAAI,OAAO,OAAO,OAAO,OAAO,MAAM;AACtD;AAAA,IACF;AACA,UAAM,WAAW,MAAM,KAAK;AAC5B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,MAAM,IAAI,CAAC,EAAE,MAAM,MAAM,MAAM,IAAI;AAAA,IACrC;AACA,YAAQ,aAAa,OAAO,QAAQ,MAAM;AAC1C,QAAI,QAAQ,SAAS,OAAO;AAC1B,YAAM,OAAQ,OAAO,SAAS,OAAO,kBAAmB,OAAU,KAAK,IAAI,IAAI;AAC/E,UAAI,OAAO,EAAG,OAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,IAAI,CAAC;AAAA,IACxE;AAAA,EACF;AACA,QAAM,WAAW,MAAM,KAAK;AAC5B,QAAM,UAAU,MAAM,eAAe,MAAM,OAAO,QAAQ;AAE1D,MACE,aAAa,UAAU,KACvB,CAAE,MAAM,KAAK,KAAK,SAAS,IAAI,GAAG,YAAY,CAAC,EAAE,MAAM,MAAM,IAAI,GACjE;AACA,UAAM,YAAY,MAAM;AAAA,MACtB;AAAA,MACA,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACjD;AACA,QAAI,UAAU,WAAW,QAAQ,UAAU,WAAW;AACpD,cAAQ,MAAM,WAAW,UAAU,MAAM,EAAE;AAAA,EAC/C;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA,WAAW,YAAY,QAAQ,SAAS,QAAQ,SAAS;AAAA,IACzD;AAAA,IACA,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,EAC7B;AACF;AAUO,SAAS,eAAe,MAAuB;AACpD,SACE,KAAK,SAAS,OAAO,oBAAoB,KAAK,IAAI,KAAK,4BAA4B,KAAK,IAAI;AAEhG;AAEA,SAAS,OAAO,OAAmB,OAAe,QAAgC;AAChF,QAAM,QAAQ,IAAI,KAAK,MAAM,eAAe,CAAC,GAAG,IAAI,CAAC,WAAW,OAAO,IAAI,CAAC;AAC5E,QAAM,QAAQ,CAAC,OAAiB,OAAe,UAC7C;AAAA,IACE,GAAG,IAAI;AAAA,MACL,MACG,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,QAAQ,CAAC,eAAe,IAAI,CAAC,EAC9C,IAAI,CAAC,SAAS,gBAAgB,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;AAAA,IACxD;AAAA,EACF,EAAE,MAAM,GAAG,KAAK;AAClB,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ;AAAA,IACA,SAAS;AAAA,IACT,SAAS,gBAAgB,OAAO,QAAQ,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,SAAS;AAAA,IACzE,UAAU,MAAM,OAAO,UAAU,OAAO,UAAU,OAAO,YAAY;AAAA,IACrE,OAAO,MAAM,OAAO,OAAO,OAAO,OAAO,OAAO,SAAS;AAAA,IACzD,SAAS,OAAO;AAAA,MACd,OAAO,QACJ,OAAO,CAAC,WAAW,MAAM,IAAI,OAAO,IAAI,KAAK,CAAC,eAAe,OAAO,QAAQ,KAAK,CAAC,CAAC,EACnF,MAAM,GAAG,OAAO,OAAO,EACvB,IAAI,CAAC,WAAW,CAAC,OAAO,MAAM,OAAO,QAAQ,KAAK,EAAE,MAAM,GAAG,OAAO,WAAW,CAAC,CAAC;AAAA,IACtF;AAAA,EACF;AACF;AAGA,eAAe,eAAe,MAAc,OAAc,OAAgC;AACxF,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,MAAM,IAAI;AACvB,QAAI,CAAC,KAAM;AACX,UAAM,SAAS,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,KAAK;AAChD,UAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE;AAAA,QACA,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,QAC/B,SAAS,KAAK,IAAI;AAAA,QAClB;AAAA,QACA;AAAA,QACA,KAAK,IAAI;AAAA,QACT;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA,GAAI,KAAK,UAAU,SAAS,CAAC,aAAa,GAAG,KAAK,SAAS,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC;AAAA,QACxF,GAAI,KAAK,OAAO,SAAS,CAAC,UAAU,KAAK,MAAM,KAAK,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;AAAA,QACpE,GAAG,OAAO,QAAQ,KAAK,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,KAAK,IAAI,KAAK,IAAI,EAAE;AAAA,QAC1E;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAIA,eAAe,eACb,MACA,OACA,UACiB;AACjB,QAAM,YAAY,SAAS,IAAI;AAE/B,QAAM,GAAG,KAAK,MAAM,gBAAgB,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACvE,QAAM,UAAU,IAAI;AAAA,IAClB,OAAO,QAAQ,KAAK,EACjB,OAAO,CAAC,CAAC,MAAM,IAAI,MAAM,SAAS,QAAQ,IAAI,GAAG,SAAS,KAAK,QAAQ,KAAK,QAAQ,EACpF,IAAI,CAAC,CAAC,IAAI,MAAM,GAAG,IAAI,KAAK;AAAA,EACjC;AACA,QAAM,WAAW,MAAM,QAAQ,WAAW,EAAE,WAAW,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,CAAa;AACzF,QAAM,UAAU,IAAI,IAAI,SAAS,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC;AAC1E,aAAW,QAAQ,SAAS;AAC1B,QAAI,KAAK,SAAS,KAAK,KAAK,CAAC,KAAK,WAAW,YAAY,KAAK,CAAC,QAAQ,IAAI,IAAI,GAAG;AAChF,YAAM,GAAG,KAAK,WAAW,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,IACjD;AAAA,EACF;AACA,QAAM,UAAU,CAAC,GAAG,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,QAAQ,IAAI,IAAI,CAAC;AAChE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,QAAQ,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,EAAE,CAAC;AAAA,EACzC;AACA,SAAO,QAAQ;AACjB;AAMA,eAAsB,aAAa,MAKhC;AACD,QAAM,EAAE,SAAS,IAAI,MAAM,eAAe,IAAI;AAC9C,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,WAAW,OAAO,KAAK,SAAS,OAAO,EAAE,OAAO,YAAY;AAClE,QAAM,WAAW,MAAM,SAAS,SAAS,IAAI,GAAG,MAAM,EACnD,KAAK,CAAC,SAAS,KAAK,MAAM,IAAI,CAAmB,EACjD,MAAM,MAAM,IAAI;AACnB,SAAO;AAAA,IACL,QAAQ,MAAM,eAAe,IAAI;AAAA,IACjC;AAAA,IACA,QAAQ,SAAS;AAAA,MACf,CAAC,SACC,MAAM,IAAI,GAAG,SAAS,SAAS,QAAQ,IAAI,GAAG,QAC9C,MAAM,IAAI,GAAG,YAAY;AAAA,IAC7B,EAAE;AAAA,IACF,UAAU,SAAS;AAAA,EACrB;AACF;AAcA,IAAM,cACJ;AAGF,eAAe,UACb,MACA,OACA,KAC2B;AAC3B,QAAM,SAAS,MAAM,kBAAkB,MAAM,MAAM,IAAI;AACvD,MAAI,WAAW,QAAQ,cAAc,MAAM,EAAE,UAAW,QAAO;AAC/D,QAAM,QAAQ,KAAK,SAAS,CAAC;AAC7B,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM;AAAA;AAAA,IAEhB,aAAa,MAAM,eAAe,CAAC,GAChC,OAAO,CAAC,WAAW,OAAO,SAAS,SAAS,EAC5C,MAAM,GAAG,OAAO,UAAU,EAC1B,IAAI,CAAC,WAAW,gBAAgB,OAAO,UAAU,MAAM,GAAG,OAAO,cAAc,CAAC,CAAC;AAAA,IACpF,YAAY;AAAA,MACV,GAAG,oBAAI,IAAI;AAAA,QACT,GAAG,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,MAAM,IAAI,EAAE,IAAI,CAAC,SAAS,KAAK,EAAE;AAAA,QACzE,GAAG,MAAM,OAAO,CAAC,SAAS,KAAK,OAAO,MAAM,IAAI,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI;AAAA,MAC3E,CAAC;AAAA,IACH,EAAE,MAAM,GAAG,OAAO,UAAU;AAAA;AAAA,IAE5B,MAAM;AAAA,MACJ,OACG,MAAM,OAAO,EACb,OAAO,CAAC,SAAS,CAAC,YAAY,KAAK,IAAI,CAAC,EACxC,KAAK,IAAI,EACT,MAAM,GAAG,OAAO,SAAS;AAAA,IAC9B;AAAA,EACF;AACF;AAGA,SAAS,kBAAkB,KAAgB,SAAqC;AAC9E,QAAM,QAAQ,oBAAI,IAAoB;AACtC,aAAW,QAAQ,KAAK,SAAS,CAAC,EAAG,OAAM,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,EAAE,KAAK,KAAK,CAAC;AACrF,SAAO,CAAC,GAAG,OAAO,EAAE;AAAA,IAClB,CAAC,MAAM,WACJ,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM,MAAM,IAAI,KAAK,IAAI,KAAK,MACxD,KAAK,KAAK,cAAc,MAAM,IAAI;AAAA,EACtC;AACF;","names":[]}
@@ -11,7 +11,7 @@ import {
11
11
  rankSemanticReuse,
12
12
  readPolicyFile,
13
13
  resolveRiskPaths
14
- } from "./chunk-OO7Q7XZF.js";
14
+ } from "./chunk-5ZLNHYJH.js";
15
15
  import {
16
16
  readTraces,
17
17
  recordTrace
@@ -25,7 +25,7 @@ import {
25
25
  import {
26
26
  ensureZvecServer,
27
27
  refreshZvecIndex
28
- } from "./chunk-RKF4HBBQ.js";
28
+ } from "./chunk-4LMBZWIF.js";
29
29
  import {
30
30
  NON_SOURCE_PATH,
31
31
  TEST_PATH,
@@ -37,16 +37,16 @@ import {
37
37
  neighbours,
38
38
  queryMap,
39
39
  taskTerms
40
- } from "./chunk-4DTO2E4Y.js";
40
+ } from "./chunk-LZTYPJ5I.js";
41
41
  import {
42
42
  codeBuddyConfigSchema,
43
43
  createRuntime,
44
44
  listFactsPage
45
- } from "./chunk-IM5RFEW4.js";
45
+ } from "./chunk-TIMS3PB7.js";
46
46
  import {
47
47
  MemoryFileStore,
48
48
  VERSION
49
- } from "./chunk-VBDM7HC5.js";
49
+ } from "./chunk-3L5IR6OW.js";
50
50
  import {
51
51
  redactPersisted
52
52
  } from "./chunk-AL6VBTNA.js";
@@ -58,7 +58,7 @@ import {
58
58
  } from "./chunk-Y4QQML24.js";
59
59
  import {
60
60
  markForeground
61
- } from "./chunk-NNJSAYHY.js";
61
+ } from "./chunk-SXVH74NE.js";
62
62
 
63
63
  // src/core/migrate-to-files.ts
64
64
  import { createHash } from "crypto";
@@ -311,7 +311,7 @@ async function traceCliCommand(command, started, root = process.cwd()) {
311
311
  }
312
312
  async function installedClients(repositoryRoot) {
313
313
  const { CLIENT_IDS } = await import("./adapters-OJRSVFV3.js");
314
- const { detectClient } = await import("./install-MCJIJMFF.js");
314
+ const { detectClient } = await import("./install-ZTE3H7QM.js");
315
315
  const found = [];
316
316
  for (const id of CLIENT_IDS) {
317
317
  const status = await detectClient(id, repositoryRoot).catch(() => null);
@@ -1626,7 +1626,7 @@ function startProjectSync(options) {
1626
1626
  if (result.status !== "disabled") logger.info(`[sync] ${result.reason}`);
1627
1627
  });
1628
1628
  const modelPass = backgroundRefresh(async () => {
1629
- const { runIndexModelPass } = await import("./index-pass-RP23G2CR.js");
1629
+ const { runIndexModelPass } = await import("./index-pass-S6NC25P5.js");
1630
1630
  const result = await runIndexModelPass(repositoryRoot, { signal: controller.signal }).catch(
1631
1631
  (error) => {
1632
1632
  logger.error(`[sync] index model pass failed: ${error.message}`);
@@ -1725,7 +1725,7 @@ function startProjectSync(options) {
1725
1725
  await Promise.all([graphRefresh.settled(), zvecRefresh.settled(), modelPass.settled()]);
1726
1726
  await stopZvecServer().catch(() => {
1727
1727
  });
1728
- await import("./local-TH5XCLVJ.js").then(({ unloadLocalModel }) => unloadLocalModel()).catch(() => {
1728
+ await import("./local-5EYLEBEP.js").then(({ unloadLocalModel }) => unloadLocalModel()).catch(() => {
1729
1729
  });
1730
1730
  }
1731
1731
  };
@@ -2016,4 +2016,4 @@ export {
2016
2016
  registerCodeBuddyTools,
2017
2017
  startMcpServer
2018
2018
  };
2019
- //# sourceMappingURL=chunk-GNV4X67F.js.map
2019
+ //# sourceMappingURL=chunk-KGDRGA2L.js.map
@@ -3,6 +3,9 @@ import {
3
3
  extractSymbolTable,
4
4
  supportsSymbols
5
5
  } from "./chunk-Y7D7PUBE.js";
6
+ import {
7
+ withCardLock
8
+ } from "./chunk-R5JRKMEP.js";
6
9
  import {
7
10
  isCredentialPath
8
11
  } from "./chunk-AL6VBTNA.js";
@@ -1268,7 +1271,7 @@ async function withLlmSummaries(repositoryRoot, manifest) {
1268
1271
  const entries = Object.values(manifest.entries);
1269
1272
  try {
1270
1273
  if ((await readConfigFile(repositoryRoot)).llm?.enabled === false) return entries;
1271
- const { loadLlmSummaries, cardWords } = await import("./summaries-W2J5FCLG.js");
1274
+ const { loadLlmSummaries, cardWords } = await import("./summaries-GCHZGIIA.js");
1272
1275
  const summaries = await loadLlmSummaries(repositoryRoot, manifest);
1273
1276
  if (summaries.size === 0) return entries;
1274
1277
  return entries.map((entry) => {
@@ -1288,7 +1291,7 @@ async function withExpansion(repositoryRoot, task, ranked, manifest, mapSource,
1288
1291
  if (ranked.paths.length > 0 && ranked.confidence !== "low") return null;
1289
1292
  if (identifierTokens(task).some((token) => CODE_IDENTIFIER.test(token))) return null;
1290
1293
  const deadlineAt = options.expansionDeadlineAt ?? Date.now() + EXPANSION_MS.pack;
1291
- const { expandTask } = await import("./expand-TONFVT5D.js");
1294
+ const { expandTask } = await import("./expand-VFYOWNZS.js");
1292
1295
  const expansion = await expandTask(repositoryRoot, task, deadlineAt - Date.now());
1293
1296
  if (!expansion) return null;
1294
1297
  const expanded = await rankTaskFiles(
@@ -1303,7 +1306,7 @@ async function withExpansion(repositoryRoot, task, ranked, manifest, mapSource,
1303
1306
  return { ...ranked, explain: [...ranked.explain, `${note}; still no match`] };
1304
1307
  return { ...expanded, explain: [...ranked.explain, note, ...expanded.explain] };
1305
1308
  }
1306
- var AGREEMENT = { topK: 5, chunkFloor: 0.8, cardFloor: 0.75 };
1309
+ var AGREEMENT = { topK: 5, chunkFloor: 0.82, cardFloor: 0.82 };
1307
1310
  function agreementVotes(path, signals, config = AGREEMENT) {
1308
1311
  const names = [];
1309
1312
  let scored = 0;
@@ -1336,6 +1339,9 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1336
1339
  (token) => CODE_IDENTIFIER.test(token) && !ABBREVIATION.test(token)
1337
1340
  );
1338
1341
  if (identifiers.length > 0) {
1342
+ const evidence2 = new Map(
1343
+ scoreTaskPaths(entries, task).filter((entry) => entry.score > 0).map((entry) => [entry.path, entry.score])
1344
+ );
1339
1345
  const exact = entries.filter(
1340
1346
  (entry) => (wantsTests || !TEST_PATH.test(entry.path)) && countsAsSource(entry.path, task) && !EXCEPTION_MODULE.test(entry.path)
1341
1347
  ).map((entry) => ({
@@ -1348,11 +1354,10 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1348
1354
  )
1349
1355
  )
1350
1356
  ).size
1351
- })).filter((entry) => entry.named > 0).sort((left, right) => right.named - left.named || left.path.localeCompare(right.path)).map((entry) => entry.path);
1357
+ })).filter((entry) => entry.named > 0).sort(
1358
+ (left, right) => right.named - left.named || (evidence2.get(right.path) ?? 0) - (evidence2.get(left.path) ?? 0) || left.path.localeCompare(right.path)
1359
+ ).map((entry) => entry.path);
1352
1360
  if (exact.length > 0) {
1353
- const evidence2 = new Map(
1354
- scoreTaskPaths(entries, task).filter((entry) => entry.score > 0).map((entry) => [entry.path, entry.score])
1355
- );
1356
1361
  const expansion2 = expandAlongImports(exact, await mapSource, evidence2, task, {
1357
1362
  allowGraphOnly: true
1358
1363
  });
@@ -1388,7 +1393,7 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1388
1393
  reason: "semantic retrieval off",
1389
1394
  stale: 0,
1390
1395
  changed: 0
1391
- } : await import("./zvec-LSA2IRAU.js").then(
1396
+ } : await import("./zvec-V34P24WF.js").then(
1392
1397
  ({ zvecCandidates }) => zvecCandidates(
1393
1398
  repositoryRoot,
1394
1399
  task,
@@ -1498,7 +1503,7 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1498
1503
  `abstention: ${weakKeyword} weak keyword candidate(s) below the evidence floor (score < ${EVIDENCE_FLOOR.keywordScore}) dropped`
1499
1504
  );
1500
1505
  }
1501
- const vectors = reranked || !chunks ? null : cards ? fuseRankings([chunks, cards].map((ranking) => ranking.map((match) => match.path))) : chunks.map((match) => match.path);
1506
+ const vectors = reranked || !chunks ? null : cards ? fuseChunksAndCards(chunks, cards) : chunks.map((match) => match.path);
1502
1507
  if (vectors && vectors.length > 0) {
1503
1508
  const doc = wantsDocs ? ranked.filter((path) => DOC_PATH.test(path)).slice(0, 1) : [];
1504
1509
  ranked = [...doc, ...vectors].slice(0, TASK_FILES);
@@ -1544,7 +1549,8 @@ function confidenceOf(paths, input) {
1544
1549
  if (!input.denseAvailable) return "medium";
1545
1550
  let best = 0;
1546
1551
  for (const path of paths) {
1547
- const dense = input.votes.get(path)?.votes ?? 0;
1552
+ const vote = input.votes.get(path);
1553
+ const dense = vote && vote.scored > 0 ? vote.votes : 0;
1548
1554
  const lexical = input.strongKeyword.includes(path) ? 2 : input.keyword.includes(path) ? 1 : 0;
1549
1555
  best = Math.max(best, dense + lexical);
1550
1556
  }
@@ -1592,7 +1598,7 @@ var MIN_RERANK_MS = 200;
1592
1598
  async function llmRerank(input) {
1593
1599
  let config;
1594
1600
  try {
1595
- config = await (await import("./config-S2BDMT4B.js")).resolveLlmConfig(input.repositoryRoot);
1601
+ config = await (await import("./config-32X53LWY.js")).resolveLlmConfig(input.repositoryRoot);
1596
1602
  } catch {
1597
1603
  return null;
1598
1604
  }
@@ -1617,7 +1623,7 @@ async function llmRerank(input) {
1617
1623
  input.deadlineAt === void 0 ? config.timeoutMs : input.deadlineAt - Date.now()
1618
1624
  );
1619
1625
  if (deadlineMs < MIN_RERANK_MS) return skipped("no time left in the hook budget");
1620
- const { rerankCandidates } = await import("./rerank-EPCTSEVP.js");
1626
+ const { rerankCandidates } = await import("./rerank-O4S5BC45.js");
1621
1627
  const result = await rerankCandidates(
1622
1628
  input.repositoryRoot,
1623
1629
  config,
@@ -1750,6 +1756,15 @@ function rankEvidenceCandidates(lexical, admittedKeywords, vector, partial) {
1750
1756
  const established = fuseRankings([lexical, vector]).filter((path) => admitted.has(path));
1751
1757
  return [.../* @__PURE__ */ new Set([...established, ...partial])];
1752
1758
  }
1759
+ function fuseChunksAndCards(chunks, cards, k = 60) {
1760
+ const chunkRank = new Map(chunks.map((match, index) => [match.path, index + 1]));
1761
+ const cardRank = new Map(cards.map((match, index) => [match.path, index + 1]));
1762
+ const rrf = (rank) => rank === void 0 ? 0 : 1 / (k + rank);
1763
+ return [.../* @__PURE__ */ new Set([...chunkRank.keys(), ...cardRank.keys()])].map((path) => {
1764
+ const chunk2 = chunkRank.get(path);
1765
+ return { path, score: rrf(chunk2) + rrf(cardRank.get(path) ?? chunk2) };
1766
+ }).sort((left, right) => right.score - left.score || left.path.localeCompare(right.path)).map((entry) => entry.path);
1767
+ }
1753
1768
  function fuseRankings(lists, k = 60) {
1754
1769
  const scores = /* @__PURE__ */ new Map();
1755
1770
  for (const list of lists) {
@@ -1779,7 +1794,7 @@ async function hasMatchedPassageEvidence(repositoryRoot, path, hits, terms, symb
1779
1794
  );
1780
1795
  });
1781
1796
  }
1782
- var TEST_PATH = /(^|\/)(__tests__|__fixtures__|fixtures?|test|tests)\/|\.(test|spec)\.[a-z]+$|(^|\/)(test_[^/]*|[^/]*_test|conftest)\.py$/;
1797
+ var TEST_PATH = /(^|\/)(__tests__|__fixtures__|fixtures?|test|tests|test_apps?|testdata)\/|\.(test|spec)\.[a-z]+$|(^|\/)(test_[^/]*|[^/]*_test|conftest)\.py$/;
1783
1798
  var CHANGELOG_PATH = new RegExp(
1784
1799
  [
1785
1800
  "(?:CHANGES|CHANGELOG|HISTORY|NEWS|RELEASES?|RELEASE[-_]?NOTES)(?:[._-][^/]*)?",
@@ -2033,7 +2048,7 @@ async function refreshFileVectors(repositoryRoot, manifest, deadlineAt, embed =
2033
2048
  }
2034
2049
  async function currentCardTexts(repositoryRoot, manifest) {
2035
2050
  try {
2036
- const { cardText, loadLlmSummaries } = await import("./summaries-W2J5FCLG.js");
2051
+ const { cardText, loadLlmSummaries } = await import("./summaries-GCHZGIIA.js");
2037
2052
  const texts = /* @__PURE__ */ new Map();
2038
2053
  for (const [path, card] of await loadLlmSummaries(repositoryRoot, manifest)) {
2039
2054
  if (card.symptoms) texts.set(path, `${path}
@@ -2050,8 +2065,11 @@ function textHash(text) {
2050
2065
  async function syncFileVectors(repositoryRoot, budgetMs) {
2051
2066
  const started = Date.now();
2052
2067
  try {
2053
- const { manifest } = await loadFreshIndex(repositoryRoot);
2054
- return await refreshFileVectors(repositoryRoot, manifest, started + budgetMs);
2068
+ const locked = await withCardLock(repositoryRoot, async () => {
2069
+ const { manifest } = await loadFreshIndex(repositoryRoot);
2070
+ return refreshFileVectors(repositoryRoot, manifest, started + budgetMs);
2071
+ });
2072
+ return locked.ok ? locked.value : { embedded: 0, files: 0, pending: 0, lockedBy: locked.holder };
2055
2073
  } catch {
2056
2074
  return null;
2057
2075
  }
@@ -2159,4 +2177,4 @@ export {
2159
2177
  namedSegments,
2160
2178
  loadProjectArchitectureMap
2161
2179
  };
2162
- //# sourceMappingURL=chunk-4DTO2E4Y.js.map
2180
+ //# sourceMappingURL=chunk-LZTYPJ5I.js.map