feat: heph list --project <name> + --json; thin AGENTS.md
Some checks failed
Build / validate (pull_request) Failing after 3m21s

`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>
This commit is contained in:
Erich Blume 2026-06-03 20:38:57 -07:00
commit dce3519345
8 changed files with 91 additions and 17 deletions

View file

@ -41,7 +41,7 @@ See [[agent-change-process]] for the full methodology.
## Project Structure
A Cargo workspace (`Cargo.toml` at the root) plus the Neovim plugin and repo tooling. **v1 reached Todoist feature-parity on 2026-06-03** — the **Rust backend is feature-complete** (all three runtime modes + sync + OIDC auth) and all three surfaces (`heph` CLI, **`heph-tui`**, **`heph.nvim`**) are installed daily-drivers. **Remaining/future work is now tracked in heph itself** (see *Planning* below), not in a doc; the **[[v1-prototype-tech-spec]]** is the historical build record.
A Cargo workspace (`Cargo.toml` at root) plus the Neovim plugin and repo tooling. Backend feature-complete (all three runtime modes + sync + OIDC); three daily-driver surfaces — `heph` (CLI), `heph-tui` (agenda/triage), `heph.nvim` (context/KB).
```
./Cargo.toml # workspace manifest (shared deps + members)
@ -58,15 +58,14 @@ A Cargo workspace (`Cargo.toml` at the root) plus the Neovim plugin and repo too
./mise-tasks/ # repo automation via `mise run`
```
**Development is TDD** (v1-prototype-tech-spec §2, §9): failing test first, implement to green, commit on green. `heph-core` is clock-injected — no ambient wall-clock reads; time is always passed in. The **historical v1 build spec** is [[v1-prototype-tech-spec]]; the **living rationale/decisions** are [[design]].
**TDD:** failing test first → implement to green → commit on green. `heph-core` is clock-injected (no ambient wall-clock; time is passed in). Spec: [[v1-prototype-tech-spec]] (frozen v1 build record); rationale: [[design]] (living). Other doc paths via `mise run ai-docs`; `[[like-this]]` wiki-links refer to `docs/` cards.
Other doc paths are listed via `mise run ai-docs`. Wiki-links (`[[like-this]]`) refer to `docs/` cards.
## Working state (heph self-hosts its roadmap)
## Planning future work (heph self-hosts its roadmap)
Outstanding/future heph work lives as tasks in the **`Hephaestus` project** — inspect it before planning:
Since v1 parity, **heph tracks its own remaining/future work as tasks in the `Hephaestus` project inside the live store** (the "bootstrap lift" — heph plans heph). This replaces the old [[v1-prototype-tech-spec]] §14 tracker, which is now a historical build record.
- `heph list --project Hephaestus` — outstanding tasks (human-readable)
- `heph list --project Hephaestus --json` — JSON rows: `node_id`, `canonical_context_id`, attention/state/do_date/recurrence (for scripting/agents)
- `heph task "<title>" --project Hephaestus -a blue` — capture new work (blue = on-deck backlog)
- **See the roadmap:** `heph view ondeck` (CLI) or `heph-tui` → the **On Deck** sidebar view (the backlog lives as blue/on-deck tasks); open the **Hephaestus** project in the sidebar to see all of it.
- **Capture new work:** `heph task "<title>" --project Hephaestus -a blue` (blue = on-deck backlog, kept out of `next`/ToM until pulled up). Add detail in the task's canonical-context doc via `heph.nvim`.
- **Don't reopen the §14 tracker** for new work — add a task instead. Update the spec only to correct historical record.
- Note the [[design]] doc remains the **living** design/decision log; the tech-spec is frozen as the v1 build description.
Triage in `heph-tui` (*On Deck* view). Add a task for new work — don't reopen the frozen §14 tracker.

View file

@ -282,6 +282,10 @@ impl Store for LocalStore {
tasks::view(&self.conn, &self.owner_id, now, name)
}
fn project_scope(&self, name: &str) -> Result<Vec<String>> {
tasks::project_scope(&self.conn, &self.owner_id, name)
}
fn health(&self) -> Result<Health> {
tasks::health(&self.conn, &self.owner_id)
}
@ -470,6 +474,21 @@ mod tests {
assert_eq!(v, latest_version());
}
#[test]
fn project_scope_resolves_by_name_and_errors_on_unknown() {
use crate::model::{NewNode, NodeKind};
let mut store = store_at(1);
let p = store
.create_node(NewNode {
kind: NodeKind::Project,
title: "Garden".into(),
body: None,
})
.unwrap();
assert_eq!(store.project_scope("Garden").unwrap(), vec![p.id]);
assert!(store.project_scope("Nope").is_err());
}
#[test]
fn delete_project_unfiles_its_tasks_then_tombstones_it() {
use crate::filter::ListFilter;
@ -511,9 +530,11 @@ mod tests {
// The project node is tombstoned…
let ts: i64 = store
.conn
.query_row("SELECT tombstoned FROM nodes WHERE id=?1", [&proj.id], |r| {
r.get(0)
})
.query_row(
"SELECT tombstoned FROM nodes WHERE id=?1",
[&proj.id],
|r| r.get(0),
)
.unwrap();
assert_eq!(ts, 1);
// …and the task survives, now unfiled (it shows in the Inbox), not orphaned.

View file

@ -438,6 +438,17 @@ fn resolve_project_names(conn: &Connection, owner: &str, names: &[&str]) -> Resu
Ok(ids)
}
/// Resolve a single project NAME to its scope: the project id plus its subtree
/// (parent→child). Errors if the name names no project, so `--project Foo` fails
/// loudly rather than silently widening to "everything".
pub(super) fn project_scope(conn: &Connection, owner: &str, name: &str) -> Result<Vec<String>> {
let scope = resolve_project_names(conn, owner, &[name])?;
if scope.is_empty() {
return Err(Error::InvalidArg(format!("no project named {name:?}")));
}
Ok(scope)
}
/// Working-set health counts (tech-spec §7) — surfaced honestly.
pub(super) fn health(conn: &Connection, owner: &str) -> Result<Health> {
let mut stmt = conn.prepare(

View file

@ -128,6 +128,10 @@ pub trait Store {
/// unknown view name.
fn view(&self, name: &str) -> Result<Vec<RankedTask>>;
/// Resolve a project NAME to its scope ids (the project + its subtree), for
/// `heph list --project <name>`. Errors if the name names no project.
fn project_scope(&self, name: &str) -> Result<Vec<String>>;
/// Working-set health — orange/active/on-deck/conflict counts (tech-spec §7).
fn health(&self) -> Result<Health>;

View file

@ -68,12 +68,18 @@ enum Command {
/// Restrict to a project node id.
#[arg(long)]
scope: Option<String>,
/// Restrict to a project by NAME (subtree-expanded). e.g. --project Hephaestus.
#[arg(long)]
project: Option<String>,
/// Only this attention-state: white|orange|red|blue.
#[arg(short = 'a', long)]
attention: Option<String>,
/// Hide on-deck (blue) items.
#[arg(long)]
no_blue: bool,
/// Print raw JSON rows (node id, canonical-context id, scalars) for scripting/agents.
#[arg(long)]
json: bool,
},
/// Run a built-in filter view (tech-spec §8.2); omit the name to list views.
View {
@ -429,16 +435,21 @@ fn main() -> Result<()> {
}
Command::List {
scope,
project,
attention,
no_blue,
json,
} => {
// `list` takes a ListFilter (tech-spec §8.2). Map the legacy flags:
// a single `--scope` id, a single `--attention` whitelist, and
// `--no-blue` as an attention exclusion.
// `list` takes a ListFilter (tech-spec §8.2). Map the flags: a single
// `--scope` id or `--project` NAME (resolved + subtree-expanded by the
// daemon), a single `--attention` whitelist, and `--no-blue`.
let mut filter = json!({});
if let Some(s) = scope {
filter["scope"] = json!([s]);
}
if let Some(p) = project {
filter["project"] = json!(p);
}
if let Some(a) = attention {
filter["attention_in"] = json!([a]);
}
@ -446,7 +457,11 @@ fn main() -> Result<()> {
filter["attention_not"] = json!(["blue"]);
}
let result = client.call("list", filter)?;
print_rows(result)?;
if json {
println!("{}", serde_json::to_string_pretty(&result)?);
} else {
print_rows(result)?;
}
}
Command::View { name } => match name {
Some(name) => {

View file

@ -209,6 +209,10 @@ impl Store for RemoteStore {
self.call_as("view", json!({ "name": name }))
}
fn project_scope(&self, name: &str) -> Result<Vec<String>> {
self.call_as("project.scope", json!({ "name": name }))
}
fn health(&self) -> Result<Health> {
self.call_as("health", json!({}))
}

View file

@ -112,6 +112,16 @@ struct IdParam {
id: String,
}
/// `list` params: a [`ListFilter`] plus an optional `project` NAME the daemon
/// resolves (subtree-expanded) into the filter's `scope`.
#[derive(Deserialize)]
struct ListParams {
#[serde(flatten)]
filter: ListFilter,
#[serde(default)]
project: Option<String>,
}
#[derive(Deserialize)]
struct GetNodeParams {
id: String,
@ -387,13 +397,22 @@ pub fn dispatch(store: &mut dyn Store, method: &str, params: Value) -> Result<Va
json!(store.next(p.scope.as_deref(), p.limit.unwrap_or(DEFAULT_LIMIT))?)
}
"list" => {
let filter: ListFilter = parse(params)?;
let p: ListParams = parse(params)?;
let mut filter = p.filter;
// `--project <name>` resolves to its subtree scope server-side.
if let Some(name) = p.project {
filter.scope = store.project_scope(&name)?;
}
json!(store.list(&filter)?)
}
"view" => {
let p: ViewParams = parse(params)?;
json!(store.view(&p.name)?)
}
"project.scope" => {
let p: ViewParams = parse(params)?;
json!(store.project_scope(&p.name)?)
}
"health" => json!(store.health()?),
"search" => {
let p: SearchParams = parse(params)?;

View file

@ -0,0 +1 @@
- **`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.