brainiac-fizzy 0.0.8 → 0.0.10

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: 98a0dcf9fc45aff45ad0dd467090b4f7ef96e426ce2862fcf5b9251baf4fc519
4
- data.tar.gz: eb2b8ec913902f3365efefa1424ae271f3e63f94161c0bbaddc116f9c0ec1bcb
3
+ metadata.gz: 58da7bc4b4b3365df978936518462a054cd9aa1f4b1715cfdb45c41820c10cac
4
+ data.tar.gz: e3b3c47d4bbc5f03e58d99fb5281fc67ababeebf299ed78d505cd831556e36f1
5
5
  SHA512:
6
- metadata.gz: 065dd360d8c26815dc3122e652003c40296edd8fcd582ac9d23a8446b3e29bbcff7d2983b468ad4c395b5729d1ee309a26756af663291ea5385ba4325d7d885d
7
- data.tar.gz: 969d24c897c660ef777576c258df0b26d9f86d478525306f53e5836bcb36716ad6b9b9d6133a465efce89a14c4fab51f5b46b3f44e2f563fdbf885d7caaccaad
6
+ metadata.gz: 0cd98dc6c45a30b806cb8da059fee1e8d49d36032fba262e0cbc9d5e82d563daa6f32bbd6b5419a3f7ae9b139ecc9613f07b93202edcb8dfb44f823e2b8867af
7
+ data.tar.gz: 7cd864aa4ab8b418052ce1c251b17f89bab7aa1fe2163efb6df3bab7df5629d57386180fe52f5d6d6983d2c9910fdd6a27fcc0bdf650e0e1186798b388cdec7f
@@ -50,6 +50,18 @@ def detect_planning_mode(text:, tags:, card_internal_id:, card_number:)
50
50
  card_internal_id: card_internal_id, card_number: card_number)
51
51
  end
52
52
 
53
+ def lookup_fizzy_card_info(card_internal_id)
54
+ Brainiac::Plugins::Fizzy::Helpers.lookup_fizzy_card_info(card_internal_id)
55
+ end
56
+
57
+ def update_fizzy_work_item(card_internal_id, updates)
58
+ Brainiac::Plugins::Fizzy::Helpers.update_fizzy_work_item(card_internal_id, updates)
59
+ end
60
+
61
+ def resolve_card_number(internal_id, repo_path:)
62
+ Brainiac::Plugins::Fizzy::Helpers.resolve_card_number(internal_id, repo_path: repo_path)
63
+ end
64
+
53
65
  def verify_fizzy_signature!(request, payload_body, board_key: nil)
54
66
  Brainiac::Plugins::Fizzy::Helpers.verify_signature!(request, payload_body, board_key: board_key)
55
67
  end
@@ -46,11 +46,23 @@ def handle_card_assigned(payload)
46
46
  react_to_assignment(card_number, repo_path, assigned_agent)
47
47
  worktree_path = setup_assigned_worktree(repo_path, branch, card_internal_id, card_number, project_key, assigned_agent)
48
48
 
49
+ initial_cli = detect_cli_provider(tags: tags)
50
+ initial_model = detect_model(project_config, tags: tags)
51
+ initial_effort = detect_effort(project_config, tags: tags)
52
+
53
+ # Persist initial overrides from card tags to the work item
54
+ resolve_work_item_overrides(
55
+ branch: branch,
56
+ inline_cli_provider: initial_cli,
57
+ inline_model: initial_model,
58
+ inline_effort: initial_effort
59
+ )
60
+
49
61
  dispatch_assigned_card(
50
62
  card_number: card_number, card_internal_id: card_internal_id, title: title, tags: tags,
51
63
  branch: branch, worktree_path: worktree_path, project_config: project_config, project_key: project_key,
52
- agent_name: assigned_agent, model: detect_model(project_config, tags: tags),
53
- effort: detect_effort(project_config, tags: tags), cli_provider_override: detect_cli_provider(tags: tags)
64
+ agent_name: assigned_agent, model: initial_model,
65
+ effort: initial_effort, cli_provider_override: initial_cli
54
66
  )
55
67
  end
56
68
 
