diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 42a9dfd2..f338a417 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -54,7 +54,7 @@ {"id":"bd-au0","content_hash":"755c63ab5b27bc77ee20034d16cc63614a0b3f10a81728f1bde5503cf6615813","title":"Command Set Standardization \u0026 Flag Consistency","description":"Comprehensive improvements to bd command set based on 2025 audit findings.\n\n## Background\nSee docs/command-audit-2025.md for detailed analysis.\n\n## Goals\n1. Standardize flag naming and behavior across all commands\n2. Add missing flags for feature parity\n3. Fix naming confusion\n4. Improve consistency in JSON output\n\n## Success Criteria\n- All mutating commands support --dry-run (no --preview variants)\n- bd update supports label operations\n- bd search has filter parity with bd list\n- Priority flags accept both int and P0-P4 format everywhere\n- JSON output is consistent across all commands","status":"open","priority":2,"issue_type":"epic","created_at":"2025-11-21T21:05:55.672749-05:00","updated_at":"2025-11-21T21:05:55.672749-05:00","source_repo":"."} {"id":"bd-au0.1","content_hash":"c125f8efd60c701e268bdb0c06a472b5ce3c9bdcf9af5d973ec3437ec4200e46","title":"Standardize --dry-run flag across all commands","description":"Replace all instances of --preview with --dry-run for consistency.\n\n**Commands to update:**\n- clean: Change --preview to --dry-run\n\n**Commands to verify:**\n- cleanup, compact, delete, duplicates, epic close-eligible, import, sync\n\n**Testing:**\n- Ensure all mutating commands support --dry-run\n- Verify backward compatibility (deprecation warning for --preview?)\n- Update tests and documentation","notes":"All commands already use --dry-run consistently. Verified:\n- cleanup: line 130\n- compact: line 873\n- delete: line 559\n- duplicates: line 158\n- import: line 703\n- sync: line 333\n- epic close-eligible: line 208\n- clean: line 162\n\nNo --preview flags found in the codebase. This task appears to have been completed before the epic was created.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-21T21:06:09.6033-05:00","updated_at":"2025-11-21T21:28:29.836473-05:00","closed_at":"2025-11-21T21:28:29.836473-05:00","source_repo":".","dependencies":[{"issue_id":"bd-au0.1","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:06:09.603828-05:00","created_by":"daemon"}]} {"id":"bd-au0.10","content_hash":"50d18ebce621f3e83c512a74dcb66d98a6d7f67d6632d872a4278f0e46e4bb7a","title":"Add global verbosity flags (--verbose, --quiet)","description":"Add consistent verbosity controls across all commands.\n\n**Current state:**\n- bd init has --quiet flag\n- No other commands have verbosity controls\n- Debug output controlled by BD_VERBOSE env var\n\n**Proposal:**\nAdd persistent flags:\n- --verbose / -v: Enable debug output\n- --quiet / -q: Suppress non-essential output\n\n**Implementation:**\n- Add to rootCmd.PersistentFlags()\n- Replace BD_VERBOSE checks with flag checks\n- Standardize output levels:\n * Quiet: Errors only\n * Normal: Errors + success messages\n * Verbose: Errors + success + debug info\n\n**Files to modify:**\n- cmd/bd/main.go (add flags)\n- internal/debug/debug.go (respect flags)\n- Update all commands to respect quiet mode\n\n**Testing:**\n- Verify --verbose shows debug output\n- Verify --quiet suppresses normal output\n- Ensure errors always show regardless of mode","status":"open","priority":3,"issue_type":"task","created_at":"2025-11-21T21:08:21.600209-05:00","updated_at":"2025-11-21T21:08:21.600209-05:00","source_repo":".","dependencies":[{"issue_id":"bd-au0.10","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:08:21.602557-05:00","created_by":"daemon"}]} -{"id":"bd-au0.2","content_hash":"2766d873187f0f31122068fe97aefdf539a9aa5171b609992b502c1921dbcd07","title":"Add label operations to bd update command","description":"Add --add-label and --remove-label flags to bd update.\n\n**Implementation:**\n- Add flags: --add-label, --remove-label, --set-labels\n- Support multiple labels (repeatable flags)\n- Test with daemon and direct mode\n\n**Files to modify:**\n- cmd/bd/show.go (updateCmd definition)\n- Add RPC support in internal/rpc/protocol.go and server\n\n**Testing:**\n- Add/remove single label\n- Add/remove multiple labels\n- Set labels (replace all)\n- JSON output\n- Daemon mode vs direct mode","notes":"Architecture review complete:\n- Current: separate 'bd label add/remove' commands exist\n- UpdateArgs struct in protocol.go needs new fields: AddLabels, RemoveLabels, SetLabels\n- RPC server needs label operation support in Update handler\n- CLI flags needed: --add-label, --remove-label, --set-labels (repeatable)\n- Both daemon and direct mode need implementation\n- Tests needed for all modes\n\nThis is a moderate-sized feature requiring changes across:\n1. cmd/bd/show.go (updateCmd flags and logic)\n2. internal/rpc/protocol.go (UpdateArgs struct)\n3. internal/rpc/server_issues_epics.go (Update handler)\n4. Tests for daemon/direct modes","status":"open","priority":0,"issue_type":"task","created_at":"2025-11-21T21:06:23.715557-05:00","updated_at":"2025-11-21T21:30:26.613578-05:00","source_repo":".","dependencies":[{"issue_id":"bd-au0.2","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:06:23.716044-05:00","created_by":"daemon"}]} +{"id":"bd-au0.2","content_hash":"a2fcaf0cc3764b792cf4a7deccf6e7f38f9ea3ff91adda246c7b7da704ac9be4","title":"Add label operations to bd update command","description":"Add --add-label and --remove-label flags to bd update.\n\n**Implementation:**\n- Add flags: --add-label, --remove-label, --set-labels\n- Support multiple labels (repeatable flags)\n- Test with daemon and direct mode\n\n**Files to modify:**\n- cmd/bd/show.go (updateCmd definition)\n- Add RPC support in internal/rpc/protocol.go and server\n\n**Testing:**\n- Add/remove single label\n- Add/remove multiple labels\n- Set labels (replace all)\n- JSON output\n- Daemon mode vs direct mode","notes":"Architecture review complete:\n- Current: separate 'bd label add/remove' commands exist\n- UpdateArgs struct in protocol.go needs new fields: AddLabels, RemoveLabels, SetLabels\n- RPC server needs label operation support in Update handler\n- CLI flags needed: --add-label, --remove-label, --set-labels (repeatable)\n- Both daemon and direct mode need implementation\n- Tests needed for all modes\n\nThis is a moderate-sized feature requiring changes across:\n1. cmd/bd/show.go (updateCmd flags and logic)\n2. internal/rpc/protocol.go (UpdateArgs struct)\n3. internal/rpc/server_issues_epics.go (Update handler)\n4. Tests for daemon/direct modes","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-21T21:06:23.715557-05:00","updated_at":"2025-11-21T22:15:19.213541-05:00","closed_at":"2025-11-21T22:15:19.213543-05:00","source_repo":".","dependencies":[{"issue_id":"bd-au0.2","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:06:23.716044-05:00","created_by":"daemon"}]} {"id":"bd-au0.3","content_hash":"51c9568427922dc2d497ebe32b9a57f7db0177ca760609078049c2b2bd72efec","title":"Fix --title vs --title-contains redundancy in bd list","description":"Clarify or consolidate --title and --title-contains flags in bd list command.\n\n**Analysis needed:**\n- Determine if these serve different purposes\n- Check actual implementation in cmd/bd/list.go\n- Review user expectations\n\n**Options:**\n1. Keep both if they have semantic difference (exact vs substring)\n2. Make one an alias of the other\n3. Remove --title-contains if truly redundant\n\n**Testing:**\n- Verify current behavior\n- Update documentation\n- Add tests for chosen approach","notes":"Investigation complete:\n\n--title and --title-contains serve DIFFERENT purposes:\n- --title: Maps to Query field (searches title, description, AND ID) - line 226\n- --title-contains: Maps to TitleContains field (searches title ONLY) - line 233\n\nThese are NOT redundant. The flag names are confusing:\n- --title is actually a general search\n- --title-contains is title-specific\n\nRECOMMENDATION: Rename --title to --query or --search for clarity\n- More intuitive: 'bd list --query foo' vs 'bd list --title foo'\n- Makes it clear it searches multiple fields\n- --title-contains remains for title-only searches\n\nAlternative: Keep as-is but improve documentation to clarify the difference.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-21T21:06:37.597079-05:00","updated_at":"2025-11-21T21:31:53.446905-05:00","closed_at":"2025-11-21T21:31:53.446905-05:00","source_repo":".","dependencies":[{"issue_id":"bd-au0.3","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:06:37.597595-05:00","created_by":"daemon"}]} {"id":"bd-au0.4","content_hash":"711036fe826d16c2c094bb1eb4978ed0ccb596aadc8a4d78550e3089ffc45922","title":"Standardize priority flag parsing across all commands","description":"Accept both integer (0-4) and P0-P4 format in all commands.\n\n**Commands to update:**\n- bd create: Currently accepts both ✓\n- bd update: Currently accepts both ✓\n- bd list: Only accepts int\n- bd search: Add priority filter (missing entirely)\n\n**Implementation:**\n- Create shared priority parsing function\n- Support both formats: '0', '1', 'P0', 'P1', etc.\n- Case-insensitive ('p0', 'P0')\n\n**Files to modify:**\n- cmd/bd/list.go\n- cmd/bd/search.go\n- Add utility function in internal/util/ or internal/types/","notes":"Implementation complete!\n\nChanges made to cmd/bd/list.go:\n1. Added validation import\n2. Changed priority flag from IntP to registerPriorityFlag (line 428)\n3. Changed priority-min/max from Int to String (lines 459-460)\n4. Updated priority parsing to use ValidatePriority (lines 90-97, 230-237)\n5. Updated priority-min/max parsing to use ValidatePriority (lines 195-210)\n\nTesting verified:\n✓ bd list -p P0 works\n✓ bd list -p 0 works \n✓ bd list --priority-min P1 --priority-max P2 works\n✓ bd list --priority-min 1 --priority-max 2 works\n✓ Invalid priorities rejected with clear error messages\n✓ All tests pass\n\nBoth integer (0-4) and P-format (P0-P4) now work consistently across all priority flags in bd list.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-11-21T21:06:51.741518-05:00","updated_at":"2025-11-21T21:56:50.522813-05:00","closed_at":"2025-11-21T21:56:50.522813-05:00","source_repo":".","dependencies":[{"issue_id":"bd-au0.4","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:06:51.743218-05:00","created_by":"daemon"}]} {"id":"bd-au0.5","content_hash":"add9038348d0512c29c42aaec387bee31f0657a5271fb10542fccd6f906d2339","title":"Add date and priority filters to bd search","description":"Add filter parity with bd list for consistent querying.\n\n**Missing filters to add:**\n- --priority, --priority-min, --priority-max\n- --created-after, --created-before\n- --updated-after, --updated-before\n- --closed-after, --closed-before\n- --empty-description, --no-assignee, --no-labels\n- --desc-contains, --notes-contains\n\n**Files to modify:**\n- cmd/bd/search.go\n- internal/rpc/protocol.go (SearchArgs)\n- internal/storage/storage.go (Search method)\n\n**Testing:**\n- All filter combinations\n- Date format parsing\n- Daemon and direct mode","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-21T21:07:05.496726-05:00","updated_at":"2025-11-21T21:07:05.496726-05:00","source_repo":".","dependencies":[{"issue_id":"bd-au0.5","depends_on_id":"bd-au0","type":"parent-child","created_at":"2025-11-21T21:07:05.497762-05:00","created_by":"daemon"}]} @@ -112,6 +112,7 @@ {"id":"bd-tne","content_hash":"2a6596980450714800bddc88e106026743a1a131e96f09198eb7dc2a16d75ca4","title":"Add Claude setup tip with dynamic priority","description":"Add a predefined tip that suggests running `bd setup claude` when Claude Code is detected but not configured. This tip should have higher priority (shown more frequently) until the setup is complete.","design":"## Implementation\n\nAdd to tip registry in `cmd/bd/tips.go`:\n\n```go\n{\n ID: \"claude_setup\",\n Condition: func() bool {\n return isClaudeDetected() \u0026\u0026 !isClaudeSetupComplete()\n },\n Message: \"Run 'bd setup claude' to enable automatic context recovery in Claude Code\",\n Frequency: 24 * time.Hour, // Daily minimum gap\n Priority: 100, // Highest priority\n Probability: 0.6, // 60% chance when eligible\n}\n```\n\n## Detection Logic\n\n```go\nfunc isClaudeDetected() bool {\n // Check environment variables\n if os.Getenv(\"CLAUDE_CODE\") != \"\" || os.Getenv(\"ANTHROPIC_CLI\") != \"\" {\n return true\n }\n // Check if .claude/ directory exists\n if _, err := os.Stat(filepath.Join(os.Getenv(\"HOME\"), \".claude\")); err == nil {\n return true\n }\n return false\n}\n\nfunc isClaudeSetupComplete() bool {\n // Check for global installation\n home, err := os.UserHomeDir()\n if err == nil {\n _, err1 := os.Stat(filepath.Join(home, \".claude/commands/prime_beads.md\"))\n _, err2 := os.Stat(filepath.Join(home, \".claude/hooks/sessionstart\"))\n if err1 == nil \u0026\u0026 err2 == nil {\n return true // Global hooks installed\n }\n }\n \n // Check for project installation\n _, err1 := os.Stat(\".claude/commands/prime_beads.md\")\n _, err2 := os.Stat(\".claude/hooks/sessionstart\")\n return err1 == nil \u0026\u0026 err2 == nil\n}\n```\n\n## Priority and Probability Behavior\n\n**Why 60% probability?**\n- Important message (priority 100) but not critical\n- Daily frequency + 60% = shows ~4 times per week\n- Avoids spam while staying visible\n- Balances persistence with user experience\n\n**Comparison with other probabilities:**\n- 100% probability: Shows EVERY day (annoying)\n- 80% probability: Shows ~6 days per week (too frequent)\n- 60% probability: Shows ~4 days per week (balanced)\n- 40% probability: Shows ~3 days per week (might be missed)\n\n**Auto-stops when setup complete:**\n- Condition becomes false after `bd setup claude`\n- No manual dismissal needed\n- Tip naturally disappears from rotation","acceptance_criteria":"- Claude setup tip added to registry\n- isClaudeDetected() checks environment and filesystem\n- isClaudeSetupComplete() verifies hook installation\n- Tip shows daily until setup complete\n- Tip stops showing after setup\n- Unit tests for detection functions","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-11T23:29:29.871324-08:00","updated_at":"2025-11-11T23:50:29.756454-08:00","source_repo":".","dependencies":[{"issue_id":"bd-tne","depends_on_id":"bd-d4i","type":"blocks","created_at":"2025-11-11T23:29:29.872081-08:00","created_by":"daemon"}]} {"id":"bd-tru","content_hash":"0de12031088519a3dcd27968d6bf17eb3a92d1853264e5a0dceef3310b3a2b04","title":"Update documentation for bd prime and Claude integration","description":"Update AGENTS.md, README.md, and QUICKSTART.md to document the new `bd prime` command, `bd setup claude` command, and tip system.","design":"## Documentation Updates\n\n### AGENTS.md\nAdd new section \"Context Recovery\":\n```markdown\n## Context Recovery\n\n### The Problem\nAfter context compaction or clearing conversation, AI agents may forget to use Beads and revert to markdown TODOs. Claude Code hooks solve this.\n\n### bd prime Command\nThe `bd prime` command outputs essential Beads workflow context in AI-optimized markdown format (~1-2k tokens).\n\n**When to use:**\n- After context compaction\n- After clearing conversation\n- Starting new session\n- When agent seems to forget bd workflow\n- Manual context refresh\n\n**Usage:**\n```bash\nbd prime # Output workflow context\n```\n\n### Automatic Integration (Recommended)\n\nRun `bd setup claude` to install hooks that auto-refresh bd context:\n- **SessionStart hook**: Loads context in new sessions\n- **PreCompact hook**: Refreshes context before compaction (survives better)\n- **Works with MCP**: Hooks complement MCP server (not replace)\n- **Works without MCP**: bd prime provides workflow via CLI\n\n**Why hooks matter even with MCP:**\n- MCP provides native tools, but agent may forget to use them\n- Hooks keep \"use bd, not markdown\" fresh in context\n- PreCompact refreshes workflow before compaction\n\n### MCP Server vs bd prime\n\n**Not an either/or choice** - they solve different problems:\n\n| Aspect | MCP Server | bd prime | Both |\n|--------|-----------|----------|------|\n| **Purpose** | Native bd tools | Workflow context | Best of both |\n| **Tokens** | 10.5k always loaded | ~1-2k when called | 10.5k + ~2k |\n| **Tool access** | Function calls | CLI via Bash | Function calls |\n| **Context memory** | Can fade after compaction | Hooks keep fresh | Hooks + tools |\n| **Recommended** | Heavy usage | Token optimization | Best experience |\n\n**Setup options:**\n```bash\nbd setup claude # Install hooks (works with or without MCP)\nbd setup claude --local # Per-project only\nbd setup claude --remove # Remove hooks\n```\n```\n\n### README.md\nAdd to \"Getting Started\" section:\n```markdown\n### AI Agent Integration\n\n**Claude Code users:** Run `bd setup claude` to install automatic context recovery hooks.\n\nHooks work with both MCP server and CLI approaches, preventing agents from forgetting bd workflow after compaction.\n\n**MCP vs bd prime:**\n- **With MCP server**: Hooks keep agent using bd tools (prevents markdown TODO reversion)\n- **Without MCP server**: Hooks provide workflow context via `bd prime` (~1-2k tokens)\n```\n\n### QUICKSTART.md\nAdd section on agent integration:\n```markdown\n## For AI Agents\n\n**Context loading:**\n```bash\nbd prime # Load workflow context (~1-2k tokens)\n```\n\n**Automatic setup (Claude Code):**\n```bash\nbd setup claude # Install hooks for automatic context recovery\n```\n\nHooks prevent agents from forgetting bd workflow after compaction.\n```","acceptance_criteria":"- AGENTS.md has Context Recovery section\n- README.md mentions bd setup claude\n- QUICKSTART.md mentions bd prime\n- Examples show when to use bd prime vs MCP\n- Clear comparison of trade-offs","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-11T23:30:22.77349-08:00","updated_at":"2025-11-11T23:45:23.242658-08:00","source_repo":".","dependencies":[{"issue_id":"bd-tru","depends_on_id":"bd-90v","type":"parent-child","created_at":"2025-11-11T23:31:35.277819-08:00","created_by":"daemon"}]} {"id":"bd-u3t","content_hash":"b462321c49b70728091902d839ab4d5adb0549b77c6a9aa21fc7d503b1681c54","title":"Phase 2: Implement sandbox auto-detection for GH #353","description":"Implement automatic sandbox detection to improve UX for users in sandboxed environments (e.g., Codex).\n\n**Tasks:**\n1. Implement sandbox detection heuristic using syscall.Kill permission checks\n2. Auto-enable --sandbox mode when sandbox is detected\n3. Display informative message when sandbox is detected\n\n**Implementation:**\n- Add isSandboxed() function in cmd/bd/main.go\n- Auto-set sandboxMode = true in PersistentPreRun when detected\n- Show: 'ℹ️ Sandbox detected, using direct mode'\n\n**References:**\n- docs/GH353_INVESTIGATION.md (Solution 3, lines 120-160)\n- Depends on: Phase 1 (bd-???)\n\n**Acceptance Criteria:**\n- Codex users don't need to manually specify --sandbox\n- No false positives in normal environments\n- Clear messaging when auto-detection triggers","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-21T18:51:57.254358-05:00","updated_at":"2025-11-21T18:51:57.254358-05:00","source_repo":"."} +{"id":"bd-ut5","content_hash":"d9b4ee9c7748c28dc2cd436911b1bee39e68e82df99b718ebe57a246f72a6bcb","title":"Test label update feature","description":"","status":"open","priority":3,"issue_type":"task","created_at":"2025-11-21T22:07:25.488849-05:00","updated_at":"2025-11-21T22:07:25.488849-05:00","source_repo":".","labels":["test-direct"]} {"id":"bd-wcl","content_hash":"c08d62ce3627a49126c63f6a630a08c1666e5b1b8d9148ae0c72d7d06611b2a9","title":"Document CLI + hooks as recommended approach over MCP","description":"Update documentation to position CLI + bd prime hooks as the primary recommended approach over MCP server, explaining why minimizing context matters even with large context windows (compute cost, energy, environment, latency).","design":"## Goals\n\nPosition CLI + `bd prime` hooks as the **primary recommended approach** for AI agent integration, with MCP server as a legacy/fallback option.\n\nExplore **hybrid mode** - if certain commands benefit from MCP (UX/DX advantages like no approval prompts), minimize MCP surface area to only those commands.\n\nThis requires production validation first - only update docs after CLI mode is proven reliable.\n\n## Why Minimize Context (Even With Large Windows)\n\n**Context window size ≠ free resource**\n\nLarge context windows (100k+, 200k+) don't mean we should fill them wastefully. Every token in context has real costs:\n\n### Compute Cost\n- **Processing overhead**: Larger context = more GPU/CPU cycles per request\n- **Memory usage**: 10.5k tokens consume significant RAM/VRAM\n- **Scaling impact**: Multiplied across all users, all sessions, all requests\n\n### Energy \u0026 Environment\n- **Electricity**: More compute = more power consumption\n- **Carbon footprint**: Data centers running on grid power (not all renewable)\n- **Sustainability**: Unnecessary token usage contributes to AI's environmental impact\n- **Responsibility**: Efficient tools are better for the planet\n\n### User Experience\n- **Latency**: Larger context = slower processing (noticeable at 10k+ tokens)\n- **Cost**: Many AI services charge per token (input + output)\n- **Rate limits**: Context counts against API quotas\n\n### Engineering Excellence\n- **Efficiency**: Good engineering minimizes resource usage\n- **Scalability**: Efficient tools scale better\n- **Best practices**: Optimize for the common case\n\n**The comparison:**\n\n| Approach | Standing Context | Efficiency | User Cost | Environmental Impact |\n|----------|-----------------|------------|-----------|---------------------|\n| **CLI + hooks** | ~1-2k tokens | 80-90% reduction | Lower | Sustainable ✓ |\n| **MCP minimal** | ~2-4k tokens | 60-80% reduction | Medium | Better ✓ |\n| **MCP full** | ~10.5k tokens | Baseline | Higher | Wasteful ✗ |\n\n**Functional equivalence:**\n- CLI via Bash tool works just as well as MCP native calls\n- Same features, same reliability\n- No downside except initial learning curve\n\n## Hybrid Mode: Minimal MCP Surface Area\n\n**Philosophy:** MCP server doesn't have to expose everything.\n\nIf certain commands have legitimate UX/DX benefits from MCP (e.g., no approval prompts, cleaner syntax), we can expose ONLY those commands via MCP while using CLI for everything else.\n\n### Potential MCP-Only Candidates (TBD)\n\nCommands that might benefit from MCP native calls:\n- `ready` - frequently checked, no side effects, approval prompt annoying\n- `show` - read-only, frequently used, approval slows workflow\n- `list` - read-only, no risk, approval adds friction\n\nCommands that work fine via CLI:\n- `create` - complex parameters, benefits from explicit confirmation\n- `update` - state changes, good to see command explicitly\n- `close` - state changes, explicit is better\n- `dep` - relationships, good to see what's being linked\n- `sync` - git operations, definitely want visibility\n\n### Token Budget\n\n**Full MCP** (current): ~10.5k tokens\n- All ~20+ bd commands exposed\n- All parameter schemas\n- All descriptions and examples\n\n**Minimal MCP** (proposed): ~2-4k tokens\n- 3-5 high-frequency read commands only\n- Simplified schemas\n- Minimal descriptions\n- Everything else via CLI\n\n**Pure CLI**: ~1-2k tokens (only on SessionStart/PreCompact)\n- No MCP tools loaded\n- All commands via Bash\n\n### Investigation Required\n\nBefore implementing hybrid mode, validate:\n\n1. **Do MCP calls actually skip approval prompts?**\n - Test with Claude Code approval settings\n - Compare MCP tool calls vs Bash tool calls\n - Measure UX difference in real usage\n\n2. **What's the actual token breakdown per command?**\n - Measure individual command schemas\n - Calculate token savings for minimal vs full\n\n3. **Is approval prompt the only benefit?**\n - Are there other UX advantages to MCP?\n - Does native syntax actually improve experience?\n - User testing with both approaches\n\n4. **Can we dynamically load MCP tools?**\n - Only load MCP when certain commands needed?\n - Hot-swap between CLI and MCP?\n - Probably not - MCP loads at startup\n\n### Hybrid Mode Documentation (If Validated)\n\n```markdown\n## Choosing Your Integration Approach\n\nBeads supports three AI agent integration approaches:\n\n### CLI + Hooks (Recommended - Most Efficient)\n\n**Setup:** `bd setup claude`\n\nUses Claude Code hooks to inject workflow context via `bd prime` command. Agent uses bd via Bash tool.\n\n**Tokens:** ~1-2k (on SessionStart/PreCompact only)\n\n**Pros:**\n- Maximum efficiency (80-90% reduction vs full MCP)\n- Lowest compute/energy usage\n- Same functionality as MCP\n\n**Cons:**\n- Bash tool calls may require approval prompts\n- Slightly more verbose in conversation\n\n### Minimal MCP + Hooks (Balanced)\n\n**Setup:** Install minimal MCP server (read-only commands) + `bd setup claude`\n\nExposes only high-frequency read commands via MCP (ready, show, list). Everything else via CLI.\n\n**Tokens:** ~2-4k MCP + ~1-2k hooks\n\n**Pros:**\n- 60-80% reduction vs full MCP\n- No approval prompts for common queries\n- Cleaner syntax for frequent operations\n- Still efficient\n\n**Cons:**\n- Requires MCP server (additional setup)\n- Mixed interface (some MCP, some CLI)\n\n### Full MCP + Hooks (Legacy)\n\n**Setup:** Install full MCP server + `bd setup claude`\n\n**Tokens:** ~10.5k MCP + hooks\n\n**Pros:**\n- All commands as native function calls\n- Consistent interface\n\n**Cons:**\n- Highest token usage (worst for compute/energy/cost)\n- Slowest processing\n- Less sustainable\n\n### Recommendation\n\n1. **Start with CLI + hooks** - most efficient, works great\n2. **Try minimal MCP** if approval prompts become annoying\n3. **Avoid full MCP** - wasteful with no significant benefit\n```\n\n## Production Validation Checklist\n\nBefore making these documentation changes, validate CLI approach works reliably:\n\n### Phase 1: Pure CLI Validation\n- [ ] `bd prime` implemented and tested\n- [ ] Hooks installed and working in Claude Code\n- [ ] Real-world usage by at least 2-3 developers for 1+ weeks\n- [ ] No significant usability issues reported\n- [ ] Agent successfully uses bd via Bash tool\n- [ ] Document which commands (if any) have approval prompt issues\n\n### Phase 2: Hybrid Mode Investigation (Optional)\n- [ ] Test if MCP calls skip approval prompts vs Bash calls\n- [ ] Measure token cost per MCP command\n- [ ] Identify minimal set of commands worth exposing via MCP\n- [ ] Build minimal MCP server variant\n- [ ] Validate token savings (should be 60-80% vs full MCP)\n- [ ] User testing shows actual UX improvement\n\n### Phase 3: Documentation Update\n- [ ] Update based on validation results\n- [ ] Include measured token counts (not estimates)\n- [ ] Provide clear migration paths\n- [ ] Update `bd doctor` recommendations\n\n## Migration Guide (Optional)\n\nFor users currently using MCP:\n\n```markdown\n### Migrating from Full MCP to CLI + Hooks\n\nAlready using full MCP server? You can switch to the more efficient CLI approach:\n\n1. Install hooks: `bd setup claude`\n2. Test it works (hooks inject context, agent uses Bash tool)\n3. Remove MCP server from `~/.claude/settings.json`\n4. Restart Claude Code\n\nYou'll get the same functionality with 80-90% less token usage.\n\n### Migrating to Minimal MCP (If Available)\n\nIf you find approval prompts annoying for certain commands:\n\n1. Replace full MCP with minimal MCP in `~/.claude/settings.json`\n2. Restart Claude Code\n3. Verify high-frequency commands (ready, show, list) work via MCP\n4. Everything else automatically uses CLI\n\nYou'll get 60-80% token reduction vs full MCP while keeping the UX benefits.\n```\n\n## Files to Update\n\n- `README.md` - Add recommendation in AI Integration section\n- `AGENTS.md` - Add \"Choosing Your Integration Approach\" section early\n- `QUICKSTART.md` - Update AI integration section\n- `docs/` - Any other AI integration docs if they exist\n- `mcp-server/` - Create minimal variant if hybrid validated\n\n## Future: Update `bd init`\n\nOnce validated, update `bd init` to:\n- Default to recommending `bd setup claude` (hooks only)\n- Mention minimal MCP as option for UX improvement\n- Detect existing full MCP and suggest migration\n- Provide token usage estimates for each approach\n\n## MCP Server Architecture Note\n\n**Key insight:** MCP server doesn't have to expose all bd functionality.\n\nCurrent design exposes ~20+ commands (all bd subcommands). This is over-engineered.\n\n**Better design:**\n- **Minimal MCP**: 3-5 read-only commands (~2-4k tokens)\n- **CLI**: Everything else via Bash tool\n- **Hooks**: Context injection via `bd prime`\n\nThis achieves best of both worlds:\n- Low token usage (efficient)\n- No approval prompts for common queries (UX)\n- Explicit visibility for state changes (safety)\n\nIf validation shows NO meaningful benefit to MCP (even minimal), skip hybrid mode entirely and recommend pure CLI.","acceptance_criteria":"- Documentation explains CLI + hooks as recommended approach\n- Explains why context size matters (compute/energy/cost/latency)\n- Token comparison table shows 80-90% reduction\n- Migration guide for existing MCP users\n- Only deployed AFTER production validation\n- Clear that both approaches are supported","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-12T00:15:25.923025-08:00","updated_at":"2025-11-12T00:18:16.786857-08:00","source_repo":"."} {"id":"bd-xwo","content_hash":"48264aedbfd8cd9ea8ab6ca37882497be431f2827004554058645b610adc3009","title":"Fix validatePreExport to use content hash instead of mtime","description":"validatePreExport() in integrity.go:70 still uses isJSONLNewer() (mtime-based), creating inconsistent behavior. Auto-import correctly uses hasJSONLChanged() (hash-based) but export validation still uses the old mtime approach. This can cause false positive blocks after git operations.\n\nFix: Replace isJSONLNewer() call with hasJSONLChanged() in validatePreExport().\n\nImpact: Without this fix, the bd-khnb solution is incomplete - we prevent resurrection but still have export blocking issues.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-20T21:31:03.183164-05:00","updated_at":"2025-11-20T21:34:00.200803-05:00","closed_at":"2025-11-20T21:34:00.200803-05:00","source_repo":".","dependencies":[{"issue_id":"bd-xwo","depends_on_id":"bd-khnb","type":"blocks","created_at":"2025-11-20T21:31:03.184049-05:00","created_by":"daemon"}]} {"id":"bd-y6d","content_hash":"d13fc9682bf8abee565cf5724c32c56ead5c080cf257ad604b0a3d508a01a4b8","title":"Refactor create_test.go to use shared DB setup","description":"Convert TestCreate_* functions to use test suites with shared database setup.\n\nExample transformation:\n- Before: 10 separate tests, each with newTestStore() \n- After: 1 TestCreate() with 10 t.Run() subtests sharing one DB\n\nEstimated speedup: 10x faster (1 DB setup instead of 10)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-21T11:48:56.858213-05:00","updated_at":"2025-11-21T15:15:31.315407-05:00","closed_at":"2025-11-21T15:15:31.315407-05:00","source_repo":".","dependencies":[{"issue_id":"bd-y6d","depends_on_id":"bd-1rh","type":"blocks","created_at":"2025-11-21T11:49:09.660182-05:00","created_by":"daemon"},{"issue_id":"bd-y6d","depends_on_id":"bd-c49","type":"blocks","created_at":"2025-11-21T11:49:26.410452-05:00","created_by":"daemon"}]} diff --git a/cmd/bd/cli_fast_test.go b/cmd/bd/cli_fast_test.go index bc9b6d1c..0de98dd5 100644 --- a/cmd/bd/cli_fast_test.go +++ b/cmd/bd/cli_fast_test.go @@ -248,6 +248,71 @@ func TestCLI_Update(t *testing.T) { } } +func TestCLI_UpdateLabels(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow CLI test in short mode") + } + // Note: Not using t.Parallel() because inProcessMutex serializes execution anyway + tmpDir := setupCLITestDB(t) + out := runBDInProcess(t, tmpDir, "create", "Issue for label testing", "-p", "2", "--json") + + var issue map[string]interface{} + json.Unmarshal([]byte(out), &issue) + id := issue["id"].(string) + + // Test adding labels + runBDInProcess(t, tmpDir, "update", id, "--add-label", "feature", "--add-label", "backend") + + out = runBDInProcess(t, tmpDir, "show", id, "--json") + var updated []map[string]interface{} + json.Unmarshal([]byte(out), &updated) + labels := updated[0]["labels"].([]interface{}) + if len(labels) != 2 { + t.Errorf("Expected 2 labels after add, got: %d", len(labels)) + } + hasBackend, hasFeature := false, false + for _, l := range labels { + if l.(string) == "backend" { + hasBackend = true + } + if l.(string) == "feature" { + hasFeature = true + } + } + if !hasBackend || !hasFeature { + t.Errorf("Expected labels 'backend' and 'feature', got: %v", labels) + } + + // Test removing a label + runBDInProcess(t, tmpDir, "update", id, "--remove-label", "backend") + + out = runBDInProcess(t, tmpDir, "show", id, "--json") + json.Unmarshal([]byte(out), &updated) + labels = updated[0]["labels"].([]interface{}) + if len(labels) != 1 { + t.Errorf("Expected 1 label after remove, got: %d", len(labels)) + } + if labels[0].(string) != "feature" { + t.Errorf("Expected label 'feature', got: %v", labels[0]) + } + + // Test setting labels (replaces all) + runBDInProcess(t, tmpDir, "update", id, "--set-labels", "api,database,critical") + + out = runBDInProcess(t, tmpDir, "show", id, "--json") + json.Unmarshal([]byte(out), &updated) + labels = updated[0]["labels"].([]interface{}) + if len(labels) != 3 { + t.Errorf("Expected 3 labels after set, got: %d", len(labels)) + } + expectedLabels := map[string]bool{"api": true, "database": true, "critical": true} + for _, l := range labels { + if !expectedLabels[l.(string)] { + t.Errorf("Unexpected label: %v", l) + } + } +} + func TestCLI_Close(t *testing.T) { if testing.Short() { t.Skip("skipping slow CLI test in short mode") diff --git a/cmd/bd/show.go b/cmd/bd/show.go index 1d2d5c7f..499886f4 100644 --- a/cmd/bd/show.go +++ b/cmd/bd/show.go @@ -393,6 +393,18 @@ var updateCmd = &cobra.Command{ externalRef, _ := cmd.Flags().GetString("external-ref") updates["external_ref"] = externalRef } + if cmd.Flags().Changed("add-label") { + addLabels, _ := cmd.Flags().GetStringSlice("add-label") + updates["add_labels"] = addLabels + } + if cmd.Flags().Changed("remove-label") { + removeLabels, _ := cmd.Flags().GetStringSlice("remove-label") + updates["remove_labels"] = removeLabels + } + if cmd.Flags().Changed("set-labels") { + setLabels, _ := cmd.Flags().GetStringSlice("set-labels") + updates["set_labels"] = setLabels + } if len(updates) == 0 { fmt.Println("No updates specified") @@ -461,6 +473,15 @@ var updateCmd = &cobra.Command{ if externalRef, ok := updates["external_ref"].(string); ok { // NEW: Map external_ref updateArgs.ExternalRef = &externalRef } + if addLabels, ok := updates["add_labels"].([]string); ok { + updateArgs.AddLabels = addLabels + } + if removeLabels, ok := updates["remove_labels"].([]string); ok { + updateArgs.RemoveLabels = removeLabels + } + if setLabels, ok := updates["set_labels"].([]string); ok { + updateArgs.SetLabels = setLabels + } resp, err := daemonClient.Update(updateArgs) if err != nil { @@ -488,9 +509,63 @@ var updateCmd = &cobra.Command{ // Direct mode updatedIssues := []*types.Issue{} for _, id := range resolvedIDs { - if err := store.UpdateIssue(ctx, id, updates, actor); err != nil { - fmt.Fprintf(os.Stderr, "Error updating %s: %v\n", id, err) - continue + // Apply regular field updates if any + regularUpdates := make(map[string]interface{}) + for k, v := range updates { + if k != "add_labels" && k != "remove_labels" && k != "set_labels" { + regularUpdates[k] = v + } + } + if len(regularUpdates) > 0 { + if err := store.UpdateIssue(ctx, id, regularUpdates, actor); err != nil { + fmt.Fprintf(os.Stderr, "Error updating %s: %v\n", id, err) + continue + } + } + + // Handle label operations + // Set labels (replaces all existing labels) + if setLabels, ok := updates["set_labels"].([]string); ok && len(setLabels) > 0 { + // Get current labels + currentLabels, err := store.GetLabels(ctx, id) + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting labels for %s: %v\n", id, err) + continue + } + // Remove all current labels + for _, label := range currentLabels { + if err := store.RemoveLabel(ctx, id, label, actor); err != nil { + fmt.Fprintf(os.Stderr, "Error removing label %s from %s: %v\n", label, id, err) + continue + } + } + // Add new labels + for _, label := range setLabels { + if err := store.AddLabel(ctx, id, label, actor); err != nil { + fmt.Fprintf(os.Stderr, "Error setting label %s on %s: %v\n", label, id, err) + continue + } + } + } + + // Add labels + if addLabels, ok := updates["add_labels"].([]string); ok { + for _, label := range addLabels { + if err := store.AddLabel(ctx, id, label, actor); err != nil { + fmt.Fprintf(os.Stderr, "Error adding label %s to %s: %v\n", label, id, err) + continue + } + } + } + + // Remove labels + if removeLabels, ok := updates["remove_labels"].([]string); ok { + for _, label := range removeLabels { + if err := store.RemoveLabel(ctx, id, label, actor); err != nil { + fmt.Fprintf(os.Stderr, "Error removing label %s from %s: %v\n", label, id, err) + continue + } + } } if jsonOutput { @@ -822,6 +897,9 @@ func init() { updateCmd.Flags().String("notes", "", "Additional notes") updateCmd.Flags().String("acceptance-criteria", "", "DEPRECATED: use --acceptance") _ = updateCmd.Flags().MarkHidden("acceptance-criteria") + updateCmd.Flags().StringSlice("add-label", nil, "Add labels (repeatable)") + updateCmd.Flags().StringSlice("remove-label", nil, "Remove labels (repeatable)") + updateCmd.Flags().StringSlice("set-labels", nil, "Set labels, replacing all existing (repeatable)") updateCmd.Flags().Bool("json", false, "Output JSON format") rootCmd.AddCommand(updateCmd) diff --git a/internal/rpc/protocol.go b/internal/rpc/protocol.go index 1628c6c3..f00bc030 100644 --- a/internal/rpc/protocol.go +++ b/internal/rpc/protocol.go @@ -74,16 +74,19 @@ type CreateArgs struct { // UpdateArgs represents arguments for the update operation type UpdateArgs struct { - ID string `json:"id"` - Title *string `json:"title,omitempty"` - Description *string `json:"description,omitempty"` - Status *string `json:"status,omitempty"` - Priority *int `json:"priority,omitempty"` - Design *string `json:"design,omitempty"` - AcceptanceCriteria *string `json:"acceptance_criteria,omitempty"` - Notes *string `json:"notes,omitempty"` - Assignee *string `json:"assignee,omitempty"` - ExternalRef *string `json:"external_ref,omitempty"` // Link to external issue trackers + ID string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` + Status *string `json:"status,omitempty"` + Priority *int `json:"priority,omitempty"` + Design *string `json:"design,omitempty"` + AcceptanceCriteria *string `json:"acceptance_criteria,omitempty"` + Notes *string `json:"notes,omitempty"` + Assignee *string `json:"assignee,omitempty"` + ExternalRef *string `json:"external_ref,omitempty"` // Link to external issue trackers + AddLabels []string `json:"add_labels,omitempty"` + RemoveLabels []string `json:"remove_labels,omitempty"` + SetLabels []string `json:"set_labels,omitempty"` } // CloseArgs represents arguments for the close operation diff --git a/internal/rpc/server_issues_epics.go b/internal/rpc/server_issues_epics.go index f1360305..1add520f 100644 --- a/internal/rpc/server_issues_epics.go +++ b/internal/rpc/server_issues_epics.go @@ -279,19 +279,73 @@ func (s *Server) handleUpdate(req *Request) Response { ctx := s.reqCtx(req) updates := updatesFromArgs(updateArgs) - if len(updates) == 0 { - return Response{Success: true} - } + actor := s.reqActor(req) - if err := store.UpdateIssue(ctx, updateArgs.ID, updates, s.reqActor(req)); err != nil { - return Response{ - Success: false, - Error: fmt.Sprintf("failed to update issue: %v", err), + // Apply regular field updates if any + if len(updates) > 0 { + if err := store.UpdateIssue(ctx, updateArgs.ID, updates, actor); err != nil { + return Response{ + Success: false, + Error: fmt.Sprintf("failed to update issue: %v", err), + } } } - // Emit mutation event for event-driven daemon - s.emitMutation(MutationUpdate, updateArgs.ID) + // Handle label operations + // Set labels (replaces all existing labels) + if len(updateArgs.SetLabels) > 0 { + // Get current labels + currentLabels, err := store.GetLabels(ctx, updateArgs.ID) + if err != nil { + return Response{ + Success: false, + Error: fmt.Sprintf("failed to get current labels: %v", err), + } + } + // Remove all current labels + for _, label := range currentLabels { + if err := store.RemoveLabel(ctx, updateArgs.ID, label, actor); err != nil { + return Response{ + Success: false, + Error: fmt.Sprintf("failed to remove label %s: %v", label, err), + } + } + } + // Add new labels + for _, label := range updateArgs.SetLabels { + if err := store.AddLabel(ctx, updateArgs.ID, label, actor); err != nil { + return Response{ + Success: false, + Error: fmt.Sprintf("failed to set label %s: %v", label, err), + } + } + } + } + + // Add labels + for _, label := range updateArgs.AddLabels { + if err := store.AddLabel(ctx, updateArgs.ID, label, actor); err != nil { + return Response{ + Success: false, + Error: fmt.Sprintf("failed to add label %s: %v", label, err), + } + } + } + + // Remove labels + for _, label := range updateArgs.RemoveLabels { + if err := store.RemoveLabel(ctx, updateArgs.ID, label, actor); err != nil { + return Response{ + Success: false, + Error: fmt.Sprintf("failed to remove label %s: %v", label, err), + } + } + } + + // Emit mutation event for event-driven daemon (only if any updates or label operations were performed) + if len(updates) > 0 || len(updateArgs.SetLabels) > 0 || len(updateArgs.AddLabels) > 0 || len(updateArgs.RemoveLabels) > 0 { + s.emitMutation(MutationUpdate, updateArgs.ID) + } issue, err := store.GetIssue(ctx, updateArgs.ID) if err != nil {