@theglitchking/semantic-memory 1.5.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +43 -0
  4. package/README.md +4 -0
  5. package/dist/{chunk-25EIQWXL.js → chunk-44BQDRXD.js} +5 -5
  6. package/dist/chunk-44BQDRXD.js.map +1 -0
  7. package/dist/{chunk-2EELGTLX.js → chunk-D5QLNNA4.js} +2 -2
  8. package/dist/{chunk-A3KYHKB5.js → chunk-FTRXH2WC.js} +8 -4
  9. package/dist/chunk-FTRXH2WC.js.map +1 -0
  10. package/dist/{chunk-OWZWMB4P.js → chunk-FURCIJBO.js} +1 -1
  11. package/dist/chunk-FURCIJBO.js.map +1 -0
  12. package/dist/{chunk-V45NJIHI.js → chunk-IGHCVAAL.js} +19 -4
  13. package/dist/chunk-IGHCVAAL.js.map +1 -0
  14. package/dist/{chunk-S4KW7VH4.js → chunk-LGQPP5FQ.js} +8 -3
  15. package/dist/chunk-LGQPP5FQ.js.map +1 -0
  16. package/dist/{chunk-NTYYS53O.js → chunk-NBY4TRP7.js} +7 -3
  17. package/dist/chunk-NBY4TRP7.js.map +1 -0
  18. package/dist/chunk-OEF5U5GV.js +175 -0
  19. package/dist/chunk-OEF5U5GV.js.map +1 -0
  20. package/dist/{chunk-FM26NFTR.js → chunk-OFXQXVG5.js} +26 -12
  21. package/dist/chunk-OFXQXVG5.js.map +1 -0
  22. package/dist/{chunk-CSXZ7QUU.js → chunk-ROKSUJFM.js} +27 -17
  23. package/dist/chunk-ROKSUJFM.js.map +1 -0
  24. package/dist/{chunk-JPQHNG3R.js → chunk-SX427M2R.js} +10 -4
  25. package/dist/chunk-SX427M2R.js.map +1 -0
  26. package/dist/{chunk-7M3JZOYH.js → chunk-WZEKIUYG.js} +8 -4
  27. package/dist/chunk-WZEKIUYG.js.map +1 -0
  28. package/dist/{chunk-WSU4QIPB.js → chunk-XAXK7NM4.js} +14 -8
  29. package/dist/chunk-XAXK7NM4.js.map +1 -0
  30. package/dist/cli/index.js +15 -15
  31. package/dist/core/index.d.ts +24 -4
  32. package/dist/core/index.js +20 -12
  33. package/dist/core/redact.d.ts +36 -0
  34. package/dist/core/redact.js +18 -0
  35. package/dist/{dossier-2UG724LB.js → dossier-RWTIMGZN.js} +4 -3
  36. package/dist/healthcheck-A32IWPNV.js +20 -0
  37. package/dist/indexer-RAKJ7JR3.js +8 -0
  38. package/dist/{lexicon-5PXQEMUF.js → lexicon-IVNJTMWE.js} +5 -4
  39. package/dist/lint-HURYGO3V.js +17 -0
  40. package/dist/log-YIXF6CYM.js +11 -0
  41. package/dist/mcp/server.js +32 -27
  42. package/dist/mcp/server.js.map +1 -1
  43. package/dist/{profile-LXUEF7TX.js → profile-UE4YVTDC.js} +5 -4
  44. package/dist/{schema-ATFCLC6Y.js → schema-LY6533NJ.js} +2 -2
  45. package/dist/{search-AHKEFI2B.js → search-ZS6OW74K.js} +3 -3
  46. package/dist/{telemetry-GB2TEHVE.js → telemetry-B4KP3UVE.js} +4 -3
  47. package/dist/telemetry-B4KP3UVE.js.map +1 -0
  48. package/hooks/vault-context.js +112 -58
  49. package/package.json +10 -10
  50. package/dist/chunk-25EIQWXL.js.map +0 -1
  51. package/dist/chunk-7M3JZOYH.js.map +0 -1
  52. package/dist/chunk-A3KYHKB5.js.map +0 -1
  53. package/dist/chunk-CSXZ7QUU.js.map +0 -1
  54. package/dist/chunk-FM26NFTR.js.map +0 -1
  55. package/dist/chunk-JPQHNG3R.js.map +0 -1
  56. package/dist/chunk-NTYYS53O.js.map +0 -1
  57. package/dist/chunk-OWZWMB4P.js.map +0 -1
  58. package/dist/chunk-S4KW7VH4.js.map +0 -1
  59. package/dist/chunk-V45NJIHI.js.map +0 -1
  60. package/dist/chunk-WSU4QIPB.js.map +0 -1
  61. package/dist/healthcheck-IMB47LZE.js +0 -19
  62. package/dist/indexer-YW4O3VVM.js +0 -8
  63. package/dist/lint-XUQ7JDYX.js +0 -16
  64. package/dist/log-LQXIJXIA.js +0 -10
  65. /package/dist/{chunk-2EELGTLX.js.map → chunk-D5QLNNA4.js.map} +0 -0
  66. /package/dist/{dossier-2UG724LB.js.map → core/redact.js.map} +0 -0
  67. /package/dist/{healthcheck-IMB47LZE.js.map → dossier-RWTIMGZN.js.map} +0 -0
  68. /package/dist/{indexer-YW4O3VVM.js.map → healthcheck-A32IWPNV.js.map} +0 -0
  69. /package/dist/{lexicon-5PXQEMUF.js.map → indexer-RAKJ7JR3.js.map} +0 -0
  70. /package/dist/{lint-XUQ7JDYX.js.map → lexicon-IVNJTMWE.js.map} +0 -0
  71. /package/dist/{log-LQXIJXIA.js.map → lint-HURYGO3V.js.map} +0 -0
  72. /package/dist/{profile-LXUEF7TX.js.map → log-YIXF6CYM.js.map} +0 -0
  73. /package/dist/{schema-ATFCLC6Y.js.map → profile-UE4YVTDC.js.map} +0 -0
  74. /package/dist/{telemetry-GB2TEHVE.js.map → schema-LY6533NJ.js.map} +0 -0
  75. /package/dist/{search-AHKEFI2B.js.map → search-ZS6OW74K.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/lexicon.ts"],"sourcesContent":["import { readFile, writeFile, mkdir, unlink } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\nimport { deriveSessionDir } from \"./session.js\";\nimport { listDossiers } from \"./dossier.js\";\n\n/**\n * Lexicon corpus (v1.4 Phase 3) — the learned human→artifact vocabulary.\n *\n * The heart of the human→LLM bridge: maps the human's own phrasing (\"the flaky\n * thing\", \"the deploy dance\") to concrete artifacts (paths / code symbols).\n * Canonical storage is markdown notes of type `alias` under <vault>/lexicon/;\n * a derived JSON cache under .claude/.semantic-memory/ is what the hot-path hook\n * reads for deterministic query expansion.\n */\n\nexport type Confidence = \"low\" | \"medium\" | \"high\";\n\nexport interface AliasEntry {\n /** The concrete target: a note path or a code symbol/path. */\n canonical: string;\n /** The human's actual words that refer to it (verbatim). */\n phrases: string[];\n confidence: Confidence;\n evidence_count: number;\n last_used?: string;\n source: \"learned\" | \"authored\";\n /** The lexicon note path this entry came from. */\n path: string;\n}\n\nconst CACHE_FILE = \"lexicon-cache.json\";\nconst LEXICON_DIR = \"lexicon\";\n\nexport function normalizePhrase(s: string): string {\n return s.toLowerCase().trim().replace(/\\s+/g, \" \");\n}\n\nexport function slugify(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 60) || \"alias\";\n}\n\nfunction confidenceFor(evidenceCount: number): Confidence {\n return evidenceCount >= 3 ? \"high\" : evidenceCount >= 2 ? \"medium\" : \"low\";\n}\n\n/** Read every lexicon note into entries and write the derived cache. */\nexport async function compileLexicon(vaultPath: string): Promise<AliasEntry[]> {\n const files = await glob(`${LEXICON_DIR}/**/*.md`, { cwd: vaultPath });\n const entries: AliasEntry[] = [];\n for (const rel of files) {\n try {\n const { data } = matter(await readFile(join(vaultPath, rel), \"utf-8\"));\n if (data.type !== \"alias\" || typeof data.canonical !== \"string\") continue;\n const phrases = Array.isArray(data.phrases) ? data.phrases.map(String).map((p) => p.trim()).filter(Boolean) : [];\n entries.push({\n canonical: data.canonical,\n phrases,\n confidence: ([\"low\", \"medium\", \"high\"] as const).includes(data.confidence) ? data.confidence : confidenceFor(1),\n evidence_count: typeof data.evidence_count === \"number\" ? data.evidence_count : phrases.length ? 1 : 0,\n last_used: typeof data.last_used === \"string\" ? data.last_used : undefined,\n source: data.source === \"authored\" ? \"authored\" : \"learned\",\n path: rel,\n });\n } catch {\n /* skip unreadable note */\n }\n }\n // Fold in dossier aliases (v1.5 Phase 8): a dossier's `aliases:` are the human's\n // phrases for that entity, so they become query-expansion bindings pointing at the\n // dossier note. Authored, not learned — the human named the entity deliberately.\n // A lexicon note for the same canonical wins (it carries real evidence_count).\n try {\n const dossiers = await listDossiers(vaultPath);\n for (const d of dossiers) {\n if (d.aliases.length === 0) continue;\n if (entries.some((e) => e.canonical === d.path)) continue;\n entries.push({\n canonical: d.path,\n phrases: d.aliases,\n confidence: \"high\",\n evidence_count: d.aliases.length,\n source: \"authored\",\n path: d.path,\n });\n }\n } catch {\n /* dossiers optional — lexicon compiles fine without them */\n }\n try {\n const dir = deriveSessionDir(vaultPath);\n await mkdir(dir, { recursive: true });\n await writeFile(join(dir, CACHE_FILE), JSON.stringify({ compiledAt: new Date().toISOString(), aliases: entries }, null, 2), \"utf-8\");\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 loadLexiconCache(vaultPath: string): Promise<AliasEntry[]> {\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?.aliases)) return data.aliases as AliasEntry[];\n } catch {\n /* fall through to recompile */\n }\n }\n return compileLexicon(vaultPath);\n}\n\nexport function lookupPhrase(aliases: AliasEntry[], phrase: string): AliasEntry[] {\n const n = normalizePhrase(phrase);\n return aliases.filter((a) => a.phrases.some((p) => normalizePhrase(p) === n));\n}\n\n/**\n * Expand a query by appending the canonical targets of any alias phrases that\n * appear in it. Deterministic, no LLM — this is the Tier-1 hook-time expansion.\n */\nexport function expandQuery(aliases: AliasEntry[], query: string): { expanded: string; matched: AliasEntry[] } {\n const nq = normalizePhrase(query);\n const matched = aliases.filter((a) => a.phrases.some((p) => p && nq.includes(normalizePhrase(p))));\n if (matched.length === 0) return { expanded: query, matched: [] };\n const additions = [...new Set(matched.map((a) => a.canonical))].join(\" \");\n return { expanded: `${query} ${additions}`, matched };\n}\n\nasync function writeAliasNote(vaultPath: string, rel: string, entry: Omit<AliasEntry, \"path\">): Promise<void> {\n const fm = {\n type: \"alias\",\n canonical: entry.canonical,\n phrases: entry.phrases,\n confidence: entry.confidence,\n evidence_count: entry.evidence_count,\n last_used: entry.last_used,\n source: entry.source,\n };\n const body = `# Alias: ${entry.canonical}\\n\\nHuman phrases that refer to \\`${entry.canonical}\\`.\\n`;\n const abs = join(vaultPath, rel);\n await mkdir(dirname(abs), { recursive: true });\n await writeFile(abs, matter.stringify(body, fm), \"utf-8\");\n}\n\n/**\n * Upsert an alias. New canonical → new note (evidence 1). Existing canonical →\n * merge phrases, bump evidence_count, recompute confidence. Recompiles the cache.\n */\nexport async function addAlias(\n vaultPath: string,\n input: { canonical: string; phrases: string[]; source?: \"learned\" | \"authored\"; today?: string }\n): Promise<{ path: string; created: boolean; evidence_count: number; confidence: Confidence }> {\n const existing = await compileLexicon(vaultPath);\n const match = existing.find((a) => a.canonical === input.canonical);\n const today = input.today ?? new Date().toISOString().slice(0, 10);\n const cleanPhrases = input.phrases.map((p) => p.trim()).filter(Boolean);\n\n if (match) {\n const phrases = [...new Set([...match.phrases, ...cleanPhrases])];\n const evidence_count = match.evidence_count + 1;\n const confidence = confidenceFor(evidence_count);\n await writeAliasNote(vaultPath, match.path, { ...match, phrases, evidence_count, confidence, last_used: today });\n await compileLexicon(vaultPath);\n return { path: match.path, created: false, evidence_count, confidence };\n }\n\n const rel = join(LEXICON_DIR, `${slugify(input.canonical)}.md`);\n const evidence_count = 1;\n const confidence = confidenceFor(evidence_count);\n await writeAliasNote(vaultPath, rel, {\n canonical: input.canonical,\n phrases: cleanPhrases,\n confidence,\n evidence_count,\n last_used: today,\n source: input.source ?? \"learned\",\n });\n await compileLexicon(vaultPath);\n return { path: rel, created: true, evidence_count, confidence };\n}\n\nexport interface AliasConflict {\n phrase: string;\n canonicals: string[];\n}\n\n/**\n * Find conflicting bindings: the same (normalized) phrase mapping to more than one\n * canonical target. A wrong alias silently misroutes every future query with that\n * phrase, so conflicts are surfaced (never auto-resolved) for a human to fix.\n */\nexport function findAliasConflicts(aliases: AliasEntry[]): AliasConflict[] {\n const byPhrase = new Map<string, Set<string>>();\n for (const a of aliases) {\n for (const p of a.phrases) {\n const n = normalizePhrase(p);\n if (!n) continue;\n if (!byPhrase.has(n)) byPhrase.set(n, new Set());\n byPhrase.get(n)!.add(a.canonical);\n }\n }\n const conflicts: AliasConflict[] = [];\n for (const [phrase, canonicals] of byPhrase) {\n if (canonicals.size > 1) conflicts.push({ phrase, canonicals: [...canonicals].sort() });\n }\n return conflicts.sort((a, b) => a.phrase.localeCompare(b.phrase));\n}\n\n/** Remove an alias note by canonical. Returns whether one was removed. */\nexport async function removeAlias(vaultPath: string, canonical: string): Promise<boolean> {\n const existing = await compileLexicon(vaultPath);\n const match = existing.find((a) => a.canonical === canonical);\n if (!match) return false;\n try {\n await unlink(join(vaultPath, match.path));\n } catch {\n return false;\n }\n await compileLexicon(vaultPath);\n return true;\n}\n"],"mappings":"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","names":["evidence_count","confidence"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/crud.ts","../src/core/frontmatter.ts","../src/core/watcher.ts","../src/core/ingest.ts","../src/core/index-regen.ts","../src/core/patch.ts","../src/core/synthesize.ts","../src/core/agents-contract.ts"],"sourcesContent":["import { readFile, writeFile, unlink, rename, mkdir } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\nimport type { UpdateNoteOptions } from \"./types.js\";\n\nexport class NoteCrud {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async create(\n relativePath: string,\n content: string,\n frontmatter?: Record<string, unknown>\n ): Promise<string> {\n const absPath = join(this.notesPath, relativePath);\n if (existsSync(absPath)) {\n throw new Error(`Note already exists: ${relativePath}`);\n }\n\n await mkdir(dirname(absPath), { recursive: true });\n\n // Merge auto-stamps with caller-supplied frontmatter (caller wins on conflicts)\n const stamps = computeStamps(content);\n const merged = { ...stamps, ...(frontmatter ?? {}) };\n const fileContent = matter.stringify(content, merged);\n\n await writeFile(absPath, fileContent, \"utf-8\");\n return relativePath;\n }\n\n async read(relativePath: string): Promise<string> {\n const absPath = join(this.notesPath, relativePath);\n return readFile(absPath, \"utf-8\");\n }\n\n async readMultiple(paths: string[]): Promise<Map<string, string>> {\n const results = new Map<string, string>();\n await Promise.all(\n paths.map(async (p) => {\n try {\n const content = await this.read(p);\n results.set(p, content);\n } catch {\n results.set(p, `[Error: could not read ${p}]`);\n }\n })\n );\n return results;\n }\n\n async update(\n relativePath: string,\n content: string,\n options: UpdateNoteOptions\n ): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n if (!existsSync(absPath)) {\n throw new Error(`Note does not exist: ${relativePath}`);\n }\n\n const existing = await readFile(absPath, \"utf-8\");\n\n let updated: string;\n\n switch (options.mode) {\n case \"overwrite\":\n updated = content;\n break;\n\n case \"append\":\n updated = existing + \"\\n\" + content;\n break;\n\n case \"prepend\": {\n const { data, content: body } = matter(existing);\n const newBody = content + \"\\n\" + body;\n updated = Object.keys(data).length > 0 ? matter.stringify(newBody, data) : newBody;\n break;\n }\n\n case \"patch-by-heading\": {\n if (!options.heading) throw new Error(\"patch-by-heading requires a heading\");\n updated = this.patchByHeading(existing, options.heading, content);\n break;\n }\n }\n\n // Re-parse the final content and inject updated stamps\n // (preserves all existing frontmatter, only overwrites the three auto fields)\n const { data: existingFm, content: body } = matter(updated);\n const stamps = computeStamps(body);\n const newFm = { ...existingFm, ...stamps };\n await writeFile(absPath, matter.stringify(body, newFm), \"utf-8\");\n }\n\n async delete(relativePath: string): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n if (!existsSync(absPath)) {\n throw new Error(`Note does not exist: ${relativePath}`);\n }\n await unlink(absPath);\n }\n\n async move(fromPath: string, toPath: string): Promise<void> {\n const absFrom = join(this.notesPath, fromPath);\n const absTo = join(this.notesPath, toPath);\n\n if (!existsSync(absFrom)) {\n throw new Error(`Note does not exist: ${fromPath}`);\n }\n if (existsSync(absTo)) {\n throw new Error(`Destination already exists: ${toPath}`);\n }\n\n await mkdir(dirname(absTo), { recursive: true });\n await rename(absFrom, absTo);\n\n // Update wikilinks in other files that reference the old path\n await this.updateWikilinksAfterMove(fromPath, toPath);\n }\n\n private async updateWikilinksAfterMove(\n oldPath: string,\n newPath: string\n ): Promise<void> {\n const oldName = oldPath.replace(/\\.md$/, \"\").split(\"/\").pop()!;\n const newName = newPath.replace(/\\.md$/, \"\").split(\"/\").pop()!;\n\n if (oldName === newName) return;\n\n const files = await glob(\"**/*.md\", { cwd: this.notesPath });\n\n for (const file of files) {\n const absPath = join(this.notesPath, file);\n const content = await readFile(absPath, \"utf-8\");\n\n const pattern = new RegExp(`\\\\[\\\\[${escapeRegex(oldName)}(\\\\|[^\\\\]]*)?\\\\]\\\\]`, \"g\");\n if (!pattern.test(content)) continue;\n\n const updated = content.replace(pattern, `[[${newName}$1]]`);\n await writeFile(absPath, updated, \"utf-8\");\n }\n }\n\n private patchByHeading(\n content: string,\n heading: string,\n newContent: string\n ): string {\n const lines = content.split(\"\\n\");\n const headingPattern = new RegExp(`^#{1,6}\\\\s+${escapeRegex(heading)}\\\\s*$`, \"i\");\n\n let headingIndex = -1;\n let headingLevel = 0;\n\n for (let i = 0; i < lines.length; i++) {\n if (headingPattern.test(lines[i])) {\n headingIndex = i;\n const match = lines[i].match(/^(#{1,6})\\s+/);\n headingLevel = match ? match[1].length : 1;\n break;\n }\n }\n\n if (headingIndex === -1) {\n throw new Error(`Heading not found: ${heading}`);\n }\n\n // Find the end of this section (next heading of same or higher level)\n let endIndex = lines.length;\n for (let i = headingIndex + 1; i < lines.length; i++) {\n const match = lines[i].match(/^(#{1,6})\\s+/);\n if (match && match[1].length <= headingLevel) {\n endIndex = i;\n break;\n }\n }\n\n const before = lines.slice(0, headingIndex + 1);\n const after = lines.slice(endIndex);\n\n return [...before, \"\", newContent, \"\", ...after].join(\"\\n\");\n }\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Compute the three auto-stamp fields from note body text.\n * These are always factual/computable — no editorial judgment required.\n * - last_updated: today's date (YYYY-MM-DD)\n * - word_count: whitespace-separated token count\n * - estimated_read_time: assumes ~200 wpm average reading speed\n */\nfunction computeStamps(body: string): Record<string, unknown> {\n const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD\n const wordCount = body.trim() ? body.trim().split(/\\s+/).length : 0;\n const minutes = Math.max(1, Math.ceil(wordCount / 200));\n return {\n last_updated: today,\n word_count: wordCount,\n estimated_read_time: `${minutes} min read`,\n };\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\n\nexport class FrontmatterManager {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async get(relativePath: string): Promise<Record<string, unknown>> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data } = matter(raw);\n return data;\n }\n\n async update(\n relativePath: string,\n fields: Record<string, unknown>\n ): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n for (const [key, value] of Object.entries(fields)) {\n if (value === null || value === undefined) {\n delete data[key];\n } else {\n data[key] = value;\n }\n }\n\n const updated = matter.stringify(content, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n}\n\nexport class TagManager {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async list(relativePath: string): Promise<string[]> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n const fmTags = Array.isArray(data.tags) ? (data.tags as string[]) : [];\n const inlineTags = [...content.matchAll(/(?:^|\\s)#([a-zA-Z][\\w-/]*)/g)].map(\n (m) => m[1]\n );\n\n return [...new Set([...fmTags, ...inlineTags])];\n }\n\n async add(relativePath: string, tags: string[]): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n const existing = Array.isArray(data.tags) ? (data.tags as string[]) : [];\n const merged = [...new Set([...existing, ...tags])];\n data.tags = merged;\n\n const updated = matter.stringify(content, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n\n async remove(relativePath: string, tags: string[]): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n // Remove from frontmatter\n if (Array.isArray(data.tags)) {\n data.tags = (data.tags as string[]).filter((t) => !tags.includes(t));\n }\n\n // Remove inline tags\n let updatedContent = content;\n for (const tag of tags) {\n const pattern = new RegExp(`(^|\\\\s)#${escapeRegex(tag)}(?=\\\\s|$)`, \"g\");\n updatedContent = updatedContent.replace(pattern, \"$1\");\n }\n\n const updated = matter.stringify(updatedContent, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n\n async renameVaultWide(oldTag: string, newTag: string): Promise<number> {\n const files = await glob(\"**/*.md\", { cwd: this.notesPath });\n let count = 0;\n\n for (const file of files) {\n const absPath = join(this.notesPath, file);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n let changed = false;\n\n // Rename in frontmatter\n if (Array.isArray(data.tags)) {\n const idx = (data.tags as string[]).indexOf(oldTag);\n if (idx >= 0) {\n (data.tags as string[])[idx] = newTag;\n changed = true;\n }\n }\n\n // Rename inline tags\n const pattern = new RegExp(`(^|\\\\s)#${escapeRegex(oldTag)}(?=\\\\s|$)`, \"g\");\n const updatedContent = content.replace(pattern, `$1#${newTag}`);\n if (updatedContent !== content) changed = true;\n\n if (changed) {\n const updated = matter.stringify(updatedContent, data);\n await writeFile(absPath, updated, \"utf-8\");\n count++;\n }\n }\n\n return count;\n }\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import { watch, type FSWatcher } from \"chokidar\";\nimport { EventEmitter } from \"node:events\";\n\nexport interface WatcherEvents {\n changed: (paths: string[]) => void;\n error: (error: Error) => void;\n}\n\nexport class Watcher extends EventEmitter {\n private notesPath: string;\n private fsWatcher: FSWatcher | null = null;\n private debounceMs: number;\n private pendingChanges = new Set<string>();\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private readyPromise: Promise<void> | null = null;\n private usePolling: boolean;\n\n constructor(notesPath: string, debounceMs: number = 500, usePolling: boolean = false) {\n super();\n this.notesPath = notesPath;\n this.debounceMs = debounceMs;\n this.usePolling = usePolling;\n }\n\n start(): void {\n if (this.fsWatcher) return;\n\n this.fsWatcher = watch(\"**/*.md\", {\n cwd: this.notesPath,\n ignoreInitial: true,\n followSymlinks: true,\n ignored: [\n \"**/node_modules/**\",\n \"**/.semantic-sidekick-index/**\",\n \"**/.git/**\",\n ],\n ...(this.usePolling ? { usePolling: true, interval: 100 } : {}),\n });\n\n this.readyPromise = new Promise<void>((resolve) => {\n this.fsWatcher!.on(\"ready\", resolve);\n });\n\n this.fsWatcher.on(\"add\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"change\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"unlink\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"error\", (err) => this.emit(\"error\", err));\n }\n\n async ready(): Promise<void> {\n if (this.readyPromise) await this.readyPromise;\n }\n\n stop(): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer);\n this.fsWatcher?.close();\n this.fsWatcher = null;\n this.pendingChanges.clear();\n }\n\n private enqueue(path: string): void {\n this.pendingChanges.add(path);\n\n if (this.debounceTimer) clearTimeout(this.debounceTimer);\n this.debounceTimer = setTimeout(() => {\n const paths = [...this.pendingChanges];\n this.pendingChanges.clear();\n this.emit(\"changed\", paths);\n }, this.debounceMs);\n }\n}\n","import { basename } from \"node:path\";\nimport type { ChangeSet, PatchCreate } from \"./patch.js\";\n\nexport interface IngestSource {\n /** Identifier of the source — URL, filesystem path, DOI, ISBN, etc. */\n source_uri: string;\n /** Human-readable title for the source-note's frontmatter */\n source_title: string;\n /** One of the schema types; typically \"source\" */\n source_type?: string;\n /** Path under the vault to write the source-note. Defaults to `sources/<slug>.md`. */\n source_path?: string;\n /** Free-form body for the source-note (summary, abstract, your notes on it) */\n source_summary?: string;\n /** Tags for the source-note */\n source_tags?: string[];\n}\n\nexport interface IngestUnit {\n /** Path under the vault where this extracted note should live */\n path: string;\n /** Note title — if absent, derived from basename */\n title?: string;\n /** Note body — markdown */\n content: string;\n /** Schema type. Default: \"note\". */\n type?: string;\n /** Additional wikilink-style derived_from targets beyond the source-note */\n extra_derived_from?: string[];\n /** Additional free-form frontmatter fields */\n extra_frontmatter?: Record<string, unknown>;\n /** Per-unit confidence override */\n confidence?: string;\n}\n\nexport interface IngestInput {\n source: IngestSource;\n units: IngestUnit[];\n /** If true, the existing source-note is updated if present; else an error is raised */\n allow_existing_source?: boolean;\n}\n\nexport interface IngestPreview {\n changeset: ChangeSet;\n sourcePath: string;\n unitPaths: string[];\n}\n\n/**\n * Build a ChangeSet for ingesting a source into the vault.\n *\n * Shape: one create for the source-note (type=source) + one create per unit,\n * each stamped with `derived_from: [<source-path>]` so lint.missing_provenance\n * is satisfied automatically and the graph reflects the provenance edge.\n *\n * Intentionally does NOT touch the filesystem — callers route the result\n * through applyPatch for atomicity, validation, and rollback.\n */\nexport function buildIngestChangeSet(input: IngestInput): IngestPreview {\n const today = new Date().toISOString().slice(0, 10);\n const sourcePath = input.source.source_path ?? defaultSourcePath(input.source);\n\n const sourceNote: PatchCreate = {\n path: sourcePath,\n content: buildSourceBody(input.source),\n frontmatter: {\n title: input.source.source_title,\n type: input.source.source_type ?? \"source\",\n status: \"active\",\n source: input.source.source_uri,\n last_verified: today,\n confidence: \"high\",\n ...(input.source.source_tags && input.source.source_tags.length > 0 && { tags: input.source.source_tags }),\n },\n };\n\n const unitCreates: PatchCreate[] = input.units.map((u) => {\n const title = u.title ?? titleFromPath(u.path);\n const derivedFrom = [sourcePath, ...(u.extra_derived_from ?? [])];\n return {\n path: u.path.endsWith(\".md\") ? u.path : u.path + \".md\",\n content: u.content.startsWith(\"#\") ? u.content : `# ${title}\\n\\n${u.content.trim()}\\n`,\n frontmatter: {\n title,\n type: u.type ?? \"note\",\n status: \"active\",\n derived_from: derivedFrom,\n last_verified: today,\n confidence: u.confidence ?? \"medium\",\n ...(u.extra_frontmatter ?? {}),\n },\n };\n });\n\n return {\n changeset: { creates: [sourceNote, ...unitCreates] },\n sourcePath,\n unitPaths: unitCreates.map((c) => c.path),\n };\n}\n\nfunction buildSourceBody(source: IngestSource): string {\n const lines = [`# ${source.source_title}`, \"\", `**Source:** ${source.source_uri}`, \"\"];\n if (source.source_summary) {\n lines.push(source.source_summary.trim(), \"\");\n } else {\n lines.push(\"_Summary not provided — fill in when referencing this source._\", \"\");\n }\n return lines.join(\"\\n\");\n}\n\nfunction defaultSourcePath(source: IngestSource): string {\n const slug = slugify(source.source_title);\n return `sources/${slug}.md`;\n}\n\nfunction titleFromPath(p: string): string {\n return basename(p).replace(/\\.md$/, \"\").replace(/[-_]/g, \" \");\n}\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 80);\n}\n","import { readdir, readFile, writeFile, stat } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname, basename, sep } from \"node:path\";\nimport matter from \"gray-matter\";\n\nexport const INDEX_FILE = \"INDEX.md\";\nexport const INDEX_OVERFLOW_THRESHOLD = 100; // entries before we warn about splitting\n\nexport interface IndexEntry {\n path: string; // relative to the directory\n title: string;\n summary?: string;\n}\n\nexport interface RegenResult {\n directory: string;\n indexPath: string;\n entryCount: number;\n overflowed: boolean;\n wrote: boolean;\n}\n\n/**\n * Regenerate INDEX.md for a single vault directory.\n * Lists every *.md file in the directory (non-recursive, excluding the index itself)\n * with its title (frontmatter or first heading) and optional summary (from frontmatter\n * `purpose` or first non-heading paragraph).\n */\nexport async function regenDirectoryIndex(vaultPath: string, dirRel: string): Promise<RegenResult> {\n const absDir = join(vaultPath, dirRel);\n const indexPath = join(absDir, INDEX_FILE);\n const relIndex = join(dirRel, INDEX_FILE);\n\n if (!existsSync(absDir)) {\n return { directory: dirRel, indexPath: relIndex, entryCount: 0, overflowed: false, wrote: false };\n }\n\n const files = (await readdir(absDir)).filter(\n (f) => f.endsWith(\".md\") && f !== INDEX_FILE && f !== \"README.md\"\n );\n\n const entries: IndexEntry[] = [];\n for (const f of files) {\n const full = join(absDir, f);\n try {\n const st = await stat(full);\n if (!st.isFile()) continue;\n const raw = await readFile(full, \"utf-8\");\n const { data, content } = matter(raw);\n const title = typeof data.title === \"string\" ? data.title : firstHeading(content) ?? basenameNoExt(f);\n const summary = typeof data.purpose === \"string\" ? data.purpose : firstParagraph(content);\n entries.push({ path: f, title, summary });\n } catch {\n /* skip unreadable */\n }\n }\n\n entries.sort((a, b) => a.title.localeCompare(b.title));\n const body = renderIndex(dirRel, entries);\n const overflowed = entries.length > INDEX_OVERFLOW_THRESHOLD;\n\n let wrote = false;\n if (existsSync(indexPath)) {\n const current = await readFile(indexPath, \"utf-8\");\n if (current !== body) {\n await writeFile(indexPath, body, \"utf-8\");\n wrote = true;\n }\n } else {\n await writeFile(indexPath, body, \"utf-8\");\n wrote = true;\n }\n\n return { directory: dirRel, indexPath: relIndex, entryCount: entries.length, overflowed, wrote };\n}\n\n/**\n * Regenerate indexes for every directory affected by a set of paths.\n * Walks up the parent chain for each input so a deep change at\n * `a/b/c/note.md` regenerates `a/b/c/INDEX.md`. Does NOT recurse further —\n * `a/b/INDEX.md` stays as-is unless a path under `a/b/` (not `a/b/c/`)\n * also changed.\n */\nexport async function regenIndexesForPaths(\n vaultPath: string,\n paths: string[]\n): Promise<RegenResult[]> {\n const dirs = new Set<string>();\n for (const p of paths) {\n const d = dirname(p);\n if (d === \".\" || d === \"/\") continue;\n dirs.add(d);\n }\n const results: RegenResult[] = [];\n for (const d of dirs) {\n results.push(await regenDirectoryIndex(vaultPath, d));\n }\n return results;\n}\n\nfunction renderIndex(dirRel: string, entries: IndexEntry[]): string {\n const header = `# ${toTitle(dirRel)} Index\\n\\n> Auto-generated by semantic-sidekick. Changes are overwritten on apply_patch. Add prose to per-note files, not here.\\n\\n`;\n if (entries.length === 0) return header + \"_No notes in this directory yet._\\n\";\n const overflowNote =\n entries.length > INDEX_OVERFLOW_THRESHOLD\n ? `> ⚠️ ${entries.length} entries — consider splitting this directory into subfolders (threshold: ${INDEX_OVERFLOW_THRESHOLD}).\\n\\n`\n : \"\";\n const lines = entries.map((e) => {\n const linkText = e.title;\n const target = e.path.replace(/\\.md$/, \"\");\n const summary = e.summary ? ` — ${truncate(e.summary, 140)}` : \"\";\n return `- [${linkText}](./${e.path}) — \\`[[${target}]]\\`${summary}`;\n });\n return header + overflowNote + lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction toTitle(dirRel: string): string {\n return dirRel\n .split(sep)\n .pop()!\n .replace(/[-_]/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction basenameNoExt(p: string): string {\n return basename(p).replace(/\\.md$/, \"\");\n}\n\nfunction firstHeading(content: string): string | null {\n const m = content.match(/^\\s*#{1,6}\\s+(.+)$/m);\n return m ? m[1].trim() : null;\n}\n\nfunction firstParagraph(content: string): string | undefined {\n // Skip leading headings, then take the first line of non-heading text\n const lines = content.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"#\")) continue;\n if (trimmed.startsWith(\">\")) continue;\n if (trimmed.startsWith(\"```\")) continue;\n return trimmed.replace(/\\s+/g, \" \");\n }\n return undefined;\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length <= n ? s : s.slice(0, n - 1).trimEnd() + \"…\";\n}\n","import { readFile, writeFile, unlink, rename, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport type { UpdateNoteOptions } from \"./types.js\";\nimport { NoteCrud } from \"./crud.js\";\nimport { loadSchema, validateNote, type LintFinding, type VaultSchema } from \"./schema.js\";\nimport { regenIndexesForPaths, type RegenResult } from \"./index-regen.js\";\n\nexport interface PatchCreate {\n path: string;\n content: string;\n frontmatter?: Record<string, unknown>;\n}\n\nexport interface PatchUpdate {\n path: string;\n content: string;\n mode: UpdateNoteOptions[\"mode\"];\n heading?: string;\n}\n\nexport interface PatchDelete {\n path: string;\n}\n\nexport interface PatchMove {\n from: string;\n to: string;\n}\n\nexport interface ChangeSet {\n creates?: PatchCreate[];\n updates?: PatchUpdate[];\n deletes?: PatchDelete[];\n moves?: PatchMove[];\n}\n\nexport interface ApplyPatchOptions {\n dryRun?: boolean;\n validate?: boolean;\n allowLintWarnings?: boolean;\n /** Regenerate INDEX.md in directories touched by this patch. Default: true. */\n regenIndexes?: boolean;\n}\n\nexport interface AppliedOp {\n op: \"create\" | \"update\" | \"delete\" | \"move\";\n path: string;\n}\n\nexport interface ApplyPatchResult {\n ok: boolean;\n applied: AppliedOp[];\n rolledBack: AppliedOp[];\n lint: LintFinding[];\n errors: string[];\n /** Index files regenerated as a side-effect. Empty if regenIndexes=false or dry-run. */\n indexes_regenerated?: RegenResult[];\n}\n\ninterface Snapshot {\n op: \"create\" | \"update\" | \"delete\" | \"move\";\n path: string;\n absPath: string;\n prevContent?: string; // for update/delete/move(from)\n prevAbsPath?: string; // for move (prev location)\n}\n\n/**\n * Atomic multi-note patch with rollback. Applies creates → updates → moves → deletes\n * in that order. If any op fails, all successful ops before it are reverted in reverse.\n *\n * In dry-run mode, nothing is written; the result describes what would have happened,\n * including lint findings on the proposed state.\n */\nexport async function applyPatch(\n notesPath: string,\n changeset: ChangeSet,\n options: ApplyPatchOptions = {}\n): Promise<ApplyPatchResult> {\n const validate = options.validate !== false;\n const dryRun = options.dryRun === true;\n const allowWarn = options.allowLintWarnings !== false;\n\n const errors: string[] = [];\n const lint: LintFinding[] = [];\n const applied: AppliedOp[] = [];\n const snapshots: Snapshot[] = [];\n\n const crud = new NoteCrud(notesPath);\n\n let schema: VaultSchema | null = null;\n if (validate) {\n try {\n schema = await loadSchema(notesPath);\n } catch (e) {\n errors.push(`failed to load schema: ${e instanceof Error ? e.message : String(e)}`);\n return { ok: false, applied, rolledBack: [], lint, errors };\n }\n }\n\n // Pre-check: collisions on creates, missing sources on updates/deletes/moves.\n for (const c of changeset.creates ?? []) {\n if (existsSync(join(notesPath, c.path))) {\n errors.push(`create: already exists: ${c.path}`);\n }\n }\n for (const u of changeset.updates ?? []) {\n if (!existsSync(join(notesPath, u.path))) {\n errors.push(`update: does not exist: ${u.path}`);\n }\n }\n for (const d of changeset.deletes ?? []) {\n if (!existsSync(join(notesPath, d.path))) {\n errors.push(`delete: does not exist: ${d.path}`);\n }\n }\n for (const m of changeset.moves ?? []) {\n if (!existsSync(join(notesPath, m.from))) {\n errors.push(`move: source does not exist: ${m.from}`);\n }\n if (existsSync(join(notesPath, m.to))) {\n errors.push(`move: destination exists: ${m.to}`);\n }\n }\n\n if (errors.length > 0) {\n return { ok: false, applied, rolledBack: [], lint, errors };\n }\n\n // Lint pre-flight on proposed state. Simulate each create/update and validate.\n if (validate && schema) {\n for (const c of changeset.creates ?? []) {\n const proposed = matter.stringify(c.content, c.frontmatter ?? {});\n lint.push(...validateNote(c.path, proposed, schema));\n }\n for (const u of changeset.updates ?? []) {\n const abs = join(notesPath, u.path);\n let proposed: string;\n try {\n const existing = await readFile(abs, \"utf-8\");\n proposed = applyUpdateInMemory(existing, u);\n } catch (e) {\n errors.push(`update precheck: ${u.path}: ${e instanceof Error ? e.message : String(e)}`);\n continue;\n }\n lint.push(...validateNote(u.path, proposed, schema));\n }\n\n const schemaErrors = lint.filter((f) => f.rule === \"schema_violations\" && f.severity === \"error\");\n if (schemaErrors.length > 0) {\n errors.push(`${schemaErrors.length} schema violation(s) blocked the patch`);\n }\n if (!allowWarn) {\n const warns = lint.filter((f) => f.severity === \"warn\");\n if (warns.length > 0) errors.push(`${warns.length} lint warning(s) blocked the patch (allowLintWarnings=false)`);\n }\n }\n\n if (errors.length > 0 || dryRun) {\n return { ok: errors.length === 0, applied, rolledBack: [], lint, errors };\n }\n\n // Execute — capture snapshots as we go so rollback can reverse.\n try {\n for (const c of changeset.creates ?? []) {\n const abs = join(notesPath, c.path);\n await crud.create(c.path, c.content, c.frontmatter);\n snapshots.push({ op: \"create\", path: c.path, absPath: abs });\n applied.push({ op: \"create\", path: c.path });\n }\n for (const u of changeset.updates ?? []) {\n const abs = join(notesPath, u.path);\n const prev = await readFile(abs, \"utf-8\");\n await crud.update(u.path, u.content, { mode: u.mode, heading: u.heading } as UpdateNoteOptions);\n snapshots.push({ op: \"update\", path: u.path, absPath: abs, prevContent: prev });\n applied.push({ op: \"update\", path: u.path });\n }\n for (const m of changeset.moves ?? []) {\n await crud.move(m.from, m.to);\n snapshots.push({\n op: \"move\",\n path: m.to,\n absPath: join(notesPath, m.to),\n prevAbsPath: join(notesPath, m.from),\n });\n applied.push({ op: \"move\", path: `${m.from} -> ${m.to}` });\n }\n for (const d of changeset.deletes ?? []) {\n const abs = join(notesPath, d.path);\n const prev = await readFile(abs, \"utf-8\");\n await crud.delete(d.path);\n snapshots.push({ op: \"delete\", path: d.path, absPath: abs, prevContent: prev });\n applied.push({ op: \"delete\", path: d.path });\n }\n } catch (e) {\n errors.push(`op failed: ${e instanceof Error ? e.message : String(e)} — rolling back`);\n const rolledBack = await rollback(snapshots);\n return { ok: false, applied, rolledBack, lint, errors };\n }\n\n let indexes_regenerated: RegenResult[] = [];\n if (options.regenIndexes !== false) {\n const touched = [\n ...(changeset.creates ?? []).map((c) => c.path),\n ...(changeset.deletes ?? []).map((d) => d.path),\n ...(changeset.moves ?? []).flatMap((m) => [m.from, m.to]),\n ];\n try {\n indexes_regenerated = await regenIndexesForPaths(notesPath, touched);\n } catch (e) {\n // Non-fatal: patch succeeded; index regen is a best-effort side effect.\n errors.push(`index regen failed (patch still applied): ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n return { ok: true, applied, rolledBack: [], lint, errors, indexes_regenerated };\n}\n\nasync function rollback(snapshots: Snapshot[]): Promise<AppliedOp[]> {\n const reverted: AppliedOp[] = [];\n for (const s of snapshots.slice().reverse()) {\n try {\n switch (s.op) {\n case \"create\":\n if (existsSync(s.absPath)) await unlink(s.absPath);\n reverted.push({ op: \"create\", path: s.path });\n break;\n case \"update\":\n if (s.prevContent !== undefined) {\n await writeFile(s.absPath, s.prevContent, \"utf-8\");\n }\n reverted.push({ op: \"update\", path: s.path });\n break;\n case \"delete\":\n if (s.prevContent !== undefined) {\n await mkdir(dirname(s.absPath), { recursive: true });\n await writeFile(s.absPath, s.prevContent, \"utf-8\");\n }\n reverted.push({ op: \"delete\", path: s.path });\n break;\n case \"move\":\n if (s.prevAbsPath && existsSync(s.absPath)) {\n await mkdir(dirname(s.prevAbsPath), { recursive: true });\n await rename(s.absPath, s.prevAbsPath);\n }\n reverted.push({ op: \"move\", path: s.path });\n break;\n }\n } catch {\n // swallow — best-effort rollback\n }\n }\n return reverted;\n}\n\nfunction applyUpdateInMemory(existing: string, u: PatchUpdate): string {\n switch (u.mode) {\n case \"overwrite\":\n return u.content;\n case \"append\":\n return existing + \"\\n\" + u.content;\n case \"prepend\": {\n const { data, content: body } = matter(existing);\n const newBody = u.content + \"\\n\" + body;\n return Object.keys(data).length > 0 ? matter.stringify(newBody, data) : newBody;\n }\n case \"patch-by-heading\": {\n if (!u.heading) throw new Error(\"patch-by-heading requires heading\");\n return patchByHeadingInMemory(existing, u.heading, u.content);\n }\n }\n}\n\nfunction patchByHeadingInMemory(content: string, heading: string, newContent: string): string {\n const lines = content.split(\"\\n\");\n const re = new RegExp(`^#{1,6}\\\\s+${heading.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\\\\s*$`, \"i\");\n let idx = -1;\n let level = 0;\n for (let i = 0; i < lines.length; i++) {\n if (re.test(lines[i])) {\n idx = i;\n const m = lines[i].match(/^(#{1,6})\\s+/);\n level = m ? m[1].length : 1;\n break;\n }\n }\n if (idx === -1) throw new Error(`Heading not found: ${heading}`);\n let end = lines.length;\n for (let i = idx + 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 [...lines.slice(0, idx + 1), \"\", newContent, \"\", ...lines.slice(end)].join(\"\\n\");\n}\n","import { basename } from \"node:path\";\nimport type { ChangeSet, PatchCreate, PatchUpdate } from \"./patch.js\";\n\nexport interface SynthesizeNoteInput {\n topic: string;\n answer: string;\n suggested_path: string;\n type?: string;\n sources?: string[];\n derived_from?: string[];\n related_notes?: string[];\n status?: string;\n confidence?: string;\n decision_maker?: string;\n decided_on?: string;\n severity?: string;\n /** Verbatim symptom phrases (the user's own words) — symptom-keyed for retrieval. */\n symptoms?: string[];\n extra_frontmatter?: Record<string, unknown>;\n /**\n * When true, the resulting note is filed under `proposals/<date>-<slug>.md` with\n * `status: proposal` frontmatter, regardless of `suggested_path`. The original path\n * is preserved as `proposed_target` in frontmatter so `synthesize_promote` knows\n * where to move it on acceptance.\n */\n proposal?: boolean;\n /**\n * When provided alongside `proposal: true`, used as the proposal subdirectory name.\n * Defaults to \"proposals\". Useful for callers that want to segregate by source\n * (e.g. \"proposals/research\", \"proposals/outage\").\n */\n proposal_subdir?: string;\n}\n\nexport interface SynthesizePreview {\n changeset: ChangeSet;\n path: string;\n title: string;\n /** True when the preview is a proposal — caller (or `synthesize_promote`) must move it later. */\n is_proposal?: boolean;\n /** When `is_proposal` is true, the canonical path that `synthesize_promote` should move it to. */\n proposed_target?: string;\n}\n\n/**\n * Convert a query answer + provenance into a ChangeSet that can be fed to applyPatch.\n * The shaped note includes provenance frontmatter and auto-inserts [[wikilinks]] for\n * any related_notes that appear as bare titles in the body.\n *\n * Does NOT write to disk — always returns a ChangeSet. Callers pass it to applyPatch.\n *\n * Proposal mode: when `proposal: true`, the path is rewritten to\n * `<proposal_subdir>/<YYYY-MM-DD>-<slug>.md`, the `status` is forced to `proposal`,\n * and `proposed_target` records the original suggested path. `synthesize_promote`\n * later moves the file to that target and clears the proposal frontmatter.\n */\nexport function buildSynthesizeChangeSet(input: SynthesizeNoteInput): SynthesizePreview {\n const today = new Date().toISOString().slice(0, 10);\n const title = input.topic.trim();\n const canonicalPath = input.suggested_path.endsWith(\".md\") ? input.suggested_path : input.suggested_path + \".md\";\n\n const isProposal = input.proposal === true;\n const subdir = (input.proposal_subdir ?? \"proposals\").replace(/\\/+$/, \"\");\n const slug = slugForTopic(title);\n const path = isProposal ? `${subdir}/${today}-${slug}.md` : canonicalPath;\n\n const frontmatter: Record<string, unknown> = {\n title,\n type: input.type ?? \"note\",\n status: isProposal ? \"proposal\" : (input.status ?? \"active\"),\n last_verified: today,\n confidence: input.confidence ?? \"medium\",\n ...(input.sources && input.sources.length > 0 && { sources: input.sources }),\n ...(input.derived_from && input.derived_from.length > 0 && { derived_from: input.derived_from }),\n ...(input.decision_maker && { decision_maker: input.decision_maker }),\n ...(input.decided_on && { decided_on: input.decided_on }),\n ...(input.severity && { severity: input.severity }),\n ...(input.symptoms && input.symptoms.length > 0 && { symptoms: input.symptoms }),\n ...(isProposal && { proposed_target: canonicalPath }),\n ...(input.extra_frontmatter ?? {}),\n };\n\n const body = buildBody(title, input.answer, input.related_notes ?? []);\n\n const create: PatchCreate = { path, content: body, frontmatter };\n const changeset: ChangeSet = { creates: [create] };\n\n return {\n changeset,\n path,\n title,\n ...(isProposal && { is_proposal: true, proposed_target: canonicalPath }),\n };\n}\n\nfunction slugForTopic(topic: string): string {\n return topic\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60) || \"untitled\";\n}\n\nfunction buildBody(title: string, answer: string, relatedNotes: string[]): string {\n let body = `# ${title}\\n\\n${answer.trim()}\\n`;\n\n if (relatedNotes.length > 0) {\n body = autoWikilink(body, relatedNotes);\n const links = relatedNotes.map((r) => `- [[${wikilinkName(r)}]]`).join(\"\\n\");\n body += `\\n## Related\\n\\n${links}\\n`;\n }\n\n return body;\n}\n\n/**\n * For each related note, replace the first bare occurrence of its title in the body\n * with a [[wikilink]]. Conservative — only replaces whole-word matches outside code blocks.\n */\nfunction autoWikilink(body: string, relatedNotes: string[]): string {\n // Skip content inside fenced code blocks\n const parts = body.split(/(```[\\s\\S]*?```)/g);\n return parts\n .map((part, i) => {\n if (i % 2 === 1) return part; // code block\n let out = part;\n for (const r of relatedNotes) {\n const name = wikilinkName(r);\n if (/[\\[\\]]/.test(out) && out.includes(`[[${name}]]`)) continue; // already linked\n const escaped = name.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const re = new RegExp(`\\\\b${escaped}\\\\b`, \"i\");\n out = out.replace(re, `[[${name}]]`);\n }\n return out;\n })\n .join(\"\");\n}\n\nfunction wikilinkName(pathOrName: string): string {\n // \"decisions/auth-migration.md\" → \"auth-migration\"\n // \"Auth Migration\" → \"Auth Migration\"\n const base = basename(pathOrName).replace(/\\.md$/, \"\");\n return base;\n}\n\nexport interface PromoteInput {\n /**\n * Path to the proposal note (relative to vault root). Must have `status: proposal` frontmatter\n * AND `proposed_target` frontmatter, both written by `buildSynthesizeChangeSet` in proposal mode.\n */\n proposal_path: string;\n /**\n * Optional override for the destination path. When omitted, the proposal's own\n * `proposed_target` frontmatter is used.\n */\n target_path?: string;\n}\n\nexport interface PromotePreview {\n changeset: ChangeSet;\n from: string;\n to: string;\n}\n\n/**\n * Promote a reviewed proposal to its canonical destination.\n *\n * Reads the proposal's existing content + frontmatter, strips `status: proposal` and\n * `proposed_target` keys, restores the original status (default \"active\"), and returns\n * a ChangeSet that:\n * - creates the canonical path with the cleaned frontmatter + body\n * - deletes the proposal path\n *\n * The caller (the MCP tool) reads the proposal file, runs this builder, and applies the\n * resulting changeset via applyPatch — preserving atomicity and rollback semantics.\n */\nexport function buildPromoteChangeSet(input: {\n proposal_path: string;\n target_path?: string;\n body: string;\n frontmatter: Record<string, unknown>;\n}): PromotePreview {\n const fm = { ...input.frontmatter };\n const target = input.target_path ?? (typeof fm.proposed_target === \"string\" ? fm.proposed_target : undefined);\n if (!target) {\n throw new Error(\n `Cannot promote ${input.proposal_path}: no target_path provided and no 'proposed_target' frontmatter on the proposal.`\n );\n }\n // Restore canonical status (default \"active\") and strip proposal markers.\n if (fm.status === \"proposal\") fm.status = \"active\";\n delete fm.proposed_target;\n\n const create: PatchCreate = {\n path: target,\n content: input.body,\n frontmatter: fm,\n };\n const changeset: ChangeSet = {\n creates: [create],\n deletes: [{ path: input.proposal_path }],\n };\n\n return { changeset, from: input.proposal_path, to: target };\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst BEGIN_MARKER = \"<!-- semantic-memory:begin contract -->\";\nconst END_MARKER = \"<!-- semantic-memory:end contract -->\";\nconst LOCAL_NOTES_HEADING = \"## Local Notes\";\n\nexport interface ToolSummary {\n name: string;\n description: string;\n deprecated?: boolean;\n}\n\nexport interface ModeSummary {\n name: string;\n description: string;\n isDefault?: boolean;\n}\n\nexport interface RegenerateContractOptions {\n projectRoot: string;\n pluginVersion: string;\n tools: ToolSummary[];\n modes?: ModeSummary[];\n /** When true, overwrite hand-edited managed blocks. Default false → fails loudly on detected hand-edits. */\n force?: boolean;\n /** Override \"now\" — used by tests for deterministic timestamps. */\n nowIso?: string;\n}\n\nexport interface RegenerateContractResult {\n path: string;\n written: boolean;\n reason?: string;\n hand_edit_detected?: boolean;\n preserved_local_notes_chars?: number;\n}\n\n/**\n * Generate or refresh AGENTS.md at the project root.\n *\n * Behavior:\n * - If AGENTS.md is absent, write the full contract + an empty Local Notes section.\n * - If AGENTS.md exists and contains the managed block markers:\n * * Regenerate the content INSIDE the markers from the current tool/mode inventory.\n * * Preserve everything OUTSIDE the markers (Local Notes tail, custom sections, frontmatter the\n * user added).\n * * If `force` is false and the inside-block content differs from a freshly-generated block AND\n * the difference is not just a regenerable timestamp/version, fail with hand_edit_detected.\n * - If AGENTS.md exists without managed-block markers, refuse and report — the user has a custom\n * AGENTS.md and we should not silently take it over.\n */\nexport async function regenerateAgentsContract(opts: RegenerateContractOptions): Promise<RegenerateContractResult> {\n const path = join(opts.projectRoot, \"AGENTS.md\");\n const generatedBlock = buildManagedBlock(opts);\n const generatedFull = buildFullDocument(opts, generatedBlock);\n\n if (!existsSync(path)) {\n await writeFile(path, generatedFull, \"utf-8\");\n return { path, written: true };\n }\n\n const existing = await readFile(path, \"utf-8\");\n const beginIdx = existing.indexOf(BEGIN_MARKER);\n const endIdx = existing.indexOf(END_MARKER);\n\n if (beginIdx === -1 || endIdx === -1 || endIdx < beginIdx) {\n return {\n path,\n written: false,\n reason:\n \"AGENTS.md exists but does not contain the semantic-memory managed-block markers. Move your existing content into a Local Notes section outside the managed block, or delete AGENTS.md and re-run to bootstrap from scratch.\",\n };\n }\n\n const existingBlock = existing.slice(beginIdx, endIdx + END_MARKER.length);\n const localTail = extractLocalTail(existing, endIdx + END_MARKER.length);\n\n if (!opts.force && hasHandEdits(existingBlock, generatedBlock)) {\n return {\n path,\n written: false,\n hand_edit_detected: true,\n reason:\n \"Managed-block content has been hand-edited. Move your changes to the Local Notes section (outside the managed block) and re-run, or pass force=true to overwrite.\",\n };\n }\n\n // Refresh the entire pre-block prefix (frontmatter + title) so timestamp/version\n // stamps reflect the current regeneration. Keep the managed block fresh and the\n // Local Notes tail untouched.\n const newPrefix = buildPrefix(opts);\n const newDoc = newPrefix + generatedBlock + localTail;\n await writeFile(path, newDoc, \"utf-8\");\n return {\n path,\n written: true,\n preserved_local_notes_chars: localTail.length,\n };\n}\n\nfunction buildPrefix(opts: RegenerateContractOptions): string {\n return [\n \"---\",\n `generated_by: semantic-memory`,\n `version: ${opts.pluginVersion}`,\n `last_generated: ${opts.nowIso ?? new Date().toISOString()}`,\n \"---\",\n \"\",\n \"# Project Agent Contract\",\n \"\",\n ].join(\"\\n\");\n}\n\nfunction buildFullDocument(opts: RegenerateContractOptions, managedBlock: string): string {\n const localNotes = `\\n\\n${LOCAL_NOTES_HEADING}\\n\\n_User-authored content lives here. Preserved across \\`regenerate_contract\\` runs._\\n`;\n return buildPrefix(opts) + managedBlock + localNotes;\n}\n\nfunction buildManagedBlock(opts: RegenerateContractOptions): string {\n const lines: string[] = [BEGIN_MARKER, \"\"];\n\n lines.push(\"## Active Modes\", \"\");\n const modes = opts.modes ?? defaultModes();\n for (const m of modes) {\n const tag = m.isDefault ? \" (default)\" : \"\";\n lines.push(`- **${m.name}**${tag} — ${m.description}`);\n }\n lines.push(\"\");\n\n lines.push(\"## Required Workflow\", \"\");\n lines.push(\"1. For multi-step work that needs verification, open a session: `session_start({task})`.\");\n lines.push(\"2. Use mode-aware retrieval: `search_hybrid`, `read_note`, `list_notes`, `backlinks`/`forwardlinks`.\");\n lines.push(\"3. Make durable updates via `apply_patch` or `synthesize_note` — never bypass the patch layer for multi-note edits.\");\n lines.push(\"4. Run verification commands inside a session: `session_run({cmd})`.\");\n lines.push(\"5. Close with `session_finish({summary})` — refused without verification unless explicitly waived.\");\n lines.push(\"6. Lint regularly: `lint_vault({checks: ['schema','provenance','stale','broken_links']})`.\");\n lines.push(\"\");\n\n lines.push(\"## Tool Surface\", \"\");\n const live = opts.tools.filter((t) => !t.deprecated);\n const deprecated = opts.tools.filter((t) => t.deprecated);\n for (const t of live) {\n lines.push(`- \\`${t.name}\\` — ${stripDeprecationPrefix(t.description)}`);\n }\n if (deprecated.length > 0) {\n lines.push(\"\");\n lines.push(\"### Deprecated (removed in v2.0.0)\", \"\");\n for (const t of deprecated) {\n lines.push(`- \\`${t.name}\\` — ${stripDeprecationPrefix(t.description)}`);\n }\n }\n lines.push(\"\");\n\n lines.push(\"## Memory Policy\", \"\");\n lines.push(\"- Markdown is canonical. The vault under `.claude/.vault/` (or wherever `--notes` points) is the source of truth.\");\n lines.push(\"- Indexes (vector / graph / FTS) are derived state — rebuildable via `reindex` without losing knowledge.\");\n lines.push(\"- Provenance frontmatter is required for `note`/`decision`/`gotcha` types — `sources` (external) or `derived_from` (internal).\");\n lines.push(\"- Mode router lives at `.claude/.semantic-memory/mode` (legacy `.claude/.sidekick-mode` still readable through v1.x); explicit `/mode` is ground truth.\");\n lines.push(\"\");\n\n lines.push(END_MARKER);\n return lines.join(\"\\n\");\n}\n\nfunction defaultModes(): ModeSummary[] {\n return [\n { name: \"vault-first\", description: \"Project-scoped prose questions trigger vault consultation with cite-or-deflect.\", isDefault: true },\n { name: \"research\", description: \"Every source introduced is filed via `ingest_source`; mandatory `synthesize_note` on exit.\" },\n { name: \"outage-silence\", description: \"No proactive vault search. Terse responses. Postmortem `synthesize_note` on exit.\" },\n ];\n}\n\nfunction stripDeprecationPrefix(description: string): string {\n return description.replace(/^\\[DEPRECATED[^\\]]*\\]\\s*/, \"(deprecated) \");\n}\n\nfunction extractLocalTail(existing: string, afterEndIdx: number): string {\n const tail = existing.slice(afterEndIdx);\n // Trim a single leading blank-line-then-newline if present so we don't double-space across regenerations.\n return tail;\n}\n\n/**\n * Detect hand-edits by comparing semantic content of the managed block, ignoring lines that are\n * regenerable (currently no per-block timestamps live inside the block — they're in frontmatter).\n * Returns true if the existing block diverges from what we'd generate now.\n */\nfunction hasHandEdits(existingBlock: string, generatedBlock: string): boolean {\n return existingBlock.trim() !== generatedBlock.trim();\n}\n\n/**\n * Inspect AGENTS.md without modifying it. Returns whether the file exists, whether it has managed\n * markers, and how many characters of Local Notes tail are preserved.\n */\nexport async function inspectAgentsContract(projectRoot: string): Promise<{\n path: string;\n exists: boolean;\n has_managed_block: boolean;\n local_notes_chars: number;\n}> {\n const path = join(projectRoot, \"AGENTS.md\");\n if (!existsSync(path)) {\n return { path, exists: false, has_managed_block: false, local_notes_chars: 0 };\n }\n const raw = await readFile(path, \"utf-8\");\n const beginIdx = raw.indexOf(BEGIN_MARKER);\n const endIdx = raw.indexOf(END_MARKER);\n if (beginIdx === -1 || endIdx === -1) {\n return { path, exists: true, has_managed_block: false, local_notes_chars: 0 };\n }\n const localTail = raw.slice(endIdx + END_MARKER.length).trim();\n return { path, exists: true, has_managed_block: true, local_notes_chars: localTail.length };\n}\n"],"mappings":"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","names":["body","readFile","writeFile","join","glob","matter","escapeRegex","readFile","writeFile","existsSync","join","dirname","basename","matter","readFile","writeFile","unlink","rename","mkdir","existsSync","dirname","join","matter","existsSync","join","matter","readFile","unlink","writeFile","mkdir","dirname","rename","basename","readFile","writeFile","existsSync","join"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/lint.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\nimport { loadSchema, validateNote, type LintFinding, type VaultSchema } from \"./schema.js\";\nimport { deriveProjectRoot } from \"./session.js\";\nimport { computeDecay, loadDecayConfig, normalizeVerifiedDate } from \"./decay.js\";\nimport { readSelectionLog } from \"./telemetry.js\";\nimport { compileLexicon, findAliasConflicts } from \"./lexicon.js\";\n\nexport interface LintReport {\n schemaPath: string;\n findings: LintFinding[];\n byRule: {\n schema_violations: LintFinding[];\n missing_provenance: LintFinding[];\n stale: LintFinding[];\n broken_links: LintFinding[];\n code_symbols: LintFinding[];\n decay_candidates: LintFinding[];\n alias_conflicts: LintFinding[];\n decoys: LintFinding[];\n };\n counts: {\n errors: number;\n warnings: number;\n filesLinted: number;\n };\n}\n\nexport async function lintVault(\n notesPath: string,\n opts: {\n pathGlob?: string;\n schemaOverride?: VaultSchema;\n todayIso?: string;\n /**\n * When true, run the code-symbol drift check: flag inline-code file-path\n * references in notes that no longer exist under the project root. Opt-in\n * (off by default) so the standard report and the healthcheck slow tier stay\n * quiet and byte-stable. Fails open when the project root is not a code repo.\n */\n checkCodeSymbols?: boolean;\n /** Override the project root for code-symbol resolution (default: derived from notesPath). */\n projectRoot?: string;\n /**\n * When true, run the decay-candidates check: cross-reference the selection\n * log (notes that appeared in recent search results) against each note's\n * current decay multiplier, and flag notes that are frequently retrieved yet\n * heavily decayed (\"relevant but going stale — verify or revise\"). Opt-in;\n * a silent no-op when no selection log exists.\n */\n checkDecayCandidates?: boolean;\n /**\n * When true, run the alias-conflict check: flag lexicon phrases that map to\n * more than one canonical target (a wrong binding misroutes queries). Opt-in;\n * no-op when the vault has no lexicon.\n */\n checkAliasConflicts?: boolean;\n /**\n * When true, run the decoys check: flag notes that were retrieved often (per\n * the selection log) but NEVER cited in an answer. Surfaced for a human to\n * judge — a decoy might be genuine noise OR a note the answers were wrong to\n * skip; usage-feedback ranking (v1.5) never auto-down-ranks on this ambiguous\n * signal (decision Q5). Opt-in; a no-op with no selection log.\n */\n checkDecoys?: boolean;\n } = {}\n): Promise<LintReport> {\n const schema = opts.schemaOverride ?? (await loadSchema(notesPath));\n const files = await glob(opts.pathGlob ?? \"**/*.md\", {\n cwd: notesPath,\n ignore: [\"node_modules/**\", \".semantic-sidekick-index/**\", \"vault.schema.yml\"],\n });\n\n const findings: LintFinding[] = [];\n // Build known-note set first, to make broken-link detection O(1) per link.\n const knownNames = new Set<string>();\n const rawByPath = new Map<string, string>();\n for (const rel of files) {\n try {\n const raw = await readFile(join(notesPath, rel), \"utf-8\");\n rawByPath.set(rel, raw);\n // Wikilink names match on basename-without-ext (obsidian-style)\n knownNames.add(basename(rel).replace(/\\.md$/, \"\"));\n } catch {\n /* skip */\n }\n }\n\n // Code-symbol drift is opt-in and only meaningful against a real code repo.\n const codeRoot = opts.checkCodeSymbols\n ? resolveCodeRoot(opts.projectRoot ?? deriveProjectRoot(notesPath))\n : null;\n\n for (const [rel, raw] of rawByPath) {\n findings.push(...validateNote(rel, raw, schema, { todayIso: opts.todayIso }));\n findings.push(...findBrokenLinks(rel, raw, knownNames));\n if (codeRoot) findings.push(...findCodeSymbolDrift(rel, raw, codeRoot));\n }\n\n if (opts.checkDecayCandidates) {\n findings.push(...(await findDecayCandidates(notesPath, rawByPath, opts.todayIso)));\n }\n\n if (opts.checkAliasConflicts) {\n for (const c of findAliasConflicts(await compileLexicon(notesPath))) {\n findings.push({\n path: \"lexicon\",\n rule: \"alias_conflicts\",\n severity: \"warn\",\n message: `phrase \"${c.phrase}\" maps to ${c.canonicals.length} targets: ${c.canonicals.join(\", \")} — pick one`,\n });\n }\n }\n\n if (opts.checkDecoys) {\n findings.push(...(await findDecoys(notesPath, rawByPath)));\n }\n\n const byRule = {\n schema_violations: findings.filter((f) => f.rule === \"schema_violations\"),\n missing_provenance: findings.filter((f) => f.rule === \"missing_provenance\"),\n stale: findings.filter((f) => f.rule === \"stale\"),\n broken_links: findings.filter((f) => f.rule === \"broken_links\"),\n code_symbols: findings.filter((f) => f.rule === \"code_symbols\"),\n decay_candidates: findings.filter((f) => f.rule === \"decay_candidates\"),\n alias_conflicts: findings.filter((f) => f.rule === \"alias_conflicts\"),\n decoys: findings.filter((f) => f.rule === \"decoys\"),\n };\n const errors = findings.filter((f) => f.severity === \"error\").length;\n const warnings = findings.filter((f) => f.severity === \"warn\").length;\n\n return {\n schemaPath: join(notesPath, \"vault.schema.yml\"),\n findings,\n byRule,\n counts: { errors, warnings, filesLinted: files.length },\n };\n}\n\nfunction findBrokenLinks(\n relPath: string,\n rawFileContent: string,\n knownNames: Set<string>\n): LintFinding[] {\n const { content } = matter(rawFileContent);\n const findings: LintFinding[] = [];\n // Match [[target]] and [[target|alias]] — skip fenced code and inline code.\n const stripped = content\n .replace(/```[\\s\\S]*?```/g, \"\")\n .replace(/`[^`]+`/g, \"\");\n const re = /\\[\\[([^\\]\\|]+)(?:\\|[^\\]]+)?\\]\\]/g;\n const seen = new Set<string>();\n let m: RegExpExecArray | null;\n while ((m = re.exec(stripped)) !== null) {\n const target = m[1].trim().split(\"#\")[0].split(\"/\").pop()!.replace(/\\.md$/, \"\");\n if (seen.has(target)) continue;\n seen.add(target);\n if (!knownNames.has(target)) {\n findings.push({\n path: relPath,\n rule: \"broken_links\",\n severity: \"warn\",\n message: `wikilink [[${target}]] has no matching note`,\n });\n }\n }\n return findings;\n}\n\n/** File extensions we treat as \"this looks like a source/doc file reference\". */\nconst CODE_EXTENSIONS = new Set([\n \"ts\", \"tsx\", \"js\", \"jsx\", \"mjs\", \"cjs\", \"py\", \"rb\", \"go\", \"rs\", \"java\", \"kt\",\n \"c\", \"h\", \"cpp\", \"hpp\", \"cs\", \"php\", \"swift\", \"scala\", \"sh\", \"bash\", \"sql\",\n \"json\", \"yaml\", \"yml\", \"toml\", \"css\", \"scss\", \"html\", \"vue\", \"svelte\", \"md\",\n]);\n\n/**\n * Decide whether `root` is a real code repository worth checking paths against.\n * Fails open (returns null) for non-code roots so the check is a silent no-op\n * outside a project — e.g. a standalone vault, or when babel-fish isn't present.\n */\nfunction resolveCodeRoot(root: string): string | null {\n if (!root || !existsSync(root)) return null;\n const markers = [\"package.json\", \".git\", \"src\", \"lib\", \"pyproject.toml\", \"go.mod\", \"Cargo.toml\"];\n return markers.some((m) => existsSync(join(root, m))) ? root : null;\n}\n\n/**\n * Code-symbol drift: scan a note's inline-code spans for repo-relative file-path\n * references and flag ones whose first path segment IS a real directory in the repo\n * but whose full path no longer exists — i.e. a stale reference to a moved/deleted\n * file. Anchoring on an existing first segment keeps false positives low: paths\n * belonging to other repos (whose top-level dir isn't present here) are skipped.\n *\n * Scope note: this validates *path* references. Fine-grained symbol-name checking\n * (function/class identifiers) needs a real symbol index and is planned for the\n * v1.4 lexicon arc, which will extend this same `code_symbols` rule.\n */\nfunction findCodeSymbolDrift(relPath: string, rawFileContent: string, codeRoot: string): LintFinding[] {\n const { content } = matter(rawFileContent);\n // Inline-code spans only (single backtick). Fenced blocks are illustrative and\n // over-match. Strip fenced blocks first so their backticks don't confuse the span regex.\n const withoutFences = content.replace(/```[\\s\\S]*?```/g, \"\");\n const findings: LintFinding[] = [];\n const seen = new Set<string>();\n const re = /`([^`\\n]+)`/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(withoutFences)) !== null) {\n let token = m[1].trim();\n if (!looksLikeRepoPath(token)) continue;\n token = token.replace(/^\\.\\//, \"\");\n if (seen.has(token)) continue;\n seen.add(token);\n const firstSeg = token.split(\"/\")[0];\n // Only judge paths anchored in a directory that actually exists in this repo.\n if (!existsSync(join(codeRoot, firstSeg))) continue;\n if (!existsSync(join(codeRoot, token))) {\n findings.push({\n path: relPath,\n rule: \"code_symbols\",\n severity: \"warn\",\n message: `inline code path \\`${token}\\` does not exist under the project root (stale reference?)`,\n });\n }\n }\n return findings;\n}\n\nfunction looksLikeRepoPath(token: string): boolean {\n if (!token.includes(\"/\")) return false;\n if (/\\s/.test(token)) return false; // commands, prose\n if (token.includes(\"://\")) return false; // URLs\n if (/[*?\\[\\]{}()<>|]/.test(token)) return false; // globs / shell / placeholders\n if (token.startsWith(\"/\") || token.startsWith(\"~\")) return false; // absolute / home\n const normalized = token.replace(/^\\.\\//, \"\");\n if (normalized.startsWith(\"..\")) return false; // escapes the repo\n const last = normalized.split(\"/\").pop() ?? \"\";\n const dot = last.lastIndexOf(\".\");\n if (dot <= 0) return false; // no extension → likely a dir or a symbol like a/b\n const ext = last.slice(dot + 1).toLowerCase();\n return CODE_EXTENSIONS.has(ext);\n}\n\n/** Multiplier at or below which a frequently-retrieved note is worth surfacing. */\nconst DECAY_CANDIDATE_THRESHOLD = 0.5;\n\n/**\n * Decay-candidates: notes that keep showing up in search results but have decayed.\n * Reads the selection log (search events record the paths each query returned),\n * computes each retrieved note's current decay multiplier from its frontmatter,\n * and flags those at/below the threshold — \"this keeps being relevant but is\n * going stale; verify or revise.\" Index-free (no embedder); a no-op when there's\n * no selection log yet.\n */\nasync function findDecayCandidates(\n notesPath: string,\n rawByPath: Map<string, string>,\n todayIso?: string\n): Promise<LintFinding[]> {\n const events = await readSelectionLog(notesPath);\n if (events.length === 0) return [];\n\n // Count how often each path was retrieved (appeared in a search's results).\n const retrieved = new Map<string, number>();\n for (const e of events) {\n if (e.kind === \"search\") {\n for (const r of e.results) retrieved.set(r.path, (retrieved.get(r.path) ?? 0) + 1);\n }\n }\n if (retrieved.size === 0) return [];\n\n const config = loadDecayConfig(notesPath);\n if (!config.enabled) return [];\n const nowMs = todayIso ? Date.parse(todayIso) : undefined;\n\n const findings: LintFinding[] = [];\n for (const [rel, count] of retrieved) {\n const raw = rawByPath.get(rel);\n if (!raw) continue; // retrieved path no longer in the vault — skip\n const { data: fm } = matter(raw);\n const decay = computeDecay({\n type: typeof fm.type === \"string\" ? fm.type : undefined,\n last_verified: normalizeVerifiedDate(fm.last_verified),\n evergreen: fm.evergreen === true,\n config,\n nowMs,\n });\n if (decay.multiplier <= DECAY_CANDIDATE_THRESHOLD) {\n findings.push({\n path: rel,\n rule: \"decay_candidates\",\n severity: \"warn\",\n message: `retrieved ${count}× recently but decayed to ${decay.multiplier.toFixed(2)} (age ${Math.round(decay.age_days)}d) — verify_note or revise`,\n });\n }\n }\n // Most-retrieved first — the ones worth re-verifying soonest.\n findings.sort((a, b) => (retrieved.get(b.path) ?? 0) - (retrieved.get(a.path) ?? 0));\n return findings;\n}\n\n/** A note must be retrieved at least this many times before \"never cited\" is signal. */\nconst DECOY_MIN_RETRIEVALS = 3;\n\n/**\n * Decoys (v1.5 Phase 9): notes retrieved ≥ DECOY_MIN_RETRIEVALS times that were\n * NEVER cited. Surfaced only — usage-feedback ranking never auto-down-ranks on\n * this ambiguous signal (a decoy might be genuine noise, or a note the answers\n * were wrong to skip). A human decides; the lint just points.\n */\nasync function findDecoys(notesPath: string, rawByPath: Map<string, string>): Promise<LintFinding[]> {\n const events = await readSelectionLog(notesPath);\n if (events.length === 0) return [];\n\n const retrieved = new Map<string, number>();\n const cited = new Set<string>();\n for (const e of events) {\n if (e.kind === \"search\") {\n for (const r of e.results) retrieved.set(r.path, (retrieved.get(r.path) ?? 0) + 1);\n } else if (e.kind === \"selection\") {\n cited.add(e.note_path);\n }\n }\n\n const findings: LintFinding[] = [];\n for (const [rel, count] of retrieved) {\n if (count < DECOY_MIN_RETRIEVALS) continue;\n if (cited.has(rel)) continue;\n if (!rawByPath.has(rel)) continue; // retrieved path no longer in the vault — skip\n findings.push({\n path: rel,\n rule: \"decoys\",\n severity: \"warn\",\n message: `retrieved ${count}× but never cited — decoy noise, or a note answers wrongly skip? (surfaced, not auto-down-ranked)`,\n });\n }\n findings.sort((a, b) => (retrieved.get(b.path) ?? 0) - (retrieved.get(a.path) ?? 0));\n return findings;\n}\n\nexport function formatLintReport(report: LintReport, opts: { rule?: keyof LintReport[\"byRule\"] } = {}): string {\n const target = opts.rule ? report.byRule[opts.rule] : report.findings;\n if (target.length === 0) {\n return `clean: 0 findings across ${report.counts.filesLinted} files`;\n }\n const lines = [\n `${target.length} finding${target.length === 1 ? \"\" : \"s\"} (errors=${report.counts.errors}, warnings=${report.counts.warnings}, files=${report.counts.filesLinted})`,\n \"\",\n ];\n for (const f of target) {\n lines.push(`[${f.severity}] ${f.rule}: ${f.path} — ${f.message}`);\n }\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/log.ts"],"sourcesContent":["import { readFile, writeFile, appendFile, access } from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Structured append-only log at the vault root.\n *\n * Shape: one markdown bullet per event, each followed by a fenced `yaml event`\n * block carrying the machine-readable payload. Human scrolling and YAML parsing\n * both work on the same file.\n *\n * Example rendered line pair:\n * - 2026-04-22T00:00:00Z — ingest: added 3 notes from RFC-1234\n * ```yaml event\n * ts: 2026-04-22T00:00:00Z\n * kind: ingest\n * payload:\n * source: RFC-1234\n * count: 3\n * ```\n */\n\nconst LOG_FILE = \"log.md\";\nconst HEADER = `# Vault Log\\n\\n> Append-only session and ingest log. Machine-readable YAML blocks follow each line.\\n\\n`;\n\nexport interface LogEventInput {\n kind: string;\n summary: string;\n payload?: Record<string, unknown>;\n ts?: string;\n}\n\nexport interface LogEntry {\n ts: string;\n kind: string;\n summary: string;\n payload?: Record<string, unknown>;\n}\n\nexport interface LogQueryOptions {\n kind?: string;\n after?: string;\n before?: string;\n limit?: number;\n}\n\nexport async function logEvent(vaultPath: string, input: LogEventInput): Promise<LogEntry> {\n const path = join(vaultPath, LOG_FILE);\n const ts = input.ts ?? new Date().toISOString();\n const entry: LogEntry = { ts, kind: input.kind, summary: input.summary };\n if (input.payload) entry.payload = input.payload;\n\n await ensureLogFile(path);\n\n const line = `- ${ts} — ${input.kind}: ${input.summary}\\n`;\n const block = renderYamlBlock(entry);\n await appendFile(path, line + block + \"\\n\", \"utf-8\");\n\n return entry;\n}\n\nexport async function logQuery(vaultPath: string, opts: LogQueryOptions = {}): Promise<LogEntry[]> {\n const path = join(vaultPath, LOG_FILE);\n let raw = \"\";\n try {\n raw = await readFile(path, \"utf-8\");\n } catch {\n return [];\n }\n\n const entries = parseEntries(raw);\n const filtered = entries.filter((e) => {\n if (opts.kind && e.kind !== opts.kind) return false;\n if (opts.after && new Date(e.ts).getTime() < new Date(opts.after).getTime()) return false;\n if (opts.before && new Date(e.ts).getTime() > new Date(opts.before).getTime()) return false;\n return true;\n });\n if (opts.limit) return filtered.slice(-opts.limit);\n return filtered;\n}\n\nasync function ensureLogFile(path: string): Promise<void> {\n try {\n await access(path, constants.F_OK);\n } catch {\n await writeFile(path, HEADER, \"utf-8\");\n }\n}\n\nfunction renderYamlBlock(entry: LogEntry): string {\n const lines = [\n \"```yaml event\",\n `ts: ${entry.ts}`,\n `kind: ${entry.kind}`,\n `summary: ${yamlInline(entry.summary)}`,\n ];\n if (entry.payload && Object.keys(entry.payload).length > 0) {\n lines.push(`payload:`);\n for (const [k, v] of Object.entries(entry.payload)) {\n lines.push(` ${k}: ${yamlInline(v)}`);\n }\n }\n lines.push(\"```\");\n return lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction yamlInline(v: unknown): string {\n if (v == null) return \"null\";\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n if (Array.isArray(v)) return \"[\" + v.map((x) => yamlInline(x)).join(\", \") + \"]\";\n if (typeof v === \"object\") return JSON.stringify(v);\n const s = String(v);\n // Quote if it contains colons, special chars, or would be parsed as a non-string\n if (/[:\\-#&*!|>%@`\"'{}[\\]]|^\\s|\\s$/.test(s) || /^(true|false|null|\\d)/i.test(s)) {\n return JSON.stringify(s);\n }\n return s;\n}\n\nfunction parseEntries(raw: string): LogEntry[] {\n const entries: LogEntry[] = [];\n const blockRe = /```yaml event\\n([\\s\\S]*?)```/g;\n let m: RegExpExecArray | null;\n while ((m = blockRe.exec(raw)) !== null) {\n const block = m[1];\n const parsed = parseFlatYaml(block);\n if (parsed && typeof parsed.ts === \"string\" && typeof parsed.kind === \"string\") {\n entries.push(parsed as LogEntry);\n }\n }\n return entries;\n}\n\n/**\n * Minimal line-by-line YAML parser for the fixed shape we emit.\n * Accepts: top-level scalars + `payload:` followed by indented `key: value` pairs.\n * Anything more complex than our renderer produces is not guaranteed.\n */\nfunction parseFlatYaml(block: string): Partial<LogEntry> | null {\n const out: any = {};\n const lines = block.split(/\\r?\\n/);\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n if (/^\\s*$/.test(line)) {\n i++;\n continue;\n }\n const topMatch = line.match(/^([a-z_]+):\\s*(.*)$/i);\n if (!topMatch) {\n i++;\n continue;\n }\n const [, key, rhs] = topMatch;\n if (key === \"payload\") {\n const payload: Record<string, unknown> = {};\n i++;\n while (i < lines.length && /^\\s{2,}[a-z_]/i.test(lines[i])) {\n const m2 = lines[i].match(/^\\s+([a-z_]+):\\s*(.*)$/i);\n if (m2) payload[m2[1]] = parseYamlScalar(m2[2]);\n i++;\n }\n out.payload = payload;\n continue;\n }\n out[key] = parseYamlScalar(rhs);\n i++;\n }\n return out;\n}\n\nfunction parseYamlScalar(s: string): unknown {\n const v = s.trim();\n if (v === \"\") return \"\";\n if (v === \"null\") return null;\n if (v === \"true\") return true;\n if (v === \"false\") return false;\n if (/^-?\\d+(\\.\\d+)?$/.test(v)) return Number(v);\n if (v.startsWith(\"[\") && v.endsWith(\"]\")) {\n try {\n return JSON.parse(v);\n } catch {\n /* fall through */\n }\n }\n if ((v.startsWith('\"') && v.endsWith('\"')) || (v.startsWith(\"'\") && v.endsWith(\"'\"))) {\n try {\n return JSON.parse(v.replace(/^'|'$/g, '\"'));\n } catch {\n return v.slice(1, -1);\n }\n }\n return v;\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/telemetry.ts"],"sourcesContent":["import { appendFile, readFile, mkdir } from \"node:fs/promises\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { deriveSessionDir } from \"./session.js\";\n\n/**\n * Selection-logging telemetry (v1.3.1).\n *\n * Records what retrieval actually returned and which note the answer used, so a\n * later release (v1.4 usage-feedback ranking) can learn which knowledge is\n * load-bearing. Strictly local, append-only JSONL, fire-and-forget, opt-out.\n *\n * NO NETWORK: this module performs local filesystem I/O only, and never opens a\n * socket or issues a remote request — a unit test greps this file to keep it that way.\n */\n\nconst LOG_FILE = \"selection.jsonl\";\nconst CORRELATION_WINDOW_MS = 60_000;\n\nexport interface SearchEvent {\n kind: \"search\";\n tool: string;\n query: string;\n results: { path: string; score: number; decay?: number }[];\n}\n\nexport interface SelectionEvent {\n kind: \"selection\";\n note_path: string;\n via: string;\n /** True when this selection followed a recent search that returned this path. */\n correlated?: boolean;\n}\n\nexport type TelemetryEvent = (SearchEvent | SelectionEvent) & { ts?: string };\n\n/** Absolute path to the selection log for a given vault. */\nexport function selectionLogPath(vaultPath: string): string {\n return join(deriveSessionDir(vaultPath), LOG_FILE);\n}\n\n/**\n * Whether telemetry is enabled. Reads `telemetry.enabled` from vault.schema.yml;\n * defaults true (opt-out, not opt-in). Fails open to enabled only if the schema\n * is unreadable — a missing `telemetry` block means \"use the default (true)\".\n */\nexport function telemetryEnabled(vaultPath: string): boolean {\n try {\n const schemaPath = join(vaultPath, \"vault.schema.yml\");\n if (!existsSync(schemaPath)) return true;\n const parsed = parseYaml(readFileSync(schemaPath, \"utf-8\")) as { telemetry?: { enabled?: boolean } } | null;\n const e = parsed?.telemetry?.enabled;\n return typeof e === \"boolean\" ? e : true;\n } catch {\n return true;\n }\n}\n\n/**\n * Append one event. Fire-and-forget: respects the opt-out and NEVER throws — a\n * telemetry failure must not affect the tool call that triggered it.\n */\nexport async function appendEvent(vaultPath: string, event: TelemetryEvent, nowIso?: string): Promise<void> {\n try {\n if (!telemetryEnabled(vaultPath)) return;\n const dir = deriveSessionDir(vaultPath);\n await mkdir(dir, { recursive: true });\n const line = JSON.stringify({ ts: nowIso ?? new Date().toISOString(), ...event });\n await appendFile(selectionLogPath(vaultPath), line + \"\\n\", \"utf-8\");\n } catch {\n // fire-and-forget\n }\n}\n\n// --- In-process correlation ring ---\n// Remembers the most recent search's top-k paths per vault so a subsequent\n// read/synthesize can be attributed to it (best-effort, time-bounded).\n\nconst recentSearch = new Map<string, { paths: Set<string>; atMs: number }>();\n\nexport function recordSearchForCorrelation(vaultPath: string, paths: string[], nowMs?: number): void {\n recentSearch.set(vaultPath, { paths: new Set(paths), atMs: nowMs ?? Date.now() });\n}\n\nexport function wasRecentlySearched(vaultPath: string, notePath: string, nowMs?: number): boolean {\n const r = recentSearch.get(vaultPath);\n if (!r) return false;\n if ((nowMs ?? Date.now()) - r.atMs > CORRELATION_WINDOW_MS) return false;\n return r.paths.has(notePath);\n}\n\n/** Read the selection log (oldest→newest). Returns [] if absent/unreadable. */\nexport async function readSelectionLog(vaultPath: string, opts: { limit?: number } = {}): Promise<TelemetryEvent[]> {\n try {\n const p = selectionLogPath(vaultPath);\n if (!existsSync(p)) return [];\n const raw = await readFile(p, \"utf-8\");\n const events = raw\n .split(\"\\n\")\n .filter((l) => l.trim())\n .map((l) => {\n try {\n return JSON.parse(l) as TelemetryEvent;\n } catch {\n return null;\n }\n })\n .filter((e): e is TelemetryEvent => e !== null);\n return opts.limit ? events.slice(-opts.limit) : events;\n } catch {\n return [];\n }\n}\n\n/** For CLI/analysis: cited-vs-retrieved rollup over the log. */\nexport interface SelectionStats {\n searches: number;\n selections: number;\n citedPaths: Record<string, number>;\n retrievedNeverCited: string[];\n}\n\nexport async function computeSelectionStats(vaultPath: string): Promise<SelectionStats> {\n const events = await readSelectionLog(vaultPath);\n const citedPaths: Record<string, number> = {};\n const retrieved = new Set<string>();\n let searches = 0;\n let selections = 0;\n for (const e of events) {\n if (e.kind === \"search\") {\n searches++;\n for (const r of e.results) retrieved.add(r.path);\n } else if (e.kind === \"selection\") {\n selections++;\n citedPaths[e.note_path] = (citedPaths[e.note_path] ?? 0) + 1;\n }\n }\n const retrievedNeverCited = [...retrieved].filter((p) => !(p in citedPaths)).sort();\n return { searches, selections, citedPaths, retrievedNeverCited };\n}\n"],"mappings":";;;;;AAAA,SAAS,YAAY,UAAU,aAAa;AAC5C,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AACrB,SAAS,SAAS,iBAAiB;AAcnC,IAAM,WAAW;AACjB,IAAM,wBAAwB;AAoBvB,SAAS,iBAAiB,WAA2B;AAC1D,SAAO,KAAK,iBAAiB,SAAS,GAAG,QAAQ;AACnD;AAOO,SAAS,iBAAiB,WAA4B;AAC3D,MAAI;AACF,UAAM,aAAa,KAAK,WAAW,kBAAkB;AACrD,QAAI,CAAC,WAAW,UAAU,EAAG,QAAO;AACpC,UAAM,SAAS,UAAU,aAAa,YAAY,OAAO,CAAC;AAC1D,UAAM,IAAI,QAAQ,WAAW;AAC7B,WAAO,OAAO,MAAM,YAAY,IAAI;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,YAAY,WAAmB,OAAuB,QAAgC;AAC1G,MAAI;AACF,QAAI,CAAC,iBAAiB,SAAS,EAAG;AAClC,UAAM,MAAM,iBAAiB,SAAS;AACtC,UAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,UAAM,OAAO,KAAK,UAAU,EAAE,IAAI,WAAU,oBAAI,KAAK,GAAE,YAAY,GAAG,GAAG,MAAM,CAAC;AAChF,UAAM,WAAW,iBAAiB,SAAS,GAAG,OAAO,MAAM,OAAO;AAAA,EACpE,QAAQ;AAAA,EAER;AACF;AAMA,IAAM,eAAe,oBAAI,IAAkD;AAEpE,SAAS,2BAA2B,WAAmB,OAAiB,OAAsB;AACnG,eAAa,IAAI,WAAW,EAAE,OAAO,IAAI,IAAI,KAAK,GAAG,MAAM,SAAS,KAAK,IAAI,EAAE,CAAC;AAClF;AAEO,SAAS,oBAAoB,WAAmB,UAAkB,OAAyB;AAChG,QAAM,IAAI,aAAa,IAAI,SAAS;AACpC,MAAI,CAAC,EAAG,QAAO;AACf,OAAK,SAAS,KAAK,IAAI,KAAK,EAAE,OAAO,sBAAuB,QAAO;AACnE,SAAO,EAAE,MAAM,IAAI,QAAQ;AAC7B;AAGA,eAAsB,iBAAiB,WAAmB,OAA2B,CAAC,GAA8B;AAClH,MAAI;AACF,UAAM,IAAI,iBAAiB,SAAS;AACpC,QAAI,CAAC,WAAW,CAAC,EAAG,QAAO,CAAC;AAC5B,UAAM,MAAM,MAAM,SAAS,GAAG,OAAO;AACrC,UAAM,SAAS,IACZ,MAAM,IAAI,EACV,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,EACtB,IAAI,CAAC,MAAM;AACV,UAAI;AACF,eAAO,KAAK,MAAM,CAAC;AAAA,MACrB,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF,CAAC,EACA,OAAO,CAAC,MAA2B,MAAM,IAAI;AAChD,WAAO,KAAK,QAAQ,OAAO,MAAM,CAAC,KAAK,KAAK,IAAI;AAAA,EAClD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAUA,eAAsB,sBAAsB,WAA4C;AACtF,QAAM,SAAS,MAAM,iBAAiB,SAAS;AAC/C,QAAM,aAAqC,CAAC;AAC5C,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,WAAW;AACf,MAAI,aAAa;AACjB,aAAW,KAAK,QAAQ;AACtB,QAAI,EAAE,SAAS,UAAU;AACvB;AACA,iBAAW,KAAK,EAAE,QAAS,WAAU,IAAI,EAAE,IAAI;AAAA,IACjD,WAAW,EAAE,SAAS,aAAa;AACjC;AACA,iBAAW,EAAE,SAAS,KAAK,WAAW,EAAE,SAAS,KAAK,KAAK;AAAA,IAC7D;AAAA,EACF;AACA,QAAM,sBAAsB,CAAC,GAAG,SAAS,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK;AAClF,SAAO,EAAE,UAAU,YAAY,YAAY,oBAAoB;AACjE;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/schema.ts","../src/core/schema-default.ts"],"sourcesContent":["import { readFile, writeFile, access } from \"node:fs/promises\";\nimport { constants } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport matter from \"gray-matter\";\nimport { DEFAULT_SCHEMA_YAML } from \"./schema-default.js\";\n\nexport interface VaultSchema {\n version: number;\n provenance_fields: string[];\n status_enum: string[];\n confidence_enum: string[];\n severity_enum: string[];\n types: Record<string, { description?: string; required: string[] }>;\n lint: {\n missing_provenance: { applies_to: string[]; severity: \"warn\" | \"error\" };\n stale: { max_age_days: number; severity: \"warn\" | \"error\" };\n schema_violations: { severity: \"warn\" | \"error\" };\n };\n}\n\nexport type Severity = \"warn\" | \"error\";\n\nexport interface LintFinding {\n path: string;\n rule: \"missing_provenance\" | \"stale\" | \"schema_violations\" | \"broken_links\" | \"code_symbols\" | \"decay_candidates\" | \"alias_conflicts\" | \"decoys\";\n severity: Severity;\n message: string;\n}\n\nexport async function loadSchema(vaultPath: string): Promise<VaultSchema> {\n const schemaPath = join(vaultPath, \"vault.schema.yml\");\n let raw: string;\n try {\n await access(schemaPath, constants.R_OK);\n raw = await readFile(schemaPath, \"utf-8\");\n } catch {\n raw = DEFAULT_SCHEMA_YAML;\n }\n const parsed = parseYaml(raw) as VaultSchema;\n return parsed;\n}\n\n/**\n * Bootstrap the schema file in a vault by writing the default if none exists.\n * Used by the `--install-schema` CLI flag. Returns the path that was (or would be) written.\n */\nexport async function installDefaultSchema(vaultPath: string, force = false): Promise<{ written: boolean; path: string }> {\n const schemaPath = join(vaultPath, \"vault.schema.yml\");\n if (!force) {\n try {\n await access(schemaPath, constants.F_OK);\n return { written: false, path: schemaPath };\n } catch {\n // not present → write it\n }\n }\n await writeFile(schemaPath, DEFAULT_SCHEMA_YAML, \"utf-8\");\n return { written: true, path: schemaPath };\n}\n\n/**\n * Validate a single note's frontmatter + content against the schema.\n * Returns findings (possibly empty). Does NOT throw — lint never throws.\n */\nexport function validateNote(\n relativePath: string,\n rawFileContent: string,\n schema: VaultSchema,\n opts: { todayIso?: string } = {}\n): LintFinding[] {\n const findings: LintFinding[] = [];\n const { data: fm } = matter(rawFileContent);\n\n const typeName = typeof fm.type === \"string\" ? fm.type : \"note\";\n const typeDef = schema.types[typeName];\n\n if (!typeDef) {\n findings.push({\n path: relativePath,\n rule: \"schema_violations\",\n severity: schema.lint.schema_violations.severity,\n message: `unknown type \"${typeName}\" — allowed: ${Object.keys(schema.types).join(\", \")}`,\n });\n return findings; // can't validate required fields if type is unknown\n }\n\n // Required field check\n for (const field of typeDef.required) {\n if (fm[field] === undefined || fm[field] === null || fm[field] === \"\") {\n findings.push({\n path: relativePath,\n rule: \"schema_violations\",\n severity: schema.lint.schema_violations.severity,\n message: `missing required field \"${field}\" for type \"${typeName}\"`,\n });\n }\n }\n\n // Enum checks\n if (fm.status !== undefined && !schema.status_enum.includes(String(fm.status))) {\n findings.push({\n path: relativePath,\n rule: \"schema_violations\",\n severity: schema.lint.schema_violations.severity,\n message: `status \"${fm.status}\" not in enum [${schema.status_enum.join(\", \")}]`,\n });\n }\n if (fm.confidence !== undefined && !schema.confidence_enum.includes(String(fm.confidence))) {\n findings.push({\n path: relativePath,\n rule: \"schema_violations\",\n severity: schema.lint.schema_violations.severity,\n message: `confidence \"${fm.confidence}\" not in enum [${schema.confidence_enum.join(\", \")}]`,\n });\n }\n if (typeName === \"gotcha\" && fm.severity !== undefined && !schema.severity_enum.includes(String(fm.severity))) {\n findings.push({\n path: relativePath,\n rule: \"schema_violations\",\n severity: schema.lint.schema_violations.severity,\n message: `severity \"${fm.severity}\" not in enum [${schema.severity_enum.join(\", \")}]`,\n });\n }\n\n // Missing provenance\n if (schema.lint.missing_provenance.applies_to.includes(typeName)) {\n const hasSources = Array.isArray(fm.sources) ? fm.sources.length > 0 : typeof fm.sources === \"string\" && fm.sources.length > 0;\n const hasDerived = Array.isArray(fm.derived_from) ? fm.derived_from.length > 0 : typeof fm.derived_from === \"string\" && fm.derived_from.length > 0;\n if (!hasSources && !hasDerived) {\n findings.push({\n path: relativePath,\n rule: \"missing_provenance\",\n severity: schema.lint.missing_provenance.severity,\n message: `no sources or derived_from on ${typeName}`,\n });\n }\n }\n\n // Stale\n if (schema.lint.stale.max_age_days > 0 && fm.last_verified) {\n const today = opts.todayIso ?? new Date().toISOString().slice(0, 10);\n const verified = String(fm.last_verified);\n const ageDays = dayDiff(today, verified);\n if (ageDays !== null && ageDays > schema.lint.stale.max_age_days) {\n findings.push({\n path: relativePath,\n rule: \"stale\",\n severity: schema.lint.stale.severity,\n message: `last_verified ${verified} is ${ageDays} days old (limit ${schema.lint.stale.max_age_days})`,\n });\n }\n }\n\n return findings;\n}\n\nfunction dayDiff(todayIso: string, thenIso: string): number | null {\n const a = Date.parse(todayIso);\n const b = Date.parse(thenIso);\n if (Number.isNaN(a) || Number.isNaN(b)) return null;\n return Math.round((a - b) / 86400000);\n}\n","// Default vault schema shipped with semantic-sidekick.\n// Intentionally minimal — the plan is to grow from pain, not preempt it.\n// Users can override with a vault.schema.yml at the vault root.\n\nexport const DEFAULT_SCHEMA_YAML = `# semantic-sidekick vault schema (minimal starter).\n# Override by placing a vault.schema.yml at the vault root.\n\nversion: 1\n\n# Provenance fields — apply to all types, enforced by lint.find_missing_provenance.\n# sources: URLs or filesystem paths the note was derived from\n# derived_from: wikilinks to other notes this one synthesizes\n# last_verified: ISO date this note was last confirmed true (YYYY-MM-DD).\n# Set at creation and by verify_note; NOT bumped on ordinary edits.\n# Drives confidence decay (see the decay: block below).\n# evergreen: true → the note is pinned against decay while last_verified is\n# within 365 days; after that it decays normally (re-affirm to reset).\n# status: one of: draft | active | stale | deprecated | archived\n# confidence: one of: high | medium | low\nprovenance_fields:\n - sources\n - derived_from\n - last_verified\n - status\n - confidence\n\n# Allowed status values (enforced by schema_violations lint).\nstatus_enum: [draft, active, stale, deprecated, archived]\nconfidence_enum: [high, medium, low]\n\n# Note types. Each type declares its required frontmatter.\n# Any field not listed as required is allowed but not enforced.\ntypes:\n note:\n description: Generic note — default type when \\`type\\` frontmatter is absent\n required: [title, status]\n\n decision:\n description: Architectural or process decision with dated rationale\n required: [title, status, decided_on, decision_maker]\n\n gotcha:\n description: Compressed lesson from a past incident/bug/surprise\n required: [title, status, last_verified, severity]\n\n source:\n description: External reference (paper, blog post, doc) used as provenance\n required: [title, status, source]\n\n alias:\n description: Learned human→artifact vocabulary entry (v1.4 lexicon). Maps the human's phrasing to a concrete path/symbol.\n required: [canonical]\n\n dossier:\n description: Entity-centric living note (v1.5). One per critical component — purpose, failure modes, knobs, incident log, current state. Accretes in place.\n required: [title, status, entity]\n\n profile:\n description: Speaker profile (v1.5). Models how THIS human communicates — severity calibration, chronic omissions, verbosity, shorthand. One human, one profile.\n required: [title, status]\n\n# Severity values for gotcha type.\nseverity_enum: [low, medium, high, critical]\n\n# Lint policy. Each rule has a default severity that pre-commit/CI consumes.\nlint:\n missing_provenance:\n # Triggers when a note type in this list has no sources AND no derived_from.\n applies_to: [note, decision, gotcha]\n severity: warn\n stale:\n # Triggers when last_verified is older than this many days (0 = disabled).\n max_age_days: 180\n severity: warn\n schema_violations:\n # Triggers on missing required fields, unknown type, or enum mismatch.\n severity: error\n\n# Confidence decay (v1.3). Down-weights search results by time since last_verified.\n# Composes multiplicatively with load_priority — it never removes notes, only ranks\n# them lower (floored). Set enabled: false for byte-identical pre-v1.3 ranking.\ndecay:\n enabled: true\n default_half_life_days: 365 # multiplier = 0.5 ^ (age_days / half_life)\n per_type:\n decision: 365\n note: 365\n gotcha: 180\n source: null # null = never decays\n proposal: 14\n floor: 0.1 # notes never drop below this multiplier\n hotness_boost:\n enabled: false # extend half-life for heavily-backlinked notes (opt-in)\n cap: 2.0\n\n# Path-class ranking (v1.4). Down-weights whole regions of the vault by path glob\n# (composes multiplicatively with decay + load_priority). First matching rule wins.\n# The default down-weights archived notes so retired docs stop dominating results.\npath_class:\n enabled: true\n rules:\n - glob: \"archive/**\"\n multiplier: 0.3\n\n# Usage-feedback ranking (v1.5). Boosts notes that get CITED after retrieval (per\n# the local selection log) so load-bearing knowledge floats up. Bounded by cap to\n# stop a feedback runaway; composes multiplicatively with decay + path_class +\n# load_priority. A no-op until the selection log has citations. The complementary\n# signal (retrieved-but-never-cited \"decoys\") is surfaced by lint_vault, NEVER\n# auto-down-ranked. Set enabled: false for byte-identical pre-v1.5 ranking.\nusage_boost:\n enabled: true\n cap: 1.5 # max multiplier a note can earn from citations\n per_citation: 0.1 # multiplier += per_citation × citation_count, then capped\n\n# Token-frugal tool surface (v1.4). When enabled, the server registers a reduced\n# tool set in modes that don't need the full surface (outage-silence → search/read\n# only). DEFAULT OFF — mid-session mode switches don't re-register, so opt in and\n# dogfood before relying on it.\ntools:\n conditional: false\n\n# Selection-logging telemetry (v1.3.1). Records what search returned and which\n# note the answer used, to a LOCAL append-only JSONL at\n# .claude/.semantic-memory/selection.jsonl (gitignored). Never leaves the machine.\n# Feeds the decay_candidates lint and (later) usage-feedback ranking. Set\n# enabled: false to disable all logging.\ntelemetry:\n enabled: true\n`;\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/session.ts"],"sourcesContent":["import { mkdir, readFile, writeFile, rm } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { spawn } from \"node:child_process\";\n\nexport interface VerificationRecord {\n cmd: string;\n exit: number | null;\n signal: string | null;\n duration_ms: number;\n tail: string;\n at: string;\n}\n\nexport interface SessionState {\n id: string;\n task: string;\n started_at: string;\n last_activity_at: string;\n verifications: VerificationRecord[];\n notes_touched: string[];\n closed_at?: string;\n closed_summary?: string;\n closed_verified_waived?: boolean;\n closed_waiver_reason?: string;\n}\n\nexport type SessionStatus =\n | { state: \"no_session\" }\n | (SessionState & { state: \"active\" | \"stale\" });\n\nexport interface SessionStartOptions {\n /** When true and a session for the same task is already open, return the existing id rather than failing. Defaults to true. */\n reuseSameTask?: boolean;\n}\n\nexport interface SessionFinishInput {\n summary: string;\n /**\n * When false, the caller is explicitly waiving verification (e.g. for doc-only edits).\n * Requires a non-empty `reason`. When true (default) and no verifications have been\n * recorded, the call is refused.\n */\n verified?: boolean;\n reason?: string;\n}\n\nconst STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\nconst TAIL_BYTES = 4096;\n\n/**\n * SessionManager owns a single session.json file at `<sessionDir>/session.json`.\n *\n * State is durable across MCP server restarts (the file persists). At most one session\n * is active per project at a time — concurrent sessions are explicitly out of scope\n * for v1.1; brain has the same constraint.\n *\n * Hard gates:\n * - session_finish refuses without verifications unless verified=false + reason\n * - session_run refuses without an open session\n * - session_start refuses if another session is open with a different task\n *\n * Stale detection: a session whose last_activity_at is >24h old is reported as\n * state:\"stale\" by status(). Stale sessions can still be finished or extended; the\n * label is informational so callers can decide whether to abandon (rm + start new) or\n * resume.\n */\nexport class SessionManager {\n constructor(private sessionDir: string) {}\n\n get sessionPath(): string {\n return join(this.sessionDir, \"session.json\");\n }\n\n async start(task: string, opts: SessionStartOptions = {}): Promise<{ ok: true; id: string; reused?: boolean } | { ok: false; error: string }> {\n if (!task || task.trim().length === 0) {\n return { ok: false, error: \"session_start requires a non-empty task.\" };\n }\n await mkdir(this.sessionDir, { recursive: true });\n const existing = await this.read();\n if (existing && !existing.closed_at) {\n const reuseSameTask = opts.reuseSameTask !== false;\n if (reuseSameTask && existing.task === task) {\n return { ok: true, id: existing.id, reused: true };\n }\n return {\n ok: false,\n error: `Session already open: id=${existing.id}, task=${JSON.stringify(existing.task)}. Close it with session_finish first.`,\n };\n }\n const now = new Date().toISOString();\n const id = `ses_${now.replace(/[:.]/g, \"-\")}`;\n const state: SessionState = {\n id,\n task: task.trim(),\n started_at: now,\n last_activity_at: now,\n verifications: [],\n notes_touched: [],\n };\n await writeFile(this.sessionPath, JSON.stringify(state, null, 2), \"utf-8\");\n return { ok: true, id };\n }\n\n async status(): Promise<SessionStatus> {\n const state = await this.read();\n if (!state || state.closed_at) return { state: \"no_session\" };\n const age = Date.now() - new Date(state.last_activity_at).getTime();\n const label: \"active\" | \"stale\" = age > STALE_THRESHOLD_MS ? \"stale\" : \"active\";\n return { ...state, state: label };\n }\n\n async run(cmd: string, timeoutMs?: number): Promise<{ ok: true; record: VerificationRecord } | { ok: false; error: string }> {\n const state = await this.read();\n if (!state || state.closed_at) {\n return { ok: false, error: \"No active session. Run session_start first.\" };\n }\n if (!cmd || cmd.trim().length === 0) {\n return { ok: false, error: \"session_run requires a non-empty command.\" };\n }\n const start = Date.now();\n const record = await this.spawnAndCapture(cmd, timeoutMs ?? DEFAULT_TIMEOUT_MS);\n record.duration_ms = Date.now() - start;\n state.verifications.push(record);\n state.last_activity_at = new Date().toISOString();\n await writeFile(this.sessionPath, JSON.stringify(state, null, 2), \"utf-8\");\n return { ok: true, record };\n }\n\n async finish(input: SessionFinishInput): Promise<{ ok: true; closed: SessionState } | { ok: false; error: string }> {\n const state = await this.read();\n if (!state || state.closed_at) {\n return { ok: false, error: \"No active session to finish.\" };\n }\n const explicitWaiver = input.verified === false;\n if (!explicitWaiver && state.verifications.length === 0) {\n return {\n ok: false,\n error:\n \"session_finish refused: no verification commands recorded. Run session_run with a real verification command (tests, lint, etc.) first, OR pass verified=false with a reason to waive verification (e.g. for doc-only edits).\",\n };\n }\n if (explicitWaiver && (!input.reason || input.reason.trim().length === 0)) {\n return {\n ok: false,\n error: \"session_finish refused: verified=false requires a non-empty `reason` explaining why verification is not applicable to this task.\",\n };\n }\n if (!input.summary || input.summary.trim().length === 0) {\n return { ok: false, error: \"session_finish requires a non-empty `summary`.\" };\n }\n const closed: SessionState = {\n ...state,\n closed_at: new Date().toISOString(),\n closed_summary: input.summary.trim(),\n ...(explicitWaiver && {\n closed_verified_waived: true,\n closed_waiver_reason: input.reason!.trim(),\n }),\n };\n // Sessions are ephemeral runtime state — finished sessions are removed from disk.\n // Durable artifacts (synthesize_note proposals, log entries) capture what mattered.\n await rm(this.sessionPath, { force: true });\n return { ok: true, closed };\n }\n\n /**\n * Append paths to notes_touched on the active session, deduplicating. No-op when no\n * session is active. Used by patch tools to record what the session edited.\n */\n async recordNotesTouched(paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n const state = await this.read();\n if (!state || state.closed_at) return;\n const set = new Set(state.notes_touched);\n for (const p of paths) set.add(p);\n state.notes_touched = Array.from(set);\n state.last_activity_at = new Date().toISOString();\n await writeFile(this.sessionPath, JSON.stringify(state, null, 2), \"utf-8\");\n }\n\n /**\n * Read the active session WITHOUT computing the stale label. Returns null when no\n * session is active or when the file is unparseable.\n */\n async readActive(): Promise<SessionState | null> {\n const state = await this.read();\n if (!state || state.closed_at) return null;\n return state;\n }\n\n private async read(): Promise<SessionState | null> {\n if (!existsSync(this.sessionPath)) return null;\n try {\n const raw = await readFile(this.sessionPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (typeof parsed?.id !== \"string\") return null;\n return parsed as SessionState;\n } catch {\n return null;\n }\n }\n\n private spawnAndCapture(cmd: string, timeoutMs: number): Promise<VerificationRecord> {\n return new Promise((resolve) => {\n const proc = spawn(cmd, { shell: true });\n let buf = \"\";\n let killedByTimeout = false;\n const timer = setTimeout(() => {\n killedByTimeout = true;\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n try { proc.kill(\"SIGKILL\"); } catch { /* already gone */ }\n }, 1000).unref();\n }, timeoutMs);\n\n const onData = (chunk: Buffer) => {\n buf += chunk.toString(\"utf-8\");\n if (buf.length > TAIL_BYTES * 4) {\n buf = buf.slice(buf.length - TAIL_BYTES * 2);\n }\n };\n proc.stdout?.on(\"data\", onData);\n proc.stderr?.on(\"data\", onData);\n\n proc.on(\"close\", (code, signal) => {\n clearTimeout(timer);\n const tail = buf.slice(-TAIL_BYTES);\n resolve({\n cmd,\n exit: killedByTimeout ? null : code,\n signal: killedByTimeout ? \"SIGTERM (timeout)\" : (signal ?? null),\n duration_ms: 0,\n tail,\n at: new Date().toISOString(),\n });\n });\n\n proc.on(\"error\", (err) => {\n clearTimeout(timer);\n const tail = buf + `\\n[spawn error: ${err.message}]`;\n resolve({\n cmd,\n exit: null,\n signal: null,\n duration_ms: 0,\n tail: tail.slice(-TAIL_BYTES),\n at: new Date().toISOString(),\n });\n });\n });\n }\n}\n\n/**\n * Heuristic: derive the project root from the vault path. If the vault path ends in\n * `.claude/.vault`, the project root is two levels up. Otherwise the vault path is\n * treated as the project root.\n */\nexport function deriveProjectRoot(notesPath: string): string {\n const norm = notesPath.replace(/[\\\\/]+$/, \"\");\n if (norm.endsWith(\"/.claude/.vault\") || norm.endsWith(\"\\\\.claude\\\\.vault\")) {\n return norm.slice(0, -\".claude/.vault\".length).replace(/[\\\\/]+$/, \"\");\n }\n return norm;\n}\n\nexport function deriveSessionDir(notesPath: string): string {\n return join(deriveProjectRoot(notesPath), \".claude\", \".semantic-memory\");\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/indexer.ts"],"sourcesContent":["import { unified } from \"unified\";\nimport remarkParse from \"remark-parse\";\nimport remarkWikiLink from \"remark-wiki-link\";\nimport matter from \"gray-matter\";\nimport { glob } from \"glob\";\nimport { readFile, writeFile, stat } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { basename, join, relative } from \"node:path\";\nimport type { IndexedDocument } from \"./types.js\";\n\nconst DOCS_CACHE_FILE = \"docs.cache.json\";\n\nconst CHUNK_TARGET_CHARS = 2000; // ~512 tokens\n\nexport class Indexer {\n private notesPath: string;\n private processor: any;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n this.processor = unified().use(remarkParse).use(remarkWikiLink);\n }\n\n async indexAll(): Promise<IndexedDocument[]> {\n // follow: true descends into symlinked directories. Without this, users\n // who symlink an external folder into their vault (a common pattern for\n // sharing notes across projects) get silently empty indexing for the\n // symlinked content. glob does cycle detection via visited inodes so\n // circular symlinks are handled safely.\n const files = await glob(\"**/*.md\", { cwd: this.notesPath, follow: true });\n const docs = await Promise.all(\n files.map((file) => this.indexFile(join(this.notesPath, file), file))\n );\n return docs;\n }\n\n /**\n * Load parsed-doc cache from disk. Returns null if missing or unreadable.\n * The cache is a verbatim JSON dump of a prior indexAll() result, written by\n * saveDocsCache. Consumers (search CLI / hook path) prefer this over re-parsing\n * 500+ markdown files; the tradeoff is that cache may be stale if the vault\n * changed since the last reindex — same staleness window as the vector cache.\n */\n async loadDocsCache(indexPath: string): Promise<IndexedDocument[] | null> {\n const p = join(indexPath, DOCS_CACHE_FILE);\n if (!existsSync(p)) return null;\n try {\n const raw = await readFile(p, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return null;\n return parsed as IndexedDocument[];\n } catch {\n return null;\n }\n }\n\n async saveDocsCache(indexPath: string, docs: IndexedDocument[]): Promise<void> {\n const p = join(indexPath, DOCS_CACHE_FILE);\n await writeFile(p, JSON.stringify(docs), \"utf-8\");\n }\n\n async indexFile(\n absolutePath: string,\n relativePath: string\n ): Promise<IndexedDocument> {\n const [raw, fileStat] = await Promise.all([\n readFile(absolutePath, \"utf-8\"),\n stat(absolutePath),\n ]);\n const { data: frontmatter, content } = matter(raw);\n const tree = this.processor.parse(content);\n\n const wikilinks = this.extractWikilinks(tree);\n const tags = this.extractTags(content, frontmatter);\n const headers = this.extractHeaders(tree);\n const plainText = this.stripMarkdown(content);\n const chunks = this.chunkText(plainText);\n\n // Symptom-keyed indexing (v1.4 Phase 5). Each verbatim `symptoms:` phrase is\n // added as its own chunk, so a terse symptom query (\"it hangs after reindex\")\n // matches the note directly — the fix for asymmetric retrieval (query-shaped\n // keys stored alongside document-shaped content). Synthetic child chunks point\n // at the same note path; no index-format change.\n const symptoms = Array.isArray(frontmatter.symptoms)\n ? (frontmatter.symptoms as unknown[]).filter((s): s is string => typeof s === \"string\" && s.trim().length > 0).map((s) => s.trim())\n : [];\n const allChunks = symptoms.length > 0 ? [...chunks, ...symptoms] : chunks;\n\n const title =\n (frontmatter.title as string) ||\n headers[0] ||\n basename(relativePath, \".md\");\n\n // Resolve modification time: prefer frontmatter date fields over fs.stat\n // Supports hit-em-with-the-docs (last_updated) and common alternatives\n const mtime = this.resolveMtime(frontmatter, fileStat.mtime);\n\n // Optional hit-em-with-the-docs fields (only populated when present)\n const loadPriority =\n typeof frontmatter.load_priority === \"number\"\n ? Math.min(10, Math.max(1, frontmatter.load_priority))\n : undefined;\n const status =\n typeof frontmatter.status === \"string\" ? frontmatter.status : undefined;\n const tier =\n typeof frontmatter.tier === \"string\" ? frontmatter.tier : undefined;\n const domains = Array.isArray(frontmatter.domains)\n ? (frontmatter.domains as string[])\n : undefined;\n const purpose =\n typeof frontmatter.purpose === \"string\" ? frontmatter.purpose : undefined;\n const relatedDocs = Array.isArray(frontmatter.related_docs)\n ? (frontmatter.related_docs as string[]).filter((v) => typeof v === \"string\")\n : undefined;\n\n return {\n path: relativePath,\n title,\n content: plainText,\n frontmatter,\n wikilinks,\n tags,\n headers,\n chunks: allChunks,\n mtime,\n ...(loadPriority !== undefined && { loadPriority }),\n ...(status !== undefined && { status }),\n ...(tier !== undefined && { tier }),\n ...(domains !== undefined && { domains }),\n ...(purpose !== undefined && { purpose }),\n ...(relatedDocs !== undefined && { relatedDocs }),\n };\n }\n\n /**\n * Resolve the best available modification date for a document.\n * Priority: last_updated → updated → date → lastmod → fs.stat mtime\n * Accepts YYYY-MM-DD strings or full ISO timestamps.\n */\n private resolveMtime(\n frontmatter: Record<string, unknown>,\n statMtime: Date\n ): string {\n const candidates = [\n frontmatter.last_updated,\n frontmatter.updated,\n frontmatter.date,\n frontmatter.lastmod,\n ];\n for (const val of candidates) {\n if (!val) continue;\n const str = val instanceof Date ? val.toISOString() : String(val);\n const parsed = new Date(str);\n if (!isNaN(parsed.getTime())) return parsed.toISOString();\n }\n return statMtime.toISOString();\n }\n\n private extractWikilinks(tree: any): string[] {\n const links: string[] = [];\n const walk = (node: any) => {\n if (node.type === \"wikiLink\") {\n links.push(node.value || node.data?.alias || \"\");\n }\n if (node.children) {\n for (const child of node.children) walk(child);\n }\n };\n walk(tree);\n return [...new Set(links.filter(Boolean))];\n }\n\n private extractTags(content: string, frontmatter: Record<string, unknown>): string[] {\n const inlineTags = [...content.matchAll(/(?:^|\\s)#([a-zA-Z][\\w-/]*)/g)].map(\n (m) => m[1]\n );\n\n const fmTags = Array.isArray(frontmatter.tags)\n ? (frontmatter.tags as string[])\n : [];\n\n return [...new Set([...fmTags, ...inlineTags])];\n }\n\n private extractHeaders(tree: any): string[] {\n const headers: string[] = [];\n const walk = (node: any) => {\n if (node.type === \"heading\") {\n const text = this.nodeToText(node);\n if (text) headers.push(text);\n }\n if (node.children) {\n for (const child of node.children) walk(child);\n }\n };\n walk(tree);\n return headers;\n }\n\n private nodeToText(node: any): string {\n if (node.type === \"text\") return node.value;\n if (node.children) return node.children.map((c: any) => this.nodeToText(c)).join(\"\");\n return \"\";\n }\n\n private stripMarkdown(content: string): string {\n return content\n .replace(/```[\\s\\S]*?```/g, \"\")\n .replace(/`[^`]+`/g, \"\")\n .replace(/!\\[.*?\\]\\(.*?\\)/g, \"\")\n .replace(/\\[([^\\]]+)\\]\\(.*?\\)/g, \"$1\")\n .replace(/#{1,6}\\s+/g, \"\")\n .replace(/[*_~]{1,3}/g, \"\")\n .replace(/>\\s+/g, \"\")\n .replace(/\\|.*\\|/g, \"\")\n .replace(/-{3,}/g, \"\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n }\n\n chunkText(text: string): string[] {\n if (text.length <= CHUNK_TARGET_CHARS) return [text];\n\n const sentences = text.match(/[^.!?\\n]+[.!?\\n]+|[^.!?\\n]+$/g) || [text];\n const chunks: string[] = [];\n let current = \"\";\n\n for (const sentence of sentences) {\n if (current.length + sentence.length > CHUNK_TARGET_CHARS && current) {\n chunks.push(current.trim());\n current = \"\";\n }\n current += sentence;\n }\n if (current.trim()) chunks.push(current.trim());\n\n return chunks;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
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":[]}
@@ -1,19 +0,0 @@
1
- import {
2
- filterToDrift,
3
- formatDriftBanner,
4
- runHealthcheck
5
- } from "./chunk-2EELGTLX.js";
6
- import "./chunk-FM26NFTR.js";
7
- import "./chunk-NTYYS53O.js";
8
- import "./chunk-A3KYHKB5.js";
9
- import "./chunk-WSU4QIPB.js";
10
- import "./chunk-S4KW7VH4.js";
11
- import "./chunk-OWZWMB4P.js";
12
- import "./chunk-5ZKNO6ZD.js";
13
- import "./chunk-PZ5AY32C.js";
14
- export {
15
- filterToDrift,
16
- formatDriftBanner,
17
- runHealthcheck
18
- };
19
- //# sourceMappingURL=healthcheck-IMB47LZE.js.map
@@ -1,8 +0,0 @@
1
- import {
2
- Indexer
3
- } from "./chunk-V45NJIHI.js";
4
- import "./chunk-PZ5AY32C.js";
5
- export {
6
- Indexer
7
- };
8
- //# sourceMappingURL=indexer-YW4O3VVM.js.map
@@ -1,16 +0,0 @@
1
- import {
2
- formatLintReport,
3
- lintVault
4
- } from "./chunk-FM26NFTR.js";
5
- import "./chunk-NTYYS53O.js";
6
- import "./chunk-A3KYHKB5.js";
7
- import "./chunk-WSU4QIPB.js";
8
- import "./chunk-S4KW7VH4.js";
9
- import "./chunk-OWZWMB4P.js";
10
- import "./chunk-5ZKNO6ZD.js";
11
- import "./chunk-PZ5AY32C.js";
12
- export {
13
- formatLintReport,
14
- lintVault
15
- };
16
- //# sourceMappingURL=lint-XUQ7JDYX.js.map
@@ -1,10 +0,0 @@
1
- import {
2
- logEvent,
3
- logQuery
4
- } from "./chunk-JPQHNG3R.js";
5
- import "./chunk-PZ5AY32C.js";
6
- export {
7
- logEvent,
8
- logQuery
9
- };
10
- //# sourceMappingURL=log-LQXIJXIA.js.map