brainiac-basecamp 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3c426c0dc0b73e9addf70df378a26ac9d60bb08b00c7ed9cce2f99014d0284e
4
- data.tar.gz: b1c4810e7141e32f45a0010e1b200b0f37912e477d4648fa5353dfbef3980f2f
3
+ metadata.gz: 996b4eddefd0655105a9dad206a11351f44a73e8a82f4d9916c81886482f569e
4
+ data.tar.gz: 9bf1fd7d2b23844ee22325231b480ddd5bb61f0929dda9aa07ee2e7c91326789
5
5
  SHA512:
6
- metadata.gz: 35b654d7e4a7248a4eb05f17ccc0e8e90d7b9ab5736669bb9a55189f760dc5646f139856f6b3214a045dba85f4edca3069d0076633e6235931df1155de7a26a2
7
- data.tar.gz: eedcd8be6c31168cffa6bf4a2460aa6f9ce280878fde13dc27395d57695580060d67f37386532cdb7cec109c77e13f3163fe973286363c40de5014248f94732b
6
+ metadata.gz: 3f47af6aa9bebeb865acec5409083042a258a5b427887dc9c88efe25230f632158f3f23845d70b0a790fc75c14e3dff4a7e7b480dce9ebb1b51c8f2af453ec4d
7
+ data.tar.gz: 2790d88373c841790cd1e495bb9b7b7dd38d283729fbe0ba9b590ab09374d48dd8c719425e3a1487b4effdc3fff55b5796ab1ea25587372a1475fc73634372c2
data/README.md CHANGED
@@ -1,201 +1,184 @@
1
1
  # brainiac-basecamp
2
2
 
