brainiac-fizzy 0.0.27 → 0.0.28

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: dabc51c154f2caad4353e83a1c837a961bf1df27139ede86181929c8fa28290d
4
- data.tar.gz: 6edde302d45dfe3628606cd005ca0ea6e3964d004bc6fe0c4c93be2d1590800d
3
+ metadata.gz: e5d55859eafd2aeccf63a2083999340a1ce4a77c01b81a8297adf136b0e553b3
4
+ data.tar.gz: b1dbff19165dec2cf391f4c258ae2a207457cdc53088ee44062ffda050530a48
5
5
  SHA512:
6
- metadata.gz: ac4ce9b1192051ce0d748d13c1ecdc834935bc503513fb101486460cfc478046749475d5eaa7cfff32252ba34e2be65efad10f3a9aecd6d8b1bdf2aa0c10cd33
7
- data.tar.gz: 24afb9c9f9af9f481be0df59afeb94546e49546033d40190cee639fb17989b90dc5011f1c7d911daea9d21be1d7c3707e4180cb4be719220aad029a91c4268d1
6
+ metadata.gz: 9438b9143636ab612da09a19be5b672ada809095317ea7ce72d9045acbebe6601d5485ba4bfd234b85edcdb54a6d676edd330a74b4241cc85c0605d590fe523d
7
+ data.tar.gz: 93fbe26b2a546b1b72e1025f7af334ac6539efe0c4144338bd6d2929b336e2ed21752cc8a1d4ef2949d380cc72635e945e0dad819afe043570c483ddc889d2d7
@@ -274,3 +274,112 @@ def resolve_deployment_url(env_config, card_tags)
274
274
  end
275
275
  env_config["url"]
276
276
  end
277
+
278
+ # Resolve the live ephemeral deployment URL for a given card number.
279
+ # Returns { env:, url: } for the environment currently occupied by this card,
280
+ # or nil if the card isn't deployed anywhere (or deployments aren't configured).
281
+ #
282
+ # Resolution order (live state is the source of truth):
283
+ # 1. A tracked dev environment (dev01/dev02...) occupied by this card, from
284
+ # deployment_state.json.
285
+ # 2. The card's ephemeral Belt environment (fizzy-<card>), from
286
+ # ephemeral_envs.json, with the public URL derived from its terraform.tfvars.
287
+ def deployment_url_for_card(card_number)
288
+ return nil unless card_number
289
+
290
+ tracked_deployment_for_card(card_number) || ephemeral_deployment_for_card(card_number)
291
+ rescue StandardError => e
292
+ LOG.warn "[Fizzy:Deploy] Could not resolve deployment URL for card ##{card_number}: #{e.message}" if defined?(LOG)
293
+ nil
294
+ end
295
+
296
+ # A tracked dev environment (dev01/dev02...) currently occupied by this card.
297
+ def tracked_deployment_for_card(card_number)
298
+ return nil unless defined?(DEPLOYMENTS_CONFIG)
299
+
300
+ config = DEPLOYMENTS_CONFIG["environments"] || {}
301
+ state = load_deployment_state
302
+
303
+ env_key, info = state.find do |key, v|
304
+ v.is_a?(Hash) && v["card_number"].to_s == card_number.to_s && v["status"] == "occupied" && config.key?(key)
305
+ end
306
+ return nil unless env_key
307
+
308
+ url = resolve_deployment_url(config[env_key], info["card_tags"])
309
+ return nil unless url
310
+
311
+ { env: env_key, url: url }
312
+ end
313
+
314
+ EPHEMERAL_ENVS_FILE = File.join(BRAINIAC_DIR, "ephemeral_envs.json")
315
+
316
+ # The active ephemeral Belt environment (fizzy-<card>) for this card, if any.
317
+ # The public URL is derived from the env's terraform.tfvars in its worktree,
318
+ # following the Belt frontend_urls convention.
319
+ def ephemeral_deployment_for_card(card_number)
320
+ info = active_ephemeral_env_info(card_number)
321
+ return nil unless info
322
+
323
+ worktree = info["worktree"]
324
+ env_name = "fizzy-#{card_number}"
325
+ return nil unless worktree
326
+
327
+ tfvars = File.join(worktree, "infrastructure", env_name, "terraform.tfvars")
328
+ return nil unless File.exist?(tfvars)
329
+
330
+ url = ephemeral_url_from_tfvars(tfvars)
331
+ return nil unless url
332
+
333
+ { env: env_name, url: url }
334
+ end
335
+
336
+ # Look up the active ephemeral env metadata for a card from ephemeral_envs.json.
337
+ def active_ephemeral_env_info(card_number)
338
+ return nil unless File.exist?(EPHEMERAL_ENVS_FILE)
339
+
340
+ state = JSON.parse(File.read(EPHEMERAL_ENVS_FILE))
341
+ info = state["fizzy-#{card_number}"]
342
+ return nil unless info.is_a?(Hash) && info["status"] == "active"
343
+
344
+ info
345
+ rescue JSON::ParserError
346
+ nil
347
+ end
348
+
349
+ # Derive the public URL from a Belt env's terraform.tfvars, matching the
350
+ # frontend_urls convention in the Belt infra module:
351
+ # parent set -> https://<env>.<parent>.<domain>
352
+ # prod -> https://<domain>
353
+ # otherwise -> https://<env>.<domain>
354
+ def ephemeral_url_from_tfvars(tfvars_path)
355
+ vars = parse_tfvars(File.read(tfvars_path))
356
+ domain = vars["domain"].to_s
357
+ return nil if domain.empty?
358
+
359
+ environment = vars["environment"].to_s
360
+ parent = vars["parent_environment"].to_s
361
+
362
+ host =
363
+ if !parent.empty?
364
+ "#{environment}.#{parent}.#{domain}"
365
+ elsif environment == "prod"
366
+ domain
367
+ else
368
+ "#{environment}.#{domain}"
369
+ end
370
+
371
+ "https://#{host}"
372
+ end
373
+
374
+ # Minimal HCL parser for `key = "value"` lines in terraform.tfvars.
375
+ def parse_tfvars(contents)
376
+ vars = {}
377
+ contents.each_line do |line|
378
+ stripped = line.strip
379
+ next if stripped.empty? || stripped.start_with?("#")
380
+
381
+ match = stripped.match(/\A(\w+)\s*=\s*"([^"]*)"/)
382
+ vars[match[1]] = match[2] if match
383
+ end
384
+ vars
385
+ end
@@ -72,9 +72,14 @@ module Brainiac
72
72
  env = Helpers.fizzy_env_for(agent_name)
