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.

10 lines
5.2 KiB
JSON

{
"title": "Wire --filter flag into list, ready, and migrate commands",
"body": "## Summary\n\nAdd `--filter KEY=VALUE` (repeatable) to the `list`, `ready`, and `migrate` CLI commands.\nParse filter arguments into a `TicketFilter` and apply it in each command handler.\n\nDepends on: TicketFilter module (ticket c2a024).\n\n## CLI changes (src/main.rs)\n\nAdd to `Commands::List`, `Commands::Ready`, and `Commands::Migrate` variants:\n\n```rust\n/// Filter tickets: key=value pairs (repeatable).\n/// Keys: priority, type, status, title.\n/// Different keys are ANDed; same key with multiple values is ORed.\n/// Values support glob wildcards: status=* matches all statuses.\n#[arg(long = \"filter\", value_name = \"KEY=VALUE\")]\nfilter: Vec<String>,\n```\n\nUpdate the `dispatch` function to pass filter args through to each handler.\n\n## Handler changes\n\n### cmd_list(filter_args, json)\n\n```rust\nlet filter = filter::parse_filters(&filter_args)?;\nlet tickets: Vec<Ticket> = list_tickets(&root).await?\n .into_iter()\n .filter(|t| filter.matches(t))\n .collect();\n```\n\nNote: the default done-exclusion behaviour (ticket for that is separate) will also\nlive here, layered on top of this filter application.\n\n### cmd_ready(filter_args, json)\n\nApply the user filter AFTER the ready check. The ready check (not done + all deps done)\nis always applied first; the user's filter narrows further within ready tickets.\n\n```rust\nlet filter = filter::parse_filters(&filter_args)?;\n// ... build done_ids as before ...\nlet ready: Vec<Ticket> = all\n .into_iter()\n .filter(|t| {\n t.status \\!= Status::Done\n && t.dependencies.iter().all(|dep| done_ids.contains(dep.as_str()))\n && filter.matches(t)\n })\n .collect();\n```\n\n### cmd_migrate(filter_args, dry_run, json)\n\nFor migrate, the filter selects which tickets are candidates for migration.\nTickets not matching the filter are skipped (counted separately, not treated as errors).\n\nAdd a `skipped` field to `MigrateReport` in `store.rs`:\n\n```rust\npub struct MigrateReport {\n pub updated: usize,\n pub already_current: usize,\n pub skipped: usize, // NEW: tickets excluded by filter\n pub errors: Vec<(String, String)>,\n}\n```\n\nUpdate `migrate_tickets` signature in `store.rs`:\n\n```rust\npub async fn migrate_tickets(\n root: &Path,\n dry_run: bool,\n filter: &TicketFilter,\n) -> Result<MigrateReport>\n```\n\nInside the per-file loop, after deserialising the ticket, check `filter.matches(&ticket)`.\nIf false: increment `report.skipped` and continue to next file.\n\nUpdate `cmd_migrate` to parse the filter and pass it to `migrate_tickets`.\n\n## display.rs changes\n\nUpdate `format_migrate_report` and `format_migrate_report_json` to include the\n`skipped` count:\n\nHuman format:\n```\nMigrated 3 tickets.\nCurrent 5 tickets (already up to date).\nSkipped 2 tickets (did not match filter).\nErrors 1 ticket could not be migrated:\n bad_ticket.json: trailing comma at line 4\n```\n\nOnly print the \"Skipped\" line when `skipped > 0`.\n\nJSON format: add `\"skipped\": N` key to the existing object.\n\n## files touched\n\n- `src/main.rs` — `filter` fields on List/Ready/Migrate variants, updated dispatch,\n updated cmd_list/cmd_ready/cmd_migrate handlers\n- `src/store.rs` — `MigrateReport::skipped`, `migrate_tickets` gains `filter` param\n- `src/display.rs` — updated `format_migrate_report` and `format_migrate_report_json`\n- `src/tests.rs` — unit tests for updated migrate report formatting\n- `tests/integration.rs` — integration tests\n\n## Integration tests to add (tests/integration.rs)\n\n**list filtering:**\n- Create tickets: 1 bug/todo, 1 task/in_progress, 1 bug/done.\n `nbd list --filter type=bug` shows only the bug tickets (done-exclusion is separate,\n but this test can use non-done bugs).\n- `nbd list --filter status=in_progress` shows only in_progress tickets.\n- `nbd list --filter status=todo --filter status=in_progress` shows both todo and in_progress\n (OR within same key).\n- `nbd list --filter type=bug --filter status=todo` shows only bug+todo tickets\n (AND across keys).\n- `nbd list --filter title=*login*` shows only tickets whose title contains \"login\".\n- `nbd list --filter status=*` matches all statuses (wildcard).\n- `nbd list --filter type=unknown` exits non-zero with an error (unknown key passes through\n as a value, but \"unknown\" does not match any type → empty results, or error? Error on\n unknown key is preferable).\n- `nbd list --filter badformat` (no `=`) exits non-zero with an error.\n\n**ready filtering:**\n- `nbd ready --filter type=bug` returns only ready bug tickets.\n- `nbd ready --filter priority=8` returns only ready tickets with priority 8.\n\n**migrate filtering:**\n- Create two tickets. Run `nbd migrate --filter status=todo --dry-run`.\n Verify `skipped` count in JSON output matches tickets not matching the filter.\n- `nbd migrate --filter status=todo --json` includes `skipped` key.\n\n**error cases:**\n- `--filter` with unknown key exits non-zero.\n- `--filter` with no `=` exits non-zero.",
"priority": 8,
"status": "done",
"dependencies": [
"c2a024"
],
"ticket_type": "feature"
}