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.
vibed/nbd/docs/ARCHITECTURE.md

128 lines
4.2 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# nbd — Architecture
## Overview
`nbd` is a single-binary CLI tool with no background service. All state lives in
`.nbd/tickets/` inside the project directory tree. The binary is structured as
four modules, each with a single responsibility:
```
src/
├── main.rs CLI definition and command dispatch
├── ticket.rs Data model, ID generation, priority validation
├── store.rs File I/O, directory traversal, CRUD
└── display.rs Human-readable and JSON output formatting
```
## Module Responsibilities
### `main.rs` — CLI entry point
Owns the `clap` struct definitions (`Cli`, `Commands`) and the top-level `main`
function. Delegates all business logic to command handlers (`cmd_create`,
`cmd_read`, `cmd_list`, `cmd_update`). The handlers are thin: they call helpers
from `store`, `ticket`, and `display`, then return.
Parsing helpers (`parse_status`, `parse_ticket_type`, `parse_deps`) live here
because they translate raw CLI strings into typed values used only by the command
handlers.
### `ticket.rs` — Data model
Defines the canonical in-memory representation of a ticket:
- `Ticket` struct — all fields
- `Status` enum — `Todo | InProgress | Done`
- `TicketType` enum — `Project | Feature | Task | Bug`
Provides two free functions:
- `generate_id() -> String` — uses `RandomState` (seeded with OS entropy) to
produce a 6-character lowercase hex string. No external randomness crate needed.
- `validate_priority(u8) -> Result<(), String>` — enforces the 010 range.
The enums derive `serde`'s `Serialize`/`Deserialize` with `#[serde(rename_all)]`
so the JSON representation uses lowercase strings (`"in_progress"`, `"bug"`, …).
### `store.rs` — File I/O
All filesystem operations are in this module. Uses `async-std` for async I/O
throughout.
**Directory discovery:**
```
find_nbd_root()
└─ find_nbd_root_from(cwd)
Walk parent dirs until .nbd/ is found
```
`find_nbd_root_from` accepts an explicit starting path to make it testable
without changing the process's working directory.
**CRUD functions:**
| Function | Operation |
|---|---|
| `ensure_tickets_dir(root)` | Create `.nbd/tickets/` if missing |
| `write_ticket(root, ticket)` | Serialise → pretty JSON → write file |
| `read_ticket(root, id)` | Read file → deserialise; friendly error if not found |
| `list_tickets(root)` | Read all `*.json` → sort by priority desc |
**Error type:** `store::Result<T>` is aliased to
`Result<T, Box<dyn Error + Send + Sync>>`, allowing `?` to propagate both
`io::Error` and `serde_json::Error` without explicit wrapping.
### `display.rs` — Output formatting
Two output modes:
- **Tabular** — human-readable keyvalue block (single ticket) or column-aligned
table (list). Column widths are compile-time constants.
- **JSON** — delegates to `serde_json::to_string_pretty`.
Each public surface is split into a `format_*` function (returns `String`,
testable) and a `print_*` function (writes to stdout via `println!`).
## Data Flow
```
CLI args (clap)
dispatch() [main.rs]
├── parse_* helpers ──► typed values
├── store::find_nbd_root() ──► PathBuf
├── store::read_ticket() ──► Ticket
├── store::list_tickets() ──► Vec<Ticket>
├── store::write_ticket() ──► ()
└── display::print_ticket() ──► stdout
display::print_list() ──► stdout
```
## Storage Layout
```
<project-root>/
└── .nbd/
└── tickets/
├── a3f9c2.json
├── b7d41e.json
└── ...
```
Each file is a pretty-printed JSON object with the fields of `Ticket`. File name
equals the ticket's `id` field with a `.json` extension.
## Testing Strategy
- **Unit tests** (`src/tests.rs`): exercise `ticket.rs`, `store.rs`, and
`display.rs` in isolation using `tempfile::TempDir` for any file I/O.
- **Integration tests** (`tests/integration.rs`): drive full command flows
(create → read → list → update) through `cmd_*` functions against a temporary
directory. Include a directory-traversal test that invokes commands from a
nested subdirectory.