73
73
 
74
74
  pr_url = Helpers.send(:detect_pr_url, branch, project_config)
75
+ deployment = respond_to?(:deployment_url_for_card, true) ? deployment_url_for_card(card_number) : nil
75
76
  body = "<p>✅ Work completed on this card.</p>"
76
- body += "<p><a href=\"#{pr_url}\">View PR</a></p>" if pr_url
77
77
  body += "<p><strong>Branch:</strong> <code>#{branch}</code></p>"
78
+ body += "<p><strong>PR:</strong> <a href=\"#{pr_url}\">#{pr_url}</a></p>" if pr_url
79
+ if deployment
80
+ body += "<p><strong>Deployment (#{deployment[:env]}):</strong> " \
81
+ "<a href=\"#{deployment[:url]}\">#{deployment[:url]}</a></p>"
82
+ end
78
83
 
79
84
  run_cmd("fizzy", "comment", "create", "--card", card_number.to_s, "--body", body,
80
85
  chdir: repo_path, env: env)
@@ -171,16 +171,13 @@ module Brainiac
171
171
  last_agent_comment = agent_comments.last
172
172
  return false unless last_agent_comment
173
173
 
174
- # Append footer if applicable
174
+ # Backup: ensure the comment surfaces branch, PR link, and live deployment link.
175
+ # Whatever the agent already included is left alone; we only append what's missing.
175
176
  body = last_agent_comment.dig("body", "html") || ""
176
- unless footer_already_present?(body)
177
- branch = detect_branch_from_comment(body, card_number)
178
- if branch
179
- footer = build_comment_footer(branch, card_number, project_config)
180
- updated_body = body + footer
181
- run_cmd("fizzy", "comment", "update", last_agent_comment["id"], "--card", card_number.to_s,
182
- "--body", updated_body, chdir: repo_path, env: env)
183
- end
177
+ updated_body = ensure_reference_footer(body, card_number, project_config)
178
+ if updated_body != body
179
+ run_cmd("fizzy", "comment", "update", last_agent_comment["id"], "--card", card_number.to_s,
180
+ "--body", updated_body, chdir: repo_path, env: env)
184
181
  end
185
182
 
186
183
  true
@@ -189,6 +186,42 @@ module Brainiac
189
186
  false
190
187
  end
191
188
 
