brainiac-fizzy 0.0.1
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 +7 -0
- data/README.md +111 -0
- data/lib/brainiac/plugins/fizzy/config.rb +104 -0
- data/lib/brainiac/plugins/fizzy/delegators.rb +106 -0
- data/lib/brainiac/plugins/fizzy/handlers/assignment.rb +128 -0
- data/lib/brainiac/plugins/fizzy/handlers/card_index.rb +389 -0
- data/lib/brainiac/plugins/fizzy/handlers/comments.rb +749 -0
- data/lib/brainiac/plugins/fizzy/handlers/dedup.rb +74 -0
- data/lib/brainiac/plugins/fizzy/handlers/deploy.rb +152 -0
- data/lib/brainiac/plugins/fizzy/handlers/deployments.rb +260 -0
- data/lib/brainiac/plugins/fizzy/helpers.rb +165 -0
- data/lib/brainiac/plugins/fizzy/hooks.rb +371 -0
- data/lib/brainiac/plugins/fizzy/planning.rb +73 -0
- data/lib/brainiac/plugins/fizzy/prompts.rb +119 -0
- data/lib/brainiac/plugins/fizzy/version.rb +9 -0
- data/lib/brainiac/plugins/fizzy.rb +212 -0
- data/lib/brainiac_fizzy.rb +4 -0
- metadata +128 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Brainiac
|
|
4
|
+
module Plugins
|
|
5
|
+
module Fizzy
|
|
6
|
+
# Registers all Fizzy lifecycle hooks with the core event system.
|
|
7
|
+
module Hooks
|
|
8
|
+
class << self
|
|
9
|
+
def register_all!
|
|
10
|
+
register_agent_completed
|
|
11
|
+
register_agent_crashed
|
|
12
|
+
register_pre_dispatch
|
|
13
|
+
register_brain_context
|
|
14
|
+
register_pr_merged
|
|
15
|
+
register_pr_review_received
|
|
16
|
+
register_pr_synchronized
|
|
17
|
+
register_production_deployed
|
|
18
|
+
register_create_work_item
|
|
19
|
+
register_server_started
|
|
20
|
+
register_detect_cli_provider
|
|
21
|
+
register_detect_effort
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# After an agent session completes — move card to needs_review, append footer
|
|
27
|
+
def register_agent_completed
|
|
28
|
+
Brainiac.on(:agent_completed) do |ctx|
|
|
29
|
+
next unless ctx[:source] == :fizzy
|
|
30
|
+
|
|
31
|
+
card_number = ctx[:card_number]
|
|
32
|
+
next unless card_number && ctx[:exit_status]&.zero? && !ctx[:signaled]
|
|
33
|
+
|
|
34
|
+
unless ctx[:skip_column_move] || work_item_merged?(card_number)
|
|
35
|
+
Helpers.move_card_to_column(card_number, "needs_review",
|
|
36
|
+
project_config: ctx[:project_config],
|
|
37
|
+
agent_name: ctx[:agent_name])
|
|
38
|
+
record_self_move(card_number)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Helpers.append_fizzy_comment_footer(card_number,
|
|
42
|
+
project_config: ctx[:project_config],
|
|
43
|
+
agent_name: ctx[:agent_name])
|
|
44
|
+
|
|
45
|
+
# Planning mode finalization
|
|
46
|
+
Planning.finalize_if_needed(ctx[:prompt_file], ctx[:agent_name], ctx[:project_config])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Agent crashed — post crash comment on Fizzy card
|
|
51
|
+
def register_agent_crashed
|
|
52
|
+
Brainiac.on(:agent_crashed) do |ctx|
|
|
53
|
+
next unless ctx[:source] == :fizzy
|
|
54
|
+
|
|
55
|
+
card_number = ctx[:source_context]&.dig(:card_number)
|
|
56
|
+
next unless card_number
|
|
57
|
+
|
|
58
|
+
repo_path = ctx[:project_config]&.dig("repo_path") || Dir.pwd
|
|
59
|
+
snippet = ctx[:snippet]
|
|
60
|
+
body = "<p>💥 <strong>#{ctx[:agent_name]} crashed</strong> (exit code #{ctx[:exit_status]})</p>" \
|
|
61
|
+
"<p>Log: <code>#{ctx[:log_file]}</code></p>"
|
|
62
|
+
if snippet
|
|
63
|
+
escaped = snippet[-1500..].to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">")
|
|
64
|
+
body += "<pre>#{escaped}</pre>"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
env = Helpers.fizzy_env_for(ctx[:agent_name])
|
|
68
|
+
run_cmd("fizzy", "comment", "create", "--card", card_number.to_s, "--body", body,
|
|
69
|
+
chdir: repo_path, env: env)
|
|
70
|
+
LOG.info "[Fizzy] Posted crash comment on card ##{card_number}" if defined?(LOG)
|
|
71
|
+
|
|
72
|
+
:fizzy # Signal that we handled this source
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
LOG.error "[Fizzy] Failed to post crash comment: #{e.message}" if defined?(LOG)
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Before agent dispatch — copy .fizzy.yaml and clean attachments
|
|
80
|
+
def register_pre_dispatch
|
|
81
|
+
Brainiac.on(:pre_dispatch) do |ctx|
|
|
82
|
+
Helpers.ensure_fizzy_yaml!(ctx[:chdir], ctx[:project_config])
|
|
83
|
+
Thread.new { Helpers.scrub_invalid_attachments!(ctx[:chdir]) }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Brain context building — inject fizzy CLI knowledge when source is fizzy
|
|
88
|
+
def register_brain_context
|
|
89
|
+
Brainiac.on(:build_brain_context) do |ctx|
|
|
90
|
+
fizzy_relevant = ctx[:source] == :fizzy ||
|
|
91
|
+
[ctx[:card_title], ctx[:comment_body]].any? { |s| s&.match?(/fizzy/i) }
|
|
92
|
+
fizzy_relevant ? ["fizzy CLI commands"] : []
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# PR merged — post comment on Fizzy card, move to UAT, dispatch UAT agent
|
|
97
|
+
def register_pr_merged
|
|
98
|
+
Brainiac.on(:pr_merged) do |ctx|
|
|
99
|
+
card_number = ctx[:card_number]
|
|
100
|
+
next unless card_number
|
|
101
|
+
|
|
102
|
+
card_info = ctx[:card_info]
|
|
103
|
+
card_agent = card_info["agent"]
|
|
104
|
+
env = Helpers.fizzy_env_for(card_agent)
|
|
105
|
+
repo_path = ctx[:repo_path]
|
|
106
|
+
|
|
107
|
+
# Post PR link comment
|
|
108
|
+
pr_url = ctx[:pr_url]
|
|
109
|
+
pr_title = ctx[:pr_title]
|
|
110
|
+
branch = ctx[:branch]
|
|
111
|
+
comment_body = "<p>PR merged into main: <a href=\"#{pr_url}\">#{pr_title}</a></p>" \
|
|
112
|
+
"<p>Branch: <code>#{branch}</code></p>"
|
|
113
|
+
run_cmd("fizzy", "comment", "create", "--card", card_number.to_s, "--body", comment_body,
|
|
114
|
+
chdir: repo_path, env: env)
|
|
115
|
+
|
|
116
|
+
# Move card to UAT
|
|
117
|
+
Helpers.move_card_to_column(card_number, "uat",
|
|
118
|
+
project_config: ctx[:project_config],
|
|
119
|
+
agent_name: card_agent)
|
|
120
|
+
record_self_move(card_number)
|
|
121
|
+
|
|
122
|
+
# Clear deployment tracking
|
|
123
|
+
clear_deployment_for_card(card_number) if respond_to?(:clear_deployment_for_card)
|
|
124
|
+
|
|
125
|
+
# Dispatch UAT agent
|
|
126
|
+
dispatch_fizzy_uat_agent(ctx)
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
LOG.error "[Fizzy] Error in pr_merged hook: #{e.message}" if defined?(LOG)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# PR review received — post status comment on card
|
|
133
|
+
def register_pr_review_received
|
|
134
|
+
Brainiac.on(:pr_review_received) do |ctx|
|
|
135
|
+
card_number = ctx[:card_number]
|
|
136
|
+
next unless card_number
|
|
137
|
+
|
|
138
|
+
Thread.new do
|
|
139
|
+
env = Helpers.fizzy_env_for(ctx[:agent_name])
|
|
140
|
+
status_comment = "<p>🔄 Code review received from @#{ctx[:reviewer]}. Updates in progress...</p>"
|
|
141
|
+
run_cmd("fizzy", "comment", "create", "--card", card_number.to_s, "--body", status_comment,
|
|
142
|
+
chdir: ctx[:repo_path], env: env)
|
|
143
|
+
rescue StandardError => e
|
|
144
|
+
LOG.warn "[Fizzy] Could not post review status: #{e.message}" if defined?(LOG)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Production deployed — close UAT cards
|
|
150
|
+
def register_production_deployed
|
|
151
|
+
Brainiac.on(:production_deployed) do |ctx|
|
|
152
|
+
project_config = ctx[:project_config]
|
|
153
|
+
repo_path = project_config["repo_path"]
|
|
154
|
+
board_key = Config.board_key_for_project(project_config)
|
|
155
|
+
next [] unless board_key
|
|
156
|
+
|
|
157
|
+
uat_col = Config.board_column_id(board_key, "uat")
|
|
158
|
+
next [] unless uat_col
|
|
159
|
+
|
|
160
|
+
env = Helpers.default_fizzy_env
|
|
161
|
+
output = run_cmd("fizzy", "card", "list", "--column", uat_col, "--all",
|
|
162
|
+
chdir: repo_path, env: env)
|
|
163
|
+
card_list = JSON.parse(output)["data"] || []
|
|
164
|
+
next [] if card_list.empty?
|
|
165
|
+
|
|
166
|
+
closed = close_uat_cards(card_list, repo_path)
|
|
167
|
+
closed
|
|
168
|
+
rescue StandardError => e
|
|
169
|
+
LOG.error "[Fizzy] Error closing UAT cards: #{e.message}" if defined?(LOG)
|
|
170
|
+
[]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Create work item (from Zoho triage) — create a Fizzy card
|
|
175
|
+
def register_create_work_item
|
|
176
|
+
Brainiac.on(:create_work_item) do |ctx|
|
|
177
|
+
board_id = ctx[:board_id]
|
|
178
|
+
title = ctx[:title]
|
|
179
|
+
description = ctx[:description]
|
|
180
|
+
tags = ctx[:tags] || []
|
|
181
|
+
assign_to = ctx[:assign_to]
|
|
182
|
+
|
|
183
|
+
# Resolve tag IDs
|
|
184
|
+
agent_env = agent_env_for("Threepio")
|
|
185
|
+
spawn_env = agent_env.empty? ? {} : agent_env
|
|
186
|
+
tag_ids = resolve_tag_ids(tags, spawn_env)
|
|
187
|
+
|
|
188
|
+
cmd = ["fizzy", "card", "create", "--board", board_id, "--title", title, "--description", description]
|
|
189
|
+
cmd.push("--tag-ids", tag_ids.join(",")) unless tag_ids.empty?
|
|
190
|
+
|
|
191
|
+
output, status = Open3.capture2e(spawn_env, *cmd)
|
|
192
|
+
next nil unless status.success?
|
|
193
|
+
|
|
194
|
+
card_data = JSON.parse(output)
|
|
195
|
+
card_number = card_data.dig("data", "number")
|
|
196
|
+
|
|
197
|
+
# Assign if requested
|
|
198
|
+
assign_card(card_number, assign_to, spawn_env) if card_number && assign_to
|
|
199
|
+
|
|
200
|
+
{ number: card_number, url: card_data.dig("data", "url"), title: title }
|
|
201
|
+
rescue StandardError => e
|
|
202
|
+
LOG.error "[Fizzy] Failed to create card: #{e.message}" if defined?(LOG)
|
|
203
|
+
nil
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# --- Private helpers ---
|
|
208
|
+
|
|
209
|
+
def dispatch_fizzy_uat_agent(ctx)
|
|
210
|
+
card_number = ctx[:card_number]
|
|
211
|
+
card_info = ctx[:card_info]
|
|
212
|
+
project_config = ctx[:project_config]
|
|
213
|
+
project_key = ctx[:project_key]
|
|
214
|
+
repo_path = ctx[:repo_path]
|
|
215
|
+
|
|
216
|
+
agent_name = card_info["agent"] || agent_name_for(project_config)
|
|
217
|
+
card_title = card_info["title"] || ctx[:pr_title]
|
|
218
|
+
|
|
219
|
+
prompt = render_prompt(Prompts::UAT_TESTING,
|
|
220
|
+
{ "CARD_NUMBER" => card_number, "CARD_TITLE" => card_title,
|
|
221
|
+
"PR_NUMBER" => ctx[:pull_request]["number"].to_s },
|
|
222
|
+
brain_context: build_brain_context(agent_name: agent_name, card_number: card_number,
|
|
223
|
+
card_title: card_title, project_key: project_key),
|
|
224
|
+
agent_name: agent_name, channel: :fizzy,
|
|
225
|
+
board_key: Config.board_key_for_project(project_config))
|
|
226
|
+
|
|
227
|
+
pid, log_file = run_agent(prompt, project_config: project_config, chdir: repo_path,
|
|
228
|
+
log_name: "uat-#{card_number}", agent_name: agent_name,
|
|
229
|
+
source: :fizzy, source_context: { card_number: card_number }, skip_column_move: true)
|
|
230
|
+
register_session("card-#{card_number}", pid, log_file: log_file, agent_name: agent_name)
|
|
231
|
+
LOG.info "[Fizzy] Dispatched #{agent_name} for UAT on card ##{card_number}" if defined?(LOG)
|
|
232
|
+
rescue StandardError => e
|
|
233
|
+
LOG.error "[Fizzy] Failed to dispatch UAT agent: #{e.message}" if defined?(LOG)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def close_uat_cards(card_list, repo_path)
|
|
237
|
+
closed = []
|
|
238
|
+
map = load_work_item_map
|
|
239
|
+
|
|
240
|
+
card_list.each do |card|
|
|
241
|
+
card_number = card["number"]
|
|
242
|
+
next unless card_number
|
|
243
|
+
|
|
244
|
+
map_entry = map.values.find { |info| info["number"] == card_number }
|
|
245
|
+
agent_name = map_entry["agent"] if map_entry
|
|
246
|
+
env = Helpers.fizzy_env_for(agent_name || AI_AGENT_NAME)
|
|
247
|
+
|
|
248
|
+
run_cmd("fizzy", "comment", "create", "--card", card_number.to_s,
|
|
249
|
+
"--body", "<p>✅ Deployed to production. Closing card.</p>", chdir: repo_path, env: env)
|
|
250
|
+
run_cmd("fizzy", "card", "close", card_number.to_s, chdir: repo_path, env: env)
|
|
251
|
+
|
|
252
|
+
cleanup_work_item_worktrees(card_number, repo_path: repo_path,
|
|
253
|
+
primary_worktree: map_entry&.dig("worktree"), primary_branch: map_entry&.dig("branch"))
|
|
254
|
+
|
|
255
|
+
if map_entry
|
|
256
|
+
internal_id = map.key(map_entry)
|
|
257
|
+
map.delete(internal_id)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
closed << { number: card_number, url: card["url"], title: card["title"] }
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
save_work_item_map(map) if closed.any?
|
|
264
|
+
closed
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def resolve_tag_ids(tag_names, spawn_env)
|
|
268
|
+
output, status = Open3.capture2e(spawn_env, "fizzy", "tag", "list", "--all")
|
|
269
|
+
return [] unless status.success?
|
|
270
|
+
|
|
271
|
+
all_tags = JSON.parse(output)["data"] || []
|
|
272
|
+
tag_names.filter_map do |name|
|
|
273
|
+
tag = all_tags.find { |t| t["title"].downcase == name.downcase }
|
|
274
|
+
tag&.dig("id")
|
|
275
|
+
end
|
|
276
|
+
rescue StandardError
|
|
277
|
+
[]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def assign_card(card_number, agent_name, spawn_env)
|
|
281
|
+
users = Config.current["authorized_users"] || []
|
|
282
|
+
user = users.find { |u| u["name"]&.downcase == agent_name.downcase }
|
|
283
|
+
return unless user
|
|
284
|
+
|
|
285
|
+
Open3.capture2e(spawn_env, "fizzy", "card", "assign", card_number.to_s, "--user", user["id"])
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Server started — run card index backfill
|
|
289
|
+
def register_server_started
|
|
290
|
+
Brainiac.on(:server_started) do
|
|
291
|
+
if defined?(CARD_INDEX)
|
|
292
|
+
LOG.info "[Fizzy:CardIndex] Starting background backfill..."
|
|
293
|
+
CARD_INDEX.backfill
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Detect CLI provider from Fizzy card tags (e.g., cli-grok tag)
|
|
299
|
+
def register_detect_cli_provider
|
|
300
|
+
Brainiac.on(:detect_cli_provider) do |ctx|
|
|
301
|
+
tags = ctx[:tags] || []
|
|
302
|
+
result = nil
|
|
303
|
+
tags.each do |tag|
|
|
304
|
+
name = (tag.is_a?(Hash) ? tag["name"] : tag).to_s.downcase
|
|
305
|
+
if name.start_with?("cli-")
|
|
306
|
+
result = name.sub("cli-", "")
|
|
307
|
+
break
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
result
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Detect effort level from Fizzy card tags (e.g., effort-high tag)
|
|
315
|
+
def register_detect_effort
|
|
316
|
+
Brainiac.on(:detect_effort) do |ctx|
|
|
317
|
+
tags = ctx[:tags] || []
|
|
318
|
+
allowed = ctx[:allowed] || []
|
|
319
|
+
result = nil
|
|
320
|
+
tags.each do |tag|
|
|
321
|
+
name = (tag.is_a?(Hash) ? tag["name"] : tag).to_s.downcase
|
|
322
|
+
next unless name.start_with?("effort-")
|
|
323
|
+
|
|
324
|
+
level = name.sub("effort-", "")
|
|
325
|
+
if allowed.include?(level)
|
|
326
|
+
result = level
|
|
327
|
+
break
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
result
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# PR synchronized — handle auto-deploy if card is on a deploy env
|
|
335
|
+
def register_pr_synchronized
|
|
336
|
+
Brainiac.on(:pr_synchronized) do |ctx|
|
|
337
|
+
next unless defined?(DEPLOYMENTS_CONFIG)
|
|
338
|
+
|
|
339
|
+
card_number = ctx[:card_number]
|
|
340
|
+
worktree = ctx[:worktree]
|
|
341
|
+
next unless worktree && File.directory?(worktree)
|
|
342
|
+
|
|
343
|
+
state = load_deployment_state
|
|
344
|
+
config = DEPLOYMENTS_CONFIG["environments"] || {}
|
|
345
|
+
env_key = state.find { |_k, v| v["card_number"] == card_number && v["status"] == "occupied" }&.first
|
|
346
|
+
next unless env_key
|
|
347
|
+
|
|
348
|
+
env_owner = config.dig(env_key, "owner")
|
|
349
|
+
next unless env_owner && env_owner.downcase == AI_AGENT_NAME.downcase
|
|
350
|
+
next if on_deploy_cooldown?(env_key)
|
|
351
|
+
|
|
352
|
+
touch_deploy_cooldown(env_key)
|
|
353
|
+
system("git", "pull", "--ff-only", chdir: worktree)
|
|
354
|
+
|
|
355
|
+
deploy_script = File.join(worktree, "scripts", "deploy.sh")
|
|
356
|
+
next unless File.exist?(deploy_script)
|
|
357
|
+
|
|
358
|
+
LOG.info "[Fizzy:Deploy] Auto-deploying card ##{card_number} to #{env_key} (PR updated)"
|
|
359
|
+
mark_deploying(env_key, worktree_path: worktree)
|
|
360
|
+
run_pr_sync_deploy(env_key, card_number, worktree, config)
|
|
361
|
+
true
|
|
362
|
+
rescue StandardError => e
|
|
363
|
+
LOG.error "[Fizzy:Deploy] PR sync deploy error: #{e.message}" if defined?(LOG)
|
|
364
|
+
nil
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Brainiac
|
|
4
|
+
module Plugins
|
|
5
|
+
module Fizzy
|
|
6
|
+
# Planning mode — generates plans from agent sessions and creates Fizzy steps.
|
|
7
|
+
module Planning
|
|
8
|
+
PLANS_DIR = File.join(
|
|
9
|
+
ENV.fetch("BRAINIAC_DIR", File.join(Dir.home, ".brainiac")),
|
|
10
|
+
"plans"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Called from :agent_completed hook after session finishes.
|
|
15
|
+
# Checks if a plan file was produced and creates Fizzy steps from it.
|
|
16
|
+
def finalize_if_needed(prompt_file, agent_name, project_config)
|
|
17
|
+
return unless prompt_file && File.exist?(prompt_file)
|
|
18
|
+
|
|
19
|
+
prompt_content = File.read(prompt_file)
|
|
20
|
+
card_id_match = prompt_content.match(/CARD_ID.*?(\d+|discord-[\w-]+)/)
|
|
21
|
+
return unless card_id_match
|
|
22
|
+
|
|
23
|
+
card_id = card_id_match[1]
|
|
24
|
+
plan_file = File.join(PLANS_DIR, "card-#{card_id}-plan.md")
|
|
25
|
+
return unless File.exist?(plan_file)
|
|
26
|
+
|
|
27
|
+
LOG.info "[Fizzy:Planning] Plan file detected for card #{card_id}, finalizing..." if defined?(LOG)
|
|
28
|
+
finalize_plan(card_id: card_id, agent_name: agent_name, project_config: project_config)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def finalize_plan(card_id:, agent_name:, project_config:)
|
|
32
|
+
plan_file = File.join(PLANS_DIR, "card-#{card_id}-plan.md")
|
|
33
|
+
return { success: false, error: "No plan file" } unless File.exist?(plan_file)
|
|
34
|
+
|
|
35
|
+
plan_content = File.read(plan_file)
|
|
36
|
+
tasks = extract_tasks(plan_content)
|
|
37
|
+
return { success: false, error: "No tasks found in plan" } if tasks.empty?
|
|
38
|
+
|
|
39
|
+
card_number = card_id.match?(/^\d+$/) ? card_id.to_i : nil
|
|
40
|
+
return { success: false, error: "No card number" } unless card_number
|
|
41
|
+
|
|
42
|
+
repo_path = project_config["repo_path"]
|
|
43
|
+
env = Helpers.fizzy_env_for(agent_name || AI_AGENT_NAME)
|
|
44
|
+
|
|
45
|
+
tasks.each do |task_title|
|
|
46
|
+
run_cmd("fizzy", "step", "create", "--card", card_number.to_s, "--content", task_title,
|
|
47
|
+
chdir: repo_path, env: env)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
LOG.info "[Fizzy:Planning] Created #{tasks.size} steps for card ##{card_number}" if defined?(LOG)
|
|
51
|
+
{ success: true, tasks: tasks }
|
|
52
|
+
rescue StandardError => e
|
|
53
|
+
LOG.error "[Fizzy:Planning] Failed to finalize plan: #{e.message}" if defined?(LOG)
|
|
54
|
+
{ success: false, error: e.message }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def extract_tasks(plan_content)
|
|
60
|
+
tasks = []
|
|
61
|
+
plan_content.each_line do |line|
|
|
62
|
+
if line.match?(/^###\s+Task\s+\d+/)
|
|
63
|
+
title = line.sub(/^###\s+Task\s+\d+:\s*/, "").strip
|
|
64
|
+
tasks << title unless title.empty?
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
tasks
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Brainiac
|
|
4
|
+
module Plugins
|
|
5
|
+
module Fizzy
|
|
6
|
+
# Fizzy-specific prompt constants.
|
|
7
|
+
# Registered with core via Brainiac.register_channel_prompt(:fizzy, ...).
|
|
8
|
+
module Prompts
|
|
9
|
+
CHANNEL = <<~PROMPT
|
|
10
|
+
## Fizzy Channel Rules
|
|
11
|
+
|
|
12
|
+
### Standard Procedure
|
|
13
|
+
- If you have questions, ask them in the card's comments.
|
|
14
|
+
- Only assign a fizzy card if it is currently unassigned and you are requested to work on it.
|
|
15
|
+
|
|
16
|
+
### Column Transitions
|
|
17
|
+
Brainiac handles column moves automatically — do NOT move cards between columns yourself.
|
|
18
|
+
Cards move to "Right Now" when you're dispatched and to "Needs Review" when your session ends.
|
|
19
|
+
|
|
20
|
+
### Formatting
|
|
21
|
+
**Fizzy comments use HTML, NOT Markdown.** Use `<h2>`/`<h3>` for sections, `<p>` for paragraphs, `<ul><li>` for lists, `<pre data-language="ruby">` for code blocks, `<strong>` for emphasis. Never use markdown syntax in Fizzy comments.
|
|
22
|
+
|
|
23
|
+
### Screenshots (MANDATORY for UI changes)
|
|
24
|
+
If you touched any `.js`, `.jsx`, `.css`, or `.html` in a web app directory and `./scripts/screenshot-page.sh` exists, screenshot every affected page.
|
|
25
|
+
|
|
26
|
+
### Retrieving Full Comment Text
|
|
27
|
+
If you need the full text of a truncated comment, run: `fizzy comment show COMMENT_ID --card CARD_NUMBER`
|
|
28
|
+
|
|
29
|
+
### Card Memory Discipline (CRITICAL for long-running cards)
|
|
30
|
+
When writing your memory file for a Fizzy card session, include:
|
|
31
|
+
- The original card scope/requirements
|
|
32
|
+
- Any scope changes from comments
|
|
33
|
+
- Any card body edits detected during pre-post check
|
|
34
|
+
- The current scope/focus as of this session
|
|
35
|
+
PROMPT
|
|
36
|
+
|
|
37
|
+
PRE_POST_CHECK = <<~PROMPT
|
|
38
|
+
## Pre-Post Comment Check (MANDATORY — do this BEFORE posting your comment)
|
|
39
|
+
|
|
40
|
+
Re-fetch the card to see if anything changed while you were working:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
fizzy card show {{CARD_NUMBER}}
|
|
44
|
+
fizzy comment list --card {{CARD_NUMBER}}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Compare against the card context from the start of your session. Check for:
|
|
48
|
+
- Card body changes (new acceptance criteria, clarified scope)
|
|
49
|
+
- New comments (requirements changes, adjustments, new context)
|
|
50
|
+
|
|
51
|
+
If nothing changed, proceed normally.
|
|
52
|
+
PROMPT
|
|
53
|
+
|
|
54
|
+
CARD_ASSIGNED = <<~'PROMPT'
|
|
55
|
+
You have been assigned Fizzy card #{{CARD_NUMBER}}: "{{CARD_TITLE}}".
|
|
56
|
+
You are on branch "{{BRANCH}}" in a fresh worktree.
|
|
57
|
+
Implement the task, commit, push, and open a PR (link back to Fizzy).
|
|
58
|
+
When you're done, post a comment on the card with a concise summary, PR link, and branch name.
|
|
59
|
+
|
|
60
|
+
**MANDATORY: Always include the branch name in your comment.** Use this format:
|
|
61
|
+
`<p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>`
|
|
62
|
+
PROMPT
|
|
63
|
+
|
|
64
|
+
FOLLOWUP_WORKTREE = <<~'PROMPT'
|
|
65
|
+
There's a new comment on Fizzy card #{{CARD_NUMBER}} that you've already started working on.
|
|
66
|
+
You are in the existing worktree for this card.
|
|
67
|
+
|
|
68
|
+
The comment from {{COMMENT_CREATOR}} (comment ID: {{COMMENT_ID}}):
|
|
69
|
+
"""
|
|
70
|
+
{{COMMENT_BODY}}
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
Focus your response on the comment above. If you've already addressed this in a previous session, reply confirming it's done.
|
|
74
|
+
Otherwise, make the requested changes, commit, push, and update the PR.
|
|
75
|
+
PROMPT
|
|
76
|
+
|
|
77
|
+
FOLLOWUP_NO_WORKTREE = <<~PROMPT
|
|
78
|
+
There's a new comment on a Fizzy card (internal_id: "{{CARD_INTERNAL_ID}}").
|
|
79
|
+
|
|
80
|
+
The comment from {{COMMENT_CREATOR}} (comment ID: {{COMMENT_ID}}):
|
|
81
|
+
"""
|
|
82
|
+
{{COMMENT_BODY}}
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
Focus your response on the comment above. If you've already addressed this, reply confirming it's done.
|
|
86
|
+
Otherwise, respond accordingly.
|
|
87
|
+
PROMPT
|
|
88
|
+
|
|
89
|
+
MENTION = <<~PROMPT
|
|
90
|
+
You were mentioned in a comment on a Fizzy card with internal_id "{{CARD_INTERNAL_ID}}"{{CARD_NUMBER_TEXT}}.
|
|
91
|
+
You are on branch "{{BRANCH}}" in a dedicated worktree.
|
|
92
|
+
|
|
93
|
+
Investigate the codebase and respond accordingly.
|
|
94
|
+
PROMPT
|
|
95
|
+
|
|
96
|
+
CROSS_AGENT_REVIEW = <<~'PROMPT'
|
|
97
|
+
You were tagged in a comment on Fizzy card #{{CARD_NUMBER}} (internal_id: "{{CARD_INTERNAL_ID}}").
|
|
98
|
+
This card is being worked on by {{CARD_AGENT}} — you're being brought in for your perspective.
|
|
99
|
+
|
|
100
|
+
The comment from {{COMMENT_CREATOR}} (comment ID: {{COMMENT_ID}}):
|
|
101
|
+
"""
|
|
102
|
+
{{COMMENT_BODY}}
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
You are in your own worktree at `{{WORKTREE_PATH}}` on branch `{{BRANCH}}`.
|
|
106
|
+
Respond to what's being asked — code review, opinion, debugging help, or sanity check.
|
|
107
|
+
|
|
108
|
+
**IMPORTANT: Do NOT @mention any other agents in your response.**
|
|
109
|
+
PROMPT
|
|
110
|
+
|
|
111
|
+
UAT_TESTING = <<~'PROMPT'
|
|
112
|
+
PR for card #{{CARD_NUMBER}} ("{{CARD_TITLE}}") has been merged to main (PR #{{PR_NUMBER}}).
|
|
113
|
+
Run any UAT testing steps defined in the card or acceptance criteria.
|
|
114
|
+
Post results as a comment on the card.
|
|
115
|
+
PROMPT
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|