3
- Basecamp epic orchestration plugin for [Brainiac](https://github.com/stowzilla/brainiac). Manages high-level epics in Basecamp while agents execute individual tasks via Fizzy cards — with dependency tracking, sequential dispatch, review gates, and bidirectional status sync.
3
+ Basecamp epic orchestration plugin for [Brainiac](https://github.com/stowzilla/brainiac). Manages high-level epics in Basecamp while agents execute individual tasks via Fizzy cards — with dependency tracking, parallel dispatch, review gates, and bidirectional status sync.
4
4
 
5
5
  ## How It Works
6
6
 
7
7
  1. Create a Basecamp **todolist** with the `Epic:` prefix
8
- 2. Add todos to it — each one references a Fizzy card with a clickable link in the description
8
+ 2. Add todos to it — each one references a Fizzy card number in the title
9
9
  3. Assign any todo in the list to your bot account
10
10
  4. The plugin receives the webhook, reads the todolist, builds the dependency graph, and starts orchestrating
11
- 5. Unblocked Fizzy cards are assigned to agents automatically
12
- 6. As cards complete (and optionally pass review), the plugin marks Basecamp todos done and dispatches the next wave
13
- 7. When all tasks finish, a summary message is posted and the epic is marked complete
11
+ 5. Unblocked Fizzy cards are assigned to agents automatically (in parallel if independent)
12
+ 6. After each task completes, an epic review agent checks if the plan still makes sense
13
+ 7. When PRs are opened, review gate agents (GLaDOS, Threepio) review in parallel
14
+ 8. After all gates approve, the implementation agent makes the final decision and merges
15
+ 9. When all tasks finish, a final PR is opened from the epic branch to main
14
16
 
15
- ## Epic Format (Option C: Todolist + Rich Descriptions)
17
+ ## Epic Format
16
18
 
17
- Create a Basecamp todolist:
19
+ Create a Basecamp todolist with the `Epic:` prefix:
18
20
 
19
21
  ```
20
22
  Todolist: "Epic: Build Authentication System"
21
23
 
22
24
  Todos:
23
- "#1234 — Set up auth models"
24
- Description: Fizzy: <link to #1234>
25
- Depends on: none
26
-
27
- □ "#1235 — Add API endpoints"
28
- Description: Fizzy: <link to #1235>
29
- Depends on: #1234
30
-
31
- □ "#1236 — Frontend login form"
32
- Description: Fizzy: <link to #1236>
33
- Depends on: #1234, #1235
25
+ □ #1234 — Set up auth models
26
+ #1235 Add API endpoints [depends:1234]
27
+ #1236 — Frontend login form [depends:1234,1235]
34
28
  ```
35
29
 
36
- Each todo's description contains:
37
- - A **clickable link** to the Fizzy card (`<a href="https://app.fizzy.do/org/cards/1234">#1234</a>`)
38
- - **Dependencies** in `[depends:1234,1235]` or `Depends on: #1234, #1235` format
39
- - Optionally, the assigned **agent** name
40
-
41
- The plugin auto-generates these descriptions via `Epic.build_todo_description`.
30
+ - **Card reference:** `#1234` in the title links to Fizzy card
31
+ - **Dependencies:** `[depends:1234,1235]` in the title declares dependencies
32
+ - Cards without dependencies (or with all deps satisfied) dispatch in parallel
42
33
 
43
- ## Review Gate
34
+ ## Review Gate Modes
44
35
 
45
- Two modes (configured via `brainiac basecamp set review-gate`):
36
+ Configure via `brainiac basecamp set review-gate <mode>`:
46
37
 
47
38
  | Mode | Behavior |
48
39
  |------|----------|
49
- | `on_complete` (default) | Advance to next task as soon as agent finishes |
50
- | `on_pr_merge` | Wait for PR merge before marking task done and unblocking dependents |
40
+ | `on_complete` | Advance immediately when agent finishes |
41
+ | `on_pr_merge` | Wait for PR merge to main before advancing |
42
+ | `epic_branch` | PRs target an epic branch; review gates + final decision before merge |
51
43
 
52
- The `on_pr_merge` mode hooks into `:pr_merged` events from brainiac-github. This gives you a full review cycle between each epic task.
44
+ ### Epic Branch Mode (Recommended)
53
45
 
54
- ## Prerequisites
46
+ The `epic_branch` mode provides the most control:
55
47
 
56
- - [Basecamp CLI](https://github.com/basecamp/basecamp-cli) installed and authenticated
57
- - A Basecamp bot user account (for webhook-triggered orchestration)
58
- - brainiac-fizzy plugin (cards already exist in Fizzy)
59
- - brainiac-github plugin (optional, for `on_pr_merge` review gate)
60
-
61
- ## Getting Started
62
-
63
- ### Step 1: Install the Basecamp CLI
64
-
65
- ```bash
66
- curl -fsSL https://basecamp.com/install-cli | bash
67
- ```
48
+ 1. Creates an `epic/<name>` branch when the epic starts
49
+ 2. All task PRs target the epic branch (not main)
50
+ 3. After PR opens, **review gate agents** (e.g., GLaDOS, Threepio) review in parallel
51
+ 4. When all gates approve, the **implementation agent** makes the final decision
52
+ 5. Agent reviews gate feedback, makes fixes if needed, then merges to epic branch
53
+ 6. After all tasks complete, a **final PR** opens from epic branch → main
68
54
 
69
- Verify it's installed:
55
+ Configure review gates in `~/.brainiac/basecamp.json`:
70
56
 
71
- ```bash
72
- basecamp --version
57
+ ```json
58
+ {
59
+ "review_gate": "epic_branch",
60
+ "review_gates": [
61
+ { "agent": "GLaDOS", "role": "test-engineer" },
62
+ { "agent": "Threepio", "role": "code-reviewer" }
63
+ ]
64
+ }
73
65
  ```
74
66
 
75
- ### Step 2: Authenticate with Basecamp
67
+ ## Epic Review Between Tasks
76
68
 
77
- ```bash
78
- basecamp auth login
79
- ```
69
+ After each task completes (before dispatching the next batch), an **epic review agent** is dispatched to:
80
70
 
81
- This opens your browser for OAuth. Once authenticated, verify:
71
+ 1. Read memory files from completed tasks
72
+ 2. Check if remaining tasks still make sense given implementation decisions
73
+ 3. Update dependencies if implementation created new relationships
74
+ 4. Mark tasks obsolete or adjust scope if needed
75
+ 5. Create new Fizzy cards if gaps are discovered
82
76
 
83
- ```bash
84
- basecamp auth status
85
- ```
86
-
87
- ### Step 3: Create a Bot Account in Basecamp
77
+ This prevents wasted work when early implementation decisions change the plan.
88
78
 
89
- Go to your Basecamp account → Adminland → People → Invite people.
79
+ ## Prerequisites
90
80
 
91
- Create a new user account for the bot (e.g. "Galen Bot" or "Brainiac Andy"). This account will be the one that receives epic assignments.
81
+ - [Basecamp CLI](https://github.com/stowzilla/basecamp-cli) installed and authenticated
82
+ - A Basecamp bot user account (for webhook-triggered orchestration)
83
+ - brainiac-fizzy plugin (cards already exist in Fizzy)
84
+ - brainiac-github plugin (required for `on_pr_merge` and `epic_branch` modes)
92
85
 
93
- Once created, find the bot's **person ID**:
86
+ ## Installation
94
87
 
95
88
  ```bash
96
- basecamp people list --jq '.data[] | select(.name | contains("Galen")) | {id, name}'
89
+ brainiac install basecamp
90
+ brainiac basecamp setup
97
91
  ```
98
92
 
99
- Note the `id` value — you'll need it in Step 6.
93
+ ## Configuration
100
94
 
101
- ### Step 4: Install the Plugin
95
+ ### Step 1: Set Fizzy Account ID
102
96
 
103
97
  ```bash
104
- brainiac install basecamp --path /home/andy/Code/brainiac-basecamp
98
+ brainiac basecamp set fizzy-account-id <your-fizzy-org-id>
105
99
  ```
106
100
 
107
- ### Step 5: Run Setup
101
+ ### Step 2: Register Bot Account
108
102
 
103
+ Find your bot's Basecamp person ID:
109
104
  ```bash
110
- brainiac basecamp setup
105
+ basecamp people list --jq '.data[] | select(.name | contains("Galen")) | {id, name}'
111
106
  ```
112
107
 
113
- This checks prerequisites and creates `~/.brainiac/basecamp.json`.
114
-
115
- ### Step 6: Configure the Plugin
116
-
108
+ Register it:
117
109
  ```bash
118
- # Set your Fizzy org slug (used to build clickable card URLs in Basecamp)
119
- brainiac basecamp set fizzy-account-id 6098707
120
-
121
- # Register your bot account (name it anything, use the person ID from Step 3)
122
- brainiac basecamp bot add andy-server <person-id-from-step-3> Galen
123
-
124
- # Map your Brainiac project(s) to Basecamp project IDs
125
- # Find your Basecamp project ID:
126
- basecamp projects list --jq '.data[] | {id, name}'
127
- # Then map it:
128
- brainiac basecamp projects map stowzilla <basecamp-project-id>
129
-
130
- # Optionally enable the review gate (waits for PR merge between tasks)
131
- brainiac basecamp set review-gate on_pr_merge
110
+ brainiac basecamp bot add my-server <person-id> Galen
132
111
  ```
133
112
 
134
- Verify your config:
113
+ ### Step 3: Map Projects
135
114
 
115
+ Find your Basecamp project ID:
136
116
  ```bash
137
- brainiac basecamp config
117
+ basecamp projects list --jq '.data[] | {id, name}'
138
118
  ```
139
119
 
140
- ### Step 7: Register the Webhook
141
-
142
- Point Basecamp at your brainiac server's ngrok URL:
143
-
120
+ Map it to your Brainiac project:
144
121
  ```bash
145
- basecamp webhooks create "https://your-ngrok.ngrok-free.app/basecamp" \
146
- --types "Todo,Todolist" --in <basecamp-project-id>
122
+ brainiac basecamp projects map stowzilla <basecamp-project-id>
147
123
  ```
148
124
 
149
- Replace `your-ngrok.ngrok-free.app` with your actual ngrok domain.
150
-
151
- ### Step 8: Restart Brainiac
125
+ ### Step 4: Set Review Gate Mode
152
126
 
153
127
  ```bash
154
- brainiac restart
155
- ```
128
+ # Simple mode — advance when agent finishes
129
+ brainiac basecamp set review-gate on_complete
156
130
 
157
- Verify the plugin loaded:
131
+ # PR mode — wait for PR merge to main
132
+ brainiac basecamp set review-gate on_pr_merge
158
133
 
159
- ```bash
160
- brainiac plugins
161
- curl http://localhost:4567/api/basecamp
134
+ # Epic branch mode — review gates + final decision (recommended)
135
+ brainiac basecamp set review-gate epic_branch
162
136
  ```
163
137
 
164
- ### Step 9: Create Your First Epic
138
+ ### Step 5: Configure Review Gates (epic_branch mode)
165
139
 
166
- In Basecamp, create a new todolist with the `Epic:` prefix:
140
+ Edit `~/.brainiac/basecamp.json`:
167
141
 
168
- **Todolist title:** `Epic: My First Feature`
169
-
170
- Add todos to it. Each todo title should reference a Fizzy card number:
171
-
172
- ```
173
- #1234 — Build the API endpoint
174
- #1235 — Add frontend form
175
- #1236 — Write integration tests [depends:1234,1235]
142
+ ```json
143
+ {
144
+ "review_gates": [
145
+ { "agent": "GLaDOS", "role": "test-engineer" },
146
+ { "agent": "Threepio", "role": "code-reviewer" }
147
+ ]
148
+ }
176
149
  ```
177
150
 
178
- Optionally, add rich text descriptions with clickable Fizzy links:
151
+ ### Step 6: Configure Notifications (Optional)
179
152
 
180
- ```html
181
- <a href="https://app.fizzy.do/stowzilla/cards/1234">Fizzy #1234</a>
182
- Depends on: none
153
+ ```json
154
+ {
155
+ "notifications": {
156
+ "channel": "discord",
157
+ "target": "1423854179880927274",
158
+ "epic_started": true,
159
+ "task_dispatched": true,
160
+ "task_completed": true,
161
+ "epic_completed": true
162
+ }
163
+ }
183
164
  ```
184
165
 
185
- ### Step 10: Start the Epic
166
+ The `channel` can be any notification backend that subscribes to the `:notify` hook (discord, slack, etc.). The `target` is the channel/room ID for that backend.
186
167
 
187
- Assign any todo in the epic todolist to your bot account (the one from Step 3).
168
+ ### Step 7: Register Webhook
188
169
 
189
- The webhook fires → brainiac-basecamp reads the todolist → builds the dependency graph → assigns the first unblocked Fizzy card to your agent.
170
+ ```bash
171
+ basecamp webhooks create "https://your-ngrok.ngrok-free.app/basecamp" \
172
+ --types "Todo,Todolist" --in <basecamp-project-id>
173
+ ```
190
174
 
191
- Watch it go:
175
+ ### Step 8: Restart Brainiac
192
176
 
193
177
  ```bash
194
- brainiac basecamp epics
195
- curl http://localhost:4567/api/basecamp/epics
178
+ brainiac restart
196
179
  ```
197
180
 
198
- ## Configuration
181
+ ## Full Configuration Example
199
182
 
200
183
  `~/.brainiac/basecamp.json`:
201
184
 
@@ -203,19 +186,24 @@ curl http://localhost:4567/api/basecamp/epics
203
186
  {
204
187
  "bot_accounts": {
205
188
  "andy-server": {
206
- "person_id": "12345",
189
+ "person_id": "52992796",
207
190
  "default_agent": "Galen"
208
191
  }
209
192
  },
210
193
  "project_mappings": {
211
- "marketplace": {
212
- "basecamp_project_id": "67890"
213
- }
194
+ "stowzilla": { "basecamp_project_id": "45920028" },
195
+ "brainiac": { "basecamp_project_id": "45920028" }
214
196
  },
215
197
  "epic_prefix": "Epic:",
216
198
  "fizzy_account_id": "6098707",
217
- "review_gate": "on_pr_merge",
199
+ "review_gate": "epic_branch",
200
+ "review_gates": [
201
+ { "agent": "GLaDOS", "role": "test-engineer" },
202
+ { "agent": "Threepio", "role": "code-reviewer" }
203
+ ],
218
204
  "notifications": {
205
+ "channel": "discord",
206
+ "target": "1423854179880927274",
219
207
  "epic_started": true,
220
208
  "task_dispatched": true,
221
209
  "task_completed": true,
@@ -236,8 +224,8 @@ brainiac basecamp bot add <name> <id> <agent> # Add bot account
236
224
  brainiac basecamp bot list # List bot accounts
237
225
  brainiac basecamp projects map <key> <bc-id> # Map project
238
226
  brainiac basecamp projects list # List mappings
239
- brainiac basecamp set fizzy-account-id <id> # Set Fizzy account ID for URLs
240
- brainiac basecamp set review-gate <mode> # on_complete or on_pr_merge
227
+ brainiac basecamp set fizzy-account-id <id> # Set Fizzy account ID
228
+ brainiac basecamp set review-gate <mode> # on_complete, on_pr_merge, epic_branch
241
229
  brainiac basecamp set epic-prefix <prefix> # Epic detection prefix
242
230
  ```
243
231
 
@@ -251,18 +239,21 @@ curl http://localhost:4567/api/basecamp
251
239
  curl http://localhost:4567/api/basecamp/epics
252
240
  curl "http://localhost:4567/api/basecamp/epics?status=all"
253
241
 
254
- # Specific epic with dependency graph
255
- curl http://localhost:4567/api/basecamp/epics/epic-123
242
+ # Specific epic
243
+ curl http://localhost:4567/api/basecamp/epics/<todolist-id>
244
+ ```
256
245
 
257
- # Manually start an epic (for testing)
258
- curl -X POST http://localhost:4567/api/basecamp/epics \
259
- -H "Content-Type: application/json" \
260
- -d '{"todolist_id":"123","project_id":"456","agent":"Galen","title":"Epic: Test"}'
246
+ ## Self-Healing on Restart
261
247
 
262
- # Pause/resume
263
- curl -X POST http://localhost:4567/api/basecamp/epics/epic-123/pause
264
- curl -X POST http://localhost:4567/api/basecamp/epics/epic-123/resume
265
- ```
248
+ When brainiac restarts, the plugin:
249
+
250
+ 1. Loads active epics from disk
251
+ 2. For tasks in `in_review` status: syncs gate approvals from GitHub PR reviews
252
+ 3. If all gates passed: dispatches final decision
253
+ 4. If all tasks complete: finalizes epic (marks todos done, opens final PR)
254
+ 5. Dispatches any unblocked pending tasks
255
+
256
+ No manual intervention needed — just restart and it picks up where it left off.
266
257
 
267
258
  ## Architecture
268
259
 
@@ -270,53 +261,41 @@ curl -X POST http://localhost:4567/api/basecamp/epics/epic-123/resume
270
261
 
271
262
  | Hook | What It Does |
272
263
  |------|-------------|
273
- | `:agent_completed` | Advances epic when Fizzy card completes (respects review gate) |
264
+ | `:agent_completed` | Dispatches review gates after PR opens; advances epic on task completion |
274
265
  | `:pr_merged` | Advances epic if review gate = `on_pr_merge` |
275
- | `:build_brain_context` | Injects epic context into agent prompts |
266
+ | `:pr_merged_to_branch` | Advances epic when PR merges to epic branch |
267
+ | `:pr_review_received` | Tracks gate approvals; triggers final decision when all gates pass |
268
+ | `:build_brain_context` | Injects epic context + memory references into agent prompts |
269
+ | `:resolve_base_branch` | Returns epic branch as worktree base |
270
+ | `:resolve_pr_target` | Returns epic branch as PR target |
276
271
 
277
- ### Orchestration Flow
272
+ ### Orchestration Flow (Epic Branch Mode)
278
273
 
279
274
  ```
280
- Webhook (todo_assignment_changed)
281
- Is parent todolist an epic? (Epic: prefix)
282
- Is assigned person a bot account?
283
- Start orchestration:
284
- 1. Read all todos in todolist
285
- 2. Parse card refs + dependencies from titles/descriptions
286
- 3. Build dependency graph
287
- 4. Dispatch unblocked Fizzy cards (assign via fizzy CLI)
288
- 5. Wait for :agent_completed / :pr_merged
289
- 6. Mark Basecamp todo complete
290
- 7. Re-evaluate graph dispatch next
291
- 8. Repeat until all done
292
- 9. Post summary message, emit notification
293
- ```
294
-
295
- ### Dual-Server Pattern
296
-
297
- Both brainiac servers receive the same Basecamp webhook. Only the server whose bot account person ID matches an `added_person_id` in the webhook will start orchestration. Same pattern as Fizzy's `local` flag.
298
-
299
- ## Dependency Tracking
300
-
301
- Dependencies are declared in todo titles or descriptions:
302
-
303
- **In title:** `#1236 — Frontend [depends:1234,1235]`
304
-
305
- **In description (rich text):**
306
- ```html
307
- <div>
308
- <strong>Fizzy:</strong> <a href="https://app.fizzy.do/stowzilla/cards/1236">#1236</a><br>
309
- <strong>Depends on:</strong> #1234, #1235<br>
310
- </div>
275
+ Webhook (todo assigned to bot)
276
+ Read todolist, build dependency graph
277
+ Create epic/<name> branch
278
+ Dispatch unblocked Fizzy cards (parallel if independent)
279
+
280
+ Card completes, PR opens
281
+ Dispatch review gate agents (parallel)
282
+ Gates review and approve/request changes
283
+
284
+ All gates approve
285
+ Dispatch implementation agent for final decision
286
+ Agent reviews feedback, makes fixes if needed, merges PR
287
+
288
+ PR merged to epic branch
289
+ → Mark Basecamp todo complete
290
+ Dispatch epic review agent (checks if plan still makes sense)
291
+ → Dispatch next unblocked cards
292
+
293
+ All tasks complete
294
+ Open final PR: epic/<name> → main
295
+ → Post summary to Basecamp
296
+ Send notification
311
297
  ```
312
298
 
313
- The orchestrator:
314
- 1. Parses all todos to build a directed acyclic graph
315
- 2. Identifies tasks with no unmet dependencies (unblocked)
316
- 3. Assigns unblocked Fizzy cards to agents
317
- 4. On completion, re-evaluates the graph for newly unblocked tasks
318
- 5. Supports mid-epic changes — re-reads the todolist on each cycle
319
-
320
299
  ## License
321
300
 
322
301
  MIT
@@ -56,6 +56,9 @@ module Brainiac
56
56
  # THEN: Create epic branches for all projects (now that we know which projects are involved)
57
57
  if review_gate == "epic_branch"
58
58
  create_epic_branches_for(epic)
59
+
60
+ # Sync PR state from work_items for cards that already have open PRs
61
+ sync_existing_pr_state(epic)
59
62
  end
60
63
 
61
64
  # CRITICAL: Save epic state BEFORE dispatching tasks.
@@ -63,7 +66,12 @@ module Brainiac
63
66
  # must be persisted before the agent is dispatched.
64
67
  save_epic(epic)
65
68
 
66
- # Finally: Dispatch unblocked work
69
+ # For tasks with open PRs, dispatch review gates immediately
70
+ if review_gate == "epic_branch"
71
+ dispatch_gates_for_existing_prs(epic)
72
+ end
73
+
74
+ # Finally: Dispatch unblocked work (skips tasks already in_review)
67
75
  dispatch_unblocked_tasks(epic)
68
76
 
69
77
  save_epic(epic)
@@ -317,6 +325,91 @@ module Brainiac
317
325
  end
318
326
  end
319
327
 
328
+ # Sync PR state from work_items for cards that already have open PRs.
329
+ # Called when an epic starts to detect cards mid-flight.
330
+ def sync_existing_pr_state(epic)
331
+ work_items_file = File.join(BRAINIAC_DIR, "work_items.json")
332
+ return unless File.exist?(work_items_file)
333
+
334
+ work_items = JSON.parse(File.read(work_items_file))
335
+
336
+ epic["tasks"].each do |task|
337
+ card_number = task["fizzy_card"]
338
+ next if task["pr_number"] # Already has PR tracked
339
+
340
+ # Find work item by card number
341
+ work_item = work_items.values.find do |wi|
342
+ wi.dig("sources", "fizzy", "card_number") == card_number
343
+ end
344
+ next unless work_item
345
+
346
+ # Check if work item has a PR
347
+ pr = work_item.dig("sources", "github", "prs")&.first
348
+ next unless pr
349
+
350
+ pr_number = pr["number"]
351
+ LOG.info "[Basecamp:Orchestrator] Found existing PR ##{pr_number} for card ##{card_number}" if defined?(LOG)
352
+
353
+ task["pr_number"] = pr_number
354
+ task["pr_repo"] = work_item.dig("sources", "github", "repo")
355
+ task["status"] = "in_review"
356
+ end
357
+ rescue StandardError => e
358
+ LOG.warn "[Basecamp:Orchestrator] Error syncing PR state: #{e.message}" if defined?(LOG)
359
+ end
360
+
361
+ # Dispatch review gates for tasks that already have open PRs.
362
+ # Called when an epic starts to kick off reviews for mid-flight cards.
363
+ def dispatch_gates_for_existing_prs(epic)
364
+ return unless ReviewGate.enabled?
365
+
366
+ tasks_with_prs = epic["tasks"].select { |t| t["pr_number"] && t["status"] == "in_review" }
367
+ return if tasks_with_prs.empty?
368
+
369
+ LOG.info "[Basecamp:Orchestrator] Dispatching gates for #{tasks_with_prs.size} existing PR(s)" if defined?(LOG)
370
+
371
+ tasks_with_prs.each do |task|
372
+ card_number = task["fizzy_card"]
373
+ pr_number = task["pr_number"]
374
+ project_key = task["project"]
375
+
376
+ # Skip if gates already dispatched
377
+ next if task["gates_dispatched_at"]
378
+
379
+ # Get repo info
380
+ projects_file = File.join(BRAINIAC_DIR, "projects.json")
381
+ projects = File.exist?(projects_file) ? JSON.parse(File.read(projects_file)) : {}
382
+ project_config = projects[project_key] || {}
383
+ repo_path = project_config["repo_path"]
384
+ github_repo = project_config["github_repo"]
385
+
386
+ next unless repo_path && github_repo
387
+
388
+ # Sync gate state from GitHub first (in case reviews already happened)
389
+ ReviewGate.sync_from_github(task, repo_path: repo_path)
390
+
391
+ # Check if all gates already passed
392
+ if ReviewGate.all_gates_passed?(task)
393
+ LOG.info "[Basecamp:Orchestrator] Gates already passed for card ##{card_number} — dispatching final decision" if defined?(LOG)
394
+ task["status"] = "final_decision"
395
+ task["awaiting_final_decision"] = true
396
+ # Final decision dispatch happens in resume logic
397
+ else
398
+ # Dispatch gate agents
399
+ LOG.info "[Basecamp:Orchestrator] Dispatching gates for card ##{card_number} (PR ##{pr_number})" if defined?(LOG)
400
+ ReviewGate.dispatch_gates(
401
+ epic: epic,
402
+ task: task,
403
+ pr_number: pr_number,
404
+ repo_name: github_repo,
405
+ repo_path: repo_path
406
+ )
407
+ end
408
+ end
409
+
410
+ save_epic(epic)
411
+ end
412
+
320
413
  # Assign a Fizzy card to an agent via Fizzy CLI.
321
414
  # If the agent is already assigned, skip — the webhook should have already fired.
322
415
  def assign_fizzy_card(card_number, agent)
@@ -696,8 +789,20 @@ module Brainiac
696
789
  {}
697
790
  end
698
791
 
699
- # Send a notification via the configured channel (Discord, etc.).
700
- # Reads discord_channel_id from basecamp.json notifications config.
792
+ # Send a notification via the configured channel.
793
+ # Supports multiple notification backends via the :notify hook.
794
+ #
795
+ # Config in basecamp.json:
796
+ # "notifications": {
797
+ # "channel": "discord", # or "slack", etc.
798
+ # "target": "1423854179880927274", # channel/room ID
799
+ # "epic_completed": true, # per-event toggles
800
+ # ...
801
+ # }
802
+ #
803
+ # Legacy config (still supported):
804
+ # "notifications": { "discord_channel_id": "..." }
805
+ #
701
806
  def send_notification(event:, message:, agent: nil)
702
807
  return unless defined?(Brainiac) && Brainiac.respond_to?(:emit)
703
808
 
@@ -705,13 +810,14 @@ module Brainiac
705
810
  notifications_config = Config.current.dig("notifications") || {}
706
811
  return unless notifications_config[event.to_s] != false
707
812
 
708
- # Get the target channel
709
- discord_channel = notifications_config["discord_channel_id"]
710
- return unless discord_channel
813
+ # Get channel type and target (supports both new and legacy config)
814
+ channel = notifications_config["channel"]&.to_sym || :discord
815
+ target = notifications_config["target"] || notifications_config["discord_channel_id"]
816
+ return unless target
711
817
 
712
818
  Brainiac.emit(:notify,
713
- channel: :discord,
714
- target: discord_channel,
819
+ channel: channel,
820
+ target: target,
715
821
  message: message,
716
822
  agent: agent)
717
823
  end
@@ -3,7 +3,7 @@
3
3
  module Brainiac
4
4
  module Plugins
5
5
  module Basecamp
6
- VERSION = "0.0.1"
6
+ VERSION = "0.0.2"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainiac-basecamp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis