generated from eblume/project-template
Phase 1: v1 prototype #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/v1-prototype"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Phase 1 of hephaestus — the v1 prototype, a single long-lived C1 (tech-spec §11.1). Umbrella PR; grows in TDD slices, each a green commit. 102 tests (unit + proptests + real-socket/CLI integration + two-replica convergence).
Progress
The feature-complete local system + the converging sync merge engine are done.
Store/LocalStore; markdown extraction; tasks/links + canonical-context + wiki-link materialization; "what is next?" ranking (proptest total order); recurrence roll-forward (never carries forward) + per-task logs.hephdlocal mode: file lock + line-delimited JSON-RPC over a unix socket; tokio blocking pool; sync client.hephCLI + export: next/task/doc/get/export/search/journal; store →<kind>/<id>.mdtree.list,health(),journal.open_or_create(deterministic id),search(FTS5).metatable).Op;apply_opconverges replicas — LWW (bodies/scalars, with aconflictsqueue for discarded cross-device values), OR-set links, monotonic tombstones, idempotent.adopt_owner(basic §13). Two-replica convergence proven.heph.nvim(obsidian.nvim parity + task views)CRDT lib ratified: yrs (kickoff decision, folded into the tech-spec).
Try it
Testing
cargo test --all— 102 tests green (incl. 6-case two-replica convergence)cargo fmt --check/cargo clippy -D warningsclean;prek run --all-filesclean🤖 Generated with Claude Code
The conceptual core of sync: every mutation records an Op, and foreign ops are applied with merge rules to converge replicas (tech-spec §12). Recording (8b): each node/task/link mutation appends an oplog Op stamped with its HLC — node.create/set/tombstone, task.create/set, link.add/ remove. `Store::ops_since(cursor)` is the push cursor. Merge/apply (8c): `Store::apply_op` replays a foreign op idempotently — - bodies/titles + task scalars: last-writer-wins by HLC; a discarded cross-device value is recorded in `conflicts` (surfaced, not dropped); - links: OR-set add/remove by link id; - tombstones: monotonic. The local clock absorbs each applied HLC. `conflicts_list`/ `conflicts_resolve` expose the queue. `adopt_owner` rewrites a replica to a canonical user id (basic §13 adoption) so replicas can share data. 13 tests: HLC stamping (4) + 6-case two-replica convergence (round-trip, idempotency, scalar LWW + conflict, body LWW, link OR-set, monotonic tombstone). 102 tests green. Body merge is LWW for now; the yrs text CRDT (8d) upgrades concurrent body edits to auto-merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>The primary surface begins (tech-spec §8): a Neovim plugin that is a thin client of the local hephd over its unix-socket JSON-RPC. - node.resolve {title} → Node|null (heph-core Store + dispatch): exact, owner-scoped, non-tombstoned alias-then-title match — the same mapping that materializes wiki links, so follow-link jumps to the node the stored link points at (never fuzzy search). Unit + rpc_socket integration tests. - heph.nvim/: vim.uv unix-socket JSON-RPC client (blocking call via vim.wait, id-demuxed, partial-line buffered, luanil so JSON null → Lua nil; isolated Sessions for tests). Buffer-backed nodes (heph://node/<id>, acwrite; BufReadCmd→node.get / BufWriteCmd→node.update, whole-buffer body round-trips exactly through the CRDT). [[wiki-link]] follow on <CR>. Daily journal. :Heph command surface + completion. - Headless e2e (§9): a self-contained busted-style runner (tests/e2e/runner.lua) — no external plugins, no network, deterministic CI exit codes. Specs: journal round-trip, follow-link (+ unresolved no-op), link-two-docs/backlink. `make -C heph.nvim test` builds hephd and runs it. Docs: heph-nvim reference card, §14 tracker (11a done; 11b/11c/11d queued), changelog fragment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>mise run test-nvim, drop the MakefileBackend (TDD): - task.promote {container_id, item_ref, attention?, project?}: mint a committed task from the item_ref-th `- [ ]` context item (1-based, document order via a new extract::context_item_lines) and rewrite that source line into a [[link]] to it. Unit + rpc_socket tests. - resolve_id now excludes canonical-context docs, so [[Task Title]] resolves to the task, not its identically-titled context doc (deterministic; a general fix surfaced by promotion's ULID-tiebreak ambiguity). Plugin: :Heph promote / promote_under_cursor (save-if-dirty → compute item index with a code-fence-aware scanner mirroring extract.rs → task.promote → reload the rewritten buffer). e2e spec (f): promote a context line, assert the new task in next, the source line became a link, and the container backlinks the task. CI via Dagger: a test_nvim function bakes a pinned, arch-detected Neovim (v0.11.2 — Debian's is too old for vim.uv) onto rust:1-bookworm, builds hephd, and runs the self-contained shim suite (cargo + target cache volumes); build.yaml calls `dagger call test-nvim`. run.lua now fails on zero specs (no false-green). Validated end-to-end: passing suite → exit 0, failing spec → Dagger exit 1. 117 Rust tests + 7 nvim e2e specs green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>mise run devtask + install how-toMake heph a real task driver and the complete daemon-API surface (the three-surface model's capture/scripting role). Structured fields are flags. - datespec: human date parsing (today/tomorrow/+3d/fri/ISO, injectable today for deterministic tests) + compact display; recurrence presets + the common Todoist-style natural-language forms ("every 3 days", "every fri", "every April 15") + raw RRULE passthrough. Table-driven unit tests. - main: new commands covering every RPC — list, done/drop/skip, attention, edit (reschedule via task.set_schedule), promote, show, log (append/tail), health, node update/rm, resolve, links/backlinks, link add, project add [--parent], sync [--status], conflicts [resolve]. task/next/list show human dates; projects referenced by name (resolved, errors if absent). - tests/cli.rs: real-socket process tests for the new verbs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Add a list-by-kind primitive so projects (and later tags) can be enumerated. - core: Store::list_nodes(kind?) — owner-scoped, non-tombstoned, title-sorted; sqlite nodes::list; LocalStore/RemoteStore impls - rpc: node.list {kind?} dispatch - cli: `heph project list` - tests: core list_nodes (kind filter, case-insensitive sort, tombstone exclusion) + cli project_list (projects only, not tasks) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`o` on a task suspends the TUI, opens its canonical-context doc in the owner's nvim via heph.nvim's live buffer surface (+lua require('heph.node').open), then restores the alternate screen and reloads to pick up edits. The child nvim is pointed at the same daemon via $HEPH_SOCKET, so it works under a custom --socket too. This is the KB↔task fusion — edit the description/checklist in the real editor and return straight to triage. handle_key now returns an Action the event loop performs (the suspend/spawn is terminal-owning, kept out of App). nvim arg builder unit-tested; the actual suspend/spawn is interactive so it's exercised manually. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>A new explanation doc making the task model explicit: lifecycle state (outstanding → done/dropped) is orthogonal to attention (white/orange/red/ blue), attention only matters while outstanding, and On Deck (blue) is a live "later" task — NOT the same as dropped ("let go", terminal). Covers delete/ tombstone (soft-delete that keeps the context doc) and a table of where each task shows up (agenda / search / export). Cross-links design §6.2/§6.3 and tech-spec §4.3/§7/§8.1/§12; wired into the explanation index. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>A tag is a `tag`-kind node with a deterministic id in (owner, name) (`tag:<owner>:<name>`, like the journal), so a name is one canonical tag shared across nodes and replicas converge with no duplicates. Tagging is an OR-set `tagged` link (mirroring in-project): - heph-core: `nodes::open_or_create_tag` (bodyless, deterministic id), `tags::{add,remove,of}`, and `Store::{add_tag,remove_tag,tags_of}`. Enumerate all tags via the existing `list_nodes(Tag)`. - hephd: `tag.add`/`tag.remove`/`tag.list` RPCs + RemoteStore forwarding. - heph: `heph tag add|rm|list` (a node's tags, or every tag). Names are trimmed; canonical case/spelling normalization is deferred to the zk import. Unblocks the `tags:` line of the frontmatter surface. Tests: core add/dedupe/remove/canonical-id/trim/missing-node + a socket add/list/enumerate/remove test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>The store-side half of the frontmatter edit surface: - heph-core `frontmatter::strip` runs in `update_node` before the yrs diff, so frontmatter never enters the body or CRDT. Conservative (only a leading `---` block whose first line is a YAML key; a prose hrule survives) and idempotent → read→write round-trip is a no-op. - hephd `frontmatter::render` (local-tz dates via new `datespec::fmt_iso`) behind `node.get {frontmatter: true}`: id/kind/title/tags, and for a task or its canonical-context doc the owning task's scalars + a `task:` ref. Subject-task + project-name resolution in dispatch. Safe against any client (inbound frontmatter always stripped). Tests: strip unit (incl. hrule/idempotency), render unit, socket round-trip + task-context-doc projection. The heph.nvim diff-into-RPCs layer is next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Node buffers now open with the editable YAML frontmatter block on top (node.get {frontmatter: true}); on :w, `frontmatter.lua` parses the block, diffs it against what was rendered, and routes each changed field to the right RPC: - title → node.update rename - attention → task.set_attention - do_date/late_on/recurrence → task.set_schedule (YYYY-MM-DD → local-ms; a removed line clears via null) - project → task.set_project (resolved by name) - tags → tag.add / tag.remove A mistyped state surfaces the daemon's validation error; a buffer with no block edits no metadata (deleting the block can't wipe tags). Body rides node.update as before (the store strips any echoed frontmatter). Body-position features are content-relative, so the prepended block doesn't disturb them; e2e specs that targeted absolute line 1 now locate body lines by content via a new `h.find` helper. New frontmatter_spec covers render + the full diff→RPC round-trip. 21 nvim e2e specs green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>