@theglitchking/semantic-memory 1.2.2 → 1.5.1

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 (65) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +154 -0
  4. package/ROADMAP.md +5 -2
  5. package/commands/healthcheck.md +13 -11
  6. package/dist/{chunk-UQSASB7Q.js → chunk-2EELGTLX.js} +32 -3
  7. package/dist/chunk-2EELGTLX.js.map +1 -0
  8. package/dist/chunk-5ZKNO6ZD.js +96 -0
  9. package/dist/chunk-5ZKNO6ZD.js.map +1 -0
  10. package/dist/chunk-7M3JZOYH.js +105 -0
  11. package/dist/chunk-7M3JZOYH.js.map +1 -0
  12. package/dist/chunk-A3KYHKB5.js +176 -0
  13. package/dist/chunk-A3KYHKB5.js.map +1 -0
  14. package/dist/{chunk-CYWPDFNI.js → chunk-CSXZ7QUU.js} +4 -203
  15. package/dist/chunk-CSXZ7QUU.js.map +1 -0
  16. package/dist/chunk-FM26NFTR.js +272 -0
  17. package/dist/chunk-FM26NFTR.js.map +1 -0
  18. package/dist/chunk-NTYYS53O.js +91 -0
  19. package/dist/chunk-NTYYS53O.js.map +1 -0
  20. package/dist/{chunk-E2Z4J4MK.js → chunk-OWZWMB4P.js} +70 -2
  21. package/dist/chunk-OWZWMB4P.js.map +1 -0
  22. package/dist/chunk-S4KW7VH4.js +203 -0
  23. package/dist/chunk-S4KW7VH4.js.map +1 -0
  24. package/dist/{chunk-HODDRXJK.js → chunk-V45NJIHI.js} +4 -2
  25. package/dist/chunk-V45NJIHI.js.map +1 -0
  26. package/dist/chunk-WSU4QIPB.js +311 -0
  27. package/dist/chunk-WSU4QIPB.js.map +1 -0
  28. package/dist/cli/index.js +265 -65
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/core/index.d.ts +39 -2
  31. package/dist/core/index.js +17 -11
  32. package/dist/decay-ELJUKVQG.js +14 -0
  33. package/dist/dossier-2UG724LB.js +39 -0
  34. package/dist/healthcheck-IMB47LZE.js +19 -0
  35. package/dist/indexer-YW4O3VVM.js +8 -0
  36. package/dist/lexicon-5PXQEMUF.js +26 -0
  37. package/dist/lexicon-5PXQEMUF.js.map +1 -0
  38. package/dist/lint-XUQ7JDYX.js +16 -0
  39. package/dist/lint-XUQ7JDYX.js.map +1 -0
  40. package/dist/mcp/server.js +478 -40
  41. package/dist/mcp/server.js.map +1 -1
  42. package/dist/profile-LXUEF7TX.js +20 -0
  43. package/dist/profile-LXUEF7TX.js.map +1 -0
  44. package/dist/{schema-F3RTY6SQ.js → schema-ATFCLC6Y.js} +2 -2
  45. package/dist/schema-ATFCLC6Y.js.map +1 -0
  46. package/dist/{search-27WYAI24.js → search-AHKEFI2B.js} +2 -2
  47. package/dist/telemetry-GB2TEHVE.js +21 -0
  48. package/dist/telemetry-GB2TEHVE.js.map +1 -0
  49. package/hooks/vault-context.js +270 -11
  50. package/package.json +16 -1
  51. package/skills/vault-first/SKILL.md +18 -0
  52. package/dist/chunk-CAN2GWUG.js +0 -88
  53. package/dist/chunk-CAN2GWUG.js.map +0 -1
  54. package/dist/chunk-CYWPDFNI.js.map +0 -1
  55. package/dist/chunk-E2Z4J4MK.js.map +0 -1
  56. package/dist/chunk-HODDRXJK.js.map +0 -1
  57. package/dist/chunk-UQSASB7Q.js.map +0 -1
  58. package/dist/healthcheck-E4GI5RMZ.js +0 -14
  59. package/dist/indexer-KD6UTXZB.js +0 -8
  60. package/dist/lint-GMXQF4RW.js +0 -11
  61. /package/dist/{healthcheck-E4GI5RMZ.js.map → decay-ELJUKVQG.js.map} +0 -0
  62. /package/dist/{indexer-KD6UTXZB.js.map → dossier-2UG724LB.js.map} +0 -0
  63. /package/dist/{lint-GMXQF4RW.js.map → healthcheck-IMB47LZE.js.map} +0 -0
  64. /package/dist/{schema-F3RTY6SQ.js.map → indexer-YW4O3VVM.js.map} +0 -0
  65. /package/dist/{search-27WYAI24.js.map → search-AHKEFI2B.js.map} +0 -0
