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.

178 lines
7.3 KiB
Markdown

# nbd — Implementation Plan
## Overview
`nbd` is a CLI tool for managing work tickets, primarily targeted at agent workflows. It stores tickets as JSON files in `.nbd/tickets/` in the project root (found by traversing up from cwd, like git).
**Crate location:** `vibed/nbd/`
**Language:** Rust (edition 2021)
**Key crates:** `clap`, `async-std`, `serde` / `serde_json`, `tempfile` (tests)
---
## Phase 1: Crate Scaffold
Set up the crate structure with no logic yet.
- [x] Create `nbd/Cargo.toml` with dependencies: `clap` (derive feature), `async-std`, `serde`, `serde_json`; dev-dependencies: `tempfile`
- [x] Add `[profile.release]` optimizations (`opt-level = "z"`, `lto = true`, `strip = true`, `codegen-units = 1`)
- [x] Create `nbd/src/main.rs``async-std` entry point, placeholder `fn main()`
- [x] Create `nbd/src/ticket.rs` — empty module
- [x] Create `nbd/src/store.rs` — empty module
- [x] Create `nbd/src/display.rs` — empty module
- [x] Create `nbd/src/tests.rs` — empty test module
- [x] Create `nbd/tests/integration.rs` — empty integration test file
- [x] Verify: `cargo check` passes
---
## Phase 2: Data Model (`ticket.rs`)
Define the core types.
- [x] Define `Status` enum: `Todo`, `InProgress`, `Done` (default: `Todo`)
- Derive: `Serialize`, `Deserialize`, `Debug`, `Clone`, `PartialEq`
- Serialize to/from lowercase strings (`"todo"`, `"in_progress"`, `"done"`)
- [x] Define `TicketType` enum: `Project`, `Feature`, `Task`, `Bug` (default: `Task`)
- Derive same traits
- Serialize to/from lowercase strings
- [x] Define `Ticket` struct:
```
id: String // 6-char hex, e.g. "a3f9c2"
title: String
body: String
priority: u8 // 0..=10, default 5
status: Status
dependencies: Vec<String> // Vec of ticket IDs
ticket_type: TicketType // field name avoids keyword collision
```
- Derive: `Serialize`, `Deserialize`, `Debug`, `Clone`
- [x] Implement `Ticket::new(id, title)` constructor with all defaults
- [x] Implement priority validation (error if > 10)
- [x] Implement ID generation: 3 random bytes → 6 hex chars (use `std::collections::hash_map::RandomState` or similar; no external crate needed for MVP)
- [x] Unit tests: serialization roundtrip, priority validation, ID format (6 hex chars, unique across N calls)
---
## Phase 3: Storage (`store.rs`)
File I/O and directory traversal using `async-std`.
- [x] Implement `find_nbd_root() -> Result<PathBuf>`: walk from cwd upward until `.nbd/` is found; error with helpful message if not found
- [x] Implement `find_nbd_root_from(start: &Path) -> Result<PathBuf>`: testable variant that accepts a starting path
- [x] Implement `tickets_dir(root: &Path) -> PathBuf`: returns `root/.nbd/tickets/`
- [x] Implement `ensure_tickets_dir(root: &Path) -> Result<()>`: creates `.nbd/tickets/` if missing (used only by `create`)
- [x] Implement `ticket_path(root: &Path, id: &str) -> PathBuf`: returns `.nbd/tickets/{id}.json`
- [x] Implement `write_ticket(root: &Path, ticket: &Ticket) -> Result<()>`: serialize to JSON, write file
- [x] Implement `read_ticket(root: &Path, id: &str) -> Result<Ticket>`: read file, deserialize; error if not found
- [x] Implement `list_tickets(root: &Path) -> Result<Vec<Ticket>>`: read all `*.json` from tickets dir, deserialize all, sort by priority descending
- [x] Unit tests: roundtrip write/read with tempdir, list returns all tickets, traversal finds `.nbd/` in grandparent dir
---
## Phase 4: Display (`display.rs`)
Output formatting.
- [x] Implement `print_ticket(ticket: &Ticket)`: full tabular display
```
ID: a3f9c2
Title: Fix login bug
Body: Users cannot log in with email addresses containing +
Priority: 8
Status: in_progress
Type: bug
Dependencies: b7d41e, c9e823
```
- [x] Implement `print_ticket_json(ticket: &Ticket)`: pretty-printed JSON to stdout
- [x] Implement `print_list(tickets: &[Ticket])`: short table
```
ID PRI TYPE STATUS TITLE
a3f9c2 8 bug in_progress Fix login bug
b7d41e 5 task todo Add rate limiting
```
- [x] Implement `print_list_json(tickets: &[Ticket])`: JSON array to stdout
- [x] Added `format_ticket`, `format_ticket_json`, `format_list`, `format_list_json` internal functions for testability
- [x] Unit tests: table output contains expected field values, JSON output is valid
---
## Phase 5: CLI Commands (`main.rs`)
Wire up `clap` subcommands to storage and display.
- [ ] Define CLI structure with `clap` derive:
- Global flag: `--json` (all commands)
- Subcommand `create`: `--title` (required), `--body`, `--priority`, `--status`, `--type`, `--deps` (comma-separated IDs)
- Subcommand `read`: positional `<id>`
- Subcommand `list`: no args
- Subcommand `update`: positional `<id>`, same optional flags as `create`
- [ ] Implement `cmd_create`: generate ID, validate deps exist, write ticket, print
- [ ] Implement `cmd_read`: find ticket by ID, print
- [ ] Implement `cmd_list`: list all tickets, print
- [ ] Implement `cmd_update`: read existing ticket, merge only provided flags, write, print
- [ ] Integration tests (tempdir): create → read roundtrip, list shows created tickets, update merges correctly, traversal test (run from subdir), error on unknown ID
---
## Phase 6: Documentation & Validation
- [ ] Write `nbd/README.md`: what, how, run, test, license, Claude Code disclaimer
- [ ] Write `nbd/docs/PLANNING.md`: this plan + work log
- [ ] Write `nbd/docs/ARCHITECTURE.md`: module overview and interactions
- [ ] Run validation in order: `cargo fmt`, `cargo check`, `cargo clippy`, `cargo test`
- [ ] Verify end-to-end:
```sh
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
```
---
## Post-MVP
The following features are planned but excluded from the MVP:
- `nbd init` — explicit initialization command
- `nbd ready` — list tickets with no blockers
- `nbd archive` — mark tickets as Closed
- `nbd update` git-diff style +/- output
- Partial ID matching (`nbd read a3f` finding `a3f9c2`)
- SQLite cache for performance
- Nix flake for depending on `nbd` in other projects
### Multiple File Format Support
`nbd` will support multiple ticket file formats selectable via `--ftype=[json|md|toml|jsonb]` on `create` and `update`.
**Format detection** is by file extension: `.json`, `.md`, `.toml`, `.jsonb`. The store layer detects format from the file extension when reading; no format metadata is stored separately.
**Markdown format** (`.md`): The file body is the ticket `body` field. Metadata is stored as frontmatter:
- `---` delimiter → YAML frontmatter
- `+++` delimiter → TOML frontmatter
Example (YAML frontmatter):
```
---
id: a3f9c2
title: Fix login bug
priority: 8
status: in_progress
ticket_type: bug
dependencies: [b7d41e]
---
Long-form body text goes here. Supports full markdown.
```
**TOML format** (`.toml`): All fields in a flat TOML file; `body` is a multi-line string.
**Binary JSON** (`.jsonb`): Compact binary-encoded JSON (e.g. via CBOR or BSON; crate TBD at implementation time).
**Conversion via `nbd update --ftype`**: Reads the ticket in its current format, writes it in the new format, and deletes the old file. The ID and all field values are preserved.
**Crates to evaluate:** `serde_yaml`, `toml`, `ciborium` (CBOR) or `bson`.