Phase 1: v1 prototype #1

Merged
eblume merged 91 commits from feature/v1-prototype into main 2026-06-03 20:48:23 -07:00
Owner

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.

  • heph-core library: schema + 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.
  • hephd local mode: file lock + line-delimited JSON-RPC over a unix socket; tokio blocking pool; sync client.
  • heph CLI + export: next/task/doc/get/export/search/journal; store → <kind>/<id>.md tree.
  • Query surface: list, health(), journal.open_or_create (deterministic id), search (FTS5).
  • Sync 8a — HLC + origin: clock-injected monotonic HLC stamping every write; persistent device origin (meta table).
  • Sync 8b/8c — op-log + merge engine: every mutation records an Op; apply_op converges replicas — LWW (bodies/scalars, with a conflicts queue for discarded cross-device values), OR-set links, monotonic tombstones, idempotent. adopt_owner (basic §13). Two-replica convergence proven.
  • Sync 8d — yrs text CRDT for bodies (concurrent body edits auto-merge; currently LWW)
  • Slice 9 — server/client modes + network push/pull + conflict-queue RPC (transport over the existing engine)
  • Slice 10 — OIDC/Authentik auth + per-user isolation
  • Slice 11 — heph.nvim (obsidian.nvim parity + task views)

CRDT lib ratified: yrs (kickoff decision, folded into the tech-spec).

Try it

cargo run -p hephd          # daemon (local mode)
heph task "Fix the roof" --attention red
heph next ; heph search roof ; heph export ./snapshot

Testing

  • cargo test --all — 102 tests green (incl. 6-case two-replica convergence)
  • cargo fmt --check / cargo clippy -D warnings clean; prek run --all-files clean

🤖 Generated with Claude Code

## 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.** - [x] **heph-core library:** schema + `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. - [x] **`hephd` local mode:** file lock + line-delimited JSON-RPC over a unix socket; tokio blocking pool; sync client. - [x] **`heph` CLI + export:** next/task/doc/get/export/search/journal; store → `<kind>/<id>.md` tree. - [x] **Query surface:** `list`, `health()`, `journal.open_or_create` (deterministic id), `search` (FTS5). - [x] **Sync 8a — HLC + origin:** clock-injected monotonic HLC stamping every write; persistent device origin (`meta` table). - [x] **Sync 8b/8c — op-log + merge engine:** every mutation records an `Op`; `apply_op` converges replicas — LWW (bodies/scalars, with a `conflicts` queue for discarded cross-device values), OR-set links, monotonic tombstones, idempotent. `adopt_owner` (basic §13). Two-replica convergence proven. - [ ] Sync 8d — yrs text CRDT for bodies (concurrent body edits auto-merge; currently LWW) - [ ] Slice 9 — server/client modes + network push/pull + conflict-queue RPC (transport over the existing engine) - [ ] Slice 10 — OIDC/Authentik auth + per-user isolation - [ ] Slice 11 — `heph.nvim` (obsidian.nvim parity + task views) CRDT lib **ratified: yrs** (kickoff decision, folded into the tech-spec). ## Try it ``` cargo run -p hephd # daemon (local mode) heph task "Fix the roof" --attention red heph next ; heph search roof ; heph export ./snapshot ``` ## Testing - [x] `cargo test --all` — 102 tests green (incl. 6-case two-replica convergence) - [x] `cargo fmt --check` / `cargo clippy -D warnings` clean; `prek run --all-files` clean 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Scaffold cargo workspace + heph-core foundation
Some checks failed
Build / validate (pull_request) Failing after 3s
bbac338f76
Kick off Phase 1 (v1 prototype) per tech-spec §11.1. Sets up the Cargo
workspace and the first TDD slice of heph-core:

- Migration runner + §4.5 SQLite schema (nodes, tasks, links, aliases,
  users, oplog, sync_state, conflicts), versioned via PRAGMA user_version.
- Clock-injected `Clock` trait (no ambient wall-clock reads; §2).
- `Store` trait + `LocalStore` SQLite backend with node create/get,
  bootstrapping the single local user (oidc_sub NULL, §13).
- Node model (kinds: doc/task/project/tag/journal).

Repo housekeeping: fill AGENTS.md Project Structure (last template TODO),
ignore /target, add self-bootstrapping .forgejo/scripts/build that runs
cargo fmt/clippy/test in CI (§9), changelog fragment.

Tests green: 4 unit tests (migration version, local-user idempotency,
create/get round-trip, missing-node None).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core: markdown extraction (wiki-links + checkboxes)
Some checks failed
Build / validate (pull_request) Failing after 3s
1995e0e3cf
Slice 2 (tech-spec §5). Pure, deterministic derivation from a body:

- `[[wiki-links]]` → wiki-link targets, in first-seen order, deduped,
  honoring `[[target|display]]`. Scans the raw body (CommonMark mangles
  `[[ ]]` brackets in inline parsing) and excludes matches inside code,
  whose byte ranges come from pulldown-cmark's offset iterator.
- GFM `- [ ]` / `- [x]` task items → the local context-item index
  (Fork A): label keeps raw markdown (for promotion) + checked state.
- Code blocks are correctly skipped for both.

10 extraction unit tests incl. idempotency; 14 total green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core: tasks, links, canonical-context doc
Some checks failed
Build / validate (pull_request) Failing after 3s
b9d2072f75
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.

