5.6 KiB
+++ title = "Add nbd next subcommand" priority = 7 status = "done" ticket_type = "feature" dependencies = ["887344"] +++
Summary
Add nbd next subcommand that selects the single highest-priority ticket that is
ready to work on. Supports --filter for additional narrowing within the ready set.
Depends on: --filter wired into CLI (ticket 887344). Implicitly depends on the
TicketFilter module (ticket c2a024) through that.
Definition of "ready"
Same semantics as nbd ready:
status \!= done- Every ID in
ticket.dependenciesbelongs to a ticket withstatus == done. - Missing dependency IDs are treated conservatively: the ticket is NOT ready.
Behaviour
- Load all tickets with
list_tickets(&root)(already sorted by priority desc). - Build
done_idsset (same ascmd_ready). - Find the first ticket (highest priority) where:
- status != done
- all dependencies are in done_ids
filter.matches(t)(if a filter was provided)
- If found: display the single ticket.
- If not found: print a "no ready tickets" message. Exit 0 (not an error).
Output
Without --json:
- Found: print the ticket in the same tabular format as
nbd read(viadisplay::print_ticket). - Not found:
No ready tickets.
With --json:
- Found:
{"next": { ...ticket fields including id... }} - Not found:
{"next": null}
Wrapping in {"next": ...} (rather than bare ticket or bare null) makes the JSON
unambiguously parseable by consumers — they always get an object with a next key.
Implementation
Add to Commands enum:
/// Choose the highest-priority ticket that is ready to work on.
///
/// A ticket is ready when its status is not `done` and every ticket it
/// depends on has status `done`. Returns the single highest-priority
/// ready ticket, optionally narrowed by `--filter KEY=VALUE`.
///
/// Exits 0 even when no ready ticket exists.
Next {
/// Filter ready tickets: key=value pairs (repeatable).
/// AND between different keys, OR within same key.
#[arg(long = "filter", value_name = "KEY=VALUE")]
filter: Vec<String>,
},
Add dispatch arm:
Commands::Next { filter } => cmd_next(filter, cli.json).await,
Implement cmd_next:
async fn cmd_next(filter_args: Vec<String>, json: bool) -> store::Result<()> {
let root = find_nbd_root()?;
let all = list_tickets(&root).await?; // sorted by priority desc
let filter = filter::parse_filters(&filter_args)?;
let done_ids: std::collections::HashSet<&str> = all
.iter()
.filter(|t| t.status == Status::Done)
.map(|t| t.id.as_str())
.collect();
let next = all.iter().find(|t| {
t.status \!= Status::Done
&& t.dependencies.iter().all(|dep| done_ids.contains(dep.as_str()))
&& filter.matches(t)
});
if json {
match next {
Some(ticket) => {
let value = serde_json::json\!({
"next": display::ticket_to_json_value(ticket)
});
println\!("{}", serde_json::to_string_pretty(&value)?);
}
None => println\!("{}", serde_json::json\!({"next": null})),
}
} else {
match next {
Some(ticket) => display::print_ticket(ticket),
None => println\!("No ready tickets."),
}
}
Ok(())
}
display.rs: make ticket_to_json_value pub(crate)
ticket_to_json_value is currently private in display.rs. It needs to be accessible
from cmd_next in main.rs. Change its visibility:
pub(crate) fn ticket_to_json_value(ticket: &Ticket) -> serde_json::Value { ... }
This is the cleanest approach — it reuses the existing id-injection logic rather than duplicating it.
README update
Add a ### Find the next ticket to work on section:
### Find the next ticket to work on
Returns the single highest-priority ticket that is ready to work on — not done
and with all dependencies completed.
```sh
nbd next
nbd next --json
nbd next --filter type=bug # highest-priority ready bug
nbd next --filter priority=9 # highest-priority ready ticket with priority 9
Exits 0 even when no ready ticket exists.
## CLAUDE.md update
Update the "Workflow" section to mention `nbd next` as an alternative to `nbd ready`
when the caller just wants to begin the single most important task:
To get the single best ticket to work on next:
cargo run -- next --json
## Files touched
- `src/main.rs` — `Next` command variant, `cmd_next` handler, dispatch arm
- `src/display.rs` — `ticket_to_json_value` changed to `pub(crate)`
- `tests/integration.rs` — integration tests
- `README.md` — new section for `nbd next`
- `CLAUDE.md` — update workflow section
## Integration tests to add (tests/integration.rs)
- Three tickets: A (priority 5, no deps), B (priority 8, dep A), C (priority 7, no deps).
`nbd next --json` returns C (highest priority ready ticket — B is blocked by A).
- After marking A done: `nbd next --json` returns B (priority 8, now unblocked).
- With only done tickets: `nbd next --json` returns `{"next": null}`.
- `nbd next` (no `--json`) with no ready tickets prints "No ready tickets." and exits 0.
- `nbd next --filter type=bug --json`: create a bug and a task, both ready.
Returns the bug if it's the highest-priority bug, otherwise the highest-priority bug.
(Create bug priority 8, task priority 9: with filter, should return the bug.)
- `nbd next --json` returns a JSON object with a `"next"` key containing all ticket fields
including `"id"`.
- `nbd next --filter priority=99 --json` returns `{"next": null}` (no ticket has priority 99).