Module: Brainiac::Plugins::Basecamp
- Defined in:
- lib/brainiac/plugins/basecamp.rb,
lib/brainiac/plugins/basecamp/cli.rb,
lib/brainiac/plugins/basecamp/epic.rb,
lib/brainiac/plugins/basecamp/hooks.rb,
lib/brainiac/plugins/basecamp/client.rb,
lib/brainiac/plugins/basecamp/config.rb,
lib/brainiac/plugins/basecamp/prompts.rb,
lib/brainiac/plugins/basecamp/version.rb,
lib/brainiac/plugins/basecamp/webhook.rb,
lib/brainiac/plugins/basecamp/metadata.rb,
lib/brainiac/plugins/basecamp/task_state.rb,
lib/brainiac/plugins/basecamp/epic_branch.rb,
lib/brainiac/plugins/basecamp/epic_memory.rb,
lib/brainiac/plugins/basecamp/review_gate.rb,
lib/brainiac/plugins/basecamp/orchestrator.rb,
lib/brainiac/plugins/basecamp/session_registry.rb,
lib/brainiac/plugins/basecamp/comment_responder.rb
Defined Under Namespace
Modules: Cli, Client, CommentResponder, Config, Epic, EpicBranch, EpicMemory, Hooks, Orchestrator, Prompts, ReviewGate, SessionRegistry, TaskState, Webhook Classes: ClientError
Constant Summary collapse
- STALE_DISPATCH_TIMEOUT =
Maximum seconds to wait for a dispatched agent/gate to respond before considering it stale and re-dispatching. Used across resume and health-check.
300- MAX_GATE_REDISPATCH_RETRIES =
Maximum number of times a gate will be re-dispatched for the same review cycle before giving up (prevents infinite re-dispatch loops).
3- VERSION =
"0.0.23"
Class Method Summary collapse
- .cli(args) ⇒ Object
- .completions ⇒ Object
- .configured? ⇒ Boolean
- .help_text ⇒ Object
-
.reconcile_active_epics(epics = Orchestrator.active_epics, triggered_by: "recovery") ⇒ Object
The sole restart/periodic reconciliation entry point.
- .reconcile_epic(epic, triggered_by: "recovery") ⇒ Object
-
.register(app) ⇒ Object
Called by Brainiac plugin system during server startup.
-
.start_epic_health_monitor ⇒ Object
Background thread that periodically runs the same reconciliation used after startup.
Class Method Details
.cli(args) ⇒ Object
1434 1435 1436 |
# File 'lib/brainiac/plugins/basecamp/cli.rb', line 1434 def self.cli(args) Cli.run(args) end |
.completions ⇒ Object
1438 1439 1440 |
# File 'lib/brainiac/plugins/basecamp/cli.rb', line 1438 def self.completions %w[setup config status epics deploy link bot projects set reset scrap webhook] end |
.configured? ⇒ Boolean
10 11 12 13 |
# File 'lib/brainiac/plugins/basecamp/metadata.rb', line 10 def self.configured? config_file = File.join(ENV.fetch("BRAINIAC_DIR", File.join(Dir.home, ".brainiac")), "basecamp.json") File.exist?(config_file) end |
.help_text ⇒ Object
15 16 17 |
# File 'lib/brainiac/plugins/basecamp/metadata.rb', line 15 def self.help_text " brainiac basecamp <command> Manage Basecamp epic orchestration" end |
.reconcile_active_epics(epics = Orchestrator.active_epics, triggered_by: "recovery") ⇒ Object
The sole restart/periodic reconciliation entry point. It deliberately uses the exact transitions and gate-state operations used by hooks.
95 96 97 98 99 |
# File 'lib/brainiac/plugins/basecamp.rb', line 95 def reconcile_active_epics(epics = Orchestrator.active_epics, triggered_by: "recovery") SessionRegistry.reap_dead! SessionRegistry.sweep! epics.each { |epic| reconcile_epic(epic, triggered_by: triggered_by) } end |
.reconcile_epic(epic, triggered_by: "recovery") ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/brainiac/plugins/basecamp.rb', line 101 def reconcile_epic(epic, triggered_by: "recovery") tasks = epic["tasks"] || [] tasks.each { |task| TaskState.migrate!(task, triggered_by: triggered_by) } if tasks.any? && tasks.all? { |task| TaskState.in?(task, :complete) } tasks.each { |task| Orchestrator.send(:mark_todo_complete, epic, task["fizzy_card"]) } Orchestrator.send(:complete_epic, epic) Orchestrator.send(:save_epic, epic) return true end # Reconcile every task in this pass. `Enumerable#any?` would stop at # the first repaired task and defer later repairs to the next sweep. changed = tasks.map do |task| reconcile_task(epic, task, triggered_by: triggered_by) end.any? # Always attempt to dispatch unblocked tasks during recovery. Even if no # individual task changed state, there may be pending tasks whose # dependencies are satisfied that were never dispatched (e.g., after a # cancellation was reversed or an epic was healed manually). has_pending = tasks.any? { |t| TaskState.in?(t, :pending) } if changed || has_pending Orchestrator.send(:dispatch_unblocked_tasks, epic) Orchestrator.send(:save_epic, epic) end changed || has_pending end |
.register(app) ⇒ Object
Called by Brainiac plugin system during server startup.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/brainiac/plugins/basecamp.rb', line 36 def register(app) Config.load! # Clear all sessions on startup — PIDs from prior runs are untrustworthy SessionRegistry.clear_all! SessionRegistry.install_global_registration_hook! # Register lifecycle hooks Hooks.register_all! # Register channel prompt (for when agents need Basecamp awareness) Brainiac.register_channel_prompt(:basecamp, Prompts::CHANNEL) # Set up routes setup_routes(app) # Log active epics on startup and resume them active = Orchestrator.active_epics if active.any? LOG.info "[Basecamp] #{active.size} active epic(s) in progress" active.each { |e| LOG.info "[Basecamp] - #{e['title']} (#{e['tasks']&.count { |t| t['status'] == 'complete' }}/#{e['tasks']&.size} complete)" } # Reconcile active epics in background after server is ready. The # session registry was cleared above, so this treats all inherited # agent sessions as dead and safely starts fresh work where needed. Thread.new do sleep 10 # Wait for server to fully start reconcile_active_epics(active, triggered_by: "startup_recovery") rescue StandardError => e LOG.error "[Basecamp:Recovery] Startup reconciliation failed: #{e.}" if defined?(LOG) end end # Start the periodic recovery loop for active epics. start_epic_health_monitor LOG.info "[Basecamp] Plugin registered (webhook: /basecamp, review_gate: #{Config.review_gate})" end |
.start_epic_health_monitor ⇒ Object
Background thread that periodically runs the same reconciliation used after startup. Recovery owns liveness reaping and state repair; normal hooks own the immediate event path.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/brainiac/plugins/basecamp.rb', line 78 def start_epic_health_monitor @health_monitor_thread = Thread.new do loop do sleep 90 # Check every 90 seconds begin reconcile_active_epics(Orchestrator.active_epics, triggered_by: "periodic_recovery") rescue StandardError => e LOG.error "[Basecamp:Recovery] Periodic reconciliation failed: #{e.}" if defined?(LOG) end end end @health_monitor_thread.abort_on_exception = false end |