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.
80 lines
3.0 KiB
Markdown
80 lines
3.0 KiB
Markdown
---
|
|
# nbd-csdh
|
|
title: Add graph computation module (src/graph.rs)
|
|
status: completed
|
|
type: feature
|
|
priority: normal
|
|
created_at: 2026-03-10T23:30:31Z
|
|
updated_at: 2026-03-10T23:30:31Z
|
|
---
|
|
|
|
Implement `src/graph.rs` — a module that builds a directed dependency graph from a flat list of tickets and provides the data structures needed by the ASCII renderer and JSON output.
|
|
|
|
## Motivation
|
|
|
|
The `nbd graph` command (see CLI ticket) needs to traverse `Ticket.dependencies` edges to produce an ordered, tree-structured representation of the dependency DAG. This module isolates that logic from I/O and rendering.
|
|
|
|
## Data structures
|
|
|
|
```rust
|
|
/// A node in the dependency graph.
|
|
pub struct GraphNode<'a> {
|
|
pub ticket: &'a Ticket,
|
|
/// Direct dependents (tickets that list this ticket as a dependency).
|
|
pub dependents: Vec<&'a str>,
|
|
/// Direct dependencies (tickets this ticket depends on).
|
|
pub dependencies: Vec<&'a str>,
|
|
}
|
|
|
|
/// A directed dependency graph built from a flat list of tickets.
|
|
pub struct TicketGraph<'a> {
|
|
nodes: IndexMap<&'a str, GraphNode<'a>>,
|
|
}
|
|
```
|
|
|
|
## Functions to implement
|
|
|
|
### `TicketGraph::build(tickets: &[Ticket]) -> TicketGraph`
|
|
- Constructs `nodes` map keyed by ticket ID.
|
|
- Iterates `ticket.dependencies` to populate both `dependencies` (forward) and `dependents` (reverse) edges.
|
|
- IDs in `dependencies` that do not correspond to a known ticket are silently ignored (dangling references are tolerated).
|
|
|
|
### `TicketGraph::roots(&self) -> Vec<&Ticket>`
|
|
- Returns tickets with no dependencies (or whose dependencies are all outside the graph).
|
|
- Sorted by priority descending (same ordering as `list_tickets`).
|
|
- These become the starting points for the recursive ASCII tree renderer.
|
|
|
|
### `TicketGraph::subtree(&self, root_id: &str) -> Vec<&str>`
|
|
- Returns all ticket IDs reachable from `root_id` by following `dependencies` edges in depth-first order, including `root_id` itself.
|
|
- Cycles: track visited set; stop recursion when an ID has been visited. This makes the function safe even if the data contains cycles.
|
|
|
|
### `TicketGraph::to_json_value(&self) -> serde_json::Value`
|
|
- Returns an object like:
|
|
```json
|
|
{
|
|
"nodes": [
|
|
{"id": "a3f9c2", "title": "...", "status": "todo", "dependencies": ["b7d41e"]},
|
|
...
|
|
],
|
|
"edges": [
|
|
{"from": "a3f9c2", "to": "b7d41e"},
|
|
...
|
|
]
|
|
}
|
|
```
|
|
|
|
## Crate dependencies
|
|
|
|
No new crates needed. If `IndexMap` insertion-order is useful, `indexmap` can be added — but a `HashMap` with a separate sorted `Vec<&str>` of keys also works. Prefer whatever avoids adding a new crate dependency.
|
|
|
|
## Files touched
|
|
- `src/graph.rs` — new file, public module
|
|
- `src/main.rs` — `mod graph;` declaration
|
|
|
|
## Tests (unit, in `src/tests.rs`)
|
|
- `build` with an empty slice returns an empty graph.
|
|
- `roots` returns only tickets with no in-graph dependencies.
|
|
- `subtree` returns the correct set of IDs for a linear chain.
|
|
- `subtree` does not infinite-loop when the data contains a cycle.
|
|
- `to_json_value` contains all expected IDs in `nodes` and all edges in `edges`.
|