diff --git a/.beads/.gitignore b/.beads/.gitignore deleted file mode 100644 index e9645a33..00000000 --- a/.beads/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# SQLite databases -*.db -*.db?* -*.db-journal -*.db-wal -*.db-shm - -# Daemon runtime files -daemon.lock -daemon.log -daemon.pid -bd.sock - -# Legacy database files -db.sqlite -bd.db - -# Merge artifacts (temporary files from 3-way merge) -beads.base.jsonl -beads.base.meta.json -beads.left.jsonl -beads.left.meta.json -beads.right.jsonl -beads.right.meta.json - -# Keep JSONL exports and config (source of truth for git) -!beads.jsonl -!metadata.json -!config.yaml - diff --git a/.beads/beads.jsonl b/.beads/beads.jsonl deleted file mode 100644 index a9b60898..00000000 --- a/.beads/beads.jsonl +++ /dev/null @@ -1,22 +0,0 @@ -{"id":"hp-1","content_hash":"0abb67dd2fb37e4bf0edc85c37cab145c7c512497e64857f6255ae373ec94de6","title":"Check code using Effect-Language-Service CLI","description":"Use the Effect-Language-Service CLI to validate Effect code quality and setup.\n\nThe CLI provides Effect-specific diagnostics and validation:\n\n**Key Commands:**\n- `diagnostics`: Analyze Effect-specific issues (use --file for individual files or --project with tsconfig path)\n- `check`: Verify TypeScript patching status for compile-time diagnostics\n- `patch`: Enable Effect diagnostics at build time (add to npm prepare script to persist)\n\n**What it Validates:**\n- Floating Effects (unhandled Effect values)\n- Incorrect yield usage in Effect.gen\n- Multiple Effect versions in the project\n- Service dependency requirements\n\n**Usage:**\nShould be installed locally (not globally) to match project's TypeScript version.\n\n**Integration:**\nAdd to CI/CD pipeline or pre-commit hooks to catch Effect-specific issues early.","status":"closed","priority":2,"issue_type":"chore","created_at":"2025-11-15T10:56:05.231614Z","updated_at":"2025-11-15T10:56:05.231614Z","closed_at":"2025-10-25T14:28:41.592959+01:00","source_repo":"."} -{"id":"hp-10","content_hash":"ef2b24ee75bca91e98301e3d4ccf59b6cc31d5b1e1a8d3957c0e8ad5f587dad7","title":"Refactor to introduce StoredEvent wherever there is a pair of EventStreamPosition and Event","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-15T10:56:05.232512Z","updated_at":"2025-11-15T10:56:05.232512Z","closed_at":"2025-10-26T13:59:27.623368Z","source_repo":".","dependencies":[{"issue_id":"hp-10","depends_on_id":"hp-9","type":"blocks","created_at":"2025-10-26T10:22:18.724252Z","created_by":"graemefoster"}]} -{"id":"hp-11","content_hash":"8a924e21e1cd811c7bbed0c7865ab77e3adc606beffe891dc77d74009833eb14","title":"Skip CI workflows for beads-only PRs","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-15T10:56:05.233083Z","updated_at":"2025-11-15T10:56:05.233083Z","closed_at":"2025-10-26T11:48:16.684935Z","source_repo":"."} -{"id":"hp-12","content_hash":"90e70d11ea1b1f2eb968597b0eb03649c95c294175cb604222d80b09858e6054","title":"Add EventBus test for late-arriving subscribers","description":"Add integration test for subscriber that joins an already-running EventBus after events have been published.\n\n**Scenario:**\n1. EventBus layer created\n2. Events written to EventStore (EventBus pump processes them)\n3. NEW subscriber joins via eventBus.subscribe()\n4. More events written\n5. Verify subscriber only receives events from step 4, not step 2\n\n**Difference from existing 'live-only' test:**\n- Existing test: events written BEFORE EventBus layer exists\n- New test: events written AFTER EventBus exists but BEFORE subscriber joins\n\n**Expected behavior:**\nSubscriber should only receive events published AFTER it subscribed, not events that were already processed by the EventBus pump before subscription.\n\n**Files:**\n- packages/eventsourcing-server/src/lib/eventBus.test.ts (add new test case)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-15T10:56:05.233702Z","updated_at":"2025-11-15T10:56:05.233702Z","closed_at":"2025-11-01T16:29:19.816089Z","source_repo":".","dependencies":[{"issue_id":"hp-12","depends_on_id":"hp-4","type":"discovered-from","created_at":"2025-10-27T12:55:57.203061Z","created_by":"graemefoster"}]} -{"id":"hp-13","content_hash":"59fe748797a2ac6881e2373679676929662d17c7667035256c8221baee43bfc8","title":"Add EventBus error handling tests","description":"Add tests for EventBus error scenarios:\n\n**Tests needed:**\n1. **EventStore.subscribeAll() failure:** If the store's subscribeAll() returns an error, the EventBus layer creation should fail gracefully\n2. **Pump fiber death:** If the pump fiber dies unexpectedly, subscribers should receive an error (not hang forever)\n3. **Filter exception:** If a subscriber's filter function throws, it should only affect that subscriber, not others\n\n**Expected behavior:**\n- Errors should be propagated to the correct scope (layer vs subscriber)\n- One subscriber's filter failure shouldn't kill the entire EventBus\n- Clear error messages for debugging\n\n**Files:**\n- packages/eventsourcing-server/src/lib/eventBus.test.ts (add error test cases)\n- packages/eventsourcing-server/src/lib/eventBus.ts (may need error handling improvements)","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-15T10:56:05.234284Z","updated_at":"2025-11-15T10:56:05.234284Z","closed_at":"2025-11-02T10:10:14.632118Z","source_repo":".","dependencies":[{"issue_id":"hp-13","depends_on_id":"hp-4","type":"discovered-from","created_at":"2025-10-27T14:58:14.538111Z","created_by":"graemefoster"}]} -{"id":"hp-14","content_hash":"1237f8a91b5da1dad1840a45249c46a2913901e00fd91e0d04a016a704fa0915","title":"Add EventBus edge case tests","description":"Add tests for EventBus edge cases:\n\n**Tests needed:**\n1. **Empty stream:** EventBus runs but no events are ever published - subscriber should wait gracefully\n2. **Filter matches nothing:** Subscriber filter never matches - should receive empty stream (not error)\n3. **Unsubscribe before events:** Subscriber created but scope closed before any events arrive\n4. **Slow consumer:** One subscriber consumes slowly - shouldn't block other subscribers\n5. **Multiple EventBus instances:** Two EventBus layers on same EventStore - both should receive events independently\n\n**Expected behavior:**\n- Empty/no-match scenarios return empty streams, not errors\n- Slow consumers don't affect fast consumers (independent Dequeues)\n- Multiple EventBus instances work correctly (each has own pump)\n\n**Priority:** P3 (nice-to-have validation tests, not critical bugs)\n\n**Files:**\n- packages/eventsourcing-server/src/lib/eventBus.test.ts (add edge case tests)","status":"open","priority":3,"issue_type":"task","created_at":"2025-11-15T10:56:05.234895Z","updated_at":"2025-11-15T10:56:05.234895Z","source_repo":".","dependencies":[{"issue_id":"hp-14","depends_on_id":"hp-4","type":"discovered-from","created_at":"2025-10-27T14:58:32.987652Z","created_by":"graemefoster"}]} -{"id":"hp-15","content_hash":"2715396bbc6ef75e7b19e787a0eb3f5573e717ab8078e897a973237171f0c2ae","title":"Refactor eventBus.test.ts to follow Effect eslint rules - remove disabled linting rules","description":"","status":"closed","priority":1,"issue_type":"chore","created_at":"2025-11-15T10:56:05.235449Z","updated_at":"2025-11-15T10:56:05.235449Z","closed_at":"2025-10-27T17:43:31.404676Z","source_repo":".","dependencies":[{"issue_id":"hp-15","depends_on_id":"hp-16","type":"parent-child","created_at":"2025-10-27T17:28:48.844982Z","created_by":"graemefoster"}]} -{"id":"hp-16","content_hash":"4d06a2111ca121c18f2958ab5458bb8efe4e6391233bbe82d2693a6e08e84e96","title":"Codebase-wide refactor: Remove all disabled Effect eslint rules from test files (23 violations across 9 files)","description":"","status":"closed","priority":1,"issue_type":"chore","created_at":"2025-11-15T10:56:05.235973Z","updated_at":"2025-11-15T10:56:05.235973Z","closed_at":"2025-10-28T06:57:23.922603Z","source_repo":"."} -{"id":"hp-17","content_hash":"b62d2307fbd005a1d8550246ad8bf3e3f3bc890eb6c41cae30f7904fedc12e04","title":"Replace isCommandSuccess/isCommandFailure type guards with Match.tag throughout codebase","description":"The codebase currently uses type guard functions isCommandSuccess and isCommandFailure to check discriminated union tags on CommandResult. This is an imperative pattern that doesn't leverage Effect's functional matching capabilities.\n\n## Why This Matters\n\n1. Consistency with Effect patterns: Effect provides Match.tag specifically for discriminated unions with _tag fields. Using it makes the code more idiomatic.\n\n2. Better type inference: Match.tag provides better type narrowing within match branches and ensures exhaustive handling.\n\n3. Reduces imperative style: Type guard functions with if statements are imperative. Match.tag with Match.orElse is functional composition.\n\n4. Avoids side effects: The current pattern often leads to imperative control flow. With Match, we get functional composition.\n\n5. Already started: We've refactored verifySuccessResult and verifyFailureResult to use Match.tag. The rest of protocol.test.ts and potentially other files still use the old pattern.\n\n## Scope\n\nSearch for all uses of isCommandSuccess and isCommandFailure in:\n- packages/eventsourcing-protocol/src/lib/protocol.test.ts (many remaining uses around lines 115, 395, 606, 705, 845, 846, 1412, 1597-1600, 1736-1741, 1791-1794, 2077-2081, 2264)\n- Any other files in the codebase that import these functions\n\nReplace with Match.tag('Success', ...) and Match.tag('Failure', ...) patterns.","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-15T10:56:05.236492Z","updated_at":"2025-11-15T10:56:05.236492Z","source_repo":"."} -{"id":"hp-18","content_hash":"e27a594fbe55f7988cd34697b9a9c6ea9ab7679980d852434183237f9cf4f450","title":"Replace JSON.parse with Schema validation in test error message parsing","description":"The test helper functions currently use JSON.parse with type assertions when parsing error messages. This is unsafe and doesn't provide proper validation. We should use Schema.decodeUnknown instead.\n\n## Current problem\n\nIn parseErrorMessage (line 337-344), we have:\n```typescript\nJSON.parse(message) as {\n readonly _tag: string;\n readonly validationErrors?: readonly string[];\n}\n```\n\nThis bypasses all type safety and validation. If the JSON structure doesn't match, we won't know until runtime failures occur.\n\n## Solution\n\nReplace JSON.parse with Schema validation:\n\n1. Define a Schema for the parsed error structure (or reuse existing schemas if available)\n2. Use Schema.decodeUnknown to parse and validate the JSON string\n3. This gives us proper error messages when parsing fails\n\nExample:\n```typescript\nconst ParsedErrorSchema = Schema.Struct({\n _tag: Schema.String,\n validationErrors: Schema.optional(Schema.Array(Schema.String))\n});\n\npipe(\n message,\n Schema.decodeUnknown(ParsedErrorSchema),\n Effect.mapError((error) =\u003e new Error(`Failed to parse error message: ${error}`))\n)\n```\n\n## Benefits\n\n- Type-safe parsing with validation\n- Better error messages when parsing fails \n- Removes unsafe type assertions\n- More idiomatic Effect code\n\n## Location\n\npackages/eventsourcing-protocol/src/lib/protocol.test.ts:337-344 in the parseErrorMessage function","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-15T10:56:05.237062Z","updated_at":"2025-11-15T10:56:05.237062Z","source_repo":"."} -{"id":"hp-19","content_hash":"b62f15988fe6d93c06c07a383bd87358db92c11ba831180de749faca180de580","title":"Add EventBus test for historical events before layer creation","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-15T10:56:05.237555Z","updated_at":"2025-11-15T10:56:05.237555Z","closed_at":"2025-11-01T17:14:44.038165Z","source_repo":".","dependencies":[{"issue_id":"hp-19","depends_on_id":"hp-12","type":"discovered-from","created_at":"2025-11-01T16:23:29.711727Z","created_by":"graemefoster"}]} -{"id":"hp-2","content_hash":"a351bb2ac4113d4d036db96e0b3a3a321d7fe4986f41d6c3bf74719d95dc8b83","title":"Fix 25 Effect-specific errors in eslint-effect test files","description":"The eslint-effect package contains 37 test files with intentional Effect anti-patterns used to test ESLint rules. These test files create floating Effects (Effects that are created but never handled).\n\nExample issue: In test/no-runSync-runPromise.test.ts, Effect.runSync() and Effect.runPromise() create Effects but don't assign or handle them.\n\nThese need to be fixed by either:\n1. Running the Effects to completion\n2. Assigning them to exported variables\n3. Returning them from the test context\n\nThe test files are in packages/eslint-effect/test/*.test.ts\n\nThis was discovered during Effect-LS integration (hp-1) when TypeScript patching enabled compile-time Effect diagnostics.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-11-15T10:56:05.238108Z","updated_at":"2025-11-15T10:56:05.238108Z","closed_at":"2025-10-25T14:39:26.441293+01:00","source_repo":".","dependencies":[{"issue_id":"hp-2","depends_on_id":"hp-1","type":"discovered-from","created_at":"2025-10-25T14:28:37.223575+01:00","created_by":"graemefoster"}]} -{"id":"hp-22","content_hash":"1164a3bdc0362509fffaed9ee1d78627ad2d1f49d9dd0a4e26d64428985d844c","title":"Implement EventBus filter exception test and error handling","description":"Complete the third error handling test case for EventBus that was deferred during hp-13.\n\n**Requirement:**\nWhen a subscriber's filter function throws an exception, it should only affect that subscriber, not crash the entire EventBus or impact other subscribers.\n\n**Current State:**\n- TODO comment added in eventBus.test.ts lines 458-462\n- No error handling exists in filterEvent() function (eventBus.ts lines 73-82)\n- Currently, any exception in a filter will crash the stream\n\n**Implementation needed:**\n\n1. **Add error handling to eventBus.ts:**\n - Location: filterEvent() function at lines 73-82\n - Current code uses a simple ternary with filter(streamEvent.event)\n - Wrap the filter call in try/catch or use Effect-based error handling\n - Return Option.none() if filter throws (silently skip the event for that subscriber)\n\n2. **Write test in eventBus.test.ts:**\n - Location: After line 462 (where TODO comment is)\n - Pattern to follow: Look at 'subscribers complete gracefully when pump fiber dies' test (lines 464-492)\n - That test successfully satisfies the linter - use its structure\n - Test structure:\n a. Create two subscribers (one with throwing filter, one normal)\n b. Publish 2 events via makeTestStoreLayer\n c. Verify normal subscriber gets 2 events\n d. Verify throwing subscriber handles error gracefully\n\n**Reference implementations:**\n- Lines 433-456: Shows how to test layer creation failure\n- Lines 464-492: Shows how to test pump failure with Effect.either\n- Both pass linter - use them as templates\n\n**Linter requirements:**\n- No nested pipe() calls - extract to named functions\n- No curried calls - use full parameter lists\n- Use Effect.either to catch errors without Effect.sync\n- Use Chunk operations for result verification\n\n**Testing approach:**\nRun: turbo test --filter='@codeforbreakfast/eventsourcing-server'\nAll 8 existing tests must continue to pass.\n\n**Files:**\n- packages/eventsourcing-server/src/lib/eventBus.ts (add error handling)\n- packages/eventsourcing-server/src/lib/eventBus.test.ts (implement test)","status":"closed","priority":3,"issue_type":"task","created_at":"2025-11-15T10:56:05.238656Z","updated_at":"2025-11-15T10:56:05.238656Z","closed_at":"2025-11-02T10:36:36.191137Z","source_repo":".","dependencies":[{"issue_id":"hp-22","depends_on_id":"hp-13","type":"discovered-from","created_at":"2025-11-02T10:11:43.463456Z","created_by":"graemefoster"}]} -{"id":"hp-23","content_hash":"befee8170864336b3ca87de1be7a62a72d89a4a404f2e76b33b91f46ddd81c99","title":"Refactor filterEvent to avoid eslint-disable and improve code quality","description":"The filterEvent function in eventBus.ts currently uses an eslint-disable comment to bypass the effect/prefer-match-over-ternary rule. We should refactor this to use proper Effect patterns.\n\n**Current code (eventBus.ts:73-88):**\n```typescript\nconst filterEvent =\n \u003cTFiltered extends TEvent\u003e(filter: (event: TEvent) =\u003e event is TFiltered) =\u003e\n (streamEvent: StreamEvent\u003cTEvent\u003e): Option.Option\u003cStreamEvent\u003cTFiltered\u003e\u003e =\u003e {\n try {\n // eslint-disable-next-line effect/prefer-match-over-ternary -- Simple boolean check for type guard, Match pattern would be unnecessarily verbose\n return filter(streamEvent.event)\n ? Option.some({\n position: streamEvent.position,\n event: streamEvent.event as TFiltered,\n })\n : Option.none();\n } catch {\n // Filter threw an exception - return none to skip this event for this subscriber\n return Option.none();\n }\n };\n```\n\n**Issues:**\n1. Uses eslint-disable to bypass effect/prefer-match-over-ternary\n2. Uses try/catch instead of Effect-based error handling\n3. The justification claims Match would be verbose, but we should verify this\n\n**Goals:**\n1. Remove the eslint-disable comment\n2. Consider using Effect-based error handling (Effect.try or similar)\n3. Evaluate if Match.value is actually more verbose or if it improves code quality\n4. Maintain the same behavior (filter exceptions should silently skip events)\n\n**Files:**\n- packages/eventsourcing-server/src/lib/eventBus.ts\n\n**Testing:**\nAll existing tests should continue to pass, especially the filter exception test.","status":"closed","priority":3,"issue_type":"chore","created_at":"2025-11-15T10:56:05.239179Z","updated_at":"2025-11-15T10:56:05.239179Z","closed_at":"2025-11-02T12:24:22.561989Z","source_repo":"."} -{"id":"hp-2yc","content_hash":"393befa0249ee63a59927846f24a43792b0b6e299a85bb486c3b48360a30e780","title":"Improve no-intermediate-effect-variables to use pipe position as semantic clue","description":"## Problem\n\nThe `no-intermediate-effect-variables` rule currently flags ANY variable that stores a pipe/Effect/Schedule result and is only used once. This is too aggressive - it can't distinguish between:\n\n1. **Intermediate pipeline variables** (should be flagged):\n ```typescript\n const step1 = pipe(data, transform);\n const result = pipe(step1, nextTransform); // step1 is just a pipeline stage\n ```\n\n2. **Configuration constants** (should NOT be flagged):\n ```typescript\n const RETRY_SCHEDULE = pipe(1000, Duration.millis, Schedule.exponential, ...);\n pipe(effect, Effect.retry(RETRY_SCHEDULE)); // RETRY_SCHEDULE is configuration data\n ```\n\n## Root Cause\n\nThe rule counts usages in ANY position within expressions. It doesn't consider WHERE the variable is used, which is the key semantic difference.\n\n## Solution: Use Pipe Position as Semantic Clue\n\nVariables used as the **first argument to `pipe()`** are being transformed (intermediate values).\nVariables used in **other positions** are configuration/data being passed to operations.\n\n**Change the `CallExpression` handler to only count usages when the variable appears as the first argument to `pipe()`:**\n\n```javascript\nCallExpression(node) {\n // Only track when variable is used as FIRST arg to pipe()\n if (node.callee.type === 'Identifier' \u0026\u0026 node.callee.name === 'pipe') {\n const firstArg = node.arguments[0];\n if (firstArg?.type === 'Identifier' \u0026\u0026 trackedVariables.has(firstArg.name)) {\n const tracked = trackedVariables.get(firstArg.name);\n tracked.usageCount++;\n tracked.usageNodes.push(firstArg);\n }\n }\n // Don't count other positions - they're legitimate configuration/data\n}\n```\n\n## Why This Works\n\n- `pipe(temp, fn)` → temp is the value being transformed (intermediate)\n- `Effect.retry(schedule)` → schedule is configuration data (semantic value)\n- `Match.tag({ case1: handler })` → handler is configuration data (semantic value)\n\n## Files\n\n- `/packages/eslint-effect/src/rules/no-intermediate-effect-variables.js` (modify CallExpression handler)\n- `/packages/eslint-effect/test/no-intermediate-effect-variables.test.js` (add test cases for configuration constants)\n\n## Test Cases Needed\n\n1. Configuration constant used in Effect.retry - should NOT be flagged\n2. Schedule constant used in pipe composition - should NOT be flagged \n3. Intermediate variable used as first arg to pipe - SHOULD be flagged\n4. Variable used multiple times - should NOT be flagged (existing behavior)","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-15T08:49:46.039224545Z","updated_at":"2025-11-15T11:10:59.76812Z","closed_at":"2025-11-15T11:10:59.76812Z","source_repo":"."} -{"id":"hp-3","content_hash":"d49b206743a78a2ad5d9245b478aeeaf449672cd06463a5ae7c40ed03934690e","title":"Implement eventsourcing-server package with building block components","description":"Create @codeforbreakfast/eventsourcing-server package with four building block components that eliminate server boilerplate while maintaining flexibility.\n\nBased on learnings from origin/feat/websocket-example spike.\n\n**Components:**\n- EventBus: Server-side pub/sub for process managers\n- CommandDispatcher: Routes WireCommands to aggregates\n- StoreSubscriptionManager: Bridges EventStore to client subscriptions\n- ProtocolBridge: Wires ServerProtocol to CommandDispatcher\n\n**Package structure:**\n- packages/eventsourcing-server/\n- Uses Effect Context/Layer DI throughout\n- Low-level building blocks (not high-level wrappers)\n\n**Success criteria:**\n- All four components implemented with tests\n- Package published\n- Integration tests prove components work together\n\nSee: docs/plans/2025-10-25-eventsourcing-server-components-design.md","status":"open","priority":1,"issue_type":"epic","created_at":"2025-11-15T10:56:05.239768Z","updated_at":"2025-11-15T10:56:05.239768Z","source_repo":"."} -{"id":"hp-4","content_hash":"fb87c627b8265d03e534c05a33baf5002b26e9b338f60a70aa1c9af9fdeee1fe","title":"Implement EventBus component for server-side pub/sub","description":"Implement EventBus component for live server-side event distribution via EventStore.subscribeAll().\n\n**Responsibility:**\nSubscribe to EventStore.subscribeAll() and distribute events to process managers and projections.\n\n**Interface:**\n```typescript\nclass EventBus extends Context.Tag('EventBus')\u003cEventBus, {\n subscribe: (filter: (event: DomainEvent) =\u003e boolean) \n =\u003e Effect.Effect\u003cStream.Stream\u003cDomainEvent\u003e\u003e\n}\u003e() {}\n\nconst EventBusLive: (config: {\n store: EventStoreTag\n}) =\u003e Layer.Layer\u003cEventBus, never, EventStore\u003e\n```\n\n**Key points:**\n- Uses EventStore.subscribeAll() internally to get all events\n- Live-only (no historical replay)\n- Best-effort delivery (not guaranteed)\n- Works across multiple server instances (via EventStore subscription)\n- Type-safe filtering for subscribers\n- Does NOT receive events from CommandDispatcher\n- Scoped lifecycle management\n\n**Implementation approach:**\n1. On layer creation, call store.subscribeAll()\n2. Maintain internal PubSub for subscribers\n3. Pump events: EventStore.subscribeAll() → internal PubSub\n4. Subscribers get filtered stream from PubSub\n\n**Tests:**\n- Subscribe to EventStore.subscribeAll() on startup\n- Events committed to ANY stream appear on EventBus\n- Type-safe filtering works correctly\n- Multiple subscribers receive same event\n- Live-only: events committed before subscription start do NOT appear\n- Multi-instance: events from different server instance appear\n- Scope cleanup (no leaked subscriptions)\n- Integration: Real EventStore with subscribeAll support\n\n**Dependencies:**\n- Blocked by hp-8 (EventStore.subscribeAll() implementation)\n- @codeforbreakfast/eventsourcing-store (EventStore)\n\n**Files:**\n- src/lib/eventBus.ts\n- src/lib/eventBus.test.ts\n\nSee design: docs/plans/2025-10-25-eventsourcing-server-components-design.md","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-15T10:56:05.240333Z","updated_at":"2025-11-15T10:56:05.240333Z","closed_at":"2025-10-27T12:33:14.371476Z","source_repo":".","dependencies":[{"issue_id":"hp-4","depends_on_id":"hp-3","type":"parent-child","created_at":"2025-10-25T18:24:33.659656+01:00","created_by":"graemefoster"}]} -{"id":"hp-5","content_hash":"f1db7294711264052eb55a6699fd12bb5652046f7a3fcafcf25cef09dd58688d","title":"Implement CommandDispatcher for routing WireCommands to aggregates","description":"Implement CommandDispatcher for routing WireCommands to aggregate command methods.\n\n**Responsibility:**\nConvention-based routing: WireCommand.name → aggregate.commands[methodName]\n\n**Interface:**\n```typescript\nclass CommandDispatcher extends Context.Tag('CommandDispatcher')\u003cCommandDispatcher, {\n dispatch: (command: WireCommand) =\u003e Effect.Effect\u003cCommandResult, DispatchError\u003e\n}\u003e() {}\n\nconst CommandDispatcherLive: (config: {\n aggregates: Array\u003cAggregateConfig\u003e\n}) =\u003e Layer.Layer\u003cCommandDispatcher, never, never\u003e\n```\n\n**Key points:**\n- Maps CreateTodo → aggregate.commands.createTodo (camelCase)\n- Loads aggregate state from EventStore\n- Executes command, commits events to EventStore\n- Returns CommandResult (Success/Failure)\n- Does NOT publish to EventBus (EventBus gets events via subscribeAll)\n- All errors become Failure (never crash)\n\n**Tests:**\n- Routes command to correct aggregate method\n- Loads aggregate state before execution\n- Commits events to EventStore\n- Returns Success with position\n- Returns Failure for aggregate errors\n- Handles unknown command name\n- Does NOT publish to EventBus directly\n- Integration: Real aggregate + store\n\n**Dependencies:**\n- Blocked by hp-8 (EventStore.subscribeAll() must exist first)\n- @codeforbreakfast/eventsourcing-aggregates\n- @codeforbreakfast/eventsourcing-commands\n- @codeforbreakfast/eventsourcing-store\n\n**Note:** CommandDispatcher no longer depends on EventBus. It just commits to EventStore, and EventBus receives events via subscribeAll().\n\n**Files:**\n- src/lib/commandDispatcher.ts\n- src/lib/commandDispatcher.test.ts\n\nSee design: docs/plans/2025-10-25-eventsourcing-server-components-design.md","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-15T10:56:05.24093Z","updated_at":"2025-11-15T10:56:05.24093Z","source_repo":".","dependencies":[{"issue_id":"hp-5","depends_on_id":"hp-3","type":"parent-child","created_at":"2025-10-25T18:24:33.86119+01:00","created_by":"graemefoster"},{"issue_id":"hp-5","depends_on_id":"hp-8","type":"blocks","created_at":"2025-10-25T18:44:50.408009+01:00","created_by":"graemefoster"}]} -{"id":"hp-6","content_hash":"de702b6c69e437fd9114ccfeb9a2e840ea3d0d55ff067a875b237a5770199afa","title":"Implement StoreSubscriptionManager for client event delivery","description":"Implement StoreSubscriptionManager to bridge EventStore subscriptions to client subscriptions.\n\n**Responsibility:**\nWatch client subscription state, dynamically subscribe to EventStore, pump events to clients.\n\n**Interface:**\n```typescript\nclass StoreSubscriptionManager extends Context.Tag('StoreSubscriptionManager')\u003c\n StoreSubscriptionManager,\n {\n start: () =\u003e Effect.Effect\u003cnever, SubscriptionError\u003e\n }\n\u003e() {}\n\nconst StoreSubscriptionManagerLive: (config: {\n stores: Array\u003c{ \n tag: EventStoreTag\n streamPattern?: (streamId: string) =\u003e boolean \n }\u003e\n}) =\u003e Layer.Layer\u003cStoreSubscriptionManager, never, ServerProtocol\u003e\n```\n\n**Key design insight:**\nWatches ServerProtocol's subscription HashMap (streamId → connectionIds). When clients subscribe, creates EventStore.subscribe(). When last client unsubscribes, cleans up store subscription.\n\n**Key points:**\n- Monitors ServerProtocol internal state (Ref\u003cServerState\u003e)\n- Creates EventStore subscriptions dynamically\n- Pumps events: EventStore → ServerProtocol.publishEvent()\n- Cleans up when no clients subscribed\n- Handles multiple EventStores (per aggregate type)\n- Does NOT filter/route (ServerProtocol does that)\n\n**Tests:**\n- Client subscribes → creates EventStore subscription\n- Events from store reach ServerProtocol.publishEvent\n- Last client unsubscribes → cleans up store subscription\n- Multiple clients subscribed → single store subscription\n- Handles multiple stores correctly\n- Integration: Real ServerProtocol + EventStore\n\n**Dependencies:**\n- @codeforbreakfast/eventsourcing-protocol (ServerProtocol)\n- @codeforbreakfast/eventsourcing-store (EventStore)\n\n**Challenges:**\n- Need to access ServerProtocol's internal Ref\u003cServerState\u003e\n- May require exposing subscription state or hooks in ServerProtocol\n\n**Files:**\n- src/lib/storeSubscriptionManager.ts\n- src/lib/storeSubscriptionManager.test.ts\n\nSee design: docs/plans/2025-10-25-eventsourcing-server-components-design.md","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-15T10:56:05.241472Z","updated_at":"2025-11-15T10:56:05.241472Z","source_repo":".","dependencies":[{"issue_id":"hp-6","depends_on_id":"hp-3","type":"parent-child","created_at":"2025-10-25T18:24:34.066468+01:00","created_by":"graemefoster"}]} -{"id":"hp-7","content_hash":"e32cfa437167242ca3d43601900ae31674a7401f889527ef039adc27a93cdd89","title":"Implement ProtocolBridge for wiring protocol to dispatcher","description":"Implement ProtocolBridge for wiring ServerProtocol command stream to CommandDispatcher.\n\n**Responsibility:**\nPure wiring function (NOT a service tag). Connects protocol commands to dispatcher, sends results back.\n\n**Interface:**\n```typescript\nconst makeProtocolBridge: (\n protocol: Context.Tag.Service\u003ctypeof ServerProtocol\u003e\n) =\u003e Effect.Effect\u003cnever, BridgeError, CommandDispatcher | Scope.Scope\u003e\n```\n\n**Key points:**\n- Pure function, not a Context.Tag service\n- Wires protocol.onWireCommand → dispatcher.dispatch\n- Sends results back via protocol.sendResult\n- Does NOT handle events (StoreSubscriptionManager does that)\n- Returns Effect\u003cnever\u003e that runs forever\n- Lifecycle managed by Scope (interruption cleans up)\n\n**Implementation:**\n```typescript\nEffect.gen(function* () {\n const dispatcher = yield* CommandDispatcher\n \n yield* pipe(\n protocol.onWireCommand,\n Stream.mapEffect((cmd) =\u003e \n dispatcher.dispatch(cmd).pipe(\n Effect.flatMap((result) =\u003e protocol.sendResult(cmd.id, result))\n )\n ),\n Stream.runDrain\n )\n})\n```\n\n**Tests:**\n- Commands from protocol reach dispatcher\n- Dispatcher results sent back through protocol\n- Command errors handled gracefully\n- One stream failure doesn't crash everything\n- Scope interruption cleans up properly\n- Integration: Full command → dispatcher → result flow\n\n**Dependencies:**\n- CommandDispatcher (hp-5)\n- @codeforbreakfast/eventsourcing-protocol\n\n**Files:**\n- src/lib/protocolBridge.ts\n- src/lib/protocolBridge.test.ts\n\nSee design: docs/plans/2025-10-25-eventsourcing-server-components-design.md","status":"open","priority":1,"issue_type":"task","created_at":"2025-11-15T10:56:05.242027Z","updated_at":"2025-11-15T10:56:05.242027Z","source_repo":".","dependencies":[{"issue_id":"hp-7","depends_on_id":"hp-3","type":"parent-child","created_at":"2025-10-25T18:24:34.271078+01:00","created_by":"graemefoster"},{"issue_id":"hp-7","depends_on_id":"hp-5","type":"blocks","created_at":"2025-10-25T18:24:39.669899+01:00","created_by":"graemefoster"}]} -{"id":"hp-8","content_hash":"60a193979a80222dbd620b709cf6bd516a0d4d3d2873f1e0efd868f17542280e","title":"Add subscribeAll() to EventStore for live cross-stream subscriptions","description":"Add required subscribeAll() method to EventStore interface for live, cross-stream event subscriptions.\n\n**Purpose:**\nEnable EventBus and process managers to receive events from all streams without per-stream subscriptions.\n\n**Design Decisions:**\n- REQUIRED for all EventStore implementations\n- Live-only (no historical replay, no global event number)\n- Best-effort delivery (not guaranteed)\n- Stream-independent (no global ordering required)\n\n**Interface (types TBD during implementation):**\n```typescript\ninterface EventStore\u003cTEvent\u003e {\n // Existing per-stream subscription\n readonly subscribe: (from: EventStreamPosition) \n =\u003e Effect.Effect\u003cStream.Stream\u003cTEvent, ...\u003e, ...\u003e\n \n // NEW: All streams, live-only (REQUIRED)\n readonly subscribeAll: () \n =\u003e Effect.Effect\u003c\n ??? // Stream.Stream or PubSub or something else?\n // Must include: {streamId: string, event: TEvent}\n \u003e\n}\n```\n\n**Implementation Notes:**\n\n**Postgres:**\n- Use existing LISTEN/NOTIFY infrastructure\n- Already triggers on all events (migration 0002)\n- Trivial to implement\n\n**InMemory:**\n- Already has events in memory\n- Simple PubSub broadcast\n\n**Filesystem:**\n- File watcher on base directory\n- Or polling-based\n- More complex but feasible\n\n**Key Questions to Investigate:**\n1. Should this return Stream.Stream, PubSub, or something else?\n2. What Error and Requirement types?\n3. Does it need Scope.Scope for cleanup?\n4. Should stores publish to a provided PubSub instead of returning one?\n\n**Out of Scope:**\n- Historical replay (use per-stream subscribe for that)\n- Global event ordering\n- Guaranteed delivery (use external queues)\n- Event filtering (EventBus does that)\n\n**Use Cases:**\n- EventBus subscribes to get all events for process managers\n- Process managers filter by event type\n- Projections that need cross-stream views\n\n**If process managers need guarantees:**\n- Don't use EventBus/subscribeAll\n- Publish to external queue (SQS, RabbitMQ, etc.) from CommandDispatcher\n- That's out of scope for this package\n\n**Testing:**\n- All three stores implement it\n- Events from any stream appear in subscription\n- Events committed after subscription start appear\n- Historical events do NOT appear (live-only)\n- Scope cleanup works correctly\n- Contract tests in eventsourcing-testing-contracts\n\n**Dependencies:**\nThis blocks EventBus (hp-4) implementation\n\n**Files:**\n- packages/eventsourcing-store/src/lib/services.ts (interface)\n- packages/eventsourcing-store-postgres/src/sqlEventStore.ts\n- packages/eventsourcing-store-inmemory/src/lib/inMemoryEventStore.ts\n- packages/eventsourcing-store-filesystem/src/lib/fileSystemEventStore.ts\n- packages/eventsourcing-testing-contracts/src/lib/store/subscribeAll.contract.ts (new)\n\nSee design: docs/plans/2025-10-25-eventsourcing-server-components-design.md","status":"closed","priority":1,"issue_type":"task","created_at":"2025-11-15T10:56:05.242579Z","updated_at":"2025-11-15T10:56:05.242579Z","closed_at":"2025-10-26T12:14:29.688302Z","source_repo":"."} -{"id":"hp-9","content_hash":"3484b19899a63707a4b1fd0f9429c0dea19622ca57ec27b68915f7f48aef26fa","title":"Refactor to introduce EventStreamPosition wherever there is a pair of stream id and event number","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-15T10:56:05.243079Z","updated_at":"2025-11-15T10:56:05.243079Z","closed_at":"2025-10-26T12:25:06.822815Z","source_repo":"."} diff --git a/.beads/config.yaml b/.beads/config.yaml deleted file mode 100644 index 95c5f3e7..00000000 --- a/.beads/config.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# Beads Configuration File -# This file configures default behavior for all bd commands in this repository -# All settings can also be set via environment variables (BD_* prefix) -# or overridden with command-line flags - -# Issue prefix for this repository (used by bd init) -# If not set, bd init will auto-detect from directory name -# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. -# issue-prefix: "" - -# Use no-db mode: load from JSONL, no SQLite, write back after each command -# When true, bd will use .beads/issues.jsonl as the source of truth -# instead of SQLite database -# no-db: false - -# Disable daemon for RPC communication (forces direct database access) -# no-daemon: false - -# Disable auto-flush of database to JSONL after mutations -# no-auto-flush: false - -# Disable auto-import from JSONL when it's newer than database -# no-auto-import: false - -# Enable JSON output by default -# json: false - -# Default actor for audit trails (overridden by BD_ACTOR or --actor) -# actor: "" - -# Path to database (overridden by BEADS_DB or --db) -# db: "" - -# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) -# auto-start-daemon: true - -# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) -# flush-debounce: "5s" - -# Multi-repo configuration (experimental - bd-307) -# Allows hydrating from multiple repositories and routing writes to the correct JSONL -# repos: -# primary: "." # Primary repo (where this database lives) -# additional: # Additional repos to hydrate from (read-only) -# - ~/beads-planning # Personal planning repo -# - ~/work-planning # Work planning repo - -# Integration settings (access with 'bd config get/set') -# These are stored in the database, not in this file: -# - jira.url -# - jira.project -# - linear.url -# - linear.api-key -# - github.org -# - github.repo -# - sync.branch - Git branch for beads commits (use BEADS_SYNC_BRANCH env var or bd config set) diff --git a/.beads/metadata.json b/.beads/metadata.json deleted file mode 100644 index 6e36c945..00000000 --- a/.beads/metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "database": "beads.db", - "jsonl_export": "issues.jsonl", - "last_bd_version": "0.25.1" -} \ No newline at end of file diff --git a/.claude/commands/automerge.md b/.claude/commands/automerge.md index 36fb91eb..48aeed22 100644 --- a/.claude/commands/automerge.md +++ b/.claude/commands/automerge.md @@ -30,7 +30,7 @@ This command automates the entire process of getting changes merged into main: - Alert when merged successfully or if merge fails 9. **Clean up** after successful merge: -- Navigate back to repo root: `cd ../../` (from worktrees/{feature-name} to brownsauce/) +- Navigate back to repo root: `cd ../../` (from worktrees/{feature-name} to eventsourcing/) - Pull latest changes to main: `git pull origin main` - Remove the feature worktree: `git worktree remove worktrees/{feature-name}` - Delete the local feature branch: `git branch -d feat/{feature-name}` diff --git a/.claude/commands/commit-beads.md b/.claude/commands/commit-beads.md deleted file mode 100644 index 722e8451..00000000 --- a/.claude/commands/commit-beads.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -allowed-tools: Bash(git status:*), Bash(git checkout:*), Bash(git add:*), Bash(git commit:*), Bash(git push:*), Bash(git pull:*), Bash(gh pr:*), Bash(git branch:*), Bash(cd:*), Bash(pwd:*), Bash(git diff:*), Bash(bd list:*), Bash(bd show:*) -description: Commit beads database changes to a dedicated branch with meaningful names based on actual changes ---- - -## Your task - -This command handles committing the shared `.beads/` database to its own branch and PR, with intelligent naming based on what actually changed. - -1. **Navigate to repo root:** - - Run `pwd` to check current location - - If in a worktree, navigate back to repo root by using `cd` with the full path to brownsauce (not a worktree) - - Run `pwd` again to confirm you're at repo root - -2. **Check for beads changes:** - - Run `git status .beads/` to check if beads has uncommitted changes - - If no changes, inform user and exit - -3. **Analyze what changed:** - - Run `git diff .beads/issues.jsonl` to see the raw changes - - Parse the diff to identify: - - New issues created (look for lines starting with `+` that have full issue records) - - Issues closed (status changed to "closed") - - Issues updated (status changes, priority changes, etc.) - - New dependencies added - - Generate a descriptive summary like: - - "created hp-123, hp-124, hp-125" (if multiple issues created) - - "closed hp-42" (if single issue closed) - - "updated 3 issues" (if general updates) - - Use the most significant change as the primary descriptor - -4. **Create descriptive branch name:** - - Based on analysis, create a kebab-case branch name like: - - `chore/beads-create-hp-123-hp-124` (for new issues) - - `chore/beads-close-hp-42` (for closing issues) - - `chore/beads-update-dependencies` (for dep changes) - - `chore/beads-sync-$(date +%Y%m%d)` (fallback for complex changes) - -5. **Update main and create branch:** - - Run `git pull origin main` to ensure main is up to date - - Create the descriptive branch: `git checkout -b {branch-name}` - -6. **Commit beads changes:** - - Run `git add .beads/` - - Create descriptive commit message based on the same analysis: - - `chore: create beads issues hp-123, hp-124, hp-125` - - `chore: close beads issue hp-42` - - `chore: update beads dependencies` - - Run `git commit -m "{descriptive-message}"` - - Run `git push -u origin HEAD` - -7. **Create PR with automerge:** - - Create PR with same descriptive title: `gh pr create --title "{commit-message}" --body "Beads database sync: {brief-summary-of-changes}"` - - Enable automerge with squash: `gh pr merge --auto --squash` - - Show PR URL to user - -8. **Monitor and report:** - - Run `gh pr checks --watch` to monitor CI checks - - After checks pass, verify merge status with `gh pr status` - - Report when merged successfully - -9. **Clean up after merge:** - - Return to main: `git checkout main` - - Pull latest: `git pull origin main` - - Delete local beads branch using the actual branch name created in step 4 - -## Important notes - -- MUST run from repo root, not from a worktree -- Analyze changes BEFORE creating branch/commit to generate meaningful names -- If diff is complex, use bd commands to get context (e.g., `bd show hp-123` to see what the issue is about) -- Branch names should be concise but descriptive -- Squash merge keeps git history clean -- If checks fail, report failure and leave branch intact for debugging diff --git a/.claude/commands/start.md b/.claude/commands/start.md index 4876a2f3..beb93e41 100644 --- a/.claude/commands/start.md +++ b/.claude/commands/start.md @@ -12,7 +12,7 @@ IMPORTANT: Always use worktrees for feature development to maintain clean separa 1. **Ensure we're in the repo root and up to date:** - Run `pwd` to confirm current location - Run `git fetch origin main` to get latest from remote - - If not in repo root (brownsauce/), navigate back to it first + - If not in repo root (eventsourcing/), navigate back to it first - Run `git pull origin main` to update main branch 2. **Create new worktree for feature branch:** diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 807d5983..00000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ - -# Use bd merge for beads JSONL files -.beads/issues.jsonl merge=beads diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f433525..468ebd91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,43 +34,16 @@ jobs: with: fetch-depth: 0 - - name: Check if beads-only PR - id: beads_check - run: | - # Get list of changed files compared to PR base branch - FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) - echo "Changed files:" - echo "$FILES" - - # Check if ONLY .beads/issues.jsonl changed - if [ "$FILES" = ".beads/issues.jsonl" ]; then - echo "beads_only=true" >> $GITHUB_OUTPUT - echo "✓ Beads-only PR detected - will skip CI" - else - echo "beads_only=false" >> $GITHUB_OUTPUT - echo "✓ Code changes detected - will run full CI" - fi - - - name: Skip CI for beads-only PR - if: steps.beads_check.outputs.beads_only == 'true' - run: | - echo "✓ Beads-only PR - skipping build, lint, and test" - echo "This PR only changes .beads/issues.jsonl (beads issue tracker database)" - echo "No code validation needed - marking as success" - exit 0 - - name: Setup - if: steps.beads_check.outputs.beads_only == 'false' uses: ./.github/actions/setup with: turbo-cache-key: 'ci' - name: Validate release readiness - if: steps.beads_check.outputs.beads_only == 'false' && github.event_name == 'pull_request' + if: github.event_name == 'pull_request' run: bun run release:validate - name: Build, lint, test, and validate architecture - if: steps.beads_check.outputs.beads_only == 'false' run: bun run all env: TEST_PG_USERNAME: postgres diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3315ecfc..a7fc14bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,40 +23,13 @@ jobs: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - - name: Check if beads-only commit - id: beads_check - run: | - # Get list of changed files in the last commit - FILES=$(git diff --name-only HEAD~1 HEAD) - echo "Changed files in last commit:" - echo "$FILES" - - # Check if ONLY .beads/issues.jsonl changed - if [ "$FILES" = ".beads/issues.jsonl" ]; then - echo "beads_only=true" >> $GITHUB_OUTPUT - echo "✓ Beads-only commit detected - will skip release" - else - echo "beads_only=false" >> $GITHUB_OUTPUT - echo "✓ Code changes detected - will run release workflow" - fi - - - name: Skip release for beads-only commit - if: steps.beads_check.outputs.beads_only == 'true' - run: | - echo "✓ Beads-only commit - skipping release workflow" - echo "This commit only changes .beads/issues.jsonl (beads issue tracker database)" - echo "No packages changed - nothing to release" - exit 0 - - name: Setup - if: steps.beads_check.outputs.beads_only == 'false' uses: ./.github/actions/setup with: turbo-cache-key: 'release' # Generate a GitHub App token for verified commits - name: Generate GitHub App Token - if: steps.beads_check.outputs.beads_only == 'false' id: github-app-token uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 with: @@ -64,11 +37,9 @@ jobs: private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - name: Auto-generate changeset for dependency updates - if: steps.beads_check.outputs.beads_only == 'false' run: bun run scripts/auto-generate-dependency-changeset.ts - name: Create Release Pull Request or Publish to npm - if: steps.beads_check.outputs.beads_only == 'false' id: changesets uses: changesets/action@v1 with: diff --git a/CLAUDE.md b/CLAUDE.md index 9dae9714..985c8f7a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,8 +16,7 @@ ## Worktree Structure ``` -brownsauce/ <- Repo root (main branch checkout) -├── .beads/ <- Shared issue database +eventsourcing/ <- Repo root (main branch checkout) ├── .git/ <- Git directory ├── worktrees/ <- All feature worktrees go here │ ├── feature-name-1/ <- Feature worktree @@ -32,7 +31,6 @@ brownsauce/ <- Repo root (main branch checkout) - Never risk contaminating main branch with uncommitted changes - Can work on multiple features simultaneously in parallel worktrees - Clean separation between repo root and feature development -- Shared `.beads/` database discoverable from all worktrees ## Before Starting Work @@ -42,141 +40,6 @@ brownsauce/ <- Repo root (main branch checkout) - Verify you're in correct worktree with `pwd` and `git branch` - Each worktree is a complete working copy with its own node_modules and mise config -## Beads (bd) Issue Tracking - -We track work in Beads (bd) instead of Markdown. Beads is a lightweight, git-based issue tracker designed for AI coding agents with dependency-aware task management. - -### Critical Setup Notes - -- **ALWAYS use `bd` CLI commands via Bash tool** - NEVER use MCP beads tools -- Daemon is disabled (`BEADS_NO_DAEMON=1`) for worktree safety - MCP won't work -- bd auto-discovers the shared `.beads/` database from any worktree by walking up the tree -- The `.beads/` directory lives at the repo root and is shared across all feature worktrees -- Works from repo root or any feature worktree - bd walks up to find the database - -### The "Let's Continue" Protocol - -**Start of every session:** - -1. Check for abandoned work: `bd list --status in_progress` -2. If none, get ready work: `bd ready --limit 5` -3. Show top priority issue: `bd show hp-X` - -When user says **"Let's continue"**, run these commands to resume work. - -### Essential Commands - -**Finding Work:** - -- `bd ready` - Show tasks with no blockers (ready to work on) -- `bd ready --limit 5` - Show top 5 ready tasks -- `bd ready --priority 0` - Show only P0 ready tasks -- `bd list --status open` - List all open issues -- `bd list --status in_progress` - See what's currently being worked on -- `bd blocked` - Show blocked issues and what's blocking them -- `bd stats` - Show project statistics and progress - -**Viewing Issues:** - -- `bd show hp-123` - Show detailed info about an issue (including dependencies) -- `bd dep tree hp-123` - Show full dependency tree for an issue - -**Creating Issues:** - -- `bd create "Task title" -p 1` - Create task (priority 0=highest, 4=lowest) -- `bd create "Bug description" -p 0 -t bug` - Create a bug -- `bd create "New feature" -p 2 -t feature` - Create a feature -- `bd create "Epic: User Management" -p 1 -t epic` - Create an epic -- `bd create "Found issue" --deps discovered-from:hp-42` - Create discovered work - -**Issue Types:** bug, feature, task, epic, chore - -**Updating Issues:** - -- `bd update hp-123 --status in_progress` - Claim work (mark as in-progress) -- `bd update hp-123 --status blocked` - Mark as blocked -- `bd update hp-123 --status open` - Unblock/reopen -- `bd update hp-123 --priority 0` - Change priority -- `bd close hp-123 --reason "Completed the work"` - Close an issue - -**Status Values:** open, in_progress, blocked, closed - -**Dependencies:** - -- `bd dep add hp-456 hp-123` - Make hp-456 depend on hp-123 (hp-456 is blocked by hp-123) -- `bd dep tree hp-456` - Show dependency tree -- `bd dep remove hp-456 hp-123` - Remove a dependency - -**Dependency Types:** - -- `blocks` (default) - Hard blocker, prevents work -- `related` - Soft link, no blocking behavior -- `parent-child` - Epic/subtask relationship -- `discovered-from` - Found during work on another issue - -### Workflow Patterns - -**Working on a Task:** - -```bash -# 1. Find ready work -bd ready - -# 2. Claim it -bd update hp-123 --status in_progress - -# 3. Do the work... - -# 4. Complete it -bd close hp-123 --reason "Implemented feature with tests" - -# 5. Check what's ready now (dependencies may have unblocked) -bd ready -``` - -**Discovering Blockers:** - -```bash -# You realize hp-456 needs OAuth setup first -bd create "Set up OAuth providers" -p 1 -t task -bd dep add hp-456 hp-789 # hp-456 now blocked by hp-789 -bd update hp-456 --status blocked -``` - -**Breaking Down Epics:** - -```bash -bd create "Epic: User Management" -p 1 -t epic -bd create "User registration flow" -p 1 -t task -bd create "User login/logout" -p 1 -t task -bd dep add hp-11 hp-10 --type parent-child -bd dep add hp-12 hp-10 --type parent-child -``` - -### When to Create Issues - -**DO create issues for:** - -- Multi-step features that need planning -- Bugs discovered during work (use `discovered-from` dependency) -- Work that has dependencies or blockers -- Tasks that span multiple sessions -- Work that needs tracking across the team - -**DON'T create issues for:** - -- Trivial fixes you're doing immediately -- Simple one-line changes -- Work you're already completing in the current session - -### Important Notes - -- `bd ready` only shows issues with NO open blockers - this is the work queue -- Closing an issue may unblock other issues (check `bd ready` after closing) -- All changes are auto-synced to `.beads/issues.jsonl` (committed to git) -- Issue IDs use the prefix `hp-` (configured during `bd init`) -- Dependencies are directional: `bd dep add A B` means A depends on B (A is blocked by B) - ## Releasing - Start each new piece of work in a new branch from the latest origin/main. Changes are always submitted via a PR. diff --git a/docs/plans/2025-10-26-skip-ci-beads-prs-design.md b/docs/plans/2025-10-26-skip-ci-beads-prs-design.md deleted file mode 100644 index 648e663d..00000000 --- a/docs/plans/2025-10-26-skip-ci-beads-prs-design.md +++ /dev/null @@ -1,139 +0,0 @@ -# Skip CI Workflows for Beads-Only PRs - -**Date:** 2025-10-26 -**Status:** Approved -**Issue:** hp-11 - -## Problem - -The CI workflow runs build, lint, test, and validation on every PR to main, including PRs that only modify the beads issue database (`.beads/issues.jsonl`). This is unnecessary and wasteful: - -- Beads-only PRs have no code changes to validate -- CI takes several minutes and consumes GitHub Actions quota -- Recent example: PR #324 changed only `.beads/issues.jsonl` but triggered full CI - -Similarly, when beads-only PRs merge to main, the release workflow runs unnecessarily since there are no packages to version or publish. - -## Solution: Use `paths-ignore` Filter - -Add GitHub Actions' built-in `paths-ignore` filter to both CI and release workflows to completely skip execution when only `.beads/issues.jsonl` changes. - -### Implementation - -**ci.yml** - Skip CI workflow for beads-only PRs: - -```yaml -on: - pull_request: - branches: [main] - paths-ignore: - - '.beads/issues.jsonl' -``` - -**release.yml** - Skip release workflow for beads-only pushes to main: - -```yaml -on: - push: - branches: - - main - paths-ignore: - - '.beads/issues.jsonl' -``` - -**publish-with-token.yml** - No change (manual workflow only) - -### Behavior - -When a PR only changes `.beads/issues.jsonl`: - -- GitHub doesn't trigger the CI workflow at all -- No workflow status appears in PR checks -- PR is mergeable without waiting for CI -- After merge, release workflow also skips - -When a PR changes `.beads/issues.jsonl` AND any other file: - -- Workflows run normally (paths-ignore doesn't apply) -- Full CI validation occurs - -### Edge Cases - -1. **Mixed changes** - PR changes `.beads/issues.jsonl` + other files - - Workflows run normally - - Any code change needs full CI validation - -2. **Renovate PRs** - Bot PRs that update dependencies - - Workflows run normally (don't touch `.beads/issues.jsonl`) - - Dependency updates need CI to catch breaking changes - -3. **Multiple beads files** (future-proofing) - - Only `.beads/issues.jsonl` is currently ignored - - Future beads files won't be ignored unless explicitly added - - Conservative approach - only skip what we know is safe - -4. **Empty PRs** - PR with no file changes - - Workflows run (GitHub doesn't treat this as paths-ignore match) - - Edge case that shouldn't happen in practice - -### Testing and Validation - -**Verification steps:** - -1. **Beads-only PR test** - - Change only `.beads/issues.jsonl` - - Verify no CI workflow appears in PR checks - - Verify PR is mergeable - -2. **Mixed-change PR test** - - Change `.beads/issues.jsonl` + any code file - - Verify CI workflow runs normally - - Verify all checks complete - -3. **Post-merge test** - - Merge a beads-only PR to main - - Verify release workflow doesn't run - - Check Actions tab shows no release workflow triggered - -4. **Renovate test** - - Wait for next renovate PR - - Verify CI runs normally for dependency updates - -**Rollback plan:** -Remove the `paths-ignore` lines from both workflows to return to previous behavior immediately. The change is purely additive to trigger conditions. - -### Documentation Updates - -Update CLAUDE.md to document that beads-only PRs skip CI. No user-facing documentation needed - this is transparent automation behavior. - -## Alternatives Considered - -**Conditional job with changed-files action:** - -- Add detection job using changed-files action -- Make CI job depend on detection result -- More flexible but adds complexity and extra job -- Rejected: Unnecessary complexity for simple use case - -**Early exit in CI job:** - -- Keep workflow trigger unchanged -- Detect file changes at start of CI job and exit early -- Workflow still runs but exits immediately -- Rejected: Workflow still consumes Actions quota and appears in PR checks - -## Trade-offs - -**Benefits:** - -- Faster PR merges for beads-only changes -- Reduced GitHub Actions quota consumption -- Cleaner PR checks UI (no irrelevant workflows) - -**Drawbacks:** - -- Beads-only PRs show no CI checks in GitHub UI -- If branch protection requires CI checks, rules need adjustment -- Less obvious that automation considered and skipped the PR - -The benefits outweigh drawbacks since beads-only PRs have nothing to validate and the skip behavior is intentional. diff --git a/docs/plans/2025-10-26-skip-ci-beads-prs-implementation.md b/docs/plans/2025-10-26-skip-ci-beads-prs-implementation.md deleted file mode 100644 index 23304fa0..00000000 --- a/docs/plans/2025-10-26-skip-ci-beads-prs-implementation.md +++ /dev/null @@ -1,379 +0,0 @@ -# Skip CI Workflows for Beads-Only PRs - Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Skip CI and release workflows when PRs only change `.beads/issues.jsonl` to reduce Actions quota usage and speed up beads-only PR merges. - -**Architecture:** Add GitHub Actions `paths-ignore` filter to workflow triggers in `ci.yml` and `release.yml`. This declarative approach prevents workflow execution entirely when only ignored paths change. - -**Tech Stack:** GitHub Actions workflow YAML configuration - ---- - -## Task 1: Update CI workflow to skip beads-only PRs - -**Files:** - -- Modify: `.github/workflows/ci.yml:4-6` - -**Step 1: Read current ci.yml trigger configuration** - -Run: `cat .github/workflows/ci.yml | head -20` - -Expected: See current trigger block starting at line 4: - -```yaml -on: - pull_request: - branches: [main] -``` - -**Step 2: Add paths-ignore to CI workflow trigger** - -Edit `.github/workflows/ci.yml` lines 4-6 to add `paths-ignore`: - -```yaml -on: - pull_request: - branches: [main] - paths-ignore: - - '.beads/issues.jsonl' -``` - -**Step 3: Verify YAML syntax is valid** - -Run: `bun run -c 'import yaml from "js-yaml"; import fs from "fs"; console.log(yaml.load(fs.readFileSync(".github/workflows/ci.yml", "utf8")) ? "✓ Valid YAML" : "✗ Invalid");'` - -Alternative: Use online YAML validator or GitHub's workflow validation - -Expected: YAML is syntactically valid - -**Step 4: Commit CI workflow change** - -```bash -git add .github/workflows/ci.yml -git commit -m "ci: skip CI workflow for beads-only PRs - -Add paths-ignore filter to skip CI when only .beads/issues.jsonl changes. -This reduces Actions quota usage and speeds up beads-only PR merges. - -Issue: hp-11" -``` - ---- - -## Task 2: Update release workflow to skip beads-only merges - -**Files:** - -- Modify: `.github/workflows/release.yml:4-7` - -**Step 1: Read current release.yml trigger configuration** - -Run: `cat .github/workflows/release.yml | head -20` - -Expected: See current trigger block starting at line 4: - -```yaml -on: - push: - branches: - - main -``` - -**Step 2: Add paths-ignore to release workflow trigger** - -Edit `.github/workflows/release.yml` lines 4-7 to add `paths-ignore`: - -```yaml -on: - push: - branches: - - main - paths-ignore: - - '.beads/issues.jsonl' -``` - -**Step 3: Verify YAML syntax is valid** - -Run: `bun run -c 'import yaml from "js-yaml"; import fs from "fs"; console.log(yaml.load(fs.readFileSync(".github/workflows/release.yml", "utf8")) ? "✓ Valid YAML" : "✗ Invalid");'` - -Expected: YAML is syntactically valid - -**Step 4: Commit release workflow change** - -```bash -git add .github/workflows/release.yml -git commit -m "ci: skip release workflow for beads-only merges - -Add paths-ignore filter to skip release workflow when only -.beads/issues.jsonl changes are merged to main. No packages change -in beads-only merges, so release workflow has nothing to do. - -Issue: hp-11" -``` - ---- - -## Task 3: Document behavior in CLAUDE.md - -**Files:** - -- Modify: `CLAUDE.md` (add new section after Beads documentation) - -**Step 1: Find insertion point in CLAUDE.md** - -Run: `grep -n "## Beads" CLAUDE.md` - -Expected: Find the Beads section (likely around line 40-50) - -**Step 2: Add CI skip documentation after Beads section** - -Add this new section after the "Beads (bd) Issue Tracking" section in `CLAUDE.md`: - -```markdown -### CI Workflow Optimization - -**Beads-only PRs skip CI:** - -- PRs that only change `.beads/issues.jsonl` automatically skip CI and release workflows -- Uses GitHub Actions `paths-ignore` filter for complete skip (no workflow run at all) -- These PRs show no CI checks in GitHub UI - this is intentional and correct -- Beads-only PRs are mergeable immediately without waiting for CI - -**When CI still runs:** - -- Any PR that changes `.beads/issues.jsonl` + other files runs CI normally -- Renovate dependency update PRs always run CI -- Any code, config, or dependency changes require full validation -``` - -**Step 3: Verify documentation clarity** - -Read the new section and ensure: - -- Explains what happens (skip CI for beads-only PRs) -- Explains why (no code to validate) -- Clarifies edge cases (mixed changes, renovate) -- Notes the UI behavior (no checks shown) - -**Step 4: Commit documentation** - -```bash -git add CLAUDE.md -git commit -m "docs: document CI skip for beads-only PRs - -Explain that beads-only PRs skip CI workflows and show no checks -in GitHub UI. Clarify when CI still runs (mixed changes, renovate). - -Issue: hp-11" -``` - ---- - -## Task 4: Push branch and create PR - -**Files:** N/A (git operations) - -**Step 1: Push feature branch to origin** - -Run: `git push -u origin feat/skip-ci-beads-prs` - -Expected: Branch pushed successfully, remote tracking set up - -**Step 2: Create pull request** - -Run: - -```bash -gh pr create --title "ci: skip CI workflows for beads-only PRs" --body "$(cat <<'EOF' -## Summary - -- Add `paths-ignore` filter to CI workflow to skip when only `.beads/issues.jsonl` changes -- Add `paths-ignore` filter to release workflow to skip on beads-only merges to main -- Document behavior in CLAUDE.md - -## Why - -Beads-only PRs have no code changes to validate. Skipping CI reduces Actions quota usage and speeds up PR merges. - -## Behavior - -**Beads-only PRs:** -- No CI workflow runs (complete skip) -- No checks appear in PR UI -- Mergeable immediately - -**Mixed changes:** -- CI runs normally -- Full validation occurs - -## Testing - -This PR itself will trigger CI because it changes workflow files + docs. - -After merge, test by: -1. Creating a PR that only changes `.beads/issues.jsonl` -2. Verifying no CI checks appear -3. Verifying PR is mergeable - -## Related - -Closes hp-11 -EOF -)" -``` - -Expected: PR created successfully with PR number - -**Step 3: Verify PR shows CI checks** - -Run: `gh pr view --web` - -Expected: - -- PR opens in browser -- CI workflow IS running (because this PR changes workflows + docs, not just beads) -- This confirms CI runs for non-beads-only changes - -**Step 4: Note PR number for testing** - -Run: `gh pr view --json number -q .number` - -Expected: Returns PR number (e.g., "325") - -Make note of this for post-merge verification testing. - ---- - -## Task 5: Post-merge verification (after PR merges) - -**Prerequisites:** - -- PR from Task 4 has been reviewed and merged to main -- You're back in the repo root or main worktree - -**Files:** - -- Create: `.beads/issues.jsonl` (test change only) - -**Step 1: Create test worktree for beads-only PR** - -Run: - -```bash -cd /Users/graemefoster/Development/brownsauce -git fetch origin main -git worktree add worktrees/test-beads-ci-skip -b test/beads-ci-skip -cd worktrees/test-beads-ci-skip -``` - -Expected: New test worktree created - -**Step 2: Make beads-only change** - -Run: - -```bash -bd create "Test CI skip functionality" -p 4 -t task -``` - -Expected: New issue created in `.beads/issues.jsonl` - -**Step 3: Commit and push beads-only change** - -Run: - -```bash -git add .beads/issues.jsonl -git commit -m "test: verify CI skip for beads-only PRs" -git push -u origin test/beads-ci-skip -``` - -Expected: Branch pushed successfully - -**Step 4: Create test PR and verify no CI** - -Run: - -```bash -gh pr create --title "test: verify CI skip for beads-only PRs" --body "This PR only changes .beads/issues.jsonl to verify CI skip works. Should show NO CI checks." -``` - -Expected: PR created - -**Step 5: Check PR has no CI workflow** - -Run: `gh pr checks` - -Expected: No checks listed, or message indicating no checks required - -Alternative: `gh pr view --web` and visually confirm no CI checks appear in the PR - -**Step 6: Verify PR is mergeable** - -Run: `gh pr view --json mergeable,mergeStateStatus -q '{mergeable: .mergeable, status: .mergeStateStatus}'` - -Expected: `mergeable: MERGEABLE` (even without CI checks) - -**Step 7: Close test PR and clean up** - -Run: - -```bash -gh pr close --delete-branch --comment "Verification complete: CI correctly skipped for beads-only PR" -bd close $(bd list --status open | grep "Test CI skip" | awk '{print $1}') --reason "Test complete" -cd /Users/graemefoster/Development/brownsauce -git worktree remove worktrees/test-beads-ci-skip -``` - -Expected: Test PR closed, branch deleted, beads issue closed, worktree removed - -**Step 8: Verify release workflow also skips** - -Check GitHub Actions tab for main branch after merging the test PR: - -- Go to: `gh repo view --web` → Actions tab -- Verify no release workflow triggered by the test PR merge -- Only beads-only merge, so release should be skipped - -Expected: No release workflow run for the test PR merge - ---- - -## Success Criteria - -- [ ] CI workflow has `paths-ignore: ['.beads/issues.jsonl']` in trigger -- [ ] Release workflow has `paths-ignore: ['.beads/issues.jsonl']` in trigger -- [ ] CLAUDE.md documents the CI skip behavior -- [ ] Implementation PR merged successfully -- [ ] Test beads-only PR shows no CI checks -- [ ] Test beads-only PR is mergeable without CI -- [ ] Release workflow doesn't run for beads-only merge to main -- [ ] Mixed-change PRs (like implementation PR) still run CI normally - -## Notes for Engineer - -**This is workflow configuration, not code:** - -- No unit tests to write (TDD doesn't apply here) -- Verification is done through actual PR creation and observation -- The "tests" are manual checks in GitHub UI - -**YAML syntax matters:** - -- Indentation must be exact (2 spaces per level) -- `paths-ignore` is an array, needs the `-` prefix -- Quote the path string for safety - -**Git worktrees:** - -- Post-merge verification uses a fresh worktree to avoid contaminating main -- This project uses worktrees for all feature work -- Test worktree is temporary and gets cleaned up after verification - -**If something goes wrong:** - -- Remove `paths-ignore` lines to rollback immediately -- Workflows return to previous behavior (run on all PRs) -- No code changes involved, so rollback is zero-risk