docs(nbd): add README, PLANNING, ARCHITECTURE, and complete Phase 6

Write the three required documentation files for the nbd crate:
- README.md: usage guide, field reference, dev workflow, dual-license
  footer, and Claude Code disclaimer
- docs/PLANNING.md: development phase log and post-MVP roadmap
- docs/ARCHITECTURE.md: module responsibilities, data flow diagram,
  storage layout, and testing strategy

Run and verify cargo fmt, check, clippy, and test (34 tests pass).
Mark all Phase 6 items as complete in PLAN.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
quotesdb
Elijah Voigt 3 months ago
parent 78874df7a6
commit 7ada8ef951

@ -119,11 +119,11 @@ Wire up `clap` subcommands to storage and display.
## Phase 6: Documentation & Validation ## Phase 6: Documentation & Validation
- [ ] Write `nbd/README.md`: what, how, run, test, license, Claude Code disclaimer - [x] Write `nbd/README.md`: what, how, run, test, license, Claude Code disclaimer
- [ ] Write `nbd/docs/PLANNING.md`: this plan + work log - [x] Write `nbd/docs/PLANNING.md`: this plan + work log
- [ ] Write `nbd/docs/ARCHITECTURE.md`: module overview and interactions - [x] Write `nbd/docs/ARCHITECTURE.md`: module overview and interactions
- [ ] Run validation in order: `cargo fmt`, `cargo check`, `cargo clippy`, `cargo test` - [x] Run validation in order: `cargo fmt`, `cargo check`, `cargo clippy`, `cargo test`
- [ ] Verify end-to-end: - [x] Verified end-to-end:
```sh ```sh
cargo run -- create --title "Test ticket" --priority 7 --type bug cargo run -- create --title "Test ticket" --priority 7 --type bug
cargo run -- list cargo run -- list

