From fc71d4e1927972c696186b6b0066964faeb426a0 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Mon, 3 Nov 2025 11:54:37 -0800 Subject: [PATCH] Add integration tests and release documentation for npm package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integration Tests: - Comprehensive test suite covering all major functionality - 5 test scenarios: installation, binary functionality, workflow, Claude Code for Web simulation, platform detection - Tests JSONL import/export across sessions - Tests all major commands (init, create, list, show, update, close, ready) - All tests passing ✅ Testing Documentation: - TESTING.md with complete test documentation - Describes unit vs integration tests - Manual testing scenarios - CI/CD recommendations - Troubleshooting guide Release Documentation: - RELEASING.md with comprehensive release process - Covers all distribution channels: GitHub, Homebrew, PyPI, npm - Step-by-step instructions for each channel - Version numbering and release cadence - Hotfix and rollback procedures - Automation opportunities with GitHub Actions npm Package Updates: - Added test:integration and test:all scripts - Integration tests validate real-world usage patterns - Tests simulate Claude Code for Web SessionStart hooks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .beads/beads.jsonl | 6 +- RELEASING.md | 609 +++++++++++++++++++++++++++ npm-package/TESTING.md | 356 ++++++++++++++++ npm-package/package.json | 4 +- npm-package/test/integration.test.js | 502 ++++++++++++++++++++++ 5 files changed, 1473 insertions(+), 4 deletions(-) create mode 100644 RELEASING.md create mode 100644 npm-package/TESTING.md create mode 100755 npm-package/test/integration.test.js diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl index 5619869d..df7b091e 100644 --- a/.beads/beads.jsonl +++ b/.beads/beads.jsonl @@ -32,7 +32,7 @@ {"id":"bd-1f4086c5","content_hash":"23fbff5ec79ea76cf9c60b64676ee446445c878e3cc17011b925a1ec167142c5","title":"Event-driven daemon architecture","description":"Replace 5-second polling sync loop with event-driven architecture that reacts instantly to changes. Eliminates stale data issues while reducing CPU ~60%. Key components: FileWatcher (fsnotify), Debouncer (500ms), RPC mutation events, optional git hooks. Target latency: \u003c500ms (vs 5000ms). See event_driven_daemon.md for full design.","notes":"Production-ready after 3 critical fixes (commit 349b892):\n- Skip redundant imports (mtime check prevents self-trigger loops)\n- Add server.Stop() in serverErrChan case (clean shutdown)\n- Fallback ticker (60s) when watcher unavailable (ensures remote sync)\n\nReady to make default after integration test (bd-1f4086c5.1) passes.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-10-29T23:05:13.969484-07:00","updated_at":"2025-10-31T20:21:25.464736-07:00","closed_at":"2025-10-31T20:21:25.464736-07:00"} {"id":"bd-1f4086c5.1","content_hash":"ba5173c61613a29786641ba06a93427de87bed65ce39dbc3c3ddd2b6900f827e","title":"Integration test: mutation to export latency","description":"Measure time from bd create to JSONL update. Verify \u003c500ms latency. Test with multiple rapid mutations to verify batching.","notes":"Test added to daemon_test.go as TestMutationToExportLatency().\n\nCurrently skipped with note that it should be enabled once bd-146 (event-driven daemon) is fully implemented and enabled by default.\n\nThe test structure is complete:\n1. Sets up test environment with fast debounce (500ms)\n2. SingleMutationLatency: measures latency from mutation to JSONL update\n3. RapidMutationBatching: verifies multiple mutations batch into single export\n\nOnce event-driven mode is default, remove the t.Skip() line and the test will validate \u003c500ms latency.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T20:49:49.103759-07:00","updated_at":"2025-10-30T17:12:58.195867-07:00","closed_at":"2025-10-29T14:19:19.808139-07:00","dependencies":[{"issue_id":"bd-1f4086c5.1","depends_on_id":"bd-1f4086c5","type":"parent-child","created_at":"2025-10-29T20:49:49.107244-07:00","created_by":"import-remap"}]} {"id":"bd-22e0bde9","content_hash":"4c03fb79e67c0948d0d887b56fcbf71ed3b987e4bfd84628d7b9b2fa047a61fa","title":"Add TestNWayCollision for 5+ clones","description":"## Overview\nAdd comprehensive tests for N-way (5+) collision resolution to verify the solution scales beyond 3 clones.\n\n## Purpose\nWhile TestThreeCloneCollision validates the basic N-way case, we need to verify:\n1. Solution scales to arbitrary N\n2. Performance is acceptable with more clones\n3. Convergence time is bounded\n4. No edge cases in larger collision groups\n\n## Implementation Tasks\n\n### 1. Create TestFiveCloneCollision\nFile: beads_twoclone_test.go (or new beads_nway_test.go)\n\n```go\nfunc TestFiveCloneCollision(t *testing.T) {\n // Test with 5 clones creating same ID with different content\n // Verify all 5 clones converge after sync rounds\n \n t.Run(\"SequentialSync\", func(t *testing.T) {\n testNCloneCollision(t, 5, \"A\", \"B\", \"C\", \"D\", \"E\")\n })\n \n t.Run(\"ReverseSync\", func(t *testing.T) {\n testNCloneCollision(t, 5, \"E\", \"D\", \"C\", \"B\", \"A\")\n })\n \n t.Run(\"RandomSync\", func(t *testing.T) {\n testNCloneCollision(t, 5, \"C\", \"A\", \"E\", \"B\", \"D\")\n })\n}\n```\n\n### 2. Implement generalized testNCloneCollision\nGeneralize the 3-clone test to handle arbitrary N:\n\n```go\nfunc testNCloneCollision(t *testing.T, numClones int, syncOrder ...string) {\n t.Helper()\n \n if len(syncOrder) != numClones {\n t.Fatalf(\"syncOrder length (%d) must match numClones (%d)\", \n len(syncOrder), numClones)\n }\n \n tmpDir := t.TempDir()\n \n // Setup remote and N clones\n remoteDir := setupBareRepo(t, tmpDir)\n cloneDirs := make(map[string]string)\n \n for i := 0; i \u003c numClones; i++ {\n name := string(rune('A' + i))\n cloneDirs[name] = setupClone(t, tmpDir, remoteDir, name)\n }\n \n // Each clone creates issue with same ID but different content\n for name, dir := range cloneDirs {\n createIssue(t, dir, fmt.Sprintf(\"Issue from clone %s\", name))\n }\n \n // Sync in specified order\n for _, name := range syncOrder {\n syncClone(t, cloneDirs[name], name)\n }\n \n // Final pull for convergence\n for name, dir := range cloneDirs {\n finalPull(t, dir, name)\n }\n \n // Verify all clones have all N issues\n expectedTitles := make(map[string]bool)\n for i := 0; i \u003c numClones; i++ {\n name := string(rune('A' + i))\n expectedTitles[fmt.Sprintf(\"Issue from clone %s\", name)] = true\n }\n \n for name, dir := range cloneDirs {\n titles := getTitles(t, dir)\n if !compareTitleSets(titles, expectedTitles) {\n t.Errorf(\"Clone %s missing issues: expected %v, got %v\", \n name, expectedTitles, titles)\n }\n }\n \n t.Log(\"✓ All\", numClones, \"clones converged successfully\")\n}\n```\n\n### 3. Add performance benchmarks\nTest convergence time and memory usage:\n\n```go\nfunc BenchmarkNWayCollision(b *testing.B) {\n for _, n := range []int{3, 5, 10, 20} {\n b.Run(fmt.Sprintf(\"N=%d\", n), func(b *testing.B) {\n for i := 0; i \u003c b.N; i++ {\n // Run N-way collision and measure time\n testNCloneCollisionBench(b, n)\n }\n })\n }\n}\n```\n\n### 4. Add convergence time tests\nVerify bounded convergence:\n\n```go\nfunc TestConvergenceTime(t *testing.T) {\n // Test that convergence happens within expected rounds\n // For N clones, should converge in at most N-1 sync rounds\n \n for n := 3; n \u003c= 10; n++ {\n t.Run(fmt.Sprintf(\"N=%d\", n), func(t *testing.T) {\n rounds := measureConvergenceRounds(t, n)\n maxExpected := n - 1\n if rounds \u003e maxExpected {\n t.Errorf(\"Convergence took %d rounds, expected ≤ %d\", \n rounds, maxExpected)\n }\n })\n }\n}\n```\n\n### 5. Add edge case tests\nTest boundary conditions:\n- All N clones have identical content (dedup works)\n- N-1 clones have same content, 1 differs\n- All N clones have unique content\n- Mix of collisions and non-collisions\n\n## Acceptance Criteria\n- TestFiveCloneCollision passes with all sync orders\n- All 5 clones converge to identical content\n- Performance is acceptable (\u003c 5 seconds for 5 clones)\n- Convergence time is bounded (≤ N-1 rounds)\n- Edge cases handled correctly\n- Benchmarks show scalability to 10+ clones\n\n## Files to Create/Modify\n- beads_twoclone_test.go or beads_nway_test.go\n- Add helper functions for N-clone setup\n\n## Testing Strategy\n\n### Test Matrix\n| N Clones | Sync Orders | Expected Result |\n|----------|-------------|-----------------|\n| 3 | A→B→C | Pass |\n| 3 | C→B→A | Pass |\n| 5 | A→B→C→D→E | Pass |\n| 5 | E→D→C→B→A | Pass |\n| 5 | Random | Pass |\n| 10 | Sequential | Pass |\n\n### Performance Targets\n- 3 clones: \u003c 2 seconds\n- 5 clones: \u003c 5 seconds\n- 10 clones: \u003c 15 seconds\n\n## Dependencies\n- Requires bd-cbed9619.5, bd-cbed9619.4, bd-cbed9619.3, bd-cbed9619.2 to be completed\n- TestThreeCloneCollision must pass first\n\n## Success Metrics\n- All tests pass for N ∈ {3, 5, 10}\n- Convergence time scales linearly (O(N))\n- Memory usage reasonable (\u003c 100MB for 10 clones)\n- No data corruption or loss in any scenario","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-29T23:05:13.974702-07:00","updated_at":"2025-10-31T12:00:43.197709-07:00","closed_at":"2025-10-31T12:00:43.197709-07:00"} -{"id":"bd-23a8","content_hash":"9d87b72ef47f258495d3a0d5ff4f1675400ec56324065da5b71902e1dbc8fe72","title":"Test simple issue","description":"","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-02T17:11:04.464726-08:00","updated_at":"2025-11-02T17:11:04.464726-08:00","comments":[{"id":1,"issue_id":"bd-23a8","author":"stevey","text":"Testing the new bd comment alias!","created_at":"2025-11-03T02:41:01Z"},{"id":2,"issue_id":"bd-23a8","author":"stevey","text":"Another test with JSON output","created_at":"2025-11-03T02:41:01Z"},{"id":3,"issue_id":"bd-23a8","author":"stevey","text":"Test comment from file\n","created_at":"2025-11-03T02:41:01Z"}]} +{"id":"bd-23a8","content_hash":"9d87b72ef47f258495d3a0d5ff4f1675400ec56324065da5b71902e1dbc8fe72","title":"Test simple issue","description":"","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-02T17:11:04.464726-08:00","updated_at":"2025-11-02T17:11:04.464726-08:00"} {"id":"bd-248bdc3e","content_hash":"8eaeb2dbef1ed6b25fc1bcf3bc5cd1b38a5cf5a487772558ba9fe12a149978f3","title":"Add optional post-merge git hook example for bd sync","description":"Create example git hook that auto-runs bd sync after git pull/merge.\n\nAdd to examples/git-hooks/:\n- post-merge hook that checks if .beads/issues.jsonl changed\n- If changed: run `bd sync` automatically\n- Make it optional/documented (not auto-installed)\n\nBenefits:\n- Zero-friction sync after git pull\n- Complements auto-detection as belt-and-suspenders\n\nNote: post-merge hook already exists for pre-commit/post-merge. Extend it to support sync.","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-25T22:47:14.668842-07:00","updated_at":"2025-10-30T17:12:58.218887-07:00"} {"id":"bd-2530","content_hash":"7056a386ee4802bce2b83a981aaac7858b5911938263d212f8f9d1f60bf2a706","title":"Issue with labels","description":"This is a description","design":"Use MVC pattern","acceptance_criteria":"All tests pass","status":"closed","priority":0,"issue_type":"feature","created_at":"2025-10-31T21:40:34.630173-07:00","updated_at":"2025-11-01T11:11:57.93151-07:00","closed_at":"2025-11-01T11:11:57.93151-07:00","labels":["bug","critical"]} {"id":"bd-2752a7a2","content_hash":"6b2a1aedbdbcb30b98d4a8196801953a1eb22204d63e31954ef9ab6020a7a26b","title":"Create cmd/bd/daemon_watcher.go (~150 LOC)","description":"Implement FileWatcher using fsnotify to watch JSONL file and git refs. Handle platform differences (inotify/FSEvents/ReadDirectoryChangesW). Include edge case handling for file rename, event storm, watcher failure.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-29T23:05:13.887269-07:00","updated_at":"2025-10-31T18:30:24.131535-07:00","closed_at":"2025-10-31T18:30:24.131535-07:00"} @@ -96,7 +96,7 @@ {"id":"bd-5e1f","content_hash":"5b0aa7a2f651393bc13c46c172828acc4306d22d749ff71fbae96f0d25741847","title":"Issue with desc","description":"This is a description","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-10-31T21:41:11.128718-07:00","updated_at":"2025-11-02T15:58:53.554407-08:00","closed_at":"2025-11-02T15:58:53.55441-08:00"} {"id":"bd-5f26","content_hash":"75bc96be4d465a5eb39bdf0b636c42cdd7b8ac7daf90b47b7b2a015991b87512","title":"Refactor daemon.go into internal/daemonrunner","description":"Extract daemon runtime from daemon.go (1,565 lines) into internal/daemonrunner with focused modules: config.go, daemon.go, process.go, rpc_server.go, sync.go, git.go. Keep cobra command thin.","design":"New structure:\n- internal/daemonrunner/config.go: Config struct\n- internal/daemonrunner/daemon.go: Daemon struct + Start/Stop\n- internal/daemonrunner/process.go: PID/lock/socket handling\n- internal/daemonrunner/rpc_server.go: RPC lifecycle\n- internal/daemonrunner/sync.go: Export/import/commit/push logic\n- internal/daemonrunner/git.go: Git operations interface\n- cmd/bd/daemon.go: Thin cobra command","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-11-01T11:41:14.821017-07:00","updated_at":"2025-11-01T21:44:44.507747-07:00","closed_at":"2025-11-01T21:44:44.507747-07:00"} {"id":"bd-5f483051","content_hash":"d69f64f7f0bdc46a539dfe0b699a8977309c9c8d59f3e9beffbbe4484275a16b","title":"Implement bd resolve-conflicts (git merge conflicts in JSONL)","description":"Automatically detect and resolve git merge conflicts in .beads/issues.jsonl file.\n\nFeatures:\n- Detect conflict markers in JSONL\n- Parse conflicting issues from HEAD and BASE\n- Provide mechanical resolution (remap duplicate IDs)\n- Support AI-assisted resolution (requires internal/ai package)\n\nSee repair_commands.md lines 125-353 for design.","status":"open","priority":1,"issue_type":"task","created_at":"2025-10-28T19:37:55.722827-07:00","updated_at":"2025-10-30T17:12:58.179718-07:00"} -{"id":"bd-6049","content_hash":"16c54bc547f4ab180aee39efbb197709a47a39047f5bc2dd59e6e6b57ca8bc87","title":"bd doctor --json flag not working","description":"The --json flag on bd doctor command doesn't produce JSON output. It continues to show human-readable output instead. The flag is registered locally on doctorCmd but the code uses the global jsonOutput variable set by PersistentPreRun. Need to investigate why the flag isn't being honored.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-02T17:08:18.170428-08:00","updated_at":"2025-11-02T18:41:01.376783-08:00","closed_at":"2025-11-02T18:41:01.376786-08:00","comments":[{"id":4,"issue_id":"bd-6049","author":"stevey","text":"Fixed by removing the local --json flag definition in doctor.go that was shadowing the persistent --json flag from main.go. The doctor command now correctly uses the global jsonOutput variable.","created_at":"2025-11-03T02:41:01Z"}]} +{"id":"bd-6049","content_hash":"16c54bc547f4ab180aee39efbb197709a47a39047f5bc2dd59e6e6b57ca8bc87","title":"bd doctor --json flag not working","description":"The --json flag on bd doctor command doesn't produce JSON output. It continues to show human-readable output instead. The flag is registered locally on doctorCmd but the code uses the global jsonOutput variable set by PersistentPreRun. Need to investigate why the flag isn't being honored.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-11-02T17:08:18.170428-08:00","updated_at":"2025-11-02T18:41:01.376783-08:00","closed_at":"2025-11-02T18:41:01.376786-08:00"} {"id":"bd-6214875c","content_hash":"d4d20e71bbf5c08f1fe1ed07f67b7554167aa165d4972ea51b5cacc1b256c4c1","title":"Split internal/rpc/server.go into focused modules","description":"The file `internal/rpc/server.go` is 2,273 lines with 50+ methods, making it difficult to navigate and prone to merge conflicts. Split into 8 focused files with clear responsibilities.\n\nCurrent structure: Single 2,273-line file with:\n- Connection handling\n- Request routing\n- All 40+ RPC method implementations\n- Storage caching\n- Health checks \u0026 metrics\n- Cleanup loops\n\nTarget structure:\n```\ninternal/rpc/\n├── server.go # Core server, connection handling (~300 lines)\n├── methods_issue.go # Issue operations (~400 lines)\n├── methods_deps.go # Dependency operations (~200 lines)\n├── methods_labels.go # Label operations (~150 lines)\n├── methods_ready.go # Ready work queries (~150 lines)\n├── methods_compact.go # Compaction operations (~200 lines)\n├── methods_comments.go # Comment operations (~150 lines)\n├── storage_cache.go # Storage caching logic (~300 lines)\n└── health.go # Health \u0026 metrics (~200 lines)\n```\n\nMigration strategy:\n1. Create new files with appropriate methods\n2. Keep `server.go` as main file with core server logic\n3. Test incrementally after each file split\n4. Final verification with full test suite","acceptance_criteria":"- All 50 methods split into appropriate files\n- Each file \u003c500 LOC\n- All methods remain on `*Server` receiver (no behavior change)\n- All tests pass: `go test ./internal/rpc/...`\n- Verify daemon works: start daemon, run operations, check health\n- Update internal documentation if needed\n- No change to public API","status":"closed","priority":1,"issue_type":"task","created_at":"2025-10-28T14:21:37.51524-07:00","updated_at":"2025-10-30T17:12:58.2179-07:00","closed_at":"2025-10-28T14:11:04.399811-07:00"} {"id":"bd-6221bdcd","content_hash":"3bf15bc9e418180e1e91691261817c872330e182dbc1bcb756522faa42416667","title":"Improve cmd/bd test coverage (currently 20.2%)","description":"CLI commands need better test coverage. Focus on:\n- Command argument parsing\n- Error handling paths\n- Edge cases in create, update, close commands\n- Daemon commands\n- Import/export workflows","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-29T14:06:27.951656-07:00","updated_at":"2025-10-30T17:12:58.185819-07:00","dependencies":[{"issue_id":"bd-6221bdcd","depends_on_id":"bd-4d7fca8a","type":"blocks","created_at":"2025-10-29T19:52:05.532391-07:00","created_by":"import-remap"}]} {"id":"bd-627d","content_hash":"5b3d3d69ceac28dcbfbc2c7ea2f7a6ff2a3a02bc58ce02dcf6b05f8469e8bddc","title":"AI-supervised database migrations for safer schema evolution","description":"## Problem\n\nDatabase migrations can lose user data through edge cases that are hard to anticipate (e.g., GH #201 where bd migrate failed to set issue_prefix, or bd-d355a07d false positive data loss warnings). Since beads is designed to be run by AI agents, we should leverage AI to make migrations safer.\n\n## Current State\n\nMigrations run blindly with:\n- No pre-flight validation\n- No data integrity verification\n- No rollback on failure\n- Limited post-migration testing\n\nRecent issues:\n- GH #201: Migration didn't set issue_prefix config, breaking commands\n- bd-d355a07d: False positive \"data loss\" warnings on collision resolution\n- Users reported migration data loss (fixed but broader problem remains)\n\n## Proposal: AI-Supervised Migration Framework\n\nUse AI to supervise migrations through structured verification:\n\n### 1. Pre-Migration Analysis\n- AI reads migration code and current schema\n- Identifies potential data loss scenarios\n- Generates validation queries to verify assumptions\n- Creates snapshot queries for before/after comparison\n\n### 2. Migration Execution\n- Take database backup/snapshot\n- Run validation queries (pre-state)\n- Execute migration in transaction\n- Run validation queries (post-state)\n\n### 3. Post-Migration Verification\n- AI compares pre/post snapshots\n- Verifies data integrity invariants\n- Checks for unexpected data loss\n- Validates config completeness (like issue_prefix)\n\n### 4. Rollback on Anomalies\n- If AI detects data loss, rollback transaction\n- Present human-readable error report\n- Suggest fix before retrying\n\n## Example Flow\n\n```\n$ bd migrate\n\n→ Analyzing migration plan...\n→ AI identified 3 potential data loss scenarios\n→ Generating validation queries...\n→ Creating pre-migration snapshot...\n→ Running migration in transaction...\n→ Verifying post-migration state...\n✓ All 247 issues accounted for\n✓ Config table complete (issue_prefix: \"mcp\")\n✓ Dependencies intact (342 relationships verified)\n→ Migration successful!\n```\n\nIf something goes wrong:\n```\n$ bd migrate\n\n→ Analyzing migration plan...\n→ AI identified issue: Missing issue_prefix config after migration\n→ Recommendation: Add prefix detection step\n→ Aborting migration - database unchanged\n```\n\n## Implementation Ideas\n\n### A. Migration Validator Tool\nCreate `bd migrate --validate` that:\n- Simulates migration on copy of database\n- Uses AI to verify data integrity\n- Reports potential issues before real migration\n\n### B. Migration Test Generator\nAI generates test cases for migrations:\n- Edge cases (empty DB, large DB, missing config)\n- Data integrity checks\n- Regression tests\n\n### C. Migration Invariants\nDefine invariants that AI checks:\n- Issue count should not decrease (unless collision resolution)\n- All required config keys present\n- Foreign key relationships intact\n- No orphaned dependencies\n\n### D. Self-Healing Migrations\nAI detects incomplete migrations and suggests fixes:\n- Missing config values (like GH #201)\n- Orphaned data\n- Index inconsistencies\n\n## Benefits\n\n1. **Catch edge cases**: AI explores scenarios humans miss\n2. **Self-documenting**: AI explains what migration does\n3. **Agent-friendly**: Agents can run migrations confidently\n4. **Fewer rollbacks**: Detect issues before committing\n5. **Better testing**: AI generates comprehensive test suites\n\n## Open Questions\n\n1. Which AI model? (Fast: Haiku, Thorough: Sonnet/GPT-4)\n2. How to balance safety vs migration speed?\n3. Should AI validation be required or optional?\n4. How to handle offline scenarios (no API access)?\n5. What invariants should always be checked?\n\n## Related Work\n\n- bd-b245: Migration registry (makes migrations introspectable)\n- GH #201: issue_prefix migration bug (motivating example)\n- bd-d355a07d: False positive data loss warnings","design":"## Architecture: Agent-Supervised Migrations (Inversion of Control)\n\n**Key principle:** Beads provides observability and validation primitives. AI agents supervise using their own reasoning. Beads NEVER makes AI API calls.\n\n## Phase 1: Migration Invariants (Pure Validation)\n\nCreate `internal/storage/sqlite/migration_invariants.go`:\n\n```go\ntype MigrationInvariant struct {\n Name string\n Description string\n Check func(*sql.DB, *Snapshot) error\n}\n\ntype Snapshot struct {\n IssueCount int\n ConfigKeys []string\n DependencyCount int\n LabelCount int\n}\n\nvar invariants = []MigrationInvariant{\n {\n Name: \"required_config_present\",\n Description: \"Required config keys must exist\",\n Check: checkRequiredConfig, // Would have caught GH #201\n },\n {\n Name: \"foreign_keys_valid\",\n Description: \"No orphaned dependencies or labels\",\n Check: checkForeignKeys,\n },\n {\n Name: \"issue_count_stable\",\n Description: \"Issue count should not decrease unexpectedly\",\n Check: checkIssueCount,\n },\n}\n\nfunc checkRequiredConfig(db *sql.DB, snapshot *Snapshot) error {\n required := []string{\"issue_prefix\", \"schema_version\"}\n for _, key := range required {\n var value string\n err := db.QueryRow(\"SELECT value FROM config WHERE key = ?\", key).Scan(\u0026value)\n if err != nil || value == \"\" {\n return fmt.Errorf(\"required config key missing: %s\", key)\n }\n }\n return nil\n}\n```\n\n## Phase 2: Dry-Run \u0026 Inspection Tools\n\nAdd `bd migrate --dry-run --json`:\n\n```json\n{\n \"pending_migrations\": [\n {\"name\": \"dirty_issues_table\", \"description\": \"Adds dirty_issues table\"},\n {\"name\": \"content_hash_column\", \"description\": \"Adds content_hash for collision resolution\"}\n ],\n \"current_state\": {\n \"schema_version\": \"0.9.9\",\n \"issue_count\": 247,\n \"config\": {\"schema_version\": \"0.9.9\"},\n \"missing_config\": [\"issue_prefix\"]\n },\n \"warnings\": [\n \"issue_prefix config not set - may break commands after migration\"\n ],\n \"invariants_to_check\": [\n \"required_config_present\",\n \"foreign_keys_valid\",\n \"issue_count_stable\"\n ]\n}\n```\n\nAdd `bd info --schema --json`:\n\n```json\n{\n \"tables\": [\"issues\", \"dependencies\", \"labels\", \"config\"],\n \"schema_version\": \"0.9.9\",\n \"config\": {},\n \"sample_issue_ids\": [\"mcp-1\", \"mcp-2\"],\n \"detected_prefix\": \"mcp\"\n}\n```\n\n## Phase 3: Pre/Post Snapshots with Rollback\n\nUpdate `RunMigrations()`:\n\n```go\nfunc RunMigrations(db *sql.DB) error {\n // Capture pre-migration snapshot\n snapshot := captureSnapshot(db)\n \n // Run migrations in transaction\n tx, err := db.Begin()\n if err != nil {\n return err\n }\n defer tx.Rollback()\n \n for _, migration := range migrations {\n if err := migration.Func(tx); err != nil {\n return fmt.Errorf(\"migration %s failed: %w\", migration.Name, err)\n }\n }\n \n // Verify invariants before commit\n if err := verifyInvariants(tx, snapshot); err != nil {\n return fmt.Errorf(\"post-migration validation failed (rolled back): %w\", err)\n }\n \n return tx.Commit()\n}\n```\n\n## Phase 4: MCP Tools for Agent Supervision\n\nAdd to beads-mcp:\n\n```python\n@server.tool()\nasync def inspect_migration(workspace_root: str) -\u003e dict:\n \"\"\"Get migration plan and current state for agent analysis.\n \n Agent should:\n 1. Review pending migrations\n 2. Check for warnings (missing config, etc.)\n 3. Verify invariants will pass\n 4. Decide whether to run bd migrate\n \"\"\"\n result = run_bd([\"migrate\", \"--dry-run\", \"--json\"], workspace_root)\n return json.loads(result.stdout)\n\n@server.tool() \nasync def get_schema_info(workspace_root: str) -\u003e dict:\n \"\"\"Get current database schema for migration analysis.\"\"\"\n result = run_bd([\"info\", \"--schema\", \"--json\"], workspace_root)\n return json.loads(result.stdout)\n```\n\n## Agent Workflow Example\n\n```python\n# Agent detects user wants to migrate\nmigration_plan = inspect_migration(\"/path/to/workspace\")\n\n# Agent analyzes (using its own reasoning, no API calls from beads)\nif \"issue_prefix\" in migration_plan[\"missing_config\"]:\n schema = get_schema_info(\"/path/to/workspace\")\n detected_prefix = schema[\"detected_prefix\"]\n \n # Agent fixes issue before migration\n run_bd([\"config\", \"set\", \"issue_prefix\", detected_prefix])\n \n# Now safe to migrate\nrun_bd([\"migrate\"])\n```\n\n## What Beads Provides\n\n✅ Deterministic validation (invariants)\n✅ Structured inspection (--dry-run, --explain)\n✅ Rollback on invariant failure\n✅ JSON output for agent parsing\n\n## What Beads Does NOT Do\n\n❌ No AI API calls\n❌ No external model access\n❌ No agent invocation\n\nAgents supervise migrations using their own reasoning and the inspection tools beads provides.","acceptance_criteria":"Phase 1: Migration invariants implemented and tested, checked after every migration, clear error messages when invariants fail.\n\nPhase 2: Snapshot capture before migrations, comparison after, rollback on verification failure.\n\nPhase 3 (stretch): AI validation optional flag implemented, AI can analyze migration code and generate custom validation queries.\n\nPhase 4 (stretch): Migration test fixtures created, all fixtures pass migrations, CI runs migration tests.","notes":"## Progress\n\n### ✅ Phase 1: Migration Invariants (COMPLETED)\n\n**Implemented:**\n- Created internal/storage/sqlite/migration_invariants.go with 3 invariants\n- Updated RunMigrations() to verify invariants after migrations\n- All tests pass ✓\n\n### ✅ Phase 2: Inspection Tools (COMPLETED \u0026 PUSHED)\n\n**Commit:** 1abe4e7 - \"Add migration inspection tools for AI agents (bd-627d Phase 2)\"\n\n**Implemented:**\n1. ✅ bd migrate --inspect --json - Shows migration plan\n2. ✅ bd info --schema --json - Returns schema details\n3. ✅ Migration warnings system\n4. ✅ Documentation updated in AGENTS.md\n5. ✅ All tests pass\n\n### ✅ Phase 3: MCP Tools (COMPLETED \u0026 PUSHED)\n\n**Commit:** 2493693 - \"Add MCP tools for migration inspection (bd-627d Phase 3)\"\n\n**Implemented:**\n1. ✅ inspect_migration(workspace_root) tool in beads-mcp\n2. ✅ get_schema_info(workspace_root) tool in beads-mcp\n3. ✅ Abstract methods in BdClientBase\n4. ✅ CLI client implementations\n5. ✅ All tests pass\n\n**All phases complete!** Migration inspection fully integrated into MCP server.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-11-02T12:57:10.722048-08:00","updated_at":"2025-11-02T14:31:25.095296-08:00","closed_at":"2025-11-02T14:31:25.095308-08:00"} @@ -139,7 +139,7 @@ {"id":"bd-8534","content_hash":"001e0ffe353115fbbaad39f392208ddd8ceaa515cdbab9f616a85b63b45f0d0e","title":"Switch from modernc.org/sqlite to ncruces/go-sqlite3 for WASM support","description":"modernc.org/sqlite depends on modernc.org/libc which has no js/wasm support (platform-specific syscalls). Need to switch to ncruces/go-sqlite3 which wraps a WASM build of SQLite using wazero runtime.\n\nKey differences:\n- ncruces/go-sqlite3: Uses WASM build of SQLite + wazero runtime\n- modernc.org/sqlite: Pure Go translation, requires libc for syscalls\n\nThis is a prerequisite for bd-62a0 (WASM build infrastructure).","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-02T22:14:27.627154-08:00","updated_at":"2025-11-02T22:23:49.377223-08:00","closed_at":"2025-11-02T22:23:49.377223-08:00","dependencies":[{"issue_id":"bd-8534","depends_on_id":"bd-44d0","type":"parent-child","created_at":"2025-11-02T22:23:49.555691-08:00","created_by":"stevey"}]} {"id":"bd-85487065","content_hash":"637cbd56af122b175ff060b4df050871fe86124c5d883ba7f8a17f2f95479613","title":"Add tests for internal/autoimport package","description":"Currently 0.0% coverage. Need tests for auto-import functionality that detects and imports updated JSONL files.","status":"open","priority":2,"issue_type":"task","created_at":"2025-10-29T14:06:18.154805-07:00","updated_at":"2025-10-30T17:12:58.182987-07:00"} {"id":"bd-879d","content_hash":"2f291ca2adead5ee3fb7fade39c088165b5467780599f9a719dcaebc87455ae3","title":"Test issue 1","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-02T09:44:12.538697729Z","updated_at":"2025-11-02T09:45:20.76214671Z","closed_at":"2025-11-02T09:45:20.76214671Z","dependencies":[{"issue_id":"bd-879d","depends_on_id":"bd-d3e5","type":"discovered-from","created_at":"2025-11-02T09:44:22.103468321Z","created_by":"mrdavidlaing"}]} -{"id":"bd-87a0","content_hash":"81caf8d588f32637faa7ad9b38456574e0643be36954ca5e61d3f5d5388387b2","title":"Publish @beads/bd package to npm registry","description":"Publish the npm package to the public npm registry:\n\n## Prerequisites\n- npm account created\n- Organization @beads created (or use different namespace)\n- npm login completed locally\n- Package tested locally (bd-f282 completed)\n\n## Publishing steps\n1. Verify package.json version matches current bd version\n2. Run npm pack and inspect tarball contents\n3. Test installation from tarball one more time\n4. Run npm publish --access public\n5. Verify package appears on https://www.npmjs.com/package/@beads/bd\n6. Test installation from registry: npm install -g @beads/bd\n\n## Post-publish\n- Add npm badge to README.md\n- Update CHANGELOG.md with npm package release\n- Announce in release notes\n\n## Note\n- May need to choose different name if @beads namespace unavailable\n- Alternative: beads-cli, bd-cli, or unscoped beads-issue-tracker","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-02T23:40:25.263569-08:00","updated_at":"2025-11-03T10:39:41.772338-08:00","closed_at":"2025-11-03T10:39:41.772338-08:00","dependencies":[{"issue_id":"bd-87a0","depends_on_id":"bd-febc","type":"parent-child","created_at":"2025-11-02T23:40:33.014043-08:00","created_by":"daemon"}],"comments":[{"id":5,"issue_id":"bd-87a0","author":"stevey","text":"Package is ready to publish. All code complete and tested locally. Next steps: 1) npm login, 2) create @beads org if needed, 3) npm publish --access public. See npm-package/PUBLISHING.md for complete instructions.","created_at":"2025-11-03T18:34:10Z"}]} +{"id":"bd-87a0","content_hash":"81caf8d588f32637faa7ad9b38456574e0643be36954ca5e61d3f5d5388387b2","title":"Publish @beads/bd package to npm registry","description":"Publish the npm package to the public npm registry:\n\n## Prerequisites\n- npm account created\n- Organization @beads created (or use different namespace)\n- npm login completed locally\n- Package tested locally (bd-f282 completed)\n\n## Publishing steps\n1. Verify package.json version matches current bd version\n2. Run npm pack and inspect tarball contents\n3. Test installation from tarball one more time\n4. Run npm publish --access public\n5. Verify package appears on https://www.npmjs.com/package/@beads/bd\n6. Test installation from registry: npm install -g @beads/bd\n\n## Post-publish\n- Add npm badge to README.md\n- Update CHANGELOG.md with npm package release\n- Announce in release notes\n\n## Note\n- May need to choose different name if @beads namespace unavailable\n- Alternative: beads-cli, bd-cli, or unscoped beads-issue-tracker","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-02T23:40:25.263569-08:00","updated_at":"2025-11-03T10:39:41.772338-08:00","closed_at":"2025-11-03T10:39:41.772338-08:00","dependencies":[{"issue_id":"bd-87a0","depends_on_id":"bd-febc","type":"parent-child","created_at":"2025-11-02T23:40:33.014043-08:00","created_by":"daemon"}]} {"id":"bd-8900f145","content_hash":"4a07f36a9e5d24aaffb092c89e2273cb58f9de357d24eeb01fcde6a4079ba775","title":"Testing event-driven mode!","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-10-29T15:28:33.564871-07:00","updated_at":"2025-10-30T17:12:58.186325-07:00","closed_at":"2025-10-29T19:12:54.43368-07:00"} {"id":"bd-8931","content_hash":"2704ca79397fd2db14741b466d18346110d6c054dfdb4d69331941c33227df7c","title":"Daemon gets stuck when auto-import blocked by git conflicts","description":"CRITICAL: The daemon enters a corrupt state that breaks RPC commands when auto-import is triggered but git pull fails due to uncommitted changes.\n\nImpact: This is a data integrity and usability issue that could cause users to lose trust in Beads. The daemon silently fails for certain commands while appearing healthy.\n\nReproduction:\n1. Make local changes to issues (creates uncommitted .beads/beads.jsonl)\n2. Remote has updates (JSONL newer, triggers auto-import)\n3. Daemon tries to pull but fails: 'cannot pull with rebase: You have unstaged changes'\n4. Daemon enters bad state - 'bd show' and other commands return EOF\n5. 'bd list' still works, daemon process is running, no errors logged\n\nTechnical details:\n- Auto-import check runs in handleRequest() before processing RPC commands\n- When import is blocked, it appears to corrupt daemon state\n- Likely: deadlock, unclosed transaction, or storage handle corruption\n- Panic recovery (server_lifecycle_conn.go:183) didn't catch anything - not a panic\n\nRequired fix:\n- Auto-import must not block RPC command execution\n- Handle git pull failures gracefully without corrupting state\n- Consider: skip auto-import if git is dirty, queue import for later, or use separate goroutine\n- Add timeout/circuit breaker for import operations\n- Log clear warnings when auto-import is skipped\n\nWithout this fix, users in collaborative environments will frequently encounter mysterious EOF errors that require daemon restarts.","design":"Options to fix:\n\n1. Skip auto-import when git is dirty (safest, simplest)\n - Check git status before pull\n - Log warning and continue without import\n - User must manually import after cleaning git state\n\n2. Async import with timeout (better UX)\n - Run auto-import in background goroutine\n - Don't block RPC command execution\n - Timeout after 5s, log error if stuck\n - Use sync.Once or similar to prevent concurrent imports\n\n3. Transactional import with rollback\n - Wrap import in database transaction\n - Rollback if git operations fail\n - Ensure storage is never left in bad state\n\nRecommended: Combine #1 and #2\n- Check git status first, skip if dirty\n- If clean, do async import with timeout\n- Add metrics to track import success/failure rates","status":"open","priority":0,"issue_type":"bug","created_at":"2025-11-02T17:15:25.181425-08:00","updated_at":"2025-11-02T17:15:25.181425-08:00","dependencies":[{"issue_id":"bd-8931","depends_on_id":"bd-1048","type":"blocks","created_at":"2025-11-02T17:15:25.181857-08:00","created_by":"stevey"}]} {"id":"bd-897a","content_hash":"ae488407bf5e71242535f4c35b59b0981d2b8b338d1701f19acba2c8e93049f0","title":"Add UNIQUE constraint on external_ref column","description":"The external_ref column should have a UNIQUE constraint to prevent multiple issues from having the same external reference. This ensures data integrity when syncing from external systems (Jira, GitHub, Linear).\n\nCurrent behavior:\n- Multiple issues can have the same external_ref\n- GetIssueByExternalRef returns first match (non-deterministic with duplicates)\n\nProposed solution:\n- Add UNIQUE constraint to external_ref column\n- Add migration to check for and resolve existing duplicates\n- Update tests to verify constraint enforcement\n\nRelated: bd-1022","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-02T15:31:54.718005-08:00","updated_at":"2025-11-02T17:19:13.18726-08:00","closed_at":"2025-11-02T17:19:13.187262-08:00"} diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..3377b66f --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,609 @@ +# Release Process for Beads + +This document describes the complete release process for beads, including GitHub releases, Homebrew, PyPI (MCP server), and npm packages. + +## Table of Contents + +- [Overview](#overview) +- [Prerequisites](#prerequisites) +- [Release Checklist](#release-checklist) +- [1. Prepare Release](#1-prepare-release) +- [2. GitHub Release](#2-github-release) +- [3. Homebrew Update](#3-homebrew-update) +- [4. PyPI Release (MCP Server)](#4-pypi-release-mcp-server) +- [5. npm Package Release](#5-npm-package-release) +- [6. Verify Release](#6-verify-release) +- [Hotfix Releases](#hotfix-releases) +- [Rollback Procedure](#rollback-procedure) + +## Overview + +A beads release involves multiple distribution channels: + +1. **GitHub Release** - Binary downloads for all platforms +2. **Homebrew** - macOS/Linux package manager +3. **PyPI** - Python MCP server (`beads-mcp`) +4. **npm** - Node.js package for Claude Code for Web (`@beads/bd`) + +## Prerequisites + +### Required Tools + +- `git` with push access to steveyegge/beads +- `goreleaser` for building binaries +- `npm` with authentication (for npm releases) +- `python3` and `twine` (for PyPI releases) +- `gh` CLI (GitHub CLI, optional but recommended) + +### Required Access + +- GitHub: Write access to repository and ability to create releases +- Homebrew: Write access to steveyegge/homebrew-beads +- PyPI: Maintainer access to `beads-mcp` package +- npm: Member of `@beads` organization + +### Verify Setup + +```bash +# Check git +git remote -v # Should show steveyegge/beads + +# Check goreleaser +goreleaser --version + +# Check GitHub CLI (optional) +gh auth status + +# Check npm +npm whoami # Should show your npm username + +# Check Python/twine (for MCP releases) +python3 --version +twine --version +``` + +## Release Checklist + +Before starting a release: + +- [ ] All tests passing (`go test ./...`) +- [ ] npm package tests passing (`cd npm-package && npm run test:all`) +- [ ] CHANGELOG.md updated with changes +- [ ] Version bumped in all locations (use `scripts/bump-version.sh`) +- [ ] No uncommitted changes +- [ ] On `main` branch and up to date with origin + +## 1. Prepare Release + +### Update Version + +Use the version bump script to update all version references: + +```bash +# Dry run - shows what will change +./scripts/bump-version.sh 0.22.0 + +# Review the diff +git diff + +# Commit if it looks good +./scripts/bump-version.sh 0.22.0 --commit +``` + +This updates: +- `cmd/bd/main.go` - CLI version constant +- `integrations/mcp/server/pyproject.toml` - MCP server version +- `npm-package/package.json` - npm package version +- `Formula/bd.rb` - Homebrew formula version +- `.goreleaser.yml` - Release configuration + +### Update CHANGELOG.md + +Add release notes: + +```markdown +## [0.22.0] - 2025-11-04 + +### Added +- New feature X +- New command Y + +### Changed +- Improved performance of Z + +### Fixed +- Bug in component A + +### Breaking Changes +- Changed behavior of B (migration guide) +``` + +### Commit and Tag + +```bash +# Commit version bump and changelog +git add -A +git commit -m "chore: Bump version to 0.22.0" + +# Create annotated tag +git tag -a v0.22.0 -m "Release v0.22.0" + +# Push to GitHub +git push origin main +git push origin v0.22.0 +``` + +## 2. GitHub Release + +### Using GoReleaser (Recommended) + +GoReleaser automates binary building and GitHub release creation: + +```bash +# Clean any previous builds +rm -rf dist/ + +# Create release (requires GITHUB_TOKEN) +export GITHUB_TOKEN="your-github-token" +goreleaser release --clean + +# Or use gh CLI for token +gh auth token | goreleaser release --clean +``` + +This will: +- Build binaries for all platforms (macOS, Linux, Windows - amd64/arm64) +- Create checksums +- Generate release notes from CHANGELOG.md +- Upload everything to GitHub releases +- Mark as latest release + +### Manual Release (Alternative) + +If goreleaser doesn't work: + +```bash +# Build for all platforms +./scripts/build-all-platforms.sh + +# Create GitHub release +gh release create v0.22.0 \ + --title "v0.22.0" \ + --notes-file CHANGELOG.md \ + dist/*.tar.gz \ + dist/*.zip \ + dist/checksums.txt +``` + +### Verify GitHub Release + +1. Visit https://github.com/steveyegge/beads/releases +2. Verify v0.22.0 is marked as "Latest" +3. Check all platform binaries are present: + - `beads_0.22.0_darwin_amd64.tar.gz` + - `beads_0.22.0_darwin_arm64.tar.gz` + - `beads_0.22.0_linux_amd64.tar.gz` + - `beads_0.22.0_linux_arm64.tar.gz` + - `beads_0.22.0_windows_amd64.zip` + - `checksums.txt` + +## 3. Homebrew Update + +Homebrew formula is in a separate tap repository. + +### Automatic Update (If Configured) + +If you have goreleaser configured with Homebrew: + +```bash +# Already done by goreleaser +# Check Formula/bd.rb was updated automatically +``` + +### Manual Update + +```bash +# Clone tap repository +git clone https://github.com/steveyegge/homebrew-beads.git +cd homebrew-beads + +# Update formula +# 1. Update version number +# 2. Update SHA256 checksums for macOS binaries + +# Test formula +brew install --build-from-source ./Formula/bd.rb +bd version # Should show 0.22.0 + +# Commit and push +git add Formula/bd.rb +git commit -m "Update bd to 0.22.0" +git push +``` + +### Verify Homebrew + +```bash +# Update tap +brew update + +# Install new version +brew upgrade bd + +# Verify +bd version # Should show 0.22.0 +``` + +## 4. PyPI Release (MCP Server) + +The MCP server is a Python package published separately to PyPI. + +### Prerequisites + +```bash +# Install build tools +pip install build twine + +# Verify PyPI credentials +cat ~/.pypirc # Should have token or credentials +``` + +### Build and Publish + +```bash +# Navigate to MCP server directory +cd integrations/mcp/server + +# Verify version was updated +cat pyproject.toml | grep version + +# Clean old builds +rm -rf dist/ build/ *.egg-info + +# Build package +python -m build + +# Verify contents +tar -tzf dist/beads-mcp-0.22.0.tar.gz + +# Upload to PyPI (test first) +twine upload --repository testpypi dist/* + +# Verify on test PyPI +pip install --index-url https://test.pypi.org/simple/ beads-mcp==0.22.0 + +# Upload to production PyPI +twine upload dist/* +``` + +### Verify PyPI Release + +```bash +# Check package page +open https://pypi.org/project/beads-mcp/ + +# Install and test +pip install beads-mcp==0.22.0 +python -m beads_mcp --version +``` + +## 5. npm Package Release + +The npm package wraps the native binary for Node.js environments. + +### Prerequisites + +```bash +# Verify npm authentication +npm whoami # Should show your username + +# Verify you're in @beads org +npm org ls beads +``` + +### Update and Test + +```bash +# Navigate to npm package +cd npm-package + +# Version should already be updated by bump-version.sh +cat package.json | grep version + +# Run all tests +npm run test:all + +# Should see: +# ✅ All unit tests passed +# ✅ All integration tests passed +``` + +### Test Installation Locally + +```bash +# Pack the package +npm pack + +# Install globally from tarball +npm install -g ./beads-bd-0.22.0.tgz + +# Verify binary downloads correctly +bd version # Should show 0.22.0 + +# Test in a project +mkdir /tmp/test-npm-bd +cd /tmp/test-npm-bd +git init +bd init +bd create "Test issue" -p 1 +bd list + +# Cleanup +npm uninstall -g @beads/bd +rm -rf /tmp/test-npm-bd +cd - +rm beads-bd-0.22.0.tgz +``` + +### Publish to npm + +```bash +# IMPORTANT: Ensure GitHub release with binaries is live first! +# The postinstall script downloads from GitHub releases + +# Publish to npm (first time use --access public) +npm publish --access public + +# Or for subsequent releases +npm publish +``` + +### Verify npm Release + +```bash +# Check package page +open https://www.npmjs.com/package/@beads/bd + +# Install and test +npm install -g @beads/bd +bd version # Should show 0.22.0 + +# Test postinstall downloaded correct binary +which bd +bd --help +``` + +## 6. Verify Release + +After all distribution channels are updated, verify each one: + +### GitHub + +```bash +# Download and test binary +wget https://github.com/steveyegge/beads/releases/download/v0.22.0/beads_0.22.0_darwin_arm64.tar.gz +tar -xzf beads_0.22.0_darwin_arm64.tar.gz +./bd version +``` + +### Homebrew + +```bash +brew update +brew upgrade bd +bd version +``` + +### PyPI + +```bash +pip install --upgrade beads-mcp +python -m beads_mcp --version +``` + +### npm + +```bash +npm install -g @beads/bd +bd version +``` + +### Installation Script + +```bash +# Test quick install script +curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash +bd version +``` + +## Hotfix Releases + +For urgent bug fixes: + +```bash +# Create hotfix branch from tag +git checkout -b hotfix/v0.22.1 v0.22.0 + +# Make fixes +# ... edit files ... + +# Bump version to 0.22.1 +./scripts/bump-version.sh 0.22.1 --commit + +# Tag and release +git tag -a v0.22.1 -m "Hotfix release v0.22.1" +git push origin hotfix/v0.22.1 +git push origin v0.22.1 + +# Follow normal release process +goreleaser release --clean + +# Merge back to main +git checkout main +git merge hotfix/v0.22.1 +git push origin main +``` + +## Rollback Procedure + +If a release has critical issues: + +### 1. Mark GitHub Release as Pre-release + +```bash +gh release edit v0.22.0 --prerelease +``` + +### 2. Create Hotfix Release + +Follow hotfix procedure above to release 0.22.1. + +### 3. Revert Homebrew (If Needed) + +```bash +cd homebrew-beads +git revert HEAD +git push +``` + +### 4. Deprecate npm Package (If Needed) + +```bash +npm deprecate @beads/bd@0.22.0 "Critical bug, please upgrade to 0.22.1" +``` + +### 5. Yank PyPI Release (If Needed) + +```bash +# Can't delete, but can yank (hide from pip install) +# Contact PyPI support or use web interface +``` + +## Automation Opportunities + +### GitHub Actions + +Create `.github/workflows/release.yml`: + +```yaml +name: Release +on: + push: + tags: + - 'v*' + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + - uses: goreleaser/goreleaser-action@v4 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + npm: + needs: goreleaser + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '18' + registry-url: 'https://registry.npmjs.org' + - run: cd npm-package && npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + pypi: + needs: goreleaser + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + - run: | + cd integrations/mcp/server + pip install build twine + python -m build + twine upload dist/* + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} +``` + +## Post-Release + +After a successful release: + +1. **Announce** on relevant channels (Twitter, blog, etc.) +2. **Update documentation** if needed +3. **Close milestone** on GitHub if using milestones +4. **Update project board** if using project management +5. **Monitor** for issues in the first 24-48 hours + +## Troubleshooting + +### "Tag already exists" + +```bash +# Delete tag locally and remotely +git tag -d v0.22.0 +git push origin :refs/tags/v0.22.0 + +# Recreate +git tag -a v0.22.0 -m "Release v0.22.0" +git push origin v0.22.0 +``` + +### "npm publish fails with EEXIST" + +```bash +# Version already published, bump version +npm version patch +npm publish +``` + +### "Binary download fails in npm postinstall" + +```bash +# Ensure GitHub release is published first +# Check binary URL is correct +# Verify version matches in package.json and GitHub release +``` + +### "GoReleaser build fails" + +```bash +# Check .goreleaser.yml syntax +goreleaser check + +# Test build locally +goreleaser build --snapshot --clean +``` + +## Version Numbering + +Beads follows [Semantic Versioning](https://semver.org/): + +- **MAJOR** (x.0.0): Breaking changes +- **MINOR** (0.x.0): New features, backwards compatible +- **PATCH** (0.0.x): Bug fixes, backwards compatible + +Examples: +- `0.21.5` → `0.22.0`: New features (minor bump) +- `0.22.0` → `0.22.1`: Bug fix (patch bump) +- `0.22.1` → `1.0.0`: Stable release (major bump) + +## Release Cadence + +- **Minor releases**: Every 2-4 weeks (new features) +- **Patch releases**: As needed (bug fixes) +- **Major releases**: When breaking changes are necessary + +## Questions? + +- Open an issue: https://github.com/steveyegge/beads/issues +- Check existing releases: https://github.com/steveyegge/beads/releases diff --git a/npm-package/TESTING.md b/npm-package/TESTING.md new file mode 100644 index 00000000..ac13ddc0 --- /dev/null +++ b/npm-package/TESTING.md @@ -0,0 +1,356 @@ +# Testing the @beads/bd npm Package + +This document describes the testing strategy and how to run tests for the @beads/bd npm package. + +## Test Suites + +### 1. Unit Tests (`npm test`) + +**Location**: `scripts/test.js` + +**Purpose**: Quick smoke tests to verify basic installation + +**Tests**: +- Binary version check +- Help command + +**Run**: +```bash +npm test +``` + +**Duration**: <1 second + +### 2. Integration Tests (`npm run test:integration`) + +**Location**: `test/integration.test.js` + +**Purpose**: Comprehensive end-to-end testing of the npm package + +**Tests**: + +#### Test 1: Package Installation +- Packs the npm package into a tarball +- Installs globally in an isolated test environment +- Verifies binary is downloaded and installed correctly + +#### Test 2: Binary Functionality +- Tests `bd version` command +- Tests `bd --help` command +- Verifies native binary works through Node wrapper + +#### Test 3: Basic bd Workflow +- Creates test project with git +- Runs `bd init --quiet` +- Creates an issue with `bd create` +- Lists issues with `bd list --json` +- Shows issue details with `bd show` +- Updates issue status with `bd update` +- Closes issue with `bd close` +- Verifies ready work detection with `bd ready` + +#### Test 4: Claude Code for Web Simulation +- **Session 1**: Initializes bd, creates an issue +- Verifies JSONL export +- Deletes database to simulate fresh clone +- **Session 2**: Re-initializes from JSONL (simulates SessionStart hook) +- Verifies issues are imported from JSONL +- Creates new issue (simulating agent discovery) +- Verifies JSONL auto-export works + +#### Test 5: Platform Detection +- Verifies current platform is supported +- Validates binary URL construction +- Confirms GitHub release has required binaries + +**Run**: +```bash +npm run test:integration +``` + +**Duration**: ~30-60 seconds (downloads binaries) + +### 3. All Tests (`npm run test:all`) + +Runs both unit and integration tests sequentially. + +```bash +npm run test:all +``` + +## Test Results + +All tests passing: + +``` +╔════════════════════════════════════════╗ +║ Test Summary ║ +╚════════════════════════════════════════╝ + +Total tests: 5 +Passed: 5 +Failed: 0 + +✅ All tests passed! +``` + +## What the Tests Verify + +### Package Installation +- ✅ npm pack creates valid tarball +- ✅ npm install downloads and installs package +- ✅ Postinstall script runs automatically +- ✅ Platform-specific binary is downloaded +- ✅ Binary is extracted correctly +- ✅ Binary is executable + +### Binary Functionality +- ✅ CLI wrapper invokes native binary +- ✅ All arguments pass through correctly +- ✅ Exit codes propagate +- ✅ stdio streams work (stdin/stdout/stderr) + +### bd Commands +- ✅ `bd init` creates .beads directory +- ✅ `bd create` creates issues with hash IDs +- ✅ `bd list` returns JSON array +- ✅ `bd show` returns issue details +- ✅ `bd update` modifies issue status +- ✅ `bd close` closes issues +- ✅ `bd ready` finds work with no blockers + +### Claude Code for Web Use Case +- ✅ Fresh installation works +- ✅ JSONL export happens automatically +- ✅ Database can be recreated from JSONL +- ✅ Issues survive database deletion +- ✅ SessionStart hook pattern works +- ✅ Agent can create new issues +- ✅ Auto-sync keeps JSONL updated + +### Platform Support +- ✅ macOS (darwin) - amd64, arm64 +- ✅ Linux - amd64, arm64 +- ✅ Windows - amd64 (zip format) +- ✅ Correct binary URLs generated +- ✅ GitHub releases have required assets + +## Testing Before Publishing + +Before publishing a new version to npm: + +```bash +# 1. Update version in package.json +npm version patch # or minor/major + +# 2. Run all tests +npm run test:all + +# 3. Test installation from local tarball +npm pack +npm install -g ./beads-bd-X.Y.Z.tgz +bd version + +# 4. Verify in a fresh project +mkdir /tmp/test-bd +cd /tmp/test-bd +git init +bd init +bd create "Test" -p 1 +bd list + +# 5. Cleanup +npm uninstall -g @beads/bd +``` + +## Continuous Integration + +### GitHub Actions (Recommended) + +Create `.github/workflows/test-npm-package.yml`: + +```yaml +name: Test npm Package +on: + push: + paths: + - 'npm-package/**' + pull_request: + paths: + - 'npm-package/**' + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + node-version: [18, 20] + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Run unit tests + run: | + cd npm-package + npm test + + - name: Run integration tests + run: | + cd npm-package + npm run test:integration +``` + +## Manual Testing Scenarios + +### Scenario 1: Claude Code for Web SessionStart Hook + +1. Create `.claude/hooks/session-start.sh`: + ```bash + #!/bin/bash + npm install -g @beads/bd + bd init --quiet + ``` + +2. Make executable: `chmod +x .claude/hooks/session-start.sh` + +3. Start new Claude Code for Web session + +4. Verify: + ```bash + bd version # Should work + bd list # Should show existing issues + ``` + +### Scenario 2: Global Installation + +```bash +# Install globally +npm install -g @beads/bd + +# Verify +which bd +bd version + +# Use in any project +mkdir ~/projects/test +cd ~/projects/test +git init +bd init +bd create "First issue" -p 1 +bd list +``` + +### Scenario 3: Project Dependency + +```bash +# Add to project +npm install --save-dev @beads/bd + +# Use via npx +npx bd version +npx bd init +npx bd create "Issue" -p 1 +``` + +### Scenario 4: Offline/Cached Installation + +```bash +# First install (downloads binary) +npm install -g @beads/bd + +# Uninstall +npm uninstall -g @beads/bd + +# Reinstall (should use npm cache) +npm install -g @beads/bd +# Should be faster (no binary download if cached) +``` + +## Troubleshooting Tests + +### Test fails with "binary not found" + +**Cause**: Postinstall script didn't download binary + +**Fix**: +- Check GitHub release has required binaries +- Verify package.json version matches release +- Check network connectivity + +### Test fails with "permission denied" + +**Cause**: Binary not executable + +**Fix**: +- Postinstall should chmod +x on Unix +- Windows doesn't need this + +### Integration test times out + +**Cause**: Network slow, binary download taking too long + +**Fix**: +- Increase timeout in test +- Use cached npm packages +- Run on faster network + +### JSONL import test fails + +**Cause**: Database format changed or JSONL format incorrect + +**Fix**: +- Check bd version compatibility +- Verify JSONL format matches current schema +- Update test to use proper operation records + +## Test Coverage + +| Area | Coverage | +|------|----------| +| Package installation | ✅ Full | +| Binary download | ✅ Full | +| CLI wrapper | ✅ Full | +| Basic commands | ✅ High (8 commands) | +| JSONL sync | ✅ Full | +| Platform detection | ✅ Full | +| Error handling | ⚠️ Partial | +| MCP server | ❌ Not included | + +## Known Limitations + +1. **No MCP server tests**: The npm package only includes the CLI binary, not the Python MCP server +2. **Platform testing**: Tests only run on the current platform (need CI for full coverage) +3. **Network dependency**: Integration tests require internet to download binaries +4. **Timing sensitivity**: JSONL auto-export has 5-second debounce, tests use sleep + +## Future Improvements + +1. **Mock binary downloads** for faster tests +2. **Cross-platform CI** to test on all OSes +3. **MCP server integration** (if Node.js MCP server is added) +4. **Performance benchmarks** for binary download times +5. **Stress testing** with many issues +6. **Concurrent operation testing** for race conditions + +## FAQ + +**Q: Do I need to run tests before every commit?** +A: Run `npm test` (quick unit tests). Run full integration tests before publishing. + +**Q: Why do integration tests take so long?** +A: They download ~17MB binary from GitHub releases. First run is slower. + +**Q: Can I run tests offline?** +A: Unit tests yes, integration tests no (need to download binary). + +**Q: Do tests work on Windows?** +A: Yes, but integration tests need PowerShell for zip extraction. + +**Q: How do I test a specific version?** +A: Update package.json version, ensure GitHub release exists, run tests. diff --git a/npm-package/package.json b/npm-package/package.json index 62e870ef..72191f9f 100644 --- a/npm-package/package.json +++ b/npm-package/package.json @@ -8,7 +8,9 @@ }, "scripts": { "postinstall": "node scripts/postinstall.js", - "test": "node scripts/test.js" + "test": "node scripts/test.js", + "test:integration": "node test/integration.test.js", + "test:all": "npm test && npm run test:integration" }, "keywords": [ "issue-tracker", diff --git a/npm-package/test/integration.test.js b/npm-package/test/integration.test.js new file mode 100755 index 00000000..ffbd2c9b --- /dev/null +++ b/npm-package/test/integration.test.js @@ -0,0 +1,502 @@ +#!/usr/bin/env node + +/** + * Integration tests for @beads/bd npm package + * + * Tests: + * 1. Package installation in clean environment + * 2. Binary download and extraction + * 3. Basic bd commands (version, init, create, list, etc.) + * 4. Claude Code for Web simulation + */ + +const { execSync, spawn } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +// Test configuration +const TEST_DIR = path.join(os.tmpdir(), `bd-integration-test-${Date.now()}`); +const PACKAGE_DIR = path.join(__dirname, '..'); + +// ANSI colors for output +const colors = { + reset: '\x1b[0m', + green: '\x1b[32m', + red: '\x1b[31m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + gray: '\x1b[90m' +}; + +function log(msg, color = 'reset') { + console.log(`${colors[color]}${msg}${colors.reset}`); +} + +function logTest(name) { + log(`\n▶ ${name}`, 'blue'); +} + +function logSuccess(msg) { + log(` ✓ ${msg}`, 'green'); +} + +function logError(msg) { + log(` ✗ ${msg}`, 'red'); +} + +function logInfo(msg) { + log(` ℹ ${msg}`, 'gray'); +} + +// Test utilities +function exec(cmd, opts = {}) { + const defaultOpts = { + stdio: 'pipe', + encoding: 'utf8', + ...opts + }; + try { + return execSync(cmd, defaultOpts); + } catch (err) { + if (opts.throwOnError !== false) { + throw err; + } + return err.stdout || err.stderr || ''; + } +} + +function setupTestDir() { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } + fs.mkdirSync(TEST_DIR, { recursive: true }); + logInfo(`Test directory: ${TEST_DIR}`); +} + +function cleanupTestDir() { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } +} + +// Test 1: Package installation +async function testPackageInstallation() { + logTest('Test 1: Package Installation'); + + try { + // Pack the package + logInfo('Packing npm package...'); + const packOutput = exec('npm pack', { cwd: PACKAGE_DIR }); + const tarball = packOutput.trim().split('\n').pop(); + const tarballPath = path.join(PACKAGE_DIR, tarball); + + logSuccess(`Package created: ${tarball}`); + + // Install from tarball in test directory + logInfo('Installing package in test environment...'); + const npmPrefix = path.join(TEST_DIR, 'npm-global'); + fs.mkdirSync(npmPrefix, { recursive: true }); + + exec(`npm install -g "${tarballPath}" --prefix "${npmPrefix}"`, { + cwd: TEST_DIR, + env: { ...process.env, npm_config_prefix: npmPrefix } + }); + + logSuccess('Package installed successfully'); + + // Verify binary exists + const bdPath = path.join(npmPrefix, 'bin', 'bd'); + if (!fs.existsSync(bdPath) && !fs.existsSync(bdPath + '.cmd')) { + // On Windows, might be bd.cmd + const windowsPath = path.join(npmPrefix, 'bd.cmd'); + if (!fs.existsSync(windowsPath)) { + throw new Error(`bd binary not found at ${bdPath}`); + } + } + + logSuccess('bd binary installed'); + + // Cleanup tarball + fs.unlinkSync(tarballPath); + + return { npmPrefix, bdPath }; + } catch (err) { + logError(`Package installation failed: ${err.message}`); + throw err; + } +} + +// Test 2: Binary functionality +async function testBinaryFunctionality(npmPrefix) { + logTest('Test 2: Binary Functionality'); + + const bdCmd = path.join(npmPrefix, 'bin', 'bd'); + const env = { ...process.env, PATH: `${path.join(npmPrefix, 'bin')}:${process.env.PATH}` }; + + try { + // Test version command + logInfo('Testing version command...'); + const version = exec(`"${bdCmd}" version`, { env }); + if (!version.includes('bd version')) { + throw new Error(`Unexpected version output: ${version}`); + } + logSuccess(`Version: ${version.trim()}`); + + // Test help command + logInfo('Testing help command...'); + const help = exec(`"${bdCmd}" --help`, { env }); + if (!help.includes('Available Commands')) { + throw new Error('Help command did not return expected output'); + } + logSuccess('Help command works'); + + return true; + } catch (err) { + logError(`Binary functionality test failed: ${err.message}`); + throw err; + } +} + +// Test 3: Basic bd workflow +async function testBasicWorkflow(npmPrefix) { + logTest('Test 3: Basic bd Workflow'); + + const projectDir = path.join(TEST_DIR, 'test-project'); + fs.mkdirSync(projectDir, { recursive: true }); + + // Initialize git repo + exec('git init', { cwd: projectDir }); + exec('git config user.email "test@example.com"', { cwd: projectDir }); + exec('git config user.name "Test User"', { cwd: projectDir }); + + const bdCmd = path.join(npmPrefix, 'bin', 'bd'); + const env = { + ...process.env, + PATH: `${path.join(npmPrefix, 'bin')}:${process.env.PATH}`, + BD_ACTOR: 'integration-test' + }; + + try { + // Test bd init + logInfo('Testing bd init...'); + exec(`"${bdCmd}" init --quiet`, { cwd: projectDir, env }); + + if (!fs.existsSync(path.join(projectDir, '.beads'))) { + throw new Error('.beads directory not created'); + } + logSuccess('bd init successful'); + + // Test bd create + logInfo('Testing bd create...'); + const createOutput = exec(`"${bdCmd}" create "Test issue" -t task -p 1 --json`, { + cwd: projectDir, + env + }); + const issue = JSON.parse(createOutput); + if (!issue.id || typeof issue.id !== 'string') { + throw new Error(`Invalid issue created: ${JSON.stringify(issue)}`); + } + // ID format can be bd-xxxx or projectname-xxxx depending on configuration + logSuccess(`Created issue: ${issue.id}`); + + // Test bd list + logInfo('Testing bd list...'); + const listOutput = exec(`"${bdCmd}" list --json`, { cwd: projectDir, env }); + const issues = JSON.parse(listOutput); + if (!Array.isArray(issues) || issues.length !== 1) { + throw new Error('bd list did not return expected issues'); + } + logSuccess(`Listed ${issues.length} issue(s)`); + + // Test bd show + logInfo('Testing bd show...'); + const showOutput = exec(`"${bdCmd}" show ${issue.id} --json`, { cwd: projectDir, env }); + const showResult = JSON.parse(showOutput); + // bd show --json returns an array with one element + const showIssue = Array.isArray(showResult) ? showResult[0] : showResult; + // Compare IDs - both should be present and match + if (!showIssue.id || showIssue.id !== issue.id) { + throw new Error(`bd show returned wrong issue: expected ${issue.id}, got ${showIssue.id}`); + } + logSuccess(`Show issue: ${showIssue.title}`); + + // Test bd update + logInfo('Testing bd update...'); + exec(`"${bdCmd}" update ${issue.id} --status in_progress`, { cwd: projectDir, env }); + const updatedOutput = exec(`"${bdCmd}" show ${issue.id} --json`, { cwd: projectDir, env }); + const updatedResult = JSON.parse(updatedOutput); + const updatedIssue = Array.isArray(updatedResult) ? updatedResult[0] : updatedResult; + if (updatedIssue.status !== 'in_progress') { + throw new Error(`bd update did not change status: expected 'in_progress', got '${updatedIssue.status}'`); + } + logSuccess('Updated issue status'); + + // Test bd close + logInfo('Testing bd close...'); + exec(`"${bdCmd}" close ${issue.id} --reason "Test completed"`, { cwd: projectDir, env }); + const closedOutput = exec(`"${bdCmd}" show ${issue.id} --json`, { cwd: projectDir, env }); + const closedResult = JSON.parse(closedOutput); + const closedIssue = Array.isArray(closedResult) ? closedResult[0] : closedResult; + if (closedIssue.status !== 'closed') { + throw new Error(`bd close did not close issue: expected 'closed', got '${closedIssue.status}'`); + } + logSuccess('Closed issue'); + + // Test bd ready (should be empty after closing) + logInfo('Testing bd ready...'); + const readyOutput = exec(`"${bdCmd}" ready --json`, { cwd: projectDir, env }); + const readyIssues = JSON.parse(readyOutput); + if (readyIssues.length !== 0) { + throw new Error('bd ready should return no issues after closing all'); + } + logSuccess('Ready work detection works'); + + return true; + } catch (err) { + logError(`Basic workflow test failed: ${err.message}`); + throw err; + } +} + +// Test 4: Claude Code for Web simulation +async function testClaudeCodeWebSimulation(npmPrefix) { + logTest('Test 4: Claude Code for Web Simulation'); + + const sessionDir = path.join(TEST_DIR, 'claude-code-session'); + fs.mkdirSync(sessionDir, { recursive: true }); + + try { + // Initialize git repo (simulating a cloned project) + exec('git init', { cwd: sessionDir }); + exec('git config user.email "agent@example.com"', { cwd: sessionDir }); + exec('git config user.name "Claude Agent"', { cwd: sessionDir }); + + const bdCmd = path.join(npmPrefix, 'bin', 'bd'); + const env = { + ...process.env, + PATH: `${path.join(npmPrefix, 'bin')}:${process.env.PATH}`, + BD_ACTOR: 'claude-agent' + }; + + // First session: initialize and create an issue + logInfo('Session 1: Initialize and create issue...'); + exec(`"${bdCmd}" init --quiet`, { cwd: sessionDir, env }); + + const createOutput = exec( + `"${bdCmd}" create "Existing issue from previous session" -t task -p 1 --json`, + { cwd: sessionDir, env } + ); + const existingIssue = JSON.parse(createOutput); + logSuccess(`Created issue in first session: ${existingIssue.id}`); + + // Simulate sync to git (bd automatically exports to JSONL) + const beadsDir = path.join(sessionDir, '.beads'); + const jsonlPath = path.join(beadsDir, 'issues.jsonl'); + + // Wait a moment for auto-export + execSync('sleep 1'); + + // Verify JSONL exists + if (!fs.existsSync(jsonlPath)) { + throw new Error('JSONL file not created'); + } + + // Remove the database to simulate a fresh clone + const dbFiles = fs.readdirSync(beadsDir).filter(f => f.endsWith('.db')); + dbFiles.forEach(f => fs.unlinkSync(path.join(beadsDir, f))); + + // Session 2: Re-initialize (simulating SessionStart hook in new session) + logInfo('Session 2: Re-initialize from JSONL...'); + exec(`"${bdCmd}" init --quiet`, { cwd: sessionDir, env }); + logSuccess('bd init re-imported from JSONL'); + + // Verify issue was imported + const listOutput = exec(`"${bdCmd}" list --json`, { cwd: sessionDir, env }); + const issues = JSON.parse(listOutput); + + if (!issues.some(i => i.id === existingIssue.id)) { + throw new Error(`Existing issue ${existingIssue.id} not imported from JSONL`); + } + logSuccess('Existing issues imported successfully'); + + // Simulate agent finding ready work + const readyOutput = exec(`"${bdCmd}" ready --json`, { cwd: sessionDir, env }); + const readyIssues = JSON.parse(readyOutput); + + if (readyIssues.length === 0) { + throw new Error('No ready work found'); + } + logSuccess(`Found ${readyIssues.length} ready issue(s)`); + + // Simulate agent creating a new issue + const newCreateOutput = exec( + `"${bdCmd}" create "Bug discovered during session" -t bug -p 0 --json`, + { cwd: sessionDir, env } + ); + const newIssue = JSON.parse(newCreateOutput); + logSuccess(`Agent created new issue: ${newIssue.id}`); + + // Verify JSONL was updated + const jsonlContent = fs.readFileSync( + path.join(beadsDir, 'issues.jsonl'), + 'utf8' + ); + const jsonlLines = jsonlContent.trim().split('\n'); + + if (jsonlLines.length < 2) { + throw new Error('JSONL not updated with new issue'); + } + logSuccess('JSONL auto-export working'); + + return true; + } catch (err) { + logError(`Claude Code for Web simulation failed: ${err.message}`); + throw err; + } +} + +// Test 5: Multi-platform binary detection +async function testPlatformDetection() { + logTest('Test 5: Platform Detection'); + + try { + const platform = os.platform(); + const arch = os.arch(); + + logInfo(`Current platform: ${platform}`); + logInfo(`Current architecture: ${arch}`); + + // Verify postinstall would work for this platform + const supportedPlatforms = { + darwin: ['x64', 'arm64'], + linux: ['x64', 'arm64'], + win32: ['x64', 'arm64'] + }; + + if (!supportedPlatforms[platform]) { + throw new Error(`Unsupported platform: ${platform}`); + } + + const archMap = { x64: 'amd64', arm64: 'arm64' }; + const mappedArch = archMap[arch]; + + if (!supportedPlatforms[platform].includes(arch)) { + throw new Error(`Unsupported architecture: ${arch} for platform ${platform}`); + } + + logSuccess(`Platform ${platform}-${mappedArch} is supported`); + + // Check if GitHub release has this binary + const version = require(path.join(PACKAGE_DIR, 'package.json')).version; + const ext = platform === 'win32' ? 'zip' : 'tar.gz'; + const binaryUrl = `https://github.com/steveyegge/beads/releases/download/v${version}/beads_${version}_${platform}_${mappedArch}.${ext}`; + + logInfo(`Expected binary URL: ${binaryUrl}`); + logSuccess('Platform detection logic validated'); + + return true; + } catch (err) { + logError(`Platform detection test failed: ${err.message}`); + throw err; + } +} + +// Main test runner +async function runTests() { + log('\n╔════════════════════════════════════════╗', 'blue'); + log('║ @beads/bd Integration Tests ║', 'blue'); + log('╚════════════════════════════════════════╝', 'blue'); + + let npmPrefix; + const results = { + passed: 0, + failed: 0, + total: 0 + }; + + try { + setupTestDir(); + + // Test 1: Installation + results.total++; + try { + const installResult = await testPackageInstallation(); + npmPrefix = installResult.npmPrefix; + results.passed++; + } catch (err) { + results.failed++; + log('\n⚠️ Skipping remaining tests due to installation failure', 'yellow'); + throw err; + } + + // Test 2: Binary functionality + results.total++; + try { + await testBinaryFunctionality(npmPrefix); + results.passed++; + } catch (err) { + results.failed++; + } + + // Test 3: Basic workflow + results.total++; + try { + await testBasicWorkflow(npmPrefix); + results.passed++; + } catch (err) { + results.failed++; + } + + // Test 4: Claude Code for Web + results.total++; + try { + await testClaudeCodeWebSimulation(npmPrefix); + results.passed++; + } catch (err) { + results.failed++; + } + + // Test 5: Platform detection + results.total++; + try { + await testPlatformDetection(); + results.passed++; + } catch (err) { + results.failed++; + } + + } finally { + // Cleanup + logInfo('\nCleaning up test directory...'); + cleanupTestDir(); + } + + // Print summary + log('\n╔════════════════════════════════════════╗', 'blue'); + log('║ Test Summary ║', 'blue'); + log('╚════════════════════════════════════════╝', 'blue'); + log(`\nTotal tests: ${results.total}`, 'blue'); + log(`Passed: ${results.passed}`, results.passed === results.total ? 'green' : 'yellow'); + log(`Failed: ${results.failed}`, results.failed > 0 ? 'red' : 'green'); + + if (results.failed > 0) { + log('\n❌ Some tests failed', 'red'); + process.exit(1); + } else { + log('\n✅ All tests passed!', 'green'); + process.exit(0); + } +} + +// Run tests +if (require.main === module) { + runTests().catch(err => { + log(`\n❌ Test suite failed: ${err.message}`, 'red'); + console.error(err); + cleanupTestDir(); + process.exit(1); + }); +} + +module.exports = { runTests };