@theglitchking/semantic-memory 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +4 -4
- package/CHANGELOG.md +66 -0
- package/commands/contract.md +14 -0
- package/commands/healthcheck.md +21 -3
- package/dist/chunk-CCSX3BHF.js +273 -0
- package/dist/chunk-CCSX3BHF.js.map +1 -0
- package/dist/{chunk-CXTRTCIK.js → chunk-XBAN7GRT.js} +380 -6
- package/dist/chunk-XBAN7GRT.js.map +1 -0
- package/dist/cli/index.js +299 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.d.ts +276 -3
- package/dist/core/index.js +22 -2
- package/dist/healthcheck-DCFGPI7M.js +14 -0
- package/dist/healthcheck-DCFGPI7M.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +1008 -568
- package/dist/mcp/server.js.map +1 -1
- package/hooks/vault-context.js +130 -1
- package/package.json +1 -1
- package/dist/chunk-CXTRTCIK.js.map +0 -1
package/hooks/vault-context.js
CHANGED
|
@@ -305,10 +305,98 @@ async function handleSessionStart(projectRoot, vaultPath, cliBin) {
|
|
|
305
305
|
const searchBlock = hits && hits.length > 0 ? formatContextBlock("sessionstart", query, hits) : "";
|
|
306
306
|
const stateBlock = formatStateDeltaBlock(sinceIso, recentEntries);
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
// Phase 8 (drift detection): inline fast-tier check. Hard requirements: <100ms total,
|
|
309
|
+
// fail open, silent on healthy installs. Caching via mtime stat is light enough that
|
|
310
|
+
// we don't need a separate cache file for the JS-side path.
|
|
311
|
+
const driftBlock = await fastDriftCheck(projectRoot).catch((err) => {
|
|
312
|
+
debug(`drift check failed: ${err?.message || err}`);
|
|
313
|
+
return "";
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const parts = [driftBlock, stateBlock, searchBlock].filter(Boolean);
|
|
309
317
|
emit("SessionStart", parts.join("\n\n"));
|
|
310
318
|
}
|
|
311
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Fast-tier drift check for the SessionStart hook. Pure JS for zero spawn cost.
|
|
322
|
+
* Surfaces a one-block warning ONLY when at least one drift signal fires; healthy
|
|
323
|
+
* installs return "" and the hook stays silent.
|
|
324
|
+
*
|
|
325
|
+
* Checks:
|
|
326
|
+
* - .mcp.json server entry presence
|
|
327
|
+
* - hook registration in .claude/settings.json (SessionStart, UserPromptSubmit, Stop)
|
|
328
|
+
* - skill manifests across global + local agent dirs (presence + version)
|
|
329
|
+
* - AGENTS.md managed-block markers (if file exists)
|
|
330
|
+
* - session staleness (>24h since last_activity_at on an open session)
|
|
331
|
+
*
|
|
332
|
+
* Heavy work (full vault lint, code-symbol drift) is reserved for the manual
|
|
333
|
+
* `/healthcheck` command; this auto path stays cheap.
|
|
334
|
+
*/
|
|
335
|
+
async function fastDriftCheck(projectRoot) {
|
|
336
|
+
const findings = [];
|
|
337
|
+
// .mcp.json
|
|
338
|
+
try {
|
|
339
|
+
const mcpPath = join(projectRoot, ".mcp.json");
|
|
340
|
+
if (existsSync(mcpPath)) {
|
|
341
|
+
const data = JSON.parse(readFileSync(mcpPath, "utf8"));
|
|
342
|
+
const servers = (data && data.mcpServers) || {};
|
|
343
|
+
const hasEntry = Object.keys(servers).some((k) => k === "semantic-vault" || k === "semantic-sidekick" || k === "semantic-memory");
|
|
344
|
+
if (!hasEntry) findings.push({ check: "mcp_json_entry", summary: ".mcp.json has no semantic-* server entry" });
|
|
345
|
+
}
|
|
346
|
+
} catch (e) { debug(`mcp check threw: ${e.message}`); }
|
|
347
|
+
|
|
348
|
+
// hook registration
|
|
349
|
+
try {
|
|
350
|
+
const settings = join(projectRoot, ".claude", "settings.json");
|
|
351
|
+
if (existsSync(settings)) {
|
|
352
|
+
const data = JSON.parse(readFileSync(settings, "utf8"));
|
|
353
|
+
const hooks = (data && data.hooks) || {};
|
|
354
|
+
const required = ["SessionStart", "UserPromptSubmit", "Stop"];
|
|
355
|
+
const missing = required.filter((e) => !Array.isArray(hooks[e]) || hooks[e].length === 0);
|
|
356
|
+
if (missing.length > 0) findings.push({ check: "hook_registration", summary: `missing hook events: ${missing.join(", ")}` });
|
|
357
|
+
}
|
|
358
|
+
} catch (e) { debug(`hook check threw: ${e.message}`); }
|
|
359
|
+
|
|
360
|
+
// AGENTS.md managed-block (warn only when file exists but markers are absent)
|
|
361
|
+
try {
|
|
362
|
+
const agents = join(projectRoot, "AGENTS.md");
|
|
363
|
+
if (existsSync(agents)) {
|
|
364
|
+
const raw = readFileSync(agents, "utf8");
|
|
365
|
+
if (!raw.includes("<!-- semantic-memory:begin contract -->") || !raw.includes("<!-- semantic-memory:end contract -->")) {
|
|
366
|
+
findings.push({ check: "agents_contract", summary: "AGENTS.md exists but lacks managed-block markers" });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} catch (e) { debug(`agents check threw: ${e.message}`); }
|
|
370
|
+
|
|
371
|
+
// session staleness
|
|
372
|
+
try {
|
|
373
|
+
const sessPath = join(projectRoot, ".claude", ".semantic-memory", "session.json");
|
|
374
|
+
if (existsSync(sessPath)) {
|
|
375
|
+
const state = JSON.parse(readFileSync(sessPath, "utf8"));
|
|
376
|
+
if (state && !state.closed_at && typeof state.last_activity_at === "string") {
|
|
377
|
+
const ageMs = Date.now() - new Date(state.last_activity_at).getTime();
|
|
378
|
+
if (ageMs > 24 * 60 * 60 * 1000) {
|
|
379
|
+
findings.push({ check: "session_staleness", summary: `session ${state.id} stale (>${Math.round(ageMs / (60 * 60 * 1000))}h)` });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} catch (e) { debug(`session check threw: ${e.message}`); }
|
|
384
|
+
|
|
385
|
+
if (findings.length === 0) return "";
|
|
386
|
+
|
|
387
|
+
const lines = [
|
|
388
|
+
`<vault-drift count="${findings.length}">`,
|
|
389
|
+
`⚠️ semantic-memory: ${findings.length} drift issue${findings.length === 1 ? "" : "s"} detected`,
|
|
390
|
+
];
|
|
391
|
+
for (const f of findings.slice(0, 5)) {
|
|
392
|
+
lines.push(` ⚠ ${f.check}: ${f.summary}`);
|
|
393
|
+
}
|
|
394
|
+
if (findings.length > 5) lines.push(` …and ${findings.length - 5} more`);
|
|
395
|
+
lines.push(`Run /healthcheck for details, or /healthcheck --fix to auto-fix safe items.`);
|
|
396
|
+
lines.push(`</vault-drift>`);
|
|
397
|
+
return lines.join("\n");
|
|
398
|
+
}
|
|
399
|
+
|
|
312
400
|
function formatStateDeltaBlock(sinceIso, entries) {
|
|
313
401
|
if (!entries || entries.length === 0) {
|
|
314
402
|
return `<vault-state-since date="${sinceIso}">\nNo logged activity in the last 14 days.\n</vault-state-since>`;
|
|
@@ -341,6 +429,19 @@ function emitStop(additionalContext, block) {
|
|
|
341
429
|
process.stdout.write(JSON.stringify(out) + "\n");
|
|
342
430
|
}
|
|
343
431
|
|
|
432
|
+
function readActiveSession(projectRoot) {
|
|
433
|
+
const sessionPath = join(projectRoot, ".claude", ".semantic-memory", "session.json");
|
|
434
|
+
if (!existsSync(sessionPath)) return null;
|
|
435
|
+
try {
|
|
436
|
+
const state = JSON.parse(readFileSync(sessionPath, "utf8"));
|
|
437
|
+
if (state && typeof state.id === "string" && !state.closed_at) return state;
|
|
438
|
+
return null;
|
|
439
|
+
} catch (e) {
|
|
440
|
+
debug(`session.json parse failed: ${e.message}`);
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
344
445
|
async function handleStop(projectRoot) {
|
|
345
446
|
const stopHookActive = process.env.CLAUDE_STOP_HOOK_ACTIVE === "1";
|
|
346
447
|
if (stopHookActive) {
|
|
@@ -350,6 +451,34 @@ async function handleStop(projectRoot) {
|
|
|
350
451
|
return;
|
|
351
452
|
}
|
|
352
453
|
|
|
454
|
+
// Session-aware branch (Phase 5). When a session is open at Stop time, prompt the
|
|
455
|
+
// agent to close it via session_finish (with or without verification waiver). This
|
|
456
|
+
// takes precedence over mode-specific capture prompts because a session represents
|
|
457
|
+
// a verification-gated work boundary that must be explicitly closed.
|
|
458
|
+
const session = readActiveSession(projectRoot);
|
|
459
|
+
if (session) {
|
|
460
|
+
const verifs = Array.isArray(session.verifications) ? session.verifications : [];
|
|
461
|
+
const lines = [
|
|
462
|
+
`<vault-session-close id="${session.id}" task="${session.task}" verifications="${verifs.length}">`,
|
|
463
|
+
`Session ${session.id} is still open: task=${JSON.stringify(session.task)}, verifications=${verifs.length}.`,
|
|
464
|
+
"",
|
|
465
|
+
];
|
|
466
|
+
if (verifs.length > 0) {
|
|
467
|
+
lines.push("Recent verifications:");
|
|
468
|
+
for (const v of verifs.slice(-3)) {
|
|
469
|
+
lines.push(`- \`${v.cmd}\` → exit=${v.exit ?? "(killed)"} in ${v.duration_ms}ms`);
|
|
470
|
+
}
|
|
471
|
+
lines.push("");
|
|
472
|
+
lines.push(`Before ending: call \`mcp__semantic-vault__session_finish\` with a one-line summary. Verification recorded — finish will succeed without a waiver.`);
|
|
473
|
+
} else {
|
|
474
|
+
lines.push(`Before ending: either run \`mcp__semantic-vault__session_run\` with at least one verification command (tests/lint), or call \`mcp__semantic-vault__session_finish\` with \`verified: false\` and a \`reason\` waiving verification (e.g. doc-only edits). session_finish refuses without one of these paths.`);
|
|
475
|
+
}
|
|
476
|
+
lines.push(`</vault-session-close>`);
|
|
477
|
+
resetCapturePending(projectRoot);
|
|
478
|
+
emitStop(lines.join("\n"), true);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
353
482
|
const pending = readCapturePending(projectRoot);
|
|
354
483
|
const mode = readMode(projectRoot);
|
|
355
484
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theglitchking/semantic-memory",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Unified memory-layer plugin for Claude Code. One MCP server, multiple typed corpora (vault, code, plans, docs, research, project-map), unified cross-corpus knowledge graph, conditional tool registration, in-process drift detection, and human↔code translation. Rebrand of @theglitchking/semantic-sidekick at v1.0 — see CHANGELOG for migration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/crud.ts","../src/core/frontmatter.ts","../src/core/watcher.ts","../src/core/ingest.ts","../src/core/index-regen.ts","../src/core/patch.ts","../src/core/synthesize.ts"],"sourcesContent":["import { readFile, writeFile, unlink, rename, mkdir } from \"node:fs/promises\";\nimport { dirname, join, relative } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\nimport type { UpdateNoteOptions } from \"./types.js\";\n\nexport class NoteCrud {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async create(\n relativePath: string,\n content: string,\n frontmatter?: Record<string, unknown>\n ): Promise<string> {\n const absPath = join(this.notesPath, relativePath);\n if (existsSync(absPath)) {\n throw new Error(`Note already exists: ${relativePath}`);\n }\n\n await mkdir(dirname(absPath), { recursive: true });\n\n // Merge auto-stamps with caller-supplied frontmatter (caller wins on conflicts)\n const stamps = computeStamps(content);\n const merged = { ...stamps, ...(frontmatter ?? {}) };\n const fileContent = matter.stringify(content, merged);\n\n await writeFile(absPath, fileContent, \"utf-8\");\n return relativePath;\n }\n\n async read(relativePath: string): Promise<string> {\n const absPath = join(this.notesPath, relativePath);\n return readFile(absPath, \"utf-8\");\n }\n\n async readMultiple(paths: string[]): Promise<Map<string, string>> {\n const results = new Map<string, string>();\n await Promise.all(\n paths.map(async (p) => {\n try {\n const content = await this.read(p);\n results.set(p, content);\n } catch {\n results.set(p, `[Error: could not read ${p}]`);\n }\n })\n );\n return results;\n }\n\n async update(\n relativePath: string,\n content: string,\n options: UpdateNoteOptions\n ): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n if (!existsSync(absPath)) {\n throw new Error(`Note does not exist: ${relativePath}`);\n }\n\n const existing = await readFile(absPath, \"utf-8\");\n\n let updated: string;\n\n switch (options.mode) {\n case \"overwrite\":\n updated = content;\n break;\n\n case \"append\":\n updated = existing + \"\\n\" + content;\n break;\n\n case \"prepend\": {\n const { data, content: body } = matter(existing);\n const newBody = content + \"\\n\" + body;\n updated = Object.keys(data).length > 0 ? matter.stringify(newBody, data) : newBody;\n break;\n }\n\n case \"patch-by-heading\": {\n if (!options.heading) throw new Error(\"patch-by-heading requires a heading\");\n updated = this.patchByHeading(existing, options.heading, content);\n break;\n }\n }\n\n // Re-parse the final content and inject updated stamps\n // (preserves all existing frontmatter, only overwrites the three auto fields)\n const { data: existingFm, content: body } = matter(updated);\n const stamps = computeStamps(body);\n const newFm = { ...existingFm, ...stamps };\n await writeFile(absPath, matter.stringify(body, newFm), \"utf-8\");\n }\n\n async delete(relativePath: string): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n if (!existsSync(absPath)) {\n throw new Error(`Note does not exist: ${relativePath}`);\n }\n await unlink(absPath);\n }\n\n async move(fromPath: string, toPath: string): Promise<void> {\n const absFrom = join(this.notesPath, fromPath);\n const absTo = join(this.notesPath, toPath);\n\n if (!existsSync(absFrom)) {\n throw new Error(`Note does not exist: ${fromPath}`);\n }\n if (existsSync(absTo)) {\n throw new Error(`Destination already exists: ${toPath}`);\n }\n\n await mkdir(dirname(absTo), { recursive: true });\n await rename(absFrom, absTo);\n\n // Update wikilinks in other files that reference the old path\n await this.updateWikilinksAfterMove(fromPath, toPath);\n }\n\n private async updateWikilinksAfterMove(\n oldPath: string,\n newPath: string\n ): Promise<void> {\n const oldName = oldPath.replace(/\\.md$/, \"\").split(\"/\").pop()!;\n const newName = newPath.replace(/\\.md$/, \"\").split(\"/\").pop()!;\n\n if (oldName === newName) return;\n\n const files = await glob(\"**/*.md\", { cwd: this.notesPath });\n\n for (const file of files) {\n const absPath = join(this.notesPath, file);\n const content = await readFile(absPath, \"utf-8\");\n\n const pattern = new RegExp(`\\\\[\\\\[${escapeRegex(oldName)}(\\\\|[^\\\\]]*)?\\\\]\\\\]`, \"g\");\n if (!pattern.test(content)) continue;\n\n const updated = content.replace(pattern, `[[${newName}$1]]`);\n await writeFile(absPath, updated, \"utf-8\");\n }\n }\n\n private patchByHeading(\n content: string,\n heading: string,\n newContent: string\n ): string {\n const lines = content.split(\"\\n\");\n const headingPattern = new RegExp(`^#{1,6}\\\\s+${escapeRegex(heading)}\\\\s*$`, \"i\");\n\n let headingIndex = -1;\n let headingLevel = 0;\n\n for (let i = 0; i < lines.length; i++) {\n if (headingPattern.test(lines[i])) {\n headingIndex = i;\n const match = lines[i].match(/^(#{1,6})\\s+/);\n headingLevel = match ? match[1].length : 1;\n break;\n }\n }\n\n if (headingIndex === -1) {\n throw new Error(`Heading not found: ${heading}`);\n }\n\n // Find the end of this section (next heading of same or higher level)\n let endIndex = lines.length;\n for (let i = headingIndex + 1; i < lines.length; i++) {\n const match = lines[i].match(/^(#{1,6})\\s+/);\n if (match && match[1].length <= headingLevel) {\n endIndex = i;\n break;\n }\n }\n\n const before = lines.slice(0, headingIndex + 1);\n const after = lines.slice(endIndex);\n\n return [...before, \"\", newContent, \"\", ...after].join(\"\\n\");\n }\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Compute the three auto-stamp fields from note body text.\n * These are always factual/computable — no editorial judgment required.\n * - last_updated: today's date (YYYY-MM-DD)\n * - word_count: whitespace-separated token count\n * - estimated_read_time: assumes ~200 wpm average reading speed\n */\nfunction computeStamps(body: string): Record<string, unknown> {\n const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD\n const wordCount = body.trim() ? body.trim().split(/\\s+/).length : 0;\n const minutes = Math.max(1, Math.ceil(wordCount / 200));\n return {\n last_updated: today,\n word_count: wordCount,\n estimated_read_time: `${minutes} min read`,\n };\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { glob } from \"glob\";\nimport matter from \"gray-matter\";\n\nexport class FrontmatterManager {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async get(relativePath: string): Promise<Record<string, unknown>> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data } = matter(raw);\n return data;\n }\n\n async update(\n relativePath: string,\n fields: Record<string, unknown>\n ): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n for (const [key, value] of Object.entries(fields)) {\n if (value === null || value === undefined) {\n delete data[key];\n } else {\n data[key] = value;\n }\n }\n\n const updated = matter.stringify(content, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n}\n\nexport class TagManager {\n private notesPath: string;\n\n constructor(notesPath: string) {\n this.notesPath = notesPath;\n }\n\n async list(relativePath: string): Promise<string[]> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n const fmTags = Array.isArray(data.tags) ? (data.tags as string[]) : [];\n const inlineTags = [...content.matchAll(/(?:^|\\s)#([a-zA-Z][\\w-/]*)/g)].map(\n (m) => m[1]\n );\n\n return [...new Set([...fmTags, ...inlineTags])];\n }\n\n async add(relativePath: string, tags: string[]): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n const existing = Array.isArray(data.tags) ? (data.tags as string[]) : [];\n const merged = [...new Set([...existing, ...tags])];\n data.tags = merged;\n\n const updated = matter.stringify(content, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n\n async remove(relativePath: string, tags: string[]): Promise<void> {\n const absPath = join(this.notesPath, relativePath);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n\n // Remove from frontmatter\n if (Array.isArray(data.tags)) {\n data.tags = (data.tags as string[]).filter((t) => !tags.includes(t));\n }\n\n // Remove inline tags\n let updatedContent = content;\n for (const tag of tags) {\n const pattern = new RegExp(`(^|\\\\s)#${escapeRegex(tag)}(?=\\\\s|$)`, \"g\");\n updatedContent = updatedContent.replace(pattern, \"$1\");\n }\n\n const updated = matter.stringify(updatedContent, data);\n await writeFile(absPath, updated, \"utf-8\");\n }\n\n async renameVaultWide(oldTag: string, newTag: string): Promise<number> {\n const files = await glob(\"**/*.md\", { cwd: this.notesPath });\n let count = 0;\n\n for (const file of files) {\n const absPath = join(this.notesPath, file);\n const raw = await readFile(absPath, \"utf-8\");\n const { data, content } = matter(raw);\n let changed = false;\n\n // Rename in frontmatter\n if (Array.isArray(data.tags)) {\n const idx = (data.tags as string[]).indexOf(oldTag);\n if (idx >= 0) {\n (data.tags as string[])[idx] = newTag;\n changed = true;\n }\n }\n\n // Rename inline tags\n const pattern = new RegExp(`(^|\\\\s)#${escapeRegex(oldTag)}(?=\\\\s|$)`, \"g\");\n const updatedContent = content.replace(pattern, `$1#${newTag}`);\n if (updatedContent !== content) changed = true;\n\n if (changed) {\n const updated = matter.stringify(updatedContent, data);\n await writeFile(absPath, updated, \"utf-8\");\n count++;\n }\n }\n\n return count;\n }\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import { watch, type FSWatcher } from \"chokidar\";\nimport { EventEmitter } from \"node:events\";\n\nexport interface WatcherEvents {\n changed: (paths: string[]) => void;\n error: (error: Error) => void;\n}\n\nexport class Watcher extends EventEmitter {\n private notesPath: string;\n private fsWatcher: FSWatcher | null = null;\n private debounceMs: number;\n private pendingChanges = new Set<string>();\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private readyPromise: Promise<void> | null = null;\n private usePolling: boolean;\n\n constructor(notesPath: string, debounceMs: number = 500, usePolling: boolean = false) {\n super();\n this.notesPath = notesPath;\n this.debounceMs = debounceMs;\n this.usePolling = usePolling;\n }\n\n start(): void {\n if (this.fsWatcher) return;\n\n this.fsWatcher = watch(\"**/*.md\", {\n cwd: this.notesPath,\n ignoreInitial: true,\n followSymlinks: true,\n ignored: [\n \"**/node_modules/**\",\n \"**/.semantic-sidekick-index/**\",\n \"**/.git/**\",\n ],\n ...(this.usePolling ? { usePolling: true, interval: 100 } : {}),\n });\n\n this.readyPromise = new Promise<void>((resolve) => {\n this.fsWatcher!.on(\"ready\", resolve);\n });\n\n this.fsWatcher.on(\"add\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"change\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"unlink\", (path) => this.enqueue(path));\n this.fsWatcher.on(\"error\", (err) => this.emit(\"error\", err));\n }\n\n async ready(): Promise<void> {\n if (this.readyPromise) await this.readyPromise;\n }\n\n stop(): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer);\n this.fsWatcher?.close();\n this.fsWatcher = null;\n this.pendingChanges.clear();\n }\n\n private enqueue(path: string): void {\n this.pendingChanges.add(path);\n\n if (this.debounceTimer) clearTimeout(this.debounceTimer);\n this.debounceTimer = setTimeout(() => {\n const paths = [...this.pendingChanges];\n this.pendingChanges.clear();\n this.emit(\"changed\", paths);\n }, this.debounceMs);\n }\n}\n","import { basename } from \"node:path\";\nimport type { ChangeSet, PatchCreate } from \"./patch.js\";\n\nexport interface IngestSource {\n /** Identifier of the source — URL, filesystem path, DOI, ISBN, etc. */\n source_uri: string;\n /** Human-readable title for the source-note's frontmatter */\n source_title: string;\n /** One of the schema types; typically \"source\" */\n source_type?: string;\n /** Path under the vault to write the source-note. Defaults to `sources/<slug>.md`. */\n source_path?: string;\n /** Free-form body for the source-note (summary, abstract, your notes on it) */\n source_summary?: string;\n /** Tags for the source-note */\n source_tags?: string[];\n}\n\nexport interface IngestUnit {\n /** Path under the vault where this extracted note should live */\n path: string;\n /** Note title — if absent, derived from basename */\n title?: string;\n /** Note body — markdown */\n content: string;\n /** Schema type. Default: \"note\". */\n type?: string;\n /** Additional wikilink-style derived_from targets beyond the source-note */\n extra_derived_from?: string[];\n /** Additional free-form frontmatter fields */\n extra_frontmatter?: Record<string, unknown>;\n /** Per-unit confidence override */\n confidence?: string;\n}\n\nexport interface IngestInput {\n source: IngestSource;\n units: IngestUnit[];\n /** If true, the existing source-note is updated if present; else an error is raised */\n allow_existing_source?: boolean;\n}\n\nexport interface IngestPreview {\n changeset: ChangeSet;\n sourcePath: string;\n unitPaths: string[];\n}\n\n/**\n * Build a ChangeSet for ingesting a source into the vault.\n *\n * Shape: one create for the source-note (type=source) + one create per unit,\n * each stamped with `derived_from: [<source-path>]` so lint.missing_provenance\n * is satisfied automatically and the graph reflects the provenance edge.\n *\n * Intentionally does NOT touch the filesystem — callers route the result\n * through applyPatch for atomicity, validation, and rollback.\n */\nexport function buildIngestChangeSet(input: IngestInput): IngestPreview {\n const today = new Date().toISOString().slice(0, 10);\n const sourcePath = input.source.source_path ?? defaultSourcePath(input.source);\n\n const sourceNote: PatchCreate = {\n path: sourcePath,\n content: buildSourceBody(input.source),\n frontmatter: {\n title: input.source.source_title,\n type: input.source.source_type ?? \"source\",\n status: \"active\",\n source: input.source.source_uri,\n last_verified: today,\n confidence: \"high\",\n ...(input.source.source_tags && input.source.source_tags.length > 0 && { tags: input.source.source_tags }),\n },\n };\n\n const unitCreates: PatchCreate[] = input.units.map((u) => {\n const title = u.title ?? titleFromPath(u.path);\n const derivedFrom = [sourcePath, ...(u.extra_derived_from ?? [])];\n return {\n path: u.path.endsWith(\".md\") ? u.path : u.path + \".md\",\n content: u.content.startsWith(\"#\") ? u.content : `# ${title}\\n\\n${u.content.trim()}\\n`,\n frontmatter: {\n title,\n type: u.type ?? \"note\",\n status: \"active\",\n derived_from: derivedFrom,\n last_verified: today,\n confidence: u.confidence ?? \"medium\",\n ...(u.extra_frontmatter ?? {}),\n },\n };\n });\n\n return {\n changeset: { creates: [sourceNote, ...unitCreates] },\n sourcePath,\n unitPaths: unitCreates.map((c) => c.path),\n };\n}\n\nfunction buildSourceBody(source: IngestSource): string {\n const lines = [`# ${source.source_title}`, \"\", `**Source:** ${source.source_uri}`, \"\"];\n if (source.source_summary) {\n lines.push(source.source_summary.trim(), \"\");\n } else {\n lines.push(\"_Summary not provided — fill in when referencing this source._\", \"\");\n }\n return lines.join(\"\\n\");\n}\n\nfunction defaultSourcePath(source: IngestSource): string {\n const slug = slugify(source.source_title);\n return `sources/${slug}.md`;\n}\n\nfunction titleFromPath(p: string): string {\n return basename(p).replace(/\\.md$/, \"\").replace(/[-_]/g, \" \");\n}\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 80);\n}\n","import { readdir, readFile, writeFile, stat } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname, basename, sep } from \"node:path\";\nimport matter from \"gray-matter\";\n\nexport const INDEX_FILE = \"INDEX.md\";\nexport const INDEX_OVERFLOW_THRESHOLD = 100; // entries before we warn about splitting\n\nexport interface IndexEntry {\n path: string; // relative to the directory\n title: string;\n summary?: string;\n}\n\nexport interface RegenResult {\n directory: string;\n indexPath: string;\n entryCount: number;\n overflowed: boolean;\n wrote: boolean;\n}\n\n/**\n * Regenerate INDEX.md for a single vault directory.\n * Lists every *.md file in the directory (non-recursive, excluding the index itself)\n * with its title (frontmatter or first heading) and optional summary (from frontmatter\n * `purpose` or first non-heading paragraph).\n */\nexport async function regenDirectoryIndex(vaultPath: string, dirRel: string): Promise<RegenResult> {\n const absDir = join(vaultPath, dirRel);\n const indexPath = join(absDir, INDEX_FILE);\n const relIndex = join(dirRel, INDEX_FILE);\n\n if (!existsSync(absDir)) {\n return { directory: dirRel, indexPath: relIndex, entryCount: 0, overflowed: false, wrote: false };\n }\n\n const files = (await readdir(absDir)).filter(\n (f) => f.endsWith(\".md\") && f !== INDEX_FILE && f !== \"README.md\"\n );\n\n const entries: IndexEntry[] = [];\n for (const f of files) {\n const full = join(absDir, f);\n try {\n const st = await stat(full);\n if (!st.isFile()) continue;\n const raw = await readFile(full, \"utf-8\");\n const { data, content } = matter(raw);\n const title = typeof data.title === \"string\" ? data.title : firstHeading(content) ?? basenameNoExt(f);\n const summary = typeof data.purpose === \"string\" ? data.purpose : firstParagraph(content);\n entries.push({ path: f, title, summary });\n } catch {\n /* skip unreadable */\n }\n }\n\n entries.sort((a, b) => a.title.localeCompare(b.title));\n const body = renderIndex(dirRel, entries);\n const overflowed = entries.length > INDEX_OVERFLOW_THRESHOLD;\n\n let wrote = false;\n if (existsSync(indexPath)) {\n const current = await readFile(indexPath, \"utf-8\");\n if (current !== body) {\n await writeFile(indexPath, body, \"utf-8\");\n wrote = true;\n }\n } else {\n await writeFile(indexPath, body, \"utf-8\");\n wrote = true;\n }\n\n return { directory: dirRel, indexPath: relIndex, entryCount: entries.length, overflowed, wrote };\n}\n\n/**\n * Regenerate indexes for every directory affected by a set of paths.\n * Walks up the parent chain for each input so a deep change at\n * `a/b/c/note.md` regenerates `a/b/c/INDEX.md`. Does NOT recurse further —\n * `a/b/INDEX.md` stays as-is unless a path under `a/b/` (not `a/b/c/`)\n * also changed.\n */\nexport async function regenIndexesForPaths(\n vaultPath: string,\n paths: string[]\n): Promise<RegenResult[]> {\n const dirs = new Set<string>();\n for (const p of paths) {\n const d = dirname(p);\n if (d === \".\" || d === \"/\") continue;\n dirs.add(d);\n }\n const results: RegenResult[] = [];\n for (const d of dirs) {\n results.push(await regenDirectoryIndex(vaultPath, d));\n }\n return results;\n}\n\nfunction renderIndex(dirRel: string, entries: IndexEntry[]): string {\n const header = `# ${toTitle(dirRel)} Index\\n\\n> Auto-generated by semantic-sidekick. Changes are overwritten on apply_patch. Add prose to per-note files, not here.\\n\\n`;\n if (entries.length === 0) return header + \"_No notes in this directory yet._\\n\";\n const overflowNote =\n entries.length > INDEX_OVERFLOW_THRESHOLD\n ? `> ⚠️ ${entries.length} entries — consider splitting this directory into subfolders (threshold: ${INDEX_OVERFLOW_THRESHOLD}).\\n\\n`\n : \"\";\n const lines = entries.map((e) => {\n const linkText = e.title;\n const target = e.path.replace(/\\.md$/, \"\");\n const summary = e.summary ? ` — ${truncate(e.summary, 140)}` : \"\";\n return `- [${linkText}](./${e.path}) — \\`[[${target}]]\\`${summary}`;\n });\n return header + overflowNote + lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction toTitle(dirRel: string): string {\n return dirRel\n .split(sep)\n .pop()!\n .replace(/[-_]/g, \" \")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction basenameNoExt(p: string): string {\n return basename(p).replace(/\\.md$/, \"\");\n}\n\nfunction firstHeading(content: string): string | null {\n const m = content.match(/^\\s*#{1,6}\\s+(.+)$/m);\n return m ? m[1].trim() : null;\n}\n\nfunction firstParagraph(content: string): string | undefined {\n // Skip leading headings, then take the first line of non-heading text\n const lines = content.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"#\")) continue;\n if (trimmed.startsWith(\">\")) continue;\n if (trimmed.startsWith(\"```\")) continue;\n return trimmed.replace(/\\s+/g, \" \");\n }\n return undefined;\n}\n\nfunction truncate(s: string, n: number): string {\n return s.length <= n ? s : s.slice(0, n - 1).trimEnd() + \"…\";\n}\n","import { readFile, writeFile, unlink, rename, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport type { UpdateNoteOptions } from \"./types.js\";\nimport { NoteCrud } from \"./crud.js\";\nimport { loadSchema, validateNote, type LintFinding, type VaultSchema } from \"./schema.js\";\nimport { regenIndexesForPaths, type RegenResult } from \"./index-regen.js\";\n\nexport interface PatchCreate {\n path: string;\n content: string;\n frontmatter?: Record<string, unknown>;\n}\n\nexport interface PatchUpdate {\n path: string;\n content: string;\n mode: UpdateNoteOptions[\"mode\"];\n heading?: string;\n}\n\nexport interface PatchDelete {\n path: string;\n}\n\nexport interface PatchMove {\n from: string;\n to: string;\n}\n\nexport interface ChangeSet {\n creates?: PatchCreate[];\n updates?: PatchUpdate[];\n deletes?: PatchDelete[];\n moves?: PatchMove[];\n}\n\nexport interface ApplyPatchOptions {\n dryRun?: boolean;\n validate?: boolean;\n allowLintWarnings?: boolean;\n /** Regenerate INDEX.md in directories touched by this patch. Default: true. */\n regenIndexes?: boolean;\n}\n\nexport interface AppliedOp {\n op: \"create\" | \"update\" | \"delete\" | \"move\";\n path: string;\n}\n\nexport interface ApplyPatchResult {\n ok: boolean;\n applied: AppliedOp[];\n rolledBack: AppliedOp[];\n lint: LintFinding[];\n errors: string[];\n /** Index files regenerated as a side-effect. Empty if regenIndexes=false or dry-run. */\n indexes_regenerated?: RegenResult[];\n}\n\ninterface Snapshot {\n op: \"create\" | \"update\" | \"delete\" | \"move\";\n path: string;\n absPath: string;\n prevContent?: string; // for update/delete/move(from)\n prevAbsPath?: string; // for move (prev location)\n}\n\n/**\n * Atomic multi-note patch with rollback. Applies creates → updates → moves → deletes\n * in that order. If any op fails, all successful ops before it are reverted in reverse.\n *\n * In dry-run mode, nothing is written; the result describes what would have happened,\n * including lint findings on the proposed state.\n */\nexport async function applyPatch(\n notesPath: string,\n changeset: ChangeSet,\n options: ApplyPatchOptions = {}\n): Promise<ApplyPatchResult> {\n const validate = options.validate !== false;\n const dryRun = options.dryRun === true;\n const allowWarn = options.allowLintWarnings !== false;\n\n const errors: string[] = [];\n const lint: LintFinding[] = [];\n const applied: AppliedOp[] = [];\n const snapshots: Snapshot[] = [];\n\n const crud = new NoteCrud(notesPath);\n\n let schema: VaultSchema | null = null;\n if (validate) {\n try {\n schema = await loadSchema(notesPath);\n } catch (e) {\n errors.push(`failed to load schema: ${e instanceof Error ? e.message : String(e)}`);\n return { ok: false, applied, rolledBack: [], lint, errors };\n }\n }\n\n // Pre-check: collisions on creates, missing sources on updates/deletes/moves.\n for (const c of changeset.creates ?? []) {\n if (existsSync(join(notesPath, c.path))) {\n errors.push(`create: already exists: ${c.path}`);\n }\n }\n for (const u of changeset.updates ?? []) {\n if (!existsSync(join(notesPath, u.path))) {\n errors.push(`update: does not exist: ${u.path}`);\n }\n }\n for (const d of changeset.deletes ?? []) {\n if (!existsSync(join(notesPath, d.path))) {\n errors.push(`delete: does not exist: ${d.path}`);\n }\n }\n for (const m of changeset.moves ?? []) {\n if (!existsSync(join(notesPath, m.from))) {\n errors.push(`move: source does not exist: ${m.from}`);\n }\n if (existsSync(join(notesPath, m.to))) {\n errors.push(`move: destination exists: ${m.to}`);\n }\n }\n\n if (errors.length > 0) {\n return { ok: false, applied, rolledBack: [], lint, errors };\n }\n\n // Lint pre-flight on proposed state. Simulate each create/update and validate.\n if (validate && schema) {\n for (const c of changeset.creates ?? []) {\n const proposed = matter.stringify(c.content, c.frontmatter ?? {});\n lint.push(...validateNote(c.path, proposed, schema));\n }\n for (const u of changeset.updates ?? []) {\n const abs = join(notesPath, u.path);\n let proposed: string;\n try {\n const existing = await readFile(abs, \"utf-8\");\n proposed = applyUpdateInMemory(existing, u);\n } catch (e) {\n errors.push(`update precheck: ${u.path}: ${e instanceof Error ? e.message : String(e)}`);\n continue;\n }\n lint.push(...validateNote(u.path, proposed, schema));\n }\n\n const schemaErrors = lint.filter((f) => f.rule === \"schema_violations\" && f.severity === \"error\");\n if (schemaErrors.length > 0) {\n errors.push(`${schemaErrors.length} schema violation(s) blocked the patch`);\n }\n if (!allowWarn) {\n const warns = lint.filter((f) => f.severity === \"warn\");\n if (warns.length > 0) errors.push(`${warns.length} lint warning(s) blocked the patch (allowLintWarnings=false)`);\n }\n }\n\n if (errors.length > 0 || dryRun) {\n return { ok: errors.length === 0, applied, rolledBack: [], lint, errors };\n }\n\n // Execute — capture snapshots as we go so rollback can reverse.\n try {\n for (const c of changeset.creates ?? []) {\n const abs = join(notesPath, c.path);\n await crud.create(c.path, c.content, c.frontmatter);\n snapshots.push({ op: \"create\", path: c.path, absPath: abs });\n applied.push({ op: \"create\", path: c.path });\n }\n for (const u of changeset.updates ?? []) {\n const abs = join(notesPath, u.path);\n const prev = await readFile(abs, \"utf-8\");\n await crud.update(u.path, u.content, { mode: u.mode, heading: u.heading } as UpdateNoteOptions);\n snapshots.push({ op: \"update\", path: u.path, absPath: abs, prevContent: prev });\n applied.push({ op: \"update\", path: u.path });\n }\n for (const m of changeset.moves ?? []) {\n await crud.move(m.from, m.to);\n snapshots.push({\n op: \"move\",\n path: m.to,\n absPath: join(notesPath, m.to),\n prevAbsPath: join(notesPath, m.from),\n });\n applied.push({ op: \"move\", path: `${m.from} -> ${m.to}` });\n }\n for (const d of changeset.deletes ?? []) {\n const abs = join(notesPath, d.path);\n const prev = await readFile(abs, \"utf-8\");\n await crud.delete(d.path);\n snapshots.push({ op: \"delete\", path: d.path, absPath: abs, prevContent: prev });\n applied.push({ op: \"delete\", path: d.path });\n }\n } catch (e) {\n errors.push(`op failed: ${e instanceof Error ? e.message : String(e)} — rolling back`);\n const rolledBack = await rollback(snapshots);\n return { ok: false, applied, rolledBack, lint, errors };\n }\n\n let indexes_regenerated: RegenResult[] = [];\n if (options.regenIndexes !== false) {\n const touched = [\n ...(changeset.creates ?? []).map((c) => c.path),\n ...(changeset.deletes ?? []).map((d) => d.path),\n ...(changeset.moves ?? []).flatMap((m) => [m.from, m.to]),\n ];\n try {\n indexes_regenerated = await regenIndexesForPaths(notesPath, touched);\n } catch (e) {\n // Non-fatal: patch succeeded; index regen is a best-effort side effect.\n errors.push(`index regen failed (patch still applied): ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n return { ok: true, applied, rolledBack: [], lint, errors, indexes_regenerated };\n}\n\nasync function rollback(snapshots: Snapshot[]): Promise<AppliedOp[]> {\n const reverted: AppliedOp[] = [];\n for (const s of snapshots.slice().reverse()) {\n try {\n switch (s.op) {\n case \"create\":\n if (existsSync(s.absPath)) await unlink(s.absPath);\n reverted.push({ op: \"create\", path: s.path });\n break;\n case \"update\":\n if (s.prevContent !== undefined) {\n await writeFile(s.absPath, s.prevContent, \"utf-8\");\n }\n reverted.push({ op: \"update\", path: s.path });\n break;\n case \"delete\":\n if (s.prevContent !== undefined) {\n await mkdir(dirname(s.absPath), { recursive: true });\n await writeFile(s.absPath, s.prevContent, \"utf-8\");\n }\n reverted.push({ op: \"delete\", path: s.path });\n break;\n case \"move\":\n if (s.prevAbsPath && existsSync(s.absPath)) {\n await mkdir(dirname(s.prevAbsPath), { recursive: true });\n await rename(s.absPath, s.prevAbsPath);\n }\n reverted.push({ op: \"move\", path: s.path });\n break;\n }\n } catch {\n // swallow — best-effort rollback\n }\n }\n return reverted;\n}\n\nfunction applyUpdateInMemory(existing: string, u: PatchUpdate): string {\n switch (u.mode) {\n case \"overwrite\":\n return u.content;\n case \"append\":\n return existing + \"\\n\" + u.content;\n case \"prepend\": {\n const { data, content: body } = matter(existing);\n const newBody = u.content + \"\\n\" + body;\n return Object.keys(data).length > 0 ? matter.stringify(newBody, data) : newBody;\n }\n case \"patch-by-heading\": {\n if (!u.heading) throw new Error(\"patch-by-heading requires heading\");\n return patchByHeadingInMemory(existing, u.heading, u.content);\n }\n }\n}\n\nfunction patchByHeadingInMemory(content: string, heading: string, newContent: string): string {\n const lines = content.split(\"\\n\");\n const re = new RegExp(`^#{1,6}\\\\s+${heading.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\\\\s*$`, \"i\");\n let idx = -1;\n let level = 0;\n for (let i = 0; i < lines.length; i++) {\n if (re.test(lines[i])) {\n idx = i;\n const m = lines[i].match(/^(#{1,6})\\s+/);\n level = m ? m[1].length : 1;\n break;\n }\n }\n if (idx === -1) throw new Error(`Heading not found: ${heading}`);\n let end = lines.length;\n for (let i = idx + 1; i < lines.length; i++) {\n const m = lines[i].match(/^(#{1,6})\\s+/);\n if (m && m[1].length <= level) {\n end = i;\n break;\n }\n }\n return [...lines.slice(0, idx + 1), \"\", newContent, \"\", ...lines.slice(end)].join(\"\\n\");\n}\n","import { basename } from \"node:path\";\nimport type { ChangeSet, PatchCreate, PatchUpdate } from \"./patch.js\";\n\nexport interface SynthesizeNoteInput {\n topic: string;\n answer: string;\n suggested_path: string;\n type?: string;\n sources?: string[];\n derived_from?: string[];\n related_notes?: string[];\n status?: string;\n confidence?: string;\n decision_maker?: string;\n decided_on?: string;\n severity?: string;\n extra_frontmatter?: Record<string, unknown>;\n}\n\nexport interface SynthesizePreview {\n changeset: ChangeSet;\n path: string;\n title: 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 */\nexport function buildSynthesizeChangeSet(input: SynthesizeNoteInput): SynthesizePreview {\n const path = input.suggested_path.endsWith(\".md\") ? input.suggested_path : input.suggested_path + \".md\";\n const title = input.topic.trim();\n const today = new Date().toISOString().slice(0, 10);\n\n const frontmatter: Record<string, unknown> = {\n title,\n type: input.type ?? \"note\",\n status: 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.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 { changeset, path, title };\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"],"mappings":"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","names":["body","readFile","writeFile","join","glob","matter","escapeRegex","readFile","writeFile","existsSync","join","dirname","basename","matter","readFile","writeFile","unlink","rename","mkdir","existsSync","dirname","join","matter","existsSync","join","matter","readFile","unlink","writeFile","mkdir","dirname","rename","basename"]}
|