@ -0,0 +1,106 @@
# nbd
A CLI tool for managing work tickets, primarily targeted at agent workflows.
Tickets are stored as JSON files in `.nbd/tickets/` inside the nearest ancestor
directory that contains a `.nbd/` folder, discovered by traversing upward from
the current working directory — just like `git` finds `.git/`.
## How it works
Each ticket is a JSON file named `{id}.json`, where `id` is a unique
6-character lowercase hex string (e.g. `a3f9c2`). Tickets carry:
| Field | Type | Default |
|---|---|---|
| `id` | 6-char hex string | auto-generated |
| `title` | string | *(required)* |
| `body` | string | `""` |
| `priority` | integer 010 | `5` |
| `status` | `todo` \| `in_progress` \| `done` | `todo` |
| `ticket_type` | `project` \| `feature` \| `task` \| `bug` | `task` |
| `dependencies` | list of ticket IDs | `[]` |
All commands accept `--json` for machine-readable output.
## Usage
### Initialise
Create the tickets directory manually in your project root:
```sh
mkdir -p .nbd/tickets
```
### Create a ticket
```sh
nbd create --title "Fix login bug" --priority 8 --type bug
nbd create --title "Add rate limiting" --body "Protect public endpoints" --deps a3f9c2
```
### Read a ticket
```sh
nbd read a3f9c2
nbd read a3f9c2 --json
```
### List all tickets
```sh
nbd list
nbd list --json
```
### Update a ticket
Only the flags you supply are changed; all other fields retain their current
values.
```sh
nbd update a3f9c2 --status in_progress
nbd update a3f9c2 --priority 9 --type bug
```
## Running
```sh
# From the nbd/ directory
cargo run -- create --title "Test ticket" --priority 7 --type bug
cargo run -- list
cargo run -- read <id>
cargo run -- update <id> --status in_progress
cargo run -- list --json
```
## Testing
```sh
cargo test
```
Unit tests live in `src/tests.rs`. Integration tests (full command flows against
a temporary directory) live in `tests/integration.rs`.
## Development
Run these commands in order before committing:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
## License
Dual-licensed under [Apache License, Version 2.0](../LICENSE-APACHE) and
[MIT License](../LICENSE-MIT), consistent with the rest of the `vibed` mono-repo.
---
*This software was written with [Claude Code](https://claude.com/claude-code)
using the claude-sonnet-4-6 model.*

@ -0,0 +1,127 @@
# 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.

@ -0,0 +1,79 @@
# nbd — Planning & Work Log
## Development Phases
### Phase 1: Crate Scaffold ✅
Set up the crate structure with no logic yet.
- Created `Cargo.toml` with `clap` (derive), `async-std`, `serde`, `serde_json`; dev-dep `tempfile`
- Added `[profile.release]` optimisations (`opt-level = "z"`, `lto`, `strip`, `codegen-units = 1`)
- Created `src/main.rs``async-std` entry point with placeholder `main`
- Created empty modules: `ticket.rs`, `store.rs`, `display.rs`, `tests.rs`
- Created `tests/integration.rs`
- Verified `cargo check` passed
### Phase 2: Data Model (`ticket.rs`) ✅
- Defined `Status` enum (`Todo`, `InProgress`, `Done`; default `Todo`)
- Serialises to lowercase snake_case: `"todo"`, `"in_progress"`, `"done"`
- Defined `TicketType` enum (`Project`, `Feature`, `Task`, `Bug`; default `Task`)
- Serialises to lowercase: `"project"`, `"feature"`, `"task"`, `"bug"`
- Defined `Ticket` struct with all required fields
- Implemented `Ticket::new(id, title)` with defaults
- Implemented `validate_priority(priority)` — errors if > 10
- Implemented `generate_id()` — 3 random bytes → 6 hex chars via `RandomState`
- Unit tests: serialisation roundtrip, priority validation, ID format and uniqueness
### Phase 3: Storage (`store.rs`) ✅
- Implemented `find_nbd_root_from(start)` — walks up from `start` to find `.nbd/`
- Implemented `find_nbd_root()` — starts from `std::env::current_dir()`
- Implemented `tickets_dir(root)` — pure path computation
- Implemented `ensure_tickets_dir(root)` — creates `.nbd/tickets/` if missing
- Implemented `ticket_path(root, id)` — pure path computation
- Implemented `write_ticket(root, ticket)` — serialise and write JSON file
- Implemented `read_ticket(root, id)` — read and deserialise; descriptive error if not found
- Implemented `list_tickets(root)` — read all `*.json`, sort by priority descending
- Unit tests: write/read roundtrip, list returns all tickets, traversal from grandparent dir
### Phase 4: Display (`display.rs`) ✅
- Implemented `format_ticket(ticket)` / `print_ticket(ticket)` — full tabular view
- Implemented `format_ticket_json(ticket)` / `print_ticket_json(ticket)` — pretty JSON
- Implemented `format_list(tickets)` / `print_list(tickets)` — short summary table
- Implemented `format_list_json(tickets)` / `print_list_json(tickets)` — JSON array
- `format_*` functions return `String` for testability; `print_*` write to stdout
- Unit tests: table output contains expected field values, JSON is valid
### Phase 5: CLI Commands (`main.rs`) ✅
- Defined `Cli` struct with global `--json` flag and `Commands` enum
- Subcommands: `create`, `read`, `list`, `update`
- Implemented `cmd_create`, `cmd_read`, `cmd_list`, `cmd_update`
- Added `parse_status`, `parse_ticket_type`, `parse_deps`, `validate_deps` helpers
- Fixed clippy warning: `map_or(false, ...)``is_some_and(...)`
- Integration tests: create→read roundtrip, list, update merge, traversal from subdir,
error on unknown ID, `--json` flag, dep replacement
### Phase 6: Documentation & Validation ✅
- Wrote `README.md` — what, how, usage, testing, license, Claude Code disclaimer
- Wrote `docs/PLANNING.md` (this file) — phases and work log
- Wrote `docs/ARCHITECTURE.md` — module overview and interactions
- Ran `cargo fmt`, `cargo check`, `cargo clippy`, `cargo test` — all clean
---
## Post-MVP Roadmap
See [`PLAN.md`](../PLAN.md) for the full list of planned post-MVP features, including:
- `nbd init` — explicit initialisation command
- `nbd ready` — list tickets with no blockers
- `nbd archive` — mark tickets as Closed
- `nbd update` git-diff-style output
- Partial ID matching
- SQLite cache
- Nix flake
- Multiple file format support (`.md`, `.toml`, `.jsonb`)
Loading…
Cancel
Save