@@ -103,12 +115,10 @@ def setup_assigned_worktree(repo_path, branch, card_internal_id, card_number, pr
103
115
  worktree_path = File.join(File.dirname(repo_path), "#{File.basename(repo_path)}--#{branch}")
104
116
  worktree_path = create_or_reuse_worktree(repo_path: repo_path, branch: branch, worktree_path: worktree_path)
105
117
 
106
- map = load_work_item_map
107
- map[card_internal_id] = {
108
- "number" => card_number, "branch" => branch, "worktree" => worktree_path,
109
- "project" => project_key, "agent" => agent_name
110
- }
111
- save_work_item_map(map)
118
+ register_work_item(
119
+ branch: branch, worktree: worktree_path, project: project_key, agent: agent_name,
120
+ source: :fizzy, source_data: { "card_internal_id" => card_internal_id, "card_number" => card_number }
121
+ )
112
122
  worktree_path
113
123
  end
114
124
 
@@ -41,11 +41,9 @@ def handle_comment(payload)
41
41
  agent_result = validate_agent_comment(creator_is_agent, is_api_sourced, creator_name, mentioned_agent, card_internal_id)
42
42
  return agent_result if agent_result
43
43
 
44
- card_info = load_work_item_map[card_internal_id]
44
+ card_info = lookup_fizzy_card_info(card_internal_id)
45
45
  comment_id = eventable["id"]
46
46
 
47
- return [200, { status: "ignored", reason: "not relevant" }.to_json] unless mentioned_agent || card_info
48
-
49
47
  project_config, project_key = resolve_fizzy_project(card_info, card_internal_id, eventable)
50
48
  return [200, { status: "ignored", reason: "no matching project" }.to_json] unless project_config
51
49
 
@@ -93,16 +91,39 @@ def build_comment_context(eventable:, plain_text:, tags:, card_internal_id:, car
93
91
  card_tags = eventable.dig("card", "tags") || []
94
92
  clean_text = tags[:clean_text]
95
93
 
94
+ inline_cli = detect_cli_provider(text: plain_text, tags: card_tags)
95
+ inline_model = detect_model(project_config, text: plain_text)
96
+ inline_effort = detect_effort(project_config, tags: card_tags, text: plain_text)
97
+
98
+ # Resolve overrides against the work item — merges stored overrides with inline tags
99
+ # and persists any new inline tags for future dispatches.
100
+ branch = card_info["branch"] if card_info.is_a?(Hash)
101
+ if branch
102
+ resolved = resolve_work_item_overrides(
103
+ branch: branch,
104
+ inline_cli_provider: inline_cli,
105
+ inline_model: inline_model,
106
+ inline_effort: inline_effort
107
+ )
108
+ effective_cli = resolved[:cli_provider]
109
+ effective_model = resolved[:model]
110
+ effective_effort = resolved[:effort]
111
+ else
112
+ effective_cli = inline_cli
113
+ effective_model = inline_model
114
+ effective_effort = inline_effort
115
+ end
116
+
96
117
  CommentContext.new(
97
118
  eventable: eventable, plain_text: clean_text, card_internal_id: card_internal_id,
98
119
  card_info: card_info, comment_id: comment_id, creator_name: creator_name,
99
120
  creator_is_agent: creator_is_agent, mentioned_agent: mentioned_agent,
100
121
  agent_name: agent_name, is_cross_agent_mention: is_cross_agent_mention,
101
122
  project_config: project_config, project_key: project_key,
102
- model: detect_model(project_config, text: plain_text),
103
- effort: detect_effort(project_config, tags: card_tags, text: plain_text),
123
+ model: effective_model,
124
+ effort: effective_effort,
104
125
  deploy_intent: deploy_intent,
105
- cli_provider_override: detect_cli_provider(text: plain_text, tags: card_tags),
126
+ cli_provider_override: effective_cli,
106
127
  card_tags: card_tags,
107
128
  worktree_override: resolve_worktree_override(tags, project_config),
108
129
  comment_vars: {
@@ -158,7 +179,7 @@ end
158
179
  def validate_agent_comment(creator_is_agent, is_api_sourced, creator_name, mentioned_agent, card_internal_id)
159
180
  return nil unless creator_is_agent || is_api_sourced
160
181
 
161
- card_info = load_work_item_map[card_internal_id]
182
+ card_info = lookup_fizzy_card_info(card_internal_id)
162
183
  card_assigned_agent = card_info&.dig("agent")
163
184
 
164
185
  agent_is_assigned = card_assigned_agent && card_assigned_agent.downcase == (creator_name || "").downcase
@@ -207,7 +228,8 @@ end
207
228
 
208
229
  def handle_cancel_command(eventable, card_internal_id)
209
230
  killed = 0
210
- card_number_for_cancel = load_work_item_map.dig(card_internal_id, "number")
231
+ cancel_card_info = lookup_fizzy_card_info(card_internal_id)
232
+ card_number_for_cancel = cancel_card_info&.dig("number")
211
233
  prefixes = ["card-#{card_internal_id}"]
212
234
  prefixes << "card-#{card_number_for_cancel}" if card_number_for_cancel
213
235
 
@@ -229,9 +251,8 @@ def handle_cancel_command(eventable, card_internal_id)
229
251
  end
230
252
 
231
253
  comment_id_for_cancel = eventable["id"]
232
- card_info_for_cancel = load_work_item_map[card_internal_id]
233
- if card_info_for_cancel && card_number_for_cancel && comment_id_for_cancel
234
- repo = (card_info_for_cancel["project"] && PROJECTS.dig(card_info_for_cancel["project"], "repo_path")) ||
254
+ if cancel_card_info && card_number_for_cancel && comment_id_for_cancel
255
+ repo = (cancel_card_info["project"] && PROJECTS.dig(cancel_card_info["project"], "repo_path")) ||
235
256
  DEFAULT_PROJECT["repo_path"]
236
257
  Thread.new do
237
258
  run_cmd("fizzy", "reaction", "create", "--card", card_number_for_cancel.to_s,
@@ -257,9 +278,7 @@ def resolve_fizzy_project(card_info, card_internal_id, eventable)
257
278
  if project_result
258
279
  project_key, project_config = project_result
259
280
  card_info["project"] = project_key
260
- map = load_work_item_map
261
- map[card_internal_id] = card_info
262
- save_work_item_map(map)
281
+ update_fizzy_work_item(card_internal_id, "project" => project_key)
263
282
  LOG.info "Backfilled project '#{project_key}' for card #{card_internal_id} in card map"
264
283
  else
265
284
  LOG.warn "No project found for card #{card_internal_id}"
@@ -305,21 +324,23 @@ def resolve_assigned_agent(card_info, card_internal_id, eventable, project_confi
305
324
  card_assignees = eventable.dig("card", "assignees") || []
306
325
  webhook_agent = card_assignees.map { |a| a["name"] }.find { |name| local_agent_names.include?(name) }
307
326
 
308
- webhook_agent = resolve_agent_via_api(card_info, card_internal_id, project_config) if webhook_agent.nil? && project_config
327
+ webhook_agent = resolve_agent_via_api(card_info, card_internal_id, eventable, project_config) if webhook_agent.nil? && project_config
309
328
 
310
329
  return nil unless webhook_agent
311
330
 
312
- map = load_work_item_map
313
- map[card_internal_id] ||= {}
314
- map[card_internal_id]["agent"] = webhook_agent
315
- save_work_item_map(map)
331
+ card_number = eventable.dig("card", "number")
332
+ update_fizzy_work_item(card_internal_id, { "agent" => webhook_agent }.tap { |h| h["number"] = card_number if card_number })
316
333
  LOG.info "Backfilled agent '#{webhook_agent}' into card map for #{card_internal_id}"
317
334
  webhook_agent
318
335
  end
319
336
 
320
- def resolve_agent_via_api(card_info, card_internal_id, project_config)
321
- api_card_number = card_info&.dig("number") || card_internal_id
322
- return nil unless api_card_number
337
+ def resolve_agent_via_api(card_info, card_internal_id, eventable, project_config)
338
+ api_card_number = card_info&.dig("number") || eventable.dig("card", "number")
339
+ api_card_number ||= resolve_card_number(card_internal_id, repo_path: project_config["repo_path"])
340
+ unless api_card_number
341
+ LOG.warn "Cannot resolve agent via API — no card number available (internal_id: #{card_internal_id})"
342
+ return nil
343
+ end
323
344
 
324
345
  output = run_cmd("fizzy", "card", "show", api_card_number.to_s,
325
346
  chdir: project_config["repo_path"], env: default_fizzy_env)
@@ -435,7 +456,7 @@ def handle_new_mention(ctx)
435
456
  card_title = card_data["title"] || "exploration"
436
457
 
437
458
  if card_number.nil?
438
- map_entry = load_work_item_map[ctx.card_internal_id]
459
+ map_entry = lookup_fizzy_card_info(ctx.card_internal_id)
439
460
  card_number = if map_entry && map_entry["number"]
440
461
  map_entry["number"]
441
462
  else
@@ -461,12 +482,10 @@ def setup_new_mention_worktree(ctx, card_number, card_title)
461
482
  repo_path = ctx.project_config["repo_path"]
462
483
  worktree_path, branch = resolve_or_create_worktree(ctx, card_number, card_title, repo_path)
463
484
 
464
- map = load_work_item_map
465
- map[ctx.card_internal_id] = {
466
- "number" => card_number, "branch" => branch, "worktree" => worktree_path,
467
- "project" => ctx.project_key, "agent" => ctx.agent_name
468
- }
469
- save_work_item_map(map)
485
+ register_work_item(
486
+ branch: branch, worktree: worktree_path, project: ctx.project_key, agent: ctx.agent_name,
487
+ source: :fizzy, source_data: { "card_internal_id" => ctx.card_internal_id, "card_number" => card_number }
488
+ )
470
489
 
471
490
  [worktree_path, branch]
472
491
  end
@@ -567,12 +586,7 @@ end
567
586
 
568
587
  def resolve_and_save_card_number(card_internal_id, project_config)
569
588
  card_number = resolve_card_number(card_internal_id, repo_path: project_config["repo_path"])
570
- if card_number
571
- map = load_work_item_map
572
- map[card_internal_id] ||= {}
573
- map[card_internal_id]["number"] = card_number
574
- save_work_item_map(map)
575
- end
589
+ update_fizzy_work_item(card_internal_id, "number" => card_number) if card_number
576
590
  card_number
577
591
  end
578
592
 
@@ -580,10 +594,7 @@ def find_and_save_worktree(card_internal_id, card_number, project_config)
580
594
  found = find_worktree_for_card(card_number, repo_path: project_config["repo_path"])
581
595
  return nil unless found
582
596
 
583
- map = load_work_item_map
584
- map[card_internal_id] ||= {}
585
- map[card_internal_id].merge!("worktree" => found[:worktree], "branch" => found[:branch])
586
- save_work_item_map(map)
597
+ update_fizzy_work_item(card_internal_id, "worktree" => found[:worktree], "branch" => found[:branch])
587
598
  LOG.info "Found worktree by card number scan: #{found[:worktree]}"
588
599
  found[:worktree]
589
600
  end
@@ -655,7 +666,7 @@ end
655
666
 
656
667
  def resolve_or_create_worktree(ctx, card_number, card_title, repo_path)
657
668
  # Check for existing worktree in card map or on disk
658
- existing_map_entry = load_work_item_map[ctx.card_internal_id]
669
+ existing_map_entry = lookup_fizzy_card_info(ctx.card_internal_id)
659
670
  if existing_map_entry && existing_map_entry["branch"] && existing_map_entry["worktree"] &&
660
671
  File.directory?(existing_map_entry["worktree"])
661
672
  LOG.info "Reusing existing worktree from card map: #{existing_map_entry["worktree"]}"
@@ -7,7 +7,7 @@
7
7
 
8
8
  def handle_deploy_comment(eventable, env_key, card_internal_id)
9
9
  comment_id = eventable["id"]
10
- card_info = load_card_map[card_internal_id]
10
+ card_info = lookup_fizzy_card_info(card_internal_id)
11
11
 
12
12
  # Validate environment exists
13
13
  deploy_config = DEPLOYMENTS_CONFIG["environments"] || {}
@@ -139,10 +139,9 @@ def clone_branch_for_deploy(eventable, card_internal_id, card_info)
139
139
  end
140
140
 
141
141
  # Update card map
142
- map = load_card_map
143
- map[card_internal_id] ||= {}
144
- map[card_internal_id].merge!("number" => card_number, "branch" => branch, "worktree" => worktree_path, "project" => project_key)
145
- save_card_map(map)
142
+ update_fizzy_work_item(card_internal_id,
143
+ "number" => card_number, "branch" => branch,
144
+ "worktree" => worktree_path, "project" => project_key)
146
145
 
147
146
  LOG.info "[Deploy] Cloned branch #{branch} into worktree #{worktree_path} for card ##{card_number}"
148
147
  { worktree: worktree_path, card_number: card_number }
@@ -168,6 +168,91 @@ module Brainiac
168
168
  { card_id: card_number || card_internal_id }
169
169
  end
170
170
 
171
+ # Look up a Fizzy card's work item info by its internal ID.
172
+ # Returns a hash with top-level "number", "agent", "branch", "worktree", "project"
173
+ # keys for backward compatibility with handler code, or nil if not found.
174
+ def lookup_fizzy_card_info(card_internal_id)
175
+ return nil unless card_internal_id
176
+
177
+ result = find_work_item_by_card(card_internal_id)
178
+ return nil unless result
179
+
180
+ _work_item_id, info = result
181
+ # Ensure "number" is at top level for fizzy handler compat
182
+ info["number"] ||= info.dig("sources", "fizzy", "card_number")
183
+ info
184
+ end
185
+
186
+ # Update or create a work item entry for a Fizzy card.
187
+ # Accepts a hash of fields to merge into the existing entry.
188
+ # Handles both new-format (wi-xxx keyed) and creates new entries properly.
189
+ def update_fizzy_work_item(card_internal_id, updates)
190
+ return unless card_internal_id
191
+
192
+ map = load_work_item_map
193
+ result = nil
194
+ work_item_id = nil
195
+
196
+ # Find existing entry by card internal ID
197
+ map.each do |wid, info|
198
+ next unless info.is_a?(Hash)
199
+
200
+ fizzy_source = info.dig("sources", "fizzy")
201
+ next unless fizzy_source && fizzy_source["card_internal_id"] == card_internal_id
202
+
203
+ work_item_id = wid
204
+ result = info
205
+ break
206
+ end
207
+
208
+ card_number = updates.delete("number")
209
+
210
+ if result
211
+ result["sources"]["fizzy"]["card_number"] = card_number if card_number
212
+ result.merge!(updates)
213
+ map[work_item_id] = result
214
+ else
215
+ new_id = generate_work_item_id(branch: updates["branch"], card_number: card_number)
216
+ map[new_id] = {
217
+ "id" => new_id,
218
+ "branch" => updates["branch"],
219
+ "worktree" => updates["worktree"],
220
+ "project" => updates["project"],
221
+ "agent" => updates["agent"],
222
+ "sources" => {
223
+ "fizzy" => {
224
+ "card_internal_id" => card_internal_id,
225
+ "card_number" => card_number
226
+ }.compact
227
+ }
228
+ }.compact.merge(updates.except("branch", "worktree", "project", "agent"))
229
+ end
230
+
231
+ save_work_item_map(map)
232
+ end
233
+
234
+ # Resolve a card number from an internal ID by querying the Fizzy API.
235
+ # Searches card lists to find the matching card.
236
+ def resolve_card_number(internal_id, repo_path:)
237
+ env = default_fizzy_env
238
+ base_cmd = %w[fizzy card list]
239
+ ["--all", "--all --indexed-by closed"].each do |flags|
240
+ cmd = base_cmd + flags.split
241
+ output = run_cmd(*cmd, chdir: repo_path, env: env)
242
+ data = JSON.parse(output)["data"] || []
243
+ match = data.find { |c| c["id"] == internal_id }
244
+ if match
245
+ LOG.info "Resolved card number #{match["number"]} for internal_id #{internal_id}" if defined?(LOG)
246
+ return match["number"]
247
+ end
248
+ rescue StandardError
249
+ next
250
+ end
251
+
252
+ LOG.warn "Could not resolve card number for internal_id #{internal_id}" if defined?(LOG)
253
+ nil
254
+ end
255
+
171
256
  private
172
257
 
173
258
  def detect_branch_from_comment(body, card_number)
@@ -175,9 +260,12 @@ module Brainiac
175
260
  match = body.match(%r{<code>(fizzy-#{card_number}-[^<]+)</code>})
176
261
  return match[1] if match
177
262
 
178
- # Fall back to card map
263
+ # Fall back to card map — check both new and old format
179
264
  map = load_work_item_map
180
- entry = map.values.find { |v| v["number"].to_s == card_number.to_s }
265
+ entry = map.values.find do |v|
266
+ v.dig("sources", "fizzy", "card_number").to_s == card_number.to_s ||
267
+ v["number"].to_s == card_number.to_s
268
+ end
181
269
  entry&.dig("branch")
182
270
  end
183
271
 
@@ -255,7 +255,10 @@ module Brainiac
255
255
  card_number = card["number"]
256
256
  next unless card_number
257
257
 
258
- map_entry = map.values.find { |info| info["number"] == card_number }
258
+ map_entry = map.values.find do |info|
259
+ info.dig("sources", "fizzy", "card_number").to_s == card_number.to_s ||
260
+ info["number"].to_s == card_number.to_s
261
+ end
259
262
  agent_name = map_entry["agent"] if map_entry
260
263
  env = Helpers.fizzy_env_for(agent_name || AI_AGENT_NAME)
261
264
 
@@ -267,8 +270,8 @@ module Brainiac
267
270
  primary_worktree: map_entry&.dig("worktree"), primary_branch: map_entry&.dig("branch"))
268
271
 
269
272
  if map_entry
270
- internal_id = map.key(map_entry)
271
- map.delete(internal_id)
273
+ work_item_id = map.key(map_entry)
274
+ map.delete(work_item_id)
272
275
  end
273
276
 
274
277
  closed << { number: card_number, url: card["url"], title: card["title"] }
@@ -3,7 +3,7 @@
3
3
  module Brainiac
4
4
  module Plugins
5
5
  module Fizzy
6
- VERSION = "0.0.8"
6
+ VERSION = "0.0.10"
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-fizzy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis