# Changelog

## 0.9.0 (2026-10-10)

- **Deferred tool tier** (ceulen pattern): the `obsidian` tool now register `exposure: "deferred"` — not declared to the model until `tool_search` loads them (they stay callable via `ctx.executeTool`/codemode). The package now force-activates `tool_search` in `session_start` when it defers tools (pi only auto-activates discovery for MCP servers). Unit tests pin the exposure flags and the activation branches.

## 0.8.20 (2026-09-30)

### Fixed

- **`create` no longer flips to overwrite when note content contains the
  literal text `overwrite=true`** — the guard now matches `overwrite=true` as
  a whole token via `parseCliString` instead of a raw substring search, so
  quoted content can no longer clobber an existing file.
- **`files folder=<name-containing-recursive>` no longer switches to a
  recursive listing** — same substring→token fix for the `recursive` flag.
- **`eval` no longer hard-fails when Obsidian 1.13.x drops the echo** —
  read-only scripts retry once on an empty echo (a retry cannot double-apply
  a read); write-capable scripts are never re-executed and instead return a
  warning noting the side effects most likely applied, so a successful write
  is no longer reported as an error.
## 0.8.19 (2026-09-26)

### Fixed

- **`files folder="/"` now lists root-level files only** (matching the
  README's documented behavior). Previously the root listing dumped the
  entire vault recursively. The full recursive listing is still available by
  passing an explicit `recursive` token. New regression tests cover both
  paths (root-only filter + explicit recursive).
- **typebox peer swapped to the unscoped `typebox` package** — the host SDK
  resolves the unscoped name (same fix pi-notebooklm shipped in 0.1.14);
  removes the scoped `@sinclair/typebox` peer/devDep that never matched the
  real host resolution.
- Tool `description` now lists the full command set (~30 commands) instead
  of a stale 6-command summary.

## 0.8.18 (2026-09-22)

### Fixed

- **`files folder=` no longer matches substring folder names.** The recursive
  listing used `f.path.startsWith(folder)`, so `folder="01"` also listed files
  from `012 Notes/`. Matching is now on path-segment boundaries (the folder
  itself or paths strictly under `folder/`); a trailing `/` is trimmed so
  `folder="Notes/"` behaves identically to `folder="Notes"`.
- **Tolerant write-step retry is now mode-aware.** A dropped echo or transient
  `Error:` on an eval step triggered a blind re-execution of the same script.
  For `append`/`prepend` steps a succeeded-but-lost-echo first execution got
  DUPLICATED by the retry, and tail/prefix verification cannot detect the extra
  copy (appending `abc` twice still tail-matches `abc`). The step-level retry is
  now skipped for `append`/`prepend` — mirroring the whole-write retry policy
  that already excluded those modes — and the read-back verify step judges the
  real file state. Create/overwrite steps keep the retry (writes are
  idempotent).
- **`search` regex-mode preview shows the regex match context.** The preview
  snippet located its window with `c.indexOf(q)` even in regex mode, showing
  the position of the raw pattern text (often `-1`) instead of the first regex
  match. The preview now uses the regex match's index and length.

### Tests

- Unit tests for `redirectionDestination` (quote tracking, `>` vs `>>`,
  redirects inside quoted strings, no-redirect cases).
- Segment-boundary tests for `files folder=` recursive listing.
- Regression test: a tolerant `append` step with a dropped echo must NOT
  re-execute the write (duplicate-content guard).

## 0.8.17 (2026-09-21)

### Fixed

- **`tag-rename` with a `#`-prefixed `from=` now actually matches.** The
  generated eval built `\b#tag\b`, but `\b` never matches against a non-word
  edge character, so a tag preceded by whitespace (`  - #moc`,
  `tags: #moc`) was never replaced and the command silently reported
  "0 updated" — the README's own `tag-rename from="#moc"` example was a
  no-op. The leading/trailing anchor now degrades to `\B` when the pattern
  edge is a non-word character, so whitespace-preceded `#tags` match while
  mid-word occurrences (`x#moc`) still don't; bare `from="moc"` behavior is
  unchanged. The replacement is now a function replacement, so `$&`-style
  sequences in `to=` are inserted literally instead of being expanded by
  `String.replace`, and an empty `from=` is rejected up front.
- **`timeout_ms=NaN` no longer crashes the command.** A non-numeric
  `timeout_ms` produced `NaN`, which `spawnSync` rejects with
  `ERR_OUT_OF_RANGE`. Non-finite values now fall back to the 30 s default.

## 0.8.16 (2026-09-20)

### Fixed

- **`files` command no longer reports "No files found." on real CLI failures.**
  The non-recursive `files` exec was wrapped in a bare catch that fell through
  to the empty-result message, so a non-zero exit / timeout / missing CLI was
  indistinguishable from an empty vault (the recursive path already propagated
  errors). Non-recursive command failures now return
  `Obsidian CLI error: <message>`; genuine empty results keep
  "No files found.".
- **Focused-vault lookup memoized.** `focusedVaultNameForCwd` spawned
  `obsidian vault` on every tool execute while `allVaultRoots` cached the
  identical call. Both now derive from one memoized spawn; per-site error
  behavior is unchanged (name lookup still surfaces CLI failure, root guard
  still treats failure as no roots) — and failures or unparseable output are
  not memoized, so a session started before the Obsidian app launches still
  recovers on a later call.
- **SMB/NFS vaults: write verification no longer false-fails after writes.**
  Obsidian 1.13.x drops eval echoes when the async body does real I/O, and
  network mounts delay read-back propagation, so the old in-eval verify
  (`adapter.read` + hash inside the eval) saw empty output and reported
  create/write/append/prepend as failed even though the note was written.
  Verification now reads the note back through the CLI (`obsidian read`)
  and hashes it in Node.js, with up to 3 attempts (500 ms apart) to ride out
  propagation delays. Notes ending in a trailing newline verify byte-exact
  (the CLI printer's added newline is inverted instead of stripped), missing
  files produce an actionable error, and `create` on an existing file fails
  fast with a clear message.
- **Notes larger than 1MiB no longer false-fail verification.** The shared
  `execObsidian` spawnSync used Node's default 1MiB `maxBuffer`, so full-note
  read output over 1MiB was truncated (ENOBUFS) and verification failed after
  retries; raised to 64MiB in the shared helper (also fixes `content_from` /
  direct read of large notes).

## 0.8.15 (2026-09-12)

### Fixed

- `files missing-property=` now fails closed like every other eval helper: the
  eval body is wrapped in the standard try/catch (`wrapEval`) and a rejected
  eval (empty output or `Error:` echo) throws instead of returning as success.

### Documentation

- Merged the duplicate `## 0.8.13` CHANGELOG headings into one entry.
- README: corrected `ensureFolder` name, replaced the eval "auto-escapes bare
  quotes" claim with the actual behavior (auto-`return` wrapping + try/catch),
  and added the enhanced commands missing from the table (`files
  missing-property=`, `files validate-tags=`, `property:rename`, `search …
  replace=`, `frontmatter:wrap`).

## 0.8.14 (2026-08-17)

### Fixed

- **Windows: cross-vault bash guard no longer silently no-ops for forward-slash
  vault paths (#4).** The guard compared raw strings, so `rm D:/MyVault/foo.md`
  (forward slashes) or mixed-case drive letters bypassed it while the CLI
  reports the root with backslashes (`D:\MyVault`). Both sides are now
  normalized (separators always; case on Windows) before the substring check,
  so Windows path-style commands are blocked exactly like their backslash
  twins. POSIX behavior is unchanged (separator-only normalization).

## 0.8.13 (2026-08-08)

### Improvements

- Patch version bump for release sync and package documentation update.

### Fixes

- `vaultWrite` write operations no longer fail with "(no output)" on
  Obsidian 1.13.x. Root causes fixed (live-verified):
  - Adaptive base64 chunking now keeps chunk sizes a **multiple of 4** and
    splits on **UTF-8 character boundaries** — previously, non-multiple-of-4
    chunks silently dropped bytes on decode (e.g. 4998 of 5000 bytes), and
    multi-byte characters (emoji/CJK) split across chunks corrupted to U+FFFD.
  - Eval scripts are sized to stay under the Obsidian 1.13.x ~3100-char
    hang/corruption ceiling (path-length aware), with a 75ms spacing between
    successive evals to avoid payload corruption.
  - Write steps tolerate the empty-echo race (write succeeds but the CLI drops
    the result); the read-back djb2 verification is the single success gate.
    Verification now covers the full content for all modes (full-file hash for
    create/overwrite, tail-hash for append, prefix-hash for prepend).
  - Whole-write retry on verification failure applies to idempotent modes
    only (create→overwrite, overwrite); append/prepend throw instead of
    risking duplicated content.
  - Multi-chunk prepend inserts chunks at the correct offset (was: appended
    to the end, sandwiching old content between prepended chunks).
  - `createFromTemplate` tolerates the empty-echo race with a read-back
    existence check.
  - Removed dead `buildSuffixScript` / `buildPrefixScript`.

### Notes

- The original report's premise (async IIFE → pending Promise → empty stdout)
  was incorrect; `obsidian eval` does await Promises.

## 0.8.12 (2026-08-01)

### Fixes

- `parseCliString` / `readQuotedContent` now support single quotes. Previously
  `eval code='...'` (single-quoted) always failed with `Unexpected identifier
  'Error'` because single quotes were treated as literal characters and split the
  value at whitespace. Single-quote mode is shell-faithful (literal, no escape
  decoding), which also avoids the `\n`-decode footgun in JS code passed to eval.

### Documentation

- Added "Quoting rules for eval" section to SKILL.md: single-quote the outer
  `code=`, double-quote all JS strings inside, use `eval file=NoteName` as the
  escape hatch for code needing both quote types.
- Updated api-examples.md header with the one-line quoting rule.

## 0.8.11 (2026-07-30)

### Improvements

- Patch version bump for release sync and package documentation update.

## 0.8.10 (2026-07-24)

### Fixes

- Hoisted `property:set` argument validation before vault detection.

## 0.8.9 (2026-07-22)

### Fixes

- Closed remaining write silent-failure gaps and hardened cross-vault guards.

## 0.8.8 (2026-07-20)

### Fixes

- Fixed overwrite hang during sync and open-editor lifecycle.

## 0.8.6 (2026-07-16)

### Features

- `vaultWrite` base64+eval for write operations — eliminates all content escaping issues and argv length ceilings.

## 0.8.0 (2026-07-10)

### Features

- Initial 0.8 release with vault-discipline skill, cross-vault guards, and unified `obsidian` tool.
