diff --git a/nbd/.nbd/tickets/460caf.json b/nbd/.nbd/tickets/460caf.json index f89c549..57bcd56 100644 --- a/nbd/.nbd/tickets/460caf.json +++ b/nbd/.nbd/tickets/460caf.json @@ -1,5 +1,4 @@ { - "id": "460caf", "title": "Multiple file format support (md, toml, jsonb)", "body": "Add `--ftype` flag to `create` and `update` to write tickets in markdown, TOML, or binary JSON (CBOR) in addition to the existing JSON format. Format is detected from file extension on read.\n\n## Motivation\n\nMarkdown format lets agents write long-form ticket bodies with full markdown syntax, and makes tickets human-readable in a file browser. TOML is a natural config format. CBOR offers compact binary storage.\n\n## Approach\n\n### New crate dependencies (Cargo.toml)\nEvaluate and add:\n- `toml` — TOML serialisation (likely `toml = \"0.8\"`)\n- `serde_yml` or `serde_yaml` — YAML frontmatter (for `.md` files)\n- `ciborium` — CBOR binary JSON (`.jsonb`)\n\n### ticket.rs\nNo changes needed — `Ticket` already derives `Serialize`/`Deserialize`.\n\n### store.rs\nNew enum `FileFormat { Json, Markdown, Toml, Jsonb }`.\n\nNew function `detect_format(path: &Path) -> FileFormat`:\n- `.json` → `Json`\n- `.md` → `Markdown`\n- `.toml` → `Toml`\n- `.jsonb` → `Jsonb`\n- Unknown → `Json` (fallback)\n\nUpdate `ticket_path(root, id, format)` to use the format-appropriate extension. This is a breaking change to the function signature — update all callers.\n\nUpdate `read_ticket(root, id)`:\n1. Try each known extension in order until a file is found.\n2. Read the file and dispatch to the format-appropriate deserialiser.\n\nSerialisation helpers (private):\n- `serialize_json(ticket) -> String`\n- `serialize_toml(ticket) -> String`\n- `serialize_markdown(ticket) -> String` — TOML frontmatter (`+++` delimiters) with body as file content\n- `serialize_jsonb(ticket) -> Vec`\n\nDeserialization helpers (private):\n- `deserialize_markdown(bytes) -> Result` — parse frontmatter + body\n\nUpdate `list_tickets` to scan for `*.json`, `*.md`, `*.toml`, `*.jsonb` files.\n\nUpdate `write_ticket` to accept `format: FileFormat` and write in the appropriate format.\n\n### main.rs\nAdd `--ftype [json|md|toml|jsonb]` option (default `json`) to `create` and `update`.\nConversion on `update --ftype`: read old file, write new format, delete old file (if extension changed).\n\n## Markdown format (TOML frontmatter)\n```\n+++\nid = \"a3f9c2\"\ntitle = \"Fix login bug\"\npriority = 8\nstatus = \"in_progress\"\nticket_type = \"bug\"\ndependencies = [\"b7d41e\"]\n+++\n\nLong-form body text goes here. Supports full markdown.\n```\n\n## Tests\n- Unit tests: roundtrip each format (JSON already tested).\n- Integration tests: `nbd create --ftype md` creates a `.md` file; `nbd read` finds and parses it.\n- Integration test: `nbd update --ftype toml` converts format and removes old file.\n\n## Files touched\n- `Cargo.toml` — new dependencies\n- `src/store.rs` — format detection, multi-format read/write, updated `list_tickets`\n- `src/main.rs` — `--ftype` flags\n- `src/tests.rs` — format roundtrip tests\n- `tests/integration.rs` — format integration tests\n- `README.md` — document `--ftype`\n- `docs/ARCHITECTURE.md` — update storage layout section", "priority": 5, diff --git a/nbd/.nbd/tickets/5f1495.json b/nbd/.nbd/tickets/5f1495.json index 39452a7..a07ae16 100644 --- a/nbd/.nbd/tickets/5f1495.json +++ b/nbd/.nbd/tickets/5f1495.json @@ -1,5 +1,4 @@ { - "id": "5f1495", "title": "nbd update diff output", "body": "Show a git-diff-style +/- summary of what changed when `nbd update` is run without `--json`.\n\n## Motivation\n\nCurrently `nbd update` prints the full ticket after the change, making it hard to see at a glance what actually changed. A diff view — showing only changed fields — is more informative.\n\n## Approach\n\n### display.rs\nAdd `format_diff(old: &Ticket, new: &Ticket) -> String`:\n- Compare each field between `old` and `new`.\n- For each field that changed, emit two lines:\n ```\n - status: todo\n + status: in_progress\n ```\n- If no fields changed, emit `(no changes)`.\n- Fields compared: `title`, `body`, `priority`, `status`, `ticket_type`, `dependencies`.\n- `id` is never shown (it cannot change).\n- Label width matches `format_ticket` (LABEL_WIDTH).\n\nAdd `print_diff(old: &Ticket, new: &Ticket)` that calls `println!(\"{}\" format_diff(...))`.\n\n### main.rs\nIn `cmd_update`:\n- Before applying changes: `let old = ticket.clone();`\n- After `write_ticket`:\n - If `json`: current behaviour (print new ticket as JSON).\n - Else: `display::print_diff(&old, &ticket)`.\n\n## Tests\n\nUnit tests in `src/tests.rs`:\n- `format_diff` shows changed fields only.\n- `format_diff` with identical tickets outputs `(no changes)`.\n- Changed dependencies are shown as comma-separated lists on each line.\n\nIntegration test:\n- `nbd update --status in_progress` (no `--json`) prints `- status:` and `+ status:` lines.\n- `nbd update --json` still prints full JSON (no diff).\n\n## Files touched\n- `src/display.rs` — `format_diff`, `print_diff`\n- `src/main.rs` — `cmd_update` uses `print_diff`\n- `src/tests.rs` — unit tests for `format_diff`\n- `tests/integration.rs` — integration tests", "priority": 5, diff --git a/nbd/.nbd/tickets/6e4239.json b/nbd/.nbd/tickets/6e4239.json index 1d0439e..ce0cef3 100644 --- a/nbd/.nbd/tickets/6e4239.json +++ b/nbd/.nbd/tickets/6e4239.json @@ -1,5 +1,4 @@ { - "id": "6e4239", "title": "Nix flake for nbd", "body": "Add `nbd/flake.nix` so that `nbd` can be consumed as a Nix package and used as a CLI tool in other projects in the mono-repo.\n\n## Motivation\n\nOther `vibed` services and external projects should be able to include `nbd` as a dev dependency via Nix, getting a pinned, reproducible binary without needing Cargo installed.\n\n## Approach\n\n### nbd/flake.nix\n\nFollow the pattern from the repo root `flake.nix`. The service flake should:\n1. Inherit the base flake's inputs (nixpkgs, rust-overlay, etc.) or declare its own.\n2. Define a `packages.default` attribute that builds the `nbd` crate with `rustPlatform.buildRustPackage`.\n3. Define a `devShells.default` that includes the `nbd` binary and standard Rust tooling (rustfmt, clippy, cargo).\n4. Optionally expose an `apps.default` for `nix run .#nbd`.\n\n### Cargo.lock\nEnsure `Cargo.lock` is committed (it already is) — required for reproducible Nix builds.\n\n### cargoHash / cargoSha256\nThe `buildRustPackage` derivation requires a `cargoHash` (or `cargoSha256`). Use the correct fetcher approach (vendored deps or `fetchCargoTarball`).\n\n## Steps\n1. Read the root `flake.nix` to understand the base structure.\n2. Write `nbd/flake.nix` following the same conventions.\n3. Run `nix build .#nbd` from the `nbd/` directory to verify it builds.\n4. Run `nix run .#nbd -- --help` to verify the binary works.\n\n## Files touched\n- `nbd/flake.nix` — new file\n- `README.md` — add `nix run` usage section", "priority": 4, diff --git a/nbd/.nbd/tickets/833807.json b/nbd/.nbd/tickets/833807.json index 72b20be..5b3bc53 100644 --- a/nbd/.nbd/tickets/833807.json +++ b/nbd/.nbd/tickets/833807.json @@ -1,5 +1,4 @@ { - "id": "833807", "title": "SQLite cache for list performance", "body": "Add an optional SQLite cache in `.nbd/cache.db` to accelerate `nbd list` and `nbd ready` for large ticket stores.\n\n## Motivation\n\n`list_tickets` currently does O(n) file reads on every call. For stores with hundreds of tickets this is measurably slow. A SQLite cache avoids re-reading unchanged files by comparing file modification times (mtimes).\n\n## Approach\n\n### Crate dependency\nAdd `sqlx` with the `sqlite` and `runtime-async-std` features to `Cargo.toml`.\n\n### store.rs additions\nNew async function `open_cache(root: &Path) -> Result`:\n- Opens (or creates) `.nbd/cache.db`.\n- Runs a migration: `CREATE TABLE IF NOT EXISTS tickets (id TEXT PRIMARY KEY, json TEXT NOT NULL, mtime INTEGER NOT NULL)`.\n\nNew function `list_tickets_cached(root: &Path) -> Result>`:\n1. Open cache.\n2. Read directory listing to get file names and mtimes.\n3. For each file: if the DB has a row with matching mtime, use cached JSON; otherwise read file, parse, insert/update row.\n4. Delete DB rows for IDs no longer on disk.\n5. Return deserialized tickets sorted by priority desc.\n\nKeep existing `list_tickets` as the non-cached fallback. Consider making `cmd_list` and `cmd_ready` use `list_tickets_cached` when available.\n\n### Migration strategy\n- The cache is always optional. If `cache.db` can't be opened, fall back to `list_tickets` (log a warning to stderr).\n- The cache is never the source of truth — the JSON files are. The cache is always reconstructable by deleting `.nbd/cache.db`.\n\n## Decision point\nDecide whether to enable the cache unconditionally or gate it behind a flag (`--cache` / `NBD_CACHE=1`). Recommendation: enable by default once the feature is stable.\n\n## Tests\n- Unit test: cache hit returns same data as direct file read.\n- Unit test: cache miss (mtime changed) re-reads the file.\n- Unit test: deleted ticket is evicted from cache.\n- Performance test (optional): benchmark 1000-ticket list with and without cache.\n\n## Files touched\n- `Cargo.toml` — add `sqlx`\n- `src/store.rs` — `open_cache`, `list_tickets_cached`\n- `src/tests.rs` — cache unit tests\n- `docs/ARCHITECTURE.md` — document the cache layer", "priority": 3, diff --git a/nbd/.nbd/tickets/fc6df4.json b/nbd/.nbd/tickets/fc6df4.json index 5c61497..91a94f3 100644 --- a/nbd/.nbd/tickets/fc6df4.json +++ b/nbd/.nbd/tickets/fc6df4.json @@ -2,7 +2,7 @@ "title": "Add nbd next subcommand", "body": "## Summary\n\nAdd `nbd next` subcommand that selects the single highest-priority ticket that is\nready to work on. Supports `--filter` for additional narrowing within the ready set.\n\nDepends on: `--filter` wired into CLI (ticket 887344). Implicitly depends on the\nTicketFilter module (ticket c2a024) through that.\n\n## Definition of \"ready\"\n\nSame semantics as `nbd ready`:\n- `status \\!= done`\n- Every ID in `ticket.dependencies` belongs to a ticket with `status == done`.\n- Missing dependency IDs are treated conservatively: the ticket is NOT ready.\n\n## Behaviour\n\n1. Load all tickets with `list_tickets(&root)` (already sorted by priority desc).\n2. Build `done_ids` set (same as `cmd_ready`).\n3. Find the first ticket (highest priority) where:\n - status \\!= done\n - all dependencies are in done_ids\n - `filter.matches(t)` (if a filter was provided)\n4. If found: display the single ticket.\n5. If not found: print a \"no ready tickets\" message. Exit 0 (not an error).\n\n## Output\n\nWithout `--json`:\n- Found: print the ticket in the same tabular format as `nbd read` (via `display::print_ticket`).\n- Not found: `No ready tickets.`\n\nWith `--json`:\n- Found: `{\"next\": { ...ticket fields including id... }}`\n- Not found: `{\"next\": null}`\n\nWrapping in `{\"next\": ...}` (rather than bare ticket or bare null) makes the JSON\nunambiguously parseable by consumers — they always get an object with a `next` key.\n\n## Implementation\n\nAdd to `Commands` enum:\n\n```rust\n/// Choose the highest-priority ticket that is ready to work on.\n///\n/// A ticket is ready when its status is not `done` and every ticket it\n/// depends on has status `done`. Returns the single highest-priority\n/// ready ticket, optionally narrowed by `--filter KEY=VALUE`.\n///\n/// Exits 0 even when no ready ticket exists.\nNext {\n /// Filter ready tickets: key=value pairs (repeatable).\n /// AND between different keys, OR within same key.\n #[arg(long = \"filter\", value_name = \"KEY=VALUE\")]\n filter: Vec,\n},\n```\n\nAdd dispatch arm:\n\n```rust\nCommands::Next { filter } => cmd_next(filter, cli.json).await,\n```\n\nImplement `cmd_next`:\n\n```rust\nasync fn cmd_next(filter_args: Vec, json: bool) -> store::Result<()> {\n let root = find_nbd_root()?;\n let all = list_tickets(&root).await?; // sorted by priority desc\n let filter = filter::parse_filters(&filter_args)?;\n\n let done_ids: std::collections::HashSet<&str> = all\n .iter()\n .filter(|t| t.status == Status::Done)\n .map(|t| t.id.as_str())\n .collect();\n\n let next = all.iter().find(|t| {\n t.status \\!= Status::Done\n && t.dependencies.iter().all(|dep| done_ids.contains(dep.as_str()))\n && filter.matches(t)\n });\n\n if json {\n match next {\n Some(ticket) => {\n let value = serde_json::json\\!({\n \"next\": display::ticket_to_json_value(ticket)\n });\n println\\!(\"{}\", serde_json::to_string_pretty(&value)?);\n }\n None => println\\!(\"{}\", serde_json::json\\!({\"next\": null})),\n }\n } else {\n match next {\n Some(ticket) => display::print_ticket(ticket),\n None => println\\!(\"No ready tickets.\"),\n }\n }\n\n Ok(())\n}\n```\n\n## display.rs: make ticket_to_json_value pub(crate)\n\n`ticket_to_json_value` is currently private in `display.rs`. It needs to be accessible\nfrom `cmd_next` in `main.rs`. Change its visibility:\n\n```rust\npub(crate) fn ticket_to_json_value(ticket: &Ticket) -> serde_json::Value { ... }\n```\n\nThis is the cleanest approach — it reuses the existing id-injection logic rather than\nduplicating it.\n\n## README update\n\nAdd a `### Find the next ticket to work on` section:\n\n```markdown\n### Find the next ticket to work on\n\nReturns the single highest-priority ticket that is ready to work on — not done\nand with all dependencies completed.\n\n```sh\nnbd next\nnbd next --json\nnbd next --filter type=bug # highest-priority ready bug\nnbd next --filter priority=9 # highest-priority ready ticket with priority 9\n```\n\nExits 0 even when no ready ticket exists.\n```\n\n## CLAUDE.md update\n\nUpdate the \"Workflow\" section to mention `nbd next` as an alternative to `nbd ready`\nwhen the caller just wants to begin the single most important task:\n\n```\n**To get the single best ticket to work on next:**\n\n```sh\ncargo run -- next --json\n```\n```\n\n## Files touched\n\n- `src/main.rs` — `Next` command variant, `cmd_next` handler, dispatch arm\n- `src/display.rs` — `ticket_to_json_value` changed to `pub(crate)`\n- `tests/integration.rs` — integration tests\n- `README.md` — new section for `nbd next`\n- `CLAUDE.md` — update workflow section\n\n## Integration tests to add (tests/integration.rs)\n\n- Three tickets: A (priority 5, no deps), B (priority 8, dep A), C (priority 7, no deps).\n `nbd next --json` returns C (highest priority ready ticket — B is blocked by A).\n- After marking A done: `nbd next --json` returns B (priority 8, now unblocked).\n- With only done tickets: `nbd next --json` returns `{\"next\": null}`.\n- `nbd next` (no `--json`) with no ready tickets prints \"No ready tickets.\" and exits 0.\n- `nbd next --filter type=bug --json`: create a bug and a task, both ready.\n Returns the bug if it's the highest-priority bug, otherwise the highest-priority bug.\n (Create bug priority 8, task priority 9: with filter, should return the bug.)\n- `nbd next --json` returns a JSON object with a `\"next\"` key containing all ticket fields\n including `\"id\"`.\n- `nbd next --filter priority=99 --json` returns `{\"next\": null}` (no ticket has priority 99).", "priority": 7, - "status": "todo", + "status": "done", "dependencies": [ "887344" ], diff --git a/nbd/triage-example.md b/nbd/triage-example.md new file mode 100644 index 0000000..4453d08 --- /dev/null +++ b/nbd/triage-example.md @@ -0,0 +1,23 @@ +Add the following tickets: + +Read @docs @src @tests @PLAN.md @README.md and @CLAUDE.md for context. + +* Add a `--filter` flag to `list`, `ready`, and `migrate` commands. + * This should accept `key=value` pairs for all metadata. + * Keys include: priority, type, status, title. + * Users can pass multiple key=value pairs. + * When passing different keys this is a logical AND reducing the results. + * When passing the same key with different values, this results in a logical OR expanding the results. + * Values should support globs, + * Example: `status=*` matches all status values + * Example: `--title=*command*` matches all tickets with 'command' in the title + +* By default do not include `done` tasks in `nbd list`. Require `--filter status=*` or `--filter status=done` + +* Add a new `next` subcommand which chooses the highest priority ticket which is ready to be worked on. + * This should support the `--filter` flag. + +Deeply understand each item before planning. +Create `nbd` tickets for each item including your plan, breaking the problem down into smaller tickets where it makes sense. +Include relevant source files, documentation, and context about each ticket so the work has the higest probability of success without additional context being required. +Do not implement any tickets.