dsh-mnemon-sync-monitor
Real-time file watcher that keeps dsh-mnemon Project Documents in sync with local Markdown knowledge bases.
dsh-mnemon's Documents Source is a snapshot store: it imports files once and never notices later edits, additions, or deletions. This plugin closes that gap. It watches your knowledge bases and reconciles changes within a debounce window.
Why this exists
Measured behaviour of dsh-mnemon 0.5.39 before this plugin:
| Change to your knowledge base | Automatic? | Why |
|---|---|---|
| New file | No | No file-system watcher anywhere in the Documents Source |
| Edited file | No | The store holds a copy, not a reference |
| Deleted file | No | The copy survives the source forever |
A grep for fs.watch, chokidar, or FSWatcher across dsh-mnemon-source-documents returns zero hits. There is also no reindex path: index.json is the only source of truth and nothing rescans directories.
What it does
| Source change | Action |
|---|---|
| New file | Create a document |
| Content hash changed | Update the document in place, revision + 1 |
| File deleted | Archive the document (only with prune: true) |
| Unchanged | Skip |
Changes are coalesced with a debounce window, then applied atomically under the Documents index lock.
Install
dsh plugin --profile <name> add /path/to/dsh-mnemon-sync-monitor
Then configure the roots to watch in your profile's cordis.patch.yml:
- id: mnemon-sync-monitor
name: dsh-mnemon-sync-monitor
config:
enabled: true
roots:
- C:\Users\me\Obsidian\vault
- C:\Users\me\Logseq\pages
debounceMs: 800
minBytes: 200
Restart DSH. The plugin reconciles each root once at startup, then follows changes.
Settings page
The bundle ships a Client half: a Settings → 知识库同步 page listing every watched root.
| Action | Effect |
|---|---|
| See roots | Each row shows the path, whether it exists, and its Markdown count |
| Add | Validates the path is an existing directory, then starts watching it immediately |
| Remove | Stops watching; documents already synced stay in the store |
| Rescan | Runs one reconciliation pass over every watched root now |
The page writes through the Host route /mnemon-sync-monitor, which owns the connection trust fence. Edits are persisted into the plugin's own row in the profile cordis.patch.yml, and every other line of that file is preserved byte for byte — the writer replaces only the roots: sequence rather than re-serializing the YAML, so comments and neighbouring rows survive.
Adding or removing a root takes effect without a restart: the watcher is attached or detached live. The configuration file is still updated so the change survives the next boot.
API
| Method | Path | Purpose |
|---|---|---|
| GET | /mnemon-sync-monitor/state |
Roots with existence and Markdown counts, store path, patch path |
| POST | /mnemon-sync-monitor/roots |
Add {root}; 400 invalid, 409 duplicate |
| POST | /mnemon-sync-monitor/roots/remove |
Remove {root}; 404 unknown |
| POST | /mnemon-sync-monitor/rescan |
Reconcile all roots now |
Configuration
| Field | Default | Meaning |
|---|---|---|
enabled |
true |
Master switch. |
roots |
[] |
Absolute directories to watch. Empty disables the plugin. |
storeDirectory |
~/.mnemon/documents |
Documents store to write into. |
debounceMs |
800 |
Quiet window before reconciling. |
minBytes |
200 |
Files smaller than this are ignored (Logseq empties are often <200 B). |
prune |
false |
Archive store records whose source file disappeared. |
dryRun |
false |
Detect and log without writing. |
limitBytes |
33554432 |
Active capacity ceiling (32 MiB). |
Safety properties
These are enforced by tests in test/sync.test.js:
- Cross-root isolation. Each record records its owning root in
sourcePaths[0]as<absolute-root>\u0000<relative>. Pruning one root can never archive another root's documents. - Lock interoperability. Writes take
.index.lockwithwxexclusive create, retry every 20 ms, give up after 5 s, and steal a lock older than 30 s — identical to the Documents Source, so a running Host and this watcher never corrupt the index. - Atomic index writes. The index is written to a temporary file and renamed, matching the Source.
- Capacity ceiling. Documents beyond
limitBytesare skipped rather than failing the whole pass. - Single-document ceiling. Documents over 2 MiB are skipped.
Important operational note
dsh-mnemon pins Project Documents into a View when the session starts (the route description is literally "Search only the project Documents pinned into this View"). The watcher updates the store immediately, but a running session will not see new documents until a new session starts.
Development
node --test "test/*.test.js" # 20 unit tests, no dependencies
node test/e2e.mjs # end-to-end watcher test
node test/route.e2e.mjs # REST route round trip
node test/live.mjs <kb-root> # validate against a real knowledge base
No runtime dependencies. Node 22+ (uses fs.watch with recursive).
Measured performance
Against a real 389-file Obsidian vault: a full import of 326 documents took 362 ms.
License
MIT
No comments yet. Be the first to write one.