brainiac-fizzy 0.0.27 → 0.0.29

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: 93dfc73c8a3984f834883fbc59057446f220cf640ef14feecdc3882eab0f5e79
4
+ data.tar.gz: 16dccf767e981ab8b7ec4f4c9ad570a6af713fe9deb410f0e3163f8745def891
5
5
  SHA512:
6
- metadata.gz: ac4ce9b1192051ce0d748d13c1ecdc834935bc503513fb101486460cfc478046749475d5eaa7cfff32252ba34e2be65efad10f3a9aecd6d8b1bdf2aa0c10cd33
7
- data.tar.gz: 24afb9c9f9af9f481be0df59afeb94546e49546033d40190cee639fb17989b90dc5011f1c7d911daea9d21be1d7c3707e4180cb4be719220aad029a91c4268d1
6
+ metadata.gz: dfabf4b7239f9fa9181fb327c24660e7c39c0a1558151362330ff67c8c334ba9565239e5da35c1335c67bd5dfdd246cacf8295509e6e61c86b9afb0f2f6be43d
7
+ data.tar.gz: ebafbeceb274b07e70f23f69d8b7123af573d646e58d92eb756b693bf2cf4d87a12ab7fc79ddca3ced785c54d3beca9c8c602a4c655d0158067cc952d441a90f
data/README.md CHANGED
@@ -37,6 +37,8 @@ Fizzy configuration lives in `~/.brainiac/fizzy.json` (same as before):
37
37
  { "id": "user-id-1", "name": "Andy", "human": true },
38
38
  { "id": "agent-id-1", "name": "Galen", "human": false }
39
39
  ],
