brainiac-basecamp 0.0.7 → 0.0.11

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: 6bffdae0c7f62eef1cf55ee5c2c23fb8594dd63afc96ffee5cf3c58cdca9ce9f
4
- data.tar.gz: 3b61e5ae5ef532935c07603c55f6fa36000796ed55951e8277205c219306600b
3
+ metadata.gz: d4d21875fecdd7aa33718a8db5fa446800e8a85924490b6716a455b211d79483
4
+ data.tar.gz: 9601289488c260e891a6df6da2b63be3e9965c9c8235d11c4366622d27a24d0b
5
5
  SHA512:
6
- metadata.gz: 6eaa711a5cd134e1838335dbf31813d44ac66b6bb596d731a8c89dde9f99ecc8bca8aea65054fa166da8f40f16cec5df1509719fc201bc82bdeb55fce99abca5
7
- data.tar.gz: d60e2e46e81dd1a1798127583e6be1620fcd136c482e369fd7b7c129f5a5074cf88edf2cb2b6f6d7bd3a258372feea0886834cfabcc421081f872c89e9bd96a5
6
+ metadata.gz: f24339950cce469195888d59fbc7ddecb0b75f9a7f8279df58cc2bb681637916525341ce8b9fc86c2f38bb9e8b1718a27c9f164d14afa4acff9376f4394da3eb
7
+ data.tar.gz: 39d9128c65180e56fe30e59d6b384dbc9ce2b7648f8586498b88e7e44b4c516fcea6d06b279bc1956373f84f1a8602aa1fb697699a40b98e699b2c6ef2f97c59
@@ -214,16 +214,12 @@ module Brainiac
214
214
  # All gates have reviewed — dispatch implementation agent to address ALL feedback
215
215
  LOG.info "[Basecamp:Hooks] All gates responded for card ##{card_number} — dispatching fixes" if defined?(LOG)
216
216
  task["status"] = "in_flight"
217
+ task["dispatched_at"] = Time.now.iso8601
217
218
  task.delete("changes_debounce_started")
218
219
  save_epic_state(epic)
219
220
 
220
- # Re-assign card to implementation agent to trigger dispatch
221
- impl_agent = epic["agent"]
222
- fizzy_user_id = Orchestrator.send(:resolve_fizzy_user_id, impl_agent)
223
- if fizzy_user_id
224
- LOG.info "[Basecamp:Hooks] Re-assigning card ##{card_number} to #{impl_agent} for fixes" if defined?(LOG)
225
- safe_assign_card(card_number, fizzy_user_id)
226
- end
221
+ # Spawn agent directly safe_assign_card won't work if already assigned
222
+ dispatch_impl_directly(epic, task)
227
223
  else
228
224
  # Wait for remaining gates to respond
229
225
  responded = (task["gate_approvals"]&.size || 0) + (task["changes_requested_by"]&.size || 0)
@@ -246,11 +242,11 @@ module Brainiac
246
242
  if task_reloaded && task_reloaded["status"] == "in_review" && task_reloaded["changes_requested_by"]&.any?
247
243
  LOG.info "[Basecamp:Hooks] Debounce timeout for card ##{card_number} — forcing dispatch" if defined?(LOG)
248
244
  task_reloaded["status"] = "in_flight"
245
+ task_reloaded["dispatched_at"] = Time.now.iso8601
249
246
  task_reloaded.delete("changes_debounce_started")
250
247
  save_epic_state(epic_reloaded)
251
- # Re-assign card to trigger dispatch
252
- fizzy_user_id = Orchestrator.send(:resolve_fizzy_user_id, epic_reloaded["agent"])
253
- safe_assign_card(card_number, fizzy_user_id) if fizzy_user_id
248
+ # Spawn agent directly safe_assign_card won't work if already assigned
249
+ dispatch_impl_directly(epic_reloaded, task_reloaded)
254
250
  end
255
251
  rescue StandardError => e
256
252
  LOG.error "[Basecamp:Hooks] Debounce dispatch failed: #{e.message}" if defined?(LOG)
@@ -728,14 +724,123 @@ module Brainiac
728
724
  end
729
725
  end
730
726
 
