brainiac-fizzy 0.0.13 → 0.0.14

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: 0e09da26fb3e4d7a37ef7fff687ad5b5e094d6cad508227fac32467273541619
4
- data.tar.gz: 4de9000657e8afd645e5dcd04b02c9522912fb2df1428ffca10e4235b9d1b462
3
+ metadata.gz: 90ffad33201985cb5a94b3460fb635d254e806e435543eeb652b533ff59562a6
4
+ data.tar.gz: 209e7829523442c8ccc7e3a278454ddd008fd16df6f83495e1edd42c0ac9dada
5
5
  SHA512:
6
- metadata.gz: 3fe71aad4fec21bb3430d71bc9461156779b590c465bb49ec7199e6a4183f9321af16d98b3b6ffd9a01dc8505dd8d8708c9f0d1dfbdef98dd7e3b8c7fe3cea96
7
- data.tar.gz: 22cff590e92a0455820feb9f40a0eacf9552f9989ccde9b155e193673c0260f108771469c4d45b739c72626a87927bd79108d2063b85c2fe0aab981681b2f0bd
6
+ metadata.gz: 207769fa7579c519e38ff9551a5b4fb0ff823c1cb88740b7bd3b7f1a459bfd8c7e9ea97e62fd7a6dbeb8a48218595d08143564be8cd577ff32a264be7a97d947
7
+ data.tar.gz: e5d52c3fc6dcf2427b70030fccdb3a9196b2c00ecf63b934710accbaa3cdfef16a7c98765afac1ae33435d86f58837371097b697e733d8484f8bdee6efdedef7
@@ -136,6 +136,14 @@ PROMPT_MENTION = Brainiac::Plugins::Fizzy::Prompts::MENTION
136
136
  PROMPT_CROSS_AGENT_REVIEW = Brainiac::Plugins::Fizzy::Prompts::CROSS_AGENT_REVIEW
137
137
  PROMPT_PLANNING_MODE = Brainiac::Plugins::Fizzy::Prompts::PLANNING_MODE
138
138
 
139
+ # Default Fizzy board column IDs — used as fallback when board_key is nil or
140
+ # the board config doesn't specify column mappings.
141
+ DEFAULT_COLUMN_IDS = {
142
+ "right_now" => "03f5xa5q9fog9592pa1279dts",
143
+ "needs_review" => "03f5ykobhpsd78hbuvajtn8g8",
144
+ "uat" => "03fsmglsr6az06ppyotawsti8"
145
+ }.freeze
146
+
139
147
  # Render planning mode prompt — identical to render_prompt but inserts the planning
140
148
  # instructions between PROMPT_CORE and the channel rules. This was originally defined
141
149
  # in brainiac core's planning.rb but belongs here after the fizzy extraction.
@@ -152,7 +152,7 @@ def dispatch_assigned_card(card_number:, card_internal_id:, title:, tags:, branc
152
152
  project_config: project_config, chdir: worktree_path,
153
153
  log_name: "assigned-#{card_number}", model: model, effort: effort,
154
154
  agent_name: agent_name, card_number: card_number, source: :fizzy,
155
- source_context: { card_number: card_number },
155
+ source_context: { card_number: card_number, dispatched_at: Time.now },
156
156
  cli_provider: cli_provider_override)
157
157
  register_session(card_key, pid, log_file: log_file, supersede_key: card_key, agent_name: agent_name)
158
158
 
@@ -408,7 +408,7 @@ def dispatch_cross_agent_review(ctx, card_key:, card_number:, card_assigned_agen
408
408
  log_name: "review-#{ctx.agent_name.downcase}-#{card_number || ctx.card_internal_id}",
409
409
  model: ctx.model, effort: ctx.effort, agent_name: ctx.agent_name,
410
410
  card_number: card_number, comment_id: ctx.comment_id,
411
- source: :fizzy, source_context: { card_number: card_number },
411
+ source: :fizzy, source_context: { card_number: card_number, dispatched_at: Time.now },
412
412
  cli_provider: ctx.cli_provider_override)
413
413
  return [200, { status: "dispatch_failed", agent: ctx.agent_name, card: card_number }.to_json] unless pid
414
414
 
@@ -514,7 +514,7 @@ def dispatch_new_mention(ctx, card_key:, card_number:, card_title:, branch:, wor
514
514
  model: ctx.model, effort: ctx.effort, agent_name: ctx.agent_name,
515
515
  card_number: card_number, comment_id: ctx.comment_id,
516
516
  source: :fizzy, cli_provider: ctx.cli_provider_override,
517
- source_context: { card_number: card_number })
517
+ source_context: { card_number: card_number, dispatched_at: Time.now })
518
518
  return [200, { status: "dispatch_failed", agent: ctx.agent_name, card: card_number }.to_json] unless pid
