@theokit/sdk 4.55.0 → 4.57.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 (119) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/dist/a2a/index.cjs +13 -4
  3. package/dist/a2a/index.cjs.map +1 -1
  4. package/dist/a2a/index.d.cts +1 -1
  5. package/dist/a2a/index.d.ts +1 -1
  6. package/dist/a2a/index.js +13 -5
  7. package/dist/a2a/index.js.map +1 -1
  8. package/dist/a2a/message-bus.d.cts +33 -4
  9. package/dist/a2a/message-bus.d.ts +33 -4
  10. package/dist/{agent-R2HOJFZJ.cjs → agent-2T2EJIP6.cjs} +12 -11
  11. package/dist/{agent-R2HOJFZJ.cjs.map → agent-2T2EJIP6.cjs.map} +1 -1
  12. package/dist/{agent-HDEVVUBS.js → agent-ZIYCVEZL.js} +11 -10
  13. package/dist/{agent-HDEVVUBS.js.map → agent-ZIYCVEZL.js.map} +1 -1
  14. package/dist/agent-session-store-G2BMUZQV.js +7 -0
  15. package/dist/{agent-session-store-P6V3FINW.js.map → agent-session-store-G2BMUZQV.js.map} +1 -1
  16. package/dist/agent-session-store-WAKLOKWK.cjs +24 -0
  17. package/dist/{agent-session-store-ZJ3JSRS4.cjs.map → agent-session-store-WAKLOKWK.cjs.map} +1 -1
  18. package/dist/{chunk-HLPSLDGL.cjs → chunk-43GFJ5SD.cjs} +7 -7
  19. package/dist/{chunk-HLPSLDGL.cjs.map → chunk-43GFJ5SD.cjs.map} +1 -1
  20. package/dist/{chunk-5PHVENFV.js → chunk-7FHZ4VQX.js} +3 -55
  21. package/dist/chunk-7FHZ4VQX.js.map +1 -0
  22. package/dist/{chunk-I5BE5M5L.js → chunk-7TCRNXNK.js} +51 -31
  23. package/dist/chunk-7TCRNXNK.js.map +1 -0
  24. package/dist/{chunk-JHPGF3FP.cjs → chunk-BUIK7GUA.cjs} +22 -2
  25. package/dist/chunk-BUIK7GUA.cjs.map +1 -0
  26. package/dist/{chunk-Q47R5E2X.cjs → chunk-E3WZ6Y4H.cjs} +7 -59
  27. package/dist/chunk-E3WZ6Y4H.cjs.map +1 -0
  28. package/dist/{chunk-5UOVYM3P.cjs → chunk-E7JHD6QJ.cjs} +132 -54
  29. package/dist/chunk-E7JHD6QJ.cjs.map +1 -0
  30. package/dist/{chunk-DZBSJX6J.js → chunk-FKMUFNQE.js} +21 -4
  31. package/dist/chunk-FKMUFNQE.js.map +1 -0
  32. package/dist/{chunk-BMAZLQQ4.js → chunk-G24KZRSA.js} +3 -3
  33. package/dist/{chunk-BMAZLQQ4.js.map → chunk-G24KZRSA.js.map} +1 -1
  34. package/dist/{chunk-DAPSQZT4.js → chunk-G6EEYRDD.js} +132 -55
  35. package/dist/chunk-G6EEYRDD.js.map +1 -0
  36. package/dist/{chunk-MVAPK2BX.js → chunk-IACR5LEM.js} +3 -3
  37. package/dist/{chunk-MVAPK2BX.js.map → chunk-IACR5LEM.js.map} +1 -1
  38. package/dist/{chunk-V2UUGUY4.cjs → chunk-KKK3FZ4A.cjs} +6 -6
  39. package/dist/{chunk-V2UUGUY4.cjs.map → chunk-KKK3FZ4A.cjs.map} +1 -1
  40. package/dist/chunk-KRD3GQAA.cjs +71 -0
  41. package/dist/chunk-KRD3GQAA.cjs.map +1 -0
  42. package/dist/chunk-KVNWIAO4.js +68 -0
  43. package/dist/chunk-KVNWIAO4.js.map +1 -0
  44. package/dist/{chunk-IVNNSANC.js → chunk-LEM2GOMI.js} +3 -3
  45. package/dist/{chunk-IVNNSANC.js.map → chunk-LEM2GOMI.js.map} +1 -1
  46. package/dist/{chunk-F5WMX4EA.cjs → chunk-M2JWHKO6.cjs} +107 -87
  47. package/dist/chunk-M2JWHKO6.cjs.map +1 -0
  48. package/dist/{chunk-YPJ5NH5N.cjs → chunk-QI7WE46I.cjs} +29 -6
  49. package/dist/chunk-QI7WE46I.cjs.map +1 -0
  50. package/dist/{chunk-OHBIUXKM.js → chunk-WG7R5W6R.js} +4 -4
  51. package/dist/{chunk-OHBIUXKM.js.map → chunk-WG7R5W6R.js.map} +1 -1
  52. package/dist/{chunk-ITSSOVOB.cjs → chunk-X7O4255I.cjs} +4 -4
  53. package/dist/{chunk-ITSSOVOB.cjs.map → chunk-X7O4255I.cjs.map} +1 -1
  54. package/dist/{chunk-GJ6RK75E.js → chunk-YJDH3CKG.js} +29 -6
  55. package/dist/chunk-YJDH3CKG.js.map +1 -0
  56. package/dist/{chunk-CTCGWIUD.cjs → chunk-Z4BCLVTI.cjs} +5 -5
  57. package/dist/{chunk-CTCGWIUD.cjs.map → chunk-Z4BCLVTI.cjs.map} +1 -1
  58. package/dist/{compact-session-FUJDVF7B.js → compact-session-QGDNP45U.js} +4 -4
  59. package/dist/{compact-session-FUJDVF7B.js.map → compact-session-QGDNP45U.js.map} +1 -1
  60. package/dist/{compact-session-LEWF554G.cjs → compact-session-YHFQIXV5.cjs} +12 -12
  61. package/dist/{compact-session-LEWF554G.cjs.map → compact-session-YHFQIXV5.cjs.map} +1 -1
  62. package/dist/context/index.cjs +6 -5
  63. package/dist/context/index.cjs.map +1 -1
  64. package/dist/context/index.js +3 -2
  65. package/dist/context/index.js.map +1 -1
  66. package/dist/cron.cjs +11 -10
  67. package/dist/cron.js +10 -9
  68. package/dist/eval.cjs +10 -9
  69. package/dist/eval.cjs.map +1 -1
  70. package/dist/eval.js +9 -8
  71. package/dist/eval.js.map +1 -1
  72. package/dist/{fs-session-store-IN7JTTXD.js → fs-session-store-4BHGIWRE.js} +4 -4
  73. package/dist/{fs-session-store-IN7JTTXD.js.map → fs-session-store-4BHGIWRE.js.map} +1 -1
  74. package/dist/{fs-session-store-3MOJQGP2.cjs → fs-session-store-XZ7ZTB3A.cjs} +5 -5
  75. package/dist/{fs-session-store-3MOJQGP2.cjs.map → fs-session-store-XZ7ZTB3A.cjs.map} +1 -1
  76. package/dist/{index-manager-NG5YENWO.cjs → index-manager-AO4LJQZ4.cjs} +5 -4
  77. package/dist/{index-manager-NG5YENWO.cjs.map → index-manager-AO4LJQZ4.cjs.map} +1 -1
  78. package/dist/{index-manager-ZMRJ6ZII.js → index-manager-QDPIYIGV.js} +4 -3
  79. package/dist/{index-manager-ZMRJ6ZII.js.map → index-manager-QDPIYIGV.js.map} +1 -1
  80. package/dist/index.cjs +40 -39
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.js +13 -12
  83. package/dist/index.js.map +1 -1
  84. package/dist/{inject-session-WQGGMCQY.cjs → inject-session-MLCJMYDG.cjs} +7 -7
  85. package/dist/{inject-session-WQGGMCQY.cjs.map → inject-session-MLCJMYDG.cjs.map} +1 -1
  86. package/dist/{inject-session-BJMJH5BX.js → inject-session-RBQZ45UM.js} +5 -5
  87. package/dist/{inject-session-BJMJH5BX.js.map → inject-session-RBQZ45UM.js.map} +1 -1
  88. package/dist/internal/memory/storage/markdown-store.d.ts +14 -2
  89. package/dist/internal/memory/storage/memory-file.d.ts +54 -0
  90. package/dist/internal/memory/types.d.ts +28 -0
  91. package/dist/internal/persistence/session-transcript.d.cts +40 -1
  92. package/dist/internal/persistence/session-transcript.d.ts +40 -1
  93. package/dist/persistence.cjs +4 -4
  94. package/dist/persistence.js +1 -1
  95. package/dist/sandbox/index.cjs +1 -1
  96. package/dist/sandbox/index.cjs.map +1 -1
  97. package/dist/sandbox/index.js +1 -1
  98. package/dist/sandbox/index.js.map +1 -1
  99. package/dist/session-transcript-HF5GEU5M.cjs +63 -0
  100. package/dist/{session-transcript-JXHFTB7G.cjs.map → session-transcript-HF5GEU5M.cjs.map} +1 -1
  101. package/dist/session-transcript-P462ZRHX.js +6 -0
  102. package/dist/{session-transcript-AKDYYGXQ.js.map → session-transcript-P462ZRHX.js.map} +1 -1
  103. package/docs/error-codes.md +7 -5
  104. package/docs/harness-capability-map.md +3 -2
  105. package/package.json +5 -9
  106. package/dist/agent-session-store-P6V3FINW.js +0 -7
  107. package/dist/agent-session-store-ZJ3JSRS4.cjs +0 -24
  108. package/dist/chunk-5PHVENFV.js.map +0 -1
  109. package/dist/chunk-5UOVYM3P.cjs.map +0 -1
  110. package/dist/chunk-DAPSQZT4.js.map +0 -1
  111. package/dist/chunk-DZBSJX6J.js.map +0 -1
  112. package/dist/chunk-F5WMX4EA.cjs.map +0 -1
  113. package/dist/chunk-GJ6RK75E.js.map +0 -1
  114. package/dist/chunk-I5BE5M5L.js.map +0 -1
  115. package/dist/chunk-JHPGF3FP.cjs.map +0 -1
  116. package/dist/chunk-Q47R5E2X.cjs.map +0 -1
  117. package/dist/chunk-YPJ5NH5N.cjs.map +0 -1
  118. package/dist/session-transcript-AKDYYGXQ.js +0 -6
  119. package/dist/session-transcript-JXHFTB7G.cjs +0 -51