731
- def mark_in_review(epic, card_number)
732
- task = epic["tasks"].find { |t| t["fizzy_card"] == card_number.to_i }
733
- return unless task
727
+ # Dispatch the implementation agent directly (spawns via run_agent) to address
728
+ # changes_requested feedback. Used by resume and health-check when the agent is
729
+ # no longer running but the Fizzy card is still assigned (so safe_assign_card
730
+ # would bail out with "already assigned, skipping").
731
+ #
732
+ # @param epic [Hash] Epic state
733
+ # @param task [Hash] Task within the epic
734
+ # @return [void]
735
+ def dispatch_impl_directly(epic, task)
736
+ card_number = task["fizzy_card"]
737
+ agent_name = epic["agent"]
738
+ pr_number = task["pr_number"]
739
+ project_key = task["project"]
740
+ card_key = "changes-requested-#{card_number}"
741
+
742
+ # Guard: don't spawn a duplicate if a session is already running for this task
743
+ session_alive = if defined?(session_active?)
744
+ session_active?(card_key)
745
+ elsif Object.respond_to?(:session_active?, true)
746
+ Object.send(:session_active?, card_key)
747
+ else
748
+ false
749
+ end
750
+
751
+ if session_alive
752
+ LOG.info "[Basecamp:Hooks] Session already active for #{card_key} — skipping dispatch" if defined?(LOG)
753
+ return
754
+ end
734
755
 
735
- task["status"] = "in_review"
736
- task["review_started_at"] = Time.now.iso8601
737
- epic["updated_at"] = Time.now.iso8601
738
- save_epic_state(epic)
756
+ LOG.info "[Basecamp:Hooks] Direct-dispatching #{agent_name} for changes_requested on card ##{card_number}" if defined?(LOG)
757
+
758
+ # Get project config
759
+ projects_file = File.join(ENV.fetch("BRAINIAC_DIR", File.join(Dir.home, ".brainiac")), "projects.json")
760
+ projects = File.exist?(projects_file) ? JSON.parse(File.read(projects_file)) : {}
761
+ project_config = projects[project_key]
762
+
763
+ unless project_config
764
+ LOG.error "[Basecamp:Hooks] No project config for '#{project_key}' — cannot direct-dispatch" if defined?(LOG)
765
+ return
766
+ end
767
+
768
+ repo_path = project_config["repo_path"]
769
+ unless repo_path
770
+ LOG.error "[Basecamp:Hooks] No repo_path for project '#{project_key}' — cannot direct-dispatch" if defined?(LOG)
771
+ return
772
+ end
773
+
774
+ # Find the worktree for this card
775
+ work_items_file = File.join(ENV.fetch("BRAINIAC_DIR", File.join(Dir.home, ".brainiac")), "work_items.json")
776
+ worktree_path = repo_path # Default to main repo
777
+ if File.exist?(work_items_file)
778
+ work_items = JSON.parse(File.read(work_items_file))
779
+ work_item = work_items.values.find { |wi| wi.dig("sources", "fizzy", "card_number") == card_number }
780
+ worktree_path = work_item["worktree"] if work_item&.dig("worktree")
781
+ end
782
+
783
+ # Build prompt
784
+ changers = (task["changes_requested_by"] || []).join(", ")
785
+ prompt = <<~PROMPT
786
+ ## Changes Requested — Fizzy Card ##{card_number}
787
+
788
+ Review gates have requested changes on your PR#{pr_number ? " ##{pr_number}" : ""}.
789
+ Reviewers who requested changes: #{changers}
790
+
791
+ Your job:
792
+ 1. Read the review feedback: #{pr_number ? "`gh pr view #{pr_number} --comments`" : "check the Fizzy card comments"}
793
+ 2. Address all requested changes
794
+ 3. Commit and push your fixes
795
+
796
+ After pushing, update the Fizzy card with a brief status comment noting what you fixed.
797
+ PROMPT
798
+
799
+ # Resolve GitHub App token so agent's `gh` commands run as their bot identity
800
+ github_repo = project_config&.dig("github_repo")
801
+ agent_env = github_repo ? ReviewGate.send(:resolve_agent_github_env, agent_name, github_repo) : {}
802
+
803
+ # Spawn the agent directly
804
+ pid = nil
805
+ log_file = nil
806
+
807
+ begin
808
+ pid, log_file = method(:run_agent).call(
809
+ prompt,
810
+ project_config: project_config,
811
+ chdir: worktree_path,
812
+ log_name: "changes-requested-#{card_number}",
813
+ agent_name: agent_name,
814
+ source: :basecamp,
815
+ card_number: card_number,
816
+ env: agent_env
817
+ )
818
+ rescue NameError
819
+ if Object.respond_to?(:run_agent, true)
820
+ pid, log_file = Object.send(:run_agent,
821
+ prompt,
822
+ project_config: project_config,
823
+ chdir: worktree_path,
824
+ log_name: "changes-requested-#{card_number}",
825
+ agent_name: agent_name,
826
+ source: :basecamp,
827
+ card_number: card_number,
828
+ env: agent_env)
829
+ else
830
+ LOG.warn "[Basecamp:Hooks] run_agent not available — direct impl dispatch skipped" if defined?(LOG)
831
+ return
832
+ end
833
+ end
834
+
835
+ # Register session for waybar visibility
836
+ if pid
837
+ if defined?(register_session)
838
+ register_session(card_key, pid, log_file: log_file, agent_name: agent_name)
839
+ elsif Object.respond_to?(:register_session, true)
840
+ Object.send(:register_session, card_key, pid, log_file: log_file, agent_name: agent_name)
841
+ end
842
+ LOG.info "[Basecamp:Hooks] Spawned #{agent_name} (pid #{pid}) for changes_requested on card ##{card_number}" if defined?(LOG)
843
+ end
739
844
  end
