@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +43 -0
- package/README.md +4 -0
- package/dist/{chunk-25EIQWXL.js → chunk-44BQDRXD.js} +5 -5
- package/dist/chunk-44BQDRXD.js.map +1 -0
- package/dist/{chunk-2EELGTLX.js → chunk-D5QLNNA4.js} +2 -2
- package/dist/{chunk-A3KYHKB5.js → chunk-FTRXH2WC.js} +8 -4
- package/dist/chunk-FTRXH2WC.js.map +1 -0
- package/dist/{chunk-OWZWMB4P.js → chunk-FURCIJBO.js} +1 -1
- package/dist/chunk-FURCIJBO.js.map +1 -0
- package/dist/{chunk-V45NJIHI.js → chunk-IGHCVAAL.js} +19 -4
- package/dist/chunk-IGHCVAAL.js.map +1 -0
- package/dist/{chunk-S4KW7VH4.js → chunk-LGQPP5FQ.js} +8 -3
- package/dist/chunk-LGQPP5FQ.js.map +1 -0
- package/dist/{chunk-NTYYS53O.js → chunk-NBY4TRP7.js} +7 -3
- package/dist/chunk-NBY4TRP7.js.map +1 -0
- package/dist/chunk-OEF5U5GV.js +175 -0
- package/dist/chunk-OEF5U5GV.js.map +1 -0
- package/dist/{chunk-FM26NFTR.js → chunk-OFXQXVG5.js} +26 -12
- package/dist/chunk-OFXQXVG5.js.map +1 -0
- package/dist/{chunk-CSXZ7QUU.js → chunk-ROKSUJFM.js} +27 -17
- package/dist/chunk-ROKSUJFM.js.map +1 -0
- package/dist/{chunk-JPQHNG3R.js → chunk-SX427M2R.js} +10 -4
- package/dist/chunk-SX427M2R.js.map +1 -0
- package/dist/{chunk-7M3JZOYH.js → chunk-WZEKIUYG.js} +8 -4
- package/dist/chunk-WZEKIUYG.js.map +1 -0
- package/dist/{chunk-WSU4QIPB.js → chunk-XAXK7NM4.js} +14 -8
- package/dist/chunk-XAXK7NM4.js.map +1 -0
- package/dist/cli/index.js +15 -15
- package/dist/core/index.d.ts +24 -4
- package/dist/core/index.js +20 -12
- package/dist/core/redact.d.ts +36 -0
- package/dist/core/redact.js +18 -0
- package/dist/{dossier-2UG724LB.js → dossier-RWTIMGZN.js} +4 -3
- package/dist/healthcheck-A32IWPNV.js +20 -0
- package/dist/indexer-RAKJ7JR3.js +8 -0
- package/dist/{lexicon-5PXQEMUF.js → lexicon-IVNJTMWE.js} +5 -4
- package/dist/lint-HURYGO3V.js +17 -0
- package/dist/log-YIXF6CYM.js +11 -0
- package/dist/mcp/server.js +32 -27
- package/dist/mcp/server.js.map +1 -1
- package/dist/{profile-LXUEF7TX.js → profile-UE4YVTDC.js} +5 -4
- package/dist/{schema-ATFCLC6Y.js → schema-LY6533NJ.js} +2 -2
- package/dist/{search-AHKEFI2B.js → search-ZS6OW74K.js} +3 -3
- package/dist/{telemetry-GB2TEHVE.js → telemetry-B4KP3UVE.js} +4 -3
- package/dist/telemetry-B4KP3UVE.js.map +1 -0
- package/hooks/vault-context.js +112 -58
- package/package.json +10 -10
- package/dist/chunk-25EIQWXL.js.map +0 -1
- package/dist/chunk-7M3JZOYH.js.map +0 -1
- package/dist/chunk-A3KYHKB5.js.map +0 -1
- package/dist/chunk-CSXZ7QUU.js.map +0 -1
- package/dist/chunk-FM26NFTR.js.map +0 -1
- package/dist/chunk-JPQHNG3R.js.map +0 -1
- package/dist/chunk-NTYYS53O.js.map +0 -1
- package/dist/chunk-OWZWMB4P.js.map +0 -1
- package/dist/chunk-S4KW7VH4.js.map +0 -1
- package/dist/chunk-V45NJIHI.js.map +0 -1
- package/dist/chunk-WSU4QIPB.js.map +0 -1
- package/dist/healthcheck-IMB47LZE.js +0 -19
- package/dist/indexer-YW4O3VVM.js +0 -8
- package/dist/lint-XUQ7JDYX.js +0 -16
- package/dist/log-LQXIJXIA.js +0 -10
- /package/dist/{chunk-2EELGTLX.js.map → chunk-D5QLNNA4.js.map} +0 -0
- /package/dist/{dossier-2UG724LB.js.map → core/redact.js.map} +0 -0
- /package/dist/{healthcheck-IMB47LZE.js.map → dossier-RWTIMGZN.js.map} +0 -0
- /package/dist/{indexer-YW4O3VVM.js.map → healthcheck-A32IWPNV.js.map} +0 -0
- /package/dist/{lexicon-5PXQEMUF.js.map → indexer-RAKJ7JR3.js.map} +0 -0
- /package/dist/{lint-XUQ7JDYX.js.map → lexicon-IVNJTMWE.js.map} +0 -0
- /package/dist/{log-LQXIJXIA.js.map → lint-HURYGO3V.js.map} +0 -0
- /package/dist/{profile-LXUEF7TX.js.map → log-YIXF6CYM.js.map} +0 -0
- /package/dist/{schema-ATFCLC6Y.js.map → profile-UE4YVTDC.js.map} +0 -0
- /package/dist/{telemetry-GB2TEHVE.js.map → schema-LY6533NJ.js.map} +0 -0
- /package/dist/{search-AHKEFI2B.js.map → search-ZS6OW74K.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
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\";\nimport { redact, redactDeep } from \"./redact.js\";\n\n/** `redacted`: secrets masked in the content written (never the values). */\nexport interface NoteWriteResult {\n path: string;\n redacted: number;\n}\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<NoteWriteResult> {\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 const body = redact(content);\n const fm = redactDeep(frontmatter ?? {}, \"precise\");\n // Merge auto-stamps with caller-supplied frontmatter (caller wins on conflicts)\n const stamps = computeStamps(body.text);\n const merged = { ...stamps, ...fm.value };\n const fileContent = matter.stringify(body.text, merged);\n\n await writeFile(absPath, fileContent, \"utf-8\");\n return { path: relativePath, redacted: body.count + fm.count };\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<NoteWriteResult> {\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 // only the incoming text is redacted; secrets already in the note are lint_vault's to report\n const incoming = redact(content);\n content = incoming.text;\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 return { path: relativePath, redacted: incoming.count };\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\";\nimport { redactDeep } from \"./redact.js\";\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<{ redacted: number }> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n const incoming = redactDeep(fields, \"precise\");\n\n for (const [key, value] of Object.entries(incoming.value)) {\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 return { redacted: incoming.count };\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\nconst IGNORED_DIRS = /(^|[\\\\/])(node_modules|\\.semantic-sidekick-index|\\.git)([\\\\/]|$)/;\nconst isMarkdown = (path: string) => path.endsWith(\".md\");\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 // chokidar ≥4 has no glob support: watch the folder and filter (a \"**/*.md\" target never fires)\n this.fsWatcher = watch(\".\", {\n cwd: this.notesPath,\n ignoreInitial: true,\n followSymlinks: true,\n ignored: (path, stats) => IGNORED_DIRS.test(path) || (stats?.isFile() === true && !isMarkdown(path)),\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 // `ignored` gets no stats for some events (e.g. unlink), so non-markdown can reach here\n if (!isMarkdown(path)) return;\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 /** secrets masked in this op's content; omitted when none */\n redacted?: number;\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 const { redacted } = 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, ...(redacted ? { redacted } : {}) });\n }\n for (const u of changeset.updates ?? []) {\n const abs = join(notesPath, u.path);\n const prev = await readFile(abs, \"utf-8\");\n const { redacted } = 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, ...(redacted ? { redacted } : {}) });\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","join","readFile","matter","writeFile","escapeRegex","glob","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,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
redact,
|
|
3
|
+
redactDeep
|
|
4
|
+
} from "./chunk-OEF5U5GV.js";
|
|
5
|
+
|
|
1
6
|
// src/core/log.ts
|
|
2
7
|
import { readFile, writeFile, appendFile, access } from "fs/promises";
|
|
3
8
|
import { constants } from "fs";
|
|
@@ -11,10 +16,11 @@ var HEADER = `# Vault Log
|
|
|
11
16
|
async function logEvent(vaultPath, input) {
|
|
12
17
|
const path = join(vaultPath, LOG_FILE);
|
|
13
18
|
const ts = input.ts ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
14
|
-
const
|
|
15
|
-
|
|
19
|
+
const summary = redact(input.summary).text;
|
|
20
|
+
const entry = { ts, kind: input.kind, summary };
|
|
21
|
+
if (input.payload) entry.payload = redactDeep(input.payload, "precise").value;
|
|
16
22
|
await ensureLogFile(path);
|
|
17
|
-
const line = `- ${ts} \u2014 ${input.kind}: ${
|
|
23
|
+
const line = `- ${ts} \u2014 ${input.kind}: ${summary}
|
|
18
24
|
`;
|
|
19
25
|
const block = renderYamlBlock(entry);
|
|
20
26
|
await appendFile(path, line + block + "\n", "utf-8");
|
|
@@ -144,4 +150,4 @@ export {
|
|
|
144
150
|
logEvent,
|
|
145
151
|
logQuery
|
|
146
152
|
};
|
|
147
|
-
//# sourceMappingURL=chunk-
|
|
153
|
+
//# sourceMappingURL=chunk-SX427M2R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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\";\nimport { redact, redactDeep } from \"./redact.js\";\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 // log.md is a vault note (usually committed): precise set, so paths and ids in payloads survive\n const summary = redact(input.summary).text;\n const entry: LogEntry = { ts, kind: input.kind, summary };\n if (input.payload) entry.payload = redactDeep(input.payload, \"precise\").value;\n\n await ensureLogFile(path);\n\n const line = `- ${ts} — ${input.kind}: ${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,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
appendToSection,
|
|
3
3
|
replaceSectionBody
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XAXK7NM4.js";
|
|
5
|
+
import {
|
|
6
|
+
redact
|
|
7
|
+
} from "./chunk-OEF5U5GV.js";
|
|
5
8
|
|
|
6
9
|
// src/core/profile.ts
|
|
7
10
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
@@ -89,9 +92,10 @@ async function updateProfileSection(vaultPath, section, text, mode = "append") {
|
|
|
89
92
|
const abs = join(vaultPath, SPEAKER_PROFILE_PATH);
|
|
90
93
|
const raw = await readFile(abs, "utf-8");
|
|
91
94
|
const parsed = matter(raw);
|
|
92
|
-
const
|
|
95
|
+
const incoming = redact(text.trim());
|
|
96
|
+
const patchedBody = mode === "replace" ? replaceSectionBody(parsed.content, section, incoming.text) : appendToSection(parsed.content, section, `- ${incoming.text}`);
|
|
93
97
|
await writeFile(abs, matter.stringify(patchedBody, parsed.data), "utf-8");
|
|
94
|
-
return { path: SPEAKER_PROFILE_PATH };
|
|
98
|
+
return { path: SPEAKER_PROFILE_PATH, ...incoming.count ? { redacted: incoming.count } : {} };
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
export {
|
|
@@ -102,4 +106,4 @@ export {
|
|
|
102
106
|
initSpeakerProfile,
|
|
103
107
|
updateProfileSection
|
|
104
108
|
};
|
|
105
|
-
//# sourceMappingURL=chunk-
|
|
109
|
+
//# sourceMappingURL=chunk-WZEKIUYG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/profile.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { redact } from \"./redact.js\";\nimport { replaceSectionBody, appendToSection } from \"./dossier.js\";\n\n/**\n * Speaker profile (v1.5 Phase 11) — the model of THIS human.\n *\n * The difference between an expert who knows the system and one who knows the\n * system *and knows you*: how this person calibrates severity (\"annoying\" vs\n * \"broken\" vs \"on fire\"), what they chronically leave unsaid, how much detail they\n * want back, and the shorthand they use that isn't an entity. One note, one human\n * (multi-speaker is an explicit non-goal). Injected at SessionStart alongside the\n * session digest; updated via capture cues when the user corrects an interpretation.\n */\n\nexport const SPEAKER_PROFILE_PATH = \"profile/speaker.md\";\n\n/** The fixed sections a speaker profile carries. */\nexport const PROFILE_SECTIONS = [\n \"Severity calibration\",\n \"Chronic omissions\",\n \"Verbosity preference\",\n \"Shorthand & terms\",\n] as const;\n\nexport type ProfileSection = (typeof PROFILE_SECTIONS)[number];\n\nexport interface SpeakerProfile {\n path: string;\n /** The whole note body (frontmatter stripped), trimmed. */\n body: string;\n /** A capped, injection-ready rendering (headings + non-placeholder lines). */\n head: string;\n}\n\nexport function speakerProfileTemplate(): string {\n return `# Speaker profile\n\n_How this human communicates — learned over time, corrected in place. One human, one profile._\n\n## Severity calibration\n\n_What their words map to. e.g. \"annoying\" = low, \"broken\" = ship-blocking, \"on fire\" = drop everything._\n\n## Chronic omissions\n\n_What they habitually leave unsaid — env, repro steps, which service — so you ask up front._\n\n## Verbosity preference\n\n_How much detail they want back: terse answer first, or full reasoning? Code or prose?_\n\n## Shorthand & terms\n\n_Non-entity shorthand: \"the usual\", \"ship it\", \"the thing from yesterday\" — and what each has meant._\n`;\n}\n\n/** True when a line is only a template placeholder (italic prompt) or blank. */\nfunction isPlaceholderLine(line: string): boolean {\n const t = line.trim();\n return t === \"\" || /^_.*_$/.test(t);\n}\n\n/**\n * Read the speaker profile, returning a capped injection head that drops the\n * template placeholders (so an unfilled profile injects nothing but its headings,\n * and a filled one injects only the real content). Returns null when absent.\n */\nexport async function readSpeakerProfile(vaultPath: string, maxLines = 30): Promise<SpeakerProfile | null> {\n const abs = join(vaultPath, SPEAKER_PROFILE_PATH);\n if (!existsSync(abs)) return null;\n try {\n const raw = await readFile(abs, \"utf-8\");\n const { content } = matter(raw);\n const body = content.trim();\n // Build the head: keep headings and real (non-placeholder) content lines.\n const kept: string[] = [];\n let lastWasHeading = false;\n for (const line of body.split(\"\\n\")) {\n const isHeading = /^#{1,6}\\s/.test(line);\n if (isHeading) {\n // Drop a heading immediately followed by nothing real (avoid trailing empty sections at render).\n kept.push(line.replace(/^#+\\s*/, \"\").trim());\n lastWasHeading = true;\n continue;\n }\n if (isPlaceholderLine(line)) continue;\n kept.push(line.trim());\n lastWasHeading = false;\n }\n void lastWasHeading;\n const head = kept.slice(0, maxLines).join(\"\\n\").trim();\n return { path: SPEAKER_PROFILE_PATH, body, head };\n } catch {\n return null;\n }\n}\n\nexport interface InitProfileResult {\n path: string;\n created: boolean;\n}\n\n/** Scaffold the speaker profile note. No-op (created:false) if it already exists. */\nexport async function initSpeakerProfile(vaultPath: string, today?: string): Promise<InitProfileResult> {\n const abs = join(vaultPath, SPEAKER_PROFILE_PATH);\n if (existsSync(abs)) return { path: SPEAKER_PROFILE_PATH, created: false };\n const date = today ?? new Date().toISOString().slice(0, 10);\n const fm = {\n title: \"Speaker profile\",\n type: \"profile\",\n status: \"active\",\n last_verified: date,\n confidence: \"medium\",\n evergreen: true,\n };\n await mkdir(dirname(abs), { recursive: true });\n await writeFile(abs, matter.stringify(speakerProfileTemplate(), fm), \"utf-8\");\n return { path: SPEAKER_PROFILE_PATH, created: true };\n}\n\n/**\n * Update one section of the speaker profile. `mode: \"replace\"` swaps the section\n * body; `mode: \"append\"` adds a line (accretion). Scaffolds the note first if\n * absent. Returns the resolved path.\n */\nexport async function updateProfileSection(\n vaultPath: string,\n section: string,\n text: string,\n mode: \"replace\" | \"append\" = \"append\"\n): Promise<{ path: string; redacted?: number }> {\n await initSpeakerProfile(vaultPath);\n const abs = join(vaultPath, SPEAKER_PROFILE_PATH);\n const raw = await readFile(abs, \"utf-8\");\n const parsed = matter(raw);\n const incoming = redact(text.trim());\n const patchedBody = mode === \"replace\"\n ? replaceSectionBody(parsed.content, section, incoming.text)\n : appendToSection(parsed.content, section, `- ${incoming.text}`);\n await writeFile(abs, matter.stringify(patchedBody, parsed.data), \"utf-8\");\n return { path: SPEAKER_PROFILE_PATH, ...(incoming.count ? { redacted: incoming.count } : {}) };\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,MAAM,eAAe;AAC9B,OAAO,YAAY;AAeZ,IAAM,uBAAuB;AAG7B,IAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAYO,SAAS,yBAAiC;AAC/C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBT;AAGA,SAAS,kBAAkB,MAAuB;AAChD,QAAM,IAAI,KAAK,KAAK;AACpB,SAAO,MAAM,MAAM,SAAS,KAAK,CAAC;AACpC;AAOA,eAAsB,mBAAmB,WAAmB,WAAW,IAAoC;AACzG,QAAM,MAAM,KAAK,WAAW,oBAAoB;AAChD,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO;AAC7B,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,KAAK,OAAO;AACvC,UAAM,EAAE,QAAQ,IAAI,OAAO,GAAG;AAC9B,UAAM,OAAO,QAAQ,KAAK;AAE1B,UAAM,OAAiB,CAAC;AACxB,QAAI,iBAAiB;AACrB,eAAW,QAAQ,KAAK,MAAM,IAAI,GAAG;AACnC,YAAM,YAAY,YAAY,KAAK,IAAI;AACvC,UAAI,WAAW;AAEb,aAAK,KAAK,KAAK,QAAQ,UAAU,EAAE,EAAE,KAAK,CAAC;AAC3C,yBAAiB;AACjB;AAAA,MACF;AACA,UAAI,kBAAkB,IAAI,EAAG;AAC7B,WAAK,KAAK,KAAK,KAAK,CAAC;AACrB,uBAAiB;AAAA,IACnB;AACA,SAAK;AACL,UAAM,OAAO,KAAK,MAAM,GAAG,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK;AACrD,WAAO,EAAE,MAAM,sBAAsB,MAAM,KAAK;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,mBAAmB,WAAmB,OAA4C;AACtG,QAAM,MAAM,KAAK,WAAW,oBAAoB;AAChD,MAAI,WAAW,GAAG,EAAG,QAAO,EAAE,MAAM,sBAAsB,SAAS,MAAM;AACzE,QAAM,OAAO,UAAS,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAC1D,QAAM,KAAK;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AACA,QAAM,MAAM,QAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AAC7C,QAAM,UAAU,KAAK,OAAO,UAAU,uBAAuB,GAAG,EAAE,GAAG,OAAO;AAC5E,SAAO,EAAE,MAAM,sBAAsB,SAAS,KAAK;AACrD;AAOA,eAAsB,qBACpB,WACA,SACA,MACA,OAA6B,UACiB;AAC9C,QAAM,mBAAmB,SAAS;AAClC,QAAM,MAAM,KAAK,WAAW,oBAAoB;AAChD,QAAM,MAAM,MAAM,SAAS,KAAK,OAAO;AACvC,QAAM,SAAS,OAAO,GAAG;AACzB,QAAM,WAAW,OAAO,KAAK,KAAK,CAAC;AACnC,QAAM,cAAc,SAAS,YACzB,mBAAmB,OAAO,SAAS,SAAS,SAAS,IAAI,IACzD,gBAAgB,OAAO,SAAS,SAAS,KAAK,SAAS,IAAI,EAAE;AACjE,QAAM,UAAU,KAAK,OAAO,UAAU,aAAa,OAAO,IAAI,GAAG,OAAO;AACxE,SAAO,EAAE,MAAM,sBAAsB,GAAI,SAAS,QAAQ,EAAE,UAAU,SAAS,MAAM,IAAI,CAAC,EAAG;AAC/F;","names":[]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deriveSessionDir
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LGQPP5FQ.js";
|
|
4
|
+
import {
|
|
5
|
+
redact
|
|
6
|
+
} from "./chunk-OEF5U5GV.js";
|
|
4
7
|
|
|
5
8
|
// src/core/dossier.ts
|
|
6
9
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
@@ -186,6 +189,7 @@ async function initDossier(vaultPath, entity, opts = {}) {
|
|
|
186
189
|
const abs = join(vaultPath, rel);
|
|
187
190
|
if (existsSync(abs)) return { path: rel, created: false, entity };
|
|
188
191
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
192
|
+
const purpose = redact(opts.purpose ?? "");
|
|
189
193
|
const fm = {
|
|
190
194
|
title: entity,
|
|
191
195
|
type: "dossier",
|
|
@@ -196,11 +200,11 @@ async function initDossier(vaultPath, entity, opts = {}) {
|
|
|
196
200
|
...opts.aliases && opts.aliases.length > 0 ? { aliases: opts.aliases } : {},
|
|
197
201
|
...opts.seeded_from ? { sources: [opts.seeded_from] } : {}
|
|
198
202
|
};
|
|
199
|
-
const body = dossierTemplate(entity, { purpose: opts.purpose, seeded_from: opts.seeded_from });
|
|
203
|
+
const body = dossierTemplate(entity, { purpose: opts.purpose === void 0 ? void 0 : purpose.text, seeded_from: opts.seeded_from });
|
|
200
204
|
await mkdir(join(vaultPath, DOSSIER_DIR), { recursive: true });
|
|
201
205
|
await writeFile(abs, matter.stringify(body, fm), "utf-8");
|
|
202
206
|
await compileDossiers(vaultPath);
|
|
203
|
-
return { path: rel, created: true, entity };
|
|
207
|
+
return { path: rel, created: true, entity, ...purpose.count ? { redacted: purpose.count } : {} };
|
|
204
208
|
}
|
|
205
209
|
async function appendIncident(vaultPath, entityOrPath, incident, opts = {}) {
|
|
206
210
|
const rel = await resolveDossierPath(vaultPath, entityOrPath);
|
|
@@ -208,21 +212,23 @@ async function appendIncident(vaultPath, entityOrPath, incident, opts = {}) {
|
|
|
208
212
|
const doc = await readDossierRaw(vaultPath, rel);
|
|
209
213
|
if (!doc) return null;
|
|
210
214
|
const today = opts.today ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
211
|
-
const
|
|
215
|
+
const text = redact(incident.trim());
|
|
216
|
+
const line = `- ${today} \u2014 ${text.text}`;
|
|
212
217
|
const patched = appendToSection(doc.raw, "Incident log", line);
|
|
213
218
|
await writeFile(join(vaultPath, rel), patched, "utf-8");
|
|
214
219
|
await compileDossiers(vaultPath);
|
|
215
|
-
return { path: rel };
|
|
220
|
+
return { path: rel, ...text.count ? { redacted: text.count } : {} };
|
|
216
221
|
}
|
|
217
222
|
async function setCurrentState(vaultPath, entityOrPath, state) {
|
|
218
223
|
const rel = await resolveDossierPath(vaultPath, entityOrPath);
|
|
219
224
|
if (!rel) return null;
|
|
220
225
|
const doc = await readDossierRaw(vaultPath, rel);
|
|
221
226
|
if (!doc) return null;
|
|
222
|
-
const
|
|
227
|
+
const text = redact(state.trim());
|
|
228
|
+
const patched = replaceSectionBody(doc.raw, "Current state", text.text);
|
|
223
229
|
await writeFile(join(vaultPath, rel), patched, "utf-8");
|
|
224
230
|
await compileDossiers(vaultPath);
|
|
225
|
-
return { path: rel };
|
|
231
|
+
return { path: rel, ...text.count ? { redacted: text.count } : {} };
|
|
226
232
|
}
|
|
227
233
|
function locateSection(markdown, heading) {
|
|
228
234
|
const lines = markdown.split("\n");
|
|
@@ -308,4 +314,4 @@ export {
|
|
|
308
314
|
appendToSection,
|
|
309
315
|
replaceSectionBody
|
|
310
316
|
};
|
|
311
|
-
//# sourceMappingURL=chunk-
|
|
317
|
+
//# sourceMappingURL=chunk-XAXK7NM4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/dossier.ts","../src/core/section.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\nimport { redact } from \"./redact.js\";\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 redacted?: number;\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 purpose = redact(opts.purpose ?? \"\");\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 === undefined ? undefined : purpose.text, 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, ...(purpose.count ? { redacted: purpose.count } : {}) };\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; redacted?: number } | 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 text = redact(incident.trim());\n const line = `- ${today} — ${text.text}`;\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, ...(text.count ? { redacted: text.count } : {}) };\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; redacted?: number } | 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 text = redact(state.trim());\n const patched = replaceSectionBody(doc.raw, \"Current state\", text.text);\n await writeFile(join(vaultPath, rel), patched, \"utf-8\");\n await compileDossiers(vaultPath);\n return { path: rel, ...(text.count ? { redacted: text.count } : {}) };\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":[]}
|
package/dist/cli/index.js
CHANGED
|
@@ -621,7 +621,7 @@ program.name("semantic-memory").description(
|
|
|
621
621
|
"Unified memory-layer MCP server for Claude Code (rebrand of semantic-sidekick at v1.0.0).\nThe `semantic-sidekick` command is preserved as an alias.\n\n Start MCP server: semantic-memory --notes ./vault\n Show vault stats: semantic-memory --notes ./vault --stats\n Force reindex: semantic-memory --notes ./vault --reindex\n List MCP tools: semantic-memory tools\n Tool details: semantic-memory tools search_semantic"
|
|
622
622
|
).version(version);
|
|
623
623
|
program.command("search <query>").description("Run a vault search and print top hits as JSON (used by activation hooks)").requiredOption("--notes <path>", "Path to markdown notes directory").option("--limit <n>", "Max results", (v) => parseInt(v, 10), 8).option("--text-only", "Skip embedder \u2014 keyword-only search (fast path, no model init)").option("--json", "Emit raw JSON (default)", true).action(async (query, opts) => {
|
|
624
|
-
const { runSearch } = await import("../search-
|
|
624
|
+
const { runSearch } = await import("../search-ZS6OW74K.js");
|
|
625
625
|
try {
|
|
626
626
|
const hits = await runSearch({
|
|
627
627
|
notes: opts.notes,
|
|
@@ -637,7 +637,7 @@ program.command("search <query>").description("Run a vault search and print top
|
|
|
637
637
|
}
|
|
638
638
|
});
|
|
639
639
|
program.command("lint").description("Lint the vault against vault.schema.yml (schema violations + missing provenance + stale)").requiredOption("--notes <path>", "Path to markdown notes directory").option("--rule <name>", "Limit output to one rule: schema_violations | missing_provenance | stale").option("--json", "Emit JSON instead of the human-readable report").option("--strict", "Exit non-zero on warnings too (default: exit non-zero only on errors)").action(async (opts) => {
|
|
640
|
-
const { lintVault, formatLintReport } = await import("../lint-
|
|
640
|
+
const { lintVault, formatLintReport } = await import("../lint-HURYGO3V.js");
|
|
641
641
|
const report = await lintVault(resolve2(opts.notes));
|
|
642
642
|
if (opts.json) {
|
|
643
643
|
const out = opts.rule ? report.byRule[opts.rule] : report;
|
|
@@ -649,7 +649,7 @@ program.command("lint").description("Lint the vault against vault.schema.yml (sc
|
|
|
649
649
|
process.exitCode = bad > 0 ? 1 : 0;
|
|
650
650
|
});
|
|
651
651
|
program.command("log-event").description("Append a structured event to the vault's log.md (used by hooks and CI scripts \u2014 same shape as mcp__semantic-vault__log_event)").requiredOption("--notes <path>", "Path to markdown notes directory").requiredOption("--kind <name>", "Event category: ingest, synthesis, error, mode_change, etc.").requiredOption("--summary <text>", "One-line human summary").option("--payload <json>", "Structured payload as JSON string").action(async (opts) => {
|
|
652
|
-
const { logEvent } = await import("../log-
|
|
652
|
+
const { logEvent } = await import("../log-YIXF6CYM.js");
|
|
653
653
|
let payload;
|
|
654
654
|
if (opts.payload) {
|
|
655
655
|
try {
|
|
@@ -673,7 +673,7 @@ program.command("log-event").description("Append a structured event to the vault
|
|
|
673
673
|
}
|
|
674
674
|
});
|
|
675
675
|
program.command("log-query").description("Read structured log entries from log.md \u2014 filters by kind, date range, limit").requiredOption("--notes <path>", "Path to markdown notes directory").option("--kind <name>", "Filter by kind (ingest, synthesis, error, mode_change, etc.)").option("--after <iso>", "ISO timestamp \u2014 only entries on or after").option("--before <iso>", "ISO timestamp \u2014 only entries on or before").option("--limit <n>", "Max entries (most recent)", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
676
|
-
const { logQuery } = await import("../log-
|
|
676
|
+
const { logQuery } = await import("../log-YIXF6CYM.js");
|
|
677
677
|
try {
|
|
678
678
|
const entries = await logQuery(resolve2(opts.notes), {
|
|
679
679
|
kind: opts.kind,
|
|
@@ -688,7 +688,7 @@ program.command("log-query").description("Read structured log entries from log.m
|
|
|
688
688
|
}
|
|
689
689
|
});
|
|
690
690
|
program.command("install-schema").description("Bootstrap vault.schema.yml in the vault root with the default schema").requiredOption("--notes <path>", "Path to markdown notes directory").option("--force", "Overwrite existing schema file").action(async (opts) => {
|
|
691
|
-
const { installDefaultSchema } = await import("../schema-
|
|
691
|
+
const { installDefaultSchema } = await import("../schema-LY6533NJ.js");
|
|
692
692
|
const r = await installDefaultSchema(resolve2(opts.notes), opts.force);
|
|
693
693
|
console.log(r.written ? `Installed default schema: ${r.path}` : `Schema already exists: ${r.path} (use --force to overwrite)`);
|
|
694
694
|
process.exit(0);
|
|
@@ -708,7 +708,7 @@ program.command("serve", { isDefault: true }).description("Start the MCP server
|
|
|
708
708
|
process.exit(1);
|
|
709
709
|
}
|
|
710
710
|
if (opts.stats) {
|
|
711
|
-
const { Indexer } = await import("../indexer-
|
|
711
|
+
const { Indexer } = await import("../indexer-RAKJ7JR3.js");
|
|
712
712
|
const indexer = new Indexer(notesPath);
|
|
713
713
|
const docs = await indexer.indexAll();
|
|
714
714
|
console.log(`Notes: ${docs.length}`);
|
|
@@ -869,7 +869,7 @@ ${r2.stderr?.toString() || ""}`);
|
|
|
869
869
|
}
|
|
870
870
|
}
|
|
871
871
|
if (smokeOk && !opts.json) console.log(`\u2713 local install starts cleanly (v${smokeVersion})`);
|
|
872
|
-
const { runHealthcheck, formatDriftBanner, filterToDrift } = await import("../healthcheck-
|
|
872
|
+
const { runHealthcheck, formatDriftBanner, filterToDrift } = await import("../healthcheck-A32IWPNV.js");
|
|
873
873
|
const vaultCandidate = join3(cwd, ".claude", ".vault");
|
|
874
874
|
const vaultPath = existsSync3(vaultCandidate) ? vaultCandidate : void 0;
|
|
875
875
|
let result = await runHealthcheck({ projectRoot: cwd, vaultPath, tier: opts.fast ? "fast" : "all", force: true });
|
|
@@ -1038,49 +1038,49 @@ program.command("decay-trace <notePath>").description("Show the decay calculatio
|
|
|
1038
1038
|
});
|
|
1039
1039
|
var lexiconCmd = program.command("lexicon").description("Manage the learned human\u2192artifact lexicon (aliases).");
|
|
1040
1040
|
lexiconCmd.command("list").description("List all aliases in the lexicon.").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
|
|
1041
|
-
const { loadLexiconCache } = await import("../lexicon-
|
|
1041
|
+
const { loadLexiconCache } = await import("../lexicon-IVNJTMWE.js");
|
|
1042
1042
|
console.log(JSON.stringify(await loadLexiconCache(resolve2(opts.notes)), null, 2));
|
|
1043
1043
|
process.exit(0);
|
|
1044
1044
|
});
|
|
1045
1045
|
lexiconCmd.command("compile").description("Rebuild the derived lexicon cache from the lexicon/ notes.").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
|
|
1046
|
-
const { compileLexicon } = await import("../lexicon-
|
|
1046
|
+
const { compileLexicon } = await import("../lexicon-IVNJTMWE.js");
|
|
1047
1047
|
const entries = await compileLexicon(resolve2(opts.notes));
|
|
1048
1048
|
console.log(`compiled ${entries.length} alias(es)`);
|
|
1049
1049
|
process.exit(0);
|
|
1050
1050
|
});
|
|
1051
1051
|
lexiconCmd.command("add <canonical> <phrases...>").description("Upsert an alias: a canonical target and the human phrases that refer to it.").requiredOption("--notes <path>", "Path to markdown notes directory").option("--authored", "Mark as human-authored (default: learned)").action(async (canonical, phrases, opts) => {
|
|
1052
|
-
const { addAlias } = await import("../lexicon-
|
|
1052
|
+
const { addAlias } = await import("../lexicon-IVNJTMWE.js");
|
|
1053
1053
|
const r = await addAlias(resolve2(opts.notes), { canonical, phrases, source: opts.authored ? "authored" : "learned" });
|
|
1054
1054
|
console.log(JSON.stringify(r, null, 2));
|
|
1055
1055
|
process.exit(0);
|
|
1056
1056
|
});
|
|
1057
1057
|
var dossierCmd = program.command("dossier").description("Manage entity dossiers \u2014 per-component living notes (purpose / failure modes / knobs / incident log / current state).");
|
|
1058
1058
|
dossierCmd.command("init <entity>").description("Scaffold a dossier for an entity (no-op if one already exists). Optionally seed aliases and provenance.").requiredOption("--notes <path>", "Path to markdown notes directory").option("--alias <phrase...>", "Human phrase(s) for this entity \u2014 fed to the lexicon compiler").option("--seeded-from <ref>", "Provenance of the scaffold (e.g. a babel-fish project-map entry)").action(async (entity, opts) => {
|
|
1059
|
-
const { initDossier } = await import("../dossier-
|
|
1059
|
+
const { initDossier } = await import("../dossier-RWTIMGZN.js");
|
|
1060
1060
|
const r = await initDossier(resolve2(opts.notes), entity, { aliases: opts.alias, seeded_from: opts.seededFrom });
|
|
1061
1061
|
console.log(JSON.stringify(r, null, 2));
|
|
1062
1062
|
process.exit(0);
|
|
1063
1063
|
});
|
|
1064
1064
|
dossierCmd.command("list").description("List all dossiers with their entity, aliases, and current state.").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
|
|
1065
|
-
const { listDossiers } = await import("../dossier-
|
|
1065
|
+
const { listDossiers } = await import("../dossier-RWTIMGZN.js");
|
|
1066
1066
|
console.log(JSON.stringify(await listDossiers(resolve2(opts.notes)), null, 2));
|
|
1067
1067
|
process.exit(0);
|
|
1068
1068
|
});
|
|
1069
1069
|
var profileCmd = program.command("profile").description("Manage the speaker profile \u2014 how THIS human communicates (severity calibration, chronic omissions, verbosity, shorthand).");
|
|
1070
1070
|
profileCmd.command("init").description("Scaffold profile/speaker.md with the fixed sections (no-op if it exists).").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
|
|
1071
|
-
const { initSpeakerProfile } = await import("../profile-
|
|
1071
|
+
const { initSpeakerProfile } = await import("../profile-UE4YVTDC.js");
|
|
1072
1072
|
const r = await initSpeakerProfile(resolve2(opts.notes));
|
|
1073
1073
|
console.log(JSON.stringify(r, null, 2));
|
|
1074
1074
|
process.exit(0);
|
|
1075
1075
|
});
|
|
1076
1076
|
profileCmd.command("show").description("Print the speaker profile's injection head (the capped, placeholder-stripped view).").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
|
|
1077
|
-
const { readSpeakerProfile } = await import("../profile-
|
|
1077
|
+
const { readSpeakerProfile } = await import("../profile-UE4YVTDC.js");
|
|
1078
1078
|
const p = await readSpeakerProfile(resolve2(opts.notes));
|
|
1079
1079
|
console.log(p ? p.head || "(profile is empty \u2014 only placeholders)" : "(no speaker profile \u2014 run `profile init`)");
|
|
1080
1080
|
process.exit(0);
|
|
1081
1081
|
});
|
|
1082
1082
|
program.command("selection-stats").description("Summarize the local selection log: searches, selections, most-cited notes, and retrieved-but-never-cited notes.").requiredOption("--notes <path>", "Path to markdown notes directory").option("--json", "Emit raw JSON instead of a human summary").action(async (opts) => {
|
|
1083
|
-
const { computeSelectionStats, selectionLogPath } = await import("../telemetry-
|
|
1083
|
+
const { computeSelectionStats, selectionLogPath } = await import("../telemetry-B4KP3UVE.js");
|
|
1084
1084
|
const stats = await computeSelectionStats(resolve2(opts.notes));
|
|
1085
1085
|
if (opts.json) {
|
|
1086
1086
|
console.log(JSON.stringify(stats, null, 2));
|