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.

102 lines
3.6 KiB
Markdown

+++
title = "Scope nbd next and nbd ready by dependency subtree"
priority = 5
status = "done"
ticket_type = "feature"
dependencies = []
+++
## Summary
Add an optional positional `<id>` argument to `nbd next` and `nbd ready` that scopes
the results to the dependency subtree of the given ticket.
## Motivation
When a project has many tickets, you often want to focus on tickets that directly
unblock a specific goal ticket. Currently `nbd next` and `nbd ready` operate over
the entire ticket store, with no way to narrow by project/feature scope except
through `--filter` expressions that don't understand the dependency graph.
## Desired behaviour
```sh
# Highest-priority ready ticket that is a dependency of abc123 (directly or transitively)
nbd next abc123
nbd next abc123 --json
# All ready tickets that are dependencies of abc123 (directly or transitively)
nbd ready abc123
nbd ready abc123 --json
# Both commands still accept --filter alongside the scoping ID
nbd next abc123 --filter type=bug
nbd ready abc123 --filter priority=8
```
The root ticket itself (abc123) is *not* included in the results — only its
dependencies (the tickets it is waiting on).
## Implementation plan
### 1. CLI changes (`src/main.rs`)
In `Commands::Next` and `Commands::Ready`, add an optional positional argument:
```rust
id: Option<String>, // Optional ticket ID or prefix to scope results
```
Update `dispatch` to pass the new argument to `cmd_next` and `cmd_ready`.
Update the CLI doc-comments for both subcommands to mention the scoping
behaviour.
### 2. Logic changes (`src/main.rs`)
In `cmd_next` and `cmd_ready`, after loading all tickets, if `scope_id` is
`Some`:
1. Resolve the prefix to a full ID using `resolve_id`.
2. Build a `TicketGraph` from all tickets (the full list, not filtered).
3. Call `graph.subtree(&resolved_id)` to get the set of all dependency IDs
reachable from the root (this already excludes the root itself when it is
not in its own dependency list).
4. Convert the subtree IDs into a `HashSet`.
5. Restrict the ready/next candidate pool to tickets whose ID is in that set
*and* whose ID is not the scoping ticket itself (the root should never be
returned as a "what to do next" result when it is the scope target).
`TicketGraph::subtree` is already implemented in `src/graph.rs` and handles
cycles correctly via a visited set.
### 3. Test coverage (`tests/integration.rs`)
Add integration tests (using `tempdir`):
- **`test_next_scoped_by_id`**: create a project ticket P with deps [A, B],
where A has dep [C]. Mark C as done. Verify `nbd next P` returns A (the
highest-priority ready dep of P), not P itself or any unrelated ticket.
- **`test_ready_scoped_by_id`**: same setup; verify `nbd ready P` returns
[A] (B is blocked because... actually B has no deps so it's also ready).
Adjust setup so exactly the expected set is returned.
- **`test_next_scoped_no_ready`**: all deps of P are done; verify `nbd next P`
returns no ticket (`{"next": null}` in JSON mode).
- **`test_ready_scoped_with_filter`**: verify `--filter` still narrows within
the scoped set.
### 4. Relevant files
| File | Change |
|---|---|
| `src/main.rs:135-156` | `Commands::Ready` and `Commands::Next` — add `id: Option<String>` |
| `src/main.rs:308-310` | `dispatch` arms for `Next` and `Ready` — pass new arg |
| `src/main.rs:473-511` | `cmd_ready` — add subtree scoping logic |
| `src/main.rs:522-566` | `cmd_next` — add subtree scoping logic |
| `src/graph.rs:165-172` | `TicketGraph::subtree` — already correct, no change needed |
| `tests/integration.rs` | Add 4 new scoped-by-id tests |
### 5. Validation
```sh
cargo fmt && cargo check && cargo clippy && cargo test
```