740
845
 
741
846
  def auto_merge_and_advance(epic, card_number, ctx)
@@ -203,6 +203,74 @@ module Brainiac
203
203
  dispatched
204
204
  end
205
205
 
206
+ # Dispatch only gate agents that have NOT yet responded (no approval, no changes_requested).
207
+ # Used during resume/health-check when some gates responded but others didn't.
208
+ #
209
+ # Tracks per-gate re-dispatch count to prevent infinite loops when a gate agent
210
+ # keeps crashing. After MAX_GATE_REDISPATCH_RETRIES, stops retrying that gate.
211
+ #
212
+ # IMPORTANT: Does NOT overwrite gates_dispatched_at — that timestamp represents
213
+ # the original dispatch and is used for stale detection. Overwriting it would
214
+ # reset the timeout window on every partial re-dispatch, causing infinite loops.
215
+ #
216
+ # @param epic [Hash] Epic state
217
+ # @param task [Hash] Task state
218
+ # @param pr_number [Integer, String] PR number
219
+ # @param repo_name [String] e.g. "stowzilla/brainiac-basecamp"
220
+ # @param repo_path [String] Local repo path
221
+ # @return [Array<String>] Agent names dispatched
222
+ def dispatch_missing_gates(epic:, task:, pr_number:, repo_name:, repo_path:)
223
+ responded_agents = Set.new
224
+ (task["gate_approvals"] || []).each { |a| responded_agents << a["agent"].downcase }
225
+ (task["changes_requested_by"] || []).each { |a| responded_agents << a.downcase }
226
+
227
+ missing_gates = gates.reject { |g| responded_agents.include?(g["agent"].downcase) }
228
+ return [] if missing_gates.empty?
229
+
230
+ # Track per-gate re-dispatch counts to prevent infinite loops
231
+ task["gate_redispatch_counts"] ||= {}
232
+
233
+ dispatched = []
234
+
235
+ missing_gates.each do |gate|
236
+ agent_name = gate["agent"]
237
+ role = gate["role"] || "review"
238
+
239
+ # Check retry count — stop if we've exceeded the max
240
+ retries = task["gate_redispatch_counts"][agent_name.downcase] || 0
241
+ if retries >= MAX_GATE_REDISPATCH_RETRIES
242
+ LOG.warn "[Basecamp:ReviewGate] Gate #{agent_name} exceeded max re-dispatch retries (#{retries}/#{MAX_GATE_REDISPATCH_RETRIES}) — skipping" if defined?(LOG)
243
+ next
244
+ end
245
+
246
+ LOG.info "[Basecamp:ReviewGate] Re-dispatching missing gate #{agent_name} (#{role}) to review PR ##{pr_number} (retry #{retries + 1}/#{MAX_GATE_REDISPATCH_RETRIES})" if defined?(LOG)
247
+
248
+ task["gate_redispatch_counts"][agent_name.downcase] = retries + 1
249
+
250
+ Thread.new do
251
+ dispatch_agent_for_review(
252
+ agent_name: agent_name,
253
+ role: role,
254
+ pr_number: pr_number,
255
+ repo_name: repo_name,
256
+ repo_path: repo_path,
257
+ card_number: task["fizzy_card"],
258
+ epic: epic
259
+ )
260
+ rescue StandardError => e
261
+ LOG.error "[Basecamp:ReviewGate] Failed to dispatch #{agent_name}: #{e.message}" if defined?(LOG)
262
+ end
263
+
264
+ dispatched << agent_name
265
+ end
266
+
267
+ # Record when this partial re-dispatch happened (for diagnostics)
268
+ # but do NOT overwrite gates_dispatched_at — that's the original timestamp
269
+ task["last_redispatch_at"] = Time.now.iso8601
270
+
271
+ dispatched
272
+ end
273
+
206
274
  # Build the summary comment for Fizzy after all gates pass and merge completes.
