seekstone 0.15.2 → 0.17.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
@@ -104,7 +104,7 @@ Other MCP clients (Windsurf, Cline, …) take the Option 3 JSON block in their o
104
104
 
105
105
  ## Tools
106
106
 
107
- 19 tools: 10 read, 9 write.
107
+ 21 tools: 11 read, 10 write.
108
108
 
109
109
  ### Read
110
110
 
@@ -120,13 +120,14 @@ Other MCP clients (Windsurf, Cline, …) take the Option 3 JSON block in their o
120
120
  | `get_backlinks` | Find all notes that link to a given note. |
121
121
  | `get_links` | List all outgoing wikilinks and markdown links from a note. |
122
122
  | `get_periodic_note` | Read a daily/weekly/monthly/quarterly/yearly note — path resolved from your vault config, no Obsidian required. |
123
+ | `list_writes` | Recent writes from the journal — seq, timestamp, tool, touched paths, and whether each is still undoable. Metadata only, never note content. |
123
124
 
124
125
  ### Write
125
126
 
126
127
  | Tool | Description |
127
128
  |---|---|
128
129
  | `create_note` | Create a note (optional frontmatter + body); parent dirs created automatically. Never clobbers an existing note unless you pass `overwrite: true`. |
129
- | `delete_note` | Move a note to the vault's `.trash/` folder (Obsidian-compatible, restorable). Pass `permanent: true` for an unrecoverable delete. |
130
+ | `delete_note` | Move a note to the vault's `.trash/` folder (Obsidian-compatible, restorable). Pass `permanent: true` to skip the trash — the write journal still lets `undo_write` restore it. |
130
131
  | `move_note` | Move/rename a note — wikilinks and markdown links in other notes that point at it are rewritten so nothing breaks; destination dirs created automatically. |
131
132
  | `rename_heading` | Rename a heading in a note — every `[[note#heading]]` wikilink and embed across the vault is rewritten so references keep working (aliases preserved, fenced code blocks left alone). |
132
133
  | `append_note` | Append to a note body without touching frontmatter. |
@@ -134,6 +135,7 @@ Other MCP clients (Windsurf, Cline, …) take the Option 3 JSON block in their o
134
135
  | `patch_note` | Append, prepend, or replace text at a heading or block reference (`createIfMissing` to add the section) — frontmatter untouched. |
135
136
  | `replace_in_note` | Find and replace text in the note body — literal or regex, whole-word, case sensitivity, optional `limit` (replaces **all** occurrences by default), dry-run preview. |
136
137
  | `append_periodic_note` | Append to today's periodic note, creating it from a template if it doesn't yet exist. |
138
+ | `undo_write` | Revert a journaled write: every file it touched goes back to its byte-identical pre-write state (a multi-file move or heading rename is restored whole; a delete is restored even if it was `permanent`). Defaults to the most recent write; refuses with `undo_conflict` if a file changed since, unless `force: true`. The undo is itself journaled — `undo_write({ seq })` on the undo entry redoes it. |
137
139
 
138
140
  Every write tool (`append_note`, `patch_note`, `patch_frontmatter`, `replace_in_note`, `rename_heading`, `move_note`, `delete_note`, `append_periodic_note`, and `create_note` with `overwrite: true`) supports optional **compare-and-swap**: pass the `contentHash` you got from `read_note` as `prevHash` and the call fails cleanly if the note changed underneath you — no silently discarded concurrent edit, no moving or deleting content you haven't seen. Every mutating result returns the new `contentHash`, so chained edits need no re-reads.
139
141
 
