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
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`.
|