207
275
  #
208
276
  # @param task [Hash] Task state
@@ -3,7 +3,7 @@
3
3
  module Brainiac
4
4
  module Plugins
5
5
  module Basecamp
6
- VERSION = "0.0.7"
6
+ VERSION = "0.0.11"
7
7
  end
8
8
  end
9
9
  end
@@ -16,6 +16,15 @@ require_relative "basecamp/cli"
16
16
  module Brainiac
17
17
  module Plugins
18
18
  module Basecamp
19
+ # Maximum seconds to wait for a dispatched agent/gate to respond before
20
+ # considering it stale and re-dispatching. Used across resume, health-check,
21
+ # and dispatch_missing_gates.
22
+ STALE_DISPATCH_TIMEOUT = 300 # 5 minutes
23
+
24
+ # Maximum number of times a gate will be re-dispatched for the same review
25
+ # cycle before giving up (prevents infinite re-dispatch loops).
26
+ MAX_GATE_REDISPATCH_RETRIES = 3
27
+
19
28
  class << self
20
29
  # Called by Brainiac plugin system during server startup.
21
30
  #
@@ -107,22 +116,34 @@ module Brainiac
107
116
  end
108
117
 
109
118
  when "in_flight"
110
- # Check if impl agent is assigned when changes were requested
111
- if task["changes_requested_by"]&.any?
112
- impl_agent = epic["agent"]
113
- card_json, = Open3.capture2("fizzy", "card", "show", card_number.to_s, "--json")
114
- card_data = JSON.parse(card_json) rescue {}
115
- assignees = card_data.dig("data", "assignees") || []
116
- assignee_names = assignees.map { |a| a["name"]&.downcase }
117
-
118
- unless assignee_names.include?(impl_agent.downcase)
119
- LOG.info "[Basecamp:HealthCheck] Task ##{card_number} in_flight with changes_requested but #{impl_agent} not assigned — healing" if defined?(LOG)
120
- fizzy_user_id = Orchestrator.send(:resolve_fizzy_user_id, impl_agent)
121
- if fizzy_user_id
122
- Open3.capture3("fizzy", "card", "assign", card_number.to_s, "--user", fizzy_user_id)
123
- healed_any = true
124
- end
119
+ # Check if impl agent session is likely dead after a restart.
120
+ # Instead of just checking Fizzy assignment (stale artifact), use
121
+ # dispatched_at as a liveness signal — if dispatched too long ago
122
+ # without completion, assume the session died and re-dispatch.
123
+ dispatched_at = task["dispatched_at"]
124
+ if dispatched_at
125
+ elapsed = Time.now - Time.parse(dispatched_at)
126
+ # If task has been in_flight for longer than the stale timeout
127
+ # AND no agent is actively running (we can't check PID, so use elapsed time)
128
+ if elapsed > STALE_DISPATCH_TIMEOUT
129
+ impl_agent = epic["agent"]
130
+ LOG.info "[Basecamp:HealthCheck] Task ##{card_number} in_flight for #{elapsed.round}s — re-dispatching #{impl_agent}" if defined?(LOG)
131
+
132
+ # Spawn the agent directly — safe_assign_card won't work if already assigned
133
+ task["dispatched_at"] = Time.now.iso8601
134
+ Hooks.send(:dispatch_impl_directly, epic, task)
135
+ healed_any = true
125
136
  end
137
+ elsif task["changes_requested_by"]&.any?
138
+ # No dispatched_at recorded but changes were requested — legacy state.
139
+ # Always re-dispatch in this case.
140
+ impl_agent = epic["agent"]
141
+ LOG.info "[Basecamp:HealthCheck] Task ##{card_number} in_flight with changes_requested but no dispatched_at — re-dispatching #{impl_agent}" if defined?(LOG)
142
+
143
+ # Spawn the agent directly — safe_assign_card won't work if already assigned
144
+ task["dispatched_at"] = Time.now.iso8601
145
+ Hooks.send(:dispatch_impl_directly, epic, task)
146
+ healed_any = true
126
147
  end
127
148
 
128
149
  when "in_review"
@@ -168,7 +189,7 @@ module Brainiac
168
189
  elsif (task["gate_approvals"] || []).empty? && !task["changes_requested_by"]&.any?
169
190
  # No gate responses at all — gates may never have been dispatched or agents crashed
170
191
  gates_dispatched_at = task["gates_dispatched_at"]
171
- should_redispatch = gates_dispatched_at.nil? || (Time.now - Time.parse(gates_dispatched_at)) > 300
192
+ should_redispatch = gates_dispatched_at.nil? || (Time.now - Time.parse(gates_dispatched_at)) > STALE_DISPATCH_TIMEOUT
172
193
 
173
194
  if should_redispatch && github_repo
174
195
  LOG.info "[Basecamp:HealthCheck] No gate responses for ##{card_number} — re-dispatching review gates" if defined?(LOG)
@@ -182,6 +203,25 @@ module Brainiac
182
203
  Hooks.send(:save_epic_state, epic)
183
204
  healed_any = true
184
205
  end
206
+ else
207
+ # Partial responses — some gates responded but others haven't
208
+ gates_dispatched_at = task["gates_dispatched_at"]
209
+ if gates_dispatched_at && (Time.now - Time.parse(gates_dispatched_at)) > STALE_DISPATCH_TIMEOUT
210
+ responded_count = (task["gate_approvals"]&.size || 0) + (task["changes_requested_by"]&.size || 0)
211
+ total_gates = ReviewGate.gates.size
212
+ if responded_count.positive? && responded_count < total_gates && github_repo
213
+ LOG.info "[Basecamp:HealthCheck] Partial gate response for ##{card_number} (#{responded_count}/#{total_gates}) — re-dispatching missing gates" if defined?(LOG)
214
+ ReviewGate.dispatch_missing_gates(
215
+ epic: epic,
216
+ task: task,
217
+ pr_number: pr_number,
218
+ repo_name: github_repo,
219
+ repo_path: repo_path
220
+ )
221
+ Hooks.send(:save_epic_state, epic)
222
+ healed_any = true
223
+ end
224
+ end
185
225
  end
186
226
  end
187
227
  end
@@ -278,61 +318,68 @@ module Brainiac
278
318
  Hooks.send(:save_epic_state, epic)
279
319
  Hooks.send(:dispatch_final_decision, epic, task, {})
280
320
  elsif task["changes_requested_by"]&.any?
281
- # Gates requested changes — check if impl agent is assigned
321
+ # Gates requested changes — after a restart, no agent session is running even if assigned.
322
+ # Always re-dispatch the impl agent to address the feedback.
282
323
  impl_agent = epic["agent"]
283
- LOG.info "[Basecamp] Resume: ##{card_number} has changes requested, checking assignment" if defined?(LOG)
324
+ LOG.info "[Basecamp] Resume: ##{card_number} has changes requested re-dispatching #{impl_agent}" if defined?(LOG)
284
325
 
285
- # Check current assignees via Fizzy CLI
286
- card_json, = Open3.capture2("fizzy", "card", "show", card_number.to_s, "--json")
287
- card_data = JSON.parse(card_json) rescue {}
288
- assignees = card_data.dig("data", "assignees") || []
289
- assignee_names = assignees.map { |a| a["name"]&.downcase }
326
+ # Transition to in_flight so the agent gets the right context
327
+ task["status"] = "in_flight"
328
+ task["dispatched_at"] = Time.now.iso8601
329
+ Hooks.send(:save_epic_state, epic)
290
330
 