40
+ "uat_agent": false,
41
+ "uat_agent_tag": "uat",
40
42
  "boards": {
41
43
  "development": {
42
44
  "board_id": "your-board-id",
@@ -51,6 +53,25 @@ Fizzy configuration lives in `~/.brainiac/fizzy.json` (same as before):
51
53
  }
52
54
  ```
53
55
 
56
+ ### UAT agent (`uat_agent`)
57
+
58
+ When a PR is merged and its card moves to the UAT column, Brainiac can dispatch
59
+ an LLM agent to write manual testing steps as a card comment. This spends tokens
60
+ on every merge, so it's **disabled by default**. Set `"uat_agent": true` at the
61
+ top level of `fizzy.json` to opt back in globally. The card still moves to UAT
62
+ and gets the PR-merged comment either way — only the agent dispatch is gated.
63
+
64
+ **Per-card opt-in:** Even with `uat_agent` off globally, you can trigger the UAT
65
+ agent for a single card by adding the `uat` tag to it. When the card's PR merges,
66
+ Brainiac fetches the card's live tags and dispatches the UAT agent if the tag is
67
+ present. Customize the tag name with `"uat_agent_tag": "your-tag"`, or set it to
68
+ `false`/`null` to disable per-card overrides entirely. This gives you three
69
+ states:
70
+
71
+ - `uat_agent: true` — dispatch on every merge
72
+ - `uat_agent: false` (default) + `uat` tag on a card — dispatch for that card only
73
+ - `uat_agent: false` + no tag — never dispatch (pure bookkeeping, zero tokens)
74
+
54
75
  ## What This Plugin Handles
55
76
 
56
77
  | Event | Action |
@@ -82,6 +82,26 @@ module Brainiac
82
82
  nil
83
83
  end
84
84
 
85
+ # Whether to dispatch an LLM agent to write UAT testing steps when a
86
+ # PR is merged and the card moves to UAT. Disabled by default to save
87
+ # tokens — set "uat_agent": true in ~/.brainiac/fizzy.json to enable
88
+ # globally. Individual cards can still opt in via the UAT tag even when
89
+ # this is false (see #uat_agent_tag).
90
+ def uat_agent_enabled?
91
+ @config.fetch("uat_agent", false) == true
92
+ end
93
+
94
+ # The card tag that opts a single card into UAT agent dispatch even
95
+ # when uat_agent is globally disabled. Defaults to "uat". Set
96
+ # "uat_agent_tag": "some-tag" in fizzy.json to customize, or false/null
97
+ # to disable per-card overrides entirely.
98
+ def uat_agent_tag
99
+ value = @config.fetch("uat_agent_tag", "uat")
100
+ return nil if value == false || value.nil? || value.to_s.strip.empty?
101
+
102
+ value.to_s
103
+ end
104
+
85
105
  def authorized?(payload)
86
106
  creator_id = payload.dig("creator", "id")
87
107
  @authorized_user_ids.include?(creator_id)
@@ -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
@@ -134,36 +134,7 @@ module Brainiac
134
134
  # PR merged — post comment on Fizzy card, move to UAT, dispatch UAT agent
135
135
  def register_pr_merged
136
136
  Brainiac.on(:pr_merged) do |ctx|
137
- card_number = ctx[:card_number]
138
- next unless card_number
139
-
140
- card_info = ctx[:card_info]
141
- card_agent = card_info["agent"]
142
- env = Helpers.fizzy_env_for(card_agent)
143
- repo_path = ctx[:repo_path]
144
- board_key = card_info.dig("sources", "fizzy", "board_key") || Config.board_key_for_project(ctx[:project_config])
145
-
146
- # Post PR link comment
147
- pr_url = ctx[:pr_url]
148
- pr_title = ctx[:pr_title]
149
- branch = ctx[:branch]
150
- comment_body = "<p>PR merged into main: <a href=\"#{pr_url}\">#{pr_title}</a></p>" \
151
- "<p>Branch: <code>#{branch}</code></p>"
152
- run_cmd("fizzy", "comment", "create", "--card", card_number.to_s, "--body", comment_body,
153
- chdir: repo_path, env: env)
154
-
155
- # Move card to UAT
156
- Helpers.move_card_to_column(card_number, "uat",
157
- project_config: ctx[:project_config],
158
- agent_name: card_agent,
159
- board_key: board_key)
160
- record_self_move(card_number)
161
-
162
- # Clear deployment tracking
163
- clear_deployment_for_card(card_number) if respond_to?(:clear_deployment_for_card)
164
-
165
- # Dispatch UAT agent
166
- dispatch_fizzy_uat_agent(ctx)
137
+ handle_pr_merged(ctx)
167
138
  rescue StandardError => e
168
139
  LOG.error "[Fizzy] Error in pr_merged hook: #{e.message}" if defined?(LOG)
169
140
  end
@@ -292,6 +263,75 @@ module Brainiac
292
263
 
293
264
  # --- Private helpers ---
294
265
 
266
+ # Body of the :pr_merged hook. Posts the PR link comment, moves the
267
+ # card to UAT, clears deployment tracking, and dispatches the UAT
268
+ # agent when enabled. Extracted from the hook block to keep the
269
+ # registration method within complexity limits.
270
+ def handle_pr_merged(ctx)
271
+ card_number = ctx[:card_number]
272
+ return unless card_number
273
+
274
+ card_info = ctx[:card_info]
275
+ card_agent = card_info["agent"]
276
+ env = Helpers.fizzy_env_for(card_agent)
277
+ repo_path = ctx[:repo_path]
278
+ board_key = card_info.dig("sources", "fizzy", "board_key") || Config.board_key_for_project(ctx[:project_config])
279
+
280
+ # Post PR link comment
281
+ comment_body = "<p>PR merged into main: <a href=\"#{ctx[:pr_url]}\">#{ctx[:pr_title]}</a></p>" \
282
+ "<p>Branch: <code>#{ctx[:branch]}</code></p>"
283
+ run_cmd("fizzy", "comment", "create", "--card", card_number.to_s, "--body", comment_body,
284
+ chdir: repo_path, env: env)
285
+
286
+ # Move card to UAT
287
+ Helpers.move_card_to_column(card_number, "uat",
288
+ project_config: ctx[:project_config],
289
+ agent_name: card_agent,
290
+ board_key: board_key)
291
+ record_self_move(card_number)
292
+
293
+ # Clear deployment tracking
294
+ clear_deployment_for_card(card_number) if respond_to?(:clear_deployment_for_card)
295
+
296
+ # Dispatch UAT agent — off by default to save tokens. Globally
297
+ # enabled via "uat_agent": true, or per-card via the UAT tag
298
+ # (default "uat") even when the global flag is off.
299
+ if uat_agent_dispatch?(card_number, repo_path: repo_path, env: env)
300
+ dispatch_fizzy_uat_agent(ctx)
301
+ else
302
+ log_uat_dispatch_skipped(card_number)
303
+ end
304
+ end
305
+
306
+ # Log that UAT dispatch was skipped for a card (global flag off, no tag).
307
+ def log_uat_dispatch_skipped(card_number)
308
+ return unless defined?(LOG)
309
+
310
+ LOG.info "[Fizzy] UAT agent disabled (set \"uat_agent\": true in fizzy.json, or add the " \
311
+ "\"#{Config.uat_agent_tag}\" tag to this card) — skipping dispatch for card ##{card_number}"
312
+ end
313
+
314
+ # Decide whether to dispatch the UAT agent for a merged card.
315
+ # True when globally enabled, OR when the card carries the configured
316
+ # UAT tag (per-card opt-in). Card tags aren't in the pr_merged ctx, so
317
+ # we fetch them live — only when the global flag is off, to avoid an
318
+ # extra API call when it's already enabled.
319
+ def uat_agent_dispatch?(card_number, repo_path:, env: nil)
320
+ return true if Config.uat_agent_enabled?
321
+
322
+ tag = Config.uat_agent_tag
323
+ return false unless tag && card_number && repo_path
324
+
325
+ tags = Helpers.fetch_card_tags(card_number, repo_path: repo_path, env: env)
326
+ return false unless tags
327
+
328
+ has_tag = Helpers.card_has_tag?(tags, tag)
329
+ if has_tag && defined?(LOG)
330
+ LOG.info "[Fizzy] Card ##{card_number} has \"#{tag}\" tag — dispatching UAT agent despite global flag being off"
331
+ end
332
+ has_tag
333
+ end
334
+
295
335
  def dispatch_fizzy_uat_agent(ctx)
296
336
  card_number = ctx[:card_number]
297
337
  card_info = ctx[:card_info]
@@ -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.29"
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.29
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