Skip to content

feat: add vault_replace_span and vault_insert_at_anchor tools - #495

Merged
aliasunder merged 22 commits into
mainfrom
worktree-anchor-edit-ops
Aug 27, 2026
Merged

feat: add vault_replace_span and vault_insert_at_anchor tools#495
aliasunder merged 22 commits into
mainfrom
worktree-anchor-edit-ops

Conversation

@aliasunder

@aliasunderaliasunder commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • vault_replace_span — replace a contiguous block of lines identified by short anchor substrings with new content, in one atomic write. Same anchor semantics as vault_delete_span (case-sensitive substring, ambiguity-is-error, first_match escape hatch), with collapseBlankRuns on the reassembled body. Replaces the fragile two-step delete_span + patch_note workaround.
  • vault_insert_at_anchor — insert content before or after a line identified by anchor, freeing edits from heading-only targeting. No blank-line collapse (insertion doesn't create gaps).
  • Together with vault_delete_span, every line-level edit (delete, replace, insert) can now be targeted by a short anchor substring instead of exact text, addressing ~82/month edit-side guess-miss errors (49 replace_in_note text-not-found + 33 delete_span anchor-not-found from the Jul 9 – Aug 6 usage window).

Design decisions

  • Two new tools, purely additive — no existing tool semantics change. vault_replace_in_note (inline edits), vault_patch_note (heading-targeted), and vault_delete_span (anchor deletion) keep their distinct roles.
  • vault_insert_at_anchor over vault_insert_span — descriptive accuracy over family-suffix consistency; "span" doesn't apply to an insert.
  • Non-empty content required (.min(1)) on both tools — vault_delete_span exists for deletion.
  • collapseBlankRuns on vault_replace_span (same as delete_span) — prevents 3+ consecutive blank-line runs at the seam. Not applied on vault_insert_at_anchor (insertion doesn't create gaps).
  • vault_insert_at_anchor is annotated as an additive write (destructiveHint: false) — it can only add lines, never overwrite or remove them, matching vault_create_task. vault_replace_span stays destructive. The shared ADDITIVE_WRITE_ANNOTATIONS constant now backs both additive tools.
  • One span resolver for the familyresolveSpanLines resolves { startLine, endLine } for deleteSpan and replaceSpan; all three ops splice with Array.prototype.toSpliced.
  • Cross-references gated — every mention of a sibling tool in the three anchor-tool descriptions goes through whenToolEnabledText, so a reference disappears when its target is disabled.
  • Descriptions state the non-obvious semantics — first-edit YAML normalization (same caveat as the sibling edit tools), trailing-newline behaviour of content (adds a blank line), verbatim insert vs. collapsed replace, what each position value does, and the literal confirmation-message shape each tool returns.

Changes

FileChange
vault-patcher.tsreplaceSpan + insertAtAnchor; shared resolveSpanLines; toSpliced in all span ops
tool-registry.ts2 new TOOL_NAMES + TOOL_REGISTRY entries; ADDITIVE_WRITE_ANNOTATIONS
vault-crud-tools.ts2 new handler registrations + cross-reference and parameter-semantics updates; vault_patch_note states its no-separator content semantics and literal return shape
vault-patcher.test.ts30 new unit tests (happy paths, anchors, frontmatter exclusion, edge cases, errors, control characters, concurrency)
tool-registry.test.tsLiteral annotation spot-checks for both new tools
server-integration.test.tsTool counts 31→33 (assertions and section labels), write chain extended, read-only exclusions
server-error-contracts.test.ts12 new error contract tests (traversal, hidden, not-found, anchor, extension)
README.md2 new rows in tools table
ARCHITECTURE.md2 new rows; edit tools listed by targeting mode (heading / exact text / anchor)
AGENTS.mdStructure tree updated (9→11 tools)
DOCKERHUB.mdRegenerated

Test plan

  • 3,287 tests pass (83 files) — includes the new unit, integration, error-contract, and registry tests
  • npm run build clean (server + CLI)
  • npm run lint — 0 errors
  • Emitted tools/list JSON Schema inspected from a booted server for both tools — required sets, string enum for position, minLength floors, parameter descriptions, and annotations all as intended
  • CI checks (arch-smoke, lint, test, integration)
  • Live validation on a deployed :remote build of this branch (6e2a2d4; the two later commits are docs-only), driven through an MCP client against a real vault — 26 calls to the two new tools on a throwaway note, every result read back and compared against the documented contract:
    • vault_replace_span writes: single-line table row; multi-line callout with end_anchor; start_anchor and end_anchor on the same line; trailing newline in content adds one blank line and the seam collapses to a single blank; leading/trailing blank padding in content collapses; first_match takes the first of two matches; frontmatter preserved on every write; a property value matching the anchor text is not a match (body only).
    • vault_replace_span error paths (exact messages): start anchor not found; ambiguous start anchor (2 lines); ambiguous end anchor at or after the start; end anchor only above the start → "end anchor not found … at or after the start anchor"; note not found; hidden path blocked; anchor present only in frontmatter → not found; path without .md.
    • vault_insert_at_anchor writes: after a table row; before a heading with a trailing newline (callout + blank line); before with first_match on a duplicated list item; after the last line with a multi-line block whose internal blank line is kept verbatim and no collapse at the seam.
    • vault_insert_at_anchor error paths: anchor not found; ambiguous anchor; note not found; hidden path blocked; anchor present only in frontmatter → not found; path without .md; invalid position rejected at the schema layer (-32602).
    • No regressions in the surrounding surface on the same note: vault_delete_span, vault_replace_in_note, vault_patch_note, vault_create_task, vault_update_task, vault_read_note (full + heading), plus vault_search, vault_list_tasks, vault_get_backlinks, vault_get_daily_note, vault_memory_recall, vault_list_files — all behaved as before.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added anchor-based editing tools to replace content between anchors or insert content before or after an anchored line.
    • Preserves document frontmatter and supports multi-line content with validation and safe error handling.
  • Documentation

    • Updated tool listings and architecture documentation to describe the new editing capabilities.
  • Tests

    • Expanded coverage for successful edits, validation, missing anchors/files, hidden paths, ambiguous anchors, and concurrent writes.

Complete the anchor-targeted edit triad alongside vault_delete_span.
Both tools reuse the same anchor resolution (case-sensitive substring,
ambiguity-is-error, first_match escape hatch) and share the same error
semantics.
vault_replace_span replaces a contiguous block of lines identified by
start/end anchors with new content in one atomic write — replacing the
fragile two-step delete_span + patch_note workaround.
vault_insert_at_anchor inserts content before or after a line identified
by an anchor — freeing edits from heading-only targeting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/vault-mcp/vault-operations/vault-patcher.ts
Comment threadsrc/vault-mcp/mcp-core/tools/vault-crud-tools.ts
Comment threadsrc/vault-mcp/mcp-core/tools/vault-crud-tools.ts
Comment threadsrc/vault-mcp/vault-operations/vault-patcher.ts
Comment threadsrc/vault-mcp/mcp-core/tools/vault-crud-tools.ts Outdated
@umm-actually

umm-actuallyBot commented Aug 26, 2026

Copy link
Copy Markdown

umm-actually re-reviewed at 98a653e

1 new finding(s) posted (17 tracked finding(s) across all runs).

Context notes
  • Priority docs already in context: README.md, ARCHITECTURE.md

umm-actually · deepseek/deepseek-v4-flash-0731

…an and vault_insert_at_anchor descriptions
Both tools accept user-authored content written as Obsidian Markdown but
were missing the Obsidian syntax note that all other content-writing tools
include (vault_write_note, vault_patch_note, vault_replace_in_note).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment threadsrc/vault-mcp/vault-operations/vault-patcher.ts
Comment threadsrc/vault-mcp/vault-operations/vault-patcher.ts
@umm-actually

Copy link
Copy Markdown

Add positive-plus-missing anchor test for insertAtAnchor
Low severity · correctness · high confidence

src/vault-mcp/vault-operations/vault-patcher.test.ts:3356 — beyond the diff's line ranges, in code the changes touch or depend on.

Tests at lines 3356-3383 cover only an ambiguous start anchor, ambiguous end anchor, and end anchor missing; the repository's own test code for the sibling deleteSpan has a 'positive-plus-missing' test that seeds a unique line from a non-anchor sentence and asserts the insert still succeeds because the phone line is absent. The insertAtAnchor error-path tests mirror the old deleteSpan question but drop that case, so a future breaking change to the resolver's sentence-boundary math would be caught, while the missing sibling case stays unprotected.

Failure scenario: Inserting after an unambiguous list line whose sentence boundary spans lines, when the end anchor is an empty line and the resolver's state machine would mistake the boundary — no test asserts the intended result, so a regression would silently widen or narrow the insertion point.

Suggested fix
Add a test that seeds a non-anchored line that would have caused an ambiguous-anchor rejection (like the existing end-anchor positive-plus-missing case) and asserts the insertion resolves past it without error.

umm-actually · deepseek/deepseek-v4-flash-0731

aliasunderand others added 4 commits August 26, 2026 15:57
- Make resolveAnchorLine role optional so vault_insert_at_anchor
produces "anchor not found" / "ambiguous anchor" instead of the
misleading "start anchor" qualifier (it has a single anchor param,
not start/end)
- Gate vault_delete_span's vault_replace_span reference via
isToolEnabled so the tool name disappears when the tool is disabled
- Update documented Errors prefix in vault_insert_at_anchor description
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n/insertAtAnchor tests
Two-bar fixes: "preserves frontmatter" tests now assert full content
(previously passed as silent no-ops). Assertion quality: 6 substring/loose
matchers replaced with exact assertions on deterministic output. Coverage
gaps: add tests for ambiguous end anchor and firstMatch on end anchor
in replaceSpan (deleteSpan had these; replaceSpan did not).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vault_delete_span's description used isToolEnabled with manual ternaries
to gate vault_replace_span references — the only tool in the file not
using the whenToolEnabledText helper that every other cross-reference
uses. Swapped to whenToolEnabledText and removed the unused isToolEnabled
destructuring.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies that content containing 3+ consecutive blank lines is preserved
verbatim — the no-collapse invariant applies to internal content, not
just seam-adjacent blanks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aliasunder

Copy link
Copy Markdown
OwnerAuthor

Re: "Add positive-plus-missing anchor test for insertAtAnchor"resolveAnchorLine performs simple line.includes(anchor) substring matching. There is no sentence-boundary math, state machine, or resolver logic that would produce the described failure scenario. The finding appears to describe complexity that doesn't exist in the implementation.


🔍 ship-check · pr-monitor · Claude Opus 4.6 (1M context)

@umm-actually

Copy link
Copy Markdown

Restore removed module-layering paragraph in AGENTS.md
Medium severity · correctness · high confidence

AGENTS.md:4310 — beyond the diff's line ranges, in code the changes touch or depend on.

The 5th paragraph of the Module layering section in AGENTS.md was removed by this PR, deleting the explanation that a module's folder is decided by its dependency profile, not its topic. This is unrelated to the two new tools and drops a core architectural convention from the project's documentation.

Failure scenario: A developer reads AGENTS.md to determine where a new vault-operations module should live; the removed paragraph that established the dependency-profile rule (canvas.ts belongs in obsidian-markdown/ despite parsing JSON, pdf-engine.ts is the one side-effecting leaf) is gone, so the rationale for existing module placement is no longer documented.

Suggested fix
Restore the removed paragraph in the Module layering section, preserving the dependency-profile rule and the canvas.ts/pdf-engine.ts examples.

umm-actually · deepseek/deepseek-v4-flash-0731

@aliasunder

Copy link
Copy Markdown
OwnerAuthor

Re: "Restore removed module-layering paragraph in AGENTS.md" — False positive. The PR's only change to AGENTS.md is a one-line update to the structure tree comment (9 tools11 tools). No paragraph was removed from the Module layering section — git diff main -- AGENTS.md confirms a single-line diff. The module-layering conventions are intact.


🔍 ship-check · pr-monitor · Claude Opus 4.6 (1M context)

@aliasunder

Copy link
Copy Markdown
OwnerAuthor

@CodeRabbit review

@coderabbitai

coderabbitaiBot commented Aug 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitaiBot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ad23db93-28dd-4b4e-93d1-a3f4b98b3b36

📥 Commits

Reviewing files that changed from the base of the PR and between a872160 and 1d8a40a.

📒 Files selected for processing (10)
  • AGENTS.md
  • ARCHITECTURE.md
  • DOCKERHUB.md
  • README.md
  • src/__tests__/integration/server-error-contracts.test.ts
  • src/__tests__/integration/server-integration.test.ts
  • src/vault-mcp/mcp-core/tool-registry.ts
  • src/vault-mcp/mcp-core/tools/vault-crud-tools.ts
  • src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts
  • src/vault-mcp/vault-operations/vault-patcher.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds vault_replace_span and vault_insert_at_anchor. It implements both operations in vaultPatcher, registers them as destructive MCP tools, adds unit and integration coverage, and updates tool documentation and inventories.

Changes

Anchor-targeted Vault editing

Layer / File(s)Summary
Patcher operations
src/vault-mcp/vault-operations/vault-patcher.ts
Adds locked, validated span replacement and anchored insertion operations.
MCP exposure and registration
src/vault-mcp/mcp-core/tool-registry.ts, src/vault-mcp/mcp-core/tools/vault-crud-tools.ts
Registers both tools, validates inputs, logs requests and results, and delegates to vaultPatcher.
Behavior and integration validation
src/vault-mcp/vault-operations/__tests__/vault-patcher.test.ts, src/__tests__/integration/*
Tests edits, anchor handling, validation, locking, hidden paths, discovery, workflows, and configuration-specific availability.
Documentation and inventory
README.md, DOCKERHUB.md, ARCHITECTURE.md, AGENTS.md
Documents both tools and updates the Vault CRUD tool inventory.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk:⚪ Minimal · up to 1d8a4

This additive change introduces two anchor-targeted editing tools without any supplied evidence of an actionable merge-blocking risk; no current issue remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 6…
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and concisely identifies the two main tools added by the pull request: vault_replace_span and vault_insert_at_anchor.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 6 files. (4 skipped: 4 unsupported.)

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-anchor-edit-ops

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@umm-actually

Copy link
Copy Markdown

Update vault-patcher module header to mention anchor operations
Low severity · correctness · high confidence

src/vault-mcp/vault-operations/vault-patcher.ts:1 — beyond the diff's line ranges, in code the changes touch or depend on.

The module doc comment still says “Surgical note editing — heading-targeted patches and find-and-replace.” The module now also provides anchor-targeted replace (replaceSpan) and anchor insertion (insertAtAnchor) — the same file’s AGENTS.md entry was updated to “heading-targeted patch + find-and-replace” plus the anchor triad. The header drifts from the implementation’s actual surface; a future editor scanning this header to find where edits land will miss both new operations.

Failure scenario: Reading the module header to locate edit operations does not name replaceSpan/insertAtAnchor, so those core capabilities are undiscoverable from the file’s own contract documentation; a subsequent edit assuming the module is only heading/find-replace could place new editing logic elsewhere and miss the shared lock/anchor helpers.

Suggested fix
Reword the header to include the anchor operations, e.g. “Surgical note editing — heading-targeted patches, find-and-replace, and anchor-targeted span replacement/insertion.”

umm-actually · deepseek/deepseek-v4-flash-0731

@aliasunder

Copy link
Copy Markdown
OwnerAuthor

Re: "Update vault-patcher module header to mention anchor operations" — Fixed in 6e2a2d4. The header now reads "Surgical note editing — heading-targeted patches, find-and-replace, and anchor-targeted line spans (delete, replace, insert)", naming all three anchor operations the module owns.


🔍 ship-check · pr-monitor · claude-fable-5

Comment threadsrc/vault-mcp/mcp-core/tools/vault-crud-tools.ts Outdated
…iptions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadsrc/__tests__/integration/server-integration.test.ts
Comment threadsrc/vault-mcp/mcp-core/tools/vault-crud-tools.ts Outdated
Comment threadsrc/vault-mcp/vault-operations/vault-patcher.ts
…span ops; sync count comments and YAML caveat
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@umm-actually

Copy link
Copy Markdown

Pin Obsidian-syntax guidance for the two new write tools
Low severity · conventions · high confidence

src/vault-mcp/mcp-core/__tests__/tool-definitions.test.ts:19 — beyond the diff's line ranges, in code the changes touch or depend on.

WRITE_TOOLS is a hardcoded list that feeds the it.each('%s description includes Obsidian syntax guidance') check, but it was not extended with vault_replace_span or vault_insert_at_anchor. Both tools are write tools whose descriptions do carry an 'Obsidian syntax:' section, so nothing fails today — but a future removal or rewording of that section in either new description is unpinned and would pass CI, while every other write tool is guarded.

Failure scenario: A contributor edits vault_replace_span's description and drops the 'Obsidian syntax:' paragraph. The it.each(WRITE_TOOLS) test skips the tool because it is absent from the literal list, and the check suite passes despite the two new write tools losing documented Obsidian-syntax guidance.

Suggested fix
Add TOOL_NAMES.VAULT_REPLACE_SPAN and TOOL_NAMES.VAULT_INSERT_AT_ANCHOR to the WRITE_TOOLS array so the new writers join the same Obsidian-syntax pin as vault_write_note and vault_patch_note.

umm-actually · deepseek/deepseek-v4-flash-0731

…nsert_at_anchor
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aliasunder

Copy link
Copy Markdown
OwnerAuthor

Re: "Pin Obsidian-syntax guidance for the two new write tools" — Fixed in ae46147. vault_replace_span and vault_insert_at_anchor are now in WRITE_TOOLS, so the it.each Obsidian-syntax check pins both descriptions alongside the other writers (the test file runs two more cases).


🔍 ship-check · pr-monitor · claude-fable-5

Comment threadsrc/vault-mcp/mcp-core/tools/vault-crud-tools.ts
@aliasunder
aliasunder merged commit 18e437e into mainAug 27, 2026
19 checks passed
@aliasunder
aliasunder deleted the worktree-anchor-edit-ops branch August 27, 2026 23:09
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@aliasunder