Skip to content

v3.11.0

Choose a tag to compare

@github-actions github-actions released this 01 Jul 19:53

Added

  • Deterministic enforcement hooks, on by default with the Claude Code native-memory
    override (ADR-0030).
    Makes the ADR-0029 doctrine hold for ANY model — old or new,
    not just ones that reliably read prose rules:

    1. PreToolUse guard (guard-native-memory-write.mjs) — DENIES Write/Edit/
      MultiEdit/NotebookEdit calls that target the native auto-memory directory
      (~/.claude/projects/*/memory/), redirecting the model to vault_write_file/
      vault_edit_file.
    2. Stop nudge (stop-vault-close-reminder.mjs) — when a session did substantive
      file work (≥2 Write/Edit/MultiEdit/NotebookEdit calls) but never touched the
      vault's close-ritual tools, blocks the stop once with a reminder to close out —
      with an explicit "ok to skip if nothing's reusable" escape hatch so it never forces
      low-value writes. Loop-safe via stop_hook_active.
    • Both ship as cross-platform Node scripts (same precedent as the SessionStart hook),
      installed into ~/.claude/hooks/ and merged idempotently into
      ~/.claude/settings.json (hooks.PreToolUse, hooks.Stop).
    • Opt out of just these two with --no-memory-enforcement (keeps
      autoMemoryEnabled:false + the SessionStart hook); force on under --minimal with
      --memory-enforcement. 19 new tests in claude-native-memory.test.mjs.
  • Effort-gate hook, on by default with the Claude Code native-memory override,
    independent of the enforcement pair above (ADR-0031).
    Makes a pause real instead of
    just announced: a PreToolUse hook (guard-effort-gate.mjs) DENIES a session's 2nd+
    substantive Write/Edit/MultiEdit/NotebookEdit call until the model has proposed
    an effort level (/effort low|medium|high|xhigh|max) and gotten an actual reply from the
    user — not just printed a "pausing here" message and kept going. The first substantive
    edit of a session is always free (no nagging on one-line fixes), and once satisfied the
    gate stays open for the rest of the session. Correctly distinguishes a real user reply
    from a tool-result message (Claude Code encodes both as type:"user"), and the marker
    text the model must print is taught in-band by the hook's own denial message, no separate
    rules-file change needed. Not a token-saving feature — each time it fires it costs at
    least one extra turn; its value is letting the user redirect a misscoped task before the
    session commits to it, documented as a deliberate trade-off in ADR-0031. Opt out with
    --no-effort-gate; force on under --minimal with --effort-gate. 18 new tests in
    claude-native-memory.test.mjs.

  • New package @vkmikc/obsidian-prompt-compiler (obsidian-prompt CLI + optional GUI).
    Compiles a one-line idea + vault context into an <orchestration_package> XML prompt and
    copies it to the clipboard, for pasting into AI tools that don't have the vault's MCP
    wired (a web chat, another editor without the wiring). No LLM call: context comes
    straight from vault_observations (already-distilled typed facts) and
    vault_hybrid_search, via the same Python bridge the hybrid MCP server uses, with the
    search qualified by the project name (+ --graph) and snippets capped at 320 chars so a
    large real vault's cross-project notes don't drown out the project's own context; when a
    project note has no formal observations yet (rich prose instead of - [decision] ...
    bullets), falls back to a capped raw excerpt of the note itself rather than coming up
    empty. Interactive project picker (PROJECTS/*.md autocomplete), optional
    $EDITOR/$VISUAL review pass before copying, and a reduced XML schema (trimmed from a
    generic ~35-tag prompt-engineering catalog down to the leaves that matter for a coding
    task — dropped boilerplate self-check tags like <thinking>/<reflect>/<verify>).
    Optional GUI (obsidian-prompt-gui, or --install-shortcut for a hidden-window
    Desktop launcher on Windows): a localhost-only node:http server + a vanilla-JS page
    (no framework, no build step — picked over Tauri/Electron for being the lightest/fastest
    option) with a live XML preview as you type (debounced) and one-click clipboard copy via
    navigator.clipboard. Extracted rag-client.mjs out of
    obsidian-memory-mcp/src/hybrid-mcp.mjs (pure refactor, same Python-bridge behavior) so
    both packages can reuse it without spawning the MCP stdio server. 34 new tests across the
    new package; versioned independently of the rest of the kit (not in
    scripts/version.mjs's MARKERS), private: true, not published.

Fixed

  • The sync daemon (obsidian-memoryd) reported healthy while the vault was
    silently not syncing.
    doctor's alarm keyed only on push failures, so a
    failing add / commit / pull --rebase (e.g. expired git credentials breaking
    the rebase) returned an error that was only written to the JSONL log — the
    heartbeat stayed fresh, the push counter never moved, and doctor exited 0
    indefinitely while nothing reached the remote. gitSync now records the outcome
    of the whole cycle: new ConsecutiveSyncFailures / LastSyncOK /
    LastSyncError state, surfaced by doctor and alarming at ≥3 consecutive sync
    failures (a transient failure that recovers self-clears on the next good sync).
    Added regression tests for the pull-failure and stuck-sync paths; normalized the
    daemon package with gofmt.
  • npm run setup's verify step now confirms the Python backend actually runs,
    not just that the index file exists. Because --install-backend / --build-index
    are best-effort, a failed backend install plus a leftover fts.sqlite previously
    showed an all-green table while every vault_hybrid_search failed at first use —
    the exact "No Python at …" / import-error landmine that leaves memory silently
    dead. The new check invokes the backend the way the MCP will
    (python -m obsidian_memory_rag --help + the wired PYTHONPATH) and points at the
    troubleshooting guide when it fails. Fresh-PC quick-check tables gained the same
    restart-free CLI check.
  • [[wikilink]]/relation parsing mistook documentation examples for real edges.
    audit.py, graphlink.py and knowledge_graph.py scanned raw note text for
    [[...]] with no Markdown-structure awareness, so a note that documents the
    syntax (e.g. `[[target]]` inline, or a fenced snippet showing
    - implements [[adr-0014]]) was parsed as a real broken link / real relation.
    New text_scrub.strip_code_regions (blanks fenced code blocks + inline code
    spans, preserving line/char offsets) is now applied before every wikilink/
    relation/observation scan, plus a fence-aware heading check in chunking.py
    (a #-prefixed line inside a code fence is real code, not a section heading).
    Verified against a real ~200k-token vault: false-positive broken links dropped
    14→2, bogus relations dropped 40→36. The fence regex needs \r?$, not just
    $ — CRLF-terminated notes silently defeated the naive version.
  • Every MCP tool response was pretty-printed JSON (JSON.stringify(v, null, 2))
    — ~15-25% wasted tokens on every search/relations/observations/report call for
    an LLM consumer that gets no benefit from indentation. Now compact.
  • session-start-vault-context.mjs had no cap on the _meta/index.md dump it
    injects into every session unconditionally — already ~10KB/~2500 tokens on a
    modest 69-note vault, and grows with every project added. Capped at 4000 chars
    with a vault_read_file pointer for the rest; also added the standard
    isEntryPoint import guard (was missing, unlike every other managed hook).
  • vault_read_file had no size cap on a whole-file read (no head/tail) —
    one large note could return unbounded tokens. Default cap 200,000 chars
    (configurable via maxChars), with a truncation notice pointing at head/tail.
  • memory_extract_candidates ran its per-bullet dedup lookups serially (one
    Python subprocess spawn at a time) and silently swallowed backend failures
    as "no duplicate found." Now parallelized (Promise.all) and surfaces a
    backendError per candidate so a broken index/Python env can't masquerade as
    a confirmed-new bullet.
  • build_report(duplicates=True) instantiated the embedder twice — once in
    ensure_fresh to actually build vectors, again just to read .name (a second
    ONNX model load when fastembed is the active embedder). New
    resolve_embedder_name() computes the identity string without constructing
    the model.
  • audit_vault's oversized/broken_links lists had no cap, unlike the
    rest of the memory-report hygiene indices (stale_notes/orphan_notes/
    hub_notes were already capped) — a messy vault could return hundreds of
    entries verbatim. Capped (default 100) with an accompanying _total count.

Documentation

  • New troubleshooting section — "Hybrid search and the Python backend (Claude
    Code)"
    (docs/en/troubleshooting.md + docs/es/troubleshooting.md), with a
    Mermaid decision tree and a two-command health check (claude mcp list +
    a live vault_hybrid_search). Covers the Claude-Code-specific failure modes the
    previous docs (Cursor-centric) didn't: the hybrid MCP missing/Failed to connect, an orphaned uv Python venv (No Python at …), an unbuilt index, and
    an MCP registered against a stale second clone.
  • ARCHITECTURE.md and the "how it works" guides updated to reflect the code-span
    scrubbing, the compact-JSON tool responses, and the vault_read_file size cap.