Skip to content

refactor(operations): fold FsVaultWriter into the disk provider - #122

Merged
AlexMost merged 9 commits into
mainfrom
worktree-consolidate-vault-writes
Aug 31, 2026
Merged

AlexMost merged 9 commits into
mainfrom
worktree-consolidate-vault-writes

Conversation

@AlexMost

Copy link
Copy Markdown
Owner

Refs #114

PR 1 of 3 for the consolidate-vault-writes change. FsVaultProvider becomes the single module that performs every note write over a vault root, with one name ⊕ path rule resolved at one depth and one filesystem-error taxonomy for notes that already exist.

What changes

  • One disk module. replaceInNote / replaceFullBody move onto VaultProvider keyed on a NoteIdentifier and are implemented by FsVaultProvider. src/lib/obsidian/vault-writer.ts is deleted, along with IVaultEntry.writer, IVaultEntryDeps.writerFactory, and the writerFactory wiring in src/server.ts. Its unit suite folds into test/operations/fs-vault-provider/edit-note.test.ts, message assertions included.
  • One name ⊕ path rule. resolveIdentifier (tool-helpers.ts) is now the only implementation — the inline XORs in create-note.ts and edit-note.ts and the non-XOR guard inside FsVaultProvider.createNote are gone. Every write tool hands an unresolved identifier down; the module resolves it in one of two named modes, resolveExisting (basename index) or resolveNew (.obsidian/app.json's newFileLocation).
  • One fs mapping over existing notes. Private readRaw → NOT_FOUND / READ_FAILED and writeRaw → WRITE_FAILED, shared by the edit and property paths, over an injectable readFile / writeFile seam carried over from the deleted writer. createNote deliberately keeps its own NOTE_EXISTS / CREATE_FAILED taxonomy (different flags, and the headless-vault-operations spec mandates it), and readDaily keeps its spec-pinned NOT_FOUND message — both now carry a comment saying so.

set_property and remove_property gain WRITE_FAILED / READ_FAILED coverage they have never had.

Three behaviour changes a reviewer should meet here rather than in the diff

  1. create_note with both name and path now reports details.field: "path" (was "name").
  2. create_note with neither now says "Provide exactly one of name or path" (was "Provide name or path") — matching what the other three write tools already say.
  3. edit_note with replace: '' plus an unresolvable name now fails INVALID_ARGUMENT before touching disk (was NOT_FOUND) — arguments are validated before I/O.

Same error codes in all three cases; no test or doc pinned either string. Everything else about the client-visible contract — tool schemas, parameter names, output shapes, codes and their messages — is unchanged.

Not in this PR

listTags / listProperties still hang off VaultProvider (PR 2 moves them to free functions over a VaultReader and drops computeVaultOverview to three deps). The architecture docs and ADR-0016 land in PR 3, with the archive.

Verification

npm test (109 files / 1355 tests), npm run lint, and npm run typecheck all pass. openspec validate consolidate-vault-writes passes. Acceptance greps from the plan hold: one construction site for the identifier rule, one for WRITE_FAILED, and no VaultWriter / writerFactory reference left in src/ or test/.

🤖 Generated with Claude Code

AlexMost and others added 9 commits August 30, 2026 19:41
…d writes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add replaceInNote/replaceFullBody to VaultProvider and implement them on
FsVaultProvider, reusing the readRaw/writeRaw seam from task 1. Rename the
private resolveIdentifierPath to resolveExisting to mirror the resolveNew
split coming in task 5. FsVaultWriter still serves edit_note; task 3 wires
the tool over to the provider.

Widening the VaultProvider interface breaks every local stub of it, so this
also adds the two new mocks to test/operations/tools/_helpers.ts,
test/lib/obsidian/vault-overview.test.ts, and test/server-modules.test.ts —
a type change ships with the call sites it breaks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
edit_note now validates input with resolveIdentifier(name, path) and
hands the unresolved NoteIdentifier down to entry.provider.replaceInNote
/ .replaceFullBody, instead of resolving name -> path itself and calling
the soon-to-be-deleted entry.writer. Argument validation (including the
empty-replace check) now runs before any disk I/O, so a malformed
`replace` is reported even when the identifier would not have resolved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fier

edit_note's tool-level "rejects unresolved name with NOT_FOUND" test was
deleted on the premise that resolveNoteName's zero-match branch is covered
where resolution now lives, but the name-addressed describe block in
fs-vault-provider/edit-note.test.ts only had unique-match and
AMBIGUOUS_MATCH cases. Add the missing zero-match -> NOT_FOUND case so the
taxonomy resolveNoteName produces (shared by edit_note, set_property,
remove_property) stays under test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FsVaultWriter and IVaultEntry.writer had no remaining consumer after the
edit_note tool moved to FsVaultProvider.replaceInNote/replaceFullBody
(Task 3). Delete the module, its unit test, and the writer field/factory
from IVaultEntry, IVaultEntryDeps, VaultRegistry, and server wiring.

The deleted vault-writer.test.ts cases that had no equivalent in
fs-vault-provider/edit-note.test.ts (missing-file NOT_FOUND and
WRITE_FAILED for replaceInNote; the no-frontmatter, verbatim-content, and
empty-content-truncation cases for replaceFullBody; and the
frontmatter-preserved-even-when-find-matches-inside-it case) are ported
into edit-note.test.ts so coverage carries forward unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…overage port

Code review of the Task 4 coverage port found four assertions narrower
than the deleted vault-writer.test.ts originals — error messages are
contract surface (ADR-0003), so dropping them silently narrowed coverage:

- replaceInNote WRITE_FAILED: restore the message assertion
- replaceFullBody WRITE_FAILED (previously "covered by existing"): that
  existing test never checked message propagation; extend it
- replaceInNote AMBIGUOUS_MATCH (previously "covered by existing"): restore
  the human-readable line-number message assertion and its explanatory
  comment (clients rendering only content[0].text need it)
- replaceInNote NOT_FOUND (note missing): restore the
  writeFile-not-called assertion

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… trim duplicate test

Re-pin the AMBIGUOUS_MATCH human-readable message for name-addressed
edits (clients without structuredContent still need candidate paths in
the text), drop a strictly-weaker duplicate traversal-name test, and
comment why readDaily's error mapping stays outside the shared
readRaw taxonomy (spec-pinned NOT_FOUND wording).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tracks the three-PR consolidation of note writes onto FsVaultProvider.
Group 1's tasks are ticked; groups 2 and 3 follow in later PRs.

Refs #114

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AlexMost
AlexMost merged commit 52c63a6 into main Aug 31, 2026
2 checks passed
@AlexMost
AlexMost deleted the worktree-consolidate-vault-writes branch August 31, 2026 10:57
Sign up for free to 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