generated from eblume/project-template
Some checks failed
Build / validate (pull_request) Failing after 18m44s
Make the owner's saved filters first-class so the agenda isn't one flat list. `list` now takes a ListFilter predicate-as-data (heph-core::filter): attention include/exclude sets, project-id scope, exclude_projects, and an actionable do-date gate. New Store::view(name) resolves a built-in ViewSpec — looking project names up to ids and subtree-expanding them through parent links — then lists. Five built-ins seeded from the Todoist queries (design §6.2.1): tom, ondeck, chores, work, tasks (Schedule dropped — time-of-day isn't modeled on date-grained do-dates). Surfaced as `heph view <name>` (no name lists them), the `view` RPC + RemoteStore forward, and `:Heph view <name>` in nvim. The list RPC/RemoteStore/CLI/heph.nvim migrate to the filter wire; legacy --scope/--attention/--no-blue map onto it (nvim view.lua updated). Tests: filter unit predicate, a views integration suite (subtree scope+exclude, actionable gate, unknown-view error, absent-project empties), a socket list/view dispatch test, two nvim e2e specs. 154 Rust tests + 18 nvim e2e green; clippy/fmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
11 KiB
Begin the v1 prototype (Phase 1, tech-spec §11.1), built in TDD slices:
- Cargo workspace +
heph-corecrate; migration-run SQLite schema (§4.5); clock-injectedStoretrait +LocalStorenode create/get; single local-user bootstrap. - Markdown extraction (§5):
[[wiki-links]]and GFM- [ ]checkbox context-items derived purely and idempotently from a body, skipping code blocks. - Committed tasks (§4.3, §6):
task.createauto-creates the canonical contextdoc+canonical-contextlink; attention/do-date/late-on/state/recurrence columns; set-state/set-attention. Links CRUD (outgoing/backlinks). A body update reconcileswikilinks (diff-based, resolved by alias/title, idempotent). - "What is next?" ranking (§7): pure, clock-injected, two-stage engine — candidacy filter (do-date as a boolean gate only) then a reorderable list of named dimensions (past-late-on → overdue-amount → attention band → FIFO).
late_onis the sole urgency signal; blue hidden; red always shown. Proptest-checked total order.Store::nextsurfaces it over SQLite. - Recurrence — roll-forward in place (§4.4): completing a recurring task resets its checklist to all-unchecked, logs the occurrence, and advances the do-date to the next RRULE instance after now (skipping misses) — completion never carries forward (proptest-checked). Per-task append-only logs (
log-of) withlog.append/log.tail;skipadvances without logging. hephddaemon, local mode (§3, §6): exclusive file lock (handoff-ready), line-delimited JSON-RPC over a unix socket exposing the node/task/next/links/log methods, with DB work on tokio's blocking pool. Synchronous client for surfaces/CLI. Model types are serde-serializable.hephCLI (§1) — a thin client of the daemon:next,task,doc,get,export. Export materializes the store to a<kind>/<id>.mdtree with YAML frontmatter + body (§5), one-way, tombstones excluded.- Sync engine, local-only (§12): real hybrid logical clock + persistent device
origin; an append-only op-log per mutation; an idempotent, order-independent merge/apply engine — last-writer-wins task scalars (discards surfaced in aconflictsqueue), OR-set links, monotonic tombstones. Two-replica convergence proven. - Body text CRDT (§5, §12, slice 8d): node bodies now merge through the
yrstext CRDT (body_crdt) instead of last-writer-wins — whole-buffer writes are diffed into the doc and the yrs delta rides the op, so concurrent edits to different regions both survive and never enqueue a conflict. - Network sync over HTTP (§6.1, §12, slice 9a):
hephd --mode serverexposes a sync hub (POST /sync/push,GET /sync/pull?after=<hlc>, axum) over the same store;hephd --mode local --hub-url <url>becomes a spoke that background-syncs its op-log with that hub (and on demand via thesync.now/sync.statusRPC). Exchange is incremental by HLC cursor (sync_state) and idempotent. The merge engine isheph-core's, unchanged. Unauthenticated/single-owner for now (auth lands with OIDC).conflicts.list/conflicts.resolveare now reachable over the daemon socket. - Client mode (§3.1, slice 9b):
hephd --mode client --server-url <url>runs with no local replica, proxying every store call to a server'sPOST /rpcendpoint (the full daemon API over HTTP). The daemon is now backend-agnostic (local/serverfront aLocalStore,clientaRemoteStore), so surfaces see the same unix-socket API in every mode. - Hub authentication (§13, slice 10a): the sync hub now verifies an OIDC bearer token on
/sync/*and/rpc— RS256-pinned JWT validation with exact issuer/audience, expiry, and a required subject; JWKS discovered and cached, refetched on key rotation (jsonwebtoken). Enabled withhephd --mode server --oidc-issuer <url> --oidc-audience <client-id>(open when unset, for local dev). A single-tenant owner gate binds the hub to the first authenticated identity and rejects any other. Verification sits behind aTokenVerifiertrait, so it's tested entirely offline (stub middleware + an adversarial battery against an in-process mock IdP). - Client authentication (§13, slice 10b):
heph auth login --hub-url <url> --issuer <url> --client-id <id>runs the OAuth 2.0 device-code flow and caches the token in the OS keyring; spokes andclientmode attach it to hub requests, refreshing on expiry (--oidc-issuer/--oidc-client-id). Offline-tested against a mock OAuth server and a full spoke-to-authenticated-hub loop. (Auth/proxy HTTP uses the runtime-freeureq, sincereqwest::blockingis unsafe inside the async daemon.) - CI runs the Rust suite (fmt/clippy/test) via the project build hook.
heph.nvimslice 11a (§8) — the primary surface begins: a Neovim plugin that is a thin client of the localhephdover its unix socket. Avim.uvJSON-RPC client (blockingcallviavim.wait, id-demuxed, partial-line buffered, JSONnull→Luanil); buffer-backed nodes (heph://node/<id>withBufReadCmd→node.get/BufWriteCmd→node.update, whole-buffer body round-tripping exactly through the CRDT);[[wiki-link]]follow on<CR>via a new exactnode.resolve {title}RPC (alias-then-title, the same mapping that materializeswikilinks — unresolved links allowed); the daily journal (:Heph today); and the:Hephcommand surface. Headless e2e (§9) drives the plugin against a real daemon over a temp socket with a self-contained busted-style runner (no external plugins, no network): journal round-trip, follow-link, and link-two-docs/backlink.heph.nvimslice 11b (§8) — task views:listis enriched to return titled rows (the same shape asnext, with the canonical-context id) so the Organizational survey needs no per-rownode.get. The plugin gains the Tactical:Heph nextand Organizational:Heph listviews (<CR>opens a task's canonical-context doc), task capture, set-attention, done/drop, skip, and per-tasklogappend — each resolving "the current task" from the buffer (a task node, or a context doc via itscanonical-contextbacklink). Avim.ui.selectpicker (Telescope auto-upgrade when installed) backs:Heph search/capture/attention. Headless e2e adds the capture→next→context→checklist→done workflow and the recurring fresh-checklist workflow (completing a recurring task rolls it forward and the next occurrence presents an all-unchecked checklist).heph.nvimslice 11c (§8) — promotion + CI:task.promotemints a committed task from a- [ ]context-item line (addressed by its 1-based index) and rewrites that line into a[[link]]to the new task;:Heph promotedoes this for the line under the cursor. Wiki-link resolution now excludes a task's canonical-context doc, so[[Task Title]]resolves to the task itself (not its identically-titled context doc). The headless e2e suite runs in CI via a Dagger function that bakes a pinned, arch-detected Neovim onto a Rust image and runs the same self-contained suite developers run natively withmise run test-nvim; the runner fails on a zero-spec discovery so a misconfigured path can't pass silently.heph.nvimmanaged daemon — plug-and-play by default:require("heph").setup({})spawns and supervises a localhephdagainst the default paths when none is running, kills only the daemon it spawned on exit, and self-heals (respawns + reconnects if the daemon dies mid-session). A daemon you started yourself (aserver/clientarchitecture, or a service) is always respected — the plugin only spawns when nothing is serving the socket; withautostart = falseit connects only and warns if unreachable.$HEPH_SOCKET/$HEPH_DBisolate a development Neovim onto a separate daemon + DB.heph.nvimfollow-or-create: pressing<CR>on a[[wiki-link]]whose target doesn't exist yet now creates a doc with that title and opens it (the zettelkasten gesture), materializing the source's backlink — so you can link a journal entry to a brand-new note in one keystroke. Plus:Heph doc <title>to create a standalone wiki entry, and:Heph home— a single designated landing/index page (open-or-create by title, configurable viaopts.home) to grow a map of content around.:Heph journalsopens a recent-days picker (preview existing days,@createfor new ones; count viaopts.journal_days, default 7) — the dailies workflow. Pickers (Telescope) now support a preview pane. The:Heph next/listviews are interactive:<CR>opens a task's context,aadds a task (prompt title + attention),dmarks the task under the cursor done,rrefreshes — with a dimmed key hint shown above the list.- Dev/installed isolation tooling: a
mise run devtask runs the working-treehephdon isolated.dev/paths, and a how-to (install-heph) covers installingheph/hephdfrom the forge (build-from-source), the lazy.nvim plugin setup, and pointing a dev Neovim at the dev daemon via$HEPH_SOCKET/$HEPH_DBso it never touches the installed store. - CLI as a complete task surface (§1, §6.2.1):
hephnow implements the entire daemon API and is the task capture/scripting surface. Structured fields are flags with human dates (--do-date tomorrow|+3d|fri|YYYY-MM-DD, shown back compactly innext/list) and recurrence (--recurpresets/natural-language like "every 3 days", or a raw--rrule). New verbs:list,done/drop/skip,attention,edit(reschedule do-date/late-on/recurrence, re-attention, re-file — backed by the newtask.set_scheduleRPC),promote,show,log(append or tail),health,node update/rm,resolve,links/backlinks,link add,project add [--parent],sync [--status],conflicts [resolve]. Projects are referenced by name. Date/recurrence parsing is unit-tested; the new verbs have real-socket process tests. - Daemon lifecycle is now an explicit OS service, and all surfaces are connect-only (no more auto-spawn).
heph daemon start/stop/restart/status/uninstallidempotently manages a launchd agent (macOS) or systemd user service (Linux) that runshephdon your default store;heph.nvimno longer spawns or supervises a daemon — it just connects and points you atheph daemon startif none is running. Rationale: once the CLI became a first-class surface, a daemon owned by one surface couldn't be shared (see run-the-daemon, design §4). - Filter views (§8.2) — saved agenda slices, so the agenda isn't one flat list.
heph view <name>runs a built-in view (tomTop of Mind,ondeckOn Deck,chores,workWork Tasks,tasks) seeded from the owner's Todoist filter queries;heph viewwith no name lists them, and:Heph view <name>does the same in Neovim. Under the hood,listnow takes aListFilterpredicate-as-data (attention include/exclude sets, project-subtree scope, project exclusions, an actionable do-date gate), and views resolve project names to ids and expand each to itsparent-link subtree. The Schedule view is intentionally omitted (time-of-day isn't modeled on date-grained do-dates).