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>
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>
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>
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>
- `<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>
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>
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>
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>
`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>
`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>
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>
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>
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>
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 `.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>
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>
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>
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>
`: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>
`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>
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>
`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>
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>
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>
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>
§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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>