Module: Brainiac::Plugins::Fizzy::Helpers

Defined in:
lib/brainiac/plugins/fizzy/helpers.rb

Overview

Fizzy-specific helper functions. These were previously in lib/brainiac/helpers.rb in core.

Class Method Summary collapse

Class Method Details



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 86

def append_fizzy_comment_footer(card_number, project_config:, agent_name: nil)
  repo_path = project_config["repo_path"]
  env = fizzy_env_for(agent_name || AI_AGENT_NAME)

  output = run_cmd("fizzy", "comment", "list", "--card", card_number.to_s, chdir: repo_path, env: env)
  comments = JSON.parse(output)["data"] || []
  agent_display = agent_display_name(agent_name || AI_AGENT_NAME)

  last_agent_comment = comments.reverse.find do |c|
    c["creator_name"]&.downcase == agent_display.downcase
  end
  return unless last_agent_comment

  # Check if footer already exists
  body = last_agent_comment.dig("body", "html") || ""
  return if body.include?("<em>Branch:")

  # Detect branch from comment content or card map
  branch = detect_branch_from_comment(body, card_number)
  return unless branch

  pr_url = detect_pr_url(branch, project_config)
  footer = "<p><em>Branch: <code>#{branch}</code>"
  footer += " | <a href=\"#{pr_url}\">PR</a>" if pr_url
  footer += "</em></p>"

  updated_body = body + footer
  run_cmd("fizzy", "comment", "update", last_agent_comment["id"], "--card", card_number.to_s,
          "--body", updated_body, chdir: repo_path, env: env)
rescue StandardError => e
  LOG.warn "[Fizzy] Could not append footer to card ##{card_number}: #{e.message}" if defined?(LOG)
end

.default_fizzy_envObject



32
33
34
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 32

def default_fizzy_env
  fizzy_env_for(AI_AGENT_NAME)
end

.detect_planning_mode(text:, tags:, card_internal_id:, card_number:) ⇒ Object

Determines whether a card should run in planning mode. Returns nil if planning is not active, or { card_id: } if it is. Planning mode is triggered by a "plan" or "planning" tag on the card.



145
146
147
148
149
150
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 145

def detect_planning_mode(text:, tags:, card_internal_id:, card_number:)
  tag_names = (tags || []).map { |t| t.is_a?(Hash) ? t["name"] : t.to_s }.map(&:downcase)
  return nil unless tag_names.include?("plan") || tag_names.include?("planning")

  { card_id: card_number || card_internal_id }
end

.ensure_fizzy_yaml!(chdir, project_config) ⇒ Object



119
120
121
122
123
124
125
126
127
128
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 119

def ensure_fizzy_yaml!(chdir, project_config)
  fizzy_yaml_dest = File.join(chdir, ".fizzy.yaml")
  return if File.exist?(fizzy_yaml_dest)

  fizzy_yaml_src = File.join(project_config["repo_path"], ".fizzy.yaml")
  return unless File.exist?(fizzy_yaml_src)

  FileUtils.cp(fizzy_yaml_src, fizzy_yaml_dest)
  LOG.info "[Fizzy] Copied .fizzy.yaml to #{chdir}" if defined?(LOG)
end

.fetch_card_comments(card_number, repo_path:, env:) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 61

def fetch_card_comments(card_number, repo_path:, env:)
  output = run_cmd("fizzy", "comment", "list", "--card", card_number.to_s, chdir: repo_path, env: env)
  comments = JSON.parse(output)["data"] || []
  return "" if comments.empty?

  comments.last(15).map do |c|
    body = c.dig("body", "plain_text") || ""
    body = "#{body[0..500]}..." if body.length > 500
    "**#{c["creator_name"]}** (#{c["id"]}):\n#{body}"
  end.join("\n\n---\n\n")
rescue StandardError => e
  LOG.warn "[Fizzy] Could not fetch comments for card ##{card_number}: #{e.message}" if defined?(LOG)
  ""
end

.fetch_card_details(card_number, repo_path:, env:) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 47

def fetch_card_details(card_number, repo_path:, env:)
  output = run_cmd("fizzy", "card", "show", card_number.to_s, chdir: repo_path, env: env)
  card = JSON.parse(output)["data"]
  return "" unless card

  parts = []
  parts << "**Title:** #{card["title"]}"
  parts << "**Body:**\n#{card.dig("body", "plain_text")}" if card.dig("body", "plain_text")
  parts.join("\n")
rescue StandardError => e
  LOG.warn "[Fizzy] Could not fetch card ##{card_number}: #{e.message}" if defined?(LOG)
  ""
end

.fizzy_env_for(agent_name) ⇒ Object



27
28
29
30
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 27

def fizzy_env_for(agent_name)
  token = fizzy_token_for(agent_name) || fizzy_token_for(AI_AGENT_NAME)
  token ? { "FIZZY_TOKEN" => token } : {}
end

.fizzy_token_for(agent_name) ⇒ Object



23
24
25
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 23

def fizzy_token_for(agent_name)
  agent_env_var(agent_name, "FIZZY_TOKEN")
end

.move_card_to_column(card_number, column_name, project_config:, agent_name: nil) ⇒ Object



76
77
78
79
80
81
82
83
84
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 76

def move_card_to_column(card_number, column_name, project_config:, agent_name: nil)
  board_key = Config.board_key_for_project(project_config)
  column_id = Config.board_column_id(board_key, column_name) if board_key
  return unless column_id

  repo_path = project_config["repo_path"]
  env = fizzy_env_for(agent_name || AI_AGENT_NAME)
  run_cmd("fizzy", "card", "column", card_number.to_s, "--column", column_id, chdir: repo_path, env: env)
end

.prefetch_card_context(card_number, repo_path:, agent_name: nil) ⇒ Object



36
37
38
39
40
41
42
43
44
45
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 36

def prefetch_card_context(card_number, repo_path:, agent_name: nil)
  env = fizzy_env_for(agent_name || AI_AGENT_NAME)
  card_details = fetch_card_details(card_number, repo_path: repo_path, env: env)
  card_comments = fetch_card_comments(card_number, repo_path: repo_path, env: env)

  context = ""
  context += "## Card Details\n#{card_details}\n\n" unless card_details.empty?
  context += "## Recent Comments\n#{card_comments}\n" unless card_comments.empty?
  context
end

.scrub_invalid_attachments!(dir) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 130

def scrub_invalid_attachments!(dir)
  attachments_dir = File.join(dir, ".fizzy-attachments")
  return unless Dir.exist?(attachments_dir)

  Dir.glob(File.join(attachments_dir, "*")).each do |file|
    next unless File.file?(file)
    next if File.size(file) > 100 # Keep files with real content

    File.delete(file)
  end
end

.verify_signature!(request, payload_body, board_key: nil) ⇒ Object

Returns true if signature is valid (or no secret configured). Returns false if signature verification fails.



12
13
14
15
16
17
18
19
20
21
# File 'lib/brainiac/plugins/fizzy/helpers.rb', line 12

def verify_signature!(request, payload_body, board_key: nil)
  signature = request.env["HTTP_X_WEBHOOK_SIGNATURE"]
  return false unless signature

  secret = board_key ? Config.board_webhook_secret(board_key) : ENV.fetch("FIZZY_WEBHOOK_SECRET", nil)
  return false unless secret

  computed = OpenSSL::HMAC.hexdigest("sha256", secret, payload_body)
  Rack::Utils.secure_compare(signature, computed)
end