519
519
 
520
520
  register_session(card_key, pid, log_file: log_file, supersede_key: card_key, agent_name: ctx.agent_name)
@@ -554,7 +554,7 @@ def dispatch_followup_comment(ctx, card_key:, card_number:, work_dir:)
554
554
  source: :fizzy, cli_provider: ctx.cli_provider_override, resume: should_resume,
555
555
  source_context: {
556
556
  card_number: card_number, card_internal_id: ctx.card_internal_id,
557
- deploy_intent: ctx.deploy_intent
557
+ deploy_intent: ctx.deploy_intent, dispatched_at: Time.now
558
558
  })
559
559
  return [200, { status: "dispatch_failed", agent: ctx.agent_name, card: card_number }.to_json] unless pid
560
560
 
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module Brainiac
6
+ module Plugins
7
+ module Fizzy
8
+ # Handles re-dispatch summarize sessions and fallback comment posting.
9
+ # Extracted from Hooks to keep module length manageable.
10
+ module Summarize
11
+ class << self
12
+ # Re-dispatch an agent to post a summary comment when it completed work
13
+ # but didn't comment on the card. Fires async with a 60s timeout.
14
+ # If this session also fails to comment, posts a minimal fallback.
15
+ def dispatch_summarize_session(ctx)
16
+ card_number = ctx[:card_number]
17
+ agent_name = ctx[:agent_name]
18
+ project_config = ctx[:project_config]
19
+ repo_path = project_config["repo_path"]
20
+ session_started_at = ctx[:source_context]&.dig(:dispatched_at)
21
+
22
+ map = load_work_item_map
23
+ work_item = find_work_item(map, card_number)
24
+
25
+ branch = work_item&.dig("branch") || "unknown"
26
+ card_title = work_item&.dig("title") || ctx[:source_context]&.dig(:card_title) || "untitled"
27
+ worktree_path = work_item&.dig("worktree") || repo_path
28
+
29
+ if defined?(LOG)
30
+ LOG.info "[Fizzy] Agent #{agent_name} completed card ##{card_number} without commenting — " \
31
+ "dispatching summarize session"
32
+ end
33
+
34
+ pid, log_file = dispatch_summarize_agent(card_number, card_title, branch, agent_name,
35
+ project_config, worktree_path)
36
+ register_session("card-#{card_number}-summarize", pid, log_file: log_file, agent_name: agent_name)
37
+
38
+ monitor_summarize_session(pid, card_number, agent_name, branch, repo_path, session_started_at,
39
+ project_config)
40
+ rescue StandardError => e
41
+ LOG.error "[Fizzy] Failed to dispatch summarize session for card ##{card_number}: #{e.message}" if defined?(LOG)
42
+ post_fallback_comment(card_number, work_item&.dig("branch") || "unknown", agent_name, project_config)
43
+ end
44
+
45
+ # Check if the agent posted a comment on the card since a given time.
46
+ # If no `since` is provided, checks for any agent comment (legacy behavior).
47
+ def agent_commented_on_card?(card_number, agent_name, repo_path:, since: nil)
48
+ env = Helpers.fizzy_env_for(agent_name)
49
+ output = run_cmd("fizzy", "comment", "list", "--card", card_number.to_s, chdir: repo_path, env: env)
50
+ comments = JSON.parse(output)["data"] || []
51
+ agent_display = agent_display_name(agent_name)
52
+
53
+ agent_comments = comments.select { |c| c["creator_name"]&.downcase == agent_display.downcase }
54
+ return agent_comments.any? unless since
55
+
56
+ agent_comments.any? do |c|
57
+ comment_time = c["created_at"] && Time.parse(c["created_at"])
58
+ comment_time && comment_time > since
59
+ end
60
+ rescue StandardError
61
+ false
62
+ end
63
+
64
+ # Post a minimal server-generated comment when all else fails.
65
+ def post_fallback_comment(card_number, branch, agent_name, project_config)
66
+ repo_path = project_config["repo_path"]
67
+ env = Helpers.fizzy_env_for(agent_name)
68
+
69
+ pr_url = Helpers.send(:detect_pr_url, branch, project_config)
70
+ body = "<p>✅ Work completed on this card.</p>"
71
+ body += "<p><a href=\"#{pr_url}\">View PR</a></p>" if pr_url
72
+ body += "<p><strong>Branch:</strong> <code>#{branch}</code></p>"
73
+
74
+ run_cmd("fizzy", "comment", "create", "--card", card_number.to_s, "--body", body,
75
+ chdir: repo_path, env: env)
76
+ LOG.info "[Fizzy] Posted fallback comment on card ##{card_number}" if defined?(LOG)
77
+ rescue StandardError => e
78
+ LOG.error "[Fizzy] Failed to post fallback comment on card ##{card_number}: #{e.message}" if defined?(LOG)
79
+ end
80
+
81
+ private
82
+
83
+ def find_work_item(map, card_number)
84
+ map.values.find do |info|
85
+ info.is_a?(Hash) &&
86
+ (info.dig("sources", "fizzy", "card_number").to_s == card_number.to_s ||
87
+ info["number"].to_s == card_number.to_s)
88
+ end
89
+ end
90
+
91
+ def dispatch_summarize_agent(card_number, card_title, branch, agent_name, project_config, worktree_path)
92
+ prompt = render_prompt(Prompts::SUMMARIZE_WORK,
93
+ { "CARD_NUMBER" => card_number, "CARD_TITLE" => card_title, "BRANCH" => branch },
94
+ brain_context: "", agent_name: agent_name, channel: :fizzy,
95
+ board_key: Config.board_key_for_project(project_config))
96
+
97
+ run_agent(prompt,
98
+ project_config: project_config, chdir: worktree_path,
99
+ log_name: "summarize-#{card_number}", agent_name: agent_name,
100
+ source: :fizzy,
101
+ source_context: { card_number: card_number, skip_summarize_redispatch: true },
102
+ skip_column_move: true)
103
+ end
104
+
105
+ def monitor_summarize_session(pid, card_number, agent_name, branch, repo_path, session_started_at,
106
+ project_config)
107
+ Thread.new do
108
+ wait_for_process_or_timeout(pid, timeout: 60)
109
+ sleep 2 # Brief pause to let any comment propagate
110
+
111
+ unless agent_commented_on_card?(card_number, agent_name, repo_path: repo_path, since: session_started_at)
112
+ post_fallback_comment(card_number, branch, agent_name, project_config)
113
+ end
114
+ rescue Errno::ECHILD
115
+ # Process already reaped by session manager — check for comment
116
+ sleep 2
117
+ unless agent_commented_on_card?(card_number, agent_name, repo_path: repo_path, since: session_started_at)
118
+ post_fallback_comment(card_number, branch, agent_name, project_config)
119
+ end
120
+ rescue StandardError => e
121
+ LOG.error "[Fizzy] Summarize monitor error for card ##{card_number}: #{e.message}" if defined?(LOG)
122
+ end
123
+ end
124
+
125
+ def wait_for_process_or_timeout(pid, timeout: 60)
126
+ deadline = Time.now + timeout
127
+ loop do
128
+ result = Process.waitpid2(pid, Process::WNOHANG)
129
+ break if result
130
+
131
+ if Time.now > deadline
132
+ safe_kill(pid)
133
+ break
134
+ end
135
+ sleep 1
136
+ end
137
+ end
138
+
139
+ def safe_kill(pid)
140
+ Process.kill("TERM", pid)
141
+ Process.waitpid2(pid)
142
+ rescue Errno::ESRCH, Errno::ECHILD
143
+ # Process already gone
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "time"
4
+
3
5
  module Brainiac
4
6
  module Plugins
5
7
  module Fizzy
@@ -25,6 +27,7 @@ module Brainiac
25
27
  private
26
28
 
27
29
  # After an agent session completes — move card to needs_review, append footer
30
+ # If the agent didn't post a comment, re-dispatch for a summary.
28
31
  def register_agent_completed
29
32
  Brainiac.on(:agent_completed) do |ctx|
30
33
  next unless ctx[:source] == :fizzy
@@ -43,6 +46,16 @@ module Brainiac
43
46
  project_config: ctx[:project_config],
44
47
  agent_name: ctx[:agent_name])
45
48
 
49
+ # If the agent didn't post any comment during this session, re-dispatch to summarize from memory.
50
+ # Uses dispatched_at from source_context to only check for comments made during THIS session,
51
+ # not previous sessions on the same card.
52
+ if !ctx[:source_context]&.dig(:skip_summarize_redispatch) &&
53
+ !agent_commented_on_card?(card_number, ctx[:agent_name],
54
+ repo_path: ctx[:project_config]["repo_path"],
55
+ since: ctx[:source_context]&.dig(:dispatched_at))
56
+ dispatch_summarize_session(ctx)
57
+ end
58
+
46
59
  # Planning mode finalization
47
60
  Planning.finalize_if_needed(ctx[:prompt_file], ctx[:agent_name], ctx[:project_config])
48
61
  end
@@ -240,13 +253,28 @@ module Brainiac
240
253
 
241
254
  pid, log_file = run_agent(prompt, project_config: project_config, chdir: repo_path,
242
255
  log_name: "uat-#{card_number}", agent_name: agent_name,
243
- source: :fizzy, source_context: { card_number: card_number }, skip_column_move: true)
256
+ source: :fizzy,
257
+ source_context: { card_number: card_number, dispatched_at: Time.now },
258
+ skip_column_move: true)
244
259
  register_session("card-#{card_number}", pid, log_file: log_file, agent_name: agent_name)
245
260
  LOG.info "[Fizzy] Dispatched #{agent_name} for UAT on card ##{card_number}" if defined?(LOG)
246
261
  rescue StandardError => e
247
262
  LOG.error "[Fizzy] Failed to dispatch UAT agent: #{e.message}" if defined?(LOG)
248
263
  end
249
264
 
265
+ # Delegated to Summarize module for manageable file length.
266
+ def dispatch_summarize_session(ctx)
267
+ Summarize.dispatch_summarize_session(ctx)
268
+ end
269
+
270
+ def agent_commented_on_card?(card_number, agent_name, repo_path:, since: nil)
271
+ Summarize.agent_commented_on_card?(card_number, agent_name, repo_path: repo_path, since: since)
272
+ end
273
+
274
+ def post_fallback_comment(card_number, branch, agent_name, project_config)
275
+ Summarize.post_fallback_comment(card_number, branch, agent_name, project_config)
276
+ end
277
+
250
278
  def close_uat_cards(card_list, repo_path)
251
279
  closed = []
252
280
  map = load_work_item_map
@@ -55,7 +55,9 @@ module Brainiac
55
55
  You have been assigned Fizzy card #{{CARD_NUMBER}}: "{{CARD_TITLE}}".
56
56
  You are on branch "{{BRANCH}}" in a fresh worktree.
57
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.
58
+
59
+ **Response destination: Post your response as a comment on Fizzy card #{{CARD_NUMBER}}.**
60
+ Your comment MUST include a concise summary of what you did, a PR link, and the branch name.
59
61
 
60
62
  **MANDATORY: Always include the branch name in your comment.** Use this format:
61
63
  `<p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>`
@@ -111,6 +113,18 @@ module Brainiac
111
113
  **IMPORTANT: Do NOT @mention any other agents in your response.**
112
114
  PROMPT
113
115
 
116
+ SUMMARIZE_WORK = <<~'PROMPT'
117
+ You completed work on Fizzy card #{{CARD_NUMBER}} ("{{CARD_TITLE}}") but did not post a comment.
118
+ Read your memory file for this card and write a brief summary comment.
119
+
120
+ **Response destination: Post your response as a comment on Fizzy card #{{CARD_NUMBER}}.**
121
+ Include what you did, any PR link, and the branch name.
122
+ Keep it concise — this is a summary, not a full report.
123
+
124
+ **MANDATORY: Always include the branch name in your comment.** Use this format:
125
+ `<p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>`
126
+ PROMPT
127
+
114
128
  UAT_TESTING = <<~'PROMPT'
115
129
  PR for card #{{CARD_NUMBER}} ("{{CARD_TITLE}}") has been merged to main (PR #{{PR_NUMBER}}).
116
130
  Run any UAT testing steps defined in the card or acceptance criteria.
@@ -3,7 +3,7 @@
3
3
  module Brainiac
4
4
  module Plugins
5
5
  module Fizzy
6
- VERSION = "0.0.13"
6
+ VERSION = "0.0.14"
7
7
  end
8
8
  end
9
9
  end
@@ -16,6 +16,7 @@ require_relative "fizzy/handlers/dedup"
16
16
  require_relative "fizzy/handlers/deploy"
17
17
  require_relative "fizzy/handlers/card_index"
18
18
  require_relative "fizzy/handlers/deployments"
19
+ require_relative "fizzy/handlers/summarize"
19
20
 
20
21
  module Brainiac
21
22
  module Plugins
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainiac-fizzy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis
@@ -98,6 +98,7 @@ files:
98
98
  - lib/brainiac/plugins/fizzy/handlers/dedup.rb
99
99
  - lib/brainiac/plugins/fizzy/handlers/deploy.rb
100
100
  - lib/brainiac/plugins/fizzy/handlers/deployments.rb
101
+ - lib/brainiac/plugins/fizzy/handlers/summarize.rb
101
102
  - lib/brainiac/plugins/fizzy/helpers.rb
102
103
  - lib/brainiac/plugins/fizzy/hooks.rb
103
104
  - lib/brainiac/plugins/fizzy/metadata.rb