291
- if assignee_names.include?(impl_agent.downcase)
292
- LOG.info "[Basecamp] Resume: ##{card_number} already assigned to #{impl_agent} — waiting for fixes" if defined?(LOG)
293
- else
294
- # Re-assign to trigger dispatch
295
- LOG.info "[Basecamp] Resume: ##{card_number} not assigned to #{impl_agent} — re-assigning" if defined?(LOG)
296
- fizzy_user_id = Orchestrator.send(:resolve_fizzy_user_id, impl_agent)
297
- if fizzy_user_id
298
- Open3.capture3("fizzy", "card", "assign", card_number.to_s, "--user", fizzy_user_id)
299
- end
300
- end
331
+ # Spawn the agent directly — safe_assign_card won't work if already assigned
332
+ Hooks.send(:dispatch_impl_directly, epic, task)
301
333
  else
302
334
  # No changes requested yet — check if gates need (re-)dispatching
303
335
  approvals = task["gate_approvals"]&.size || 0
304
336
  gates_dispatched_at = task["gates_dispatched_at"]
305
337
 
338
+ # Calculate how many gates have responded (approved or requested changes)
339
+ responded_count = approvals + (task["changes_requested_by"]&.size || 0)
340
+ total_gates = ReviewGate.gates.size
341
+
306
342
  # Re-dispatch gates if:
307
343
  # 1. Gates were never dispatched (missed webhook), OR
308
- # 2. Gates were dispatched but no responses received after a reasonable window
344
+ # 2. Gates were dispatched but no responses received after 5 minutes, OR
345
+ # 3. Some gates responded but others didn't after 5 minutes (partial response)
309
346
  should_redispatch = if gates_dispatched_at.nil?
310
347
  true
311
- elsif approvals.zero?
312
- # If dispatched more than 5 minutes ago with no responses, re-dispatch
313
- elapsed = Time.now - Time.parse(gates_dispatched_at)
314
- elapsed > 300
315
348
  else
316
- false
349
+ elapsed = Time.now - Time.parse(gates_dispatched_at)
350
+ elapsed > STALE_DISPATCH_TIMEOUT && responded_count < total_gates
317
351
  end
318
352
 
319
353
  if should_redispatch && ReviewGate.enabled?
320
354
  github_repo = projects.dig(project_key, "github_repo")
321
355
  if github_repo
322
- LOG.info "[Basecamp] Resume: ##{card_number} has #{approvals} approvals — re-dispatching review gates" if defined?(LOG)
323
- ReviewGate.dispatch_gates(
324
- epic: epic,
325
- task: task,
326
- pr_number: pr_number,
327
- repo_name: github_repo,
328
- repo_path: repo_path
329
- )
356
+ if responded_count.zero?
357
+ # No responses at all — dispatch all gates
358
+ LOG.info "[Basecamp] Resume: ##{card_number} has 0/#{total_gates} gate responses — re-dispatching all review gates" if defined?(LOG)
359
+ ReviewGate.dispatch_gates(
360
+ epic: epic,
361
+ task: task,
362
+ pr_number: pr_number,
363
+ repo_name: github_repo,
364
+ repo_path: repo_path
365
+ )
366
+ else
367
+ # Partial responses — dispatch only the missing gates
368
+ LOG.info "[Basecamp] Resume: ##{card_number} has #{responded_count}/#{total_gates} gate responses — re-dispatching missing gates" if defined?(LOG)
369
+ ReviewGate.dispatch_missing_gates(
370
+ epic: epic,
371
+ task: task,
372
+ pr_number: pr_number,
373
+ repo_name: github_repo,
374
+ repo_path: repo_path
375
+ )
376
+ end
330
377
  Hooks.send(:save_epic_state, epic)
331
378
  else
332
379
  LOG.warn "[Basecamp] Resume: ##{card_number} — cannot re-dispatch gates, no github_repo for #{project_key}" if defined?(LOG)
333
380
  end
334
381
  else
335
- LOG.info "[Basecamp] Resume: ##{card_number} has #{approvals} approvals, waiting for more reviews" if defined?(LOG)
382
+ LOG.info "[Basecamp] Resume: ##{card_number} has #{responded_count}/#{total_gates} gate responses, waiting for more reviews" if defined?(LOG)
336
383
  end
337
384
  end
338
385
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainiac-basecamp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis