obsidian-mcp-pro 1.5.3 → 1.7.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/README.md CHANGED
@@ -462,3 +462,11 @@ MIT
462
462
  Contributions welcome! Please open an issue first to discuss what you'd like to change. Pull requests without a corresponding issue may be closed.
463
463
 
464
464
  If you're adding or editing a tool, read [docs/TOOL_AUTHORING.md](./docs/TOOL_AUTHORING.md) first — it documents the description, schema, and annotation conventions that keep every tool at A-grade quality.
465
+
466
+ ---
467
+
468
+ ## Acknowledgments
469
+
470
+ - Vault-wide link rewriting on `move_note` ([#3](https://github.com/rps321321/obsidian-mcp-pro/issues/3), [#4](https://github.com/rps321321/obsidian-mcp-pro/pull/4)) and the `sanitizeError` defense-in-depth hardening contributed by [@brentkearney](https://github.com/brentkearney).
471
+
472
+ For the full list of everyone who's contributed, see the [contributors page](https://github.com/rps321321/obsidian-mcp-pro/graphs/contributors).
@@ -1,8 +1,28 @@
1
1
  /**
2
2
  * Convert an unknown thrown value into a message safe to return to an MCP
3
- * client. Strips absolute paths, stack traces, and collapses known errno
4
- * codes to generic human-readable text.
3
+ * client. Strips absolute paths, stack traces, collapses known errno
4
+ * codes to generic human-readable text, and escapes control characters so
5
+ * an attacker-controlled value (e.g. a filename with `\n` in it embedded
6
+ * in an error message) can't break out of its line and inject text into
7
+ * the LLM context.
5
8
  */
6
9
  export declare function sanitizeError(err: unknown): string;
10
+ /**
11
+ * Escape ASCII control characters (newlines, carriage returns, tabs, NULs,
12
+ * etc.) so an attacker-controlled string interpolated into a multi-line tool
13
+ * response can't break out of its line. `\n` becomes the two literal
14
+ * characters `\` and `n`; other control bytes use `\xHH`. Printable input
15
+ * passes through unchanged.
16
+ *
17
+ * Exists as a separate export from `sanitizeError` because that function's
18
+ * path-stripping step would rewrite a path-shaped value to literal `<path>`
19
+ * — fine inside an error message that mentions a host path, but it would
20
+ * erase the value when the value itself is the path you want to display
21
+ * (e.g. `f.path` from `failedReferrers`). Both functions apply the same
22
+ * control-char escape, so passing `f.path` here and `f.error` to
23
+ * `sanitizeError` gives equivalent injection protection through different
24
+ * doors.
25
+ */
26
+ export declare function escapeControlChars(s: string): string;
7
27
  export declare function stripPaths(s: string): string;
8
28
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAUlD;AAUD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK5C"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AA0BA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAUlD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOpD;AAUD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK5C"}
@@ -19,12 +19,15 @@ const FS_ERROR_MESSAGES = {
19
19
  };
20
20
  /**
21
21
  * Convert an unknown thrown value into a message safe to return to an MCP
22
- * client. Strips absolute paths, stack traces, and collapses known errno
23
- * codes to generic human-readable text.
22
+ * client. Strips absolute paths, stack traces, collapses known errno
23
+ * codes to generic human-readable text, and escapes control characters so
24
+ * an attacker-controlled value (e.g. a filename with `\n` in it embedded
25
+ * in an error message) can't break out of its line and inject text into
26
+ * the LLM context.
24
27
  */
25
28
  export function sanitizeError(err) {
26
29
  if (typeof err === "string")
27
- return stripPaths(err);
30
+ return escapeControlChars(stripPaths(err));
28
31
  if (!err || typeof err !== "object")
29
32
  return "Unknown error";
30
33
  const e = err;
@@ -32,7 +35,34 @@ export function sanitizeError(err) {
32
35
  if (code && FS_ERROR_MESSAGES[code])
33
36
  return FS_ERROR_MESSAGES[code];
34
37
  const msg = typeof e.message === "string" ? e.message : String(err);
35
- return stripPaths(msg);
38
+ return escapeControlChars(stripPaths(msg));
39
+ }
40
+ /**
41
+ * Escape ASCII control characters (newlines, carriage returns, tabs, NULs,
42
+ * etc.) so an attacker-controlled string interpolated into a multi-line tool
43
+ * response can't break out of its line. `\n` becomes the two literal
44
+ * characters `\` and `n`; other control bytes use `\xHH`. Printable input
45
+ * passes through unchanged.
46
+ *
47
+ * Exists as a separate export from `sanitizeError` because that function's
48
+ * path-stripping step would rewrite a path-shaped value to literal `<path>`
49
+ * — fine inside an error message that mentions a host path, but it would
50
+ * erase the value when the value itself is the path you want to display
51
+ * (e.g. `f.path` from `failedReferrers`). Both functions apply the same
52
+ * control-char escape, so passing `f.path` here and `f.error` to
53
+ * `sanitizeError` gives equivalent injection protection through different
54
+ * doors.
55
+ */
56
+ export function escapeControlChars(s) {
57
+ return s.replace(/[\x00-\x1f\x7f]/g, (c) => {
58
+ if (c === "\n")
59
+ return "\\n";
60
+ if (c === "\r")
61
+ return "\\r";
62
+ if (c === "\t")
63
+ return "\\t";
64
+ return `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`;
65
+ });
36
66
  }
37
67
  // Replace anything that looks like an absolute path with `<path>`. Covers:
38
68
  // - POSIX: starts with `/` followed by a non-space char
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,yEAAyE;AACzE,uEAAuE;AAEvE,MAAM,iBAAiB,GAA2B;IAChD,MAAM,EAAE,6BAA6B;IACrC,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE,yBAAyB;IAChC,MAAM,EAAE,qBAAqB;IAC7B,MAAM,EAAE,qBAAqB;IAC7B,OAAO,EAAE,yBAAyB;IAClC,SAAS,EAAE,wBAAwB;IACnC,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,qBAAqB;IAC7B,YAAY,EAAE,kBAAkB;CACjC,CAAC;AAOF;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,eAAe,CAAC;IAE5D,MAAM,CAAC,GAAG,GAAgB,CAAC;IAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,IAAI,IAAI,IAAI,iBAAiB,CAAC,IAAI,CAAC;QAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAEpE,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpE,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,2EAA2E;AAC3E,0DAA0D;AAC1D,gCAAgC;AAChC,+CAA+C;AAC/C,EAAE;AACF,wEAAwE;AACxE,qEAAqE;AACrE,uCAAuC;AACvC,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC;SACL,OAAO,CAAC,oBAAoB,EAAE,QAAQ,CAAC;SACvC,OAAO,CAAC,2BAA2B,EAAE,QAAQ,CAAC;SAC9C,OAAO,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;AAC9C,CAAC"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,yEAAyE;AACzE,uEAAuE;AAEvE,MAAM,iBAAiB,GAA2B;IAChD,MAAM,EAAE,6BAA6B;IACrC,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE,yBAAyB;IAChC,MAAM,EAAE,qBAAqB;IAC7B,MAAM,EAAE,qBAAqB;IAC7B,OAAO,EAAE,yBAAyB;IAClC,SAAS,EAAE,wBAAwB;IACnC,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,qBAAqB;IAC7B,YAAY,EAAE,kBAAkB;CACjC,CAAC;AAOF;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,kBAAkB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,eAAe,CAAC;IAE5D,MAAM,CAAC,GAAG,GAAgB,CAAC;IAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,IAAI,IAAI,IAAI,iBAAiB,CAAC,IAAI,CAAC;QAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAEpE,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpE,OAAO,kBAAkB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAS;IAC1C,OAAO,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC,EAAE,EAAE;QACzC,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAC7B,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAC7B,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAC7B,OAAO,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2EAA2E;AAC3E,0DAA0D;AAC1D,gCAAgC;AAChC,+CAA+C;AAC/C,EAAE;AACF,wEAAwE;AACxE,qEAAqE;AACrE,uCAAuC;AACvC,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC;SACL,OAAO,CAAC,oBAAoB,EAAE,QAAQ,CAAC;SACvC,OAAO,CAAC,2BAA2B,EAAE,QAAQ,CAAC;SAC9C,OAAO,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,85 @@
1
+ /** A single in-place edit on a referrer file, expressed as a byte-offset slice
2
+ * to replace. Edits within a file are listed in increasing-offset order.
3
+ *
4
+ * `expected` captures the exact bytes at `[start, end)` at plan time. The
5
+ * apply step compares against current contents so a parallel `write_note`
6
+ * that mutated the referrer between plan and apply (offsets shift but
7
+ * bounds still pass) doesn't silently splice the wrong bytes. */
8
+ interface SpanEdit {
9
+ start: number;
10
+ end: number;
11
+ expected: string;
12
+ replacement: string;
13
+ }
14
+ /** A complete plan of edits across all referrer files. Canvas edits are
15
+ * represented separately because they're applied via JSON re-serialization
16
+ * rather than offset substitution. */
17
+ export interface RewritePlan {
18
+ /** Markdown file edits keyed by vault-relative path. Empty map = no work. */
19
+ notes: Map<string, SpanEdit[]>;
20
+ /** Canvas files that need their `nodes[].file` field updated. */
21
+ canvases: string[];
22
+ /** Old vault-relative path being moved (with `.md` extension). */
23
+ oldPath: string;
24
+ /** New vault-relative path being moved to (with `.md` extension). */
25
+ newPath: string;
26
+ }
27
+ export interface ApplyResult {
28
+ /** Vault-relative paths whose contents were rewritten. */
29
+ updated: string[];
30
+ /** Per-file failures encountered during apply; rename has already committed
31
+ * by the time this runs, so partial failures are surfaced rather than
32
+ * rolled back. */
33
+ failed: Array<{
34
+ path: string;
35
+ error: string;
36
+ }>;
37
+ }
38
+ /**
39
+ * Build an edit plan describing how to update every reference to `oldPath`
40
+ * across the vault so it points at `newPath` instead. Pure planning — does
41
+ * not write to disk. Caller is expected to invoke `applyRewrites` after the
42
+ * rename has committed.
43
+ *
44
+ * `preMoveNotes` is the list returned by `listNotes(vaultPath)` *before* the
45
+ * rename. Resolution is computed against this list so a wikilink that pointed
46
+ * at the moved file is correctly identified, even if a different note
47
+ * elsewhere happens to share its basename.
48
+ */
49
+ export declare function planMoveRewrites(vaultPath: string, oldPath: string, newPath: string, preMoveNotes: string[]): Promise<RewritePlan>;
50
+ /**
51
+ * Build an edit plan describing how to strip every reference to `deletedPath`
52
+ * across the vault. Pure planning — does not write to disk. Caller is
53
+ * expected to invoke `applyRewrites` after the deletion has committed.
54
+ *
55
+ * Replacement rules:
56
+ *
57
+ * - Wikilinks: `[[file]]` becomes the deleted file's basename (or alias if
58
+ * present). `![[file]]` (embed) is removed entirely since an embed has
59
+ * no fallback text. Fragments (`#heading`, `#^block`) are dropped — the
60
+ * target is gone, so the fragment is meaningless.
61
+ * - Markdown links: `[text](file.md)` becomes the visible text. `![alt](...)`
62
+ * embed pointing at the deleted file is removed entirely.
63
+ * - Canvas: not handled here. Cleaning canvas refs after delete is a
64
+ * separate decision (remove node? leave dangling? convert to text node?)
65
+ * tracked separately. The returned plan has `canvases: []`.
66
+ *
67
+ * `preDeleteNotes` is the list returned by `listNotes(vaultPath)` *before*
68
+ * the delete. Resolution is computed against it so a wikilink that pointed
69
+ * at the deleted file is correctly identified, even if a different note
70
+ * elsewhere happens to share its basename.
71
+ */
72
+ export declare function planDeleteRewrites(vaultPath: string, deletedPath: string, preDeleteNotes: string[]): Promise<RewritePlan>;
73
+ /**
74
+ * Apply a previously-built `RewritePlan` to the vault. Each file is
75
+ * serialized through the existing per-file lock so concurrent MCP writes
76
+ * don't lose updates. Markdown edits are applied back-to-front to keep
77
+ * earlier offsets valid.
78
+ *
79
+ * Failures are accumulated, not thrown — the rename has already committed
80
+ * by the time we get here, so callers need to know which referrers landed
81
+ * and which need a manual retry.
82
+ */
83
+ export declare function applyRewrites(vaultPath: string, plan: RewritePlan): Promise<ApplyResult>;
84
+ export {};
85
+ //# sourceMappingURL=link-rewriter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-rewriter.d.ts","sourceRoot":"","sources":["../../src/lib/link-rewriter.ts"],"names":[],"mappings":"AAwBA;;;;;;kEAMkE;AAClE,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;uCAEuC;AACvC,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;uBAEmB;IACnB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,WAAW,CAAC,CAiItB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAAC,WAAW,CAAC,CAwFtB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,WAAW,CAAC,CA6EtB"}
@@ -0,0 +1,381 @@
1
+ import fs from "fs/promises";
2
+ import { listCanvasFiles, readNote, readCanvasFile, resolveVaultPathSafe, withFileLock, atomicWriteFile, } from "./vault.js";
3
+ import { extractAliases, extractWikilinkSpans, extractMarkdownLinkSpans, formatWikilinkTarget, resolveWikilink, } from "./markdown.js";
4
+ import { mapConcurrent } from "./concurrency.js";
5
+ import { log } from "./logger.js";
6
+ const SCAN_CONCURRENCY = 8;
7
+ /**
8
+ * Build an edit plan describing how to update every reference to `oldPath`
9
+ * across the vault so it points at `newPath` instead. Pure planning — does
10
+ * not write to disk. Caller is expected to invoke `applyRewrites` after the
11
+ * rename has committed.
12
+ *
13
+ * `preMoveNotes` is the list returned by `listNotes(vaultPath)` *before* the
14
+ * rename. Resolution is computed against this list so a wikilink that pointed
15
+ * at the moved file is correctly identified, even if a different note
16
+ * elsewhere happens to share its basename.
17
+ */
18
+ export async function planMoveRewrites(vaultPath, oldPath, newPath, preMoveNotes) {
19
+ // Build the post-move note set by substituting old → new. Used for picking
20
+ // the output form (basename vs path) that matches Obsidian's behavior.
21
+ const postMoveNotes = preMoveNotes.map((n) => (n === oldPath ? newPath : n));
22
+ // Build an alias map from the pre-move state. Mirrors the logic in
23
+ // tools/links.ts so resolution behavior is identical.
24
+ const aliasMap = new Map();
25
+ await mapConcurrent(preMoveNotes, SCAN_CONCURRENCY, async (notePath) => {
26
+ let content;
27
+ try {
28
+ content = await readNote(vaultPath, notePath);
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ for (const alias of extractAliases(content)) {
34
+ const key = alias.toLowerCase();
35
+ if (key)
36
+ aliasMap.set(key, notePath);
37
+ }
38
+ return undefined;
39
+ });
40
+ const notesPlan = new Map();
41
+ // Markdown file pass.
42
+ await mapConcurrent(preMoveNotes, SCAN_CONCURRENCY, async (notePath) => {
43
+ let content;
44
+ try {
45
+ content = await readNote(vaultPath, notePath);
46
+ }
47
+ catch (err) {
48
+ log.warn("link-rewriter: read failed during plan", {
49
+ note: notePath,
50
+ err: err,
51
+ });
52
+ return undefined;
53
+ }
54
+ const edits = [];
55
+ // Wikilinks.
56
+ for (const span of extractWikilinkSpans(content)) {
57
+ if (!span.target)
58
+ continue;
59
+ const resolved = resolveWikilink(span.target, notePath, preMoveNotes, { aliasMap });
60
+ if (resolved !== oldPath)
61
+ continue;
62
+ // Don't rewrite a self-reference inside the moved file itself — the
63
+ // file's path is changing too, so the link's resolution travels with
64
+ // the new file. Leaving it untouched is the simpler invariant.
65
+ if (notePath === oldPath)
66
+ continue;
67
+ const newTarget = formatWikilinkTarget(newPath, span.target, postMoveNotes);
68
+ const aliasPart = span.alias !== undefined ? `|${span.alias}` : "";
69
+ const replacement = `${span.isEmbed ? "!" : ""}[[${newTarget}${span.fragment}${aliasPart}]]`;
70
+ // Skip no-op rewrites: a bare `[[idea]]` that resolves to the moved
71
+ // note may already be in the right form when the basename stays
72
+ // unambiguous post-move. Reporting these as "updated" would be
73
+ // misleading and would touch mtimes for nothing.
74
+ const expected = content.slice(span.start, span.end);
75
+ if (replacement === expected)
76
+ continue;
77
+ edits.push({ start: span.start, end: span.end, expected, replacement });
78
+ }
79
+ // Markdown `[text](url)` links. Resolve URL paths the same way wikilinks
80
+ // resolve, so `[x](inbox/idea.md)` and `[x](inbox/idea)` both rewrite.
81
+ for (const span of extractMarkdownLinkSpans(content)) {
82
+ if (notePath === oldPath)
83
+ continue;
84
+ const url = span.urlPath;
85
+ // Skip absolute / external URLs — only intra-vault references rewrite.
86
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(url) || url.startsWith("/"))
87
+ continue;
88
+ const decoded = safeDecode(url);
89
+ const resolved = resolveWikilink(decoded, notePath, preMoveNotes, { aliasMap });
90
+ if (resolved !== oldPath)
91
+ continue;
92
+ // Preserve the user's choice of with/without `.md` extension and their
93
+ // basename-vs-path style. `formatWikilinkTarget` handles the
94
+ // basename-collision check so a post-move ambiguous basename falls back
95
+ // to the full path automatically.
96
+ const hadExt = /\.md$/i.test(decoded);
97
+ const decodedBare = decoded.replace(/\.md$/i, "");
98
+ const newBare = formatWikilinkTarget(newPath, decodedBare, postMoveNotes);
99
+ const newUrl = encodeUrlPath(hadExt ? `${newBare}.md` : newBare);
100
+ const replacement = `${span.isEmbed ? "!" : ""}[${span.text}](${newUrl}${span.fragment}${span.title})`;
101
+ const expected = content.slice(span.start, span.end);
102
+ if (replacement === expected)
103
+ continue;
104
+ edits.push({ start: span.start, end: span.end, expected, replacement });
105
+ }
106
+ if (edits.length > 0) {
107
+ // Edits within a file may have been produced in interleaved order
108
+ // (wikilinks then markdown links). Sort ascending so the apply step can
109
+ // walk back-to-front cleanly.
110
+ edits.sort((a, b) => a.start - b.start);
111
+ notesPlan.set(notePath, edits);
112
+ }
113
+ return undefined;
114
+ });
115
+ // Canvas pass — `nodes[].file` is the structured equivalent of a wikilink,
116
+ // and Obsidian stores the vault-relative path verbatim. Match by exact
117
+ // (case-insensitive) path equality on the old path.
118
+ const canvasPaths = await listCanvasFiles(vaultPath);
119
+ const oldLower = oldPath.toLowerCase();
120
+ const canvasesToRewrite = [];
121
+ await mapConcurrent(canvasPaths, SCAN_CONCURRENCY, async (cp) => {
122
+ let data;
123
+ try {
124
+ data = await readCanvasFile(vaultPath, cp);
125
+ }
126
+ catch (err) {
127
+ log.warn("link-rewriter: canvas read failed during plan", {
128
+ note: cp,
129
+ err: err,
130
+ });
131
+ return undefined;
132
+ }
133
+ for (const node of data.nodes) {
134
+ if (typeof node.file === "string" && node.file.toLowerCase() === oldLower) {
135
+ canvasesToRewrite.push(cp);
136
+ return undefined;
137
+ }
138
+ }
139
+ return undefined;
140
+ });
141
+ return {
142
+ notes: notesPlan,
143
+ canvases: canvasesToRewrite,
144
+ oldPath,
145
+ newPath,
146
+ };
147
+ }
148
+ /**
149
+ * Build an edit plan describing how to strip every reference to `deletedPath`
150
+ * across the vault. Pure planning — does not write to disk. Caller is
151
+ * expected to invoke `applyRewrites` after the deletion has committed.
152
+ *
153
+ * Replacement rules:
154
+ *
155
+ * - Wikilinks: `[[file]]` becomes the deleted file's basename (or alias if
156
+ * present). `![[file]]` (embed) is removed entirely since an embed has
157
+ * no fallback text. Fragments (`#heading`, `#^block`) are dropped — the
158
+ * target is gone, so the fragment is meaningless.
159
+ * - Markdown links: `[text](file.md)` becomes the visible text. `![alt](...)`
160
+ * embed pointing at the deleted file is removed entirely.
161
+ * - Canvas: not handled here. Cleaning canvas refs after delete is a
162
+ * separate decision (remove node? leave dangling? convert to text node?)
163
+ * tracked separately. The returned plan has `canvases: []`.
164
+ *
165
+ * `preDeleteNotes` is the list returned by `listNotes(vaultPath)` *before*
166
+ * the delete. Resolution is computed against it so a wikilink that pointed
167
+ * at the deleted file is correctly identified, even if a different note
168
+ * elsewhere happens to share its basename.
169
+ */
170
+ export async function planDeleteRewrites(vaultPath, deletedPath, preDeleteNotes) {
171
+ // Build alias map from the pre-delete state. Same logic as planMoveRewrites.
172
+ const aliasMap = new Map();
173
+ await mapConcurrent(preDeleteNotes, SCAN_CONCURRENCY, async (notePath) => {
174
+ let content;
175
+ try {
176
+ content = await readNote(vaultPath, notePath);
177
+ }
178
+ catch {
179
+ return undefined;
180
+ }
181
+ for (const alias of extractAliases(content)) {
182
+ const key = alias.toLowerCase();
183
+ if (key)
184
+ aliasMap.set(key, notePath);
185
+ }
186
+ return undefined;
187
+ });
188
+ const notesPlan = new Map();
189
+ // Fallback display text for wikilinks without an alias: the basename of
190
+ // the deleted file, without extension or path. Matches what Obsidian would
191
+ // render for a now-broken bare wikilink.
192
+ const deletedBasename = deletedPath.replace(/\.md$/i, "").split("/").pop() ??
193
+ deletedPath.replace(/\.md$/i, "");
194
+ await mapConcurrent(preDeleteNotes, SCAN_CONCURRENCY, async (notePath) => {
195
+ // Skip the file being deleted itself — it's about to disappear, so
196
+ // editing its body is wasted work (and the FS write would race the
197
+ // unlink). The `removeReferences` path only edits *other* notes.
198
+ if (notePath === deletedPath)
199
+ return undefined;
200
+ let content;
201
+ try {
202
+ content = await readNote(vaultPath, notePath);
203
+ }
204
+ catch (err) {
205
+ log.warn("link-rewriter: read failed during delete plan", {
206
+ note: notePath,
207
+ err: err,
208
+ });
209
+ return undefined;
210
+ }
211
+ const edits = [];
212
+ // Wikilinks.
213
+ for (const span of extractWikilinkSpans(content)) {
214
+ if (!span.target)
215
+ continue;
216
+ const resolved = resolveWikilink(span.target, notePath, preDeleteNotes, { aliasMap });
217
+ if (resolved !== deletedPath)
218
+ continue;
219
+ // Embeds disappear entirely; plain wikilinks fall back to alias-or-basename.
220
+ const replacement = span.isEmbed
221
+ ? ""
222
+ : (span.alias !== undefined ? span.alias : deletedBasename);
223
+ const expected = content.slice(span.start, span.end);
224
+ if (replacement === expected)
225
+ continue;
226
+ edits.push({ start: span.start, end: span.end, expected, replacement });
227
+ }
228
+ // Markdown `[text](url)` links.
229
+ for (const span of extractMarkdownLinkSpans(content)) {
230
+ const url = span.urlPath;
231
+ // Skip absolute / external URLs — only intra-vault refs strip.
232
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(url) || url.startsWith("/"))
233
+ continue;
234
+ const decoded = safeDecode(url);
235
+ const resolved = resolveWikilink(decoded, notePath, preDeleteNotes, { aliasMap });
236
+ if (resolved !== deletedPath)
237
+ continue;
238
+ const replacement = span.isEmbed ? "" : span.text;
239
+ const expected = content.slice(span.start, span.end);
240
+ if (replacement === expected)
241
+ continue;
242
+ edits.push({ start: span.start, end: span.end, expected, replacement });
243
+ }
244
+ if (edits.length > 0) {
245
+ edits.sort((a, b) => a.start - b.start);
246
+ notesPlan.set(notePath, edits);
247
+ }
248
+ return undefined;
249
+ });
250
+ return {
251
+ notes: notesPlan,
252
+ canvases: [],
253
+ oldPath: deletedPath,
254
+ newPath: "", // unused by applyRewrites when canvases is empty
255
+ };
256
+ }
257
+ /**
258
+ * Apply a previously-built `RewritePlan` to the vault. Each file is
259
+ * serialized through the existing per-file lock so concurrent MCP writes
260
+ * don't lose updates. Markdown edits are applied back-to-front to keep
261
+ * earlier offsets valid.
262
+ *
263
+ * Failures are accumulated, not thrown — the rename has already committed
264
+ * by the time we get here, so callers need to know which referrers landed
265
+ * and which need a manual retry.
266
+ */
267
+ export async function applyRewrites(vaultPath, plan) {
268
+ const updated = [];
269
+ const failed = [];
270
+ // Markdown pass.
271
+ await mapConcurrent(Array.from(plan.notes.keys()), SCAN_CONCURRENCY, async (notePath) => {
272
+ const edits = plan.notes.get(notePath);
273
+ if (!edits || edits.length === 0)
274
+ return undefined;
275
+ try {
276
+ const fullPath = await resolveVaultPathSafe(vaultPath, notePath);
277
+ let didWrite = false;
278
+ await withFileLock(fullPath, async () => {
279
+ // Re-read inside the lock so we apply edits to current content.
280
+ // applyEditsBackToFront verifies each edit's `expected` slice
281
+ // matches before splicing — a parallel `write_note` that landed
282
+ // between plan and apply will fail the comparison and we report
283
+ // the file rather than corrupting it.
284
+ const current = await fs.readFile(fullPath, "utf-8");
285
+ const next = applyEditsBackToFront(current, edits);
286
+ if (next === null) {
287
+ failed.push({
288
+ path: notePath,
289
+ error: "content changed during move; references not updated",
290
+ });
291
+ return;
292
+ }
293
+ if (next !== current) {
294
+ await atomicWriteFile(fullPath, next);
295
+ didWrite = true;
296
+ }
297
+ });
298
+ if (didWrite)
299
+ updated.push(notePath);
300
+ }
301
+ catch (err) {
302
+ failed.push({ path: notePath, error: err.message });
303
+ }
304
+ return undefined;
305
+ });
306
+ // Canvas pass.
307
+ for (const cp of plan.canvases) {
308
+ try {
309
+ const fullPath = await resolveVaultPathSafe(vaultPath, cp);
310
+ let didWrite = false;
311
+ await withFileLock(fullPath, async () => {
312
+ const raw = await fs.readFile(fullPath, "utf-8");
313
+ let parsed;
314
+ try {
315
+ parsed = JSON.parse(raw);
316
+ }
317
+ catch {
318
+ throw new Error(`Invalid canvas file (malformed JSON): ${cp}`);
319
+ }
320
+ const obj = (parsed && typeof parsed === "object" ? parsed : {});
321
+ const nodes = Array.isArray(obj.nodes) ? obj.nodes : [];
322
+ let mutated = false;
323
+ const oldLower = plan.oldPath.toLowerCase();
324
+ for (const node of nodes) {
325
+ if (typeof node.file === "string" && node.file.toLowerCase() === oldLower) {
326
+ node.file = plan.newPath;
327
+ mutated = true;
328
+ }
329
+ }
330
+ if (mutated) {
331
+ await atomicWriteFile(fullPath, JSON.stringify({ ...obj, nodes }, null, 2));
332
+ didWrite = true;
333
+ }
334
+ });
335
+ if (didWrite)
336
+ updated.push(cp);
337
+ }
338
+ catch (err) {
339
+ failed.push({ path: cp, error: err.message });
340
+ }
341
+ }
342
+ return { updated, failed };
343
+ }
344
+ /** Apply pre-sorted edits back-to-front. Returns `null` if any edit's span
345
+ * is out of bounds or if the bytes at `[start, end)` don't match the edit's
346
+ * `expected` slice — which means the file was modified between plan and
347
+ * apply (offsets shifted, or the link itself was rewritten). Caller treats
348
+ * `null` as a failed referrer rather than corrupting the file. */
349
+ function applyEditsBackToFront(content, edits) {
350
+ // Walk from the back so earlier offsets stay valid as we splice.
351
+ let out = content;
352
+ for (let i = edits.length - 1; i >= 0; i--) {
353
+ const e = edits[i];
354
+ if (e.start < 0 || e.end > out.length || e.start > e.end) {
355
+ return null;
356
+ }
357
+ if (out.slice(e.start, e.end) !== e.expected) {
358
+ return null;
359
+ }
360
+ out = out.slice(0, e.start) + e.replacement + out.slice(e.end);
361
+ }
362
+ return out;
363
+ }
364
+ /** Tolerant `decodeURIComponent`: returns the input unchanged on malformed
365
+ * percent-escapes rather than throwing. Vault paths sometimes contain raw
366
+ * spaces or characters that aren't valid URI components. */
367
+ function safeDecode(s) {
368
+ try {
369
+ return decodeURIComponent(s);
370
+ }
371
+ catch {
372
+ return s;
373
+ }
374
+ }
375
+ /** Encode a vault-relative path for use inside a markdown link URL. Keeps
376
+ * forward slashes literal (they're path separators) while escaping spaces
377
+ * and other characters that would break the `(...)` parser. */
378
+ function encodeUrlPath(p) {
379
+ return p.split("/").map(encodeURIComponent).join("/");
380
+ }
381
+ //# sourceMappingURL=link-rewriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-rewriter.js","sourceRoot":"","sources":["../../src/lib/link-rewriter.ts"],"names":[],"mappings":"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"}