@@ -217,7 +217,24 @@ function expandTilde(p) {
217
217
  if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
218
218
  return p;
219
219
  }
220
+ var SESSION_NAMESPACE = Buffer.from("b7a1f0c94e3d4a1b8f26d05c3a91e7d2", "hex");
221
+ var UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
222
+ function isSessionUuid(id) {
223
+ return UUID_SHAPE.test(id);
224
+ }
225
+ function sessionUuidFor(id) {
226
+ if (isSessionUuid(id)) return id.toLowerCase();
227
+ const hash = crypto.createHash("sha256").update(Buffer.concat([SESSION_NAMESPACE, Buffer.from(id, "utf8")])).digest();
228
+ const bytes = Buffer.from(hash.subarray(0, 16));
229
+ bytes[6] = bytes[6] & 15 | 128;
230
+ bytes[8] = bytes[8] & 63 | 128;
231
+ const hex = bytes.toString("hex");
232
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
233
+ }
220
234
  function transcriptPath(baseDir, cwd, sessionId) {
235
+ return path.join(baseDir, "projects", encodeProjectDir(cwd), `${sessionUuidFor(sessionId)}.jsonl`);
236
+ }
237
+ function legacyTranscriptPath(baseDir, cwd, sessionId) {
221
238
  return path.join(baseDir, "projects", encodeProjectDir(cwd), `${safeSessionId(sessionId)}.jsonl`);
222
239
  }
223
240
  async function writeTranscript(path$1, records) {
@@ -252,11 +269,14 @@ exports.SessionTranscript = SessionTranscript;
252
269
  exports.defaultBaseDir = defaultBaseDir;
253
270
  exports.encodeProjectDir = encodeProjectDir;
254
271
  exports.expandTilde = expandTilde;
272
+ exports.isSessionUuid = isSessionUuid;
273
+ exports.legacyTranscriptPath = legacyTranscriptPath;
255
274
  exports.readTranscript = readTranscript;
256
275
  exports.reconstructMessages = reconstructMessages;
257
276
  exports.safeSessionId = safeSessionId;
277
+ exports.sessionUuidFor = sessionUuidFor;
258
278
  exports.transcriptPath = transcriptPath;
259
279
  exports.transcriptRoot = transcriptRoot;
260
280
  exports.writeTranscript = writeTranscript;
261
- //# sourceMappingURL=chunk-JHPGF3FP.cjs.map
262
- //# sourceMappingURL=chunk-JHPGF3FP.cjs.map
281
+ //# sourceMappingURL=chunk-BUIK7GUA.cjs.map
282
+ //# sourceMappingURL=chunk-BUIK7GUA.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/session-transcript.ts"],"names":["redactSecrets","randomUUID","join","homedir","createHash","path","mkdir","dirname","replaceFileAtomic","readFile"],"mappings":"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","file":"chunk-BUIK7GUA.cjs","sourcesContent":["/**\n * SE40 — native session transcript (Claude-Code-compatible on-disk shape, theokit-native names).\n *\n * The theokit session format IS the Claude Code record shape (a `uuid`/`parentUuid` DAG of records with\n * structured `text`/`tool_use`/`tool_result`/`thinking` blocks) — so the ecosystem's read-side tools\n * parse our sessions AND, pointed at `~/.claude`, the Claude Code CLI can `--continue` them. This module\n * is the PURE core: append-per-turn record builders + the DAG reader that rebuilds `LlmMessage[]` for\n * resume. File I/O + loop wiring + append-only compaction live in the FS store.\n *\n * Reader mirrors `claude-code-log` dag.py: leaf = a uuid never used as a parentUuid; walk parentUuid to a\n * root (cycle-break); dedup a uuid across sessions keeping the earliest; a `compact_boundary` root\n * terminates the walk so a resume-after-compaction replays only the post-boundary continuation.\n *\n * reference: knowledge-base/references/claude-code-log/claude_code_log/dag.py + models.py\n */\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport type { SessionRecord, TranscriptBlock } from \"../../types/session-record.js\";\nimport type { LlmContentPart, LlmMessage, LlmToolResultPart } from \"../llm/types.js\";\nimport { redactSecrets } from \"../security/redact.js\";\nimport { replaceFileAtomic } from \"./atomic-write.js\";\n\n// The `SessionRecord` contract now lives in the domain `types/` layer (SE46 DIP\n// direction). Re-exported here so existing importers of this DAG-core module\n// keep resolving the same name.\nexport type { SessionRecord } from \"../../types/session-record.js\";\n\nexport interface SessionTranscriptOptions {\n cwd: string;\n sessionId: string;\n model: string;\n version?: string;\n}\n\nexport interface AssistantTurn {\n text?: string;\n thinking?: string;\n /**\n * theokit#122 — the provider's signature for the thinking block. Written into the `thinking`\n * record block (`Block` already declared `signature?`), so a resumed session can replay the block\n * exactly as the provider issued it.\n */\n thinkingSignature?: string;\n toolCalls?: Array<{ id: string; name: string; input: Record<string, unknown> }>;\n}\n\n/** Claude Code path convention: cwd with every non-alphanumeric replaced by `-`. */\nexport function encodeProjectDir(cwd: string): string {\n return cwd.replace(/[^a-zA-Z0-9]/g, \"-\");\n}\n\n/** Make a caller-supplied session id safe as a path component (no traversal). */\nexport function safeSessionId(id: string): string {\n return id.replace(/[^a-zA-Z0-9_-]/g, \"-\");\n}\n\nconst red = (s: string): string => redactSecrets(s);\n\n/** Deep-redact a structured value (tool input) preserving object shape. */\nfunction redactValue(value: unknown): unknown {\n try {\n return JSON.parse(redactSecrets(JSON.stringify(value ?? null)));\n } catch {\n return redactSecrets(String(value));\n }\n}\n\n// M94 — it was `Record<string, unknown>`. The published type describes the same shape this file\n// has always produced; the local alias now points at it so writer and reader cannot\n// diverge silently.\ntype Block = TranscriptBlock;\n\n/**\n * Builds a session transcript append-by-append, maintaining the `parentUuid` chain. Each turn mints a\n * fresh `uuid` and parents on the previous record; `appendCompactBoundary` starts a NEW ROOT.\n */\nexport class SessionTranscript {\n #records: SessionRecord[] = [];\n #last: string | null = null;\n #opts: SessionTranscriptOptions;\n\n constructor(opts: SessionTranscriptOptions) {\n this.#opts = opts;\n }\n\n /**\n * Seed a transcript from records already on disk so a subsequent append parents\n * on the existing DAG leaf (the last record in file order). Prior records are\n * re-emitted verbatim by {@link records}, so the whole file rewrites atomically\n * as one append-only line set — never shrinking (SE40 compaction is append-only).\n */\n static fromRecords(\n prior: readonly SessionRecord[],\n opts: SessionTranscriptOptions,\n ): SessionTranscript {\n const t = new SessionTranscript(opts);\n t.#records = [...prior];\n t.#last = prior.length > 0 ? (prior[prior.length - 1]?.uuid ?? null) : null;\n return t;\n }\n\n #push(\n rec: Omit<SessionRecord, \"uuid\" | \"parentUuid\" | \"sessionId\" | \"timestamp\">,\n ): SessionRecord {\n const full: SessionRecord = {\n ...rec,\n uuid: randomUUID(),\n parentUuid: this.#last,\n sessionId: this.#opts.sessionId,\n timestamp: new Date().toISOString(),\n isSidechain: false,\n userType: \"external\",\n cwd: this.#opts.cwd,\n version: this.#opts.version ?? \"1.0.0-theokit\",\n };\n this.#records.push(full);\n this.#last = full.uuid;\n return full;\n }\n\n appendUserTurn(text: string): SessionRecord {\n return this.#push({\n type: \"user\",\n message: { role: \"user\", content: [{ type: \"text\", text: red(text) }] },\n });\n }\n\n appendAssistantTurn(turn: AssistantTurn): SessionRecord {\n const content: Block[] = [];\n if (turn.thinking) {\n // theokit#122 — the signature is computed by the provider over the ORIGINAL text, so it is\n // only valid while the text is byte-identical. Redaction rewrites the text; keeping the\n // signature next to a masked body persists a pair that Anthropic rejects with\n // `400 \"thinking blocks cannot be modified\"` — the very failure this issue exists to remove.\n //\n // So redaction WINS and the signature is dropped. The block survives as display-only history\n // (`thinkingToWireBlock` already refuses to replay an unsigned block), which loses one block\n // of context; keeping a signature that cannot verify would lose the whole turn.\n //\n // The signature itself is never redacted — it is an opaque provider token with no user\n // content — but that was only half the reasoning the first version of this fix wrote down.\n const redactedThinking = red(turn.thinking);\n const signatureStillValid =\n turn.thinkingSignature !== undefined && redactedThinking === turn.thinking;\n content.push({\n type: \"thinking\",\n thinking: redactedThinking,\n ...(signatureStillValid ? { signature: turn.thinkingSignature } : {}),\n });\n }\n if (turn.text) content.push({ type: \"text\", text: red(turn.text) });\n for (const c of turn.toolCalls ?? []) {\n content.push({ type: \"tool_use\", id: c.id, name: c.name, input: redactValue(c.input) });\n }\n return this.#push({\n type: \"assistant\",\n message: {\n id: randomUUID(),\n type: \"message\",\n role: \"assistant\",\n model: this.#opts.model,\n content,\n },\n });\n }\n\n appendToolResults(\n results: Array<{ toolUseId: string; content: string; isError?: boolean }>,\n ): SessionRecord {\n const content: Block[] = results.map((r) => ({\n type: \"tool_result\",\n tool_use_id: r.toolUseId,\n content: red(r.content),\n is_error: r.isError === true,\n }));\n return this.#push({ type: \"user\", message: { role: \"user\", content } });\n }\n\n /** Append-only compaction: a `compact_boundary` system record becomes a NEW ROOT (parentUuid null). */\n appendCompactBoundary(meta: { preTokens: number; trigger: string }): SessionRecord {\n const rec: SessionRecord = {\n type: \"system\",\n uuid: randomUUID(),\n parentUuid: null,\n sessionId: this.#opts.sessionId,\n timestamp: new Date().toISOString(),\n subtype: \"compact_boundary\",\n compactMetadata: meta,\n isSidechain: false,\n userType: \"external\",\n cwd: this.#opts.cwd,\n version: this.#opts.version ?? \"1.0.0-theokit\",\n };\n this.#records.push(rec);\n this.#last = rec.uuid;\n return rec;\n }\n\n records(): readonly SessionRecord[] {\n return this.#records;\n }\n}\n\n// ─── Reader (DAG → LlmMessage[]) ──────────────────────────────────────────────────────────────────\n\n/** Earliest timestamp seen per session id. */\nfunction sessionFirstTimestamps(records: readonly SessionRecord[]): Map<string, string> {\n const first = new Map<string, string>();\n for (const r of records) {\n const cur = first.get(r.sessionId);\n if (cur === undefined || r.timestamp < cur) first.set(r.sessionId, r.timestamp);\n }\n return first;\n}\n\n/** Dedup a uuid across sessions: keep the record whose session's earliest timestamp is smallest. */\nfunction dedupByUuid(records: readonly SessionRecord[]): Map<string, SessionRecord> {\n const firstTs = sessionFirstTimestamps(records);\n const byUuid = new Map<string, SessionRecord>();\n for (const r of records) {\n const existing = byUuid.get(r.uuid);\n const wins =\n existing === undefined ||\n (firstTs.get(r.sessionId) ?? \"\") < (firstTs.get(existing.sessionId) ?? \"\");\n if (wins) byUuid.set(r.uuid, r);\n }\n return byUuid;\n}\n\n/**\n * Map one structured block to an `LlmContentPart`.\n *\n * theokit#122 — thinking blocks used to be DROPPED here, which is why an extended-thinking session\n * could not be resumed: the block never made it back into the replayed messages, so the provider saw\n * a conversation whose assistant turn had lost its thinking. They are now reconstructed with their\n * signature; the Anthropic wire re-serializes signed blocks and skips unsigned ones.\n */\nfunction thinkingBlockToPart(b: Extract<Block, { type: \"thinking\" }>): LlmContentPart {\n return {\n type: \"thinking\",\n text: String(b.thinking ?? \"\"),\n ...(b.signature !== undefined ? { signature: String(b.signature) } : {}),\n };\n}\n\nfunction blockToPart(b: Block): LlmContentPart | undefined {\n if (b.type === \"text\") return { type: \"text\", text: String(b.text ?? \"\") };\n if (b.type === \"thinking\") return thinkingBlockToPart(b);\n if (b.type === \"tool_use\") {\n return {\n type: \"tool_use\",\n id: String(b.id),\n name: String(b.name),\n input: (b.input as Record<string, unknown>) ?? {},\n };\n }\n if (b.type === \"tool_result\") {\n const tr: LlmToolResultPart = {\n type: \"tool_result\",\n toolUseId: String(b.tool_use_id),\n content: typeof b.content === \"string\" ? b.content : JSON.stringify(b.content),\n isError: b.is_error === true,\n };\n return tr;\n }\n return undefined;\n}\n\n/** Map one record to an LlmMessage; `undefined` for non-conversational (system) records. */\nfunction recordToMessage(rec: SessionRecord): LlmMessage | undefined {\n if (rec.type !== \"user\" && rec.type !== \"assistant\") return undefined;\n const blocks = rec.message?.content ?? [];\n const parts = blocks.map(blockToPart).filter((p): p is LlmContentPart => p !== undefined);\n return { role: rec.type, content: parts };\n}\n\n/** The most-recent leaf: the LAST record (in input order) whose uuid is never used as a parentUuid. */\nfunction findLatestLeaf(\n records: readonly SessionRecord[],\n byUuid: Map<string, SessionRecord>,\n parents: Set<string>,\n): SessionRecord | undefined {\n let leaf: SessionRecord | undefined;\n for (const r of records) {\n if (byUuid.get(r.uuid) === r && !parents.has(r.uuid)) leaf = r;\n }\n return leaf;\n}\n\n/** Walk parentUuid from a leaf to a root (cycle-safe), returning root→leaf order. */\nfunction walkToRoot(leaf: SessionRecord, byUuid: Map<string, SessionRecord>): SessionRecord[] {\n const chain: SessionRecord[] = [];\n const seen = new Set<string>();\n let cur: SessionRecord | undefined = leaf;\n while (cur !== undefined && !seen.has(cur.uuid)) {\n seen.add(cur.uuid);\n chain.push(cur);\n cur = cur.parentUuid !== null ? byUuid.get(cur.parentUuid) : undefined;\n }\n return chain.reverse();\n}\n\n/**\n * Rebuild the resumable `LlmMessage[]` from transcript records: dedup → find the most-recent leaf →\n * walk `parentUuid` to a root (a `compact_boundary` root terminates the walk) → map to LlmMessages.\n * Returns them in root→leaf (chronological replay) order.\n */\nexport function reconstructMessages(records: readonly SessionRecord[]): LlmMessage[] {\n const byUuid = dedupByUuid(records);\n const parents = new Set<string>();\n for (const r of byUuid.values()) if (r.parentUuid) parents.add(r.parentUuid);\n const leaf = findLatestLeaf(records, byUuid, parents);\n if (leaf === undefined) return [];\n\n const msgs: LlmMessage[] = [];\n for (const rec of walkToRoot(leaf, byUuid)) {\n const m = recordToMessage(rec);\n if (m !== undefined) msgs.push(m);\n }\n return msgs;\n}\n\n// ─── File I/O (the on-disk Claude layout) ─────────────────────────────────────────────────────────\n\n/**\n * Root of the transcript state.\n *\n * `THEOKIT_HOME` wins; the fallback is `~/.theokit`. Home-anchored on purpose, and NOT\n * `paths.ts`'s `getTheokitHome(cwd)`: that one falls back to `<cwd>/.theokit`, and switching\n * by it would move the transcript of everyone who does **not** set the variable. The sibling with the\n * right shape is `catalog-source-models-dev.ts` (M94 ADR-2).\n *\n * Before M94 this function ignored the env var, so whoever set it had their state split in\n * two silently — sessions stayed in `~/.theokit` while the rest of the SDK migrated.\n */\nexport function transcriptRoot(): string {\n const override = process.env.THEOKIT_HOME?.trim();\n if (override !== undefined && override.length > 0) return override;\n return join(homedir(), \".theokit\");\n}\n\n/** @deprecated Use {@link transcriptRoot}. Kept as an alias — same value. */\nexport function defaultBaseDir(): string {\n return transcriptRoot();\n}\n\n/**\n * Expand a leading `~` / `~/` to the user's home dir. `node:path` does NOT expand `~`, so a\n * `baseDir: \"~/.claude\"` (the documented Claude Code CLI interop path) would otherwise resolve to a\n * literal `./~/.claude` under cwd and the CLI would never find the session. Absolute or relative\n * paths without a leading tilde pass through untouched.\n */\nexport function expandTilde(p: string): string {\n if (p === \"~\") return homedir();\n if (p.startsWith(\"~/\")) return join(homedir(), p.slice(2));\n return p;\n}\n\n/**\n * The fixed namespace for deriving a session UUID from an agent id (RFC 9562 § 5.8, version 8).\n *\n * A constant, because the derivation must be STABLE FOREVER: it is what lets the SDK find the\n * session it wrote last week without storing an id-to-filename map anywhere. Changing this value\n * orphans every transcript ever written.\n */\nconst SESSION_NAMESPACE = Buffer.from(\"b7a1f0c94e3d4a1b8f26d05c3a91e7d2\", \"hex\");\n\nconst UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** Is this id already a canonical UUID, and therefore usable as a filename verbatim? */\nexport function isSessionUuid(id: string): boolean {\n return UUID_SHAPE.test(id);\n}\n\n/**\n * The transcript filename for an agent id — always a UUID.\n *\n * #400: the Claude Code CLI resumes a transcript ONLY when its basename is a UUID. Measured against\n * CLI 2.1.236: `<uuid>.jsonl` resumes; `agent-<uuid>.jsonl` and `billing-bot.jsonl` are both ignored,\n * with no error — the session simply is not offered. So a session written under a human-readable\n * agent id was invisible to `claude --continue`, which is the interoperability this project claims\n * as its difference from a proprietary session store.\n *\n * An id that is ALREADY a UUID passes through verbatim, so a transcript Claude Code wrote keeps its\n * own name and the two directions stay symmetric. Anything else is derived with a UUIDv8, which is\n * deterministic: the same agent id always yields the same filename, so nothing has to be persisted\n * to map one back to the other.\n *\n * A DERIVED name is version 8 — RFC 9562's slot for a UUID whose bits come from an\n * implementation-defined scheme, which is exactly what this is. The obvious alternative was v5, but\n * RFC 4122 fixes v5's hash to SHA-1, and reaching for SHA-1 in 2026 means either shipping a weak\n * primitive or arguing with every scanner that flags it, forever. v8 lets the hash be SHA-256 and\n * lets the version nibble say honestly that this is our scheme rather than a standard one.\n *\n * There is no security claim here either way — no secret, no signature, and nothing an attacker\n * gains by colliding two agent ids inside one consumer's own directory. The hash is a naming\n * function. The point of preferring SHA-256 is not that SHA-1 was exploitable here; it is that a\n * weak primitive in the tree costs a permanent argument with every scanner that sees it.\n *\n * The version nibble was measured, not assumed: CLI 2.1.236 resumes a v8-named transcript exactly as\n * it does a v4 one.\n */\nexport function sessionUuidFor(id: string): string {\n if (isSessionUuid(id)) return id.toLowerCase();\n const hash = createHash(\"sha256\")\n .update(Buffer.concat([SESSION_NAMESPACE, Buffer.from(id, \"utf8\")]))\n .digest();\n const bytes = Buffer.from(hash.subarray(0, 16));\n bytes[6] = ((bytes[6] as number) & 0x0f) | 0x80; // version 8 — RFC 9562 custom\n bytes[8] = ((bytes[8] as number) & 0x3f) | 0x80; // RFC 9562 variant\n const hex = bytes.toString(\"hex\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** The `.jsonl` path for a session: `<baseDir>/projects/<encoded-cwd>/<session-uuid>.jsonl`. */\nexport function transcriptPath(baseDir: string, cwd: string, sessionId: string): string {\n return join(baseDir, \"projects\", encodeProjectDir(cwd), `${sessionUuidFor(sessionId)}.jsonl`);\n}\n\n/**\n * The path this session used BEFORE #400 made transcript filenames UUIDs.\n *\n * Kept so an existing transcript is never orphaned: {@link FsSessionStore} prefers this file when it\n * exists, so history written under the old scheme keeps accumulating in the same file instead of\n * being silently abandoned for a fresh one under the new name.\n */\nexport function legacyTranscriptPath(baseDir: string, cwd: string, sessionId: string): string {\n return join(baseDir, \"projects\", encodeProjectDir(cwd), `${safeSessionId(sessionId)}.jsonl`);\n}\n\n/** Write records as one JSONL line each (atomic; parent dir created). Returns the path. */\nexport async function writeTranscript(\n path: string,\n records: readonly SessionRecord[],\n): Promise<string> {\n await mkdir(dirname(path), { recursive: true });\n const body = records.map((r) => JSON.stringify(r)).join(\"\\n\");\n await replaceFileAtomic(path, records.length > 0 ? `${body}\\n` : \"\");\n return path;\n}\n\n/** Parse one JSONL line to a record; `undefined` for blank/torn/invalid lines (skipped on read). */\nfunction parseRecordLine(line: string): SessionRecord | undefined {\n const trimmed = line.trim();\n if (trimmed.length === 0) return undefined;\n try {\n const rec = JSON.parse(trimmed) as SessionRecord;\n return typeof rec.uuid === \"string\" && typeof rec.type === \"string\" ? rec : undefined;\n } catch {\n return undefined; // a torn last line (crash mid-write) — the rest of the DAG still reconstructs\n }\n}\n\n/** Parse a transcript `.jsonl` into records; a missing file yields `[]`; malformed lines are skipped. */\nexport async function readTranscript(path: string): Promise<SessionRecord[]> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (cause) {\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw cause;\n }\n return raw\n .split(\"\\n\")\n .map(parseRecordLine)\n .filter((r): r is SessionRecord => r !== undefined);\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var chunkKRD3GQAA_cjs = require('./chunk-KRD3GQAA.cjs');
3
4
  var chunkZNW6V4Y6_cjs = require('./chunk-ZNW6V4Y6.cjs');
4
5
  var chunkVKJ7V7EB_cjs = require('./chunk-VKJ7V7EB.cjs');
5
6
  var os = require('os');
@@ -127,59 +128,6 @@ function anyGlobMatches(patterns, paths) {
127
128
  return paths.some((p) => res.some((re) => re.test(p)));
128
129
  }
129
130
 
130
- // src/internal/runtime/context/context-yaml-lite.ts
131
- var FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
132
- function splitFrontmatter(content) {
133
- const m = FRONTMATTER_RE.exec(content);
134
- if (m === null) return { yaml: void 0, body: content };
135
- return { yaml: m[1] ?? "", body: m[2] ?? "" };
136
- }
137
- function parseSimpleYaml(yaml) {
138
- const out = {};
139
- const lines = yaml.split(/\r?\n/);
140
- let i = 0;
141
- while (i < lines.length) {
142
- const line = lines[i];
143
- i += 1;
144
- if (line === void 0) continue;
145
- const trimmed = line.trim();
146
- if (trimmed === "" || trimmed.startsWith("#")) continue;
147
- const colon = trimmed.indexOf(":");
148
- if (colon === -1) throw new Error(`Invalid YAML line: ${trimmed}`);
149
- const key = trimmed.slice(0, colon).trim();
150
- const rest = trimmed.slice(colon + 1).trim();
151
- if (rest === "") {
152
- const items = [];
153
- while (i < lines.length) {
154
- const next = lines[i];
155
- if (next === void 0) break;
156
- const nextTrim = next.trim();
157
- if (nextTrim.startsWith("- ")) {
158
- items.push(parseScalar(nextTrim.slice(2).trim()));
159
- i += 1;
160
- } else if (nextTrim === "") {
161
- i += 1;
162
- } else {
163
- break;
164
- }
165
- }
166
- out[key] = items;
167
- } else if (rest.startsWith("[") && rest.endsWith("]")) {
168
- const inner = rest.slice(1, -1).trim();
169
- out[key] = inner === "" ? [] : inner.split(",").map((s) => parseScalar(s.trim().replace(/^["']|["']$/g, "")));
170
- } else {
171
- out[key] = parseScalar(rest);
172
- }
173
- }
174
- return out;
175
- }
176
- function parseScalar(s) {
177
- if (s === "true") return true;
178
- if (s === "false") return false;
179
- if (/^-?\d+$/.test(s)) return parseInt(s, 10);
180
- return s.replace(/^["']|["']$/g, "");
181
- }
182
-
183
131
  // src/internal/runtime/context/context-rules-frontmatter.ts
184
132
  var RulesFrontmatterSchema = zod.z.object({
185
133
  description: zod.z.string().optional(),
@@ -189,12 +137,12 @@ var RulesFrontmatterSchema = zod.z.object({
189
137
  enabled: zod.z.boolean().optional()
190
138
  });
191
139
  function parseRules(content) {
192
- const { yaml, body } = splitFrontmatter(content);
140
+ const { yaml, body } = chunkKRD3GQAA_cjs.splitFrontmatter(content);
193
141
  if (yaml === void 0) {
194
142
  return { frontmatter: { alwaysApply: true }, body: content };
195
143
  }
196
144
  try {
197
- const parsed = parseSimpleYaml(yaml);
145
+ const parsed = chunkKRD3GQAA_cjs.parseSimpleYaml(yaml);
198
146
  const validated = RulesFrontmatterSchema.safeParse(parsed);
199
147
  if (!validated.success) return void 0;
200
148
  return { frontmatter: validated.data, body };
@@ -215,12 +163,12 @@ var McdFrontmatterSchema = zod.z.object({
215
163
  alwaysApply: zod.z.boolean().optional()
216
164
  });
217
165
  function parseMdc(content) {
218
- const { yaml, body } = splitFrontmatter(content);
166
+ const { yaml, body } = chunkKRD3GQAA_cjs.splitFrontmatter(content);
219
167
  if (yaml === void 0) {
220
168
  return { frontmatter: { alwaysApply: true }, body: content };
221
169
  }
222
170
  try {
223
- const parsed = parseSimpleYaml(yaml);
171
+ const parsed = chunkKRD3GQAA_cjs.parseSimpleYaml(yaml);
224
172
  const validated = McdFrontmatterSchema.safeParse(parsed);
225
173
  if (!validated.success) return void 0;
226
174
  return { frontmatter: validated.data, body };
@@ -336,5 +284,5 @@ exports.resolveImports = resolveImports;
336
284
  exports.runDiscovery = runDiscovery;
337
285
  exports.shouldActivateRule = shouldActivateRule;
338
286
  exports.truncateWithMarker = truncateWithMarker;
339
- //# sourceMappingURL=chunk-Q47R5E2X.cjs.map
340
- //# sourceMappingURL=chunk-Q47R5E2X.cjs.map
287
+ //# sourceMappingURL=chunk-E3WZ6Y4H.cjs.map
288
+ //# sourceMappingURL=chunk-E3WZ6Y4H.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/runtime/context/context-loaders.ts","../src/internal/runtime/context/context-import-resolver.ts","../src/internal/runtime/context/context-glob.ts","../src/internal/runtime/context/context-rules-frontmatter.ts","../src/internal/runtime/context/context-mdc-parser.ts","../src/internal/runtime/context/context-discovery-runner.ts"],"names":["readFile","dirname","insideRoot","resolvePath","join","homedir","isAbsolute","z","splitFrontmatter","parseSimpleYaml","findGitRoot","DEFAULT_DISCOVERY_SPECS","walkUpForFile","walkUpForGlob","path","relative"],"mappings":"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","file":"chunk-E3WZ6Y4H.cjs","sourcesContent":["/**\n * Context file loaders + truncation (T1.2, ADRs D154 / D155 / D159).\n *\n * Reads context source files from disk, applies the per-file size cap\n * with 70%/20% head/tail truncation, and emits the\n * `context_files_truncated` telemetry counter when the cap fires.\n * Pure (no I/O) for `truncateWithMarker` — testable in isolation.\n *\n * @internal\n */\n\nimport { readFile } from \"node:fs/promises\";\n\nconst HEAD_RATIO = 0.7;\nconst TAIL_RATIO = 0.2;\nconst MARKER = \"\\n\\n…[truncated by theokit]\\n\\n\";\n\n/** Default per-file cap (40k chars ≈ 10k tokens). D155. */\nexport const DEFAULT_MAX_BYTES_PER_FILE = 40_000;\n\nexport interface LoadedSource {\n /** Absolute path the content came from. */\n readonly source: string;\n readonly content: string;\n readonly originalBytes: number;\n readonly truncated: boolean;\n}\n\nexport interface TruncateResult {\n readonly truncated: boolean;\n readonly finalContent: string;\n}\n\n/**\n * Cap `content` at `max` characters with 70%/20% head/tail truncation\n * and an explicit marker. Pure — no I/O, no telemetry.\n *\n * EC-C: when `max <= MARKER.length`, skip the marker entirely and\n * return a head-only slice. Without this guard, `budget = max -\n * MARKER.length` would go negative and `content.slice(-tailBytes)`\n * would slice from the END of the string rather than truncating.\n *\n * EC-H: codepoint integrity — `String.slice()` operates on UTF-16\n * code units, which can split surrogate pairs. We accept that\n * trailing/leading replacement chars (U+FFFD) may appear at boundaries.\n * Modern LLMs tolerate them; byte-exact safety would require Buffer\n * slicing + TextDecoder with `fatal: true` retry, which adds complexity\n * without value.\n *\n * @public\n */\nexport function truncateWithMarker(content: string, max: number): TruncateResult {\n if (content.length <= max) {\n return { truncated: false, finalContent: content };\n }\n // EC-C: max too small to fit even the marker — return head-only slice.\n if (max <= MARKER.length) {\n return { truncated: true, finalContent: content.slice(0, max) };\n }\n const budget = max - MARKER.length;\n const headBytes = Math.floor(budget * (HEAD_RATIO / (HEAD_RATIO + TAIL_RATIO)));\n const tailBytes = budget - headBytes;\n return {\n truncated: true,\n finalContent: content.slice(0, headBytes) + MARKER + content.slice(-tailBytes),\n };\n}\n\n/**\n * Read a file from disk and apply the per-file cap. Emits telemetry\n * counter when truncation fires.\n *\n * EC-G: file deleted between discovery and read (FS race) → returns\n * `undefined`, never throws.\n *\n * @internal\n */\nexport async function loadPlainMarkdown(\n absPath: string,\n opts: { maxBytesPerFile?: number } = {},\n): Promise<LoadedSource | undefined> {\n const max = opts.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE;\n let content: string;\n try {\n content = await readFile(absPath, \"utf8\");\n } catch {\n // EC-G: ENOENT / EACCES / etc → caller treats as missing.\n return undefined;\n }\n const { truncated, finalContent } = truncateWithMarker(content, max);\n if (truncated) {\n emitTruncationCounter(absPath);\n }\n return {\n source: absPath,\n content: finalContent,\n originalBytes: content.length,\n truncated,\n };\n}\n\n/**\n * EC-L: telemetry no-op when OTel is not imported. We import the tracer\n * lazily and use `safeCall` so the lookup never throws and never pulls\n * OTel into the import graph for users without `@opentelemetry/api`\n * installed.\n *\n * @internal\n */\nfunction emitTruncationCounter(source: string): void {\n // Lazy resolution via globalThis avoids static import of telemetry/tracer.\n // Tests can spy via the same path; production users without OTel see no-op.\n const tracer = (\n globalThis as { __theokit_tracer?: { inc?: (k: string, attrs: unknown) => void } }\n ).__theokit_tracer;\n if (tracer?.inc === undefined) return;\n try {\n tracer.inc(\"context_files_truncated\", { file: source });\n } catch {\n // Telemetry must never break the loader.\n }\n}\n","/**\n * `@path/to/file` import resolver (T2.1, ADR D156).\n *\n * Anthropic/Gemini convention: lines that are EXACTLY `@path` get\n * replaced with the imported file's content, recursively (5-hop cap),\n * with cycle detection. Inline `see @x.md, also @y.md` references are\n * NOT resolved (EC-Q — own-line only, mirrors Anthropic's actual\n * behavior).\n *\n * EC-D fix: every imported file is itself capped at `maxBytesPerFile`\n * via `loadPlainMarkdown` BEFORE concatenation. Prevents a CLAUDE.md\n * with 5 imports of 30k each from ballooning to 150k of imported\n * content before the outer cap fires.\n *\n * @internal\n */\n\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, join, resolve as resolvePath } from \"node:path\";\n\nimport { loadPlainMarkdown } from \"./context-loaders.js\";\nimport { insideRoot } from \"./path-containment.js\";\n\n/** EC-Q: line-anchored. `@path` must be alone on its line. */\nconst IMPORT_RE = /^@(\\S+)\\s*$/gm;\nconst MAX_HOPS = 5;\n\nexport interface ResolveImportsOptions {\n /** Absolute paths already resolved (cycle detection). */\n readonly visited: Set<string>;\n /** Current recursion depth — caps at MAX_HOPS. */\n readonly depth: number;\n /** Per-import file cap (EC-D). Forwarded to loadPlainMarkdown. */\n readonly maxBytesPerFile: number;\n /**\n * The directory an import may not escape. When set, a target resolving outside it is\n * refused and replaced with a placeholder.\n *\n * The file carrying the import is REPOSITORY-CONTROLLED — `CLAUDE.md` and `GEMINI.md`\n * are the two default specs with `followImports: true`, and both are found by\n * `git-root-walk` inside the tree the agent was pointed at. Without a root, a cloned\n * repository could name `@~/.ssh/id_rsa` or any absolute path and have its contents\n * inlined into the system prompt, and from there sent to the model provider. The\n * traversal guard that already existed (`isSafePattern`) guards the discovery PATTERN,\n * not the import TARGET, so it never saw this.\n *\n * OPTIONAL, so a caller outside the discovery path keeps the previous behaviour rather\n * than breaking on an upgrade. `runDiscovery` always supplies `gitRoot ?? cwd` — the\n * same value it already uses to keep absolute paths out of `<source name=\"\">`.\n */\n readonly projectRoot?: string;\n}\n\n/**\n * Resolve `@path` directives in `content`. Each match is replaced with\n * the imported file content (already cap-truncated per EC-D), and\n * recursion continues on the resolved content until MAX_HOPS or cycle.\n *\n * Failure modes (placeholders, never throws):\n * - file not found → `[@import not found: <path>]`\n * - cycle detected → `[@import cycle detected: <path>]`\n * - depth exceeded → trailing `\\n\\n…[@import depth limit 5 reached]\\n\\n`\n *\n * @internal\n */\nexport async function resolveImports(\n content: string,\n basePath: string,\n opts: ResolveImportsOptions,\n): Promise<string> {\n if (opts.depth >= MAX_HOPS) {\n return `${content}\\n\\n…[@import depth limit ${MAX_HOPS} reached]\\n\\n`;\n }\n const baseDir = dirname(basePath);\n return replaceAsync(content, IMPORT_RE, async (raw) => {\n const absolute = resolveImportPath(raw, baseDir);\n // Containment BEFORE the read. Reporting the refusal after loading the file would\n // already have put the bytes in memory, and the placeholder names the path the author\n // wrote rather than the resolved one — echoing `/home/<user>/.ssh/id_rsa` back into the\n // prompt would leak the layout of the machine to the same untrusted document.\n if (opts.projectRoot !== undefined && !insideRoot(absolute, opts.projectRoot)) {\n return `[@import outside the project root, refused: ${raw}]`;\n }\n if (opts.visited.has(absolute)) {\n return `[@import cycle detected: ${raw}]`;\n }\n opts.visited.add(absolute);\n const loaded = await loadPlainMarkdown(absolute, { maxBytesPerFile: opts.maxBytesPerFile });\n if (loaded === undefined) {\n return `[@import not found: ${raw}]`;\n }\n // Recurse with same visited set + incremented depth. `projectRoot` is FORWARDED: an\n // imported file is repository-controlled too, so a root that applied only at depth 0\n // would be escapable in one extra hop.\n return resolveImports(loaded.content, absolute, {\n visited: opts.visited,\n depth: opts.depth + 1,\n maxBytesPerFile: opts.maxBytesPerFile,\n ...(opts.projectRoot === undefined ? {} : { projectRoot: opts.projectRoot }),\n });\n });\n}\n\nfunction resolveImportPath(raw: string, baseDir: string): string {\n if (raw.startsWith(\"~/\")) {\n return resolvePath(join(homedir(), raw.slice(2)));\n }\n if (isAbsolute(raw)) {\n return resolvePath(raw);\n }\n return resolvePath(join(baseDir, raw));\n}\n\n/**\n * Async equivalent of `String.prototype.replace` — sequential. Returns\n * `content` with every `re` match replaced by `await replacer(match)`.\n *\n * @internal\n */\nasync function replaceAsync(\n content: string,\n re: RegExp,\n replacer: (raw: string) => Promise<string>,\n): Promise<string> {\n const matches: Array<{ match: string; raw: string; index: number }> = [];\n // Reset lastIndex; clone the regex to avoid stateful traps.\n const localRe = new RegExp(re.source, re.flags);\n let m: RegExpExecArray | null;\n // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex loop\n while ((m = localRe.exec(content)) !== null) {\n matches.push({ match: m[0], raw: m[1] ?? \"\", index: m.index });\n if (m.index === localRe.lastIndex) localRe.lastIndex += 1;\n }\n if (matches.length === 0) return content;\n let result = \"\";\n let cursor = 0;\n for (const entry of matches) {\n result += content.slice(cursor, entry.index);\n result += await replacer(entry.raw);\n cursor = entry.index + entry.match.length;\n }\n result += content.slice(cursor);\n return result;\n}\n","/**\n * Shared glob → RegExp compiler for context discovery (extracted from the\n * MDC parser so the `.cursor/rules/*.mdc` and `.theokit/rules/*.md` parsers\n * share ONE implementation — DRY, no new dependency).\n *\n * Supports `**` (any depth), `*` (single path segment, no `/`), `?` (single\n * non-separator char). Sufficient for the patterns Cursor and Claude Code\n * themselves recommend.\n *\n * @internal\n */\nexport function globToRegex(glob: string): RegExp {\n // Support **/ (zero-or-more path segments), ** (any depth), * (single\n // segment), ? (single non-separator char). `**/` collapses so `src/**/*.ts`\n // matches `src/foo.ts` AND `src/a/b/foo.ts` — the semantics Cursor and Claude\n // Code document (\"src/**/* → all files under src/\"). `*` and `?` never cross a\n // `/` (strict glob segment semantics). Escape other regex metas.\n const compiled = glob\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*\\*\\//g, \"::GLOBSTAR_SLASH::\")\n .replace(/\\*\\*/g, \"::GLOBSTAR::\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/\\?/g, \"[^/]\")\n .replace(/::GLOBSTAR_SLASH::/g, \"(?:.*/)?\")\n .replace(/::GLOBSTAR::/g, \".*\");\n return new RegExp(`^${compiled}$`);\n}\n\n/**\n * True when any of `patterns` glob-matches any of `paths`. Central helper so\n * both rule parsers agree on activation semantics.\n *\n * @internal\n */\nexport function anyGlobMatches(\n patterns: ReadonlyArray<string>,\n paths: ReadonlyArray<string>,\n): boolean {\n if (patterns.length === 0 || paths.length === 0) return false;\n const res = patterns.map(globToRegex);\n return paths.some((p) => res.some((re) => re.test(p)));\n}\n","/**\n * Parser for `.theokit/rules/*.md` — theokit-native path-scoped rules,\n * mirroring Claude Code's `.claude/rules/`.\n *\n * Frontmatter fields:\n * - `paths` — glob-pattern array (Claude Code parity: `.claude/rules` uses `paths:`).\n * - `globs` — glob-pattern array (Cursor-compatible alias; unioned with `paths`).\n * - `alwaysApply` — activate unconditionally (no scope needed).\n * - `enabled` — set `false` to disable the rule entirely (wins over everything).\n * - `description` — human note; not used for activation in v1.\n *\n * Activation (`shouldActivateRule`):\n * - `enabled: false` → never activates.\n * - `alwaysApply: true` → always activates.\n * - otherwise → activates iff a `paths`/`globs` pattern matches an in-scope\n * file (the caller's `agent.send(..., { contextPaths })`). With no in-scope\n * files, a scoped rule stays dormant (parity with Cursor `.mdc`).\n *\n * A file with no `---` frontmatter is treated as an unconditional rule\n * (`alwaysApply: true`). Malformed YAML → `undefined` (caller drops + counts).\n *\n * Glob + YAML subset are shared with the MDC parser (DRY, no new dependency).\n *\n * NOTE — no internal-visibility tag in this block. `tsconfig.base.json` sets `stripInternal: true`,\n * and TypeScript scans EVERY leading comment range of the declaration that follows, including the\n * import right below this one. The tag that used to sit here deleted that import from the emitted\n * `.d.ts`, leaving the types it binds unresolvable for any consumer running type-aware lint\n * (usetheodev/theokit-sdk#283 records the same trap on a declaration).\n */\n\nimport { z } from \"zod\";\n\nimport { anyGlobMatches } from \"./context-glob.js\";\nimport { parseSimpleYaml, splitFrontmatter } from \"./context-yaml-lite.js\";\n\n/**\n * YAML frontmatter schema for `.theokit/rules/*.md` files.\n *\n * Every field is optional, and unknown keys are dropped rather than rejected — `safeParse` failing\n * therefore means a declared key had the wrong TYPE (`paths` as a bare string, `enabled` as the\n * string `\"false\"`), never that an unexpected key was present.\n *\n * Exported because {@link RulesFrontmatter} is inferred from it and appears in the signature of\n * {@link shouldActivateRule}, which `@theokit/sdk/context` publishes: the constant has to reach the\n * emitted declarations for that inferred type to resolve in a consumer's project.\n */\nexport const RulesFrontmatterSchema = z.object({\n description: z.string().optional(),\n paths: z.array(z.string()).optional(),\n globs: z.array(z.string()).optional(),\n alwaysApply: z.boolean().optional(),\n enabled: z.boolean().optional(),\n});\n\nexport type RulesFrontmatter = z.infer<typeof RulesFrontmatterSchema>;\n\nexport interface RulesParseResult {\n readonly frontmatter: RulesFrontmatter;\n readonly body: string;\n}\n\n/**\n * Split a `.theokit/rules/*.md` document into its frontmatter and its body.\n *\n * A file with no `---` fence is not an error: the whole text becomes the body and the frontmatter\n * is synthesised as `{ alwaysApply: true }`, so a plain markdown rule with no metadata applies\n * unconditionally. That default is the one thing to know before adding a fence — adding one and\n * omitting both `alwaysApply` and a path pattern turns an always-on rule into a dormant one.\n *\n * `undefined` means the fence was there and its contents did not survive: YAML the lite parser\n * could not read, or a shape the schema rejected (`paths` as a bare string rather than a list,\n * `enabled` as `\"false\"` rather than `false`). It never throws, and it does not say WHICH of the\n * two happened — the caller drops the file and counts it.\n *\n * Unknown frontmatter keys are dropped rather than rejected, so a misspelled `alwaysAply` parses\n * fine and simply has no effect.\n *\n * Nothing here decides whether the rule applies; that is `shouldActivateRule`.\n *\n * @public — re-exported from `@theokit/sdk/context`, and therefore under semver.\n */\nexport function parseRules(content: string): RulesParseResult | undefined {\n const { yaml, body } = splitFrontmatter(content);\n if (yaml === undefined) {\n return { frontmatter: { alwaysApply: true }, body: content };\n }\n try {\n const parsed = parseSimpleYaml(yaml);\n const validated = RulesFrontmatterSchema.safeParse(parsed);\n if (!validated.success) return undefined;\n return { frontmatter: validated.data, body };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Decide whether a parsed rule applies to this turn, given the files in scope.\n *\n * Checked in strict order, first match wins:\n *\n * 1. `enabled: false` — never applies, whatever else the frontmatter says. This overrides\n * `alwaysApply: true`, which is the point of having it.\n * 2. `alwaysApply: true` — applies, and `inScopePaths` is not consulted.\n * 3. otherwise — applies only if some pattern from `paths` or `globs` matches some path in\n * `inScopePaths`. The two lists are a union, not alternatives; `globs` exists for\n * Cursor compatibility and behaves identically.\n *\n * A scoped rule with an EMPTY `inScopePaths` never applies. That is the case worth planning for:\n * `contextPaths` is empty on a plain `agent.send()`, so a rule scoped by path stays dormant until\n * the caller declares which files the turn is about. It is quiet — a dormant rule looks exactly\n * like a rule that was never written.\n *\n * A rule with a fence but no patterns and no `alwaysApply` also never applies, since step 3 has\n * nothing to match.\n *\n * `description` is not consulted. It is a note for humans, not an activation condition.\n *\n * @public — re-exported from `@theokit/sdk/context`, and therefore under semver.\n */\nexport function shouldActivateRule(\n fm: RulesFrontmatter,\n inScopePaths: ReadonlyArray<string>,\n): boolean {\n if (fm.enabled === false) return false;\n if (fm.alwaysApply === true) return true;\n const patterns = [...(fm.paths ?? []), ...(fm.globs ?? [])];\n if (patterns.length === 0) return false;\n return anyGlobMatches(patterns, inScopePaths);\n}\n","/**\n * MDC (Markdown Cursor) parser for `.cursor/rules/*.mdc` (T3.1, ADR D154).\n *\n * Parses YAML frontmatter with `description` / `globs` / `alwaysApply`\n * fields. Honors activation per Cursor's docs:\n * - `alwaysApply: true` → activates unconditionally.\n * - `alwaysApply: false` + matching glob in `touchedFiles` → activates.\n * - `alwaysApply: false` + no glob match → skipped.\n *\n * **EC-I (v1 semantic):** at `agent.send()` time, `touchedFiles` is empty\n * UNLESS the caller passes `contextPaths` (the in-scope file set). When\n * `contextPaths` is provided, per-glob activation fires; otherwise only\n * `alwaysApply: true` rules activate. Description-based \"agent requested\"\n * classification is out of scope.\n *\n * Glob matching + the YAML subset are shared with the `.theokit/rules/*.md`\n * parser via `context-glob.ts` / `context-yaml-lite.ts` (DRY, no new dep).\n *\n * @internal\n */\n\nimport { z } from \"zod\";\n\nimport { anyGlobMatches } from \"./context-glob.js\";\nimport { parseSimpleYaml, splitFrontmatter } from \"./context-yaml-lite.js\";\n\n/** YAML frontmatter schema for MDC files. @internal */\nexport const McdFrontmatterSchema = z.object({\n description: z.string().optional(),\n globs: z.array(z.string()).optional(),\n alwaysApply: z.boolean().optional(),\n});\n\nexport type McdFrontmatter = z.infer<typeof McdFrontmatterSchema>;\n\nexport interface McdParseResult {\n readonly frontmatter: McdFrontmatter;\n readonly body: string;\n}\n\n/**\n * Parse `.mdc` content. Returns frontmatter + body. EC-18: when the\n * file has no `---` frontmatter, returns `frontmatter: {alwaysApply:\n * true}` so the body is treated as an unconditional rule.\n *\n * On YAML parse error, returns `undefined` and the caller emits a\n * telemetry counter (EC-21).\n *\n * @internal\n */\nexport function parseMdc(content: string): McdParseResult | undefined {\n const { yaml, body } = splitFrontmatter(content);\n if (yaml === undefined) {\n // EC-18: no frontmatter — treat as alwaysApply\n return { frontmatter: { alwaysApply: true }, body: content };\n }\n try {\n const parsed = parseSimpleYaml(yaml);\n const validated = McdFrontmatterSchema.safeParse(parsed);\n if (!validated.success) return undefined;\n return { frontmatter: validated.data, body };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Decide whether an MDC rule activates given the currently-touched\n * files. EC-I: empty `touchedFiles` → only `alwaysApply: true`\n * activates.\n *\n * @internal\n */\nexport function shouldActivate(fm: McdFrontmatter, touchedFiles: ReadonlyArray<string>): boolean {\n if (fm.alwaysApply === true) return true;\n if (fm.globs === undefined || fm.globs.length === 0) return false;\n return anyGlobMatches(fm.globs, touchedFiles);\n}\n","/**\n * Multi-format context discovery runner (T5.1, ADRs D150-D156).\n *\n * Walks `DEFAULT_DISCOVERY_SPECS` (or caller override), loads each\n * spec via the appropriate parser, applies `@import` resolution where\n * declared, and returns a flat list of `AggregatorSource[]` ready for\n * the aggregate cap.\n *\n * **EC-E privacy fix:** source disambiguation uses\n * `relative(gitRoot ?? cwd, ...)` — NEVER absolute paths in\n * `<source name=\"\">`.\n *\n * @internal\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { dirname, relative } from \"node:path\";\n\nimport type { AggregatorSource } from \"./context-aggregator.js\";\nimport {\n DEFAULT_DISCOVERY_SPECS,\n type DiscoverySpec,\n findGitRoot,\n walkUpForFile,\n walkUpForGlob,\n} from \"./context-discovery.js\";\nimport { resolveImports } from \"./context-import-resolver.js\";\nimport { loadPlainMarkdown } from \"./context-loaders.js\";\nimport { parseMdc, shouldActivate } from \"./context-mdc-parser.js\";\nimport { parseRules, shouldActivateRule } from \"./context-rules-frontmatter.js\";\n\n/**\n * Input to the context-discovery run: where to walk, how much of each file to keep, and which\n * trust boundary an `@import` may not cross.\n *\n * `cwd` and `maxBytesPerFile` are required because neither has a safe default — an unset root walks\n * the wrong tree, and an unset cap lets one large file consume the context window.\n *\n * `importRoot` is the field to reach for when the caller's trust boundary is narrower than the\n * repository. Left unset, the repository IS the boundary (`gitRoot ?? cwd`), which is the honest\n * default for a document found by walking the repository — but it does mean an `@import` can pull\n * in any file the repo contains.\n *\n * @public — re-exported from '@theokit/sdk/context', and therefore under semver.\n */\nexport interface DiscoveryRunnerOptions {\n /** Workspace root passed to all discovery scopes. */\n readonly cwd: string;\n /** Per-file truncation cap (D155). */\n readonly maxBytesPerFile: number;\n /** Optional override of the default registry. */\n readonly specs?: ReadonlyArray<DiscoverySpec>;\n /** Cursor MDC: file paths the LLM has touched this turn (EC-I: empty at send-time). */\n readonly touchedFiles?: ReadonlyArray<string>;\n /** When true, skip `theokit-context` spec — caller already handles the legacy path. */\n readonly skipLegacyTheokitContext?: boolean;\n /**\n * The root an `@import` may not escape. Defaults to `gitRoot ?? cwd` — the same value\n * this runner already uses to keep absolute paths out of `<source name=\"\">`.\n *\n * Present so an embedder with a trust boundary narrower than the repository can declare\n * it. Absent, the repository IS the boundary, which is the honest default for a document\n * discovered by walking the repository.\n */\n readonly importRoot?: string;\n}\n\n/**\n * Find, read and parse every context file the specs describe, and return them ready for the\n * aggregator.\n *\n * Specs are processed in the order given — `opts.specs` when supplied, otherwise\n * `DEFAULT_DISCOVERY_SPECS` — and within a spec, in the order its scope resolves paths. The\n * returned array carries `priority` on each source; it is NOT sorted here, so the aggregator is\n * what applies the ordering.\n *\n * A path already emitted is skipped, across specs as well as within one. Paths arrive resolved\n * through `realpath`, so two specs pointing at the same physical file through a symlink produce\n * one source rather than two.\n *\n * Missing and unreadable files are not errors: a file that does not exist is simply not matched,\n * and one that fails to read or parse is dropped and the run continues. A frontmatter file whose\n * activation conditions do not hold — `enabled: false`, or a scope that no touched file matches —\n * is dropped the same way. So a shorter result than expected means \"nothing qualified\", and this\n * function will not tell you which of those it was.\n *\n * Reads the filesystem and nothing else. No network, no writes, and no mutation of `opts`.\n *\n * Privacy: `<source name=\"\">` is built from the path RELATIVE to the git root (or `cwd` when\n * there is no git root), never an absolute path, so a home directory never reaches the prompt.\n * `@import` resolution is bounded by `opts.importRoot`, defaulting to that same root — a document\n * cannot pull in a file from outside the repository.\n *\n * @public — re-exported from `@theokit/sdk/context`, and therefore under semver.\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: per-spec dispatch ladder + cross-spec dedup is a flat orchestrator; splitting would obscure the priority-merge invariant.\nexport async function runDiscovery(opts: DiscoveryRunnerOptions): Promise<AggregatorSource[]> {\n const gitRoot = findGitRoot(opts.cwd);\n const specs = opts.specs ?? DEFAULT_DISCOVERY_SPECS;\n const out: AggregatorSource[] = [];\n const seenReal = new Set<string>();\n\n for (const spec of specs) {\n if (opts.skipLegacyTheokitContext && spec.id === \"theokit-context\") continue;\n const paths = await resolvePathsForSpec(spec, opts.cwd, gitRoot);\n for (const path of paths) {\n if (seenReal.has(path)) continue;\n seenReal.add(path);\n const source = await loadOneSource(spec, path, opts, gitRoot);\n if (source !== undefined) out.push(source);\n }\n }\n return out;\n}\n\nasync function resolvePathsForSpec(\n spec: DiscoverySpec,\n cwd: string,\n gitRoot: string | undefined,\n): Promise<string[]> {\n if (spec.scope === \"cwd-only\") {\n return walkUpForFile(cwd, spec.pattern, cwd);\n }\n if (spec.scope === \"git-root-walk\") {\n return walkUpForFile(cwd, spec.pattern, gitRoot ?? cwd);\n }\n // globbed\n return walkUpForGlob(cwd, spec.pattern);\n}\n\nasync function loadOneSource(\n spec: DiscoverySpec,\n path: string,\n opts: DiscoveryRunnerOptions,\n gitRoot: string | undefined,\n): Promise<AggregatorSource | undefined> {\n // EC-E privacy: name uses relative-to-git-root for disambiguation;\n // NEVER absolute paths in the public `<source name=\"\">` attribute.\n // For cwd-only specs (THEO.md), no disambiguation is needed because\n // the scope can only ever match a single file.\n const relPath = relative(gitRoot ?? opts.cwd, dirname(path));\n const needsSuffix = spec.scope !== \"cwd-only\" && relPath !== \"\" && relPath !== \".\";\n const id = needsSuffix ? `${spec.id}@${relPath}` : spec.id;\n\n if (spec.parser === \"mdc\") {\n return loadMdcSource(spec, path, id, opts);\n }\n if (spec.parser === \"rules-frontmatter\") {\n return loadRulesSource(spec, path, id, opts);\n }\n if (spec.parser === \"frontmatter-zod\") {\n // Legacy `.theokit/context/*.md` — handled by `loadContextConfig` in\n // `context-manager.ts` for backward compat. We skip here unless caller\n // explicitly wants us to load it (currently always skipped).\n return undefined;\n }\n // plain-markdown\n return loadPlainMarkdownSource(spec, path, id, opts, gitRoot);\n}\n\n/**\n * Shared read → parse → activation-gate → source pipeline for the frontmatter\n * discovery parsers (mdc, rules). Binds the parser + the activation predicate so\n * the mdc and rules loaders differ only in those two, not in the surrounding\n * read/guard/shape boilerplate (DRY).\n */\nasync function loadParsedSource<F>(\n spec: DiscoverySpec,\n path: string,\n id: string,\n parse: (raw: string) => { frontmatter: F; body: string } | undefined,\n isActive: (frontmatter: F) => boolean,\n): Promise<AggregatorSource | undefined> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch {\n return undefined;\n }\n const parsed = parse(raw);\n if (parsed === undefined || !isActive(parsed.frontmatter)) return undefined;\n return { id, source: path, content: parsed.body, priority: spec.priority, truncated: false };\n}\n\nfunction loadMdcSource(\n spec: DiscoverySpec,\n path: string,\n id: string,\n opts: DiscoveryRunnerOptions,\n): Promise<AggregatorSource | undefined> {\n return loadParsedSource(spec, path, id, parseMdc, (fm) =>\n shouldActivate(fm, opts.touchedFiles ?? []),\n );\n}\n\nfunction loadRulesSource(\n spec: DiscoverySpec,\n path: string,\n id: string,\n opts: DiscoveryRunnerOptions,\n): Promise<AggregatorSource | undefined> {\n return loadParsedSource(spec, path, id, parseRules, (fm) =>\n shouldActivateRule(fm, opts.touchedFiles ?? []),\n );\n}\n\nasync function loadPlainMarkdownSource(\n spec: DiscoverySpec,\n path: string,\n id: string,\n opts: DiscoveryRunnerOptions,\n gitRoot: string | undefined,\n): Promise<AggregatorSource | undefined> {\n const loaded = await loadPlainMarkdown(path, { maxBytesPerFile: opts.maxBytesPerFile });\n if (loaded === undefined) return undefined;\n let content = loaded.content;\n if (spec.followImports) {\n content = await resolveImports(content, path, {\n visited: new Set([path]),\n depth: 0,\n maxBytesPerFile: opts.maxBytesPerFile,\n // The document carrying the import is repository-controlled, so the repository is the\n // boundary it may not cross. Without this, `CLAUDE.md` / `GEMINI.md` — the two specs\n // with `followImports: true` — could name any absolute or `~/` path and have it\n // inlined into the system prompt.\n projectRoot: opts.importRoot ?? gitRoot ?? opts.cwd,\n });\n }\n return {\n id,\n source: path,\n content,\n priority: spec.priority,\n truncated: loaded.truncated,\n };\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkGHX4P3V2_cjs = require('./chunk-GHX4P3V2.cjs');
4
+ var chunkKRD3GQAA_cjs = require('./chunk-KRD3GQAA.cjs');
4
5
  var chunkR3UPQFKK_cjs = require('./chunk-R3UPQFKK.cjs');
5
6
  var chunkSKXBJ2NU_cjs = require('./chunk-SKXBJ2NU.cjs');
6
7
  var chunkZF2LDKQQ_cjs = require('./chunk-ZF2LDKQQ.cjs');
@@ -77,7 +78,7 @@ function defaultIndexPath(cwd) {
77
78
  }
78
79
  function requireLance() {
79
80
  try {
80
- const r = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-5UOVYM3P.cjs', document.baseURI).href)));
81
+ const r = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-E7JHD6QJ.cjs', document.baseURI).href)));
81
82
  return r("@lancedb/lancedb");
82
83
  } catch (cause) {
83
84
  throw new chunkK3FW2XZD_cjs.ConfigurationError(
@@ -300,6 +301,7 @@ async function openLanceIndex(opts) {
300
301
  });
301
302
  return new LanceMemoryAdapter(lance);
302
303
  }
304
+ var MEMORY_KINDS = ["user", "feedback", "project", "reference"];
303
305
  function legacyMemoryJsonPath(cwd, config) {
304
306
  if (config.storePath !== void 0) {
305
307
  return path.resolve(cwd, config.storePath);
@@ -310,6 +312,54 @@ function legacyMemoryJsonPath(cwd, config) {
310
312
  return chunkR3UPQFKK_cjs.safePathJoin(cwd, ".theokit", "memory", namespace, `${scope}-${userId}.json`);
311
313
  }
312
314
 
315
+ // src/internal/memory/storage/memory-file.ts
316
+ var SAFE_SLUG = /^[a-z0-9][a-z0-9_-]*$/;
317
+ var MAX_SLUG_LENGTH = 64;
318
+ function slugForFact(text) {
319
+ const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_SLUG_LENGTH).replace(/-+$/, "");
320
+ return SAFE_SLUG.test(slug) ? slug : chunkR3UPQFKK_cjs.safeFilenameForId(text);
321
+ }
322
+ function renderMemoryFile(fields) {
323
+ const metadata = [" node_type: memory"];
324
+ if (fields.kind !== void 0) metadata.push(` type: ${fields.kind}`);
325
+ if (fields.modified !== void 0) metadata.push(` modified: ${fields.modified}`);
326
+ return [
327
+ "---",
328
+ `name: ${fields.name}`,
329
+ `description: ${JSON.stringify(fields.description)}`,
330
+ "metadata:",
331
+ ...metadata,
332
+ "---",
333
+ "",
334
+ fields.body,
335
+ ""
336
+ ].join("\n");
337
+ }
338
+ function parseMemoryFile(raw) {
339
+ const { yaml, body } = chunkKRD3GQAA_cjs.splitFrontmatter(raw);
340
+ if (yaml === void 0) return void 0;
341
+ let fields;
342
+ try {
343
+ fields = chunkKRD3GQAA_cjs.parseSimpleYaml(yaml);
344
+ } catch {
345
+ return void 0;
346
+ }
347
+ const name = fields.name;
348
+ const description = fields.description;
349
+ if (typeof name !== "string" || typeof description !== "string") return void 0;
350
+ const metadata = fields.metadata ?? {};
351
+ const rawKind = metadata.type;
352
+ const kind = typeof rawKind === "string" && MEMORY_KINDS.includes(rawKind) ? rawKind : void 0;
353
+ const modified = typeof metadata.modified === "string" ? metadata.modified : void 0;
354
+ return {
355
+ name,
356
+ description,
357
+ ...kind !== void 0 ? { kind } : {},
358
+ ...modified !== void 0 ? { modified } : {},
359
+ body: body.trim()
360
+ };
361
+ }
362
+
313
363
  // src/internal/memory/storage/markdown-store.ts
314
364
  var MEMORY_MD_HEADER = "# Memory\n\n> Auto-managed by @theokit/sdk. Edit freely \u2014 the SDK reads from here.\n";
315
365
  var FACTS_HEADING = "## Facts";
@@ -323,65 +373,86 @@ function notesDir(cwd) {
323
373
  return path.join(memoryDir(cwd), "notes");
324
374
  }
325
375
  async function readFactsFromMarkdown(cwd) {
326
- let raw;
376
+ let entries;
327
377
  try {
328
- raw = await promises.readFile(memoryMdPath(cwd), "utf8");
378
+ entries = await promises.readdir(memoryDir(cwd));
329
379
  } catch {
330
380
  return [];
331
381
  }
332
- return parseFactsSection(raw);
382
+ const facts = [];
383
+ for (const entry of entries.sort()) {
384
+ const fact = await readMemoryFileAt(cwd, entry);
385
+ if (fact !== void 0) facts.push(fact);
386
+ }
387
+ try {
388
+ facts.push(...parseFactsSection(await promises.readFile(memoryMdPath(cwd), "utf8")));
389
+ } catch {
390
+ }
391
+ return facts;
392
+ }
393
+ async function readMemoryFileAt(cwd, entry) {
394
+ if (!entry.endsWith(".md") || entry === "MEMORY.md") return void 0;
395
+ let raw;
396
+ try {
397
+ raw = await promises.readFile(path.join(memoryDir(cwd), entry), "utf8");
398
+ } catch {
399
+ return void 0;
400
+ }
401
+ const parsed = parseMemoryFile(raw);
402
+ if (parsed === void 0) return void 0;
403
+ return {
404
+ text: parsed.description,
405
+ ...parsed.kind !== void 0 ? { kind: parsed.kind } : {},
406
+ ...parsed.modified !== void 0 ? { modified: parsed.modified } : {}
407
+ };
333
408
  }
334
409
  function appendFactToMarkdown(cwd, fact) {
335
410
  return chunkZF2LDKQQ_cjs.withCwdMutex(memoryDir(cwd), async () => {
336
- const path = memoryMdPath(cwd);
337
- let raw = "";
338
- try {
339
- raw = await promises.readFile(path, "utf8");
340
- } catch {
341
- raw = "";
411
+ if (fact.kind !== void 0 && !MEMORY_KINDS.includes(fact.kind)) {
412
+ throw new chunkK3FW2XZD_cjs.ConfigurationError(
413
+ `Unknown memory fact kind "${fact.kind}". Expected one of: ${MEMORY_KINDS.join(", ")}.`,
414
+ { code: "invalid_memory_kind" }
415
+ );
342
416
  }
343
- const sanitized = chunkJTB5Q42C_cjs.redactSecrets(fact.text);
344
- const next = insertFactBullet(raw, sanitized);
417
+ const text = chunkJTB5Q42C_cjs.redactSecrets(fact.text);
418
+ const name = slugForFact(text);
345
419
  await promises.mkdir(memoryDir(cwd), { recursive: true });
346
- await chunkI6TGFUCO_cjs.replaceFileAtomic(path, next);
420
+ await chunkI6TGFUCO_cjs.replaceFileAtomic(
421
+ path.join(memoryDir(cwd), `${name}.md`),
422
+ renderMemoryFile({
423
+ name,
424
+ description: text,
425
+ ...fact.kind !== void 0 ? { kind: fact.kind } : {},
426
+ modified: (/* @__PURE__ */ new Date()).toISOString(),
427
+ body: text
428
+ })
429
+ );
430
+ await chunkI6TGFUCO_cjs.replaceFileAtomic(memoryMdPath(cwd), await nextIndex(cwd, text, name));
347
431
  });
348
432
  }
433
+ async function nextIndex(cwd, text, name) {
434
+ let existing = "";
435
+ try {
436
+ existing = await promises.readFile(memoryMdPath(cwd), "utf8");
437
+ } catch {
438
+ existing = "";
439
+ }
440
+ const entry = `- [${text}](${name}.md)`;
441
+ const kept = existing.split("\n").filter((line) => !line.startsWith(`- [`) || !line.includes(`](${name}.md)`));
442
+ const body = kept.join("\n").trimEnd();
443
+ const head = body.length > 0 ? body : MEMORY_MD_HEADER.trimEnd();
444
+ return `${head}
445
+ ${entry}
446
+ `;
447
+ }
448
+ var INDEX_ENTRY = /^\[[^\]]*\]\([^)]+\.md\)$/;
349
449
  function parseFactsSection(raw) {
350
450
  const idx = raw.indexOf(FACTS_HEADING);
351
451
  if (idx === -1) return [];
352
452
  const tail = raw.slice(idx + FACTS_HEADING.length);
353
453
  const nextHeading = tail.search(/\n#{1,2}\s/);
354
454
  const block = nextHeading === -1 ? tail : tail.slice(0, nextHeading);
355
- return block.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("- ")).map((line) => ({ text: line.slice(2).trim() }));
356
- }
357
- function insertFactBullet(raw, fact) {
358
- const bullet = `- ${fact}`;
359
- if (raw.length === 0) {
360
- return `${MEMORY_MD_HEADER}
361
- ${FACTS_HEADING}
362
-
363
- ${bullet}
364
- `;
365
- }
366
- const idx = raw.indexOf(FACTS_HEADING);
367
- if (idx === -1) {
368
- const sep = raw.endsWith("\n") ? "" : "\n";
369
- return `${raw}${sep}
370
- ${FACTS_HEADING}
371
-
372
- ${bullet}
373
- `;
374
- }
375
- const after = idx + FACTS_HEADING.length;
376
- const nextHeading = raw.slice(after).search(/\n#{1,2}\s/);
377
- if (nextHeading === -1) {
378
- const trailing = raw.endsWith("\n") ? "" : "\n";
379
- return `${raw}${trailing}${bullet}
380
- `;
381
- }
382
- const insertAt = after + nextHeading;
383
- return `${raw.slice(0, insertAt)}
384
- ${bullet}${raw.slice(insertAt)}`;
455
+ return block.split("\n").map((line) => line.trim()).filter((line) => line.startsWith("- ")).map((line) => line.slice(2).trim()).filter((body) => !INDEX_ENTRY.test(body)).map((body) => ({ text: body }));
385
456
  }
386
457
  async function readFacts(cwd, config) {
387
458
  if (!config.enabled) return [];
@@ -495,6 +566,19 @@ function blendScores(hit, vectorScore, weights) {
495
566
  ...vectorScore > 0 ? { vectorScore } : {}
496
567
  };
497
568
  }
569
+ async function markdownFilesIn(dir, root, skip = []) {
570
+ let entries;
571
+ try {
572
+ entries = await promises.readdir(dir);
573
+ } catch {
574
+ return [];
575
+ }
576
+ return entries.filter((entry) => entry.endsWith(".md") && !skip.includes(entry)).map((entry) => ({
577
+ absolutePath: path.join(dir, entry),
578
+ relPath: path.relative(root, path.join(dir, entry)),
579
+ source: "memory"
580
+ }));
581
+ }
498
582
  async function collectMarkdownFiles(cwd) {
499
583
  const root = memoryDir(cwd);
500
584
  const results = [];
@@ -507,15 +591,8 @@ async function collectMarkdownFiles(cwd) {
507
591
  });
508
592
  } catch {
509
593
  }
510
- try {
511
- const entries = await promises.readdir(notesDir(cwd));
512
- for (const entry of entries) {
513
- if (!entry.endsWith(".md")) continue;
514
- const abs = path.join(notesDir(cwd), entry);
515
- results.push({ absolutePath: abs, relPath: path.relative(root, abs), source: "memory" });
516
- }
517
- } catch {
518
- }
594
+ results.push(...await markdownFilesIn(memoryDir(cwd), root, ["MEMORY.md"]));
595
+ results.push(...await markdownFilesIn(notesDir(cwd), root));
519
596
  const wikiFiles = await discoverWikiFiles(cwd);
520
597
  for (const wiki of wikiFiles) {
521
598
  results.push({
@@ -972,6 +1049,7 @@ var IndexManager = class _IndexManager {
972
1049
 
973
1050
  exports.IndexManager = IndexManager;
974
1051
  exports.LanceIndex = LanceIndex;
1052
+ exports.MEMORY_KINDS = MEMORY_KINDS;
975
1053
  exports.appendFact = appendFact;
976
1054
  exports.appendFactToMarkdown = appendFactToMarkdown;
977
1055
  exports.defaultIndexPath = defaultIndexPath;
@@ -983,5 +1061,5 @@ exports.openMemoryDb = openMemoryDb;
983
1061
  exports.readFacts = readFacts;
984
1062
  exports.readFactsFromMarkdown = readFactsFromMarkdown;
985
1063
  exports.writeSessionSummary = writeSessionSummary;
986
- //# sourceMappingURL=chunk-5UOVYM3P.cjs.map
987
- //# sourceMappingURL=chunk-5UOVYM3P.cjs.map
1064
+ //# sourceMappingURL=chunk-E7JHD6QJ.cjs.map
1065
+ //# sourceMappingURL=chunk-E7JHD6QJ.cjs.map