hephaestus/docs/reference/heph-nvim.md
Erich Blume e3db2ac550 heph.nvim: plug-and-play managed daemon (autostart, self-heal, client/server guardrail)
The plugin now manages its own hephd by default (autostart = true): if nothing
is serving the socket it spawns a local daemon against the default XDG paths,
kills only what it spawned on VimLeavePre, and self-heals — rpc.call retries
once through a respawn hook when the connection drops (the prior owner releases
the DB lock on exit, so a respawn can claim it).

- daemon.ensure() connects to an already-running daemon (any mode) or spawns one
  we own; stop_spawned()/is_managed() track lifecycle.
- A server/client daemon you started is always respected (spawn only when nothing
  serves the socket). autostart = false → connect-only, warns/errors if down,
  and clears the self-heal hook so it fails loudly.
- config: autostart defaults true; new `db` option; $HEPH_SOCKET / $HEPH_DB
  fallbacks isolate a dev Neovim onto a separate daemon + DB.

e2e: managed_daemon_spec covers autostart spawn, self-heal-after-kill, and
connect-only error. 10 specs green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:37:49 -07:00

5.7 KiB

title modified tags
heph.nvim 2026-06-01
reference
design

heph.nvim

The primary user surface (tech-spec §8): a Neovim plugin that replaces obsidian.nvim and is a thin client of the local hephd over its unix-socket JSON-RPC. Notes, journals, and tasks are edited as ordinary buffers; the daemon owns all storage and sync. Built in checkpointed slices on feature/v1-prototype; this card tracks the stable surface as it lands.

Architecture

heph.nvim/lua/heph/ modules, each small and single-purpose:

Module Responsibility
rpc libuv (vim.uv) unix-socket JSON-RPC client. A blocking call() is built over the async pipe by pumping the loop with vim.wait until the matching id returns. Demuxes responses by id; partial lines are buffered; JSON null decodes to Lua nil (luanil). A Session is one connection — the module keeps a default singleton and lets tests open isolated sessions.
node Buffer-backed nodes. A node is a buffer named heph://node/<id> with buftype=acwrite; BufReadCmd loads the body via node.get, BufWriteCmd saves the whole buffer via node.update.
link Parse the [[wiki-link]] under the cursor (mirroring extract.rs grammar) and follow it via node.resolve (exact, never fuzzy search). Unresolved links are allowed.
journal Open/create a dated journal node (idempotent — deterministic id).
daemon Managed-daemon lifecycle: ensure (connect if a daemon already serves the socket, else spawn one we own), stop_spawned (kill only what we spawned, on exit), readiness-poll. Shared with the e2e harness.
config / init setup(opts), socket resolution, default keymaps.
command The :Heph <subcommand> dispatch + completion.

Surfaces never touch SQLite — every operation is a daemon RPC (tech-spec §3). The plugin is mode-agnostic: Tactical/Strategic/Organizational are plugin-side compositions of daemon primitives, not daemon concepts.

Daemon lifecycle

setup({}) is plug-and-play by default (autostart = true): if nothing is serving the socket, the plugin spawns a local hephd against the default XDG paths, kills only the daemon it spawned on VimLeavePre, and self-healsrpc.call retries once through a respawn hook if the connection drops. It only ever spawns when nothing is already serving the socket, so a server/client daemon you started is respected. With autostart = false the plugin connects only and warns/errors if unreachable — for when you run your own daemon. The $HEPH_SOCKET / $HEPH_DB env knobs (and mise run dev) isolate a dev Neovim onto a separate daemon + DB so real data is never touched.

Daemon RPC dependencies

Beyond the existing methods (tech-spec §6), the plugin relies on node.resolve {title} → Node | null: an exact, owner-scoped, non-tombstoned alias-then-title match — the same mapping the store uses to materialize wiki links, so "follow link under cursor" jumps to the same node the stored link points at.

Commands (as of slice 11c)

Command Action
:Heph today / :Heph journal <YYYY-MM-DD> Open today's / a dated journal
:Heph follow (also <CR> in a node buffer) Follow the [[link]] under the cursor
:Heph open <id> Open a node buffer by id
:Heph search <query> Full-text search; pick a result to open
:Heph next [scope] Tactical "what is next?" view (<CR> opens a task's context)
:Heph list [attention] Organizational survey of the outstanding set
:Heph capture <title> Capture a committed task (pick attention)
:Heph attention [color] Set the current task's attention
:Heph done / :Heph drop / :Heph skip State change on the current task
:Heph promote [attention] Promote the - [ ] line under the cursor to a committed task
:Heph log <text> Append a breadcrumb to the current task's log

"Current task" is resolved from the buffer: a task node, or a canonical-context doc whose owning task is followed via its canonical-context backlink. The next/list views render the titled rows the daemon returns (list enriched to carry titles + the context id, so no N+1 node.get). Pickers use built-in vim.ui.select, auto-upgrading to Telescope when installed.

Promotion (:Heph promote) mints a committed task from the - [ ] line under the cursor (the daemon's task.promote, item_ref = the cursor item's 1-based index among context items, computed code-fence-aware to mirror extract.rs) and rewrites that line into a [[link]] to the new task. To keep that link unambiguous, wiki-link resolution excludes canonical-context docs, so [[Task Title]] resolves to the task, not its identically-titled context doc.

Testing (tech-spec §9)

The headless e2e suite drives the plugin in nvim --headless against a real hephd over a temp socket, asserting both buffer contents and resulting DB state (via an isolated RPC session). It uses a self-contained busted-style runner (tests/e2e/runner.lua) — no external plugins, no network — so it is deterministic. mise run test-nvim builds the daemon and runs the suite against system-installed Neovim; a deliberately failing spec exits non-zero (no false-green; the runner also fails if it discovers zero specs). CI runs the same suite through the test-nvim Dagger function (.dagger/, invoked by build.yaml as dagger call test-nvim), which bakes a pinned, arch-detected Neovim onto a Rust image, builds hephd, and runs the suite — reproducible, and identical to the native mise run test-nvim path.

  • tech-spec — §8 surface spec, §6 RPC API, §9 testing strategy
  • design — the mode model (Tactical/Strategic/Organizational) and rationale