brainiac-fizzy 0.0.1

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.
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "fizzy/version"
4
+ require_relative "fizzy/config"
5
+ require_relative "fizzy/helpers"
6
+ require_relative "fizzy/prompts"
7
+ require_relative "fizzy/planning"
8
+ require_relative "fizzy/hooks"
9
+ require_relative "fizzy/delegators"
10
+
11
+ # Handler sub-modules (define top-level functions for webhook handling)
12
+ require_relative "fizzy/handlers/assignment"
13
+ require_relative "fizzy/handlers/comments"
14
+ require_relative "fizzy/handlers/dedup"
15
+ require_relative "fizzy/handlers/deploy"
16
+ require_relative "fizzy/handlers/card_index"
17
+ require_relative "fizzy/handlers/deployments"
18
+
19
+ module Brainiac
20
+ module Plugins
21
+ module Fizzy
22
+ class << self
23
+ # Called by Brainiac plugin system during server startup.
24
+ #
25
+ # @param app [Sinatra::Application] The running Brainiac server
26
+ def register(app)
27
+ # Load Fizzy config
28
+ Brainiac::Plugins::Fizzy::Config.load!
29
+
30
+ # Register channel prompt
31
+ Brainiac.register_channel_prompt(:fizzy,
32
+ Brainiac::Plugins::Fizzy::Prompts::CHANNEL,
33
+ pre_post_check: Brainiac::Plugins::Fizzy::Prompts::PRE_POST_CHECK)
34
+
35
+ # Register lifecycle hooks
36
+ Brainiac::Plugins::Fizzy::Hooks.register_all!
37
+
38
+ # Set up webhook route
39
+ setup_routes(app)
40
+
41
+ LOG.info "[Fizzy] Plugin registered (webhook: /fizzy)"
42
+ end
43
+
44
+ private
45
+
46
+ def setup_routes(app)
47
+ setup_webhook_route(app)
48
+ setup_api_routes(app)
49
+ end
50
+
51
+ def setup_webhook_route(app)
52
+ app.post "/fizzy/?:board_key?" do
53
+ content_type :json
54
+ request.body.rewind
55
+ payload_body = request.body.read
56
+ board_key = params["board_key"]
57
+
58
+ LOG.debug "[Fizzy] Webhook received: board_key=#{board_key}, content_length=#{payload_body.length}" if LOG.debug?
59
+
60
+ unless Brainiac::Plugins::Fizzy::Helpers.verify_signature!(request, payload_body, board_key: board_key)
61
+ LOG.warn "[Fizzy] Signature verification failed for board_key=#{board_key}"
62
+ halt 401, { error: "Invalid signature" }.to_json
63
+ end
64
+
65
+ payload = JSON.parse(payload_body)
66
+ event_id = payload["id"]
67
+ action = payload["action"]
68
+
69
+ LOG.info "[Fizzy] Received event #{event_id}: action=#{action}"
70
+
71
+ if already_processed?(event_id)
72
+ LOG.info "[Fizzy] Skipping duplicate event #{event_id}"
73
+ halt 200, { status: "duplicate" }.to_json
74
+ end
75
+
76
+ reload_projects!
77
+ reload_agent_registry!
78
+
79
+ status_code, body = dispatch_webhook_action(action, payload)
80
+ LOG.info "[Fizzy] #{action} response: #{status_code} - #{body}"
81
+ halt status_code, body
82
+ rescue JSON::ParserError => e
83
+ LOG.error "[Fizzy] Invalid JSON: #{e.message}"
84
+ halt 400, { error: "Invalid JSON" }.to_json
85
+ rescue StandardError => e
86
+ LOG.error "[Fizzy] Unhandled error: #{e.message}\n#{e.backtrace.first(5).join("\n")}"
87
+ halt 500, { error: e.message }.to_json
88
+ end
89
+ end
90
+
91
+ def setup_api_routes(app)
92
+ # API status endpoint
93
+ app.get "/api/fizzy" do
94
+ content_type :json
95
+ config = Brainiac::Plugins::Fizzy::Config.current
96
+ {
97
+ enabled: true,
98
+ boards: config["boards"]&.keys || [],
99
+ authorized_users: (config["authorized_users"] || []).size
100
+ }.to_json
101
+ end
102
+
103
+ # Card index API (duplicate detection)
104
+ app.get "/api/card-index" do
105
+ content_type :json
106
+ halt 404, { error: "Card index not available" }.to_json unless defined?(CARD_INDEX)
107
+
108
+ query = params["q"]
109
+ if query && !query.empty?
110
+ similar = CARD_INDEX.find_similar_cards(query)
111
+ { query: query, matches: similar, total_indexed: CARD_INDEX.size }.to_json
112
+ else
113
+ { total: CARD_INDEX.size, cards: CARD_INDEX }.to_json
114
+ end
115
+ end
116
+
117
+ # Deployment API routes (if deployments are configured)
118
+ return unless defined?(DEPLOYMENTS_CONFIG)
119
+
120
+ app.get "/api/deployments" do
121
+ content_type :json
122
+ reload_deployments_config!
123
+ reload_deployment_state!
124
+ { deployments: deployment_status }.to_json
125
+ end
126
+
127
+ app.post "/api/deployments/:env" do
128
+ content_type :json
129
+ env_key = params["env"]
130
+ request.body.rewind
131
+ payload = JSON.parse(request.body.read)
132
+ result = deploy_to_environment(env_key, worktree_path: payload["worktree"], deployed_by: payload["deployed_by"])
133
+ if result[:error]
134
+ halt 404, result.to_json
135
+ else
136
+ { status: "deployed", env: env_key, deployment: result }.to_json
137
+ end
138
+ rescue JSON::ParserError
139
+ halt 400, { error: "Invalid JSON" }.to_json
140
+ end
141
+
142
+ app.delete "/api/deployments/:env" do
143
+ content_type :json
144
+ env_key = params["env"]
145
+ state = load_deployment_state
146
+ if state.key?(env_key)
147
+ state[env_key] = { "status" => "available", "cleared_at" => Time.now.iso8601, "last_card" => state[env_key]["card_number"] }
148
+ save_deployment_state(state)
149
+ DEPLOYMENT_STATE.replace(state)
150
+ LOG.info "[Fizzy:Deploy] Manually cleared #{env_key}"
151
+ { status: "cleared", env: env_key }.to_json
152
+ else
153
+ halt 404, { error: "Unknown environment: #{env_key}" }.to_json
154
+ end
155
+ end
156
+
157
+ app.post "/api/deployments/:env/deploying" do
158
+ content_type :json
159
+ env_key = params["env"]
160
+ config = DEPLOYMENTS_CONFIG["environments"] || {}
161
+ halt 404, { error: "Unknown environment: #{env_key}" }.to_json unless config.key?(env_key)
162
+ request.body.rewind
163
+ payload = begin
164
+ JSON.parse(request.body.read)
165
+ rescue StandardError
166
+ {}
167
+ end
168
+ mark_deploying(env_key, worktree_path: payload["worktree"] || "")
169
+ LOG.info "[Fizzy:Deploy] #{env_key} marked deploying via API"
170
+ { status: "deploying", env: env_key }.to_json
171
+ end
172
+ end
173
+
174
+ def dispatch_webhook_action(action, payload)
175
+ case action
176
+ when "card_assigned"
177
+ handle_card_assigned(payload)
178
+ when "comment_created"
179
+ handle_comment(payload)
180
+ when "card_published", "card_triaged"
181
+ Brainiac::Plugins::Fizzy.handle_publish_or_triage(action, payload)
182
+ else
183
+ LOG.info "[Fizzy] Ignoring unknown action: #{action}"
184
+ [200, { status: "ignored", action: action }.to_json]
185
+ end
186
+ end
187
+
188
+ public
189
+
190
+ def handle_publish_or_triage(action, payload)
191
+ eventable = payload["eventable"] || {}
192
+ card_number = eventable["number"]&.to_s
193
+
194
+ if action == "card_triaged" && card_number
195
+ return [200, { status: "ignored", reason: "self_move" }.to_json] if self_move_recent?(card_number)
196
+ return [200, { status: "ignored", reason: "card_merged" }.to_json] if work_item_merged?(card_number)
197
+
198
+ card_key = "card-#{card_number}"
199
+ return [200, { status: "ignored", reason: "recently_completed" }.to_json] if recently_completed?(card_key)
200
+ end
201
+
202
+ if action == "card_published"
203
+ assignees = eventable["assignees"] || []
204
+ return handle_card_assigned(payload) if assignees.any? { |a| local_agent_names.include?(a["name"]) }
205
+ end
206
+
207
+ handle_card_published(payload)
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Brainiac Fizzy Plugin — entry point loaded by RubyGems.
4
+ require_relative "brainiac/plugins/fizzy"
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brainiac-fizzy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andy Davis
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: brainiac
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.0.8
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.0.8
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.25'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.25'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.75'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.75'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-performance
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.25'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.25'
82
+ description: Full Fizzy integration for Brainiac — card assignment, comment routing,
83
+ @mentions, cross-agent reviews, duplicate detection, deploy shortcuts, deployment
84
+ tracking, and planning mode. Uses Brainiac's hook system for lifecycle integration
85
+ (PR merge → card close, agent complete → column move).
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - README.md
91
+ - lib/brainiac/plugins/fizzy.rb
92
+ - lib/brainiac/plugins/fizzy/config.rb
93
+ - lib/brainiac/plugins/fizzy/delegators.rb
94
+ - lib/brainiac/plugins/fizzy/handlers/assignment.rb
95
+ - lib/brainiac/plugins/fizzy/handlers/card_index.rb
96
+ - lib/brainiac/plugins/fizzy/handlers/comments.rb
97
+ - lib/brainiac/plugins/fizzy/handlers/dedup.rb
98
+ - lib/brainiac/plugins/fizzy/handlers/deploy.rb
99
+ - lib/brainiac/plugins/fizzy/handlers/deployments.rb
100
+ - lib/brainiac/plugins/fizzy/helpers.rb
101
+ - lib/brainiac/plugins/fizzy/hooks.rb
102
+ - lib/brainiac/plugins/fizzy/planning.rb
103
+ - lib/brainiac/plugins/fizzy/prompts.rb
104
+ - lib/brainiac/plugins/fizzy/version.rb
105
+ - lib/brainiac_fizzy.rb
106
+ homepage: https://github.com/stowzilla/brainiac-fizzy
107
+ licenses:
108
+ - MIT
109
+ metadata:
110
+ rubygems_mfa_required: 'true'
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '3.4'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubygems_version: 3.6.9
126
+ specification_version: 4
127
+ summary: Fizzy card management plugin for Brainiac
128
+ test_files: []