hephaestus/CHANGELOG.md
2026-06-04 12:42:46 -07:00

121 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: changelog
tags:
- meta
---
# Changelog
All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
<!-- towncrier release notes start -->
## [v1.0.3] - 2026-06-04
### Features
- New `heph context <task-id>` command reads or edits a task's canonical-context doc body **by task id**, with no manual `canonical_context_id` lookup. With no flag it prints the body; `--body <text>` replaces it (`-` reads stdin, like `node update`); `--append <text>` adds a blank-line-separated paragraph. Errors clearly on a node that has no canonical-context doc (e.g. a plain doc, not a task).
- `heph-tui --version` now reports the version plus build commit (e.g. `1.0.0 (ab6701d12)`), matching `heph` and `hephd`. All three daily-driver binaries answer `--version` consistently.
- `--project <name>` is now case-insensitive and prefix-fuzzy when unambiguous, across `heph task`, `heph edit`, `heph promote`, `heph list`, and project-parent resolution. `--project heph` or `--project hephaestus` both resolve `Hephaestus`. An exact (case-sensitive) title always wins outright, and an ambiguous prefix (e.g. `Wor` matching both `Work` and `Workshop`) resolves to nothing rather than silently picking one. A new `project.resolve` RPC backs the shared resolver.
- New `version` RPC returns the daemon's build version (`heph_core::VERSION`, e.g. `1.0.0 (ab6701d12)`), so RPC clients — notably the `hephaestus.nvim` plugin's `:Heph version` command — can report which `hephd` they are talking to without shelling out to the binary.
### Infrastructure
- Added a `cargo-fmt-check` pre-push prek hook that runs `cargo fmt --all --check` (mirroring CI's Dagger `check` step) whenever a push touches a `.rs` file. The pre-commit `cargo-fmt` hook reformats in place, but only fires when installed and run; the pre-push check is a last-line guard so an unformatted commit can't reach the runner. Run `prek install --hook-type pre-push` to activate it.
## [v1.0.2] - 2026-06-04
### Bug Fixes
- Fix `heph`/`hephd` `--version` falsely showing a `-dirty` suffix on clean release installs. The build-time dirty check now considers only tracked modifications (`git diff --quiet HEAD`) instead of counting untracked files such as cargo's own `.cargo-ok` marker in a `cargo install --git` checkout.
## [v1.0.1] - 2026-06-04
### Bug Fixes
- `heph --version` / `hephd --version` now report the release version plus the build commit (e.g. `1.0.0 (ab6701d12)`) instead of a bare `0.0.0`. The short git SHA is captured at build time, so any build — released, branch, or dirty working tree — is traceable to its commit.
### Infrastructure
- The release workflow now bakes the release version into `Cargo.toml` + `Cargo.lock` on a commit that only the release tag points at (tagging it manually), so `cargo install --git --tag vX.Y.Z` reports the real version while `main` stays at `0.0.0`.
## [v1.0.0] - 2026-06-04
### Features
- - **Inbox view** (§8.2): a sixth built-in filter view, listed below On Deck, showing every outstanding task with **no project** — the capture inbox to triage and file. A new `ListFilter.unfiled` predicate (kept purely in `matches()`) drives it; the `inbox` `ViewSpec` is deliberately un-gated (no attention or do-date filter) so nothing hides from triage. Appears automatically in the `heph-tui` sidebar and `heph view`; `heph view inbox` from the CLI.
- - **`heph list --project <name>` + `--json`** (§8.2): list a project's outstanding tasks by **name** (subtree-expanded, resolved server-side via a new `project.scope` path that reuses the view machinery — errors loudly on an unknown name), and `--json` prints the raw rows (`node_id`, `canonical_context_id`, attention/state/do_date/late_on/recurrence/project_id) for scripting and agents. This is the canonical "show me a project's outstanding work" command — `AGENTS.md` documents it as how to inspect heph's own roadmap (the `Hephaestus` project), now that heph self-hosts it.
- Begin the v1 prototype (Phase 1, tech-spec §11.1), built in TDD slices:
- Cargo workspace + `heph-core` crate; migration-run SQLite schema (§4.5); clock-injected `Store` trait + `LocalStore` node 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.create` auto-creates the canonical context `doc` + `canonical-context` link; attention/do-date/late-on/state/recurrence columns; set-state/set-attention. Links CRUD (outgoing/backlinks). A body update reconciles `wiki` links (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_on` is the sole urgency signal; blue hidden; red always shown. Proptest-checked total order. `Store::next` surfaces 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`) with `log.append`/`log.tail`; `skip` advances without logging.
- `hephd` daemon, 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.
- `heph` CLI (§1) — a thin client of the daemon: `next`, `task`, `doc`, `get`, `export`. Export materializes the store to a `<kind>/<id>.md` tree 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 a `conflicts` queue), OR-set links, monotonic tombstones. Two-replica convergence proven.
- Body text CRDT (§5, §12, slice 8d): node bodies now merge through the `yrs` text 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 server` exposes 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 the `sync.now`/`sync.status` RPC). Exchange is incremental by HLC cursor (`sync_state`) and idempotent. The merge engine is `heph-core`'s, unchanged. Unauthenticated/single-owner for now (auth lands with OIDC). `conflicts.list`/`conflicts.resolve` are 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's `POST /rpc` endpoint (the full daemon API over HTTP). The daemon is now backend-agnostic (`local`/`server` front a `LocalStore`, `client` a `RemoteStore`), 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 with `hephd --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 a `TokenVerifier` trait, 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 and `client` mode 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-free `ureq`, since `reqwest::blocking` is unsafe inside the async daemon.)
- CI runs the Rust suite (fmt/clippy/test) via the project build hook.
- `heph.nvim` slice 11a (§8) — the primary surface begins: a Neovim plugin that is a thin client of the local `hephd` over its unix socket. A `vim.uv` JSON-RPC client (blocking `call` via `vim.wait`, id-demuxed, partial-line buffered, JSON `null`→Lua `nil`); buffer-backed nodes (`heph://node/<id>` with `BufReadCmd``node.get` / `BufWriteCmd``node.update`, whole-buffer body round-tripping exactly through the CRDT); `[[wiki-link]]` follow on `<CR>` via a new exact `node.resolve {title}` RPC (alias-then-title, the same mapping that materializes `wiki` links — unresolved links allowed); the daily journal (`:Heph today`); and the `:Heph` command 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.nvim` slice 11b (§8) — task views: `list` is enriched to return titled rows (the same shape as `next`, with the canonical-context id) so the Organizational survey needs no per-row `node.get`. The plugin gains the Tactical **`:Heph next`** and Organizational **`:Heph list`** views (`<CR>` opens a task's canonical-context doc), task **capture**, **set-attention**, **done/drop**, **skip**, and per-task **`log`** append — each resolving "the current task" from the buffer (a task node, or a context doc via its `canonical-context` backlink). A `vim.ui.select` picker (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.nvim` slice 11c (§8) — promotion + CI: `task.promote` mints 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 promote` does 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 with `mise run test-nvim`; the runner fails on a zero-spec discovery so a misconfigured path can't pass silently.
- `heph.nvim` managed daemon — plug-and-play by default: `require("heph").setup({})` spawns and supervises a local `hephd` against 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 (a `server`/`client` architecture, or a service) is always respected — the plugin only spawns when nothing is serving the socket; with `autostart = false` it connects only and warns if unreachable. `$HEPH_SOCKET` / `$HEPH_DB` isolate a development Neovim onto a separate daemon + DB.
- `heph.nvim` follow-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 via `opts.home`) to grow a map of content around. `:Heph journals` opens a recent-days picker (preview existing days, `@create` for new ones; count via `opts.journal_days`, default 7) — the dailies workflow. Pickers (Telescope) now support a preview pane. The `:Heph next`/`list` views are interactive: `<CR>` opens a task's context, `a` adds a task (prompt title + attention), `d` marks the task under the cursor done, `r` refreshes — with a dimmed key hint shown above the list.
- Dev/installed isolation tooling: a `mise run dev` task runs the working-tree `hephd` on isolated `.dev/` paths, and a how-to ([[install-heph]]) covers installing `heph`/`hephd` from the forge (build-from-source), the lazy.nvim plugin setup, and pointing a dev Neovim at the dev daemon via `$HEPH_SOCKET`/`$HEPH_DB` so it never touches the installed store.
- CLI as a complete task surface (§1, §6.2.1): `heph` now 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 in `next`/`list`) and **recurrence** (`--recur` presets/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 new `task.set_schedule` RPC), `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/uninstall` idempotently manages a launchd agent (macOS) or systemd user service (Linux) that runs `hephd` on your default store; `heph.nvim` no longer spawns or supervises a daemon — it just connects and points you at `heph daemon start` if 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 (`tom` Top of Mind, `ondeck` On Deck, `chores`, `work` Work Tasks, `tasks`) seeded from the owner's Todoist filter queries; `heph view` with no name lists them, and `:Heph view <name>` does the same in Neovim. Under the hood, `list` now takes a `ListFilter` predicate-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 its `parent`-link subtree. The Schedule view is intentionally omitted (time-of-day isn't modeled on date-grained do-dates).
- `heph-tui` (§8.1) — a terminal task agenda/triage UI, the primary surface for working a large task set (the §6.2.1 Todoist study showed triage, not single edits, dominates). A `ratatui` app, thin client of the daemon socket. Three panes: a sidebar of the five filter views + your projects, an attention-colored task list with compact human do/late dates, and a preview of the highlighted task's context doc + recent log. Triage from the keyboard: `a` add (guided title → attention → do-date, filed under the selected project), `x` done, `s` skip, `d` drop, `A` cycle attention, `b` push to On Deck, `e` reschedule the do-date; `o` opens the task's context doc in your nvim (live, via heph.nvim) and returns. `j/k` move, `Tab`/`h`/`l` switch panes, `r` refresh, `q` quit. Run it with `heph-tui` (honors `--socket` / `$HEPH_SOCKET`). `a` is a Todoist-style single-line quick-add: `Buy milk tomorrow p2 #Work every week` parses into title + attention (p1p4) + do-date + recurrence + project (multi-word project names match greedily; an unresolved `#tag` just stays in the title). `/` runs a full-text search whose results overlay the task list; Enter opens a hit (a task at its context doc) in nvim.
- Move-to-project (§8.1): a new `task.set_project` RPC re-files a task under another project (or unfiles it) with OR-set link semantics — the old `in-project` link is tombstoned and a new one added, so a task is never filed under two projects at once. In `heph-tui`, **`m`** opens a list-pick overlay ("(Unfile)" then every project) on the highlighted task. `heph edit <task> --project <name>` now routes through the same RPC (fixing a bug where re-filing piled on a duplicate link), and `--project none` unfiles the task. This closes the last Todoist-parity capture gap.
- `heph-tui` task-list visuals (§8.1): each row now leads with an attention **flag** (`⚑`, colored red/orange/blue; blank for white) and a **project-colored bullet** — the bullet's color is derived stably from the project id (so it survives projects being added/removed), letting you scan a mixed list by project at a glance. The list also grows a **scrollbar** and keeps the selected task scrolled into view when there are more tasks than fit.
- `heph-tui`: **`<Enter>`** opens the selected task's context editor in nvim (from the sidebar it first drills into the task list); the old `o` binding is retired. The view sidebar / `heph view` order is now **Top of Mind, Tasks, Work Tasks, Chores, On Deck**.
- `heph-tui` sort toggle (§8.1): **`s`** flips the task list between two orders — **default** (attention → most-overdue → project → creation) and **by-project** (grouped under dimmed `──── Project ────` separators, then the same sub-order). The view's filter still applies first. (To free `s`, **skip** moved to **`S`**.)
- `heph.nvim` task-view rows (§8): `:Heph next`/`:Heph list` rows now show a compact **do/late date chip** (and a recurrence `↻`), so you can see scheduling at a glance; `<CR>` still jumps to a task's context doc.
- Wiki-links by node id (§8.4): node resolution is now **id-first** (`[[NODEID]]` resolves to its node ahead of any name match, so links can't be shadowed by a like-named node), and heph.nvim grows a **`[[` picker** — type `[[` (or `:Heph link`) to pick a node and insert a canonical `[[NODEID]]` link. With Telescope it's a **live fuzzy filter over your linkable nodes with a preview pane** (the node's body, or a task's context doc) — the list shows first-class targets only (a task appears once; its internal context/log docs and tag nodes are hidden, via the new `node.linkable` query) — type to narrow, Enter to insert, `<C-x>` to create a doc named what you've typed; without Telescope it falls back to a search-then-select prompt. Following such a link (`<CR>`) jumps straight by id. Those id links are kept **readable**: on read a bare `[[NODEID]]` is expanded to `[[NODEID|Current Name]]` (so it follows renames, in both the nvim buffer and the TUI preview), and on save it collapses back to the canonical bare id — a custom `|label` you write is preserved as an override. In the editor the id is **concealed** — a link renders as just its name, styled like a hyperlink, with the raw `[[id|Name]]` revealed on the line your cursor is on. Legacy `[[Name]]` links keep working, and **`heph migrate-links`** rewrites them to the canonical id form in one pass when you're ready (idempotent).
- Frontmatter editing in heph.nvim (§8.3): opening a node now shows an editable **YAML frontmatter** block on top of the body (`id`/`kind`/`title`/`tags`, and for a task or its context doc the task's `state`/`attention`/`do_date`/`late_on`/`recurrence`/`project`). On save, the plugin diffs the block and issues the right RPC per changed field — rename, set-attention, reschedule (dates as `YYYY-MM-DD`), move-to-project (by name), and tag add/remove — then saves the body; the store strips the block so it never persists. A mistyped `state` surfaces a validation error; a buffer with no block changes no metadata (so deleting the block can't wipe your tags). Inline **`#hashtags`** typed in the body are also added as tags on save (a `# heading` doesn't count) and are rendered in **italics** so they stand out. Link-follow and promotion are unaffected (they're content-relative, not line-absolute).
- Frontmatter projection (§8.3): a node can now be fetched with an editable **YAML frontmatter** block prepended — `node.get {frontmatter: true}` renders `id`/`kind`/`title`/`tags`, and for a task (or its context doc) the owning task's `state`/`attention`/`do_date`/`late_on`/`recurrence`/`project` plus a `task:` ref. Dates are local `YYYY-MM-DD`. On write, the store **strips and ignores** any leading frontmatter (conservatively — a real `---` hrule in prose survives) before the CRDT diff, so frontmatter never persists and an unchanged read→write is a no-op; a naive editor can't corrupt metadata. This is the read/write groundwork for editing a node's metadata as frontmatter in heph.nvim (the diff-into-RPCs layer is next).
- Tags (§4, §8.3): nodes can now be **tagged**. A tag is a `tag`-kind node whose id is deterministic in `(owner, name)`, so the same name is **one canonical tag** shared across everything it's applied to (and replicas converge — no duplicate tags). Tagging is an OR-set link, so adding/removing is idempotent and merge-safe. Surfaced as `tag.add`/`tag.remove`/`tag.list` RPCs and `heph tag add|rm|list` (list a node's tags, or every tag with no node). Tag names are trimmed; a canonical case/spelling normalization is deferred to the future zk import. This is the groundwork for the `tags:` line of the upcoming frontmatter edit surface.
- - `heph-quickadd` (§8) — a **global quick-capture popover**, the last piece needed to retire Todoist. A tiny always-warm `eframe`/`egui` agent registers **⌘'** system-wide (`global-hotkey`, via Carbon — no Accessibility permission) and, on press, *toggles an already-created hidden window visible and focuses a single capture field* — never spawning on the keypress, so it's a muscle-reflex ([[design]] §6.2.1). The field reuses the shared `quickadd::parse` (lifted from `heph-tui` into `hephd`'s lib so both surfaces share one parser): one line like `Call dentist fri p1 #Health` parses live into title + attention (`p1``p4`) + do-date + recurrence + project, shown as a chip row (⚑ attention · 📁 project · ⏰ do-date · ↻ recurrence) as you type. **Enter** saves **optimistically** — the window hides instantly and `task.create` runs on a background thread, so perceived latency is just the keystroke; a failed RPC re-shows the window with the text restored, so a capture is never lost. **Esc** dismisses.
- Supervision (no second launch agent): **hephd supervises `heph-quickadd`** as a child in local mode on macOS (opt-in via `HEPH_QUICKADD=1`, which the installed launchd plist now sets — dev/test runs that spawn a local daemon never pop a window). hephd already runs as a `gui/$uid` LaunchAgent, so the child inherits the Aqua session the hotkey/GUI need, and the user still installs/manages exactly one service (`heph daemon`). The helper **self-exits when orphaned** (its parent daemon gone) and exits if ⌘' is already held, so killing hephd — even `kill -9` — leaves nothing behind and duplicates never stack. Runs as a macOS **accessory** app (no Dock icon). macOS-only for v1; `global-hotkey` + `egui` also run on Linux/X11, so the Linux port is mostly window-behavior polish.
- - `heph-tui` move-to-project picker (`m`) is now an **fzf-style filter** (§8.1): a prompt line on top narrows the project list by fuzzy subsequence match as you type (↑/↓ or Ctrl-n/p move, Enter selects, Esc cancels) — no more scrolling the full list. When the typed text names no existing project, a **"+ New project"** row is offered: selecting it **creates the project and files the task under it** in one step (via `node.create`), so projects can be created without leaving the TUI.
- - `heph-tui` safety + undo wave (§8.1):
- **Pane-specific keys.** Task-triage gestures (`x`/`d`/`S`/`A`/`b`/`e`/`m`/`D`) now fire **only when the task pane is focused**, so a stray keypress while navigating the sidebar can no longer drop or delete a task. The sidebar gets its own actions; the status-line hints are now focus-aware.
- **Undo / redo.** **`u`** undoes the last triage action (drop, done, skip, attention, move) and **Ctrl-z** redoes it, restoring the task's prior state from a snapshot — multi-level, capped at 200 steps. (Tombstone-delete stays excluded — it keeps its y/N prompt — and an attention of "none" can't be re-cleared.)
- **Delete a project** from the sidebar with **`D`** (y/N confirm); its tasks become unfiled (they move to the Inbox), not deleted.
- **Sidebar auto-refreshes** after creating or deleting a project, so a new project shows up immediately (no more quit-and-reload).
### Bug Fixes
- Fix `clippy::single_match` lint in heph-tui sidebar key handling (CI failure on main).
- Pin the Quartz docs build to v4.5.2. The Dagger `build_docs` pipeline cloned Quartz from the default branch unpinned; Quartz v5.0.0 restructured its config layout (`.quartz/plugins`, `../quartz` imports) and broke the docs build against our existing `quartz.config.ts`/`quartz.layout.ts`.
### Infrastructure
- Extracted the Neovim plugin into its own forge repo, [eblume/hephaestus.nvim](ssh://forgejo@forge.ops.eblu.me:2222/eblume/hephaestus.nvim.git). Removed `heph.nvim/` from the monorepo along with its build/test wiring: the `test_nvim` Dagger function, the `dagger call test-nvim` CI step, the `mise run test-nvim` task, and the `.stylua.toml` + stylua prek hook (no Lua remains here). The CLI/TUI→nvim integration is unchanged (they shell out to `nvim` expecting the plugin installed). Install now uses a plain lazy.nvim spec pointing at the plugin repo — see [[install-heph]].
- Pre-v1 dependency-refresh sweep: bumped all external crates to latest stable — `keyring` 3→4 (the new `keyring_core` split + register-the-native-store model), `rusqlite` 0.32→0.40, `ratatui` 0.29→0.30, `rrule` 0.13→0.14, `yrs` 0.26→0.27, plus semver-compatible updates across the rest. Dropped the `fs4` dependency in favor of `std::fs::File::try_lock` (stable since Rust 1.89), raising the workspace MSRV to 1.89. Removed the orphaned `.forgejo/scripts/build` hook (CI calls Dagger directly). Full suite green: 228 Rust tests + 25 heph.nvim headless e2e specs, `clippy -D warnings` + `fmt` clean.
- Hub auth now **resolves an OIDC `sub` to an `owner_id`** (`Store::resolve_owner → Option<owner_id>`) instead of a single-tenant boolean gate (`authorize_owner_sub → bool`). Behavior is unchanged for the single-owner hub (claim-on-first; a stranger's token still 403s), but the contract no longer assumes one global owner — this is the multi-tenancy seam, so serving N owners later is additive rather than a rewrite. See the `Adoption + multi-tenant` task's context for the full decision.
- - **`cargo fmt` is now a prek hook** — `cargo fmt --all` runs in place over the workspace on any staged `.rs` change (grouped with the other in-place formatters), so unformatted Rust can't be committed locally. CI already enforced `cargo fmt --check` via `dagger call check`; this catches it at commit time instead.
- - Lua is now formatted by **stylua** (a `.stylua.toml` + a `stylua-system` prek hook), so heph.nvim's whitespace is enforced by an opinionated formatter rather than by hand.
- `mise run import-todoist` — a one-way importer that seeds a heph store from your Todoist projects + active tasks (project hierarchy, priority→attention, do-dates, natural-language recurrence, descriptions + sub-tasks as context items). Dry-run by default; `-- --commit` writes into your real store after backing it up. See [[import-todoist]].
- Add a `cargo clippy -D warnings` pre-commit hook so lints are caught locally before CI.
- Customize the repository from the `project-template`: rename the Dagger module to `hephaestus_ci` (`HephaestusCi`), set the docs `baseUrl`, add an All-Rights-Reserved `LICENSE`, and update `README.md`/`AGENTS.md` for the hephaestus project.
- Slimmed the credential-keyring dependency to cut CI compile time. keyring 4's `keyring` meta-crate compiles *every* platform backend for the target — on Linux that pulled the zbus async stack, a redundant libdbus secret-service, the kernel keyutils store, a SQLite/zstd `db-keystore`, and OpenSSL (~290 crates in its subtree). Replaced it with `keyring-core` (the API) plus a single store per OS — macOS Keychain (`apple-native-keyring-store`), Linux Secret Service (`dbus-secret-service-keyring-store`, pure-Rust crypto, vendored libdbus so the build needs no system `libdbus-1-dev`) — registered directly in `oauth.rs`. hephd's Linux dependency graph drops from **401 to 235 crates** (166), removing the zbus stack and two C builds. Runtime behavior is unchanged.
### Documentation
- New how-to: [[set-up-sync-hub]] — stand up the canonical hub and connect an existing local device as an offline-capable spoke, the data-safe way (the hub adopts the device's identity rather than rewriting the device).
- - Added a [[task-lifecycle]] explanation doc: the **two-axis task model** (lifecycle state — outstanding/done/dropped — kept separate from attention white/orange/red/blue), why **On Deck (blue) is not the same as dropped**, what **delete/tombstone** does (and that it keeps the context doc), and where each task shows up across the agenda, full-text search, and export.
- Surface strategy revised to a **three-surface model** ([[design]] §4, tech-spec §1/§8), grounded in a study of the owner's live Todoist usage (387 active tasks, 34 hierarchical projects — [[design]] §6.2.1): the **CLI** is task capture/scripting plus the complete daemon API, a planned **`heph-tui`** terminal UI (tech-spec §8.1) is the primary task agenda/triage surface, and **`heph.nvim`** is the primary context/knowledge-base surface. The study also confirms do-date-not-due-date (zero deadlines used), that task descriptions are already full of unresolved `[[wiki-links]]` (the exact task↔KB fusion heph provides), and surfaces new needs (project hierarchy, natural-language recurrence) while showing tags are negligible.
- - **heph self-hosts its roadmap (the "bootstrap lift").** With v1 at Todoist feature-parity, remaining/future work is now tracked **in heph itself** — as tasks in the `Hephaestus` project (`heph view ondeck`) — rather than in a document. The tech-spec is reframed accordingly: `tech-spec.md`**`v1-prototype-tech-spec.md`** (all wiki-links + paths updated), retitled and bannered as a **historical** record of the v1 build, with its §14 tracker frozen. `AGENTS.md` gains a *Planning future work* section (capture via `heph task "…" --project Hephaestus`, triage in `heph-tui` On Deck); `README.md`'s status reflects parity + the three daily-driver surfaces. The living **[[design]]** doc remains the rationale of record.
- Add the project design document (`docs/explanation/design.md`, rationale + decision history) and the distilled technical specification (`docs/reference/v1-prototype-tech-spec.md`, the build artifact) defining hephaestus as a unified, self-hosted, **client/server + offline-first** task + knowledge-base system: typed node graph, the lived priority discipline ("what is next?"), recurrence with fresh-per-occurrence checklists, op-log/CRDT sync with conflict resolution, OIDC/Authentik auth, the heph.nvim surface, and a test-driven development strategy.
- Second-pass review of the v1 design + tech spec before Phase 1: resolved context-item storage (Fork A index model), recurrence (roll-forward in place), key-unique node identity (deterministic ids for journal/tag), mode/sync orthogonality (`hub_url` spoke), the local-only owner / OIDC adoption path, the "what is next?" ranking (do-date as candidacy filter only, late-on as the sole urgency signal), and the plugin-side mode model (added `list` + `log.tail`).