From b5adb9c55f4809a6c0ffd79c2c3528759a4130e5 Mon Sep 17 00:00:00 2001 From: Graeme Foster <80714+GraemeF@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:35:41 +0100 Subject: [PATCH] chore: stop tracking beads in the repo The issue tracker is now private to each checkout, so the repo no longer carries its data or the tooling built around committing it. Also corrects the repo root name in CLAUDE.md and the start and automerge commands. --- .beads/.gitignore | 30 -- .beads/beads.jsonl | 22 - .beads/config.yaml | 56 --- .beads/metadata.json | 5 - .claude/commands/automerge.md | 2 +- .claude/commands/commit-beads.md | 74 ---- .claude/commands/start.md | 2 +- .gitattributes | 3 - .github/workflows/ci.yml | 29 +- .github/workflows/release.yml | 29 -- CLAUDE.md | 139 +------ .../2025-10-26-skip-ci-beads-prs-design.md | 139 ------- ...-10-26-skip-ci-beads-prs-implementation.md | 379 ------------------ 13 files changed, 4 insertions(+), 905 deletions(-) delete mode 100644 .beads/.gitignore delete mode 100644 .beads/beads.jsonl delete mode 100644 .beads/config.yaml delete mode 100644 .beads/metadata.json delete mode 100644 .claude/commands/commit-beads.md delete mode 100644 .gitattributes delete mode 100644 docs/plans/2025-10-26-skip-ci-beads-prs-design.md delete mode 100644 docs/plans/2025-10-26-skip-ci-beads-prs-implementation.md 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