generated from eblume/project-template
feat: heph list --project <name> + --json; thin AGENTS.md
Some checks failed
Build / validate (pull_request) Failing after 3m21s
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:
parent
911255fece
commit
dce3519345
8 changed files with 91 additions and 17 deletions
17
AGENTS.md
17
AGENTS.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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!({}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
1
docs/changelog.d/v1-list-project.feature.md
Normal file
1
docs/changelog.d/v1-list-project.feature.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue