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 +4 -4
- data/README.md +157 -178
- data/lib/brainiac/plugins/basecamp/orchestrator.rb +114 -8
- data/lib/brainiac/plugins/basecamp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 996b4eddefd0655105a9dad206a11351f44a73e8a82f4d9916c81886482f569e
|
|
4
|
+
data.tar.gz: 9bf1fd7d2b23844ee22325231b480ddd5bb61f0929dda9aa07ee2e7c91326789
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,
|
|
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
|
|
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.
|
|
13
|
-
7. When
|
|
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
|
|
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
|
-
□
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
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
|
-
|
|
36
|
+
Configure via `brainiac basecamp set review-gate <mode>`:
|
|
46
37
|
|
|
47
38
|
| Mode | Behavior |
|
|
48
39
|
|------|----------|
|
|
49
|
-
| `on_complete`
|
|
50
|
-
| `on_pr_merge` | Wait for PR merge
|
|
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
|
-
|
|
44
|
+
### Epic Branch Mode (Recommended)
|
|
53
45
|
|
|
54
|
-
|
|
46
|
+
The `epic_branch` mode provides the most control:
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
55
|
+
Configure review gates in `~/.brainiac/basecamp.json`:
|
|
70
56
|
|
|
71
|
-
```
|
|
72
|
-
|
|
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
|
-
|
|
67
|
+
## Epic Review Between Tasks
|
|
76
68
|
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
+
## Prerequisites
|
|
90
80
|
|
|
91
|
-
|
|
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
|
-
|
|
86
|
+
## Installation
|
|
94
87
|
|
|
95
88
|
```bash
|
|
96
|
-
|
|
89
|
+
brainiac install basecamp
|
|
90
|
+
brainiac basecamp setup
|
|
97
91
|
```
|
|
98
92
|
|
|
99
|
-
|
|
93
|
+
## Configuration
|
|
100
94
|
|
|
101
|
-
### Step
|
|
95
|
+
### Step 1: Set Fizzy Account ID
|
|
102
96
|
|
|
103
97
|
```bash
|
|
104
|
-
brainiac
|
|
98
|
+
brainiac basecamp set fizzy-account-id <your-fizzy-org-id>
|
|
105
99
|
```
|
|
106
100
|
|
|
107
|
-
### Step
|
|
101
|
+
### Step 2: Register Bot Account
|
|
108
102
|
|
|
103
|
+
Find your bot's Basecamp person ID:
|
|
109
104
|
```bash
|
|
110
|
-
|
|
105
|
+
basecamp people list --jq '.data[] | select(.name | contains("Galen")) | {id, name}'
|
|
111
106
|
```
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
### Step 6: Configure the Plugin
|
|
116
|
-
|
|
108
|
+
Register it:
|
|
117
109
|
```bash
|
|
118
|
-
|
|
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
|
-
|
|
113
|
+
### Step 3: Map Projects
|
|
135
114
|
|
|
115
|
+
Find your Basecamp project ID:
|
|
136
116
|
```bash
|
|
137
|
-
|
|
117
|
+
basecamp projects list --jq '.data[] | {id, name}'
|
|
138
118
|
```
|
|
139
119
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Point Basecamp at your brainiac server's ngrok URL:
|
|
143
|
-
|
|
120
|
+
Map it to your Brainiac project:
|
|
144
121
|
```bash
|
|
145
|
-
basecamp
|
|
146
|
-
--types "Todo,Todolist" --in <basecamp-project-id>
|
|
122
|
+
brainiac basecamp projects map stowzilla <basecamp-project-id>
|
|
147
123
|
```
|
|
148
124
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
### Step 8: Restart Brainiac
|
|
125
|
+
### Step 4: Set Review Gate Mode
|
|
152
126
|
|
|
153
127
|
```bash
|
|
154
|
-
|
|
155
|
-
|
|
128
|
+
# Simple mode — advance when agent finishes
|
|
129
|
+
brainiac basecamp set review-gate on_complete
|
|
156
130
|
|
|
157
|
-
|
|
131
|
+
# PR mode — wait for PR merge to main
|
|
132
|
+
brainiac basecamp set review-gate on_pr_merge
|
|
158
133
|
|
|
159
|
-
|
|
160
|
-
brainiac
|
|
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
|
|
138
|
+
### Step 5: Configure Review Gates (epic_branch mode)
|
|
165
139
|
|
|
166
|
-
|
|
140
|
+
Edit `~/.brainiac/basecamp.json`:
|
|
167
141
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
151
|
+
### Step 6: Configure Notifications (Optional)
|
|
179
152
|
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
168
|
+
### Step 7: Register Webhook
|
|
188
169
|
|
|
189
|
-
|
|
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
|
-
|
|
175
|
+
### Step 8: Restart Brainiac
|
|
192
176
|
|
|
193
177
|
```bash
|
|
194
|
-
brainiac
|
|
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": "
|
|
189
|
+
"person_id": "52992796",
|
|
207
190
|
"default_agent": "Galen"
|
|
208
191
|
}
|
|
209
192
|
},
|
|
210
193
|
"project_mappings": {
|
|
211
|
-
"
|
|
212
|
-
|
|
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": "
|
|
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
|
|
240
|
-
brainiac basecamp set review-gate <mode> # on_complete
|
|
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
|
|
255
|
-
curl http://localhost:4567/api/basecamp/epics
|
|
242
|
+
# Specific epic
|
|
243
|
+
curl http://localhost:4567/api/basecamp/epics/<todolist-id>
|
|
244
|
+
```
|
|
256
245
|
|
|
257
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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` |
|
|
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
|
-
| `:
|
|
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 (
|
|
281
|
-
→
|
|
282
|
-
→
|
|
283
|
-
→
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
#
|
|
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
|
|
700
|
-
#
|
|
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
|
|
709
|
-
|
|
710
|
-
|
|
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:
|
|
714
|
-
target:
|
|
819
|
+
channel: channel,
|
|
820
|
+
target: target,
|
|
715
821
|
message: message,
|
|
716
822
|
agent: agent)
|
|
717
823
|
end
|