@orquentra/codebuddy 2.8.0 → 2.14.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 (73) hide show
  1. package/CLAUDE_CODEX.md +33 -0
  2. package/README.md +27 -3
  3. package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
  4. package/dist/{chunk-GKNYMNFM.js → chunk-4DKYKDHI.js} +6 -1
  5. package/dist/chunk-4DKYKDHI.js.map +1 -0
  6. package/dist/{chunk-6KGYAFG7.js → chunk-7QMHIJBA.js} +30 -3
  7. package/dist/chunk-7QMHIJBA.js.map +1 -0
  8. package/dist/{chunk-6NZEZJYD.js → chunk-ESC4Y3L7.js} +3 -5
  9. package/dist/{chunk-6NZEZJYD.js.map → chunk-ESC4Y3L7.js.map} +1 -1
  10. package/dist/chunk-GQKWUHMA.js +20 -0
  11. package/dist/chunk-GQKWUHMA.js.map +1 -0
  12. package/dist/{chunk-J4T6HHI4.js → chunk-L6BRYZ6G.js} +537 -316
  13. package/dist/chunk-L6BRYZ6G.js.map +1 -0
  14. package/dist/{chunk-UK6XTS2Y.js → chunk-LRQNPYCK.js} +13 -19
  15. package/dist/chunk-LRQNPYCK.js.map +1 -0
  16. package/dist/{chunk-GXOML4UZ.js → chunk-LW75SKZR.js} +2 -2
  17. package/dist/{chunk-EJQVGFH2.js → chunk-UOTKHSQ5.js} +26 -9
  18. package/dist/chunk-UOTKHSQ5.js.map +1 -0
  19. package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
  20. package/dist/chunk-V7PT6ER5.js.map +1 -0
  21. package/dist/{chunk-ZNLSCV3J.js → chunk-XSNW6Y3X.js} +1253 -315
  22. package/dist/chunk-XSNW6Y3X.js.map +1 -0
  23. package/dist/{chunk-6DONJW3W.js → chunk-YJPCS2GP.js} +274 -69
  24. package/dist/chunk-YJPCS2GP.js.map +1 -0
  25. package/dist/{chunk-XL2HLACE.js → chunk-YN7TEIUH.js} +1 -1
  26. package/dist/chunk-YN7TEIUH.js.map +1 -0
  27. package/dist/{chunk-TLWGZVPC.js → chunk-YNSTIMCF.js} +746 -66
  28. package/dist/chunk-YNSTIMCF.js.map +1 -0
  29. package/dist/{chunk-NY2YXP3R.js → chunk-YWDH6DQ6.js} +70 -4
  30. package/dist/chunk-YWDH6DQ6.js.map +1 -0
  31. package/dist/cli/index.js +23 -9
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  34. package/dist/index.d.ts +6 -4
  35. package/dist/index.js +9 -9
  36. package/dist/install-EU35ERK5.js +30 -0
  37. package/dist/langgraph/index.d.ts +1 -1
  38. package/dist/lean-43ADYTBW.js +28 -0
  39. package/dist/{main-QUASSFP7.js → main-SFC5LV4E.js} +1333 -437
  40. package/dist/main-SFC5LV4E.js.map +1 -0
  41. package/dist/{operations-WJR7AZZF.js → operations-6Z6ZDVBP.js} +3 -3
  42. package/dist/{pretool-command-JTKX6G3M.js → pretool-command-B5GY5T6E.js} +3 -3
  43. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  44. package/dist/{runtime-OQQ22VJF.js → runtime-W5AW32SD.js} +15 -7
  45. package/dist/{summaries-ML53R7EL.js → summaries-32PHOB6L.js} +3 -4
  46. package/dist/{zvec-XDZNZ2OL.js → zvec-24HLAFGN.js} +3 -4
  47. package/dist/zvec-24HLAFGN.js.map +1 -0
  48. package/package.json +11 -2
  49. package/dist/chunk-6DONJW3W.js.map +0 -1
  50. package/dist/chunk-6KGYAFG7.js.map +0 -1
  51. package/dist/chunk-EJQVGFH2.js.map +0 -1
  52. package/dist/chunk-GKNYMNFM.js.map +0 -1
  53. package/dist/chunk-ITQO632W.js +0 -673
  54. package/dist/chunk-ITQO632W.js.map +0 -1
  55. package/dist/chunk-J4T6HHI4.js.map +0 -1
  56. package/dist/chunk-NY2YXP3R.js.map +0 -1
  57. package/dist/chunk-TLWGZVPC.js.map +0 -1
  58. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  59. package/dist/chunk-W7GAXL6C.js.map +0 -1
  60. package/dist/chunk-XL2HLACE.js.map +0 -1
  61. package/dist/chunk-ZNLSCV3J.js.map +0 -1
  62. package/dist/install-BXM56QCD.js +0 -19
  63. package/dist/lean-command-7DFW32MA.js +0 -19
  64. package/dist/main-QUASSFP7.js.map +0 -1
  65. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  66. /package/dist/{chunk-GXOML4UZ.js.map → chunk-LW75SKZR.js.map} +0 -0
  67. /package/dist/{install-BXM56QCD.js.map → install-EU35ERK5.js.map} +0 -0
  68. /package/dist/{lean-command-7DFW32MA.js.map → lean-43ADYTBW.js.map} +0 -0
  69. /package/dist/{operations-WJR7AZZF.js.map → operations-6Z6ZDVBP.js.map} +0 -0
  70. /package/dist/{pretool-command-JTKX6G3M.js.map → pretool-command-B5GY5T6E.js.map} +0 -0
  71. /package/dist/{runtime-OQQ22VJF.js.map → prompt-gate-TDTFFQW2.js.map} +0 -0
  72. /package/dist/{summaries-ML53R7EL.js.map → runtime-W5AW32SD.js.map} +0 -0
  73. /package/dist/{zvec-XDZNZ2OL.js.map → summaries-32PHOB6L.js.map} +0 -0
package/CLAUDE_CODEX.md CHANGED
@@ -343,6 +343,28 @@ CODEBUDDY_PROJECT_ROOT = "/absolute/path/to/your-project"
343
343
 
344
344
  Restart Codex after installing or changing an MCP entry.
345
345
 
346
+ Native project hooks are available in Codex 0.169.0. To retain task checkpoints
347
+ with minimal prompt injection:
348
+
349
+ ```bash
350
+ codebuddy clients install codex --mode lean --capture
351
+ ```
352
+
353
+ Review the commands in Codex `/hooks` and approve them through Codex's trust UI.
354
+ CodeBuddy writes `.codex/hooks.json`, preserves unrelated hooks, and never marks
355
+ its own commands trusted. Without that review, automatic recovery is not active.
356
+ Visible prompts, edits, explicit command outcomes and final answers are journaled;
357
+ private reasoning and unstable rollout formats are not required. Compaction and
358
+ startup/resume can restore unfinished task state. Host-payload tests pass; a live
359
+ agent A/B has not yet verified correctness, token savings or cost savings.
360
+ `codebuddy clients verify codex` checks installation, not trust approval.
361
+
362
+ OpenCode and Hermes capture-enabled plugins use the same checkpoint bridge.
363
+ Reinstall existing plugins to obtain it; capability reporting distinguishes older
364
+ plugins. Disable capture with `--no-capture`; this also disables checkpoint capture
365
+ and recovery. Hermes rehydrates its ephemeral prompt context from visible events.
366
+
367
+
346
368
  ## Useful CLI Commands
347
369
 
348
370
  List namespaces:
@@ -433,3 +455,14 @@ codebuddy doctor --skip-model-check
433
455
  ```
434
456
 
435
457
  Database and MCP setup can still be valid even when live Hugging Face diagnostics are temporarily unavailable.
458
+
459
+ ## Read guard modes
460
+
461
+ Large unranged Claude `Read` calls are initially bounded to 200 lines, with an
462
+ explicit continuation hint. Any explicit offset or limit passes unchanged; request
463
+ the full line count when the whole file is needed. No permission approval is
464
+ emitted. `CODEBUDDY_TOOL_GUARD=strict` restores the legacy outline-and-deny mode;
465
+ `CODEBUDDY_TOOL_GUARD=0` disables the guard. The default leaves Bash commands
466
+ unchanged and caps content-mode `Grep` calls only. Shell `grep` output is not
467
+ silently capped because pipelines and exit status can be part of correctness.
468
+ Live-host turn and token effects remain unmeasured.
package/README.md CHANGED
@@ -33,12 +33,35 @@ Claude Desktop.
33
33
  - **Task context:** relevant files, symbols, code excerpts, nearby dependencies,
34
34
  and covering tests within a token budget.
35
35
  - **Project memory:** decisions, rules, and handoffs stored as readable Markdown,
36
- recalled on every prompt by hybrid lexical + vector search
36
+ recalled for relevant tasks by hybrid lexical + vector search
37
37
  ([how it works](docs/semantic-memory.md)).
38
+ - **Continuity:** a local journal of each session's goal, edits, checks and
39
+ findings, re-injected after compaction and offered to the next session when
40
+ a task was left unfinished. Native bridges cover Claude Code, Codex, OpenCode
41
+ and Hermes; live-host recovery and agent savings are not yet measured.
42
+ - **Capture controls:** Claude Code, Codex, OpenCode and Hermes can keep turn capture
43
+ enabled with lean prompt injection (`clients install <client> --mode lean --capture`).
44
+ Codex project hooks require review in `/hooks`; installation does not bypass trust.
38
45
  - **Change guidance:** plans, policies, incident history, and risk signals.
46
+ - **Reuse hints:** exported helpers matched lexically, with a conservative local
47
+ semantic fallback after indexing. A five-query smoke test admitted 2/5 correct
48
+ suggestions and withheld three; neither of two unrelated queries was admitted.
49
+ This is small-sample evidence, not an agent savings result.
39
50
  - **Optional retrieval:** zvec semantic search, Graphify architecture maps, and
40
51
  a bring-your-own LLM layer.
41
52
 
53
+ ## What it costs
54
+
55
+ Context is not free: tool schemas, the rules block and the injected context are
56
+ re-read on every turn. A 2026-11 A/B on 23 real Litestar bug fixes (Claude Code,
57
+ Sonnet) found CodeBuddy 2.9 cost a median 22% more per task than stock Claude
58
+ Code with no change in pass rate or exploration. 2.10 cuts the fixed overhead
59
+ from about 4,250 to about 400 tokens per session; that it now breaks even or
60
+ saves is not yet measured. On small, well-specified tasks expect near-zero
61
+ overhead, not savings; savings need vague tasks, large repositories or
62
+ cross-session memory. Method and pre-registered criteria:
63
+ [docs/benchmarks](docs/benchmarks/).
64
+
42
65
  ## See what it does
43
66
 
44
67
  ```bash
@@ -68,8 +91,9 @@ codebuddy init --non-interactive
68
91
  codebuddy context preview "fix the login flow"
69
92
  ```
70
93
 
71
- Agents use `context_pack` to request task evidence and `context_after_turn` to
72
- capture useful knowledge. Request `evidence: "source"` when a short excerpt
94
+ By default the MCP server lists three tools: `context_pack` for task evidence,
95
+ and `recall` / `remember` for project memory (`CODEBUDDY_TOOL_PROFILE=full`
96
+ lists all of them). Request `evidence: "source"` when a short excerpt
73
97
  needs more detail. Inspect missing or truncated evidence before editing.
74
98
 
75
99
  ## Documentation
@@ -8,7 +8,7 @@ import {
8
8
  parseMode,
9
9
  sharedRulesReaders,
10
10
  supportedModes
11
- } from "./chunk-W7GAXL6C.js";
11
+ } from "./chunk-V7PT6ER5.js";
12
12
  import "./chunk-PZ5AY32C.js";
13
13
  export {
14
14
  AUTOMATION_MODES,
@@ -21,4 +21,4 @@ export {
21
21
  sharedRulesReaders,
22
22
  supportedModes
23
23
  };
24
- //# sourceMappingURL=adapters-S4HHTWJW.js.map
24
+ //# sourceMappingURL=adapters-OJRSVFV3.js.map
@@ -69,6 +69,11 @@ var SessionCapsuleLedger = class {
69
69
  capsules.filter((capsule) => ledger.records[capsule.key]?.hash === digest(capsule.text)).map((capsule) => capsule.key)
70
70
  );
71
71
  }
72
+ /** Whether anything was delivered in this session yet (a later prompt is a follow-up). */
73
+ async delivered(sessionId, now = /* @__PURE__ */ new Date()) {
74
+ const ledger = await this.read(this.path(sessionId), now);
75
+ return Object.keys(ledger.records).length > 0;
76
+ }
72
77
  /**
73
78
  * Record capsules as delivered. Call only after their text actually reached
74
79
  * the client, so capsules that were clipped or failed to write stay unseen
@@ -326,4 +331,4 @@ export {
326
331
  coveringTestCandidates,
327
332
  detectTestCommand
328
333
  };
329
- //# sourceMappingURL=chunk-GKNYMNFM.js.map
334
+ //# sourceMappingURL=chunk-4DKYKDHI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/savings/tokens.ts","../src/context/session-ledger.ts","../src/context/tests.ts"],"sourcesContent":["import { countTokens } from \"../planner/budget.js\";\n\n/**\n * The one place token counts are computed. Strings use the same deterministic\n * cl100k tokenizer as planner budgets; objects are measured by their JSON\n * serialization, which matches how they travel over MCP.\n */\n\nexport function estimateTokens(value: unknown): number {\n if (value === undefined || value === null) return 0;\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\n if (!text) return 0;\n return countTokens(text);\n}\n","import { createHash } from \"node:crypto\";\nimport { mkdir, readdir, readFile, rm, stat } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { generateEntityId } from \"../core/ids.js\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { estimateTokens } from \"../savings/tokens.js\";\n\nconst VERSION = 2;\nconst MAX_CAPSULES = 128;\nconst MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;\nconst MAX_PENDING = 8;\nconst PENDING_MAX_AGE_MS = 24 * 60 * 60 * 1000;\nconst STALE_LOCK_MS = 60_000;\n/** Acknowledged versions kept per section, so switching back to a task reuses them. */\nconst MAX_ACKNOWLEDGED_PER_KEY = 8;\n\ntype LedgerRecord = { hash: string; lastSentAt: string };\n/** A delivery sent to a client that has not yet confirmed receipt. */\ntype PendingDelivery = { createdAt: string; records: Record<string, string> };\n/** One acknowledged version of an `exchange` section: content hash and when it was sent. */\ntype AcknowledgedVersion = { hash: string; sentAt: string };\ntype LedgerFile = {\n version: 2;\n updatedAt: string;\n /** `deliver`/`markSent` capsules (hooks): one current hash per key. */\n records: Record<string, LedgerRecord>;\n pending?: Record<string, PendingDelivery>;\n /** `exchange` sections (MCP): every recently acknowledged version, newest first. */\n acknowledged?: Record<string, AcknowledgedVersion[]>;\n};\n\nexport type ContextCapsule = { key: string; text: string };\nexport type CapsuleDelivery = {\n /** Full text of new or changed capsules, in input order. */\n lines: string[];\n /** Keys of capsules already sent this session and not resent. */\n unchanged: string[];\n full: number;\n referenced: number;\n sentTokens: number;\n avoidedTokens: number;\n};\n\nexport type SectionHash = { key: string; hash: string };\nexport type AcknowledgedExchange = {\n deliveryId: string;\n /** Keys whose content the client confirmed receiving and that have not changed. */\n unchanged: string[];\n ack: \"none\" | \"accepted\" | \"unknown\";\n};\n\nexport class SessionCapsuleLedger {\n private readonly directory: string;\n\n constructor(repositoryRoot = process.cwd()) {\n this.directory = join(resolve(repositoryRoot), \".codebuddy\", \"cache\", \"session\");\n }\n\n async deliver(\n sessionId: string,\n capsules: ContextCapsule[],\n now = new Date(),\n ): Promise<CapsuleDelivery> {\n await mkdir(this.directory, { recursive: true, mode: 0o700 });\n const release = await this.acquire(sessionId);\n try {\n const path = this.path(sessionId);\n const ledger = await this.read(path, now);\n const lines: string[] = [];\n const unchanged: string[] = [];\n let full = 0;\n let referenced = 0;\n let sentTokens = 0;\n let avoidedTokens = 0;\n\n for (const capsule of capsules) {\n const hash = digest(capsule.text);\n const existing = ledger.records[capsule.key];\n if (existing?.hash === hash) {\n unchanged.push(capsule.key);\n referenced++;\n avoidedTokens += estimateTokens(capsule.text);\n } else {\n lines.push(capsule.text);\n full++;\n sentTokens += estimateTokens(capsule.text);\n }\n ledger.records[capsule.key] = { hash, lastSentAt: now.toISOString() };\n }\n\n await this.write(path, ledger, now);\n return { lines, unchanged, full, referenced, sentTokens, avoidedTokens };\n } finally {\n await release();\n }\n }\n\n /** Keys of capsules whose current text was already recorded as sent this session. */\n async seen(\n sessionId: string,\n capsules: ContextCapsule[],\n now = new Date(),\n ): Promise<Set<string>> {\n const ledger = await this.read(this.path(sessionId), now);\n return new Set(\n capsules\n .filter((capsule) => ledger.records[capsule.key]?.hash === digest(capsule.text))\n .map((capsule) => capsule.key),\n );\n }\n\n /** Whether anything was delivered in this session yet (a later prompt is a follow-up). */\n async delivered(sessionId: string, now = new Date()): Promise<boolean> {\n const ledger = await this.read(this.path(sessionId), now);\n return Object.keys(ledger.records).length > 0;\n }\n\n /**\n * Record capsules as delivered. Call only after their text actually reached\n * the client, so capsules that were clipped or failed to write stay unseen\n * and are delivered by a later prompt.\n */\n async markSent(sessionId: string, capsules: ContextCapsule[], now = new Date()): Promise<void> {\n if (capsules.length === 0) return;\n await mkdir(this.directory, { recursive: true, mode: 0o700 });\n const release = await this.acquire(sessionId);\n try {\n const path = this.path(sessionId);\n const ledger = await this.read(path, now);\n for (const capsule of capsules) {\n ledger.records[capsule.key] = { hash: digest(capsule.text), lastSentAt: now.toISOString() };\n }\n await this.write(path, ledger, now);\n } finally {\n await release();\n }\n }\n\n /**\n * Acknowledged delivery for clients that can lose context (MCP). Unlike\n * `deliver`, sending marks nothing as seen: the sent hashes are held as a\n * pending delivery until the client echoes its `deliveryId` back as `ack` on\n * a later call. Only acknowledged, unchanged sections sent within `ttlMs` are\n * reported unchanged, so a dropped response or a compacted client context\n * fails safe to a full resend.\n *\n * A section keeps its last few acknowledged versions (by content hash), not\n * just the latest, so returning to an earlier task in the same session does\n * not resend what the client already holds. `full` means the client lost its\n * context: every acknowledged version and unconfirmed delivery is forgotten.\n */\n async exchange(\n sessionId: string,\n sections: SectionHash[],\n options: {\n ack?: string;\n ttlMs: number;\n /** The client lost its context: nothing acknowledged before this call counts. */\n full?: boolean;\n now?: Date;\n /** Finalize the response before recording hashes of the actual sent sections. */\n prepare?: (exchange: AcknowledgedExchange) => SectionHash[];\n },\n ): Promise<AcknowledgedExchange> {\n const now = options.now ?? new Date();\n await mkdir(this.directory, { recursive: true, mode: 0o700 });\n const release = await this.acquire(sessionId);\n try {\n const path = this.path(sessionId);\n const ledger = await this.read(path, now);\n let pending = ledger.pending ?? {};\n let acknowledged = ledger.acknowledged ?? {};\n\n let ack: AcknowledgedExchange[\"ack\"] = \"none\";\n if (options.ack !== undefined) {\n const confirmed = pending[options.ack];\n if (confirmed) {\n for (const [key, hash] of Object.entries(confirmed.records)) {\n acknowledged[key] = [\n { hash, sentAt: confirmed.createdAt },\n ...(acknowledged[key] ?? []).filter((version) => version.hash !== hash),\n ]\n .sort((left, right) => right.sentAt.localeCompare(left.sentAt))\n .slice(0, MAX_ACKNOWLEDGED_PER_KEY);\n }\n delete pending[options.ack];\n ack = \"accepted\";\n } else {\n ack = \"unknown\";\n }\n }\n if (options.full) {\n acknowledged = {};\n pending = {};\n }\n\n const unchanged: string[] = [];\n const sent: Record<string, string> = {};\n for (const section of sections) {\n const held = acknowledged[section.key]?.some((version) => {\n const age = now.getTime() - Date.parse(version.sentAt);\n return version.hash === section.hash && age >= 0 && age <= options.ttlMs;\n });\n if (held) {\n unchanged.push(section.key);\n } else {\n sent[section.key] = section.hash;\n }\n }\n\n const deliveryId = generateEntityId(\"dlv\", now.getTime());\n const exchange = { deliveryId, unchanged, ack };\n const delivered = options.prepare?.(exchange);\n pending[deliveryId] = {\n createdAt: now.toISOString(),\n records: delivered\n ? Object.fromEntries(delivered.map(({ key, hash }) => [key, hash]))\n : sent,\n };\n ledger.pending = Object.fromEntries(\n Object.entries(pending)\n .filter(([, item]) => now.getTime() - Date.parse(item.createdAt) <= PENDING_MAX_AGE_MS)\n .sort((left, right) => right[1].createdAt.localeCompare(left[1].createdAt))\n .slice(0, MAX_PENDING),\n );\n ledger.acknowledged = Object.fromEntries(\n Object.entries(acknowledged)\n .map(([key, versions]): [string, AcknowledgedVersion[]] => [\n key,\n versions.filter((version) => now.getTime() - Date.parse(version.sentAt) <= MAX_AGE_MS),\n ])\n .filter(([, versions]) => versions.length > 0)\n .slice(0, MAX_CAPSULES),\n );\n await this.write(path, ledger, now);\n return { deliveryId, unchanged, ack };\n } finally {\n await release();\n }\n }\n\n /** Forget everything sent in a session, e.g. after the client compacted its context. */\n async reset(sessionId: string): Promise<void> {\n await mkdir(this.directory, { recursive: true, mode: 0o700 });\n const release = await this.acquire(sessionId);\n try {\n await rm(this.path(sessionId), { force: true });\n } finally {\n await release();\n }\n }\n\n /** Remove expired disposable state; locked sessions are left for the next pass. */\n async pruneExpired(now = new Date()): Promise<number> {\n let removed = 0;\n for (const entry of await readdir(this.directory).catch(() => [])) {\n if (!entry.endsWith(\".json\")) continue;\n const path = join(this.directory, entry);\n try {\n await stat(`${path}.lock`);\n continue;\n } catch {\n // No active lock.\n }\n try {\n const metadata = await stat(path);\n if (now.getTime() - metadata.mtimeMs > MAX_AGE_MS) {\n await rm(path, { force: true });\n removed++;\n }\n } catch {\n // Another cleanup may have removed it already.\n }\n }\n return removed;\n }\n\n private async write(path: string, ledger: LedgerFile, now: Date): Promise<void> {\n ledger.updatedAt = now.toISOString();\n ledger.records = Object.fromEntries(\n Object.entries(ledger.records)\n .sort((left, right) => right[1].lastSentAt.localeCompare(left[1].lastSentAt))\n .slice(0, MAX_CAPSULES),\n );\n await writeAtomic(path, `${JSON.stringify(ledger, null, 2)}\\n`);\n }\n\n private async read(path: string, now: Date): Promise<LedgerFile> {\n try {\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as LedgerFile;\n if (parsed.version !== VERSION || typeof parsed.records !== \"object\") return empty(now);\n const records = Object.fromEntries(\n Object.entries(parsed.records).filter(([, record]) => {\n const age = now.getTime() - Date.parse(record.lastSentAt);\n return Number.isFinite(age) && age <= MAX_AGE_MS;\n }),\n );\n const pending =\n parsed.pending && typeof parsed.pending === \"object\" ? parsed.pending : undefined;\n const acknowledged =\n parsed.acknowledged && typeof parsed.acknowledged === \"object\"\n ? Object.fromEntries(\n Object.entries(parsed.acknowledged).filter(([, versions]) => Array.isArray(versions)),\n )\n : undefined;\n return {\n version: VERSION,\n updatedAt: parsed.updatedAt,\n records,\n ...(pending ? { pending } : {}),\n ...(acknowledged ? { acknowledged } : {}),\n };\n } catch {\n return empty(now);\n }\n }\n\n private path(sessionId: string): string {\n // The full identity participates in the key. Legacy sanitized filenames\n // are intentionally ignored, so a fresh session cannot inherit collisions.\n return join(this.directory, `${digest(sessionId)}.json`);\n }\n\n private async acquire(sessionId: string): Promise<() => Promise<void>> {\n const lock = `${this.path(sessionId)}.lock`;\n for (let attempt = 0; ; attempt++) {\n try {\n await mkdir(lock);\n return async () => rm(lock, { recursive: true, force: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\" || attempt >= 200) throw error;\n try {\n const age = Date.now() - (await stat(lock)).mtimeMs;\n if (age > STALE_LOCK_MS) {\n await rm(lock, { recursive: true, force: true });\n continue;\n }\n } catch {\n // The owner may have released the lock between checks.\n }\n await new Promise((resolveWait) => setTimeout(resolveWait, 10));\n }\n }\n }\n}\n\nfunction empty(now: Date): LedgerFile {\n return { version: VERSION, updatedAt: now.toISOString(), records: {} };\n}\n\nfunction digest(text: string): string {\n return createHash(\"sha256\").update(text).digest(\"hex\");\n}\n","import { access, readFile } from \"node:fs/promises\";\nimport { basename, dirname, extname, join } from \"node:path\";\n\n/**\n * Which test covers a file and how this repo runs its tests, from naming\n * conventions and project markers only. Agents otherwise spend turns finding\n * both; a wrong guess costs one failed command, so first match wins.\n */\nexport function findCoveringTest(path: string, paths: ReadonlySet<string>): string | null {\n return coveringTestCandidates(path).find((candidate) => paths.has(candidate)) ?? null;\n}\n\n/** Conventional covering-test paths for `path`, most specific first. */\nexport function coveringTestCandidates(path: string): string[] {\n const ext = extname(path);\n const stem = basename(path, ext);\n const dir = dirname(path);\n const join2 = (...parts: string[]) => parts.filter((part) => part && part !== \".\").join(\"/\");\n // `src/a/x.ts` mirrors to `test/a/x.test.ts`: drop a leading source root.\n const mirrored = dir.replace(/^(src|lib|app)(\\/|$)/, \"\");\n return ext === \".py\"\n ? [\n join2(dir, `test_${stem}.py`),\n join2(dir, `${stem}_test.py`),\n join2(dir, \"tests\", `test_${stem}.py`),\n // `src/pkg/sub/x.py` → `tests/pkg/sub/test_x.py`, `tests/sub/test_x.py`, `tests/test_x.py`.\n join2(\"tests\", mirrored, `test_${stem}.py`),\n join2(\"tests\", mirrored.replace(/^[^/]+\\/?/, \"\"), `test_${stem}.py`),\n join2(\"tests\", `test_${stem}.py`),\n ]\n : ext === \".go\"\n ? [join2(dir, `${stem}_test.go`)]\n : [\n join2(dir, `${stem}.test${ext}`),\n join2(dir, `${stem}.spec${ext}`),\n join2(dir, \"__tests__\", `${stem}.test${ext}`),\n join2(dir, \"__tests__\", `${stem}${ext}`),\n join2(\"test\", mirrored, `${stem}.test${ext}`),\n join2(\"tests\", mirrored, `${stem}.test${ext}`),\n ];\n}\n\nconst NPM_PLACEHOLDER = /no test specified/;\n\nexport async function detectTestCommand(repositoryRoot: string): Promise<string | null> {\n const exists = (file: string) =>\n access(join(repositoryRoot, file)).then(\n () => true,\n () => false,\n );\n try {\n const pkg = JSON.parse(await readFile(join(repositoryRoot, \"package.json\"), \"utf8\")) as {\n scripts?: Record<string, unknown>;\n packageManager?: unknown;\n };\n const script = pkg.scripts?.test;\n if (typeof script === \"string\" && script.trim() && !NPM_PLACEHOLDER.test(script)) {\n const declared =\n typeof pkg.packageManager === \"string\" ? pkg.packageManager.split(/[\\s@]/, 1)[0] : \"\";\n // `bun test` is Bun's own runner, not the package's test script.\n if (declared === \"bun\") return \"bun run test\";\n if (declared && [\"npm\", \"pnpm\", \"yarn\"].includes(declared)) return `${declared} test`;\n if (await exists(\"pnpm-lock.yaml\")) return \"pnpm test\";\n if (await exists(\"yarn.lock\")) return \"yarn test\";\n if ((await exists(\"bun.lockb\")) || (await exists(\"bun.lock\"))) return \"bun run test\";\n return \"npm test\";\n }\n } catch {\n // No or unreadable package.json: try other ecosystems.\n }\n for (const marker of [\"pyproject.toml\", \"pytest.ini\", \"setup.cfg\"]) {\n if (await exists(marker)) return \"pytest\";\n }\n if (await exists(\"go.mod\")) return \"go test ./...\";\n if (await exists(\"Cargo.toml\")) return \"cargo test\";\n return null;\n}\n"],"mappings":"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","names":["readFile","join","join2"]}
@@ -14,7 +14,7 @@ import {
14
14
  // package.json
15
15
  var package_default = {
16
16
  name: "@orquentra/codebuddy",
17
- version: "2.8.0",
17
+ version: "2.14.0",
18
18
  description: "Local-first project context and memory for AI coding agents.",
19
19
  type: "module",
20
20
  license: "MIT",
@@ -72,13 +72,22 @@ var package_default = {
72
72
  "eval:semantic": "tsx scripts/eval-semantic.mts"
73
73
  },
74
74
  keywords: [
75
+ "codebuddy",
75
76
  "mcp",
77
+ "mcp-server",
78
+ "model-context-protocol",
79
+ "claude-code",
80
+ "codex",
81
+ "opencode",
82
+ "ai-agent",
83
+ "coding-agent",
84
+ "context-engine",
76
85
  "memory",
77
86
  "agent",
78
87
  "multi-agent",
79
- "huggingface",
80
88
  "pgvector",
81
89
  "postgres",
90
+ "huggingface",
82
91
  "langgraph",
83
92
  "claude"
84
93
  ],
@@ -989,6 +998,22 @@ function termOverlap(a, b) {
989
998
  for (const term of left) if (right.has(term)) shared += 1;
990
999
  return shared / (left.size + right.size - shared);
991
1000
  }
1001
+ function memoryConceptPatterns(query) {
1002
+ const text2 = query.slice(0, 4096).toLowerCase();
1003
+ const patterns = [];
1004
+ if (/\b(?:lines?|formatter|formatting)\b/.test(text2) && /\b(?:wide|width|length|long|characters?|columns?)\b/.test(text2)) {
1005
+ patterns.push("line[_ -]?(length|width)|max[_ -]?line[_ -]?(length|width)|print[_ -]?width");
1006
+ }
1007
+ if (/\b(?:interpreter|runtime|language|python|node|ruby|php|java|jdk|dotnet|rust|golang)\b/.test(
1008
+ text2
1009
+ ) && /\b(?:oldest|earliest|minimum|min|version|release|supports?)\b/.test(text2)) {
1010
+ patterns.push(
1011
+ "requires[-_ ]?(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang)|(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang|interpreter|runtime)[^.!?]{0,40}(>=|minimum|newer)|minimum[^.!?]{0,40}(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang|interpreter|runtime)"
1012
+ );
1013
+ }
1014
+ return patterns;
1015
+ }
1016
+ var MEMORY_CONCEPT_SCORE = 0.5;
992
1017
 
993
1018
  export {
994
1019
  isEligibleMemory,
@@ -1006,9 +1031,11 @@ export {
1006
1031
  lexicalTerms,
1007
1032
  admitsLexically,
1008
1033
  termOverlap,
1034
+ memoryConceptPatterns,
1035
+ MEMORY_CONCEPT_SCORE,
1009
1036
  VERSION,
1010
1037
  FACT_CATEGORIES,
1011
1038
  INCIDENT_SEVERITIES,
1012
1039
  MemoryFileStore
1013
1040
  };
1014
- //# sourceMappingURL=chunk-6KGYAFG7.js.map
1041
+ //# sourceMappingURL=chunk-7QMHIJBA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../package.json","../src/version.ts","../src/core/memory-file-store.ts","../src/core/memory-lifecycle.ts","../src/db/schema.ts","../src/core/memory-search.ts"],"sourcesContent":["{\n \"name\": \"@orquentra/codebuddy\",\n \"version\": \"2.14.0\",\n \"description\": \"Local-first project context and memory for AI coding agents.\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"author\": \"Orquentra Inc\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Orquentra-Inc/CodeBuddy\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"bin\": {\n \"codebuddy\": \"./dist/cli/index.js\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./langgraph\": {\n \"types\": \"./dist/langgraph/index.d.ts\",\n \"import\": \"./dist/langgraph/index.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"src/db/migrations\",\n \"README.md\",\n \"CLAUDE_CODEX.md\",\n \"ARCHITECTURE.md\",\n \"WORKFLOW.md\",\n \"LICENSE\",\n \"docker-compose.yml\",\n \"examples\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx src/cli/index.ts\",\n \"db:generate\": \"drizzle-kit generate\",\n \"db:migrate\": \"drizzle-kit migrate\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run --passWithNoTests\",\n \"test:pg\": \"bash scripts/test-pg-recovery.sh\",\n \"eval:agent-ab\": \"tsx scripts/eval-agent-ab.mts\",\n \"eval:memory-ab\": \"tsx scripts/eval-memory-ab.mts\",\n \"eval:retrieval\": \"tsx scripts/eval-retrieval.mts\",\n \"eval:savings\": \"tsx scripts/eval-task-savings.mts\",\n \"branches:prune\": \"bash scripts/prune-merged-branches.sh\",\n \"lint\": \"biome check .\",\n \"format\": \"biome format --write .\",\n \"prepublishOnly\": \"node scripts/check-release.mjs && npm run typecheck && npm test && npm run build\",\n \"eval:semantic\": \"tsx scripts/eval-semantic.mts\"\n },\n \"keywords\": [\n \"codebuddy\",\n \"mcp\",\n \"mcp-server\",\n \"model-context-protocol\",\n \"claude-code\",\n \"codex\",\n \"opencode\",\n \"ai-agent\",\n \"coding-agent\",\n \"context-engine\",\n \"memory\",\n \"agent\",\n \"multi-agent\",\n \"pgvector\",\n \"postgres\",\n \"huggingface\",\n \"langgraph\",\n \"claude\"\n ],\n \"homepage\": \"https://github.com/Orquentra-Inc/CodeBuddy\",\n \"bugs\": {\n \"url\": \"https://github.com/Orquentra-Inc/CodeBuddy/issues\"\n },\n \"dependencies\": {\n \"@clack/prompts\": \"^1.8.1\",\n \"@huggingface/inference\": \"^4.13.19\",\n \"@modelcontextprotocol/sdk\": \"^1.17.5\",\n \"commander\": \"^14.0.0\",\n \"drizzle-orm\": \"^0.45.2\",\n \"js-tiktoken\": \"^1.0.21\",\n \"p-queue\": \"^10.0.0\",\n \"p-retry\": \"^8.0.1\",\n \"p-timeout\": \"^8.1.0\",\n \"picocolors\": \"^1.1.1\",\n \"pino\": \"^10.4.0\",\n \"postgres\": \"^3.4.7\",\n \"yaml\": \"^2.9.0\",\n \"zod\": \"^4.6.7\"\n },\n \"peerDependencies\": {\n \"@langchain/langgraph\": \"^0.2.0 || ^0.3.0\"\n },\n \"peerDependenciesMeta\": {\n \"@langchain/langgraph\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.5.0\",\n \"@types/node\": \"^24.3.0\",\n \"drizzle-kit\": \"^0.31.10\",\n \"tsup\": \"^8.5.0\",\n \"tsx\": \"^4.20.5\",\n \"typescript\": \"^5.9.2\",\n \"vitest\": \"^4.1.12\"\n },\n \"optionalDependencies\": {\n \"@huggingface/transformers\": \"^4.3.2\"\n }\n}\n","import pkg from \"../package.json\" with { type: \"json\" };\n\n/**\n * The single source of truth for the product version. `package.json` is the\n * canonical value (npm reads it for the published package); the CLI `--version`\n * flag and the MCP server handshake both import it from here so a version bump\n * only ever touches `package.json`. The bundler inlines this at build time, so\n * there is no runtime file read.\n */\nexport const VERSION: string = pkg.version;\n","import { mkdir, readdir, readFile, rm, stat } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { parse as parseYaml, stringify as stringifyYaml } from \"yaml\";\nimport { z } from \"zod\";\nimport { redactPersisted } from \"../core/sensitive.js\";\nimport { VERSION } from \"../version.js\";\nimport {\n assertNoSymlinkBetween,\n composeMarkdown,\n parseFrontMatter,\n writeAtomic,\n} from \"./markdown-store-fs.js\";\nimport { isEligibleMemory } from \"./memory-lifecycle.js\";\n\nexport const FACT_CATEGORIES = [\"general\", \"incident\"] as const;\nexport type FactCategory = (typeof FACT_CATEGORIES)[number];\nexport const INCIDENT_SEVERITIES = [\"low\", \"medium\", \"high\", \"critical\"] as const;\nexport type IncidentSeverity = (typeof INCIDENT_SEVERITIES)[number];\n\nconst factFrontMatterSchema = z.object({\n schemaVersion: z.literal(1),\n id: z.string().regex(/^fact_[a-z0-9]+$/),\n namespace: z.string().min(1),\n type: z.literal(\"fact\"),\n subject: z.string(),\n predicate: z.string(),\n object: z.string(),\n /** Explicit single-valued claim identity; facts sharing it are rival assertions. */\n propositionKey: z.string().min(1).nullable().default(null),\n /** The caller's `remember` idempotency key, so a retry after reindex dedupes. */\n idempotencyKey: z.string().min(1).max(200).nullable().default(null),\n confidence: z.number().min(0).max(1),\n createdAt: z.preprocess(\n (value) => (value instanceof Date ? value.toISOString() : value),\n z.string().datetime(),\n ),\n createdByAgent: z.string().nullable(),\n sourceInteractionId: z.string().nullable(),\n sourceDeleted: z.boolean(),\n category: z.enum(FACT_CATEGORIES).default(\"general\"),\n paths: z.array(z.string().min(1)).default([]),\n severity: z.enum(INCIDENT_SEVERITIES).nullable().default(null),\n introducedBy: z.string().nullable().default(null),\n resolvedBy: z.string().nullable().default(null),\n fingerprint: z\n .string()\n .regex(/^[a-f0-9]{64}$/)\n .nullable()\n .default(null),\n sourcePlanId: z.string().nullable().default(null),\n sourceSessionId: z.string().nullable().default(null),\n verification: z.array(z.string()).default([]),\n supersededBy: z.string().nullable().default(null),\n /** Namespaces granted a live reference to this fact (durable form of reference shares). */\n sharedWith: z.array(z.string().min(1)).default([]),\n});\n\nconst summaryFrontMatterSchema = z.object({\n schemaVersion: z.literal(1),\n id: z.string().regex(/^sum_[a-z0-9]+$/),\n namespace: z.string().min(1),\n type: z.literal(\"summary\"),\n sessionId: z.string().min(1),\n version: z.number().int().positive(),\n tokenCount: z.number().int().nonnegative(),\n createdAt: z.preprocess(\n (value) => (value instanceof Date ? value.toISOString() : value),\n z.string().datetime(),\n ),\n createdByAgent: z.string().nullable(),\n});\n\nexport type FactFile = z.infer<typeof factFrontMatterSchema> & {\n content: string;\n path: string;\n};\n\nexport type FactFileWrite = Omit<\n FactFile,\n | \"path\"\n | \"schemaVersion\"\n | \"type\"\n | \"category\"\n | \"paths\"\n | \"severity\"\n | \"introducedBy\"\n | \"resolvedBy\"\n | \"fingerprint\"\n | \"sourcePlanId\"\n | \"sourceSessionId\"\n | \"verification\"\n | \"supersededBy\"\n | \"sharedWith\"\n | \"propositionKey\"\n | \"idempotencyKey\"\n> &\n Partial<\n Pick<\n FactFile,\n | \"category\"\n | \"paths\"\n | \"severity\"\n | \"introducedBy\"\n | \"resolvedBy\"\n | \"fingerprint\"\n | \"sourcePlanId\"\n | \"sourceSessionId\"\n | \"verification\"\n | \"supersededBy\"\n | \"sharedWith\"\n | \"propositionKey\"\n | \"idempotencyKey\"\n >\n >;\nexport type IncidentFactFile = FactFile & {\n category: \"incident\";\n severity: IncidentSeverity;\n};\nexport type SummaryFile = z.infer<typeof summaryFrontMatterSchema> & {\n content: string;\n path: string;\n};\nexport type SummaryFileWrite = Omit<SummaryFile, \"path\" | \"schemaVersion\" | \"type\">;\n/** A managed memory file that exists on disk but could not be parsed. */\nexport type InvalidMemoryFile = { id: string; path: string; error: string };\n\n/** Files read concurrently while scanning. */\nconst SCAN_BATCH = 64;\n\n/** Parsed memory files by absolute path, valid while mtime and size match. */\nconst parsedFiles = new Map<string, { mtimeMs: number; size: number; item: { id: string } }>();\nconst loadedCaches = new Set<string>();\n\n/**\n * The parse cache also lives on disk (`.codebuddy/cache/parsed-<dir>.json`,\n * gitignored and rebuildable like the index manifest), so each short-lived\n * hook process re-parses only files that changed since any earlier process.\n * Entries are still checked against each file's mtime and size, the cache is\n * discarded when the CodeBuddy version changes (the parsed shape may differ),\n * and a missing or corrupt cache only costs a full parse.\n */\nfunction parseCachePath(root: string, directory: string): string {\n return join(\n root,\n \".codebuddy\",\n \"cache\",\n `parsed-${directory.split(sep).at(-1) ?? \"memory\"}.json`,\n );\n}\n\nasync function loadParseCache(root: string, directory: string): Promise<void> {\n if (loadedCaches.has(directory)) return;\n loadedCaches.add(directory);\n try {\n const raw = JSON.parse(await readFile(parseCachePath(root, directory), \"utf8\")) as {\n version: string;\n files: Record<string, { mtimeMs: number; size: number; item: { id: string } }>;\n };\n if (raw.version !== VERSION) return;\n for (const [name, entry] of Object.entries(raw.files)) {\n const path = join(directory, name);\n if (!parsedFiles.has(path)) parsedFiles.set(path, entry);\n }\n } catch {\n // No cache yet, or unreadable: parse from the files.\n }\n}\n\nasync function saveParseCache(root: string, directory: string): Promise<void> {\n const files: Record<string, unknown> = {};\n for (const [path, entry] of parsedFiles) {\n if (dirname(path) === directory) files[relative(directory, path)] = entry;\n }\n try {\n const path = parseCachePath(root, directory);\n await mkdir(dirname(path), { recursive: true });\n await writeAtomic(path, JSON.stringify({ version: VERSION, files }));\n } catch {\n // A cache that cannot be written only costs a re-parse next time.\n }\n}\n\nexport class MemoryFileStore {\n readonly repositoryRoot: string;\n readonly factsDirectory: string;\n readonly summariesDirectory: string;\n\n constructor(repositoryRoot = process.cwd()) {\n this.repositoryRoot = resolve(repositoryRoot);\n this.factsDirectory = join(this.repositoryRoot, \".codebuddy\", \"memory\", \"facts\");\n this.summariesDirectory = join(this.repositoryRoot, \".codebuddy\", \"memory\", \"summaries\");\n }\n\n async writeFact(raw: FactFileWrite): Promise<string> {\n // Persistence boundary: every fact writer (capture, remember, outcomes,\n // migration, resolve/supersede rewrites) is redacted here.\n const input = redactPersisted(raw);\n const path = this.factPath(input.id);\n await this.prepareDirectory(this.factsDirectory);\n await assertNoSymlinkBetween(this.repositoryRoot, dirname(path));\n\n const metadata = {\n schemaVersion: 1,\n id: input.id,\n namespace: input.namespace,\n type: \"fact\",\n subject: input.subject,\n predicate: input.predicate,\n object: input.object,\n propositionKey: input.propositionKey,\n idempotencyKey: input.idempotencyKey,\n confidence: input.confidence,\n createdAt: input.createdAt,\n createdByAgent: input.createdByAgent,\n sourceInteractionId: input.sourceInteractionId,\n sourceDeleted: input.sourceDeleted,\n category: input.category,\n paths: input.paths,\n severity: input.severity,\n introducedBy: input.introducedBy,\n resolvedBy: input.resolvedBy,\n fingerprint: input.fingerprint,\n sourcePlanId: input.sourcePlanId,\n sourceSessionId: input.sourceSessionId,\n verification: input.verification,\n supersededBy: input.supersededBy,\n sharedWith: input.sharedWith,\n };\n const frontMatter = stringifyYaml(stripUndefinedAndDefaultIncidentFields(metadata));\n\n await writeAtomic(path, composeMarkdown(frontMatter, input.content));\n return path;\n }\n\n async writeSummary(input: SummaryFileWrite): Promise<string> {\n const path = this.summaryPath(input.id);\n await this.prepareDirectory(this.summariesDirectory);\n await assertNoSymlinkBetween(this.repositoryRoot, dirname(path));\n const metadata = stringifyYaml({\n schemaVersion: 1,\n id: input.id,\n namespace: input.namespace,\n type: \"summary\",\n sessionId: input.sessionId,\n version: input.version,\n tokenCount: input.tokenCount,\n createdAt: input.createdAt,\n createdByAgent: input.createdByAgent,\n });\n await writeAtomic(path, composeMarkdown(metadata, input.content));\n return path;\n }\n\n async readFact(pathOrId: string): Promise<FactFile> {\n return this.readFactFile(pathOrId, false);\n }\n\n /** `trusted`: the caller (scan) already proved the path is a regular file in a checked directory. */\n private async readFactFile(pathOrId: string, trusted: boolean): Promise<FactFile> {\n const path = pathOrId.endsWith(\".md\") ? this.safePath(pathOrId) : this.factPath(pathOrId);\n if (!trusted) await assertNoSymlinkBetween(this.repositoryRoot, path);\n const parsed = parseFrontMatter(await readFile(path, \"utf8\"));\n const metadata = factFrontMatterSchema.parse(parseYaml(parsed.frontMatter));\n return {\n ...metadata,\n content: parsed.content.trim(),\n path,\n };\n }\n\n async listFacts(): Promise<FactFile[]> {\n return (await this.scanFacts()).facts;\n }\n\n /**\n * Parse every managed fact file. Hand-written markdown is ignored, and a\n * schema-invalid/corrupt fact is reported in `invalid` (not thrown) so one\n * bad file can't break recall, capture or reindexing for the whole project.\n * Invalid files stay on disk for manual repair.\n */\n async scanFacts(): Promise<{ facts: FactFile[]; invalid: InvalidMemoryFile[] }> {\n const scan = await this.scan(this.factsDirectory, /^fact_[a-z0-9]+\\.md$/, (path, trusted) =>\n this.readFactFile(path, trusted),\n );\n return { facts: scan.items, invalid: scan.invalid };\n }\n\n /**\n * List incident facts for the namespace, ranked by severity then recency.\n * Unlike {@link findIncidentFactsForPaths} this does not filter by path, so\n * callers (e.g. the context engine's project bootstrap) can surface the\n * repository's incident hotspots before any specific edit target is known.\n */\n async listIncidentFacts(\n input: { namespace?: string; includeResolved?: boolean } = {},\n ): Promise<IncidentFactFile[]> {\n const incidents = (await this.listFacts()).filter(\n (fact): fact is IncidentFactFile =>\n fact.category === \"incident\" && Boolean(fact.severity) && isEligibleMemory(fact, input),\n );\n\n return incidents.sort((left, right) => {\n const severity = severityRank(right.severity) - severityRank(left.severity);\n if (severity !== 0) return severity;\n return right.createdAt.localeCompare(left.createdAt);\n });\n }\n\n async findIncidentFactsForPaths(input: {\n namespace?: string;\n paths: string[];\n includeResolved?: boolean;\n }): Promise<IncidentFactFile[]> {\n const wanted = new Set(input.paths.map(normalizeMemoryPath));\n if (wanted.size === 0) return [];\n\n return (\n await this.listIncidentFacts({\n ...(input.namespace !== undefined ? { namespace: input.namespace } : {}),\n ...(input.includeResolved !== undefined ? { includeResolved: input.includeResolved } : {}),\n })\n ).filter((fact) => fact.paths.map(normalizeMemoryPath).some((path) => wanted.has(path)));\n }\n\n async readSummary(pathOrId: string): Promise<SummaryFile> {\n const path = pathOrId.endsWith(\".md\") ? this.safePath(pathOrId) : this.summaryPath(pathOrId);\n await assertNoSymlinkBetween(this.repositoryRoot, path);\n const parsed = parseFrontMatter(await readFile(path, \"utf8\"));\n return {\n ...summaryFrontMatterSchema.parse(parseYaml(parsed.frontMatter)),\n content: parsed.content.trim(),\n path,\n };\n }\n\n async listSummaries(): Promise<SummaryFile[]> {\n return (await this.scanSummaries()).summaries;\n }\n\n async scanSummaries(): Promise<{ summaries: SummaryFile[]; invalid: InvalidMemoryFile[] }> {\n const scan = await this.scan(this.summariesDirectory, /^sum_[a-z0-9]+\\.md$/, (path) =>\n this.readSummary(path),\n );\n return { summaries: scan.items, invalid: scan.invalid };\n }\n\n /**\n * Parse every managed file in `directory`. Hot path: the prompt hook and\n * capture scan facts several times per process, and the MCP server for its\n * lifetime, so each parsed file is cached by path, mtime and size (a changed\n * file is re-read, from any process) and files are read in parallel batches.\n * The directory chain is checked for symlinks once; a `withFileTypes` entry\n * that is a symlink is never `isFile()`, so no file escapes through one.\n */\n private async scan<T extends { id: string }>(\n directory: string,\n managedName: RegExp,\n read: (path: string, trusted: boolean) => Promise<T>,\n ): Promise<{ items: T[]; invalid: InvalidMemoryFile[] }> {\n try {\n await this.prepareDirectory(directory);\n await assertNoSymlinkBetween(this.repositoryRoot, directory);\n await loadParseCache(this.repositoryRoot, directory);\n let reparsed = false;\n const entries = (await readdir(directory, { withFileTypes: true }))\n .filter((entry) => entry.isFile() && managedName.test(entry.name))\n .sort((left, right) => left.name.localeCompare(right.name));\n // Forget files that were deleted, so the cache never outgrows the directory.\n const live = new Set(entries.map((entry) => join(directory, entry.name)));\n for (const path of [...parsedFiles.keys()]) {\n if (dirname(path) === directory && !live.has(path)) {\n parsedFiles.delete(path);\n reparsed = true;\n }\n }\n const outcomes: Array<{ item: T } | { invalid: InvalidMemoryFile }> = [];\n for (let start = 0; start < entries.length; start += SCAN_BATCH) {\n const batch = entries.slice(start, start + SCAN_BATCH);\n outcomes.push(\n ...(await Promise.all(\n batch.map(async (entry) => {\n const path = join(directory, entry.name);\n const id = entry.name.slice(0, -\".md\".length);\n try {\n const info = await stat(path);\n const cached = parsedFiles.get(path);\n const fresh =\n cached !== undefined &&\n cached.mtimeMs === info.mtimeMs &&\n cached.size === info.size;\n const item = fresh ? (cached.item as T) : await read(path, true);\n if (!fresh) reparsed = true;\n if (item.id !== id) {\n throw new Error(`file ID ${id} differs from front matter ID ${item.id}`);\n }\n parsedFiles.set(path, { mtimeMs: info.mtimeMs, size: info.size, item });\n return { item: { ...item } };\n } catch (error) {\n parsedFiles.delete(path);\n return {\n invalid: {\n id,\n path,\n error: (error as Error).message.split(\"\\n\")[0]?.slice(0, 300) ?? \"unreadable\",\n },\n };\n }\n }),\n )),\n );\n }\n if (reparsed) await saveParseCache(this.repositoryRoot, directory);\n const result: { items: T[]; invalid: InvalidMemoryFile[] } = { items: [], invalid: [] };\n for (const outcome of outcomes) {\n if (\"item\" in outcome) result.items.push(outcome.item);\n else result.invalid.push(outcome.invalid);\n }\n return result;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { items: [], invalid: [] };\n throw error;\n }\n }\n\n async deleteFact(id: string): Promise<void> {\n await rm(this.factPath(id), { force: true });\n }\n\n async markIncidentResolved(id: string, resolvedBy: string): Promise<void> {\n const fact = await this.readFact(id);\n await this.writeFact(copyFact(fact, { resolvedBy }));\n }\n\n async markFactSuperseded(id: string, supersededBy: string): Promise<void> {\n const fact = await this.readFact(id);\n await this.writeFact(copyFact(fact, { supersededBy }));\n }\n\n async setFactSharedWith(id: string, sharedWith: string[]): Promise<void> {\n const fact = await this.readFact(id);\n await this.writeFact(copyFact(fact, { sharedWith: [...new Set(sharedWith)].sort() }));\n }\n\n /** Persist interaction deletion in Markdown before the derived DB is updated. */\n async markSourceInteractionDeleted(\n namespace: string,\n sourceInteractionId: string,\n ): Promise<void> {\n const { facts } = await this.scanFacts();\n for (const fact of facts) {\n if (fact.namespace !== namespace || fact.sourceInteractionId !== sourceInteractionId)\n continue;\n if (fact.sourceDeleted) continue;\n await this.writeFact(copyFact(fact, { sourceDeleted: true }));\n }\n }\n\n /** Persist that a fact's source interaction was forgotten, keeping its provenance id. */\n async markFactSourceDeleted(id: string): Promise<void> {\n const fact = await this.readFact(id);\n await this.writeFact(copyFact(fact, { sourceDeleted: true }));\n }\n\n private factPath(id: string): string {\n if (!/^fact_[a-z0-9]+$/.test(id)) {\n throw new Error(`Invalid fact id: ${id}`);\n }\n return this.safePath(join(this.factsDirectory, `${id}.md`));\n }\n\n private summaryPath(id: string): string {\n if (!/^sum_[a-z0-9]+$/.test(id)) throw new Error(`Invalid summary id: ${id}`);\n return this.safePath(join(this.summariesDirectory, `${id}.md`));\n }\n\n private safePath(path: string): string {\n const absolute = resolve(path);\n const relativePath = relative(this.repositoryRoot, absolute);\n if (relativePath === \"..\" || relativePath.startsWith(`..${sep}`) || relativePath === \"\") {\n throw new Error(`Memory path escapes repository root: ${path}`);\n }\n return absolute;\n }\n\n private async prepareDirectory(directory: string): Promise<void> {\n await assertNoSymlinkBetween(this.repositoryRoot, dirname(directory));\n await mkdir(directory, { recursive: true, mode: 0o700 });\n }\n}\n\nfunction copyFact(\n fact: FactFile,\n patch: Partial<Pick<FactFile, \"resolvedBy\" | \"supersededBy\" | \"sourceDeleted\" | \"sharedWith\">>,\n): FactFileWrite {\n const { path: _path, schemaVersion: _schemaVersion, type: _type, ...write } = fact;\n return { ...write, ...patch };\n}\n\nfunction stripUndefinedAndDefaultIncidentFields(\n metadata: Record<string, unknown>,\n): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(metadata).filter(([key, value]) => {\n if (value === undefined) return false;\n if (key === \"category\" && value === \"general\") return false;\n if ((key === \"paths\" || key === \"sharedWith\") && Array.isArray(value) && value.length === 0)\n return false;\n if (\n [\"severity\", \"introducedBy\", \"resolvedBy\", \"propositionKey\", \"idempotencyKey\"].includes(\n key,\n ) &&\n (value === null || value === undefined)\n ) {\n return false;\n }\n return true;\n }),\n );\n}\n\nfunction normalizeMemoryPath(path: string): string {\n return path.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n}\n\nfunction severityRank(severity: IncidentSeverity): number {\n switch (severity) {\n case \"critical\":\n return 4;\n case \"high\":\n return 3;\n case \"medium\":\n return 2;\n case \"low\":\n return 1;\n }\n}\n","/** Lifecycle fields shared by Markdown facts and indexed (database) fact rows. */\nexport type MemoryLifecycle = {\n namespace?: string;\n sourceDeleted: boolean;\n supersededBy?: string | null;\n category?: string;\n resolvedBy?: string | null;\n};\n\n/**\n * The single eligibility rule for durable memory evidence. A fact is eligible\n * when it belongs to the requested namespace, its source was not deleted, and\n * it was not superseded. A resolved incident is eligible only for historical\n * inspection (`includeResolved`). File context, risk signals, and database\n * recall apply this before ranking or deduplication, so ineligible evidence\n * can never displace or suppress eligible evidence.\n */\nexport function isEligibleMemory(\n fact: MemoryLifecycle,\n options: { namespace?: string; includeResolved?: boolean } = {},\n): boolean {\n if (options.namespace && fact.namespace !== options.namespace) return false;\n if (fact.sourceDeleted || fact.supersededBy) return false;\n if (fact.category === \"incident\" && fact.resolvedBy && !options.includeResolved) return false;\n return true;\n}\n","import { sql } from \"drizzle-orm\";\nimport {\n boolean,\n customType,\n index,\n integer,\n jsonb,\n numeric,\n pgEnum,\n pgTable,\n text,\n timestamp,\n uniqueIndex,\n} from \"drizzle-orm/pg-core\";\n\nexport const VECTOR_DIMENSIONS = 384;\nexport const HNSW_M = 16;\nexport const HNSW_EF_CONSTRUCTION = 64;\n\nconst vector = customType<{ data: string; driverData: string }>({\n dataType() {\n return `vector(${VECTOR_DIMENSIONS})`;\n },\n});\n\nexport const memoryTypeEnum = pgEnum(\"memory_type\", [\"interaction\", \"fact\", \"summary\"]);\nexport const interactionRoleEnum = pgEnum(\"interaction_role\", [\"user\", \"assistant\", \"system\"]);\nexport const embeddingOwnerTypeEnum = pgEnum(\"embedding_owner_type\", [\n \"interaction\",\n \"fact\",\n \"summary\",\n]);\nexport const embeddingStatusEnum = pgEnum(\"embedding_status\", [\n \"pending\",\n \"processing\",\n \"ready\",\n \"failed\",\n]);\nexport const shareModeEnum = pgEnum(\"share_mode\", [\"reference\", \"snapshot\"]);\nexport const modelCallTypeEnum = pgEnum(\"model_call_type\", [\"embedding\", \"llm\"]);\nexport const modelCallStatusEnum = pgEnum(\"model_call_status\", [\n \"success\",\n \"failed\",\n \"timeout\",\n \"rate_limited\",\n \"gated\",\n \"fallback\",\n]);\nexport const auditActionEnum = pgEnum(\"audit_action\", [\n \"remember\",\n \"remember_batch\",\n \"share\",\n \"forget\",\n \"config\",\n]);\n\nconst createdAt = timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow();\nconst updatedAt = timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow();\n\nexport const namespaces = pgTable(\n \"namespaces\",\n {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n createdAt,\n updatedAt,\n },\n (table) => ({\n nameUnique: uniqueIndex(\"namespaces_name_unique\").on(table.name),\n }),\n);\n\nexport const interactions = pgTable(\n \"interactions\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n sessionId: text(\"session_id\").notNull(),\n role: interactionRoleEnum(\"role\").notNull().default(\"user\"),\n content: text(\"content\").notNull(),\n contentHash: text(\"content_hash\").notNull(),\n tokenCount: integer(\"token_count\").notNull().default(0),\n createdByAgent: text(\"created_by_agent\"),\n createdAt,\n },\n (table) => ({\n namespaceContentHashUnique: uniqueIndex(\"interactions_namespace_content_hash_unique\").on(\n table.namespaceId,\n table.contentHash,\n ),\n namespaceSessionCreatedIdx: index(\"interactions_namespace_session_created_idx\").on(\n table.namespaceId,\n table.sessionId,\n table.createdAt,\n ),\n }),\n);\n\nexport const sessionSummaries = pgTable(\n \"session_summaries\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n sessionId: text(\"session_id\").notNull(),\n content: text(\"content\").notNull(),\n version: integer(\"version\").notNull().default(1),\n tokenCount: integer(\"token_count\").notNull().default(0),\n createdByAgent: text(\"created_by_agent\"),\n /** See facts.fileBacked. */\n fileBacked: boolean(\"file_backed\").notNull().default(false),\n createdAt,\n },\n (table) => ({\n namespaceSessionVersionIdx: index(\"session_summaries_namespace_session_version_idx\").on(\n table.namespaceId,\n table.sessionId,\n table.version,\n ),\n }),\n);\n\nexport const facts = pgTable(\n \"facts\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n subject: text(\"subject\").notNull(),\n predicate: text(\"predicate\").notNull(),\n object: text(\"object\").notNull(),\n propositionKey: text(\"proposition_key\"),\n /** Kept apart from content_hash so a reindex (which recomputes the hash) keeps it. */\n idempotencyKey: text(\"idempotency_key\"),\n content: text(\"content\").notNull(),\n contentHash: text(\"content_hash\").notNull(),\n confidence: numeric(\"confidence\", { precision: 5, scale: 4 }).notNull().default(\"1\"),\n /**\n * Historical provenance only — deliberately not a foreign key. Interactions\n * are ephemeral DB rows; Markdown facts must re-import after a rebuild that\n * lost them, and the ID must survive so `sourceDeleted` can be tracked.\n */\n sourceInteractionId: text(\"source_interaction_id\"),\n sourceDeleted: boolean(\"source_deleted\").notNull().default(false),\n supersededBy: text(\"superseded_by\"),\n /**\n * True once a reindex imported this row from its Markdown file. Only such\n * rows are deleted when the file disappears; DB-only legacy rows are kept.\n */\n fileBacked: boolean(\"file_backed\").notNull().default(false),\n embeddingModel: text(\"embedding_model\"),\n createdByAgent: text(\"created_by_agent\"),\n createdAt,\n updatedAt,\n },\n (table) => ({\n namespaceContentHashUnique: uniqueIndex(\"facts_namespace_content_hash_unique\").on(\n table.namespaceId,\n table.contentHash,\n ),\n namespaceSubjectIdx: index(\"facts_namespace_subject_idx\").on(table.namespaceId, table.subject),\n namespaceIdempotencyIdx: index(\"facts_namespace_idempotency_idx\").on(\n table.namespaceId,\n table.idempotencyKey,\n ),\n sourceInteractionIdx: index(\"facts_source_interaction_idx\").on(table.sourceInteractionId),\n }),\n);\n\nexport const embeddings = pgTable(\n \"embeddings\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n ownerType: embeddingOwnerTypeEnum(\"owner_type\").notNull(),\n ownerId: text(\"owner_id\").notNull(),\n vector: vector(\"vector\").notNull(),\n embeddingModel: text(\"embedding_model\").notNull(),\n embeddingSpace: text(\"embedding_space\"),\n status: embeddingStatusEnum(\"status\").notNull().default(\"pending\"),\n attempts: integer(\"attempts\").notNull().default(0),\n lastError: text(\"last_error\"),\n /** Lease deadline while status='processing'; null otherwise. */\n leaseUntil: timestamp(\"lease_until\", { withTimezone: true }),\n leaseToken: text(\"lease_token\"),\n createdAt,\n updatedAt,\n },\n (table) => ({\n ownerUnique: uniqueIndex(\"embeddings_owner_unique\").on(table.ownerType, table.ownerId),\n namespaceModelStatusIdx: index(\"embeddings_namespace_model_status_idx\").on(\n table.namespaceId,\n table.embeddingModel,\n table.status,\n ),\n namespaceSpaceStatusIdx: index(\"embeddings_namespace_space_status_idx\").on(\n table.namespaceId,\n table.embeddingSpace,\n table.status,\n ),\n vectorHnswIdx: index(\"embeddings_vector_hnsw_idx\").using(\n \"hnsw\",\n sql`${table.vector} vector_cosine_ops`,\n ),\n }),\n);\n\nexport const shares = pgTable(\n \"shares\",\n {\n id: text(\"id\").primaryKey(),\n fromNamespaceId: text(\"from_namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n toNamespaceId: text(\"to_namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n sourceFactId: text(\"source_fact_id\")\n .notNull()\n .references(() => facts.id, { onDelete: \"cascade\" }),\n snapshotFactId: text(\"snapshot_fact_id\").references(() => facts.id, { onDelete: \"set null\" }),\n mode: shareModeEnum(\"mode\").notNull().default(\"reference\"),\n tombstoned: boolean(\"tombstoned\").notNull().default(false),\n createdByAgent: text(\"created_by_agent\"),\n createdAt,\n },\n (table) => ({\n shareUnique: uniqueIndex(\"shares_target_source_unique\").on(\n table.toNamespaceId,\n table.sourceFactId,\n table.mode,\n ),\n fromNamespaceIdx: index(\"shares_from_namespace_idx\").on(table.fromNamespaceId),\n }),\n);\n\nexport const checkpoints = pgTable(\n \"checkpoints\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\")\n .notNull()\n .references(() => namespaces.id, { onDelete: \"cascade\" }),\n threadId: text(\"thread_id\").notNull(),\n checkpointId: text(\"checkpoint_id\").notNull(),\n parentId: text(\"parent_id\"),\n channelValues: jsonb(\"channel_values\").notNull().default(sql`'{}'::jsonb`),\n metadata: jsonb(\"metadata\").notNull().default(sql`'{}'::jsonb`),\n createdAt,\n },\n (table) => ({\n checkpointUnique: uniqueIndex(\"checkpoints_thread_checkpoint_unique\").on(\n table.namespaceId,\n table.threadId,\n table.checkpointId,\n ),\n }),\n);\n\nexport const modelCalls = pgTable(\n \"model_calls\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\").references(() => namespaces.id, { onDelete: \"set null\" }),\n model: text(\"model\").notNull(),\n type: modelCallTypeEnum(\"type\").notNull(),\n latencyMs: integer(\"latency_ms\"),\n tokens: integer(\"tokens\"),\n status: modelCallStatusEnum(\"status\").notNull(),\n error: text(\"error\"),\n metadata: jsonb(\"metadata\").notNull().default(sql`'{}'::jsonb`),\n createdAt,\n },\n (table) => ({\n modelCreatedIdx: index(\"model_calls_model_created_idx\").on(table.model, table.createdAt),\n namespaceCreatedIdx: index(\"model_calls_namespace_created_idx\").on(\n table.namespaceId,\n table.createdAt,\n ),\n }),\n);\n\nexport const auditLog = pgTable(\n \"audit_log\",\n {\n id: text(\"id\").primaryKey(),\n namespaceId: text(\"namespace_id\").references(() => namespaces.id, { onDelete: \"set null\" }),\n action: auditActionEnum(\"action\").notNull(),\n entityType: text(\"entity_type\").notNull(),\n entityId: text(\"entity_id\"),\n createdByAgent: text(\"created_by_agent\"),\n metadata: jsonb(\"metadata\").notNull().default(sql`'{}'::jsonb`),\n createdAt,\n },\n (table) => ({\n namespaceActionCreatedIdx: index(\"audit_log_namespace_action_created_idx\").on(\n table.namespaceId,\n table.action,\n table.createdAt,\n ),\n }),\n);\n","import { VECTOR_DIMENSIONS } from \"../db/schema.js\";\n\n/** Bound both DB hydration and the lexical query work independently of caller input. */\nexport function candidateLimit(limit: number): number {\n if (!Number.isFinite(limit)) return 256;\n return Math.max(1, Math.min(1024, Math.trunc(limit)));\n}\n\n/** pgvector stores float4, so values must stay finite after float4 rounding. */\nexport function validSearchVector(vector: number[]): boolean {\n return (\n vector.length === VECTOR_DIMENSIONS &&\n vector.every((value) => Number.isFinite(Math.fround(value))) &&\n vector.some((value) => value !== 0)\n );\n}\n\n/** Common prompt scaffolding should not make unrelated facts look relevant. */\nconst QUERY_STOPWORDS = new Set([\n \"a\",\n \"about\",\n \"after\",\n \"again\",\n \"all\",\n \"also\",\n \"always\",\n \"an\",\n \"and\",\n \"any\",\n \"are\",\n \"as\",\n \"at\",\n \"back\",\n \"be\",\n \"been\",\n \"before\",\n \"being\",\n \"but\",\n \"by\",\n \"can\",\n \"could\",\n \"did\",\n \"do\",\n \"does\",\n \"done\",\n \"each\",\n \"even\",\n \"ever\",\n \"for\",\n \"from\",\n \"get\",\n \"gets\",\n \"got\",\n \"had\",\n \"has\",\n \"have\",\n \"here\",\n \"how\",\n \"i\",\n \"if\",\n \"in\",\n \"into\",\n \"is\",\n \"it\",\n \"its\",\n \"just\",\n \"like\",\n \"make\",\n \"me\",\n \"more\",\n \"most\",\n \"my\",\n \"never\",\n \"no\",\n \"not\",\n \"now\",\n \"of\",\n \"on\",\n \"only\",\n \"or\",\n \"other\",\n \"our\",\n \"out\",\n \"over\",\n \"please\",\n \"really\",\n \"same\",\n \"should\",\n \"so\",\n \"some\",\n \"sometimes\",\n \"still\",\n \"such\",\n \"than\",\n \"that\",\n \"the\",\n \"their\",\n \"them\",\n \"then\",\n \"there\",\n \"these\",\n \"they\",\n \"this\",\n \"those\",\n \"through\",\n \"to\",\n \"too\",\n \"under\",\n \"up\",\n \"us\",\n \"very\",\n \"was\",\n \"we\",\n \"were\",\n \"what\",\n \"when\",\n \"where\",\n \"which\",\n \"while\",\n \"who\",\n \"why\",\n \"will\",\n \"with\",\n \"would\",\n \"yet\",\n \"you\",\n \"your\",\n]);\n\n/** Literal, case-insensitive terms; no SQL wildcard or query-language syntax. */\nexport function lexicalTerms(query: string): string[] {\n return [\n ...new Set(\n query\n .slice(0, 4096)\n .toLowerCase()\n .match(/[\\p{L}\\p{N}_]+/gu) ?? [],\n ),\n ]\n .filter((term) => !QUERY_STOPWORDS.has(term))\n .slice(0, 32)\n .map((term) => term.slice(0, 64));\n}\n\n/**\n * Everyday coding words. Shared alone they do not say which fact a prompt is\n * about; in a small fact corpus they would otherwise look rare.\n */\nconst GENERIC_TERMS = new Set(\n (\n \"add added adding bug build call change changed changes check code config configure configuration \" +\n \"create data default delete error errors feature file files fix fixed handle issue line load logic \" +\n \"new option options problem project read remove result return run running save set setting settings \" +\n \"show step support test tests thing time update updated use used value values version want way work \" +\n \"works write\"\n ).split(\" \"),\n);\n/** A lone shared term admits a fact only if at most this share of the facts hold it… */\nconst RARE_TERM_SHARE = 0.005;\n/** …or at most this many (so small corpora are not held to a fraction of one fact). */\nconst RARE_TERM_MIN_DF = 2;\n\n/**\n * Lexical admission for per-prompt fact recall (#9): at least two distinct\n * non-generic shared terms, or one that is rare across the fact corpus\n * (document frequency ≤ max(2, 0.5% of facts)) and not generic. One common\n * word in common (\"session\" in an unrelated deploy prompt) is a coincidence.\n */\nexport function admitsLexically(\n shared: string[],\n documentFrequency: Map<string, number>,\n corpusSize: number,\n): boolean {\n const specific = shared.filter((term) => !GENERIC_TERMS.has(term));\n if (specific.length >= 2) return true;\n const rare = Math.max(RARE_TERM_MIN_DF, Math.floor(RARE_TERM_SHARE * corpusSize));\n return specific.some((term) => (documentFrequency.get(term) ?? 0) <= rare);\n}\n\n/** Words every captured decision shares (\"we decided to use …\"); they carry no claim. */\nconst CLAIM_BOILERPLATE = new Set([\n \"decided\",\n \"decide\",\n \"decision\",\n \"made\",\n \"use\",\n \"uses\",\n \"used\",\n \"using\",\n \"now\",\n \"going\",\n \"will\",\n]);\n\n/**\n * Jaccard overlap of two texts' content words (stopwords dropped), 0..1.\n * Used to spot near-duplicate facts: at capture (so a reworded decision is not\n * saved twice) and at recall (so two copies never take two of the few slots).\n */\nexport function termOverlap(a: string, b: string): number {\n const terms = (text: string) =>\n new Set(\n (text.toLowerCase().match(/[\\p{L}\\p{N}_]+/gu) ?? []).filter(\n (term) => !QUERY_STOPWORDS.has(term) && !CLAIM_BOILERPLATE.has(term),\n ),\n );\n const left = terms(a);\n const right = terms(b);\n if (left.size === 0 || right.size === 0) return 0;\n let shared = 0;\n for (const term of left) if (right.has(term)) shared += 1;\n return shared / (left.size + right.size - shared);\n}\n\n/** Score complete-word evidence, favoring explicit fact fields over free text. */\nexport function lexicalFactScore(\n fact: { subject: string; object: string; content: string },\n terms: string[],\n): number {\n if (terms.length === 0) return 0;\n const structured = new Set(\n `${fact.subject} ${fact.object}`.toLowerCase().match(/[\\p{L}\\p{N}_]+/gu) ?? [],\n );\n const prose = new Set(fact.content.toLowerCase().match(/[\\p{L}\\p{N}_]+/gu) ?? []);\n const points = terms.reduce(\n (total, term) => total + (structured.has(term) ? 2 : 0) + (prose.has(term) ? 1 : 0),\n 0,\n );\n return points / (3 * terms.length);\n}\n\nexport function cosine(a: number[], b: number[]): number {\n if (a.length !== b.length) return 0;\n let dot = 0;\n let na = 0;\n let nb = 0;\n for (let i = 0; i < a.length; i++) {\n const av = a[i] ?? 0;\n const bv = b[i] ?? 0;\n dot += av * bv;\n na += av * av;\n nb += bv * bv;\n }\n const denom = Math.sqrt(na) * Math.sqrt(nb);\n return denom === 0 ? 0 : dot / denom;\n}\n\n/** Narrow convention intents, shared by PG and the in-memory repository. These\n * admit existing evidence, never synthesize a language, version or numeric value.\n * Patterns deliberately avoid broad aliases such as \"requires\" on its own. */\nexport function memoryConceptPatterns(query: string): string[] {\n const text = query.slice(0, 4096).toLowerCase();\n const patterns: string[] = [];\n if (\n /\\b(?:lines?|formatter|formatting)\\b/.test(text) &&\n /\\b(?:wide|width|length|long|characters?|columns?)\\b/.test(text)\n ) {\n patterns.push(\"line[_ -]?(length|width)|max[_ -]?line[_ -]?(length|width)|print[_ -]?width\");\n }\n if (\n /\\b(?:interpreter|runtime|language|python|node|ruby|php|java|jdk|dotnet|rust|golang)\\b/.test(\n text,\n ) &&\n /\\b(?:oldest|earliest|minimum|min|version|release|supports?)\\b/.test(text)\n ) {\n patterns.push(\n \"requires[-_ ]?(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang)|\" +\n \"(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang|interpreter|runtime)[^.!?]{0,40}(>=|minimum|newer)|\" +\n \"minimum[^.!?]{0,40}(python|node(js)?|ruby|php|java|jdk|dotnet|rust|golang|interpreter|runtime)\",\n );\n }\n return patterns;\n}\n\n/** Supporting lexical evidence, not a semantic probability. */\nexport const MEMORY_CONCEPT_SCORE = 0.5;\nexport function memoryConceptScore(text: string, patterns: string[]): number {\n return patterns.some((pattern) => new RegExp(pattern, \"i\").test(text)) ? MEMORY_CONCEPT_SCORE : 0;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,eAAiB;AAAA,IACf,QAAU;AAAA,EACZ;AAAA,EACA,QAAU;AAAA,EACV,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,KAAO;AAAA,IACL,WAAa;AAAA,EACf;AAAA,EACA,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,QAAU;AAAA,IACZ;AAAA,IACA,eAAe;AAAA,MACb,OAAS;AAAA,MACT,QAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,OAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,eAAe;AAAA,IACf,cAAc;AAAA,IACd,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,WAAW;AAAA,IACX,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,kBAAkB;AAAA,IAClB,MAAQ;AAAA,IACR,QAAU;AAAA,IACV,gBAAkB;AAAA,IAClB,iBAAiB;AAAA,EACnB;AAAA,EACA,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,UAAY;AAAA,EACZ,MAAQ;AAAA,IACN,KAAO;AAAA,EACT;AAAA,EACA,cAAgB;AAAA,IACd,kBAAkB;AAAA,IAClB,0BAA0B;AAAA,IAC1B,6BAA6B;AAAA,IAC7B,WAAa;AAAA,IACb,eAAe;AAAA,IACf,eAAe;AAAA,IACf,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,IACb,YAAc;AAAA,IACd,MAAQ;AAAA,IACR,UAAY;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,kBAAoB;AAAA,IAClB,wBAAwB;AAAA,EAC1B;AAAA,EACA,sBAAwB;AAAA,IACtB,wBAAwB;AAAA,MACtB,UAAY;AAAA,IACd;AAAA,EACF;AAAA,EACA,iBAAmB;AAAA,IACjB,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,eAAe;AAAA,IACf,MAAQ;AAAA,IACR,KAAO;AAAA,IACP,YAAc;AAAA,IACd,QAAU;AAAA,EACZ;AAAA,EACA,sBAAwB;AAAA,IACtB,6BAA6B;AAAA,EAC/B;AACF;;;AC9GO,IAAM,UAAkB,gBAAI;;;ACTnC,SAAS,OAAO,SAAS,UAAU,IAAI,YAAY;AACnD,SAAS,SAAS,MAAM,UAAU,SAAS,WAAW;AACtD,SAAS,SAAS,WAAW,aAAa,qBAAqB;AAC/D,SAAS,SAAS;;;ACcX,SAAS,iBACd,MACA,UAA6D,CAAC,GACrD;AACT,MAAI,QAAQ,aAAa,KAAK,cAAc,QAAQ,UAAW,QAAO;AACtE,MAAI,KAAK,iBAAiB,KAAK,aAAc,QAAO;AACpD,MAAI,KAAK,aAAa,cAAc,KAAK,cAAc,CAAC,QAAQ,gBAAiB,QAAO;AACxF,SAAO;AACT;;;ADXO,IAAM,kBAAkB,CAAC,WAAW,UAAU;AAE9C,IAAM,sBAAsB,CAAC,OAAO,UAAU,QAAQ,UAAU;AAGvE,IAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,eAAe,EAAE,QAAQ,CAAC;AAAA,EAC1B,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB;AAAA,EACvC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,MAAM,EAAE,QAAQ,MAAM;AAAA,EACtB,SAAS,EAAE,OAAO;AAAA,EAClB,WAAW,EAAE,OAAO;AAAA,EACpB,QAAQ,EAAE,OAAO;AAAA;AAAA,EAEjB,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA;AAAA,EAEzD,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAClE,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,EACnC,WAAW,EAAE;AAAA,IACX,CAAC,UAAW,iBAAiB,OAAO,MAAM,YAAY,IAAI;AAAA,IAC1D,EAAE,OAAO,EAAE,SAAS;AAAA,EACtB;AAAA,EACA,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,EACpC,qBAAqB,EAAE,OAAO,EAAE,SAAS;AAAA,EACzC,eAAe,EAAE,QAAQ;AAAA,EACzB,UAAU,EAAE,KAAK,eAAe,EAAE,QAAQ,SAAS;AAAA,EACnD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC5C,UAAU,EAAE,KAAK,mBAAmB,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAC7D,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAChD,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAC9C,aAAa,EACV,OAAO,EACP,MAAM,gBAAgB,EACtB,SAAS,EACT,QAAQ,IAAI;AAAA,EACf,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EAChD,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EACnD,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC5C,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA;AAAA,EAEhD,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC;AAED,IAAM,2BAA2B,EAAE,OAAO;AAAA,EACxC,eAAe,EAAE,QAAQ,CAAC;AAAA,EAC1B,IAAI,EAAE,OAAO,EAAE,MAAM,iBAAiB;AAAA,EACtC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,MAAM,EAAE,QAAQ,SAAS;AAAA,EACzB,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACnC,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACzC,WAAW,EAAE;AAAA,IACX,CAAC,UAAW,iBAAiB,OAAO,MAAM,YAAY,IAAI;AAAA,IAC1D,EAAE,OAAO,EAAE,SAAS;AAAA,EACtB;AAAA,EACA,gBAAgB,EAAE,OAAO,EAAE,SAAS;AACtC,CAAC;AAyDD,IAAM,aAAa;AAGnB,IAAM,cAAc,oBAAI,IAAqE;AAC7F,IAAM,eAAe,oBAAI,IAAY;AAUrC,SAAS,eAAe,MAAc,WAA2B;AAC/D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,UAAU,MAAM,GAAG,EAAE,GAAG,EAAE,KAAK,QAAQ;AAAA,EACnD;AACF;AAEA,eAAe,eAAe,MAAc,WAAkC;AAC5E,MAAI,aAAa,IAAI,SAAS,EAAG;AACjC,eAAa,IAAI,SAAS;AAC1B,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,MAAM,SAAS,eAAe,MAAM,SAAS,GAAG,MAAM,CAAC;AAI9E,QAAI,IAAI,YAAY,QAAS;AAC7B,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,IAAI,KAAK,GAAG;AACrD,YAAM,OAAO,KAAK,WAAW,IAAI;AACjC,UAAI,CAAC,YAAY,IAAI,IAAI,EAAG,aAAY,IAAI,MAAM,KAAK;AAAA,IACzD;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEA,eAAe,eAAe,MAAc,WAAkC;AAC5E,QAAM,QAAiC,CAAC;AACxC,aAAW,CAAC,MAAM,KAAK,KAAK,aAAa;AACvC,QAAI,QAAQ,IAAI,MAAM,UAAW,OAAM,SAAS,WAAW,IAAI,CAAC,IAAI;AAAA,EACtE;AACA,MAAI;AACF,UAAM,OAAO,eAAe,MAAM,SAAS;AAC3C,UAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,UAAM,YAAY,MAAM,KAAK,UAAU,EAAE,SAAS,SAAS,MAAM,CAAC,CAAC;AAAA,EACrE,QAAQ;AAAA,EAER;AACF;AAEO,IAAM,kBAAN,MAAsB;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,iBAAiB,QAAQ,IAAI,GAAG;AAC1C,SAAK,iBAAiB,QAAQ,cAAc;AAC5C,SAAK,iBAAiB,KAAK,KAAK,gBAAgB,cAAc,UAAU,OAAO;AAC/E,SAAK,qBAAqB,KAAK,KAAK,gBAAgB,cAAc,UAAU,WAAW;AAAA,EACzF;AAAA,EAEA,MAAM,UAAU,KAAqC;AAGnD,UAAM,QAAQ,gBAAgB,GAAG;AACjC,UAAM,OAAO,KAAK,SAAS,MAAM,EAAE;AACnC,UAAM,KAAK,iBAAiB,KAAK,cAAc;AAC/C,UAAM,uBAAuB,KAAK,gBAAgB,QAAQ,IAAI,CAAC;AAE/D,UAAM,WAAW;AAAA,MACf,eAAe;AAAA,MACf,IAAI,MAAM;AAAA,MACV,WAAW,MAAM;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,MAAM;AAAA,MACf,WAAW,MAAM;AAAA,MACjB,QAAQ,MAAM;AAAA,MACd,gBAAgB,MAAM;AAAA,MACtB,gBAAgB,MAAM;AAAA,MACtB,YAAY,MAAM;AAAA,MAClB,WAAW,MAAM;AAAA,MACjB,gBAAgB,MAAM;AAAA,MACtB,qBAAqB,MAAM;AAAA,MAC3B,eAAe,MAAM;AAAA,MACrB,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM;AAAA,MACb,UAAU,MAAM;AAAA,MAChB,cAAc,MAAM;AAAA,MACpB,YAAY,MAAM;AAAA,MAClB,aAAa,MAAM;AAAA,MACnB,cAAc,MAAM;AAAA,MACpB,iBAAiB,MAAM;AAAA,MACvB,cAAc,MAAM;AAAA,MACpB,cAAc,MAAM;AAAA,MACpB,YAAY,MAAM;AAAA,IACpB;AACA,UAAM,cAAc,cAAc,uCAAuC,QAAQ,CAAC;AAElF,UAAM,YAAY,MAAM,gBAAgB,aAAa,MAAM,OAAO,CAAC;AACnE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAa,OAA0C;AAC3D,UAAM,OAAO,KAAK,YAAY,MAAM,EAAE;AACtC,UAAM,KAAK,iBAAiB,KAAK,kBAAkB;AACnD,UAAM,uBAAuB,KAAK,gBAAgB,QAAQ,IAAI,CAAC;AAC/D,UAAM,WAAW,cAAc;AAAA,MAC7B,eAAe;AAAA,MACf,IAAI,MAAM;AAAA,MACV,WAAW,MAAM;AAAA,MACjB,MAAM;AAAA,MACN,WAAW,MAAM;AAAA,MACjB,SAAS,MAAM;AAAA,MACf,YAAY,MAAM;AAAA,MAClB,WAAW,MAAM;AAAA,MACjB,gBAAgB,MAAM;AAAA,IACxB,CAAC;AACD,UAAM,YAAY,MAAM,gBAAgB,UAAU,MAAM,OAAO,CAAC;AAChE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,UAAqC;AAClD,WAAO,KAAK,aAAa,UAAU,KAAK;AAAA,EAC1C;AAAA;AAAA,EAGA,MAAc,aAAa,UAAkB,SAAqC;AAChF,UAAM,OAAO,SAAS,SAAS,KAAK,IAAI,KAAK,SAAS,QAAQ,IAAI,KAAK,SAAS,QAAQ;AACxF,QAAI,CAAC,QAAS,OAAM,uBAAuB,KAAK,gBAAgB,IAAI;AACpE,UAAM,SAAS,iBAAiB,MAAM,SAAS,MAAM,MAAM,CAAC;AAC5D,UAAM,WAAW,sBAAsB,MAAM,UAAU,OAAO,WAAW,CAAC;AAC1E,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,OAAO,QAAQ,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,YAAiC;AACrC,YAAQ,MAAM,KAAK,UAAU,GAAG;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,YAA0E;AAC9E,UAAM,OAAO,MAAM,KAAK;AAAA,MAAK,KAAK;AAAA,MAAgB;AAAA,MAAwB,CAAC,MAAM,YAC/E,KAAK,aAAa,MAAM,OAAO;AAAA,IACjC;AACA,WAAO,EAAE,OAAO,KAAK,OAAO,SAAS,KAAK,QAAQ;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,kBACJ,QAA2D,CAAC,GAC/B;AAC7B,UAAM,aAAa,MAAM,KAAK,UAAU,GAAG;AAAA,MACzC,CAAC,SACC,KAAK,aAAa,cAAc,QAAQ,KAAK,QAAQ,KAAK,iBAAiB,MAAM,KAAK;AAAA,IAC1F;AAEA,WAAO,UAAU,KAAK,CAAC,MAAM,UAAU;AACrC,YAAM,WAAW,aAAa,MAAM,QAAQ,IAAI,aAAa,KAAK,QAAQ;AAC1E,UAAI,aAAa,EAAG,QAAO;AAC3B,aAAO,MAAM,UAAU,cAAc,KAAK,SAAS;AAAA,IACrD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,0BAA0B,OAIA;AAC9B,UAAM,SAAS,IAAI,IAAI,MAAM,MAAM,IAAI,mBAAmB,CAAC;AAC3D,QAAI,OAAO,SAAS,EAAG,QAAO,CAAC;AAE/B,YACE,MAAM,KAAK,kBAAkB;AAAA,MAC3B,GAAI,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,MACtE,GAAI,MAAM,oBAAoB,SAAY,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;AAAA,IAC1F,CAAC,GACD,OAAO,CAAC,SAAS,KAAK,MAAM,IAAI,mBAAmB,EAAE,KAAK,CAAC,SAAS,OAAO,IAAI,IAAI,CAAC,CAAC;AAAA,EACzF;AAAA,EAEA,MAAM,YAAY,UAAwC;AACxD,UAAM,OAAO,SAAS,SAAS,KAAK,IAAI,KAAK,SAAS,QAAQ,IAAI,KAAK,YAAY,QAAQ;AAC3F,UAAM,uBAAuB,KAAK,gBAAgB,IAAI;AACtD,UAAM,SAAS,iBAAiB,MAAM,SAAS,MAAM,MAAM,CAAC;AAC5D,WAAO;AAAA,MACL,GAAG,yBAAyB,MAAM,UAAU,OAAO,WAAW,CAAC;AAAA,MAC/D,SAAS,OAAO,QAAQ,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,gBAAwC;AAC5C,YAAQ,MAAM,KAAK,cAAc,GAAG;AAAA,EACtC;AAAA,EAEA,MAAM,gBAAqF;AACzF,UAAM,OAAO,MAAM,KAAK;AAAA,MAAK,KAAK;AAAA,MAAoB;AAAA,MAAuB,CAAC,SAC5E,KAAK,YAAY,IAAI;AAAA,IACvB;AACA,WAAO,EAAE,WAAW,KAAK,OAAO,SAAS,KAAK,QAAQ;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,KACZ,WACA,aACA,MACuD;AACvD,QAAI;AACF,YAAM,KAAK,iBAAiB,SAAS;AACrC,YAAM,uBAAuB,KAAK,gBAAgB,SAAS;AAC3D,YAAM,eAAe,KAAK,gBAAgB,SAAS;AACnD,UAAI,WAAW;AACf,YAAM,WAAW,MAAM,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC,GAC9D,OAAO,CAAC,UAAU,MAAM,OAAO,KAAK,YAAY,KAAK,MAAM,IAAI,CAAC,EAChE,KAAK,CAAC,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AAE5D,YAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,KAAK,WAAW,MAAM,IAAI,CAAC,CAAC;AACxE,iBAAW,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,GAAG;AAC1C,YAAI,QAAQ,IAAI,MAAM,aAAa,CAAC,KAAK,IAAI,IAAI,GAAG;AAClD,sBAAY,OAAO,IAAI;AACvB,qBAAW;AAAA,QACb;AAAA,MACF;AACA,YAAM,WAAgE,CAAC;AACvE,eAAS,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,YAAY;AAC/D,cAAM,QAAQ,QAAQ,MAAM,OAAO,QAAQ,UAAU;AACrD,iBAAS;AAAA,UACP,GAAI,MAAM,QAAQ;AAAA,YAChB,MAAM,IAAI,OAAO,UAAU;AACzB,oBAAM,OAAO,KAAK,WAAW,MAAM,IAAI;AACvC,oBAAM,KAAK,MAAM,KAAK,MAAM,GAAG,CAAC,MAAM,MAAM;AAC5C,kBAAI;AACF,sBAAM,OAAO,MAAM,KAAK,IAAI;AAC5B,sBAAM,SAAS,YAAY,IAAI,IAAI;AACnC,sBAAM,QACJ,WAAW,UACX,OAAO,YAAY,KAAK,WACxB,OAAO,SAAS,KAAK;AACvB,sBAAM,OAAO,QAAS,OAAO,OAAa,MAAM,KAAK,MAAM,IAAI;AAC/D,oBAAI,CAAC,MAAO,YAAW;AACvB,oBAAI,KAAK,OAAO,IAAI;AAClB,wBAAM,IAAI,MAAM,WAAW,EAAE,iCAAiC,KAAK,EAAE,EAAE;AAAA,gBACzE;AACA,4BAAY,IAAI,MAAM,EAAE,SAAS,KAAK,SAAS,MAAM,KAAK,MAAM,KAAK,CAAC;AACtE,uBAAO,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE;AAAA,cAC7B,SAAS,OAAO;AACd,4BAAY,OAAO,IAAI;AACvB,uBAAO;AAAA,kBACL,SAAS;AAAA,oBACP;AAAA,oBACA;AAAA,oBACA,OAAQ,MAAgB,QAAQ,MAAM,IAAI,EAAE,CAAC,GAAG,MAAM,GAAG,GAAG,KAAK;AAAA,kBACnE;AAAA,gBACF;AAAA,cACF;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AACA,UAAI,SAAU,OAAM,eAAe,KAAK,gBAAgB,SAAS;AACjE,YAAM,SAAuD,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AACtF,iBAAW,WAAW,UAAU;AAC9B,YAAI,UAAU,QAAS,QAAO,MAAM,KAAK,QAAQ,IAAI;AAAA,YAChD,QAAO,QAAQ,KAAK,QAAQ,OAAO;AAAA,MAC1C;AACA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAK,MAAgC,SAAS,SAAU,QAAO,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AACxF,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,IAA2B;AAC1C,UAAM,GAAG,KAAK,SAAS,EAAE,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EAC7C;AAAA,EAEA,MAAM,qBAAqB,IAAY,YAAmC;AACxE,UAAM,OAAO,MAAM,KAAK,SAAS,EAAE;AACnC,UAAM,KAAK,UAAU,SAAS,MAAM,EAAE,WAAW,CAAC,CAAC;AAAA,EACrD;AAAA,EAEA,MAAM,mBAAmB,IAAY,cAAqC;AACxE,UAAM,OAAO,MAAM,KAAK,SAAS,EAAE;AACnC,UAAM,KAAK,UAAU,SAAS,MAAM,EAAE,aAAa,CAAC,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,kBAAkB,IAAY,YAAqC;AACvE,UAAM,OAAO,MAAM,KAAK,SAAS,EAAE;AACnC,UAAM,KAAK,UAAU,SAAS,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAAA,EACtF;AAAA;AAAA,EAGA,MAAM,6BACJ,WACA,qBACe;AACf,UAAM,EAAE,OAAAA,OAAM,IAAI,MAAM,KAAK,UAAU;AACvC,eAAW,QAAQA,QAAO;AACxB,UAAI,KAAK,cAAc,aAAa,KAAK,wBAAwB;AAC/D;AACF,UAAI,KAAK,cAAe;AACxB,YAAM,KAAK,UAAU,SAAS,MAAM,EAAE,eAAe,KAAK,CAAC,CAAC;AAAA,IAC9D;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,sBAAsB,IAA2B;AACrD,UAAM,OAAO,MAAM,KAAK,SAAS,EAAE;AACnC,UAAM,KAAK,UAAU,SAAS,MAAM,EAAE,eAAe,KAAK,CAAC,CAAC;AAAA,EAC9D;AAAA,EAEQ,SAAS,IAAoB;AACnC,QAAI,CAAC,mBAAmB,KAAK,EAAE,GAAG;AAChC,YAAM,IAAI,MAAM,oBAAoB,EAAE,EAAE;AAAA,IAC1C;AACA,WAAO,KAAK,SAAS,KAAK,KAAK,gBAAgB,GAAG,EAAE,KAAK,CAAC;AAAA,EAC5D;AAAA,EAEQ,YAAY,IAAoB;AACtC,QAAI,CAAC,kBAAkB,KAAK,EAAE,EAAG,OAAM,IAAI,MAAM,uBAAuB,EAAE,EAAE;AAC5E,WAAO,KAAK,SAAS,KAAK,KAAK,oBAAoB,GAAG,EAAE,KAAK,CAAC;AAAA,EAChE;AAAA,EAEQ,SAAS,MAAsB;AACrC,UAAM,WAAW,QAAQ,IAAI;AAC7B,UAAM,eAAe,SAAS,KAAK,gBAAgB,QAAQ;AAC3D,QAAI,iBAAiB,QAAQ,aAAa,WAAW,KAAK,GAAG,EAAE,KAAK,iBAAiB,IAAI;AACvF,YAAM,IAAI,MAAM,wCAAwC,IAAI,EAAE;AAAA,IAChE;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,iBAAiB,WAAkC;AAC/D,UAAM,uBAAuB,KAAK,gBAAgB,QAAQ,SAAS,CAAC;AACpE,UAAM,MAAM,WAAW,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAAA,EACzD;AACF;AAEA,SAAS,SACP,MACA,OACe;AACf,QAAM,EAAE,MAAM,OAAO,eAAe,gBAAgB,MAAM,OAAO,GAAG,MAAM,IAAI;AAC9E,SAAO,EAAE,GAAG,OAAO,GAAG,MAAM;AAC9B;AAEA,SAAS,uCACP,UACyB;AACzB,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,MAAM;AAChD,UAAI,UAAU,OAAW,QAAO;AAChC,UAAI,QAAQ,cAAc,UAAU,UAAW,QAAO;AACtD,WAAK,QAAQ,WAAW,QAAQ,iBAAiB,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW;AACxF,eAAO;AACT,UACE,CAAC,YAAY,gBAAgB,cAAc,kBAAkB,gBAAgB,EAAE;AAAA,QAC7E;AAAA,MACF,MACC,UAAU,QAAQ,UAAU,SAC7B;AACA,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAEA,SAAS,oBAAoB,MAAsB;AACjD,SAAO,KAAK,QAAQ,OAAO,GAAG,EAAE,QAAQ,SAAS,EAAE;AACrD;AAEA,SAAS,aAAa,UAAoC;AACxD,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;;;AEvhBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,WAAW;AACpB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,IAAM,oBAAoB;AAC1B,IAAM,SAAS;AACf,IAAM,uBAAuB;AAEpC,IAAM,SAAS,WAAiD;AAAA,EAC9D,WAAW;AACT,WAAO,UAAU,iBAAiB;AAAA,EACpC;AACF,CAAC;AAEM,IAAM,iBAAiB,OAAO,eAAe,CAAC,eAAe,QAAQ,SAAS,CAAC;AAC/E,IAAM,sBAAsB,OAAO,oBAAoB,CAAC,QAAQ,aAAa,QAAQ,CAAC;AACtF,IAAM,yBAAyB,OAAO,wBAAwB;AAAA,EACnE;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACM,IAAM,sBAAsB,OAAO,oBAAoB;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACM,IAAM,gBAAgB,OAAO,cAAc,CAAC,aAAa,UAAU,CAAC;AACpE,IAAM,oBAAoB,OAAO,mBAAmB,CAAC,aAAa,KAAK,CAAC;AACxE,IAAM,sBAAsB,OAAO,qBAAqB;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACM,IAAM,kBAAkB,OAAO,gBAAgB;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,YAAY,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AACvF,IAAM,YAAY,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ,EAAE,WAAW;AAEhF,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,IAC3B;AAAA,IACA;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,YAAY,YAAY,wBAAwB,EAAE,GAAG,MAAM,IAAI;AAAA,EACjE;AACF;AAEO,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,aAAa,KAAK,cAAc,EAC7B,QAAQ,EACR,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IAC1D,WAAW,KAAK,YAAY,EAAE,QAAQ;AAAA,IACtC,MAAM,oBAAoB,MAAM,EAAE,QAAQ,EAAE,QAAQ,MAAM;AAAA,IAC1D,SAAS,KAAK,SAAS,EAAE,QAAQ;AAAA,IACjC,aAAa,KAAK,cAAc,EAAE,QAAQ;AAAA,IAC1C,YAAY,QAAQ,aAAa,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAAA,IACtD,gBAAgB,KAAK,kBAAkB;AAAA,IACvC;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,4BAA4B,YAAY,4CAA4C,EAAE;AAAA,MACpF,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,IACA,4BAA4B,MAAM,4CAA4C,EAAE;AAAA,MAC9E,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,aAAa,KAAK,cAAc,EAC7B,QAAQ,EACR,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IAC1D,WAAW,KAAK,YAAY,EAAE,QAAQ;AAAA,IACtC,SAAS,KAAK,SAAS,EAAE,QAAQ;AAAA,IACjC,SAAS,QAAQ,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAAA,IAC/C,YAAY,QAAQ,aAAa,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAAA,IACtD,gBAAgB,KAAK,kBAAkB;AAAA;AAAA,IAEvC,YAAY,QAAQ,aAAa,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,IAC1D;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,4BAA4B,MAAM,iDAAiD,EAAE;AAAA,MACnF,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,IAAM,QAAQ;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,aAAa,KAAK,cAAc,EAC7B,QAAQ,EACR,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IAC1D,SAAS,KAAK,SAAS,EAAE,QAAQ;AAAA,IACjC,WAAW,KAAK,WAAW,EAAE,QAAQ;AAAA,IACrC,QAAQ,KAAK,QAAQ,EAAE,QAAQ;AAAA,IAC/B,gBAAgB,KAAK,iBAAiB;AAAA;AAAA,IAEtC,gBAAgB,KAAK,iBAAiB;AAAA,IACtC,SAAS,KAAK,SAAS,EAAE,QAAQ;AAAA,IACjC,aAAa,KAAK,cAAc,EAAE,QAAQ;AAAA,IAC1C,YAAY,QAAQ,cAAc,EAAE,WAAW,GAAG,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMnF,qBAAqB,KAAK,uBAAuB;AAAA,IACjD,eAAe,QAAQ,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,IAChE,cAAc,KAAK,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlC,YAAY,QAAQ,aAAa,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,IAC1D,gBAAgB,KAAK,iBAAiB;AAAA,IACtC,gBAAgB,KAAK,kBAAkB;AAAA,IACvC;AAAA,IACA;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,4BAA4B,YAAY,qCAAqC,EAAE;AAAA,MAC7E,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,IACA,qBAAqB,MAAM,6BAA6B,EAAE,GAAG,MAAM,aAAa,MAAM,OAAO;AAAA,IAC7F,yBAAyB,MAAM,iCAAiC,EAAE;AAAA,MAChE,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,IACA,sBAAsB,MAAM,8BAA8B,EAAE,GAAG,MAAM,mBAAmB;AAAA,EAC1F;AACF;AAEO,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,aAAa,KAAK,cAAc,EAC7B,QAAQ,EACR,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IAC1D,WAAW,uBAAuB,YAAY,EAAE,QAAQ;AAAA,IACxD,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,QAAQ,OAAO,QAAQ,EAAE,QAAQ;AAAA,IACjC,gBAAgB,KAAK,iBAAiB,EAAE,QAAQ;AAAA,IAChD,gBAAgB,KAAK,iBAAiB;AAAA,IACtC,QAAQ,oBAAoB,QAAQ,EAAE,QAAQ,EAAE,QAAQ,SAAS;AAAA,IACjE,UAAU,QAAQ,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAAA,IACjD,WAAW,KAAK,YAAY;AAAA;AAAA,IAE5B,YAAY,UAAU,eAAe,EAAE,cAAc,KAAK,CAAC;AAAA,IAC3D,YAAY,KAAK,aAAa;AAAA,IAC9B;AAAA,IACA;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,aAAa,YAAY,yBAAyB,EAAE,GAAG,MAAM,WAAW,MAAM,OAAO;AAAA,IACrF,yBAAyB,MAAM,uCAAuC,EAAE;AAAA,MACtE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,IACA,yBAAyB,MAAM,uCAAuC,EAAE;AAAA,MACtE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,IACA,eAAe,MAAM,4BAA4B,EAAE;AAAA,MACjD;AAAA,MACA,MAAM,MAAM,MAAM;AAAA,IACpB;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AAAA,EACpB;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,iBAAiB,KAAK,mBAAmB,EACtC,QAAQ,EACR,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IAC1D,eAAe,KAAK,iBAAiB,EAClC,QAAQ,EACR,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IAC1D,cAAc,KAAK,gBAAgB,EAChC,QAAQ,EACR,WAAW,MAAM,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IACrD,gBAAgB,KAAK,kBAAkB,EAAE,WAAW,MAAM,MAAM,IAAI,EAAE,UAAU,WAAW,CAAC;AAAA,IAC5F,MAAM,cAAc,MAAM,EAAE,QAAQ,EAAE,QAAQ,WAAW;AAAA,IACzD,YAAY,QAAQ,YAAY,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,IACzD,gBAAgB,KAAK,kBAAkB;AAAA,IACvC;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,aAAa,YAAY,6BAA6B,EAAE;AAAA,MACtD,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,IACA,kBAAkB,MAAM,2BAA2B,EAAE,GAAG,MAAM,eAAe;AAAA,EAC/E;AACF;AAEO,IAAM,cAAc;AAAA,EACzB;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,aAAa,KAAK,cAAc,EAC7B,QAAQ,EACR,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IAC1D,UAAU,KAAK,WAAW,EAAE,QAAQ;AAAA,IACpC,cAAc,KAAK,eAAe,EAAE,QAAQ;AAAA,IAC5C,UAAU,KAAK,WAAW;AAAA,IAC1B,eAAe,MAAM,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,gBAAgB;AAAA,IACzE,UAAU,MAAM,UAAU,EAAE,QAAQ,EAAE,QAAQ,gBAAgB;AAAA,IAC9D;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,kBAAkB,YAAY,sCAAsC,EAAE;AAAA,MACpE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,IAAM,aAAa;AAAA,EACxB;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,aAAa,KAAK,cAAc,EAAE,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,WAAW,CAAC;AAAA,IAC1F,OAAO,KAAK,OAAO,EAAE,QAAQ;AAAA,IAC7B,MAAM,kBAAkB,MAAM,EAAE,QAAQ;AAAA,IACxC,WAAW,QAAQ,YAAY;AAAA,IAC/B,QAAQ,QAAQ,QAAQ;AAAA,IACxB,QAAQ,oBAAoB,QAAQ,EAAE,QAAQ;AAAA,IAC9C,OAAO,KAAK,OAAO;AAAA,IACnB,UAAU,MAAM,UAAU,EAAE,QAAQ,EAAE,QAAQ,gBAAgB;AAAA,IAC9D;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,iBAAiB,MAAM,+BAA+B,EAAE,GAAG,MAAM,OAAO,MAAM,SAAS;AAAA,IACvF,qBAAqB,MAAM,mCAAmC,EAAE;AAAA,MAC9D,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,IAAM,WAAW;AAAA,EACtB;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,aAAa,KAAK,cAAc,EAAE,WAAW,MAAM,WAAW,IAAI,EAAE,UAAU,WAAW,CAAC;AAAA,IAC1F,QAAQ,gBAAgB,QAAQ,EAAE,QAAQ;AAAA,IAC1C,YAAY,KAAK,aAAa,EAAE,QAAQ;AAAA,IACxC,UAAU,KAAK,WAAW;AAAA,IAC1B,gBAAgB,KAAK,kBAAkB;AAAA,IACvC,UAAU,MAAM,UAAU,EAAE,QAAQ,EAAE,QAAQ,gBAAgB;AAAA,IAC9D;AAAA,EACF;AAAA,EACA,CAAC,WAAW;AAAA,IACV,2BAA2B,MAAM,wCAAwC,EAAE;AAAA,MACzE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AAAA,EACF;AACF;;;AChTO,SAAS,eAAe,OAAuB;AACpD,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,KAAK,MAAM,KAAK,CAAC,CAAC;AACtD;AAGO,SAAS,kBAAkBC,SAA2B;AAC3D,SACEA,QAAO,WAAW,qBAClBA,QAAO,MAAM,CAAC,UAAU,OAAO,SAAS,KAAK,OAAO,KAAK,CAAC,CAAC,KAC3DA,QAAO,KAAK,CAAC,UAAU,UAAU,CAAC;AAEtC;AAGA,IAAM,kBAAkB,oBAAI,IAAI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,aAAa,OAAyB;AACpD,SAAO;AAAA,IACL,GAAG,IAAI;AAAA,MACL,MACG,MAAM,GAAG,IAAI,EACb,YAAY,EACZ,MAAM,kBAAkB,KAAK,CAAC;AAAA,IACnC;AAAA,EACF,EACG,OAAO,CAAC,SAAS,CAAC,gBAAgB,IAAI,IAAI,CAAC,EAC3C,MAAM,GAAG,EAAE,EACX,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,EAAE,CAAC;AACpC;AAMA,IAAM,gBAAgB,IAAI;AAAA,EAEtB,uZAKA,MAAM,GAAG;AACb;AAEA,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;AAQlB,SAAS,gBACd,QACA,mBACA,YACS;AACT,QAAM,WAAW,OAAO,OAAO,CAAC,SAAS,CAAC,cAAc,IAAI,IAAI,CAAC;AACjE,MAAI,SAAS,UAAU,EAAG,QAAO;AACjC,QAAM,OAAO,KAAK,IAAI,kBAAkB,KAAK,MAAM,kBAAkB,UAAU,CAAC;AAChF,SAAO,SAAS,KAAK,CAAC,UAAU,kBAAkB,IAAI,IAAI,KAAK,MAAM,IAAI;AAC3E;AAGA,IAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,SAAS,YAAY,GAAW,GAAmB;AACxD,QAAM,QAAQ,CAACC,UACb,IAAI;AAAA,KACDA,MAAK,YAAY,EAAE,MAAM,kBAAkB,KAAK,CAAC,GAAG;AAAA,MACnD,CAAC,SAAS,CAAC,gBAAgB,IAAI,IAAI,KAAK,CAAC,kBAAkB,IAAI,IAAI;AAAA,IACrE;AAAA,EACF;AACF,QAAM,OAAO,MAAM,CAAC;AACpB,QAAM,QAAQ,MAAM,CAAC;AACrB,MAAI,KAAK,SAAS,KAAK,MAAM,SAAS,EAAG,QAAO;AAChD,MAAI,SAAS;AACb,aAAW,QAAQ,KAAM,KAAI,MAAM,IAAI,IAAI,EAAG,WAAU;AACxD,SAAO,UAAU,KAAK,OAAO,MAAM,OAAO;AAC5C;AAsCO,SAAS,sBAAsB,OAAyB;AAC7D,QAAMC,QAAO,MAAM,MAAM,GAAG,IAAI,EAAE,YAAY;AAC9C,QAAM,WAAqB,CAAC;AAC5B,MACE,sCAAsC,KAAKA,KAAI,KAC/C,sDAAsD,KAAKA,KAAI,GAC/D;AACA,aAAS,KAAK,6EAA6E;AAAA,EAC7F;AACA,MACE,wFAAwF;AAAA,IACtFA;AAAA,EACF,KACA,gEAAgE,KAAKA,KAAI,GACzE;AACA,aAAS;AAAA,MACP;AAAA,IAGF;AAAA,EACF;AACA,SAAO;AACT;AAGO,IAAM,uBAAuB;","names":["facts","vector","text","text"]}
@@ -4,12 +4,10 @@ import {
4
4
  import {
5
5
  NON_SOURCE_PATH,
6
6
  TEST_PATH,
7
+ loadFreshIndex,
7
8
  loadProjectArchitectureMap,
8
9
  readBoundedSource
9
- } from "./chunk-TLWGZVPC.js";
10
- import {
11
- loadFreshIndex
12
- } from "./chunk-ITQO632W.js";
10
+ } from "./chunk-YNSTIMCF.js";
13
11
  import {
14
12
  redactSensitive,
15
13
  scanSensitive
@@ -203,4 +201,4 @@ export {
203
201
  loadLlmSummaries,
204
202
  summarizeRepository
205
203
  };
206
- //# sourceMappingURL=chunk-6NZEZJYD.js.map
204
+ //# sourceMappingURL=chunk-ESC4Y3L7.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/llm/summaries.ts"],"sourcesContent":["import { mkdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\nimport {\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 { chatJson } from \"./client.js\";\nimport { type EnabledLlmConfig, resolveLlmConfig } from \"./config.js\";\n\n/**\n * One-line descriptions of files and their main symbols, written by the BYOK\n * model off the prompt path (`codebuddy llm summarize`, `watch`) and cached by\n * content hash. Retrieval reads them so a paraphrased task can match a file\n * whose own text never uses the task's words. Generated text is a hint for\n * ordering only; it never satisfies the evidence floor on its own.\n */\nexport const SUMMARY_PROMPT_VERSION = 1;\nconst LIMITS = {\n fileChars: 160,\n symbolChars: 100,\n signatures: 12,\n headLines: 20,\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: 120,\n tokensPerMinute: 6_000,\n timeoutMs: 20_000,\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};\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 symbols: z.array(z.object({ name: z.string(), summary: z.string() })).default([]),\n }),\n ),\n});\n\nconst SYSTEM = [\n \"You describe source files for a code search index.\",\n 'Answer with JSON only: {\"files\":[{\"path\":\"...\",\"summary\":\"...\",\"symbols\":[{\"name\":\"...\",\"summary\":\"...\"}]}]}.',\n `For each input file: one sentence (max ${LIMITS.fileChars} chars) saying what it does, using the words a developer would search for;`,\n `for its main symbols (only names listed in \"signatures\"), one short phrase each (max ${LIMITS.symbolChars} chars).`,\n \"Never invent paths or symbol names.\",\n].join(\" \");\n\nexport type SummarizeResult = {\n summarized: number;\n skipped: number;\n remaining: number;\n stoppedBy: \"done\" | \"limit\" | \"budget\" | \"cooldown\" | \"disabled\" | \"aborted\" | \"error\";\n};\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 } = {},\n): Promise<SummarizeResult> {\n const resolved = options.config ?? (await resolveLlmConfig(root));\n if (!resolved.enabled || !resolved.features.summaries) {\n return { summarized: 0, skipped: 0, remaining: 0, stoppedBy: \"disabled\" };\n }\n const config = resolved;\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 pending = await orderByImportance(\n root,\n manifest,\n Object.values(manifest.entries).filter((entry) => {\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 limit = options.limit ?? Number.POSITIVE_INFINITY;\n let summarized = 0;\n let skipped = 0;\n let index = 0;\n let stoppedBy: SummarizeResult[\"stoppedBy\"] = \"done\";\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 < (options.batchFiles ?? LIMITS.batchFiles)) {\n if (summarized + batch.length >= limit) break;\n const entry = pending[index] as IndexEntry;\n const input = await fileInput(root, entry);\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 const started = Date.now();\n const result = await chatJson(config, {\n system: SYSTEM,\n user: JSON.stringify({ files: batch.map(({ input }) => input) }),\n schema: outputSchema,\n maxTokens: LIMITS.outputTokensPerFile * batch.length,\n deadlineMs: LIMITS.timeoutMs,\n purpose: \"background\",\n repositoryRoot: root,\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 for (const { entry } of batch) {\n const answer = result.value.files.find((file) => file.path === entry.path);\n if (!answer) {\n skipped++;\n continue;\n }\n const known = new Set((entry.symbolTable ?? []).map((symbol) => symbol.name));\n store[entry.path] = {\n hash: entry.hash,\n model: config.model,\n version: SUMMARY_PROMPT_VERSION,\n summary: answer.summary.trim().slice(0, LIMITS.fileChars),\n symbols: Object.fromEntries(\n answer.symbols\n .filter((symbol) => known.has(symbol.name))\n .map((symbol) => [symbol.name, symbol.summary.trim().slice(0, LIMITS.symbolChars)]),\n ),\n };\n summarized++;\n }\n await writeStore(root, store);\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 return { summarized, skipped, remaining: pending.length - index, stoppedBy };\n}\n\ntype FileInput = {\n path: string;\n language: string;\n summary: string;\n signatures: string[];\n head: string;\n};\n\n/** What is sent for one file; null for unreadable or secret-bearing files. */\nasync function fileInput(root: string, entry: IndexEntry): Promise<FileInput | null> {\n const source = await readBoundedSource(root, entry.path);\n if (source === null || scanSensitive(source).sensitive) return null;\n return {\n path: entry.path,\n language: entry.language,\n summary: redactSensitive(entry.summary),\n signatures: (entry.symbolTable ?? [])\n .slice(0, LIMITS.signatures)\n .map((symbol) => redactSensitive(symbol.signature.slice(0, LIMITS.signatureChars))),\n head: redactSensitive(\n source.split(/\\r?\\n/).slice(0, LIMITS.headLines).join(\"\\n\").slice(0, LIMITS.headChars),\n ),\n };\n}\n\n/** Most-imported source first; tests and non-source files last. */\nasync function orderByImportance(\n root: string,\n manifest: IndexManifest,\n entries: IndexEntry[],\n): Promise<IndexEntry[]> {\n const fanIn = new Map<string, number>();\n try {\n for (const edge of (await loadProjectArchitectureMap(root, manifest)).edges) {\n fanIn.set(edge.to, (fanIn.get(edge.to) ?? 0) + 1);\n }\n } catch {\n // No map: path order is still a usable order.\n }\n const tier = (path: string) => (TEST_PATH.test(path) || NON_SOURCE_PATH.test(path) ? 1 : 0);\n return [...entries].sort(\n (left, right) =>\n tier(left.path) - tier(right.path) ||\n (fanIn.get(right.path) ?? 0) - (fanIn.get(left.path) ?? 0) ||\n left.path.localeCompare(right.path),\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/llm/summaries.ts"],"sourcesContent":["import { mkdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\nimport {\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 { chatJson } from \"./client.js\";\nimport { type EnabledLlmConfig, resolveLlmConfig } from \"./config.js\";\n\n/**\n * One-line descriptions of files and their main symbols, written by the BYOK\n * model off the prompt path (`codebuddy llm summarize`, `watch`) and cached by\n * content hash. Retrieval reads them so a paraphrased task can match a file\n * whose own text never uses the task's words. Generated text is a hint for\n * ordering only; it never satisfies the evidence floor on its own.\n */\nexport const SUMMARY_PROMPT_VERSION = 1;\nconst LIMITS = {\n fileChars: 160,\n symbolChars: 100,\n signatures: 12,\n headLines: 20,\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: 120,\n tokensPerMinute: 6_000,\n timeoutMs: 20_000,\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};\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 symbols: z.array(z.object({ name: z.string(), summary: z.string() })).default([]),\n }),\n ),\n});\n\nconst SYSTEM = [\n \"You describe source files for a code search index.\",\n 'Answer with JSON only: {\"files\":[{\"path\":\"...\",\"summary\":\"...\",\"symbols\":[{\"name\":\"...\",\"summary\":\"...\"}]}]}.',\n `For each input file: one sentence (max ${LIMITS.fileChars} chars) saying what it does, using the words a developer would search for;`,\n `for its main symbols (only names listed in \"signatures\"), one short phrase each (max ${LIMITS.symbolChars} chars).`,\n \"Never invent paths or symbol names.\",\n].join(\" \");\n\nexport type SummarizeResult = {\n summarized: number;\n skipped: number;\n remaining: number;\n stoppedBy: \"done\" | \"limit\" | \"budget\" | \"cooldown\" | \"disabled\" | \"aborted\" | \"error\";\n};\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 } = {},\n): Promise<SummarizeResult> {\n const resolved = options.config ?? (await resolveLlmConfig(root));\n if (!resolved.enabled || !resolved.features.summaries) {\n return { summarized: 0, skipped: 0, remaining: 0, stoppedBy: \"disabled\" };\n }\n const config = resolved;\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 pending = await orderByImportance(\n root,\n manifest,\n Object.values(manifest.entries).filter((entry) => {\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 limit = options.limit ?? Number.POSITIVE_INFINITY;\n let summarized = 0;\n let skipped = 0;\n let index = 0;\n let stoppedBy: SummarizeResult[\"stoppedBy\"] = \"done\";\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 < (options.batchFiles ?? LIMITS.batchFiles)) {\n if (summarized + batch.length >= limit) break;\n const entry = pending[index] as IndexEntry;\n const input = await fileInput(root, entry);\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 const started = Date.now();\n const result = await chatJson(config, {\n system: SYSTEM,\n user: JSON.stringify({ files: batch.map(({ input }) => input) }),\n schema: outputSchema,\n maxTokens: LIMITS.outputTokensPerFile * batch.length,\n deadlineMs: LIMITS.timeoutMs,\n purpose: \"background\",\n repositoryRoot: root,\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 for (const { entry } of batch) {\n const answer = result.value.files.find((file) => file.path === entry.path);\n if (!answer) {\n skipped++;\n continue;\n }\n const known = new Set((entry.symbolTable ?? []).map((symbol) => symbol.name));\n store[entry.path] = {\n hash: entry.hash,\n model: config.model,\n version: SUMMARY_PROMPT_VERSION,\n summary: answer.summary.trim().slice(0, LIMITS.fileChars),\n symbols: Object.fromEntries(\n answer.symbols\n .filter((symbol) => known.has(symbol.name))\n .map((symbol) => [symbol.name, symbol.summary.trim().slice(0, LIMITS.symbolChars)]),\n ),\n };\n summarized++;\n }\n await writeStore(root, store);\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 return { summarized, skipped, remaining: pending.length - index, stoppedBy };\n}\n\ntype FileInput = {\n path: string;\n language: string;\n summary: string;\n signatures: string[];\n head: string;\n};\n\n/** What is sent for one file; null for unreadable or secret-bearing files. */\nasync function fileInput(root: string, entry: IndexEntry): Promise<FileInput | null> {\n const source = await readBoundedSource(root, entry.path);\n if (source === null || scanSensitive(source).sensitive) return null;\n return {\n path: entry.path,\n language: entry.language,\n summary: redactSensitive(entry.summary),\n signatures: (entry.symbolTable ?? [])\n .slice(0, LIMITS.signatures)\n .map((symbol) => redactSensitive(symbol.signature.slice(0, LIMITS.signatureChars))),\n head: redactSensitive(\n source.split(/\\r?\\n/).slice(0, LIMITS.headLines).join(\"\\n\").slice(0, LIMITS.headChars),\n ),\n };\n}\n\n/** Most-imported source first; tests and non-source files last. */\nasync function orderByImportance(\n root: string,\n manifest: IndexManifest,\n entries: IndexEntry[],\n): Promise<IndexEntry[]> {\n const fanIn = new Map<string, number>();\n try {\n for (const edge of (await loadProjectArchitectureMap(root, manifest)).edges) {\n fanIn.set(edge.to, (fanIn.get(edge.to) ?? 0) + 1);\n }\n } catch {\n // No map: path order is still a usable order.\n }\n const tier = (path: string) => (TEST_PATH.test(path) || NON_SOURCE_PATH.test(path) ? 1 : 0);\n return [...entries].sort(\n (left, right) =>\n tier(left.path) - tier(right.path) ||\n (fanIn.get(right.path) ?? 0) - (fanIn.get(left.path) ?? 0) ||\n left.path.localeCompare(right.path),\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,20 @@
1
+ // src/hooks/prompt-gate.ts
2
+ function skipPromptRetrieval(prompt) {
3
+ if (prompt === void 0) return false;
4
+ const text = prompt.trim();
5
+ if (!text || text.startsWith("/")) return true;
6
+ if (/^(?:please\s+)?(?:(?:re-?run|run)\s+(?:the\s+)?(?:tests?|test suite|lint|typecheck|type check|build)(?:\s+again)?|continue|yes(?:\s+do it)?|go ahead)[.!?\s]*$/i.test(
7
+ text
8
+ ))
9
+ return true;
10
+ if (text.split(/\s+/).length > NAMED_FILE_MAX_WORDS) return false;
11
+ return /(?:^|[\s`'"(])(?:\.?\.?[/\\])?(?:[\w@.-]+[/\\])*[\w@.-]+\.(?:[cm]?[jt]sx?|py|rs|go|java|kt|swift|rb|php|cs|cpp|cc|c|h|vue|svelte|md|rst|json|ya?ml|toml|sql|sh)(?=$|[\s`'"),:])/i.test(
12
+ text
13
+ );
14
+ }
15
+ var NAMED_FILE_MAX_WORDS = 30;
16
+
17
+ export {
18
+ skipPromptRetrieval
19
+ };
20
+ //# sourceMappingURL=chunk-GQKWUHMA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/prompt-gate.ts"],"sourcesContent":["/** Task-independent fast path: no retrieval when a short prompt already names its\n * file or only requests an existing check. A long report that mentions a file in\n * passing still gets retrieval. Capture/recovery is separate. */\nexport function skipPromptRetrieval(prompt: string | undefined): boolean {\n if (prompt === undefined) return false;\n const text = prompt.trim();\n if (!text || text.startsWith(\"/\")) return true;\n if (\n /^(?:please\\s+)?(?:(?:re-?run|run)\\s+(?:the\\s+)?(?:tests?|test suite|lint|typecheck|type check|build)(?:\\s+again)?|continue|yes(?:\\s+do it)?|go ahead)[.!?\\s]*$/i.test(\n text,\n )\n )\n return true;\n if (text.split(/\\s+/).length > NAMED_FILE_MAX_WORDS) return false;\n return /(?:^|[\\s`'\"(])(?:\\.?\\.?[/\\\\])?(?:[\\w@.-]+[/\\\\])*[\\w@.-]+\\.(?:[cm]?[jt]sx?|py|rs|go|java|kt|swift|rb|php|cs|cpp|cc|c|h|vue|svelte|md|rst|json|ya?ml|toml|sql|sh)(?=$|[\\s`'\"),:])/i.test(\n text,\n );\n}\n\n/** \"Fix cookie parsing in session.py\" names its target; a pasted issue does not. */\nconst NAMED_FILE_MAX_WORDS = 30;\n"],"mappings":";AAGO,SAAS,oBAAoB,QAAqC;AACvE,MAAI,WAAW,OAAW,QAAO;AACjC,QAAM,OAAO,OAAO,KAAK;AACzB,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,EAAG,QAAO;AAC1C,MACE,kKAAkK;AAAA,IAChK;AAAA,EACF;AAEA,WAAO;AACT,MAAI,KAAK,MAAM,KAAK,EAAE,SAAS,qBAAsB,QAAO;AAC5D,SAAO,mLAAmL;AAAA,IACxL;AAAA,EACF;AACF;AAGA,IAAM,uBAAuB;","names":[]}