189
+ # Build the reference footer (Branch / PR / Deployment) and append any pieces
190
+ # that the agent's comment body doesn't already contain. Returns the (possibly
191
+ # unchanged) body. Detection is deliberately lenient — if the branch name, PR
192
+ # URL, or deployment URL already appears anywhere in the body, it's not re-added.
193
+ def ensure_reference_footer(body, card_number, project_config)
194
+ branch = detect_branch_from_comment(body, card_number)
195
+ return body unless branch
196
+
197
+ pr_url = detect_pr_url(branch, project_config)
198
+ deployment = detect_deployment(card_number)
199
+
200
+ footer_lines = []
201
+ footer_lines << "<strong>Branch:</strong> <code>#{branch}</code>" unless body.include?(branch)
202
+ if pr_url && !body.include?(pr_url) && !body.include?("github.com/#{project_config["github_repo"]}/pull")
203
+ footer_lines << "<strong>PR:</strong> <a href=\"#{pr_url}\">#{pr_url}</a>"
204
+ end
205
+ if deployment && !body.include?(deployment[:url])
206
+ footer_lines << "<strong>Deployment (#{deployment[:env]}):</strong> " \
207
+ "<a href=\"#{deployment[:url]}\">#{deployment[:url]}</a>"
208
+ end
209
+
210
+ return body if footer_lines.empty?
211
+
212
+ "#{body}<p><em>#{footer_lines.join(" &middot; ")}</em></p>"
213
+ end
214
+
215
+ # Resolve the live deployment for a card as { env:, url: }, or nil.
216
+ # Reads live deployment state (tracked dev envs from deployment_state.json,
217
+ # and the card's ephemeral Belt env from ephemeral_envs.json) — the actual
218
+ # source of truth for which environment a card currently occupies.
219
+ def detect_deployment(card_number)
220
+ return nil unless respond_to?(:deployment_url_for_card, true)
221
+
222
+ deployment_url_for_card(card_number)
223
+ end
224
+
192
225
  def ensure_fizzy_yaml!(chdir, project_config)
193
226
  fizzy_yaml_dest = File.join(chdir, ".fizzy.yaml")
194
227
  return if File.exist?(fizzy_yaml_dest)
@@ -345,33 +378,6 @@ module Brainiac
345
378
  rescue StandardError
346
379
  nil
347
380
  end
348
-
349
- # True if the comment body already carries a "Branch:" footer/label, in
350
- # either the footer format (`<em>Branch:`) or the agent-authored format
351
- # (`<strong>Branch:</strong>`). Prevents appending a duplicate footer.
352
- def footer_already_present?(body)
353
- body.include?("<em>Branch:") ||
354
- body.match?(%r{<strong>\s*Branch:\s*</strong>}i) ||
355
- body.match?(/(^|>)\s*Branch:\s*</)
356
- end
357
-
358
- # Build the "<em>Branch: <code>...</code> | PR | Env</em>" footer.
359
- # Includes the ephemeral env link when the card has an active env.
360
- def build_comment_footer(branch, card_number, project_config)
361
- pr_url = detect_pr_url(branch, project_config)
362
- env_url = detect_env_url(card_number)
363
-
364
- footer = "<p><em>Branch: <code>#{branch}</code>"
365
- footer += " | <a href=\"#{pr_url}\">PR</a>" if pr_url
366
- footer += " | <a href=\"#{env_url}\">Env</a>" if env_url
367
- footer += "</em></p>"
368
- footer
369
- end
370
-
371
- # URL of the ephemeral Belt environment for this card, if one is active.
372
- def detect_env_url(card_number)
373
- EnvUrl.for_card(card_number)
374
- end
375
381
  end
376
382
  end
377
383
  end
@@ -62,10 +62,19 @@ module Brainiac
62
62
  {{PR_TARGET_INSTRUCTION}}
63
63
 
64
64
  **Response destination: Post your response as a comment on Fizzy card #{{CARD_NUMBER}}.**
65
- Your comment MUST include a concise summary of what you did, a PR link, and the branch name.
65
+ Your comment MUST include a concise summary of what you did.
66
66
 