@@ -0,0 +1,311 @@
1
+ import {
2
+ deriveSessionDir
3
+ } from "./chunk-S4KW7VH4.js";
4
+
5
+ // src/core/dossier.ts
6
+ import { readFile, writeFile, mkdir } from "fs/promises";
7
+ import { existsSync } from "fs";
8
+ import { join } from "path";
9
+ import { glob } from "glob";
10
+ import matter from "gray-matter";
11
+
12
+ // src/core/section.ts
13
+ function extractSection(markdown, heading) {
14
+ const target = heading.trim().toLowerCase();
15
+ const lines = markdown.split("\n");
16
+ let startIdx = -1;
17
+ let startLevel = 0;
18
+ const headingAt = (line) => {
19
+ const m = /^(#{1,6})\s+(.*?)\s*$/.exec(line);
20
+ return m ? { level: m[1].length, text: m[2].trim().toLowerCase() } : null;
21
+ };
22
+ for (let i = 0; i < lines.length; i++) {
23
+ const h = headingAt(lines[i]);
24
+ if (h && h.text === target) {
25
+ startIdx = i;
26
+ startLevel = h.level;
27
+ break;
28
+ }
29
+ }
30
+ if (startIdx === -1) return null;
31
+ let endIdx = lines.length;
32
+ for (let i = startIdx + 1; i < lines.length; i++) {
33
+ const h = headingAt(lines[i]);
34
+ if (h && h.level <= startLevel) {
35
+ endIdx = i;
36
+ break;
37
+ }
38
+ }
39
+ return lines.slice(startIdx, endIdx).join("\n").trimEnd();
40
+ }
41
+
42
+ // src/core/dossier.ts
43
+ var DOSSIER_DIR = "dossiers";
44
+ var CACHE_FILE = "dossier-cache.json";
45
+ var DOSSIER_SECTIONS = [
46
+ "Purpose",
47
+ "Failure modes",
48
+ "Knobs & commands",
49
+ "Incident log",
50
+ "Current state"
51
+ ];
52
+ function slugifyEntity(entity) {
53
+ return entity.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "entity";
54
+ }
55
+ function normalizeEntity(s) {
56
+ return s.toLowerCase().trim().replace(/\s+/g, " ");
57
+ }
58
+ function sectionBody(markdown, heading) {
59
+ const section = extractSection(markdown, heading);
60
+ if (!section) return "";
61
+ return section.split("\n").slice(1).join("\n").trim();
62
+ }
63
+ function firstParagraph(body) {
64
+ const para = body.split(/\n\s*\n/).map((p) => p.trim()).find(Boolean);
65
+ return para ?? "";
66
+ }
67
+ function dossierTemplate(entity, opts = {}) {
68
+ const purpose = opts.purpose?.trim() || `_What ${entity} is and why it exists._`;
69
+ const seedNote = opts.seeded_from ? `
70
+ <!-- seeded from ${opts.seeded_from} -->
71
+ ` : "";
72
+ return `# ${entity}
73
+ ${seedNote}
74
+ ## Purpose
75
+
76
+ ${purpose}
77
+
78
+ ## Failure modes
79
+
80
+ _How it breaks, and the tell-tale symptoms._
81
+
82
+ ## Knobs & commands
83
+
84
+ _The buttons to push: commands, config, flags, dashboards._
85
+
86
+ ## Incident log
87
+
88
+ _Dated entries append here (newest last). Each: what happened \u2192 cause \u2192 fix._
89
+
90
+ ## Current state
91
+
92
+ _The single freshest sentence about where this entity stands right now._
93
+ `;
94
+ }
95
+ async function listDossiers(vaultPath) {
96
+ const files = await glob(`${DOSSIER_DIR}/**/*.md`, { cwd: vaultPath });
97
+ const entries = [];
98
+ for (const rel of files) {
99
+ try {
100
+ const raw = await readFile(join(vaultPath, rel), "utf-8");
101
+ const { data, content } = matter(raw);
102
+ if (data.type !== "dossier") continue;
103
+ const entity = typeof data.entity === "string" && data.entity.trim() ? data.entity.trim() : rel.replace(/^dossiers\//, "").replace(/\.md$/, "");
104
+ const aliases = Array.isArray(data.aliases) ? data.aliases.map(String).map((a) => a.trim()).filter(Boolean) : [];
105
+ entries.push({
106
+ entity,
107
+ aliases,
108
+ path: rel,
109
+ purpose: firstParagraph(sectionBody(content, "Purpose")),
110
+ current_state: sectionBody(content, "Current state")
111
+ });
112
+ } catch {
113
+ }
114
+ }
115
+ return entries.sort((a, b) => a.entity.localeCompare(b.entity));
116
+ }
117
+ async function compileDossiers(vaultPath) {
118
+ const entries = await listDossiers(vaultPath);
119
+ try {
120
+ const dir = deriveSessionDir(vaultPath);
121
+ await mkdir(dir, { recursive: true });
122
+ await writeFile(
123
+ join(dir, CACHE_FILE),
124
+ JSON.stringify({ compiledAt: (/* @__PURE__ */ new Date()).toISOString(), dossiers: entries }, null, 2),
125
+ "utf-8"
126
+ );
127
+ } catch {
128
+ }
129
+ return entries;
130
+ }
131
+ async function loadDossierCache(vaultPath) {
132
+ const cachePath = join(deriveSessionDir(vaultPath), CACHE_FILE);
133
+ if (existsSync(cachePath)) {
134
+ try {
135
+ const data = JSON.parse(await readFile(cachePath, "utf-8"));
136
+ if (Array.isArray(data?.dossiers)) return data.dossiers;
137
+ } catch {
138
+ }
139
+ }
140
+ return compileDossiers(vaultPath);
141
+ }
142
+ function findDossier(entries, entityOrAlias) {
143
+ const n = normalizeEntity(entityOrAlias);
144
+ return entries.find(
145
+ (d) => normalizeEntity(d.entity) === n || d.aliases.some((a) => normalizeEntity(a) === n)
146
+ );
147
+ }
148
+ function resolveDossierForQuery(entries, query) {
149
+ const nq = normalizeEntity(query);
150
+ let best;
151
+ for (const d of entries) {
152
+ const keys = [d.entity, ...d.aliases].map(normalizeEntity).filter(Boolean);
153
+ for (const k of keys) {
154
+ if (nq.includes(k) && (!best || k.length > best.keyLen)) {
155
+ best = { entry: d, keyLen: k.length };
156
+ }
157
+ }
158
+ }
159
+ return best?.entry;
160
+ }
161
+ async function readDossierRaw(vaultPath, rel) {
162
+ const abs = join(vaultPath, rel);
163
+ if (!existsSync(abs)) return null;
164
+ try {
165
+ const raw = await readFile(abs, "utf-8");
166
+ const { data } = matter(raw);
167
+ if (data.type !== "dossier") return null;
168
+ const entity = typeof data.entity === "string" ? data.entity : rel;
169
+ return { raw, entity };
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+ async function resolveDossierPath(vaultPath, entityOrPath) {
175
+ if (entityOrPath.endsWith(".md") && existsSync(join(vaultPath, entityOrPath))) return entityOrPath;
176
+ const entries = await listDossiers(vaultPath);
177
+ const match = findDossier(entries, entityOrPath);
178
+ if (match) return match.path;
179
+ return null;
180
+ }
181
+ async function initDossier(vaultPath, entity, opts = {}) {
182
+ const existing = await listDossiers(vaultPath);
183
+ const already = findDossier(existing, entity);
184
+ if (already) return { path: already.path, created: false, entity: already.entity };
185
+ const rel = join(DOSSIER_DIR, `${slugifyEntity(entity)}.md`);
186
+ const abs = join(vaultPath, rel);
187
+ if (existsSync(abs)) return { path: rel, created: false, entity };
188
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
189
+ const fm = {
190
+ title: entity,
191
+ type: "dossier",
192
+ entity,
193
+ status: "active",
194
+ last_verified: today,
195
+ confidence: "medium",
196
+ ...opts.aliases && opts.aliases.length > 0 ? { aliases: opts.aliases } : {},
197
+ ...opts.seeded_from ? { sources: [opts.seeded_from] } : {}
198
+ };
199
+ const body = dossierTemplate(entity, { purpose: opts.purpose, seeded_from: opts.seeded_from });
200
+ await mkdir(join(vaultPath, DOSSIER_DIR), { recursive: true });
201
+ await writeFile(abs, matter.stringify(body, fm), "utf-8");
202
+ await compileDossiers(vaultPath);
203
+ return { path: rel, created: true, entity };
204
+ }
205
+ async function appendIncident(vaultPath, entityOrPath, incident, opts = {}) {
206
+ const rel = await resolveDossierPath(vaultPath, entityOrPath);
207
+ if (!rel) return null;
208
+ const doc = await readDossierRaw(vaultPath, rel);
209
+ if (!doc) return null;
210
+ const today = opts.today ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
211
+ const line = `- ${today} \u2014 ${incident.trim()}`;
212
+ const patched = appendToSection(doc.raw, "Incident log", line);
213
+ await writeFile(join(vaultPath, rel), patched, "utf-8");
214
+ await compileDossiers(vaultPath);
215
+ return { path: rel };
216
+ }
217
+ async function setCurrentState(vaultPath, entityOrPath, state) {
218
+ const rel = await resolveDossierPath(vaultPath, entityOrPath);
219
+ if (!rel) return null;
220
+ const doc = await readDossierRaw(vaultPath, rel);
221
+ if (!doc) return null;
222
+ const patched = replaceSectionBody(doc.raw, "Current state", state.trim());
223
+ await writeFile(join(vaultPath, rel), patched, "utf-8");
224
+ await compileDossiers(vaultPath);
225
+ return { path: rel };
226
+ }
227
+ function locateSection(markdown, heading) {
228
+ const lines = markdown.split("\n");
229
+ const want = heading.trim().toLowerCase();
230
+ let start = -1;
231
+ let level = 0;
232
+ for (let i = 0; i < lines.length; i++) {
233
+ const m = lines[i].match(/^(#{1,6})\s+(.*?)\s*$/);
234
+ if (m && m[2].trim().toLowerCase() === want) {
235
+ start = i;
236
+ level = m[1].length;
237
+ break;
238
+ }
239
+ }
240
+ if (start === -1) return null;
241
+ let end = lines.length;
242
+ for (let i = start + 1; i < lines.length; i++) {
243
+ const m = lines[i].match(/^(#{1,6})\s+/);
244
+ if (m && m[1].length <= level) {
245
+ end = i;
246
+ break;
247
+ }
248
+ }
249
+ return { start, level, end, lines };
250
+ }
251
+ function isPlaceholderBody(body) {
252
+ const t = body.trim();
253
+ return t === "" || /^_.*_$/.test(t) && !t.includes("\n");
254
+ }
255
+ function appendToSection(markdown, heading, line) {
256
+ const loc = locateSection(markdown, heading);
257
+ if (!loc) {
258
+ const sep = markdown.endsWith("\n") ? "" : "\n";
259
+ return `${markdown}${sep}
260
+ ## ${heading}
261
+
262
+ ${line}
263
+ `;
264
+ }
265
+ const { lines, start, end } = loc;
266
+ const body = lines.slice(start + 1, end).join("\n");
267
+ const bodyStripped = body.trim();
268
+ const replacePlaceholder = isPlaceholderBody(bodyStripped);
269
+ let insertAt = end;
270
+ while (insertAt - 1 > start && lines[insertAt - 1].trim() === "") insertAt--;
271
+ const before = lines.slice(0, replacePlaceholder ? start + 1 : insertAt);
272
+ const after = lines.slice(replacePlaceholder ? end : insertAt);
273
+ const injected = replacePlaceholder ? ["", line] : [line];
274
+ return [...before, ...injected, ...after].join("\n");
275
+ }
276
+ function replaceSectionBody(markdown, heading, newBody) {
277
+ const loc = locateSection(markdown, heading);
278
+ if (!loc) {
279
+ const sep = markdown.endsWith("\n") ? "" : "\n";
280
+ return `${markdown}${sep}
281
+ ## ${heading}
282
+
283
+ ${newBody}
284
+ `;
285
+ }
286
+ const { lines, start, end } = loc;
287
+ const before = lines.slice(0, start + 1);
288
+ const after = lines.slice(end);
289
+ return [...before, "", newBody, "", ...after].join("\n").replace(/\n{3,}/g, "\n\n");
290
+ }
291
+
292
+ export {
293
+ extractSection,
294
+ DOSSIER_DIR,
295
+ DOSSIER_SECTIONS,
296
+ slugifyEntity,
297
+ normalizeEntity,
298
+ dossierTemplate,
299
+ listDossiers,
300
+ compileDossiers,
301
+ loadDossierCache,
302
+ findDossier,
303
+ resolveDossierForQuery,
304
+ resolveDossierPath,
305
+ initDossier,
306
+ appendIncident,
307
+ setCurrentState,
308
+ appendToSection,
309
+ replaceSectionBody
310
+ };
311
+ //# sourceMappingURL=chunk-WSU4QIPB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/dossier.ts","../src/core/section.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\nimport { deriveSessionDir } from \"./session.js\";\nimport { extractSection } from \"./section.js\";\n\n/**\n * Entity dossiers (v1.5 Phase 8) — the organizing unit of the resident expert.\n *\n * A dossier is a living note about one critical component/entity: what it is, how\n * it fails, which knobs to turn, its incident history, and its current state.\n * Knowledge accretes IN PLACE (incidents append to a log section; current-state is\n * revised) instead of spawning a new note per event — so the entity, not the file,\n * is the memory's unit. Canonical storage is markdown under <vault>/dossiers/\n * (type `dossier`); a derived JSON cache under .claude/.semantic-memory/ lets the\n * hot-path hook resolve an utterance to a dossier head for two-hop retrieval.\n */\n\nexport const DOSSIER_DIR = \"dossiers\";\nconst CACHE_FILE = \"dossier-cache.json\";\n\n/** The fixed section skeleton every dossier carries (order is meaningful). */\nexport const DOSSIER_SECTIONS = [\n \"Purpose\",\n \"Failure modes\",\n \"Knobs & commands\",\n \"Incident log\",\n \"Current state\",\n] as const;\n\nexport type DossierSection = (typeof DOSSIER_SECTIONS)[number];\n\nexport interface DossierEntry {\n /** The canonical entity name (e.g. \"payment-gateway\"). */\n entity: string;\n /** The human's phrases for this entity (feed the lexicon compiler). */\n aliases: string[];\n /** Dossier note path relative to vault root. */\n path: string;\n /** First paragraph of the Purpose section — the head injected on two-hop. */\n purpose: string;\n /** The Current state section body — the freshest one-liner about the entity. */\n current_state: string;\n}\n\nexport function slugifyEntity(entity: string): string {\n return (\n entity\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60) || \"entity\"\n );\n}\n\nexport function normalizeEntity(s: string): string {\n return s.toLowerCase().trim().replace(/\\s+/g, \" \");\n}\n\n/** Body-only text of a section (heading + leading/trailing blank lines stripped). */\nfunction sectionBody(markdown: string, heading: string): string {\n const section = extractSection(markdown, heading);\n if (!section) return \"\";\n // Drop the heading line, collapse to a trimmed body.\n return section\n .split(\"\\n\")\n .slice(1)\n .join(\"\\n\")\n .trim();\n}\n\n/** First non-empty paragraph of a section body (the \"head\" for injection). */\nfunction firstParagraph(body: string): string {\n const para = body.split(/\\n\\s*\\n/).map((p) => p.trim()).find(Boolean);\n return para ?? \"\";\n}\n\n/**\n * The dossier template — the fixed skeleton `dossier init` scaffolds and every\n * section edit patches into. Placeholders are intentionally terse prompts, not\n * prose, so an empty dossier is cheap to read and obvious to fill.\n */\nexport function dossierTemplate(entity: string, opts: { purpose?: string; seeded_from?: string } = {}): string {\n const purpose = opts.purpose?.trim() || `_What ${entity} is and why it exists._`;\n const seedNote = opts.seeded_from ? `\\n<!-- seeded from ${opts.seeded_from} -->\\n` : \"\";\n return `# ${entity}\n${seedNote}\n## Purpose\n\n${purpose}\n\n## Failure modes\n\n_How it breaks, and the tell-tale symptoms._\n\n## Knobs & commands\n\n_The buttons to push: commands, config, flags, dashboards._\n\n## Incident log\n\n_Dated entries append here (newest last). Each: what happened → cause → fix._\n\n## Current state\n\n_The single freshest sentence about where this entity stands right now._\n`;\n}\n\n/** Read every dossier note into an entry (best-effort; skips unreadable files). */\nexport async function listDossiers(vaultPath: string): Promise<DossierEntry[]> {\n const files = await glob(`${DOSSIER_DIR}/**/*.md`, { cwd: vaultPath });\n const entries: DossierEntry[] = [];\n for (const rel of files) {\n try {\n const raw = await readFile(join(vaultPath, rel), \"utf-8\");\n const { data, content } = matter(raw);\n if (data.type !== \"dossier\") continue;\n const entity = typeof data.entity === \"string\" && data.entity.trim() ? data.entity.trim() : rel.replace(/^dossiers\\//, \"\").replace(/\\.md$/, \"\");\n const aliases = Array.isArray(data.aliases) ? data.aliases.map(String).map((a) => a.trim()).filter(Boolean) : [];\n entries.push({\n entity,\n aliases,\n path: rel,\n purpose: firstParagraph(sectionBody(content, \"Purpose\")),\n current_state: sectionBody(content, \"Current state\"),\n });\n } catch {\n /* skip unreadable dossier */\n }\n }\n return entries.sort((a, b) => a.entity.localeCompare(b.entity));\n}\n\n/** Compile all dossiers into the derived cache the hook reads for two-hop routing. */\nexport async function compileDossiers(vaultPath: string): Promise<DossierEntry[]> {\n const entries = await listDossiers(vaultPath);\n try {\n const dir = deriveSessionDir(vaultPath);\n await mkdir(dir, { recursive: true });\n await writeFile(\n join(dir, CACHE_FILE),\n JSON.stringify({ compiledAt: new Date().toISOString(), dossiers: entries }, null, 2),\n \"utf-8\"\n );\n } catch {\n /* cache write is best-effort */\n }\n return entries;\n}\n\n/** Fast read for the hook: use the cache if present, else compile. */\nexport async function loadDossierCache(vaultPath: string): Promise<DossierEntry[]> {\n const cachePath = join(deriveSessionDir(vaultPath), CACHE_FILE);\n if (existsSync(cachePath)) {\n try {\n const data = JSON.parse(await readFile(cachePath, \"utf-8\"));\n if (Array.isArray(data?.dossiers)) return data.dossiers as DossierEntry[];\n } catch {\n /* fall through to recompile */\n }\n }\n return compileDossiers(vaultPath);\n}\n\n/** Find a dossier by exact entity name or any of its aliases (normalized). */\nexport function findDossier(entries: DossierEntry[], entityOrAlias: string): DossierEntry | undefined {\n const n = normalizeEntity(entityOrAlias);\n return entries.find(\n (d) => normalizeEntity(d.entity) === n || d.aliases.some((a) => normalizeEntity(a) === n)\n );\n}\n\n/**\n * Resolve an utterance to a dossier for two-hop retrieval: the query mentions the\n * entity name or one of its aliases. Returns the best (longest-alias) match so a\n * specific phrase wins over a generic one. Deterministic — no LLM, no embedding.\n */\nexport function resolveDossierForQuery(entries: DossierEntry[], query: string): DossierEntry | undefined {\n const nq = normalizeEntity(query);\n let best: { entry: DossierEntry; keyLen: number } | undefined;\n for (const d of entries) {\n const keys = [d.entity, ...d.aliases].map(normalizeEntity).filter(Boolean);\n for (const k of keys) {\n // Word-ish containment: the key appears in the query.\n if (nq.includes(k) && (!best || k.length > best.keyLen)) {\n best = { entry: d, keyLen: k.length };\n }\n }\n }\n return best?.entry;\n}\n\nasync function readDossierRaw(vaultPath: string, rel: string): Promise<{ raw: string; entity: string } | null> {\n const abs = join(vaultPath, rel);\n if (!existsSync(abs)) return null;\n try {\n const raw = await readFile(abs, \"utf-8\");\n const { data } = matter(raw);\n if (data.type !== \"dossier\") return null;\n const entity = typeof data.entity === \"string\" ? data.entity : rel;\n return { raw, entity };\n } catch {\n return null;\n }\n}\n\n/** Resolve an entity/alias/path argument to a concrete dossier note path. */\nexport async function resolveDossierPath(vaultPath: string, entityOrPath: string): Promise<string | null> {\n // Direct path?\n if (entityOrPath.endsWith(\".md\") && existsSync(join(vaultPath, entityOrPath))) return entityOrPath;\n const entries = await listDossiers(vaultPath);\n const match = findDossier(entries, entityOrPath);\n if (match) return match.path;\n // Fall back to the slug path even if not yet created.\n return null;\n}\n\nexport interface InitDossierResult {\n path: string;\n created: boolean;\n entity: string;\n}\n\n/**\n * Scaffold a dossier for an entity. No-op (created:false) if one already exists for\n * that entity/slug. `seeded_from` records provenance (e.g. a babel-fish project-map\n * entry) without failing when that source is absent — same fail-open posture as the\n * v1.2 code-symbol drift seed.\n */\nexport async function initDossier(\n vaultPath: string,\n entity: string,\n opts: { aliases?: string[]; purpose?: string; seeded_from?: string } = {}\n): Promise<InitDossierResult> {\n const existing = await listDossiers(vaultPath);\n const already = findDossier(existing, entity);\n if (already) return { path: already.path, created: false, entity: already.entity };\n\n const rel = join(DOSSIER_DIR, `${slugifyEntity(entity)}.md`);\n const abs = join(vaultPath, rel);\n if (existsSync(abs)) return { path: rel, created: false, entity };\n\n const today = new Date().toISOString().slice(0, 10);\n const fm: Record<string, unknown> = {\n title: entity,\n type: \"dossier\",\n entity,\n status: \"active\",\n last_verified: today,\n confidence: \"medium\",\n ...(opts.aliases && opts.aliases.length > 0 ? { aliases: opts.aliases } : {}),\n ...(opts.seeded_from ? { sources: [opts.seeded_from] } : {}),\n };\n const body = dossierTemplate(entity, { purpose: opts.purpose, seeded_from: opts.seeded_from });\n await mkdir(join(vaultPath, DOSSIER_DIR), { recursive: true });\n await writeFile(abs, matter.stringify(body, fm), \"utf-8\");\n await compileDossiers(vaultPath);\n return { path: rel, created: true, entity };\n}\n\n/**\n * Append a dated incident line to a dossier's Incident log (accretion, never a new\n * file). Returns the resolved path, or null if no dossier exists for the entity.\n */\nexport async function appendIncident(\n vaultPath: string,\n entityOrPath: string,\n incident: string,\n opts: { today?: string } = {}\n): Promise<{ path: string } | null> {\n const rel = await resolveDossierPath(vaultPath, entityOrPath);\n if (!rel) return null;\n const doc = await readDossierRaw(vaultPath, rel);\n if (!doc) return null;\n const today = opts.today ?? new Date().toISOString().slice(0, 10);\n const line = `- ${today} — ${incident.trim()}`;\n const patched = appendToSection(doc.raw, \"Incident log\", line);\n await writeFile(join(vaultPath, rel), patched, \"utf-8\");\n await compileDossiers(vaultPath);\n return { path: rel };\n}\n\n/**\n * Replace the body of a dossier's Current state section (the freshest one-liner).\n * Returns the resolved path, or null if no dossier exists for the entity.\n */\nexport async function setCurrentState(\n vaultPath: string,\n entityOrPath: string,\n state: string\n): Promise<{ path: string } | null> {\n const rel = await resolveDossierPath(vaultPath, entityOrPath);\n if (!rel) return null;\n const doc = await readDossierRaw(vaultPath, rel);\n if (!doc) return null;\n const patched = replaceSectionBody(doc.raw, \"Current state\", state.trim());\n await writeFile(join(vaultPath, rel), patched, \"utf-8\");\n await compileDossiers(vaultPath);\n return { path: rel };\n}\n\n// --- section-level markdown editing (heading-scoped, format-preserving) ---\n\n/** Split into lines, find a `## <heading>` (case-insensitive), return its bounds. */\nfunction locateSection(markdown: string, heading: string): { start: number; level: number; end: number; lines: string[] } | null {\n const lines = markdown.split(\"\\n\");\n const want = heading.trim().toLowerCase();\n let start = -1;\n let level = 0;\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(/^(#{1,6})\\s+(.*?)\\s*$/);\n if (m && m[2].trim().toLowerCase() === want) {\n start = i;\n level = m[1].length;\n break;\n }\n }\n if (start === -1) return null;\n let end = lines.length;\n for (let i = start + 1; i < lines.length; i++) {\n const m = lines[i].match(/^(#{1,6})\\s+/);\n if (m && m[1].length <= level) {\n end = i;\n break;\n }\n }\n return { start, level, end, lines };\n}\n\n/** True if a section body is only the template placeholder (an italic prompt). */\nfunction isPlaceholderBody(body: string): boolean {\n const t = body.trim();\n return t === \"\" || (/^_.*_$/.test(t) && !t.includes(\"\\n\"));\n}\n\n/** Append a line to the end of a section's body (before the next heading/EOF). */\nexport function appendToSection(markdown: string, heading: string, line: string): string {\n const loc = locateSection(markdown, heading);\n if (!loc) {\n // Section absent — append a fresh one at EOF.\n const sep = markdown.endsWith(\"\\n\") ? \"\" : \"\\n\";\n return `${markdown}${sep}\\n## ${heading}\\n\\n${line}\\n`;\n }\n const { lines, start, end } = loc;\n const body = lines.slice(start + 1, end).join(\"\\n\");\n // If the body is just the placeholder prompt, replace it with the first real line.\n const bodyStripped = body.trim();\n const replacePlaceholder = isPlaceholderBody(bodyStripped);\n // Find the last non-empty line index within the section body.\n let insertAt = end;\n while (insertAt - 1 > start && lines[insertAt - 1].trim() === \"\") insertAt--;\n const before = lines.slice(0, replacePlaceholder ? start + 1 : insertAt);\n const after = lines.slice(replacePlaceholder ? end : insertAt);\n const injected = replacePlaceholder ? [\"\", line] : [line];\n return [...before, ...injected, ...after].join(\"\\n\");\n}\n\n/** Replace a section's entire body (keeps the heading), preserving surrounding doc. */\nexport function replaceSectionBody(markdown: string, heading: string, newBody: string): string {\n const loc = locateSection(markdown, heading);\n if (!loc) {\n const sep = markdown.endsWith(\"\\n\") ? \"\" : \"\\n\";\n return `${markdown}${sep}\\n## ${heading}\\n\\n${newBody}\\n`;\n }\n const { lines, start, end } = loc;\n const before = lines.slice(0, start + 1);\n const after = lines.slice(end);\n return [...before, \"\", newBody, \"\", ...after].join(\"\\n\").replace(/\\n{3,}/g, \"\\n\\n\");\n}\n","/**\n * Section-targeted read (v1.4 Phase 7). Extract one heading's section from a\n * markdown document so pulling a dossier's *Knobs* section doesn't cost the whole\n * file. Matches a heading by text (case-insensitive), returns from that heading\n * through to the next heading of the same or higher level.\n */\nexport function extractSection(markdown: string, heading: string): string | null {\n const target = heading.trim().toLowerCase();\n const lines = markdown.split(\"\\n\");\n let startIdx = -1;\n let startLevel = 0;\n\n const headingAt = (line: string): { level: number; text: string } | null => {\n const m = /^(#{1,6})\\s+(.*?)\\s*$/.exec(line);\n return m ? { level: m[1].length, text: m[2].trim().toLowerCase() } : null;\n };\n\n for (let i = 0; i < lines.length; i++) {\n const h = headingAt(lines[i]);\n if (h && h.text === target) {\n startIdx = i;\n startLevel = h.level;\n break;\n }\n }\n if (startIdx === -1) return null;\n\n let endIdx = lines.length;\n for (let i = startIdx + 1; i < lines.length; i++) {\n const h = headingAt(lines[i]);\n if (h && h.level <= startLevel) {\n endIdx = i;\n break;\n }\n }\n return lines.slice(startIdx, endIdx).join(\"\\n\").trimEnd();\n}\n"],"mappings":"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","names":[]}