- Model: Attention (white/orange/red/blue), TaskState
  (outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
  canonical context doc, the canonical-context link, and an optional
  in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
  node's wiki links — diff-based and idempotent, resolved via alias then
  exact title (owner-scoped); unresolved targets link on a later edit
  once the target exists; dropped targets are tombstoned.

20 tests green (12 unit + 8 integration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core: "what is next?" ranking (tech-spec §7)
Some checks failed
Build / validate (pull_request) Failing after 3s
7f63f926d0
Slice 4 — the flagship Tactical blank-slate engine. Pure and
clock-injected, two stages:

- Candidacy filter: committed ∧ outstanding ∧ ¬tombstoned ∧ ≠blue ∧
  actionable (do_date NULL or ≤ now) ∧ in scope. do_date is used ONLY
  here — a boolean "can I do this now?" gate, never urgency.
- Order: an ordered list of named Dimensions applied lexicographically
  (PastLateOn → LateOverdueAmount → Attention band → CreatedAt FIFO),
  with node_id as final tiebreak for a total order. Reorder RANKING in
  one place to retune. late_on is the sole urgency signal (global tier);
  age never becomes urgency. blue hidden; red always shown past limit.

Storage `Store::next` loads candidates via a SQL join (project +
canonical-context links) and runs the pure engine with the store clock.

13 table-driven unit cases + 3 proptests (antisymmetry, sorted output
fully ordered, equality ⇒ identity) + 2 end-to-end. 38 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core: recurrence (roll-forward in place) + per-task logs
Some checks failed
Build / validate (pull_request) Failing after 4s
d0debfceb9
Slice 5 (tech-spec §4.4). Completing a recurring task rolls it forward in
place instead of marking it done — the Todoist-corner-avoiding model.

Pure recurrence module:
- next_occurrence(rrule, anchor, after): lazy RRULE expansion (rrule +
  chrono/UTC) returning the next instance strictly after `after`,
  skipping missed occurrences; None when a finite series is exhausted.
- reset_checkboxes(body): the fresh-checklist transform — unchecks every
  `- [x]`, idempotent, preserves indentation/bullet/line-endings.

Storage roll-forward (one transaction, on set_state(done) of a recurring
task): reset the canonical context doc's checklist, append the completed
occurrence to the task's log, advance do_date to the next instance after
now (skipping misses); finite series finally goes done. `skip` advances
the same way without logging. Non-recurring done is unchanged.

Per-task append-only log (`log-of` doc): log_append / log_tail — the
resumption breadcrumb + recurring-completion narrative ([[design]] §6.4).

Tests: 7 recurrence unit + 2 proptests (no checked marker survives reset;
reset idempotent for any body) + 6 end-to-end incl. five-occurrence
no-carry-forward and missed-collapse-to-one. 53 tests green. This
completes the heph-core library layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hephd local mode: file lock + JSON-RPC over unix socket
Some checks failed
Build / validate (pull_request) Failing after 3s
ed8c7a733a
Slice 6 (tech-spec §3, §6, §10). First async component — the per-device
daemon in local mode.

- `LockGuard`: exclusive advisory flock on a sidecar `<db>.lock`; a second
  acquire fails and releases on drop (the §3.1 lock handoff).
- JSON-RPC (line-delimited): `rpc::dispatch` maps node/task/next/links/log
  methods onto the heph-core Store; `Daemon::serve` accepts unix-socket
  connections and runs dispatch on tokio's blocking pool behind an
  Arc<Mutex<LocalStore>> (DB never touches an async worker).
- Synchronous `Client` for surfaces/CLI; `hephd` binary (clap) opens the
  store under lock and serves the default socket.
- heph-core model/ranking types are now serde-(de)serializable; added
  node.tombstone + Store::tombstone_node.

Tests: 2 lock unit tests + 5 real-socket e2e (round-trip with clock
injection, next, error paths, recurring roll-forward over RPC, 8-client
concurrency). 60 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph CLI + export
Some checks failed
Build / validate (pull_request) Failing after 2s
739214bd07
Slice 7 (tech-spec §1, §5, §9).

- Export (heph-core): render each non-tombstoned node to `<kind>/<id>.md`
  with YAML frontmatter (id, kind, title, timestamps, task scalars,
  aliases, outgoing links) + body. One-way snapshot; `Store::export`
  writes the tree; tombstones excluded. Added `export` RPC method and
  Error::Io.
- `heph` CLI (clap): thin client of hephd over the socket — `next`
  (concise ranked rows), `task`, `doc`, `get`, `export`. Never touches
  SQLite directly.

Tests: 3 export render unit + 2 export round-trip integration + 3 CLI
process tests driving the real `heph` binary against a real daemon
(task→next, empty-store message, export writes files). 70 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round out the local query surface ahead of the distributed layer:

- list(scope, attention, include_blue): the Organizational survey —
  outstanding committed tasks incl. backlog, with project/attention
  filters and an include_blue toggle.
- health(): working-set tensions surfaced honestly — orange / active
  (white+orange+red) / on-deck (blue) counts; conflict_count + sync_status
  reserved for sync.
- journal.open_or_create(date): deterministic id in (owner, ISO-date)
  (§3.1) so offline replicas converge; idempotent; rejects non-ISO dates.
- Exposed over RPC (list / health / journal.open_or_create).

6 integration tests; 76 total green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core: full-text search (FTS5)
Some checks failed
Build / validate (pull_request) Failing after 3s
5d8ec45c55
Slice query-surface, part 2 (tech-spec §6). Migration v2 adds an FTS5
external-content table over nodes(title, body), kept in sync by
insert/update/delete triggers (with a backfill for existing rows).

- Store::search(query): owner-scoped, tombstones excluded, best-match
  first (FTS5 MATCH + rank). Exposed over RPC; `heph search` and
  `heph journal` CLI commands added.

3 search integration tests (title/body match, edits reflected via trigger,
tombstone exclusion, all insert paths indexed). 79 tests green. This
completes the local feature surface; the remaining slices are the
distributed/auth/nvim layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core: real HLC + persistent device origin (sync 8a)
Some checks failed
Build / validate (pull_request) Failing after 4s
c81d45a291
First sync slice. Ratifies yrs for body merge in the tech-spec.

- hlc module: Hlc (physical, counter, origin) with a fixed-width encoding
  whose lexical order equals causal order; HlcClock generator (tick/update)
  — clock-injected, strictly monotonic. Unit + 2 proptests.
- meta table (migration v3) holds the stable per-device `origin` and the
  last HLC. next_hlc() does a read-modify-write inside the caller's
  transaction (store is single-writer, so no race), replacing the
  timestamp placeholder. Every node write is now stamped with a real,
  monotonic, causally-ordered HLC.

4 stamping integration tests (monotonic under stalled/regressed clock;
origin shared + persists across reopen; HLC resumes from persisted state).
89 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core: op-log recording + merge/apply engine (sync 8b/8c)
Some checks failed
Build / validate (pull_request) Failing after 3s
b05ddf4bb5
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>
docs: Phase 1 progress tracker (design roadmap + tech-spec §14)
Some checks failed
Build / validate (pull_request) Failing after 5s
662a360da8
Record what's built and the resume order for the next context session:
heph-core + hephd local mode + CLI/export + local query surface + the
sync engine (HLC, op-log, converging merge/conflict-queue) are done;
resume at yrs body-CRDT → network sync → OIDC auth → heph.nvim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs: rewrite README for the hephaestus application
Some checks failed
Build / validate (pull_request) Failing after 2s
16b552abd5
Replace the template-residue body (docs-scaffolding focus) with content
about the actual project: what heph is, the "what is next?" discipline,
current Phase 1 status table, the workspace/crate architecture, build &
run instructions (hephd + heph CLI), and development conventions. Keep
the All-Rights-Reserved license.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core: body text-CRDT via yrs (sync 8d)
Some checks failed
Build / validate (pull_request) Failing after 4s
455f172a54
Replace last-writer-wins for node bodies with the yrs text CRDT, so
concurrent edits to different regions of a body merge instead of one
clobbering the other (tech-spec §5, §12).

- New crate::crdt module wraps yrs: a device authors under a stable
  client_id derived from its sync origin; a whole-buffer write is diffed
  (common prefix/suffix, char-boundary safe) into the doc and the yrs
  delta is captured; merge is commutative/idempotent.
- nodes::create/update/journal maintain the body_crdt BLOB and put the
  yrs delta in the node.create/node.set op payload (body_crdt field).
  Recurrence's local checklist reset goes through the same path to keep
  body and body_crdt consistent (still records no op, as before).
- apply::node_upsert merges the body delta through the CRDT regardless of
  HLC order and drops body-conflict recording; titles + task scalars stay
  LWW with the conflict queue.
- convergence test now asserts disjoint concurrent body edits both survive
  and enqueue no conflict.

97 tests green; clippy -D warnings + fmt + prek clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hephd: network sync over HTTP — hub + spoke (sync 9a)
Some checks failed
Build / validate (pull_request) Failing after 2s
8c25d114c4
Wire the existing merge engine over the network so the everyday config
(local + hub_url) syncs through a hub. Transport ratified = axum HTTP/JSON
(tech-spec §6.1, §12).

- heph-core: SyncCursors model + Store::sync_state/record_sync over the
  sync_state table (per-peer push/pull HLC cursors). Incremental, so each
  exchange transfers only the tail.
- hephd::sync: the hub router (POST /sync/push, GET /sync/pull?after=<hlc>)
  served from the shared LocalStore, and sync_once — a spoke's pull-then-
  merge, then push-tail exchange, advancing the cursors. Idempotent: a
  re-pushed op the hub already has is a no-op.
- Daemon carries optional hub config; sync.now/sync.status handled at the
  daemon (they need the hub transport the store can't reach). conflicts.
  list/resolve now reachable over the unix socket too.
- main: --mode local|server, --hub-url, --http-addr. server mode binds the
  hub HTTP endpoint on the same store; a local+hub_url spoke background-
  syncs on a 30s interval.
- tests/sync_http.rs: two spokes converge through a real-HTTP hub on an
  ephemeral port — node propagation and a divergent-scalar conflict.

Unauthenticated/single-owner for now; OIDC + per-user scoping is slice 10,
client mode + RemoteStore is 9b. 100 tests green; clippy -D warnings + fmt
+ prek clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hephd: client mode + RemoteStore (sync 9b)
Some checks failed
Build / validate (pull_request) Failing after 4s
5d54e913c2
Add the online-only escape hatch — a no-replica daemon that proxies every
Store call to a server over HTTP (tech-spec §3.1).

- Daemon is now generic over the backing store (Arc<Mutex<dyn Store +
  Send>>), so the same unix-socket surface fronts either a LocalStore
  (local/server) or a RemoteStore (client). sync::router/sync_once and the
  Ctx follow suit.
- New POST /rpc route on the hub router runs the full rpc::dispatch over
  HTTP (result-xor-error body, always 200). dispatch gains task.get and
  links.add so the proxied API is complete.
- RemoteStore (hephd): implements heph_core::Store by forwarding each call
  to /rpc via a blocking reqwest client (Store is sync; the daemon only
  calls it from the blocking pool). Error::Remote for transport failures;
  NOT_FOUND is preserved as Error::NodeNotFound. Sync primitives are
  stubbed (a client keeps no op-log).
- main: --mode client + --server-url; client skips the file lock and opens
  no LocalStore.
- tests/client_mode.rs: a RemoteStore drives node/task/search/list/health
  against a real HTTP server, and not-found maps back correctly.

102 tests green; clippy -D warnings + fmt + prek clean. Next: OIDC auth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hephd: OIDC hub authentication — verification side (auth 10a)
Some checks failed
Build / validate (pull_request) Failing after 3s
497c62a988
Authenticate op exchange at the network boundary (tech-spec §13). The hub
now requires a valid OIDC bearer token on /sync/* and /rpc; local mode is
unchanged (no auth).

- heph-core: Store::authorize_owner_sub — single-tenant gate that claims the
  owner's oidc_sub on first sight, then authorizes only that sub (403 for any
  other identity). LocalStore impl over users.oidc_sub; RemoteStore stub.
- hephd auth module: TokenVerifier trait (mockable seam) + OidcVerifier
  (jsonwebtoken, rust_crypto). Strict validation: RS256 pinned, exact iss +
  aud, exp/nbf, required sub; JWKS discovered + cached, refetched on unknown
  kid (rotation). Claims/AuthError.
- Hub router takes Option<verifier>; an axum middleware on every route
  extracts the Bearer token, verifies it off the async worker, and runs the
  owner gate — 401 missing/invalid, 403 wrong identity, 503 IdP-unreachable.
  Open (no auth) when unconfigured, for local dev.
- main: --oidc-issuer/--oidc-audience enable the hub verifier (server mode).
- Security tests, all offline: stub-verifier middleware (missing/bad/valid +
  owner gate) and an adversarial battery driving OidcVerifier against an
  in-process mock IdP — rejects expired, wrong iss/aud, unknown kid, tampered
  signature, alg confusion (HS256/none), and missing sub. The RSA key + JWKS
  are generated at runtime (rsa/rand/base64 dev-deps) so no key is committed.
- tech-spec: add an end-of-v1 dependency-refresh pass to the roadmap.

108 tests green; clippy -D warnings + fmt + prek clean. Next: client-side
device-code login + keyring (10b).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hephd: OIDC client auth — device-code flow + token attach (auth 10b)
Some checks failed
Build / validate (pull_request) Failing after 9s
f4db186234
Close the auth loop: clients obtain a bearer token and present it to the
hub (tech-spec §13).

- oauth module: DeviceFlow (RFC 8628 — discover, start, poll handling
  authorization_pending/slow_down, refresh) + StoredToken + TokenStore
  (OS keyring via `keyring`, in-memory for tests) + current_bearer (loads
  and refreshes-on-expiry).
- heph auth login/logout: runs the device flow, prints the verification
  URL + user code, caches the token in the keyring.
- sync_once gains a bearer arg; the daemon (Daemon::spawn_sync_loop +
  sync.now) obtains it via current_bearer; RemoteStore attaches it to /rpc.
  --oidc-issuer/--oidc-client-id configure the spoke/client.
- Fix a latent panic: reqwest::blocking spins its own runtime and panics
  inside the daemon's spawn_blocking pool. All blocking auth/proxy HTTP
  (OidcVerifier JWKS, DeviceFlow, RemoteStore) now uses runtime-free `ureq`;
  async reqwest remains only for sync_once. (Caught by the new e2e test.)
- Tests (offline): device flow + refresh + token store vs a mock OAuth
  server; a full spoke->authenticated-hub loop (valid token accepted,
  missing token rejected) signed by a runtime-generated RSA key.

112 tests green; clippy -D warnings + fmt + prek clean. Slice 10 (auth)
complete; next is heph.nvim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs: refresh resume state for next session (AGENTS structure + §14 order)
Some checks failed
Build / validate (pull_request) Failing after 5s
87c76da659
Backend is feature-complete; point the trackers at heph.nvim as the next
slice so a cold-start session resumes correctly.

- AGENTS.md: update the (always-in-context) project-structure block — all
  three daemon modes + sync + auth are done; CLI gained search/journal/auth;
  heph.nvim is the next slice. Point at tech-spec §14 as the live tracker.
- tech-spec §14: reorder "not yet done" so heph.nvim is #1 (the remaining
  build slice), with adoption refinement and the dependency-refresh sweep as
  non-blocking follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: RPC client + buffer editing + wiki-links + journal (slice 11a)
Some checks failed
Build / validate (pull_request) Failing after 4s
ee865e5635
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>
heph.nvim: run e2e via mise run test-nvim, drop the Makefile
Some checks failed
Build / validate (pull_request) Failing after 3s
3997e948ed
Make mise the dev entrypoint for the headless e2e suite, matching the repo's
mise-tasks convention (auto-discovered, shows in `mise tasks`). Dev relies on
system-installed nvim + rustc; CI will provide them via Dagger (slice 11c).
The self-contained shim runner is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: task views — next/list/capture/attention/state/log (slice 11b)
Some checks failed
Build / validate (pull_request) Failing after 2s
7c9a734ebd
Backend: enrich `list` to return titled RankedTask rows (title +
canonical_context_id, via a shared ranked_from_row with `next`), so the
Organizational view needs no N+1 node.get. TDD: query_surface test asserts
list rows carry title + context id.

Plugin:
- view.lua: Tactical `next` + Organizational `list` rendered scratch buffers;
  <CR> opens the row's canonical-context doc. Narrowed the node autocmd to
  heph://node/* so view buffers (heph://next, heph://list) don't trip it.
- task.lua: capture, set-attention, done/drop, skip, per-task log append, all
  resolving "the current task" from the buffer (a task node, or a context doc
  via its canonical-context backlink).
- picker.lua: vim.ui.select with Telescope auto-upgrade (headless-safe).
- command.lua: :Heph next/list/capture/attention/done/drop/skip/log/search.

e2e: capture→next→open context→add/check checklist→done; recurring
fresh-checklist (complete rolls forward in place, next occurrence all-unchecked
— the §4.4 hard requirement). 6 specs green via `mise run test-nvim`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: context-item promotion + Dagger headless-nvim CI (slice 11c)
Some checks failed
Build / validate (pull_request) Failing after 3s
b97c387252
Backend (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>
ci: run build entirely through Dagger; drop prek from CI
All checks were successful
Build / validate (pull_request) Successful in 21m0s
fef0e82d26
The Forgejo k8s job image is a thin Alpine + Dagger orchestrator (no native
Rust/Neovim toolchain, no prek) with a DinD sidecar — so CI was failing on
step 1 (`prek: command not found`) on every run, and a native cargo hook
wouldn't work there either.

- build.yaml now runs `dagger call check` (cargo fmt/clippy/test on
  rust:1-bookworm) + `dagger call test-nvim` (build hephd + headless e2e).
- New Dagger `check` function; CARGO_BUILD_JOBS capped on both functions so
  parallel rustc on heavy crates doesn't OOM the build engine; cargo registry
  + target caches shared across runs.
- prek is intentionally not run in CI — it runs locally via git hooks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
heph: dev/installed isolation — mise run dev task + install how-to
Some checks failed
Build / validate (pull_request) Failing after 10m32s
652d2e89e6
- mise-tasks/dev runs the working-tree hephd on isolated .dev/ paths (gitignored)
  so in-repo development never touches the installed daemon's data; point a dev
  nvim at it via HEPH_SOCKET/HEPH_DB.
- docs/how-to/install-heph.md: install heph/hephd from the forge (build from
  source), the lazy.nvim `dir` setup for the subdir plugin, and dev isolation.
- gitignore .dev/ and the transient .claude scheduled-task state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: fix daemon.wait_ready deadlock on a stale socket
Some checks failed
Build / validate (pull_request) Failing after 5m51s
b932a814ca
wait_ready ran the rpc health probe inside a `vim.wait` predicate, and the probe
itself uses `vim.wait` — nesting vim.wait inside another vim.wait's predicate
deadlocks Neovim. It only bit when the socket file existed: the first launch's
socket doesn't exist yet (probe short-circuits), but the second launch hit the
stale socket left by the prior daemon and froze in setup().

- wait_ready now probes in a plain Lua loop (deadline via uv.hrtime + a bare
  vim.wait(50) yield) — never a vim.wait inside a vim.wait predicate.
- stop_spawned now unlinks the socket on exit, so a clean exit leaves no stale
  socket (a crash still can — the wait_ready fix handles that too).

Verified: two-launch repro no longer hangs; a crash-left stale socket recovers
in ~460ms. 10 e2e specs green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: regression test for the stale-socket wait_ready deadlock
Some checks failed
Build / validate (pull_request) Failing after 5m35s
acb3949896
Recreates a crash-left stale socket (spawn a managed daemon, SIGKILL it) and
asserts wait_ready returns promptly (a deadlock would freeze the suite) and that
a fresh autostart recovers. Verified it fails (suite hangs) against the buggy
nested-vim.wait version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: follow-or-create wiki links + :Heph doc
Some checks failed
Build / validate (pull_request) Failing after 4m13s
9249ca46a1
Pressing <CR> on a [[wiki-link]] whose target doesn't exist now creates a doc
with that title and opens it (the zettelkasten gesture), and materializes the
source's backlink: if the source has unsaved edits, saving re-extracts and
links it (and persists the edits); otherwise the wiki link is added directly
(a no-op re-save wouldn't re-extract). Adds :Heph doc <title> to create a
standalone wiki entry. e2e covers both the saved-source and just-typed-source
paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: :Heph home — a base index/landing page for the zk
Some checks failed
Build / validate (pull_request) Failing after 5m3s
e99c284941
A single designated "home" doc (open-or-create by title, configurable via
opts.home, default "Home") — a stable landing page to grow a map of content
around. e2e covers create-on-first-open + idempotent reuse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: :Heph journals — recent-days picker with preview + @create
All checks were successful
Build / validate (pull_request) Successful in 16m0s
d0930aa6a3
A dailies picker (zkd-style): lists the last `journal_days` (default 7) days
newest-first, previews existing journals and shows "@create" for new ones, and
opens the chosen day (creating if new). Journals resolve by their ISO-date
title, so no new RPC is needed. picker.select gains an optional Telescope
preview pane. e2e covers the recent-days list (exists/@create across a month
boundary) and open-on-pick via a stubbed vim.ui.select.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: interactive next/list views (add, done, refresh) + key hint
Some checks failed
Build / validate (pull_request) Failing after 4m47s
0462a6e43b
The Tactical next and Organizational list buffers are now actionable:
- a  add a task from the list (prompt title + attention)
- d  mark the task under the cursor done
- r  refresh
- <CR> open the task's context (as before)

A dimmed key hint renders above the rows as a virtual line (extmark), so it's
discoverable without taking a task row. e2e covers add-from-list and
done-from-list via stubbed vim.ui.input/select.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: make the view key-hint actually visible (header line, not virt-line)
Some checks failed
Build / validate (pull_request) Failing after 6m25s
95948d5563
virt_lines_above on the first line renders above the top screen row, which can't
be scrolled into view — so the hint was invisible. Use a real header line at the
top of next/list (dimmed via an extmark highlight); task rows start at line 2,
and the cursor lands on the first task. Specs updated for the +1 offset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs: refresh §14 tracker + README/AGENTS for the heph.nvim UX iteration
Some checks failed
Build / validate (pull_request) Has been cancelled
7188daeb32
The plugin is built, installed, and well past 11a–11c. Record the post-11c UX
iteration (managed daemon + self-heal, follow-or-create, home/index, dailies
picker, interactive views, dev isolation, fully-Dagger CI) as done, and reset
the "not yet done" backlog to lead with the highest-value next work:
task-scheduling UX (do-date/late-on/recurrence from the editor), then more
surfacing (backlinks/tags/health/log-read), 11d (deferred), and the heph.nvim
repo split.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs: revise to a three-surface model (CLI/TUI/nvim) from a Todoist study
Some checks failed
Build / validate (pull_request) Has been cancelled
0aa7e725a5
Study of the owner's live Todoist (387 tasks, 34 hierarchical projects)
grounds a surface-strategy revision: structured task fields suit CLI flags,
large-set triage suits an interactive TUI, and context/KB suits nvim — so v1
adopts three surfaces, each to its strength. Supersedes the earlier
"heph.nvim is the primary surface" framing.

- design.md: new §6.2.1 (Todoist study) + revised §4 (three-surface model)
- tech-spec §1/§8: reframe surface roles; new §8.1 (planned heph-tui agenda)
- tech-spec §14: reorder remaining work (CLI-complete task surface in progress,
  heph-tui next, nvim navigation polish, tags/hierarchy deferred)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(core): task.set_schedule — reschedule do-date/late-on/recurrence
Some checks failed
Build / validate (pull_request) Has been cancelled
70d5af5bdc
There was no way to change a task's do-date, late-on, or recurrence after
creation (only attention/state had setters) — a real reschedule gap. Add a
single patch method covering the three schedule scalars with no setter.

- model: SchedulePatch with double-option fields (absent=leave, null=clear,
  value=set), serde-skips absent fields so the distinction round-trips
- Store::set_task_schedule + LocalStore/RemoteStore impls; sqlite set_schedule
  overlays present fields then records the LWW task.set op (sync-correct)
- rpc dispatch: task.set_schedule (id + flattened patch)
- tests: core set/clear/leave + missing-task; rpc_socket round-trip asserting
  the absent/null/value semantics over the wire

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(cli): complete task surface — human dates, recurrence, full API
Some checks failed
Build / validate (pull_request) Failing after 1m52s
07e4d786b3
Make 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>
feat(cli): heph project list (+ node.list RPC)
Some checks failed
Build / validate (pull_request) Has been cancelled
f122c9e6a4
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>
feat(cli): parse "every Nth" recurrence → monthly by day-of-month
All checks were successful
Build / validate (pull_request) Successful in 2m47s
2d4e4ae4d7
Todoist uses "every 5th" for monthly-on-the-5th; map it to
FREQ=MONTHLY;BYMONTHDAY=N (1..=31). Surfaced by the Todoist import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(tooling): mise run import-todoist — seed a heph store from Todoist
All checks were successful
Build / validate (pull_request) Successful in 7m1s
0b32ed4397
Turn the one-off Todoist importer into a documented, repeatable mise task.
Self-contained (spawns its own hephd), dry-run by default into a throwaway
store, `-- --commit` writes into the real store after backing it up. Auth via
TODOIST_TOKEN or TODOIST_OP_REF (op://). Mapping per design §6.2.1:
project hierarchy (+ Inbox→unfiled), priority→attention by meaning, due→do-date,
NL recurrence, descriptions + sub-tasks→canonical-context doc.

- mise-tasks/import-todoist
- docs/how-to/import-todoist.md (+ how-to index, reference mise-tasks table)
- changelog fragment

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs: daemon lifecycle becomes an explicit service (connect-only surfaces)
Some checks failed
Build / validate (pull_request) Has been cancelled
1315b9ce18
A surface-owned, auto-spawned daemon can't be shared once the CLI is also a
first-class client — so drop auto-spawn and manage the daemon as an OS service.

- design §4: daemon lifecycle = explicit OS service; surfaces connect-only
- heph-nvim.md: rewrite the daemon-lifecycle section (connect-only) + history
- new how-to/run-the-daemon.md (heph daemon start/stop/restart/status); indexed
- install-heph.md: post-install is `heph daemon start`; plugin no longer spawns
- tech-spec §14: mark the managed-daemon entry superseded
- changelog fragment

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(cli): heph daemon — manage hephd as a launchd/systemd service
Some checks failed
Build / validate (pull_request) Has been cancelled
0cfe627055
Surfaces are connect-only; the daemon now runs as an explicit OS service so it
can be shared without any surface owning its lifecycle.

- service.rs: heph daemon start/stop/restart/status/uninstall, idempotent;
  launchd LaunchAgent (macOS) / systemd user service (Linux); resolves hephd
  next to heph else on PATH; pure plist/unit render fns unit-tested
- main.rs: Command::Daemon handled before connecting (like auth)
- hephd: default socket is now a STABLE <data-dir>/heph/hephd.sock when
  XDG_RUNTIME_DIR is unset (was $TMPDIR — fragile for a persistent service;
  macOS prunes /var/folders and the path varied per session)
- tech-spec §14: CLI + daemon-service done entries

Verified live on macOS: start/restart/stop/uninstall + CLI reaches the store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph.nvim: rip out auto-spawn — connect-only plugin
All checks were successful
Build / validate (pull_request) Successful in 10m44s
cdd4d9f62a
The daemon is now an OS service (`heph daemon`); the plugin no longer spawns or
supervises one. Removes the managed-daemon machinery entirely.

- delete lua/heph/daemon.lua (spawn/ensure/stop_spawned/self-heal)
- init.lua: connect-only; probe `health` once and guide to `heph daemon start`
- rpc.lua: drop set_respawn + respawn-on-drop; a dropped connection just
  reconnects once (e.g. after `heph daemon restart`), never spawns
- config.lua: drop autostart/bin/db; stable socket fallback (data-dir, matches
  hephd::default_socket_path), keep $HEPH_SOCKET for dev isolation
- tests: spawn/wait_ready move into the e2e harness (test infra); rework
  managed_daemon_spec into a connect-only spec (connect / clean-fail / reconnect)

16 nvim e2e specs pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs: plan the filter-views slice + TUI coherence; note chores rework
Some checks failed
Build / validate (pull_request) Failing after 8m9s
eb4a827700
- tech-spec §8.2 (new): filter-views slice — extend `list` to a data-expressed
  predicate (attention set, project-subtree/multi scope, exclude-projects,
  actionable toggle), five built-in views (`heph view <name>`) seeded from the
  owner's verbatim Todoist queries; the TUI's filter pane reuses them
- tech-spec §8.1: TUI filter pane = the §8.2 views; depends on that slice
- tech-spec §14: filter-views is the next slice (before heph-tui); CLI +
  daemon-service marked done
- design §6.2.1: record the verbatim filter queries + the reference-context →
  wiki reclassification, and note future direction: chores as a first-class
  feature (own do-date/recurrence; retire Chores/Camano-Chores projects) and
  dropping the Schedule filter (time-of-day not modeled)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(views): filter views (§8.2) — saved agenda slices
Some checks failed
Build / validate (pull_request) Failing after 18m44s
a5fc578525
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>
New crate crates/heph-tui: a ratatui terminal agenda, thin client of the
hephd unix socket (never touches SQLite, same as heph.nvim). The next big
surface — the interactive triage UI the §6.2.1 Todoist study calls for.

- 3-pane layout: sidebar (the five §8.2 filter views + projects), task list
  (attention-colored rows with compact human do/late dates), and a preview
  pane (the highlighted task's canonical-context doc body + log tail).
- App state is generic over a `Backend` seam, so navigation/selection logic
  is unit-testable without a terminal or daemon; `ClientBackend` forwards to
  the socket. Rendering is a pure `ui::render(frame, &app)`.
- Navigation: j/k within the focused pane, Tab / h / l to move focus,
  selecting a sidebar source reloads the list, moving the task cursor
  refreshes the preview. r refresh, q quit.
- Socket resolution: --socket flag, then $HEPH_SOCKET, then the standard
  runtime path (the TUI honors the env var the CLI doesn't).

Tests: a headless TestBackend render against a real spawned daemon (asserts
views/projects/tasks/preview paint, and Top of Mind excludes blue), plus
in-memory navigation unit tests. 8 heph-tui tests; clippy/fmt clean.

Mutations (add/done/attention/reschedule/blue) + nvim handoff land in T2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single-keypress mutations on the highlighted task, each → RPC → reload with a
status confirmation: x done (recurring roll-forward), d drop, s skip, A cycle
attention (white→orange→red→blue, §6.2), b push-to-blue (On Deck). The bulk of
daily triage — the daily orange reconfirm and blue keep/drop review made fast.

Tests: next_attention cycle unit test; integration tests against a real daemon
that completing/pushing-to-blue removes a task from Top of Mind (and it then
shows under On Deck). 11 heph-tui tests; clippy/fmt clean.

Input-requiring actions (a add, e reschedule) + nvim context handoff are T2b.

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 single-line input modal (centered popup, Esc cancels, Enter submits) drives
the two input-requiring gestures:

- a: guided capture — title → attention (o/r/b, blank=white) → do-date
  (today/tomorrow/+3d, blank=none). If a project is the current sidebar
  selection, the task is filed there (Todoist's add-within-project).
- e: reschedule the highlighted task's do-date (blank clears it).

Parse errors keep the input step (typed text isn't lost) and show in the
status line. Shared client-side date/recurrence parsing (datespec) moved from
the heph CLI into hephd's lib so both the CLI and TUI use one parser; heph-core
stays clock-pure. The CLI now uses hephd::datespec (no behavior change; its
tests moved with it).

Tests: add-flow + reschedule unit tests against a recording fake (asserting the
collected title/attention/project and the clear-on-blank double-option), plus
real-daemon integration tests that guided-add surfaces a task and reschedule
sets the do_date. 171 workspace tests; clippy/fmt clean.

With done/drop/skip/attention/blue (T2a) + nvim handoff (T2b), all four
day-one daily-driver gestures now land. NL single-line quick-add + search are T3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
§8.1 status → "core built" with the implemented panes/gestures/handoff; §14
adds the heph-tui Done bullet, retargets the resume list to T3 (NL quick-add +
search), and bumps the count to 171 Rust tests. Changelog fragment for the TUI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`a` is now Todoist-style one-line capture: parse a line like
`Water plants tomorrow p2 #Camano Chores every 3 days` into title + attention
(p1 red / p2 orange / p3 blue / p4 white) + do-date (today/tomorrow/+3d/fri/ISO)
+ recurrence (`every …`, longest suffix that parses) + project (`#Name`, greedy
multi-word match against existing projects). An unresolved `#tag` stays in the
title verbatim (no surprise project creation); with no `#project`, the task is
filed under the selected sidebar project.

The parser (`quickadd::parse`) is pure — `today` and the project list are
passed in — reusing hephd::datespec for dates/recurrence, so it's exhaustively
unit-tested (priority, relative/weekday dates, single + multi-word projects,
recurrence extraction, unresolved tags, the all-at-once case, and the
"every"-not-a-recurrence fallback). `Backend::create_task` gained a recurrence
arg. The multi-step guided add it replaces is gone.

181 workspace tests; clippy/fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`/` opens a search prompt; submitting runs the FTS `search` RPC and overlays
the results on the center pane (title + [kind]). j/k move, Enter opens the hit
(a task hit opens its canonical-context doc via context_of; docs/journals open
themselves) in nvim, Esc exits search. Backend gained `search` + `context_of`.

Tests: fake-backend flow (results populate; task hit resolves to its context,
doc hit to itself; clear) + a real-daemon integration test (seed a doc, search,
assert the hit + that the Search pane renders). 183 workspace tests; clippy/fmt
clean.

Move-to-project is the last Todoist-parity gap; it needs a new task.set_project
RPC (no link-remove RPC yet) and is deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(tui): FTS search done; move-to-project (needs task.set_project) is the last gap
Some checks failed
Build / validate (pull_request) Failing after 11s
ffec575249
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(build): commit the datespec-move wiring (orphaned in T2c)
Some checks are pending
Build / validate (pull_request) Has started running
391277c939
The T2c commit moved datespec.rs into hephd but left its wiring uncommitted:
hephd's lib never exported `pub mod datespec`, hephd lacked the chrono dep,
and the CLI still declared `mod datespec` for a file that had moved. The
working tree had these (so local builds passed) but the pushed tree didn't,
breaking `cargo install` of heph + heph-tui. Commit them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recurring tasks now show a ↻ marker on their row, and the highlighted task
expands inline beneath itself with a dimmed detail block: project name,
recurrence rule, and do/late dates (only the fields that are set). Project
name resolves client-side from the sidebar; dates were already on the row.

Backend: RankedTask gains `recurrence: Option<String>` (populated in
ranked_from_row from t.recurrence; both list/next select lists updated) — the
only data the row was missing. Serializes over the socket automatically.

Tested: a real-daemon render test asserts the ↻ glyph plus the selected
detail block (recurs: FREQ=DAILY, project: Routines). 184 workspace tests;
clippy/fmt clean.

Note: the recurrence is shown as the raw RRULE for now (humanizing it is a
later polish). Subtask/checklist folding was dropped — those reference items
turned out to be blue backlog items, not sub-items.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(tui): note recurring ↻ marker + inline selected-row detail (§8.1)
All checks were successful
Build / validate (pull_request) Successful in 25m8s
bed2d6a8aa
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`D` arms a delete on the highlighted task; the status line shows
"Delete \"title\"? (y / N)" and the next key confirms (y) or cancels (anything
else). Confirming calls node.tombstone — a true soft-delete that removes the
task from every view, recurring tasks included (unlike `x` done, which rolls a
recurring task forward, or `d` dropped, which keeps it in the store). Backend
gains `tombstone`.

Tests: confirm-flow unit test against a recording fake (arm → cancel keeps it;
arm → confirm tombstones), plus a real-daemon integration test that deleting a
recurring task drops it from the view and sets the node's tombstoned flag.
186 workspace tests; clippy/fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(tui): note D delete/tombstone gesture (§8.1)
Some checks failed
Build / validate (pull_request) Has been cancelled
8bd16664c4
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(explanation): add Task Lifecycle — the two-axis model
All checks were successful
Build / validate (pull_request) Successful in 9m33s
1c94a08cda
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>
Capture the agreed remaining-v1 plan: TUI move-to-project + the task-list
UX wave (flag column, project-colored bullets, sort toggle, scrollbar),
tags model, YAML frontmatter as an edit surface (§8.3), and wiki-links by
node id (§8.4). Reorders the §14 resume list to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `Store::set_task_project` (heph-core + RemoteStore) and the
`task.set_project` RPC: tombstone the task's existing `in-project` link(s)
and add a new one (or none, to unfile). A given project id must name a
live project-kind node, else InvalidArg/NodeNotFound.

Route `heph edit --project` through it, fixing a duplicate-link bug (the
old path added an in-project link without removing the prior one);
`--project none` now unfiles. Factor a `links::tombstone` helper out of
`sync_wiki_links`.

Tests: core move/unfile/reject + a duplicate-link regression; a socket
dispatch test. The TUI `m` gesture follows in the next commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(tui): m move-to-project picker (§8.1)
Some checks failed
Build / validate (pull_request) Failing after 3m48s
288e902573
`m` opens a list-pick overlay on the highlighted task — "(Unfile)" then
every project — and re-files it via `task.set_project` (cursor starts on
the task's current project). j/k navigate, Enter applies, Esc cancels.
Adds `Backend::set_project`, a `Mode::MoveToProject` overlay, and its
render. Navigation tests cover refile + cancel.

Closes the last Todoist-parity capture gap (§14 item 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each task row now leads with a colored attention flag (⚑ for
red/orange/blue, blank for white/none) and a project-colored bullet (●).
The bullet color is derived stably from the project id (FNV-1a → HSL →
truecolor RGB) so it survives projects being added/removed; a per-project
override on the model is a later refinement. The glyph shape is reserved
for future semantics.

The task list also gains a scrollbar and ListState-driven
scroll-to-visible so a selected task below the fold stays reachable.

Tests: fmt::project_color determinism unit; a flag-glyph render assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`s` flips the task list between two orders:
- default: attention (red→orange→white→blue) → most-overdue (desc) →
  project name → created_at (FIFO)
- project: project first, with dimmed ──── Name ──── separators riding
  atop each group's first task (the cursor only lands on real tasks)

The view filter still runs before the sort. Pure comparator (`cmp_tasks`/
`sort_tasks`, today injected) with unit tests for both modes + a
navigation test for the toggle. `skip` moved from `s` to `S` to free `s`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(nvim): do/late date chip (+ ↻) on task-view rows (§8)
Some checks failed
Build / validate (pull_request) Failing after 1m17s
9d84eb7427
`:Heph next`/`list` rows now render a compact relative do/late date chip
(today/tomorrow/yesterday/MM-DD/YYYY-MM-DD, mirroring heph-tui's fmt) and
a recurrence ↻, so scheduling is visible at a glance. `<CR>` already jumps
to a row's canonical-context doc. e2e: a do-date-chip render assertion.

Completes the §14 item-2 task-list UX wave.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(core): tags — canonical tag nodes + OR-set tagging (§4, §8.3)
Some checks failed
Build / validate (pull_request) Failing after 4m35s
4cdf0de64c
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>
feat(nvim): frontmatter edit surface — diff block into RPCs on save (§8.3)
Some checks failed
Build / validate (pull_request) Failing after 5m1s
0e9cfc1fd7
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>
A `.stylua.toml` (Spaces/2, else stylua defaults) + a `stylua-system`
prek hook make Lua whitespace formatter-enforced (the repo had no Lua
formatter, so style was hand-maintained and drifted). Normalized the
three non-conformant files in passing. 21 nvim e2e specs still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(nvim): inline #hashtags become tags on save (§8.3)
Some checks failed
Build / validate (pull_request) Failing after 4m57s
8dc98dc9c1
On save, whitespace-prefixed `#hashtags` in a node's body are unioned
into its tag set (via `frontmatter.hashtags` + the existing tag diff), so
you can tag a note by writing `#kitchen` inline. A markdown `# heading`
has a space after the `#`, so it never matches. e2e covers it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A buffer-local syntax match (`HephHashtag`, italic, `default`-overridable)
highlights whitespace-prefixed #hashtags so they're visually obvious —
mirroring the save-time tag detection (a `# heading` doesn't match).
Attached alongside the <CR> link follow. Syntax match is the right-sized
tool here (treesitter has no hashtag node; extmark conceal is reserved
for the upcoming [[link]] display layer). e2e asserts the italic hl group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: wiki-links by id — id-first resolution + heph.nvim [[ picker (§8.4)
Some checks failed
Build / validate (pull_request) Failing after 6m34s
4e8f6743cf
Backend: `links::resolve_id` now checks for an exact live node id before
alias/title, so a canonical `[[NODEID]]` link resolves to its node and
can't be shadowed by a like-named node. Legacy `[[Name]]` links still
resolve by name (until the migration), so this is additive.

heph.nvim: `link.insert` (bound to insert-mode `[[` and `:Heph link`)
searches via the `search` RPC and inserts `[[NODEID]]`, with a "+ Create
new doc" entry; `<CR>` follow resolves the id directly. e2e covers
search→insert→materialize and the create path.

Remaining (§8.4): read-expansion/conceal display + the one-time
[[Title]]→[[NODEID]] migration (then retire name-resolution + the hack).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep canonical `[[NODEID]]` links readable without storing names. New pure
`heph-core::wikilink` (injected id→title): `expand` turns a bare `[[id]]`
into `[[id|Current Name]]`, `collapse` turns a name-matching `[[id|text]]`
back to bare (a custom label is preserved as an override).

- `node.get` expands on every read (nvim buffer + TUI preview both
  readable), then prepends frontmatter when asked.
- `update_node` strips frontmatter, then collapses links, then CRDT-diffs
  — so neither projection ever persists and an unchanged read→write is a
  no-op to the bare id.

Tests: wikilink unit (expand/collapse/round-trip), a heph-core collapse
+ materialize-by-id integration test, and a socket expand→collapse
round-trip. `heph export` still emits raw ids (later polish).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`conceal.lua` hides the `[[id|` prefix and `]]` suffix with conceal
extmarks (refreshed on edit), leaving the label as a styled `HephLink`;
`conceallevel=2` + empty `concealcursor` reveal the raw `[[id|Name]]` on
the cursor's line so it stays editable. The `[[` picker now inserts the
labelled `[[id|Name]]` form (readable + conceal-ready; collapses to bare
on save). e2e asserts the conceal extmarks + conceallevel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: heph migrate-links — rewrite legacy [[Name]] links to [[id]] (§8.4)
Some checks failed
Build / validate (pull_request) Failing after 11s
b112b0d7c1
`wikilink::to_ids` rewrites name-addressed links to the canonical id
(id-first resolve: an already-id target is left alone, a name → its id
with any label preserved). `Store::migrate_wikilinks_to_ids` runs it over
every body and re-saves through update_node (which collapses + materializes
by id); idempotent. Surfaced as the `migrate.wikilinks` RPC + RemoteStore
forward + the `heph migrate-links` CLI command (not auto-run — the owner
runs it once per store).

Name-resolution + the canonical-context hack stay for now so legacy links
keep resolving pre-migration; retiring them is a later tidy. Tests:
to_ids unit + a heph-core migrate integration (rewrite + materialize +
idempotency).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(nvim): live Telescope filter for the [[ link picker (§8.4)
Some checks failed
Build / validate (pull_request) Failing after 10s
d178a657e0
Replace the "guess a query, then filter a frozen result set" flow with a
live fuzzy filter over every node when Telescope is present: type to
narrow `node.list`, <CR> inserts the highlighted node, <C-x> creates a
doc named the current prompt (a miss flows straight into making it). The
search-then-`vim.ui.select` two-step stays as the no-Telescope fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(nvim): preview pane in the Telescope [[ link picker (§8.4)
Some checks failed
Build / validate (pull_request) Failing after 8s
1737f8c266
Add a buffer previewer that shows the highlighted node's body as you
filter — for a task (no body of its own), it previews the canonical-
context doc instead. RPC-fed (heph nodes aren't files, unlike
obsidian.nvim's notes), pcall-guarded so a fetch miss just blanks the
preview. Reuses the global fzy_native sorter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: node.linkable — first-class link targets for the [[ picker (§8.4)
Some checks failed
Build / validate (pull_request) Failing after 8s
2fc48a1aa9
The picker listed every node, so each task showed up twice (itself + its
same-titled canonical-context doc) plus tag/log noise — and the new
preview made the duplicates look identical. New `Store::list_linkable_nodes`
/ `node.linkable` returns non-tombstoned nodes minus `tag`s and the docs
that are a task's canonical-context or log attachment (you link the task,
not its body). The Telescope picker now sources from it.

Tests: a socket test (5 nodes → 2 linkable: task + standalone doc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(tui): <Enter> opens the context editor; reorder views (§8.1/§8.2)
All checks were successful
Build / validate (pull_request) Successful in 3m57s
44d6847fae
- `<Enter>` now opens the selected task's context doc in nvim (App::enter:
  from the sidebar it drills into the task list first); the `o` binding is
  retired. Hint line updated.
- BUILTIN_VIEWS reordered to the owner's preference — Top of Mind, Tasks,
  Work Tasks, Chores, On Deck — which drives the TUI sidebar and
  `heph view`. Tests that walked to On Deck by a fixed offset now seek it
  by title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A one-cell gap between the ⚑ attention flag and the project-colored ●
bullet; the blank-flag case already reserves the same width, so rows stay
aligned whether or not a flag is present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A macOS global quick-capture popover to retire Todoist. New `heph-quickadd`
crate: an always-warm eframe/egui agent that registers ⌘' (global-hotkey,
Carbon — no Accessibility permission) and toggles a hidden, pre-created
window visible + focused on press — never spawning on the keypress, so it's
a muscle reflex. A single field live-parses Todoist-style inline syntax via
the shared parser; chips show ⚑ attention · 📁 project ·  do-date · ↻
recurrence as you type. Enter saves optimistically (hide now, task.create
on a bg thread; a failed RPC re-shows with the text restored). #project
autocomplete (Tab/↑↓/click; focus-locked so Tab completes instead of
traversing). Example hints rotate, fading in only after ~2s idle.

Parser lifted from heph-tui into hephd's lib (hephd::quickadd) so the TUI
and the popover share one parser. hephd supervises the helper as a child in
local mode on macOS (opt-in HEPH_QUICKADD=1, set by the installed launchd
plist) — one service to manage, no second launch agent; the helper
self-exits when orphaned so killing hephd leaves nothing behind.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A sixth built-in filter view (listed below On Deck) showing every
outstanding task filed under no project — the capture inbox to triage. New
ListFilter.unfiled predicate kept purely in matches(); the inbox ViewSpec is
un-gated (no attention/do-date filter) so nothing hides from triage. Surfaces
automatically in the heph-tui sidebar and `heph view`. Tests cover the
predicate and the view spec; navigation tests updated for the 6th view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `m` move-to-project overlay is now a filterable picker: a prompt line
narrows the project list by fuzzy subsequence match as you type (↑/↓ or
Ctrl-n/p move, Enter selects, Esc cancels), so there's no scrolling a long
list. When the filter names no existing project, a "+ New project" row
creates it and files the task there in one step (Backend::create_project →
node.create). Tests cover fuzzy narrowing and the create-then-file flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(tui): pane-specific keys, undo/redo, project delete, sidebar refresh (§8.1)
Some checks failed
Build / validate (pull_request) Failing after 12s
9511f6a009
Triage gestures (x/d/S/A/b/e/m/D) now fire only when the task pane is
focused, so a stray key while in the sidebar can't drop or delete a task;
hints are focus-aware. `u` undoes the last triage action (drop/done/skip/
attention/move) and Ctrl-z redoes it, restoring from a pre-action snapshot
(multi-level, cap 200; tombstone-delete excluded — no restore path yet). `D`
in the sidebar deletes the highlighted project (y/N), unfiling its tasks to
the Inbox. The sidebar's Projects section now rebuilds after create/delete,
so a new project appears without a restart. Tests cover undo/redo, the
empty-undo no-op, and sidebar project delete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix: deleting a project unfiles its tasks to the Inbox (§8.1/§8.2)
Some checks failed
Build / validate (pull_request) Failing after 11s
dd5ef7dc63
Project delete previously tombstoned only the project node, leaving its
tasks with a live in-project link to a dead project — orphaned (not in the
Inbox, unbrowsable, blank project) rather than unfiled as intended. New
atomic Store::delete_project tombstones every in-project link to the project
(tasks fall to the Inbox), then tombstones the project node; tasks are never
deleted. Exposed as the project.delete RPC (LocalStore + RemoteStore); the
heph-tui sidebar `D` now routes through it. Core test asserts the task
survives and becomes unfiled; the project node is tombstoned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v1 reached Todoist feature-parity, so remaining/future work is now tracked
in heph itself — tasks in the Hephaestus project (heph view ondeck) — not in
a doc. Renamed docs/reference/tech-spec.md -> v1-prototype-tech-spec.md and
rewrote all 27 [[tech-spec]] wiki-links + README/changelog path refs (docs
checks green). Retitled + bannered the spec as a historical v1 build record
and froze its §14 tracker. AGENTS.md gains a "Planning future work" section
(capture via `heph task --project Hephaestus`, triage in heph-tui On Deck);
README status reflects parity + the three daily-driver surfaces. The design
doc remains the living rationale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Run rustfmt over files landed in earlier commits this session that weren't
fmt-checked (heph-quickadd, the heph-tui undo/move wave, the hephd quickadd
supervisor). Pure formatting (struct/if-else expansion, line wrapping); no
behavior change. Restores `cargo fmt --check` clean for CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: heph list --project <name> + --json; thin AGENTS.md
Some checks failed
Build / validate (pull_request) Failing after 3m21s
dce3519345
`heph list --project <name>` lists a project's outstanding tasks by name
(subtree-expanded, resolved server-side via a new project.scope path that
reuses the view machinery; errors on unknown names). `--json` prints raw
rows — node_id, canonical_context_id, attention/state/do_date/late_on/
recurrence/project_id — for scripting and agents. Store::project_scope on
the trait + LocalStore + RemoteStore; new project.scope RPC and a flattened
ListParams so `list` accepts an optional project name. Test covers
resolve-by-name + unknown-name error.

AGENTS.md thinned to tight command/pattern sections: dropped the historical
parity narrative and the verbose roadmap section; added a "Working state"
section documenting `heph list --project Hephaestus [--json]` as the way to
inspect heph's self-hosted roadmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
infra(prek): add cargo fmt as a pre-commit hook
Some checks failed
Build / validate (pull_request) Failing after 18s
ec522f49ec
Run `cargo fmt --all` in place over the workspace on any staged .rs change,
matching the repo's other in-place formatters (ruff-format, stylua, shfmt).
Unformatted Rust now fails the commit locally (it reformats + reports
"files were modified"), so the fmt-dirty commits that slipped through this
session can't recur. CI still enforces `cargo fmt --check` via Dagger as the
backstop. Verified: passes clean, catches + fixes a deviation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove heph-tui note from AGENTS.md
Some checks failed
Build / validate (pull_request) Failing after 15s
2ae1c09838
eblume merged commit c29ac46559 into main 2026-06-03 20:48:23 -07:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
eblume/hephaestus!1
No description provided.