67
- **MANDATORY: Always include the branch name in your comment.** Use this format:
68
- `<p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>`
67
+ **MANDATORY: Always end your comment with a References block containing all three of the following:**
68
+ - The branch name
69
+ - A link to the PR
70
+ - A link to the live deployed ephemeral environment (if this card was deployed — check the card's [deploy] tag / deployment; if not deployed, state "Not deployed")
71
+
72
+ Use this exact HTML format:
73
+ ```
74
+ <p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>
75
+ <p><strong>PR:</strong> <a href="PR_URL_HERE">PR_URL_HERE</a></p>
76
+ <p><strong>Deployment:</strong> <a href="DEPLOY_URL_HERE">DEPLOY_URL_HERE</a></p>
77
+ ```
69
78
  PROMPT
70
79
 
71
80
  FOLLOWUP_WORKTREE = <<~'PROMPT'
@@ -123,11 +132,19 @@ module Brainiac
123
132
  Read your memory file for this card and write a brief summary comment.
124
133
 
125
134
  **Response destination: Post your response as a comment on Fizzy card #{{CARD_NUMBER}}.**
126
- Include what you did, any PR link, and the branch name.
127
- Keep it concise — this is a summary, not a full report.
135
+ Include what you did. Keep it concise — this is a summary, not a full report.
128
136
 
129
- **MANDATORY: Always include the branch name in your comment.** Use this format:
130
- `<p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>`
137
+ **MANDATORY: Always end your comment with all three of the following:**
138
+ - The branch name
139
+ - A link to the PR
140
+ - A link to the live deployed ephemeral environment (or "Not deployed" if the card wasn't deployed)
141
+
142
+ Use this exact HTML format:
143
+ ```
144
+ <p><strong>Branch:</strong> <code>{{BRANCH}}</code></p>
145
+ <p><strong>PR:</strong> <a href="PR_URL_HERE">PR_URL_HERE</a></p>
146
+ <p><strong>Deployment:</strong> <a href="DEPLOY_URL_HERE">DEPLOY_URL_HERE</a></p>
147
+ ```
131
148
  PROMPT
132
149
 
133
150
  UAT_TESTING = <<~'PROMPT'
@@ -3,7 +3,7 @@
3
3
  module Brainiac
4
4
  module Plugins
5
5
  module Fizzy
6
- VERSION = "0.0.27"
6
+ VERSION = "0.0.28"
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,6 @@
3
3
  require_relative "fizzy/version"
4
4
  require_relative "fizzy/metadata"
5
5
  require_relative "fizzy/config"
6
- require_relative "fizzy/env_url"
7
6
  require_relative "fizzy/helpers"
8
7
  require_relative "fizzy/prompts"
9
8
  require_relative "fizzy/planning"
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.27
4
+ version: 0.0.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis
@@ -92,7 +92,6 @@ files:
92
92
  - lib/brainiac/plugins/fizzy/cli.rb
93
93
  - lib/brainiac/plugins/fizzy/config.rb
94
94
  - lib/brainiac/plugins/fizzy/delegators.rb
95
- - lib/brainiac/plugins/fizzy/env_url.rb
96
95
  - lib/brainiac/plugins/fizzy/handlers/assignment.rb
97
96
  - lib/brainiac/plugins/fizzy/handlers/card_index.rb
98
97
  - lib/brainiac/plugins/fizzy/handlers/comments.rb
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Brainiac
4
- module Plugins
5
- module Fizzy
6
- # Derives the public URL of an ephemeral Belt environment from its
7
- # terraform.tfvars. Kept separate from Helpers so the pure URL/HCL logic
8
- # stays small and independently testable.
9
- module EnvUrl
10
- module_function
11
-
12
- # URL of the ephemeral Belt environment for a card, if one is active.
13
- # The env name is `fizzy-<card_number>`; the public URL is derived from the
14
- # env's terraform.tfvars, matching the frontend_urls convention in the Belt
15
- # infra module:
16
- # parent set -> https://<env>.<parent>.<domain>
17
- # prod -> https://<domain>
18
- # otherwise -> https://<env>.<domain>
19
- def for_card(card_number)
20
- return nil unless defined?(BeltConfig)
21
-
22
- env_name = BeltConfig.ephemeral_env_for_card(card_number)
23
- return nil unless BeltConfig.ephemeral_env?(env_name)
24
-
25
- info = BeltConfig.respond_to?(:ephemeral_env_info) ? BeltConfig.ephemeral_env_info(env_name) : nil
26
- worktree = info && info["worktree"]
27
- return nil unless worktree
28
-
29
- tfvars = File.join(worktree, "infrastructure", env_name.to_s, "terraform.tfvars")
30
- return nil unless File.exist?(tfvars)
31
-
32
- from_tfvars(tfvars)
33
- rescue StandardError => e
34
- LOG.warn "[Fizzy] Could not detect env URL for card ##{card_number}: #{e.message}" if defined?(LOG)
35
- nil
36
- end
37
-
38
- def from_tfvars(tfvars_path)
39
- vars = parse(File.read(tfvars_path))
40
- domain = vars["domain"].to_s
41
- return nil if domain.empty?
42
-
43
- environment = vars["environment"].to_s
44
- parent = vars["parent_environment"].to_s
45
-
46
- host =
47
- if !parent.empty?
48
- "#{environment}.#{parent}.#{domain}"
49
- elsif environment == "prod"
50
- domain
51
- else
52
- "#{environment}.#{domain}"
53
- end
54
-
55
- "https://#{host}"
56
- end
57
-
58
- # Minimal HCL parser for `key = "value"` lines in terraform.tfvars.
59
- def parse(contents)
60
- vars = {}
61
- contents.each_line do |line|
62
- stripped = line.strip
63
- next if stripped.empty? || stripped.start_with?("#")
64
-
65
- match = stripped.match(/\A(\w+)\s*=\s*"([^"]*)"/)
66
- vars[match[1]] = match[2] if match
67
- end
68
- vars
69
- end
70
- end
71
- end
72
- end
73
- end