From f4acf5c662f02db8871324f8bdd63d99963c992a Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Tue, 23 Dec 2025 14:18:09 -0800 Subject: [PATCH] bd sync: 2025-12-23 14:18:09 --- .beads/issues.jsonl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 4412a4bf..f5c2b2e6 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -384,7 +384,7 @@ {"id":"bd-psg","title":"Add tests for dependency management","description":"Key dependency functions like mergeBidirectionalTrees, GetDependencyTree, and DetectCycles have low or no coverage. These are essential for maintaining data integrity in the dependency graph.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T07:00:43.458548462-07:00","updated_at":"2025-12-19T09:54:57.018745301-07:00","closed_at":"2025-12-18T10:24:56.271508339-07:00","dependencies":[{"issue_id":"bd-psg","depends_on_id":"bd-6ss","type":"discovered-from","created_at":"2025-12-18T07:00:43.463910911-07:00","created_by":"matt"}]} {"id":"bd-pvu0","title":"Merge: bd-4opy","description":"branch: polecat/angharad\ntarget: main\nsource_issue: bd-4opy\nrig: beads","status":"closed","priority":2,"issue_type":"merge-request","created_at":"2025-12-23T00:24:44.057267-08:00","updated_at":"2025-12-23T01:33:25.730271-08:00","closed_at":"2025-12-23T01:33:25.730271-08:00","close_reason":"Merged to main"} {"id":"bd-pzw7","title":"gt handoff deadlock at handoff.go:125","notes":"When running 'gt handoff -m \"message\"' after successful MR submit, go panics with 'fatal error: all goroutines are asleep - deadlock\\!' at handoff.go:125. The shutdown request still appears to be sent successfully but the command crashes. Stack trace shows issue is in runHandoff select statement.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-19T23:22:12.46315-08:00","updated_at":"2025-12-21T17:51:25.817355-08:00","closed_at":"2025-12-21T17:51:25.817355-08:00","close_reason":"Moved to gastown: gt-dich"} -{"id":"bd-qioh","title":"Standardize error handling: replace direct fmt.Fprintf+os.Exit with FatalError","description":"Standardize error handling in cmd/bd/ using FatalError pattern.\n\n## Current State\n~200+ instances of direct `fmt.Fprintf(os.Stderr, ...) + os.Exit(1)` pattern scattered across cmd/bd/*.go files.\n\n## Target Pattern\n\nUse existing FatalError helper (or create if missing):\n\n```go\n// In cmd/bd/helpers.go or similar\nfunc FatalError(format string, args ...interface{}) {\n fmt.Fprintf(os.Stderr, \"Error: \"+format+\"\\n\", args...)\n os.Exit(1)\n}\n\nfunc FatalErrorf(err error, context string) {\n fmt.Fprintf(os.Stderr, \"Error: %s: %v\\n\", context, err)\n os.Exit(1)\n}\n```\n\n## Transformation\n\n```go\n// Before\nfmt.Fprintf(os.Stderr, \"Error: %v\\n\", err)\nos.Exit(1)\n\n// After\nFatalError(\"%v\", err)\n\n// Before\nfmt.Fprintf(os.Stderr, \"Error: invalid --since duration: %v\\n\", err)\nos.Exit(1)\n\n// After\nFatalError(\"invalid --since duration: %v\", err)\n```\n\n## Files to Update (by occurrence count)\nRun: `grep -c \"os.Exit(1)\" cmd/bd/*.go | sort -t: -k2 -rn | head -20`\n\nPriority files (highest occurrence):\n- close.go\n- show.go\n- sync.go\n- init.go\n- list.go\n- create.go\n- update.go\n\n## Implementation Steps\n\n1. **Check if FatalError exists** in cmd/bd/helpers.go or create it\n2. **Create migration script** or use sed:\n ```bash\n # Find patterns to replace\n grep -rn \"fmt.Fprintf(os.Stderr.*Error.*\\n.*os.Exit(1)\" cmd/bd/\n ```\n3. **Replace systematically** file by file\n4. **Run tests** after each file to verify behavior unchanged\n5. **Run linter** to catch any missed patterns\n\n## Verification\n```bash\n# Count remaining direct exits (should be near zero)\ngrep -c \"os.Exit(1)\" cmd/bd/*.go | awk -F: \"{sum+=\\$2} END {print sum}\"\n\n# Run tests\ngo test -short ./cmd/bd/...\n```\n\n## Success Criteria\n- All error exits use FatalError/FatalErrorf\n- Consistent \"Error: \" prefix on all error messages\n- Tests pass\n- No behavior changes (exit codes remain 1)","notes":"## Progress (Dec 2025)\n\nStandardized error handling in 3 major files:\n- compact.go: All 48 os.Exit(1) calls converted to FatalError\n- sync.go: All error patterns converted (kept 1 valid summary exit)\n- migrate.go: 4 patterns converted\n\n## Remaining Work\n~326 fmt.Fprintf(os.Stderr, \"Error:\") patterns remain across ~30 files.\nHigh-count files remaining: show.go (16), dep.go (14), gate.go (28), init.go (11).\n\n## Verification\n- Build compiles successfully\n- Tests pass","status":"in_progress","priority":2,"issue_type":"task","assignee":"beads/Errata","created_at":"2025-12-16T18:17:19.309394-08:00","updated_at":"2025-12-23T13:45:39.675331-08:00","dependencies":[{"issue_id":"bd-qioh","depends_on_id":"bd-iz5t","type":"parent-child","created_at":"2025-12-23T12:44:07.43514-08:00","created_by":"daemon"}]} +{"id":"bd-qioh","title":"Standardize error handling: replace direct fmt.Fprintf+os.Exit with FatalError","description":"Standardize error handling in cmd/bd/ using FatalError pattern.\n\n## Current State\n~200+ instances of direct `fmt.Fprintf(os.Stderr, ...) + os.Exit(1)` pattern scattered across cmd/bd/*.go files.\n\n## Target Pattern\n\nUse existing FatalError helper (or create if missing):\n\n```go\n// In cmd/bd/helpers.go or similar\nfunc FatalError(format string, args ...interface{}) {\n fmt.Fprintf(os.Stderr, \"Error: \"+format+\"\\n\", args...)\n os.Exit(1)\n}\n\nfunc FatalErrorf(err error, context string) {\n fmt.Fprintf(os.Stderr, \"Error: %s: %v\\n\", context, err)\n os.Exit(1)\n}\n```\n\n## Transformation\n\n```go\n// Before\nfmt.Fprintf(os.Stderr, \"Error: %v\\n\", err)\nos.Exit(1)\n\n// After\nFatalError(\"%v\", err)\n\n// Before\nfmt.Fprintf(os.Stderr, \"Error: invalid --since duration: %v\\n\", err)\nos.Exit(1)\n\n// After\nFatalError(\"invalid --since duration: %v\", err)\n```\n\n## Files to Update (by occurrence count)\nRun: `grep -c \"os.Exit(1)\" cmd/bd/*.go | sort -t: -k2 -rn | head -20`\n\nPriority files (highest occurrence):\n- close.go\n- show.go\n- sync.go\n- init.go\n- list.go\n- create.go\n- update.go\n\n## Implementation Steps\n\n1. **Check if FatalError exists** in cmd/bd/helpers.go or create it\n2. **Create migration script** or use sed:\n ```bash\n # Find patterns to replace\n grep -rn \"fmt.Fprintf(os.Stderr.*Error.*\\n.*os.Exit(1)\" cmd/bd/\n ```\n3. **Replace systematically** file by file\n4. **Run tests** after each file to verify behavior unchanged\n5. **Run linter** to catch any missed patterns\n\n## Verification\n```bash\n# Count remaining direct exits (should be near zero)\ngrep -c \"os.Exit(1)\" cmd/bd/*.go | awk -F: \"{sum+=\\$2} END {print sum}\"\n\n# Run tests\ngo test -short ./cmd/bd/...\n```\n\n## Success Criteria\n- All error exits use FatalError/FatalErrorf\n- Consistent \"Error: \" prefix on all error messages\n- Tests pass\n- No behavior changes (exit codes remain 1)","notes":"## Progress (Dec 2025)\n\nStandardized error handling in 3 major files:\n- compact.go: All 48 os.Exit(1) calls converted to FatalError\n- sync.go: All error patterns converted (kept 1 valid summary exit)\n- migrate.go: 4 patterns converted\n\n## Remaining Work\n~326 fmt.Fprintf(os.Stderr, \"Error:\") patterns remain across ~30 files.\nHigh-count files remaining: show.go (16), dep.go (14), gate.go (28), init.go (11).\n\n## Verification\n- Build compiles successfully\n- Tests pass","status":"closed","priority":2,"issue_type":"task","assignee":"beads/Errata","created_at":"2025-12-16T18:17:19.309394-08:00","updated_at":"2025-12-23T14:14:37.939802-08:00","closed_at":"2025-12-23T14:14:37.939802-08:00","close_reason":"Error handling standardization - polecat completed, MR submitted","dependencies":[{"issue_id":"bd-qioh","depends_on_id":"bd-iz5t","type":"parent-child","created_at":"2025-12-23T12:44:07.43514-08:00","created_by":"daemon"}]} {"id":"bd-qkw9","title":"Run bump-version.sh {{version}}","description":"Run ./scripts/bump-version.sh {{version}} to update version in all files","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T22:55:58.841424-08:00","updated_at":"2025-12-20T17:59:26.262877-08:00","closed_at":"2025-12-20T01:18:48.99813-08:00","dependencies":[{"issue_id":"bd-qkw9","depends_on_id":"bd-6s61","type":"parent-child","created_at":"2025-12-19T22:56:14.786027-08:00","created_by":"daemon"}]} {"id":"bd-qqc","title":"Release v{{version}}","description":"Version bump workflow for beads release {{version}}.\n\n## Variables\n- `{{version}}` - The new version number (e.g., 0.31.0)\n- `{{date}}` - Release date (YYYY-MM-DD format)\n\n## Workflow Steps\n1. Kill running daemons\n2. Run tests and linting\n3. Bump version in all files (10 files total)\n4. Update cmd/bd/info.go with release notes\n5. Commit and push version bump\n6. Create and push git tag\n7. Update Homebrew formula\n8. Upgrade local Homebrew installation\n9. Verify installation\n\n## Files Updated by bump-version.sh\n- cmd/bd/version.go\n- .claude-plugin/plugin.json\n- .claude-plugin/marketplace.json\n- integrations/beads-mcp/pyproject.toml\n- integrations/beads-mcp/src/beads_mcp/__init__.py\n- README.md\n- npm-package/package.json\n- cmd/bd/templates/hooks/* (4 files)\n- CHANGELOG.md\n\n## Manual Step Required\n- cmd/bd/info.go - Add versionChanges entry with release notes","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-18T12:59:00.610371-08:00","updated_at":"2025-12-20T17:59:26.263219-08:00","closed_at":"2025-12-20T01:18:46.71424-08:00","labels":["template"]} {"id":"bd-qqc.1","title":"Update version to {{version}} in version.go","description":"Edit cmd/bd/version.go line 17:\n\n```go\nVersion = \"{{version}}\"\n```\n\nVerify with: `grep 'Version =' cmd/bd/version.go`","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-18T12:59:13.887087-08:00","updated_at":"2025-12-18T23:34:18.630067-08:00","closed_at":"2025-12-18T22:41:41.82664-08:00","dependencies":[{"issue_id":"bd-qqc.1","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T12:59:13.887655-08:00","created_by":"stevey"}]} @@ -447,7 +447,7 @@ {"id":"bd-u2sc","title":"GH#692: Code quality and refactoring improvements","description":"Epic for implementing refactoring suggestions from GitHub issue #692 (rsnodgrass). These are code quality improvements that don't change functionality but improve maintainability, type safety, and performance.\n\nOriginal issue: https://github.com/steveyegge/beads/issues/692\n\nHigh priority items:\n1. Replace map[string]interface{} with typed structs for JSON output\n2. Adopt slices.SortFunc instead of sort.Slice (Go 1.21+)\n3. Split large files (sync.go, init.go, show.go)\n4. Introduce slog for structured logging in daemon\n\nLower priority:\n5. Further CLI helper extraction\n6. Preallocate slices in hot paths\n7. Polish items (error wrapping, table-driven parsing)","status":"open","priority":3,"issue_type":"epic","created_at":"2025-12-22T14:26:31.630004-08:00","updated_at":"2025-12-22T14:43:08.562025-08:00","external_ref":"gh-692"} {"id":"bd-u2sc.1","title":"Replace map[string]interface{} with typed JSON response structs","description":"Many CLI commands use map[string]interface{} for JSON output which loses type safety and compile-time error detection.\n\nFiles with map[string]interface{}:\n- cmd/bd/compact.go (10+ instances)\n- cmd/bd/cleanup.go\n- cmd/bd/daemons.go\n- cmd/bd/daemon_lifecycle.go\n\nExample fix:\n```go\n// Before\nresult := map[string]interface{}{\n \"status\": \"ok\",\n \"count\": 42,\n}\n\n// After\ntype CompactResponse struct {\n Status string `json:\"status\"`\n Count int `json:\"count\"`\n}\nresult := CompactResponse{Status: \"ok\", Count: 42}\n```\n\nBenefits:\n- Compile-time type checking\n- IDE autocompletion\n- Easier refactoring\n- Self-documenting API","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-22T14:26:44.088548-08:00","updated_at":"2025-12-22T15:48:22.88824-08:00","closed_at":"2025-12-22T15:48:22.88824-08:00","close_reason":"Replaced map[string]interface{} with typed JSON response structs in compact.go, cleanup.go, daemons.go, and daemon_lifecycle.go. Created 15 typed response structs for improved type safety and compile-time error detection.","dependencies":[{"issue_id":"bd-u2sc.1","depends_on_id":"bd-u2sc","type":"parent-child","created_at":"2025-12-22T14:26:44.088931-08:00","created_by":"daemon"}]} {"id":"bd-u2sc.2","title":"Migrate sort.Slice to slices.SortFunc","description":"Go 1.21+ provides slices.SortFunc which is cleaner and slightly faster than sort.Slice.\n\nFound 15+ instances of sort.Slice in:\n- cmd/bd/autoflush.go\n- cmd/bd/count.go\n- cmd/bd/daemon_sync.go\n- cmd/bd/doctor.go\n- cmd/bd/export.go\n- cmd/bd/import.go\n- cmd/bd/integrity.go\n- cmd/bd/jira.go\n- cmd/bd/list.go\n- cmd/bd/migrate_hash_ids.go\n- cmd/bd/rename_prefix.go\n- cmd/bd/show.go\n\nExample migration:\n```go\n// Before\nsort.Slice(issues, func(i, j int) bool {\n return issues[i].Priority \u003c issues[j].Priority\n})\n\n// After\nslices.SortFunc(issues, func(a, b *types.Issue) int {\n return cmp.Compare(a.Priority, b.Priority)\n})\n```\n\nBenefits:\n- Cleaner 3-way comparison\n- Slightly better performance\n- Modern idiomatic Go","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-22T14:26:55.573524-08:00","updated_at":"2025-12-22T15:10:12.639807-08:00","closed_at":"2025-12-22T15:10:12.639807-08:00","close_reason":"Migrated all 19 instances of sort.Slice to slices.SortFunc across 17 files in cmd/bd/. Used cmp.Compare for orderable types, time.Compare for time comparisons, and cmp.Or for multi-field sorts.","dependencies":[{"issue_id":"bd-u2sc.2","depends_on_id":"bd-u2sc","type":"parent-child","created_at":"2025-12-22T14:26:55.573978-08:00","created_by":"daemon"}]} -{"id":"bd-u2sc.3","title":"Split large cmd/bd files into logical modules","description":"Split large cmd/bd files into logical modules for maintainability.\n\n## Current State\n| File | Lines | Status |\n|------|-------|--------|\n| sync.go | 2203 | Too large |\n| init.go | 1742 | Too large |\n| show.go | 1419 | Too large |\n| compact.go | 1190 | Borderline |\n\n## Proposed Splits\n\n### 1. sync.go (2203 lines) → 4 files\n\n**sync.go** (~500 lines) - Main command and coordination\n```go\nvar syncCmd = \u0026cobra.Command{...}\nfunc init() {...}\nfunc doSync(...) {...}\n```\n\n**sync_export.go** (~400 lines) - Export operations\n```go\nfunc exportToJSONL(...)\nfunc markDirtyAndScheduleFlush(...)\nfunc flushPendingChanges(...)\n```\n\n**sync_import.go** (~500 lines) - Import operations\n```go\nfunc importFromJSONL(...)\nfunc handleImportConflicts(...)\nfunc mergeImportedIssues(...)\n```\n\n**sync_branch.go** (~400 lines) - Git branch operations\n```go\nfunc commitToSyncBranch(...)\nfunc pullFromSyncBranch(...)\nfunc handleSyncBranchDivergence(...)\n```\n\n### 2. init.go (1742 lines) → 3 files\n\n**init.go** (~400 lines) - Main init command\n```go\nvar initCmd = \u0026cobra.Command{...}\nfunc runInit(...)\nfunc determinePrefix(...)\n```\n\n**init_wizard.go** (~500 lines) - Interactive setup\n```go\nfunc runContributorWizard(...)\nfunc runTeamWizard(...)\nfunc promptForConfig(...)\n```\n\n**init_hooks.go** (~400 lines) - Git hooks setup\n```go\nfunc installGitHooks(...)\nfunc configureAutosync(...)\nfunc setupMergeDriver(...)\n```\n\n### 3. show.go (1419 lines) → 3 files\n\n**show.go** (~400 lines) - Main show command\n```go\nvar showCmd = \u0026cobra.Command{...}\nfunc showIssue(...)\nfunc showMultipleIssues(...)\n```\n\n**show_format.go** (~400 lines) - Output formatting\n```go\nfunc formatIssueText(...)\nfunc formatIssueMarkdown(...)\nfunc formatDependencyTree(...)\n```\n\n**show_threads.go** (~300 lines) - Thread/conversation display\n```go\nfunc showThread(...)\nfunc formatThreadMessages(...)\n```\n\n### 4. compact.go (1190 lines) → 2 files\n\n**compact.go** (~600 lines) - Main compact command\n```go\nvar compactCmd = \u0026cobra.Command{...}\nfunc runCompact(...)\n```\n\n**compact_tiers.go** (~400 lines) - Tier-specific logic\n```go\nfunc compactTier1(...)\nfunc compactTier2(...)\nfunc squashWisps(...)\n```\n\n## Implementation Steps\n\n1. **Start with sync.go** (largest file)\n2. **Create new files** with same package declaration\n3. **Move functions** maintaining related code together\n4. **Update any file-local variables** that need to be shared\n5. **Run tests** after each split:\n ```bash\n go test -short ./cmd/bd/...\n ```\n\n## File Organization After Split\n```\ncmd/bd/\n├── sync.go (~500 lines)\n├── sync_export.go (~400 lines)\n├── sync_import.go (~500 lines)\n├── sync_branch.go (~400 lines)\n├── init.go (~400 lines)\n├── init_wizard.go (~500 lines)\n├── init_hooks.go (~400 lines)\n├── show.go (~400 lines)\n├── show_format.go (~400 lines)\n├── show_threads.go (~300 lines)\n├── compact.go (~600 lines)\n└── compact_tiers.go (~400 lines)\n```\n\n## Success Criteria\n- No file \u003e 600 lines\n- All tests pass\n- Related code stays together\n- Clear file naming indicates purpose","status":"in_progress","priority":3,"issue_type":"task","assignee":"beads/Modular","created_at":"2025-12-22T14:27:06.146343-08:00","updated_at":"2025-12-23T13:47:01.510023-08:00","dependencies":[{"issue_id":"bd-u2sc.3","depends_on_id":"bd-u2sc","type":"parent-child","created_at":"2025-12-22T14:27:06.146704-08:00","created_by":"daemon"},{"issue_id":"bd-u2sc.3","depends_on_id":"bd-iz5t","type":"parent-child","created_at":"2025-12-23T12:44:07.583136-08:00","created_by":"daemon"}]} +{"id":"bd-u2sc.3","title":"Split large cmd/bd files into logical modules","description":"Split large cmd/bd files into logical modules for maintainability.\n\n## Current State\n| File | Lines | Status |\n|------|-------|--------|\n| sync.go | 2203 | Too large |\n| init.go | 1742 | Too large |\n| show.go | 1419 | Too large |\n| compact.go | 1190 | Borderline |\n\n## Proposed Splits\n\n### 1. sync.go (2203 lines) → 4 files\n\n**sync.go** (~500 lines) - Main command and coordination\n```go\nvar syncCmd = \u0026cobra.Command{...}\nfunc init() {...}\nfunc doSync(...) {...}\n```\n\n**sync_export.go** (~400 lines) - Export operations\n```go\nfunc exportToJSONL(...)\nfunc markDirtyAndScheduleFlush(...)\nfunc flushPendingChanges(...)\n```\n\n**sync_import.go** (~500 lines) - Import operations\n```go\nfunc importFromJSONL(...)\nfunc handleImportConflicts(...)\nfunc mergeImportedIssues(...)\n```\n\n**sync_branch.go** (~400 lines) - Git branch operations\n```go\nfunc commitToSyncBranch(...)\nfunc pullFromSyncBranch(...)\nfunc handleSyncBranchDivergence(...)\n```\n\n### 2. init.go (1742 lines) → 3 files\n\n**init.go** (~400 lines) - Main init command\n```go\nvar initCmd = \u0026cobra.Command{...}\nfunc runInit(...)\nfunc determinePrefix(...)\n```\n\n**init_wizard.go** (~500 lines) - Interactive setup\n```go\nfunc runContributorWizard(...)\nfunc runTeamWizard(...)\nfunc promptForConfig(...)\n```\n\n**init_hooks.go** (~400 lines) - Git hooks setup\n```go\nfunc installGitHooks(...)\nfunc configureAutosync(...)\nfunc setupMergeDriver(...)\n```\n\n### 3. show.go (1419 lines) → 3 files\n\n**show.go** (~400 lines) - Main show command\n```go\nvar showCmd = \u0026cobra.Command{...}\nfunc showIssue(...)\nfunc showMultipleIssues(...)\n```\n\n**show_format.go** (~400 lines) - Output formatting\n```go\nfunc formatIssueText(...)\nfunc formatIssueMarkdown(...)\nfunc formatDependencyTree(...)\n```\n\n**show_threads.go** (~300 lines) - Thread/conversation display\n```go\nfunc showThread(...)\nfunc formatThreadMessages(...)\n```\n\n### 4. compact.go (1190 lines) → 2 files\n\n**compact.go** (~600 lines) - Main compact command\n```go\nvar compactCmd = \u0026cobra.Command{...}\nfunc runCompact(...)\n```\n\n**compact_tiers.go** (~400 lines) - Tier-specific logic\n```go\nfunc compactTier1(...)\nfunc compactTier2(...)\nfunc squashWisps(...)\n```\n\n## Implementation Steps\n\n1. **Start with sync.go** (largest file)\n2. **Create new files** with same package declaration\n3. **Move functions** maintaining related code together\n4. **Update any file-local variables** that need to be shared\n5. **Run tests** after each split:\n ```bash\n go test -short ./cmd/bd/...\n ```\n\n## File Organization After Split\n```\ncmd/bd/\n├── sync.go (~500 lines)\n├── sync_export.go (~400 lines)\n├── sync_import.go (~500 lines)\n├── sync_branch.go (~400 lines)\n├── init.go (~400 lines)\n├── init_wizard.go (~500 lines)\n├── init_hooks.go (~400 lines)\n├── show.go (~400 lines)\n├── show_format.go (~400 lines)\n├── show_threads.go (~300 lines)\n├── compact.go (~600 lines)\n└── compact_tiers.go (~400 lines)\n```\n\n## Success Criteria\n- No file \u003e 600 lines\n- All tests pass\n- Related code stays together\n- Clear file naming indicates purpose","status":"closed","priority":3,"issue_type":"task","assignee":"beads/Modular","created_at":"2025-12-22T14:27:06.146343-08:00","updated_at":"2025-12-23T14:14:39.023606-08:00","closed_at":"2025-12-23T14:14:39.023606-08:00","close_reason":"Split cmd/bd files - polecat completed, MR submitted","dependencies":[{"issue_id":"bd-u2sc.3","depends_on_id":"bd-u2sc","type":"parent-child","created_at":"2025-12-22T14:27:06.146704-08:00","created_by":"daemon"},{"issue_id":"bd-u2sc.3","depends_on_id":"bd-iz5t","type":"parent-child","created_at":"2025-12-23T12:44:07.583136-08:00","created_by":"daemon"}]} {"id":"bd-u2sc.4","title":"Introduce slog for structured daemon logging","description":"Introduce slog for structured daemon logging.\n\n## Current State\nDaemon uses fmt.Fprintf for logging:\n```go\nfmt.Fprintf(os.Stderr, \"Warning: failed to detect user role: %v\\n\", err)\nfmt.Fprintf(logFile, \"[%s] %s\\n\", time.Now().Format(time.RFC3339), msg)\n```\n\nThis produces unstructured, hard-to-parse logs.\n\n## Target State\nUse Go 1.21+ slog for structured logging:\n```go\nslog.Warn(\"failed to detect user role\", \"error\", err)\nslog.Info(\"sync completed\", \"created\", 5, \"updated\", 3, \"duration_ms\", 150)\n```\n\n## Implementation\n\n### 1. Create logger setup (internal/daemon/logger.go)\n\n```go\npackage daemon\n\nimport (\n \"io\"\n \"log/slog\"\n \"os\"\n)\n\n// SetupLogger configures the daemon logger.\n// Returns a cleanup function to close the log file.\nfunc SetupLogger(logPath string, jsonFormat bool, level slog.Level) (func(), error) {\n var w io.Writer = os.Stderr\n var cleanup func()\n \n if logPath \\!= \"\" {\n f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)\n if err \\!= nil {\n return nil, err\n }\n w = io.MultiWriter(os.Stderr, f)\n cleanup = func() { f.Close() }\n }\n \n var handler slog.Handler\n opts := \u0026slog.HandlerOptions{Level: level}\n \n if jsonFormat {\n handler = slog.NewJSONHandler(w, opts)\n } else {\n handler = slog.NewTextHandler(w, opts)\n }\n \n slog.SetDefault(slog.New(handler))\n \n return cleanup, nil\n}\n```\n\n### 2. Add log level flag\n\nIn cmd/bd/daemon.go:\n```go\ndaemonCmd.Flags().String(\"log-level\", \"info\", \"Log level (debug, info, warn, error)\")\ndaemonCmd.Flags().Bool(\"log-json\", false, \"Output logs in JSON format\")\n```\n\n### 3. Replace fmt.Fprintf with slog calls\n\n**Pattern 1: Simple messages**\n```go\n// Before\nfmt.Fprintf(os.Stderr, \"Starting daemon on %s\\n\", socketPath)\n\n// After\nslog.Info(\"starting daemon\", \"socket\", socketPath)\n```\n\n**Pattern 2: Errors**\n```go\n// Before\nfmt.Fprintf(os.Stderr, \"Error: failed to connect: %v\\n\", err)\n\n// After\nslog.Error(\"failed to connect\", \"error\", err)\n```\n\n**Pattern 3: Debug info**\n```go\n// Before\nif debug {\n fmt.Fprintf(os.Stderr, \"Received request: %s\\n\", method)\n}\n\n// After\nslog.Debug(\"received request\", \"method\", method)\n```\n\n**Pattern 4: Structured data**\n```go\n// Before\nfmt.Fprintf(logFile, \"Import: %d created, %d updated\\n\", created, updated)\n\n// After\nslog.Info(\"import completed\", \n \"created\", created,\n \"updated\", updated,\n \"unchanged\", unchanged,\n \"duration_ms\", duration.Milliseconds())\n```\n\n### 4. Files to Update\n\n| File | Changes |\n|------|---------|\n| cmd/bd/daemon.go | Add log flags, call SetupLogger |\n| internal/daemon/server.go | Replace fmt with slog |\n| internal/daemon/rpc_handler.go | Replace fmt with slog |\n| internal/daemon/sync.go | Replace fmt with slog |\n| internal/daemon/autoflush.go | Replace fmt with slog |\n| internal/daemon/logger.go | New file |\n\n### 5. Log output examples\n\n**Text format (default):**\n```\ntime=2025-12-23T12:30:00Z level=INFO msg=\"daemon started\" socket=/tmp/bd.sock pid=12345\ntime=2025-12-23T12:30:01Z level=INFO msg=\"import completed\" created=5 updated=3 duration_ms=150\ntime=2025-12-23T12:30:05Z level=WARN msg=\"sync branch diverged\" local_ahead=2 remote_ahead=1\n```\n\n**JSON format (--log-json):**\n```json\n{\"time\":\"2025-12-23T12:30:00Z\",\"level\":\"INFO\",\"msg\":\"daemon started\",\"socket\":\"/tmp/bd.sock\",\"pid\":12345}\n{\"time\":\"2025-12-23T12:30:01Z\",\"level\":\"INFO\",\"msg\":\"import completed\",\"created\":5,\"updated\":3,\"duration_ms\":150}\n```\n\n## Migration Strategy\n\n1. **Add logger.go** with SetupLogger\n2. **Update daemon startup** to initialize slog\n3. **Convert one file at a time** (start with server.go)\n4. **Test after each file**\n5. **Remove old logging code** once all converted\n\n## Testing\n\n```bash\n# Start daemon with debug logging\nbd daemon start --log-level debug\n\n# Check logs\nbd daemons logs . | head -20\n\n# Test JSON output\nbd daemon start --log-json --log-level debug\nbd daemons logs . | jq .\n```\n\n## Success Criteria\n- All daemon logging uses slog\n- --log-level controls verbosity\n- --log-json produces machine-parseable output\n- Log entries have consistent structure\n- No fmt.Fprintf to stderr in daemon code","status":"closed","priority":3,"issue_type":"task","assignee":"beads/Logger","created_at":"2025-12-22T14:27:16.47144-08:00","updated_at":"2025-12-23T13:44:23.374935-08:00","closed_at":"2025-12-23T13:44:23.374935-08:00","close_reason":"Implemented slog for structured daemon logging with --log-level and --log-json flags","dependencies":[{"issue_id":"bd-u2sc.4","depends_on_id":"bd-u2sc","type":"parent-child","created_at":"2025-12-22T14:27:16.471878-08:00","created_by":"daemon"},{"issue_id":"bd-u2sc.4","depends_on_id":"bd-iz5t","type":"parent-child","created_at":"2025-12-23T12:44:08.048156-08:00","created_by":"daemon"}]} {"id":"bd-u66e","title":"Implement bd gate create/show/list/close/wait commands","description":"Implement the gate CLI commands.\n\n## Commands\n```bash\n# Create gate (returns gate ID)\nbd gate create --await \u003ctype\u003e:\u003cid\u003e --timeout \u003cduration\u003e --notify \u003caddr\u003e\n\n# Check gate status\nbd gate show \u003cid\u003e\n\n# List open gates\nbd gate list\n\n# Close gate (usually done by Deacon)\nbd gate close \u003cid\u003e --reason \"completed\"\n\n# Add waiter to existing gate\nbd gate wait \u003cid\u003e --notify \u003caddr\u003e\n```\n\n## Implementation\n- Add cmd/bd/gate.go with subcommands\n- Gate create creates wisp issue of type gate\n- Gate list filters for open gates\n- Gate wait adds to waiters[] array\n- All support --json output","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-23T11:44:34.022464-08:00","updated_at":"2025-12-23T12:06:18.550673-08:00","closed_at":"2025-12-23T12:06:18.550673-08:00","close_reason":"Implemented bd gate commands: create, show, list, close, wait. All support --json output and work with wisp/gate fields.","dependencies":[{"issue_id":"bd-u66e","depends_on_id":"bd-udsi","type":"parent-child","created_at":"2025-12-23T11:44:52.823353-08:00","created_by":"daemon"},{"issue_id":"bd-u66e","depends_on_id":"bd-lz49","type":"blocks","created_at":"2025-12-23T11:44:56.349662-08:00","created_by":"daemon"}]} {"id":"bd-ucgz","title":"Migration invariants should exclude external dependencies from orphan check","description":"## Summary\n\nThe `checkForeignKeys` function in `migration_invariants.go` flags external dependencies as orphaned because they dont exist in the local issues table.\n\n## Location\n\n`internal/storage/sqlite/migration_invariants.go` around line 150-162\n\n## Current Code (buggy)\n\n```go\n// Check for orphaned dependencies\nvar orphanCount int\nerr = db.QueryRowContext(ctx, \\`\n SELECT COUNT(*)\n FROM dependencies d\n WHERE NOT EXISTS (SELECT 1 FROM issues WHERE id = d.depends_on_id)\n\\`).Scan(\u0026orphanCount)\n```\n\n## Fix\n\nExclude external references (format: `external:\u003cproject\u003e:\u003ccapability\u003e`):\n\n```go\n// Check for orphaned dependencies (excluding external refs)\nvar orphanCount int\nerr = db.QueryRowContext(ctx, \\`\n SELECT COUNT(*)\n FROM dependencies d\n WHERE NOT EXISTS (SELECT 1 FROM issues WHERE id = d.depends_on_id)\n AND d.depends_on_id NOT LIKE 'external:%'\n\\`).Scan(\u0026orphanCount)\n```\n\n## Reproduction\n\n```bash\n# Add external dependency\nbd dep add bd-xxx external:other-project:some-capability\n\n# Try to sync - fails\nbd sync\n# Error: found 1 orphaned dependencies\n```\n\n## Files to Modify\n\n1. **internal/storage/sqlite/migration_invariants.go** - Add WHERE clause\n\n## Testing\n\n```bash\n# Create issue with external dep\nbd create \"Test external deps\" -t task\nbd dep add bd-xxx external:beads:release-workflow\n\n# Sync should succeed\nbd sync\n\n# Verify dep exists\nbd show bd-xxx --json | jq .dependencies\n```\n\n## Success Criteria\n- External dependencies dont trigger orphan check\n- `bd sync` succeeds with external deps\n- Regular orphan detection still works for internal deps","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-23T12:37:08.99387-08:00","updated_at":"2025-12-23T12:42:03.722691-08:00","closed_at":"2025-12-23T12:42:03.722691-08:00","close_reason":"Fixed in commit 90196eca - added NOT LIKE 'external:%' exclusion"}