From 2e7b993a884f5cd500499b3dfc562e244e4968db Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sat, 27 Dec 2025 14:26:00 -0800 Subject: [PATCH] feat: add --prefix flag for cross-rig issue creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds --prefix as a forgiving alias to --rig flag. Accepts: - Exact prefix: bd-, gt- - Prefix without hyphen: bd, gt - Rig name: beads, gastown All resolve to the correct rig via routes.jsonl lookup. This matches agents' mental model of prefix-based routing. šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .beads/issues.jsonl | 3 +++ cmd/bd/create.go | 16 ++++++++--- internal/routing/routes.go | 54 ++++++++++++++++++++++++++++++++------ 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index c773725a..134d367f 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -24,6 +24,7 @@ {"id":"bd-1dez.7","title":"Formula versioning: Version field and git tag integration","description":"Add versioning support to formulas for tracking updates.\n\n## Formula Version Field\n```json\n{\n \"formula\": \"mol-code-review\",\n \"version\": \"1.2.0\",\n \"min_bd_version\": \"0.25.0\",\n ...\n}\n```\n\n## Version Semantics\n- Use semver: MAJOR.MINOR.PATCH\n- MAJOR: Breaking changes to step structure\n- MINOR: New optional steps or variables\n- PATCH: Documentation, bug fixes\n\n## Git Tag Integration\n- `bd mol install repo@v1.2.0` fetches that tag\n- `bd mol install repo` fetches latest tag (or main if no tags)\n- `bd mol publish --tag v1.2.0` creates tag\n\n## Proto Version Tracking\nLocal proto should store:\n```\nsource_repo: github.com/anthropics/mol-code-review\nsource_version: v1.2.0\ninstalled_at: 2025-12-25T12:00:00Z\n```\n\nThis enables `bd mol update` to check for newer versions.\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T12:06:42.674849-08:00","updated_at":"2025-12-25T19:43:22.501926-08:00","closed_at":"2025-12-25T19:43:22.501926-08:00","close_reason":"Implemented formula versioning with formula_version and min_bd_version fields. Added version comparison functions and compatibility checking during cook/pour.","dependencies":[{"issue_id":"bd-1dez.7","depends_on_id":"bd-1dez","type":"parent-child","created_at":"2025-12-25T12:06:42.67694-08:00","created_by":"daemon"}]} {"id":"bd-1dez.8","title":"Installation tracking: .beads/installed.json","description":"Track installed formulas for update checking and provenance.\n\n## File Format\n```json\n{\n \"mol-code-review\": {\n \"source\": \"github.com/anthropics/mol-code-review\",\n \"version\": \"v1.2.0\",\n \"installed_at\": \"2025-12-25T12:00:00Z\",\n \"formula_hash\": \"abc123...\"\n },\n \"mol-polecat-work\": {\n \"source\": \"local\",\n \"version\": \"1\",\n \"installed_at\": \"2025-12-24T10:00:00Z\"\n }\n}\n```\n\n## Commands That Update This\n- `bd mol install` - Adds entry\n- `bd mol update` - Updates version/timestamp\n- `bd cook` - Adds entry with source:local\n- `bd mol uninstall` - Removes entry (new command)\n\n## Usage\n```bash\nbd mol list --installed # Shows installed with source/version\nbd mol outdated # Shows formulas with available updates\n```\n\n## Sync Behavior\n- installed.json is gitignored (local state)\n- Only formulas/ directory is synced\n- Each clone tracks its own installations\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-25T12:06:43.934727-08:00","updated_at":"2025-12-25T19:38:04.14827-08:00","closed_at":"2025-12-25T19:38:04.14827-08:00","close_reason":"Implemented installation tracking with .beads/installed.json. Added: bd mol install, bd mol uninstall, bd mol outdated, bd mol list --installed. Updated bd cook to track local formulas.","dependencies":[{"issue_id":"bd-1dez.8","depends_on_id":"bd-1dez","type":"parent-child","created_at":"2025-12-25T12:06:43.937653-08:00","created_by":"daemon"}]} {"id":"bd-1pr6","title":"Time-dependent worktree detection tests may be flaky","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-26T01:21:22.065353-08:00","updated_at":"2025-12-26T01:21:22.065353-08:00"} +{"id":"bd-1ri4","title":"Test prefix bd-","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-27T14:24:41.240483-08:00","created_by":"stevey","updated_at":"2025-12-27T14:24:50.419881-08:00","deleted_at":"2025-12-27T14:24:50.419881-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} {"id":"bd-1slh","title":"Investigate charmbracelet-based TUI for beads","description":"Now that we've merged the create-form command (PR #603) which uses charmbracelet/huh, investigate whether beads should have a more comprehensive TUI.\n\nConsiderations:\n- Should this be in core or a separate binary (bd-tui)?\n- What functionality would benefit from a TUI? (list view, issue details, search, bulk operations)\n- Plugin/extension architecture vs build tags vs separate binary\n- Dependency cost vs user experience tradeoff\n- Target audience: humans who want interactive workflows vs CLI/scripting users\n\nRelated: PR #603 added charmbracelet/huh dependency for create-form command.","notes":"Foundation is in place (lipgloss, huh), but not a priority right now","status":"deferred","priority":3,"issue_type":"feature","created_at":"2025-12-17T14:20:51.503563-08:00","updated_at":"2025-12-20T23:31:34.354023-08:00"} {"id":"bd-1tw","title":"Fix G104 errors unhandled in internal/storage/sqlite/queries.go:1186","description":"Linting issue: G104: Errors unhandled (gosec) at internal/storage/sqlite/queries.go:1186:2. Error: rows.Close()","status":"tombstone","priority":0,"issue_type":"bug","created_at":"2025-12-07T15:35:13.051671889-07:00","updated_at":"2025-12-25T01:21:01.952723-08:00","deleted_at":"2025-12-25T01:21:01.952723-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"bug"} {"id":"bd-20j","title":"sync branch not match config","description":"./bd sync\n→ Exporting pending changes to JSONL...\n→ No changes to commit\n→ Pulling from sync branch 'gh-386'...\nError pulling from sync branch: failed to create worktree: failed to create worktree parent directory: mkdir /var/home/matt/dev/beads/worktree-db-fail/.git: not a directory\nmatt@blufin-framation ~/d/b/worktree-db-fail (worktree-db-fail) [1]\u003e bd config list\n\nConfiguration:\n auto_compact_enabled = false\n compact_batch_size = 50\n compact_model = claude-3-5-haiku-20241022\n compact_parallel_workers = 5\n compact_tier1_days = 30\n compact_tier1_dep_levels = 2\n compact_tier2_commits = 100\n compact_tier2_days = 90\n compact_tier2_dep_levels = 5\n compaction_enabled = false\n issue_prefix = worktree-db-fail\n sync.branch = worktree-db-fail","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-08T06:49:04.449094018-07:00","updated_at":"2025-12-08T06:49:04.449094018-07:00"} @@ -167,6 +168,7 @@ {"id":"bd-9gvf","title":"Add prefix-based routing with routes.jsonl","description":"## Summary\n\nWhen running `bd show gt-xyz` from a location where that bead doesn't exist locally,\nbd should check a `routes.jsonl` file to find where beads with that prefix live.\n\n## Problem\n\nFrom ~/gt (town root):\n- `bd show hq-xxx` works (hq-* beads are local)\n- `bd show gt-xxx` fails (gt-* beads live in gastown/mayor/rig)\n\nThis breaks `gt hook`, `gt handoff`, and `gt prime` which need to verify beads exist.\n\n## Solution\n\nAdd `.beads/routes.jsonl` for prefix-based routing:\n\n\\`\\`\\`json\n{\"prefix\": \"gt-\", \"path\": \"gastown/mayor/rig\"}\n{\"prefix\": \"hq-\", \"path\": \".\"}\n{\"prefix\": \"bd-\", \"path\": \"beads/crew/dave\"}\n\\`\\`\\`\n\n### Lookup Algorithm\n\n1. Try local .beads/ first (current behavior)\n2. If not found, check for routes.jsonl in current .beads/\n3. Match ID prefix against routes\n4. If match found, resolve path relative to routes file location and retry\n5. If still not found, error as usual\n\n### Affected Commands\n\nRead operations that take a bead ID:\n- bd show \u003cid\u003e\n- bd update \u003cid\u003e\n- bd close \u003cid\u003e\n- bd dep add \u003cid\u003e \u003cdep\u003e (both IDs)\n\nWrite operations (bd create) still go to local .beads/ - no change needed.\n\n## Implementation Notes\n\n1. New file: routes.jsonl in .beads/ directory\n2. New function: resolveBeadsPath(id string) (string, error)\n3. Update: beads.Show(), beads.Update(), beads.Close(), etc. to use resolver\n4. Caching: Can cache routes in memory since file rarely changes\n\n## Context\n\nThis unblocks the gt handoff flow for Mayor. Currently gt handoff gt-xyz fails\nbecause it can't verify the bead exists when running from town root.","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-26T14:18:19.399691-08:00","updated_at":"2025-12-26T14:36:43.551435-08:00","closed_at":"2025-12-26T14:36:43.551435-08:00","close_reason":"Implemented prefix-based routing with routes.jsonl. Tested with gt- and bd- prefixes from town root."} {"id":"bd-9l0h","title":"Run tests and linting","description":"go test -short ./... \u0026\u0026 golangci-lint run ./...","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-20T21:53:19.527602-08:00","updated_at":"2025-12-20T21:55:29.660914-08:00","closed_at":"2025-12-20T21:55:29.660914-08:00","close_reason":"Tests passed, go vet clean","dependencies":[{"issue_id":"bd-9l0h","depends_on_id":"bd-an4s","type":"parent-child","created_at":"2025-12-20T21:53:19.529203-08:00","created_by":"daemon"},{"issue_id":"bd-9l0h","depends_on_id":"bd-gocx","type":"blocks","created_at":"2025-12-20T21:53:29.753682-08:00","created_by":"daemon"}]} {"id":"bd-9qj5","title":"Merge: bd-c7y5","description":"branch: polecat/toast\ntarget: main\nsource_issue: bd-c7y5\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-23T20:45:02.626929-08:00","updated_at":"2025-12-23T21:21:57.699742-08:00","closed_at":"2025-12-23T21:21:57.699742-08:00","close_reason":"stale - no code pushed"} +{"id":"bd-9szg","title":"Test prefix bd","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-27T14:24:41.470665-08:00","created_by":"stevey","updated_at":"2025-12-27T14:24:50.420887-08:00","deleted_at":"2025-12-27T14:24:50.420887-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} {"id":"bd-9usz","title":"Test suite hangs/never finishes","description":"Running 'go test ./... -count=1' hangs indefinitely. The full test suite never completes, making it difficult to verify changes. Need to investigate which tests are hanging and fix or add timeouts.","status":"closed","priority":2,"issue_type":"bug","assignee":"beads/cheedo","created_at":"2025-12-16T21:56:27.80191-08:00","updated_at":"2025-12-23T23:48:57.837606-08:00","closed_at":"2025-12-23T23:48:57.837606-08:00","close_reason":"Tests no longer hang - verified test suite completes in ~30s across multiple runs on both polecat/cheedo and main branches. Issue may have been resolved by recent test infrastructure improvements."} {"id":"bd-a0cp","title":"Consider using types.Status in merge package for type safety","description":"The merge package uses string for status comparison (e.g., result.Status == closed, issue.Status == StatusTombstone). The types package defines Status as a type alias with validation. While the merge package needs its own Issue struct for JSONL flexibility, it could import and use types.Status for constants to get compile-time type checking. Current code: if left == closed || right == closed. Could be: if left == string(types.StatusClosed). This is low priority since string comparison works correctly. Files: internal/merge/merge.go:44, 488, 501-521","status":"open","priority":4,"issue_type":"task","created_at":"2025-12-05T16:37:10.690424-08:00","updated_at":"2025-12-05T16:37:10.690424-08:00"} {"id":"bd-a15d","title":"Add test files for internal/storage","description":"The internal/storage package has no test files at all. This package provides the storage interface abstraction.\n\nCurrent coverage: N/A (no test files)\nTarget: Add basic interface tests","status":"closed","priority":2,"issue_type":"task","assignee":"beads/valkyrie","created_at":"2025-12-13T20:43:11.363017-08:00","updated_at":"2025-12-23T23:56:07.002735-08:00","closed_at":"2025-12-23T23:56:07.002735-08:00","close_reason":"Closed"} @@ -526,6 +528,7 @@ {"id":"bd-t4sb","title":"Work on beads-d8h: Fix prefix mismatch false positive wit...","description":"Work on beads-d8h: Fix prefix mismatch false positive with multi-hyphen prefixes like 'asianops-audit-' (GH#422). When done, submit MR (not PR) to integration branch for Refinery.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T22:56:19.545069-08:00","updated_at":"2025-12-19T23:28:32.429127-08:00","closed_at":"2025-12-19T23:21:45.471711-08:00","close_reason":"Fixed multi-hyphen prefix false positive in ValidateIDFormat (GH#422)"} {"id":"bd-t4u1","title":"False positive detection by Kaspersky Antivirus (Trojan)","description":"Kaspersky Antivirus falsely detects beads (bd.exe v0.23.1) as a Trojan (PDM:Trojan.Win32.Generic) and removes it.\nEvent: Malicious object detected\nComponent: System Watcher\nObject name: bd.exe\n","status":"closed","priority":1,"issue_type":"task","assignee":"beads/capable","created_at":"2025-11-20T18:56:12.498187-05:00","updated_at":"2025-12-23T23:47:23.182018-08:00","closed_at":"2025-12-23T23:47:23.182018-08:00","close_reason":"Documentation already comprehensive in docs/ANTIVIRUS.md and docs/TROUBLESHOOTING.md. Added TROUBLESHOOTING.md link to README for discoverability. Created follow-up issue bd-14v0 to track Windows code signing implementation as a long-term solution."} {"id":"bd-tbz3","title":"bd init UX Improvements","description":"bd init leaves users with incomplete setup, requiring manual bd doctor --fix. Issues found: (1) git hooks not installed if user declines prompt, (2) no auto-migration when CLI is upgraded, (3) stale merge driver configs from old versions. Fix by making bd init more robust with better defaults and auto-migration.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-11-21T23:16:00.333543-08:00","updated_at":"2025-12-23T04:20:51.88847-08:00","closed_at":"2025-12-23T04:20:51.88847-08:00","close_reason":"Already implemented in commits ec4117d0 and 3a36d0b9 - hooks/merge driver install by default, doctor runs at end of init"} +{"id":"bd-tcvh","title":"Test prefix beads","status":"tombstone","priority":2,"issue_type":"task","created_at":"2025-12-27T14:24:41.707092-08:00","created_by":"stevey","updated_at":"2025-12-27T14:24:50.421598-08:00","deleted_at":"2025-12-27T14:24:50.421598-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"} {"id":"bd-tggf","title":"Code Health Review Dec 2025: Technical Debt Cleanup","description":"Epic grouping technical debt identified in the Dec 16, 2025 code health review.\n\n## Overall Health Grade: B (Solid foundation, needs cleanup)\n\n### P1 (High Priority):\n- bd-74w1: Consolidate duplicate path-finding utilities\n- [deleted:bd-b6xo]: Remove/fix ClearDirtyIssues() race condition\n- [deleted:bd-b3og]: Fix TestImportBugIntegration deadlock\n\n### P2 (Medium Priority):\n- bd-05a8: Split large files (doctor.go, sync.go)\n- bd-qioh: Standardize error handling patterns\n- bd-rgyd: Split queries.go (1586 lines)\n- bd-9g1z: Fix/remove TestFindJSONLPathDefault\n\n### P3 (Low Priority):\n- bd-ork0: Add comments to 30+ ignored errors\n- bd-4nqq: Remove dead test code in info_test.go\n- bd-dhza: Reduce global state in main.go\n\n## Key Areas:\n1. Code duplication in path utilities\n2. Large monolithic files (5 files \u003e1000 lines)\n3. Global state (25+ variables, 3 deprecated)\n4. Silent error suppression (30+ instances)\n5. Test gaps and dead test code\n6. Atomicity risks in batch operations","status":"open","priority":2,"issue_type":"epic","created_at":"2025-12-16T18:18:58.115507-08:00","updated_at":"2025-12-25T01:21:01.961139-08:00","dependencies":[{"issue_id":"bd-tggf","depends_on_id":"bd-74w1","type":"blocks","created_at":"2025-12-22T21:00:21.429274-08:00","created_by":"daemon"},{"issue_id":"bd-tggf","depends_on_id":"bd-05a8","type":"blocks","created_at":"2025-12-22T21:00:21.501589-08:00","created_by":"daemon"},{"issue_id":"bd-tggf","depends_on_id":"bd-9g1z","type":"blocks","created_at":"2025-12-22T21:00:21.571116-08:00","created_by":"daemon"},{"issue_id":"bd-tggf","depends_on_id":"bd-qioh","type":"blocks","created_at":"2025-12-22T21:00:21.640589-08:00","created_by":"daemon"},{"issue_id":"bd-tggf","depends_on_id":"bd-rgyd","type":"blocks","created_at":"2025-12-22T21:00:21.710912-08:00","created_by":"daemon"},{"issue_id":"bd-tggf","depends_on_id":"bd-4nqq","type":"blocks","created_at":"2025-12-22T21:00:21.781914-08:00","created_by":"daemon"},{"issue_id":"bd-tggf","depends_on_id":"bd-dhza","type":"blocks","created_at":"2025-12-22T21:00:21.852-08:00","created_by":"daemon"},{"issue_id":"bd-tggf","depends_on_id":"bd-ork0","type":"blocks","created_at":"2025-12-22T21:00:21.930168-08:00","created_by":"daemon"}]} {"id":"bd-thgk","title":"Improve test coverage for internal/compact (18.2% → 70%)","description":"Improve test coverage for internal/compact package from 17% to 70%.\n\n## Current State\n- Coverage: 17.3%\n- Files: compactor.go, git.go, haiku.go\n- Tests: compactor_test.go (minimal tests)\n\n## Functions Needing Tests\n\n### compactor.go (core compaction)\n- [ ] New - needs config validation tests\n- [ ] CompactTier1 - needs single issue compaction tests\n- [ ] CompactTier1Batch - needs batch processing tests\n- [ ] compactSingleWithResult - internal, test via public API\n\n### git.go\n- [ ] GetCurrentCommitHash - needs git repo fixture tests\n\n### haiku.go (AI summarization) - MOCK REQUIRED\n- [ ] NewHaikuClient - needs API key validation tests\n- [ ] SummarizeTier1 - needs mock API response tests\n- [ ] callWithRetry - needs retry logic tests\n- [ ] isRetryable - needs error classification tests\n- [ ] renderTier1Prompt - needs template rendering tests\n\n## Implementation Guide\n\n1. **Mock the Anthropic API:**\n ```go\n // Create mock HTTP server\n server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n json.NewEncoder(w).Encode(map[string]interface{}{\n \"content\": []map[string]string{{\"text\": \"Summarized content\"}},\n })\n }))\n defer server.Close()\n \n // Point client at mock\n client.baseURL = server.URL\n ```\n\n2. **Test scenarios:**\n - Successful compaction with AI summary\n - API failure with retry\n - Rate limit handling\n - Empty issue handling\n - Large issue truncation\n\n3. **Use test database:**\n ```go\n store, cleanup := testutil.NewTestStore(t)\n defer cleanup()\n ```\n\n## Success Criteria\n- Coverage ≄ 70%\n- AI calls properly mocked (no real API calls in tests)\n- Retry logic verified\n- Error paths covered\n\n## Run Tests\n```bash\ngo test -v -cover ./internal/compact\ngo test -race ./internal/compact\n```","status":"closed","priority":1,"issue_type":"task","assignee":"beads/Compactor","created_at":"2025-12-13T20:42:58.455767-08:00","updated_at":"2025-12-23T13:41:10.80832-08:00","closed_at":"2025-12-23T13:41:10.80832-08:00","close_reason":"Coverage improved from 17.3% to 81.8%, exceeding 70% target","dependencies":[{"issue_id":"bd-thgk","depends_on_id":"bd-iz5t","type":"parent-child","created_at":"2025-12-23T12:44:07.287377-08:00","created_by":"daemon"}]} {"id":"bd-tj00","title":"Update local installation","description":"go build -o ~/.local/bin/bd ./cmd/bd \u0026\u0026 codesign -s - ~/.local/bin/bd","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-20T21:53:20.616907-08:00","updated_at":"2025-12-20T21:55:42.756171-08:00","closed_at":"2025-12-20T21:55:42.756171-08:00","close_reason":"Installed bd 0.32.1","dependencies":[{"issue_id":"bd-tj00","depends_on_id":"bd-an4s","type":"parent-child","created_at":"2025-12-20T21:53:20.619834-08:00","created_by":"daemon"},{"issue_id":"bd-tj00","depends_on_id":"bd-9l0h","type":"blocks","created_at":"2025-12-20T21:53:29.817989-08:00","created_by":"daemon"}]} diff --git a/cmd/bd/create.go b/cmd/bd/create.go index 6e93f036..3f820b85 100644 --- a/cmd/bd/create.go +++ b/cmd/bd/create.go @@ -110,11 +110,20 @@ var createCmd = &cobra.Command{ forceCreate, _ := cmd.Flags().GetBool("force") repoOverride, _ := cmd.Flags().GetString("repo") rigOverride, _ := cmd.Flags().GetString("rig") + prefixOverride, _ := cmd.Flags().GetString("prefix") wisp, _ := cmd.Flags().GetBool("ephemeral") - // Handle --rig flag: create issue in a different rig - if rigOverride != "" { - createInRig(cmd, rigOverride, title, description, issueType, priority, design, acceptance, assignee, labels, externalRef, wisp) + // Handle --rig or --prefix flag: create issue in a different rig + // Both flags use the same forgiving lookup (accepts rig names or prefixes) + targetRig := rigOverride + if prefixOverride != "" { + if targetRig != "" { + FatalError("cannot specify both --rig and --prefix flags") + } + targetRig = prefixOverride + } + if targetRig != "" { + createInRig(cmd, targetRig, title, description, issueType, priority, design, acceptance, assignee, labels, externalRef, wisp) return } @@ -457,6 +466,7 @@ func init() { createCmd.Flags().Bool("force", false, "Force creation even if prefix doesn't match database prefix") createCmd.Flags().String("repo", "", "Target repository for issue (overrides auto-routing)") createCmd.Flags().String("rig", "", "Create issue in a different rig (e.g., --rig beads)") + createCmd.Flags().String("prefix", "", "Create issue in rig by prefix (e.g., --prefix bd- or --prefix bd or --prefix beads)") createCmd.Flags().IntP("estimate", "e", 0, "Time estimate in minutes (e.g., 60 for 1 hour)") createCmd.Flags().Bool("ephemeral", false, "Create as ephemeral (ephemeral, not exported to JSONL)") // Note: --json flag is defined as a persistent flag in main.go, not here diff --git a/internal/routing/routes.go b/internal/routing/routes.go index 8eb56ce0..72332688 100644 --- a/internal/routing/routes.go +++ b/internal/routing/routes.go @@ -100,21 +100,59 @@ func LookupRigByName(rigName, beadsDir string) (Route, bool) { return Route{}, false } -// ResolveBeadsDirForRig returns the beads directory for a given rig name. -// This is used by --rig flag to create issues in a different rig. +// LookupRigForgiving finds a route using flexible matching. +// Accepts any of these formats and normalizes them: +// - "bd-" (exact prefix) +// - "bd" (prefix without hyphen, will try "bd-") +// - "beads" (rig name) +// +// This provides good agent UX - meet them where they are. +func LookupRigForgiving(input, beadsDir string) (Route, bool) { + routes, err := LoadRoutes(beadsDir) + if err != nil || len(routes) == 0 { + return Route{}, false + } + + // Normalize: remove trailing hyphen for comparison + normalized := strings.TrimSuffix(input, "-") + + for _, route := range routes { + // Try exact prefix match (with or without hyphen) + prefixBase := strings.TrimSuffix(route.Prefix, "-") + if normalized == prefixBase || input == route.Prefix { + return route, true + } + + // Try rig name match + project := ExtractProjectFromPath(route.Path) + if input == project { + return route, true + } + } + + return Route{}, false +} + +// ResolveBeadsDirForRig returns the beads directory for a given rig identifier. +// This is used by --rig and --prefix flags to create issues in a different rig. +// +// The input is forgiving - accepts any of: +// - "beads", "gastown" (rig names) +// - "bd-", "gt-" (exact prefixes) +// - "bd", "gt" (prefixes without hyphen) // // Parameters: -// - rigName: the rig name (e.g., "beads", "gastown") +// - rigOrPrefix: rig name or prefix in any format // - currentBeadsDir: the current .beads directory (used to find routes.jsonl) // // Returns: // - beadsDir: the target .beads directory path // - prefix: the issue prefix for that rig (e.g., "bd-") // - err: error if rig not found or path doesn't exist -func ResolveBeadsDirForRig(rigName, currentBeadsDir string) (beadsDir string, prefix string, err error) { - route, found := LookupRigByName(rigName, currentBeadsDir) +func ResolveBeadsDirForRig(rigOrPrefix, currentBeadsDir string) (beadsDir string, prefix string, err error) { + route, found := LookupRigForgiving(rigOrPrefix, currentBeadsDir) if !found { - return "", "", fmt.Errorf("rig %q not found in routes.jsonl", rigName) + return "", "", fmt.Errorf("rig or prefix %q not found in routes.jsonl", rigOrPrefix) } // Resolve the target beads directory @@ -126,11 +164,11 @@ func ResolveBeadsDirForRig(rigName, currentBeadsDir string) (beadsDir string, pr // Verify the target exists if info, statErr := os.Stat(targetPath); statErr != nil || !info.IsDir() { - return "", "", fmt.Errorf("rig %q beads directory not found: %s", rigName, targetPath) + return "", "", fmt.Errorf("rig %q beads directory not found: %s", rigOrPrefix, targetPath) } if os.Getenv("BD_DEBUG_ROUTING") != "" { - fmt.Fprintf(os.Stderr, "[routing] Rig %q -> prefix %s, path %s\n", rigName, route.Prefix, targetPath) + fmt.Fprintf(os.Stderr, "[routing] Rig %q -> prefix %s, path %s\n", rigOrPrefix, route.Prefix, targetPath) } return targetPath, route.Prefix, nil