You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
179 lines
5.6 KiB
Markdown
179 lines
5.6 KiB
Markdown
+++
|
|
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.dependencies` belongs to a ticket with `status == done`.
|
|
- Missing dependency IDs are treated conservatively: the ticket is NOT ready.
|
|
|
|
## Behaviour
|
|
|
|
1. Load all tickets with `list_tickets(&root)` (already sorted by priority desc).
|
|
2. Build `done_ids` set (same as `cmd_ready`).
|
|
3. Find the first ticket (highest priority) where:
|
|
- status \!= done
|
|
- all dependencies are in done_ids
|
|
- `filter.matches(t)` (if a filter was provided)
|
|
4. If found: display the single ticket.
|
|
5. 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` (via `display::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:
|
|
|
|
```rust
|
|
/// 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:
|
|
|
|
```rust
|
|
Commands::Next { filter } => cmd_next(filter, cli.json).await,
|
|
```
|
|
|
|
Implement `cmd_next`:
|
|
|
|
```rust
|
|
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:
|
|
|
|
```rust
|
|
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:
|
|
|
|
```markdown
|
|
### 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:**
|
|
|
|
```sh
|
|
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). |