@@ -147,18 +149,41 @@ Every write tool (`append_note`, `patch_note`, `patch_frontmatter`, `replace_in_
147
149
  | `SEEKSTONE_LOG_LEVEL` | no | `error` \| `warn` \| `info` (default) \| `debug`. |
148
150
  | `SEEKSTONE_LOG_FILE` | no | Absolute path; when set, JSON-line logs are appended here (size-rotated). |
149
151
  | `SEEKSTONE_WATCH_POLL` | no | Set to `1` to stat-poll for changes instead of native OS events — reliable on network drives, WSL, containers. |
152
+ | `SEEKSTONE_WATCH_POLL_INTERVAL` | no | Stat-poll interval in ms (default `10000`). Only used with `SEEKSTONE_WATCH_POLL=1`. Lower = faster pickup of external edits, higher CPU. |
150
153
  | `SEEKSTONE_LOG_MAX_SIZE` | no | Log-rotation threshold for `SEEKSTONE_LOG_FILE` (e.g. `10mb`; default 5 MB). |
151
- | `SEEKSTONE_READ_ONLY` | no | Set to `1` to run read-only: the 9 write tools are unregistered entirely (and rejected if called anyway), so the session provably cannot modify your vault. |
154
+ | `SEEKSTONE_READ_ONLY` | no | Set to `1` to run read-only: the 10 write tools are unregistered entirely (and rejected if called anyway), so the session provably cannot modify your vault. |
152
155
  | `SEEKSTONE_WRITE_PATHS` | no | Comma-separated vault-relative globs (e.g. `journal/**,inbox/*.md`). Writes are permitted only under matching paths; the rest of the vault stays read-only. |
156
+ | `SEEKSTONE_HISTORY` | no | Set to `0` to disable the write journal (default on). With it on, every write tool stores the pre-image of each file it touches under `<vault>/.seekstone/history/` so `undo_write` can restore it byte-for-byte. |
157
+ | `SEEKSTONE_HISTORY_MAX_SIZE` | no | Cap on stored pre-images (e.g. `100mb`; default 50 MB). Oldest entries are evicted first and then show `undoable: false` in `list_writes` — never silently. |
158
+ | `SEEKSTONE_HISTORY_MAX_ENTRIES` | no | Cap on journal entries (default `1000`); the oldest are dropped past it. |
159
+ | `SEEKSTONE_AUDIT_FILE` | no | Absolute path; off unless set. Appends one JSON-line audit record per write-tool call — ok or refused — with the tool, paths, sha-256 before/after, outcome, and op metadata. Never note content. |
160
+ | `SEEKSTONE_AUDIT_MAX_SIZE` | no | Rotate the audit file to `<file>.1` past this size (e.g. `10mb`; default 10 MB). |
153
161
  | `SEEKSTONE_SEMANTIC` | no | Set to `1` to enable semantic search (`search` gains `mode: "semantic"` and `"hybrid"`). Download the local model once with `npx -y seekstone fetch-model`; the running server never touches the network. |
154
- | `SEEKSTONE_MODEL_PATH` | no | Directory holding the Model2Vec embedding model (default: where `fetch-model` puts it). |
162
+ | `SEEKSTONE_SEMANTIC_MODEL` | no | `potion-base-8M` (default, ~30 MB) or `potion-retrieval-32M` (~129 MB, more accurate, ~2× query latency). Fetch it first: `npx -y seekstone fetch-model --model potion-retrieval-32M`. |
163
+ | `SEEKSTONE_MODEL_PATH` | no | Directory holding the Model2Vec embedding model (default: where `fetch-model` puts the selected model). |
155
164
  | `SEEKSTONE_CACHE_DIR` | no | Cache root for the model and per-vault embedding caches (default `~/.cache/seekstone`). |
156
165
 
157
166
  ---
158
167
 
159
168
  ## Write safety
160
169
 
161
- Giving an AI write access to your notes deserves more than "trust us." Seekstone ships the [Write-Safety Contract](https://github.com/shaqmughal/seekstone/blob/main/docs/WRITE-SAFETY.md): **eight named guarantees, each linked to the code that enforces it and the test that proves it**, verified byte-by-byte in CI on every commit and release — zero network, vault sandbox, byte-identical frontmatter on body edits, atomic writes, no-clobber creates, recoverable deletes, optional compare-and-swap, and write scoping / read-only mode.
170
+ Giving an AI write access to your notes deserves more than "trust us." Seekstone ships the [Write-Safety Contract](https://github.com/shaqmughal/seekstone/blob/main/docs/WRITE-SAFETY.md): **ten named guarantees, each linked to the code that enforces it and the test that proves it**, verified byte-by-byte in CI on every commit and release — zero network, vault sandbox, byte-identical frontmatter on body edits, atomic writes, no-clobber creates, recoverable deletes, optional compare-and-swap, write scoping / read-only mode, a write journal that makes every write reversible, and an opt-in audit log that gives every write a hash-verifiable receipt.
171
+
172
+ **Every write is reversible.** Before any write tool changes a byte, it journals the pre-image of every file it is about to touch under `<vault>/.seekstone/history/` — content-addressed (identical states are stored once) and fsync'd before the vault write commits. `list_writes` shows the journal; `undo_write` restores byte-identically: a multi-file `move_note` or `rename_heading` is restored whole (the note *and* every link rewrite), and a `delete_note` comes back even if it was `permanent`. An undo after an external edit is refused with a structured `undo_conflict` unless you pass `force: true` — and even then the clobbered state is journaled first, so nothing is ever lost. Undo is itself journaled: repeated default undos walk backwards through the history, and `undo_write({ seq })` on an undo entry redoes it. `.seekstone/` is excluded from indexing and search like `.trash/`; add it to your vault's `.gitignore`. This complements git and Obsidian's File Recovery rather than replacing them — it is the recovery path the *agent* can drive.
173
+
174
+ **Every write leaves a receipt.** Set `SEEKSTONE_AUDIT_FILE` and every write-tool call — successful *or refused* — appends one JSON line: tool, vault-relative paths, sha-256 before/after, outcome (`ok`, `hash_conflict`, `undo_conflict`, `policy_denied`, `error`), and op metadata such as replacement counts or the `.trash/` destination — never note content, frontmatter values, or search queries, so the file is safe to attach to a bug report.
175
+
176
+ ```jsonl
177
+ {"v":1,"ts":"2026-08-29T21:02:11.042Z","tool":"replace_in_note","outcome":"ok","durationMs":1.8,"seq":42,"files":[{"path":"notes/a.md","hashBefore":"3f9c…","hashAfter":"b71e…"}],"path":"notes/a.md","replacements":3}
178
+ ```
179
+
180
+ The hashes are the same `contentHash` values `read_note` returns, so any record can be checked against the vault; `seq` is the journal entry the call committed, so a row indexes straight into `list_writes` / `undo_write`. Records are appended and fsync'd after the vault write commits, the file rotates to `<file>.1` past `SEEKSTONE_AUDIT_MAX_SIZE`, an unwritable audit path fails boot, and a failed append reports the call as a structured `audit_failed` error rather than a clean success. A few `jq` recipes:
181
+
182
+ ```bash
183
+ jq -r '[.tool, .outcome] | @tsv' audit.jsonl | sort | uniq -c # session summary by tool + outcome
184
+ jq -c 'select(.files[]?.path == "notes/a.md")' audit.jsonl # history of one note
185
+ jq -c 'select(.ts > "2026-08-29T21:00:00Z" and .outcome == "ok")' audit.jsonl # what changed since a timestamp
186
+ ```
162
187
 
163
188
  ---
164
189