brainiac-basecamp 0.0.16 → 0.0.17
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 +4 -4
- data/lib/brainiac/plugins/basecamp/cli.rb +297 -3
- data/lib/brainiac/plugins/basecamp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 632f37b6a52ba851b1f16430cc9f2af1a34b0343adcdc486e950d78470d01eb8
|
|
4
|
+
data.tar.gz: 7af23b5ee546eacb7b5d51b0dab4879dc642d7f0e8548ba4d94451e2aadc6518
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d69200fb71f7d04244f0bfcd3d1e8b9ecc97920cb9f1488a96a3fab6da51117af64dd4a47a0ba3a4f8857ce2c36fd5ec23efcf48e35efa5b34f29f86e3af72d
|
|
7
|
+
data.tar.gz: c2253c409cdc64c02ec853dfca0623cc483ddd527b6f8135d9af9eb7ee256745a86a5992f998f6fcc73d10175593150f2288beb6437a4fc25e3c2ce7c278d8e1
|
|
@@ -30,6 +30,8 @@ module Brainiac
|
|
|
30
30
|
cmd_projects(args)
|
|
31
31
|
when "set"
|
|
32
32
|
cmd_set(args)
|
|
33
|
+
when "reset"
|
|
34
|
+
cmd_reset(args)
|
|
33
35
|
else
|
|
34
36
|
print_help
|
|
35
37
|
end
|
|
@@ -172,8 +174,23 @@ module Brainiac
|
|
|
172
174
|
|
|
173
175
|
status_icon = epic["status"] == "active" ? "🚀" : "✅"
|
|
174
176
|
puts "#{status_icon} #{epic['title']}"
|
|
175
|
-
puts "
|
|
177
|
+
puts " Todolist: #{epic['basecamp_todolist_id']} | Agent: #{epic['agent']}"
|
|
178
|
+
puts " Tasks: #{complete}/#{total} complete, #{in_flight} in-flight"
|
|
176
179
|
puts " Started: #{epic['started_at']}"
|
|
180
|
+
|
|
181
|
+
# Show per-task detail if verbose or few tasks
|
|
182
|
+
if args.include?("--verbose") || args.include?("-v")
|
|
183
|
+
tasks.each do |t|
|
|
184
|
+
icon = case t["status"]
|
|
185
|
+
when "complete" then "✅"
|
|
186
|
+
when "in_flight" then "🚀"
|
|
187
|
+
when "in_review" then "👀"
|
|
188
|
+
when "final_decision" then "⚡"
|
|
189
|
+
else "⬜"
|
|
190
|
+
end
|
|
191
|
+
puts " #{icon} ##{t['fizzy_card']} — #{t['title']} [#{t['status']}]"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
177
194
|
puts ""
|
|
178
195
|
end
|
|
179
196
|
end
|
|
@@ -317,9 +334,12 @@ module Brainiac
|
|
|
317
334
|
projects map <key> <basecamp-id> Map a Brainiac project to Basecamp
|
|
318
335
|
projects list List project mappings
|
|
319
336
|
projects unmap <key> Remove a project mapping
|
|
320
|
-
set fizzy-account-id <id>
|
|
337
|
+
set fizzy-account-id <id> Set Fizzy account ID (for card URLs)
|
|
321
338
|
set review-gate <mode> Set review gate (on_complete or on_pr_merge)
|
|
322
339
|
set epic-prefix <prefix> Set epic todolist prefix (default: "Epic:")
|
|
340
|
+
reset task <card-number> [--to <status>] Reset a task to a given status (default: pending)
|
|
341
|
+
reset epic <todolist-id> [--to <status>] Reset entire epic or all tasks within it
|
|
342
|
+
reset gates <card-number> Clear gate approvals and re-dispatch gates
|
|
323
343
|
|
|
324
344
|
Config file: ~/.brainiac/basecamp.json
|
|
325
345
|
Epics state: ~/.brainiac/basecamp_epics.json
|
|
@@ -327,6 +347,13 @@ module Brainiac
|
|
|
327
347
|
Review gate modes:
|
|
328
348
|
on_complete — Advance to next task as soon as agent finishes (default)
|
|
329
349
|
on_pr_merge — Wait for PR to be merged before advancing (review gate)
|
|
350
|
+
|
|
351
|
+
Task statuses (for reset --to):
|
|
352
|
+
pending — Not yet started, waiting for dependencies
|
|
353
|
+
in_flight — Agent is actively working on it
|
|
354
|
+
in_review — PR open, gates reviewing
|
|
355
|
+
final_decision — Gates passed, awaiting implementation agent merge decision
|
|
356
|
+
complete — Done
|
|
330
357
|
HELP
|
|
331
358
|
end
|
|
332
359
|
|
|
@@ -385,6 +412,273 @@ module Brainiac
|
|
|
385
412
|
end
|
|
386
413
|
end
|
|
387
414
|
|
|
415
|
+
def cmd_reset(args)
|
|
416
|
+
subcommand = args.shift
|
|
417
|
+
|
|
418
|
+
case subcommand
|
|
419
|
+
when "task"
|
|
420
|
+
cmd_reset_task(args)
|
|
421
|
+
when "epic"
|
|
422
|
+
cmd_reset_epic(args)
|
|
423
|
+
when "gates"
|
|
424
|
+
cmd_reset_gates(args)
|
|
425
|
+
else
|
|
426
|
+
puts <<~HELP
|
|
427
|
+
Usage: brainiac basecamp reset <subcommand>
|
|
428
|
+
|
|
429
|
+
Subcommands:
|
|
430
|
+
task <card-number> [--to <status>] Reset a task's status (default: pending)
|
|
431
|
+
epic <todolist-id> [--to <status>] Reset entire epic or all its tasks
|
|
432
|
+
gates <card-number> Clear gate approvals and re-dispatch
|
|
433
|
+
|
|
434
|
+
Task statuses:
|
|
435
|
+
pending, in_flight, in_review, final_decision, complete
|
|
436
|
+
|
|
437
|
+
Examples:
|
|
438
|
+
brainiac basecamp reset task 1234 # Reset card #1234 to pending
|
|
439
|
+
brainiac basecamp reset task 1234 --to in_flight # Reset to in_flight
|
|
440
|
+
brainiac basecamp reset gates 1234 # Clear gates, re-dispatch reviewers
|
|
441
|
+
brainiac basecamp reset epic 10233212224 # Reset all tasks to pending
|
|
442
|
+
brainiac basecamp reset epic 10233212224 --to pending # Same as above
|
|
443
|
+
HELP
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def cmd_reset_task(args)
|
|
448
|
+
card_number = args.shift&.gsub(/^#/, "")
|
|
449
|
+
unless card_number
|
|
450
|
+
puts "Usage: brainiac basecamp reset task <card-number> [--to <status>]"
|
|
451
|
+
return
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
target_status = parse_to_flag(args) || "pending"
|
|
455
|
+
unless valid_task_status?(target_status)
|
|
456
|
+
puts "Error: Invalid status '#{target_status}'"
|
|
457
|
+
puts "Valid statuses: pending, in_flight, in_review, final_decision, complete"
|
|
458
|
+
return
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
epics = load_epics
|
|
462
|
+
epic = epics.find do |e|
|
|
463
|
+
e["status"] == "active" &&
|
|
464
|
+
e["tasks"]&.any? { |t| t["fizzy_card"] == card_number.to_i }
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
unless epic
|
|
468
|
+
puts "Error: Card ##{card_number} not found in any active epic"
|
|
469
|
+
return
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
task = epic["tasks"].find { |t| t["fizzy_card"] == card_number.to_i }
|
|
473
|
+
old_status = task["status"]
|
|
474
|
+
|
|
475
|
+
# Reset the task
|
|
476
|
+
reset_task_to(task, target_status)
|
|
477
|
+
|
|
478
|
+
save_epics(epics)
|
|
479
|
+
puts "✓ Reset card ##{card_number} from '#{old_status}' → '#{target_status}'"
|
|
480
|
+
puts " Epic: #{epic['title']}"
|
|
481
|
+
|
|
482
|
+
return unless target_status == "pending"
|
|
483
|
+
|
|
484
|
+
puts " The task will be picked up on next dispatch cycle (restart brainiac or wait for next event)"
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def cmd_reset_gates(args)
|
|
488
|
+
card_number = args.shift&.gsub(/^#/, "")
|
|
489
|
+
unless card_number
|
|
490
|
+
puts "Usage: brainiac basecamp reset gates <card-number>"
|
|
491
|
+
return
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
epics = load_epics
|
|
495
|
+
epic = epics.find do |e|
|
|
496
|
+
e["status"] == "active" &&
|
|
497
|
+
e["tasks"]&.any? { |t| t["fizzy_card"] == card_number.to_i }
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
unless epic
|
|
501
|
+
puts "Error: Card ##{card_number} not found in any active epic"
|
|
502
|
+
return
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
task = epic["tasks"].find { |t| t["fizzy_card"] == card_number.to_i }
|
|
506
|
+
|
|
507
|
+
# Clear all gate-related state
|
|
508
|
+
old_approvals = task["gate_approvals"]&.size || 0
|
|
509
|
+
old_changes = task["changes_requested_by"]&.size || 0
|
|
510
|
+
|
|
511
|
+
task["gate_approvals"] = []
|
|
512
|
+
task["changes_requested_by"] = []
|
|
513
|
+
task["gates_dispatched_at"] = nil
|
|
514
|
+
task["awaiting_final_decision"] = nil
|
|
515
|
+
task["changes_debounce_started"] = nil
|
|
516
|
+
task["gate_redispatch_counts"] = {}
|
|
517
|
+
task["last_redispatch_at"] = nil
|
|
518
|
+
task["status"] = "in_review"
|
|
519
|
+
epic["updated_at"] = Time.now.iso8601
|
|
520
|
+
|
|
521
|
+
save_epics(epics)
|
|
522
|
+
puts "✓ Cleared gates for card ##{card_number}"
|
|
523
|
+
puts " Removed #{old_approvals} approval(s), #{old_changes} changes_requested"
|
|
524
|
+
puts " Status set to 'in_review' — gates will be re-dispatched on next health check"
|
|
525
|
+
puts ""
|
|
526
|
+
puts " To force immediate re-dispatch, restart brainiac:"
|
|
527
|
+
puts " brainiac restart"
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def cmd_reset_epic(args)
|
|
531
|
+
todolist_id = args.shift
|
|
532
|
+
unless todolist_id
|
|
533
|
+
puts "Usage: brainiac basecamp reset epic <todolist-id> [--to <status>]"
|
|
534
|
+
puts ""
|
|
535
|
+
puts "Options:"
|
|
536
|
+
puts " --to <status> Reset all tasks to this status (default: pending)"
|
|
537
|
+
puts " --reactivate Reset a completed epic back to active"
|
|
538
|
+
puts ""
|
|
539
|
+
puts "Find your todolist ID with: brainiac basecamp epics --all"
|
|
540
|
+
return
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
target_status = parse_to_flag(args) || "pending"
|
|
544
|
+
reactivate = args.include?("--reactivate")
|
|
545
|
+
|
|
546
|
+
unless valid_task_status?(target_status)
|
|
547
|
+
puts "Error: Invalid status '#{target_status}'"
|
|
548
|
+
puts "Valid statuses: pending, in_flight, in_review, final_decision, complete"
|
|
549
|
+
return
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
epics = load_epics
|
|
553
|
+
epic = epics.find { |e| e["basecamp_todolist_id"] == todolist_id.to_s }
|
|
554
|
+
|
|
555
|
+
unless epic
|
|
556
|
+
puts "Error: No epic found with todolist ID #{todolist_id}"
|
|
557
|
+
puts ""
|
|
558
|
+
puts "Active epics:"
|
|
559
|
+
epics.select { |e| e["status"] == "active" }.each do |e|
|
|
560
|
+
puts " #{e['basecamp_todolist_id']} — #{e['title']}"
|
|
561
|
+
end
|
|
562
|
+
return
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
if epic["status"] != "active" && !reactivate
|
|
566
|
+
puts "Error: Epic '#{epic['title']}' is #{epic['status']} (not active)"
|
|
567
|
+
puts " Use --reactivate to set it back to active"
|
|
568
|
+
return
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# Reactivate if needed
|
|
572
|
+
if epic["status"] != "active" && reactivate
|
|
573
|
+
epic["status"] = "active"
|
|
574
|
+
epic["completed_at"] = nil
|
|
575
|
+
puts "✓ Reactivated epic '#{epic['title']}'"
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Reset all non-complete tasks (unless resetting to pending, in which case reset all)
|
|
579
|
+
tasks_to_reset = if target_status == "pending"
|
|
580
|
+
epic["tasks"]
|
|
581
|
+
else
|
|
582
|
+
epic["tasks"].reject { |t| t["status"] == "complete" }
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
tasks_to_reset.each { |t| reset_task_to(t, target_status) }
|
|
586
|
+
epic["updated_at"] = Time.now.iso8601
|
|
587
|
+
|
|
588
|
+
save_epics(epics)
|
|
589
|
+
puts "✓ Reset #{tasks_to_reset.size} task(s) → '#{target_status}' in epic '#{epic['title']}'"
|
|
590
|
+
puts ""
|
|
591
|
+
epic["tasks"].each do |t|
|
|
592
|
+
icon = case t["status"]
|
|
593
|
+
when "complete" then "✅"
|
|
594
|
+
when "in_flight" then "🚀"
|
|
595
|
+
when "in_review" then "👀"
|
|
596
|
+
when "final_decision" then "⚡"
|
|
597
|
+
else "⬜"
|
|
598
|
+
end
|
|
599
|
+
puts " #{icon} ##{t['fizzy_card']} — #{t['title']} [#{t['status']}]"
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# --- Reset helpers ---
|
|
604
|
+
|
|
605
|
+
def valid_task_status?(status)
|
|
606
|
+
%w[pending in_flight in_review final_decision complete].include?(status)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def parse_to_flag(args)
|
|
610
|
+
idx = args.index("--to")
|
|
611
|
+
return nil unless idx
|
|
612
|
+
|
|
613
|
+
args.delete_at(idx) # remove --to
|
|
614
|
+
args.delete_at(idx) # remove the value (now at same index)
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def reset_task_to(task, status)
|
|
618
|
+
task["status"] = status
|
|
619
|
+
|
|
620
|
+
case status
|
|
621
|
+
when "pending"
|
|
622
|
+
# Clear all progress state
|
|
623
|
+
task["dispatched_at"] = nil
|
|
624
|
+
task["completed_at"] = nil
|
|
625
|
+
task["pr_number"] = nil
|
|
626
|
+
task["pr_repo"] = nil
|
|
627
|
+
task["gate_approvals"] = []
|
|
628
|
+
task["changes_requested_by"] = []
|
|
629
|
+
task["gates_dispatched_at"] = nil
|
|
630
|
+
task["awaiting_final_decision"] = nil
|
|
631
|
+
task["changes_debounce_started"] = nil
|
|
632
|
+
task["gate_redispatch_counts"] = {}
|
|
633
|
+
task["last_redispatch_at"] = nil
|
|
634
|
+
task["last_reviewed_sha"] = nil
|
|
635
|
+
when "in_flight"
|
|
636
|
+
# Keep PR info but clear gate state
|
|
637
|
+
task["completed_at"] = nil
|
|
638
|
+
task["gate_approvals"] = []
|
|
639
|
+
task["changes_requested_by"] = []
|
|
640
|
+
task["gates_dispatched_at"] = nil
|
|
641
|
+
task["awaiting_final_decision"] = nil
|
|
642
|
+
task["changes_debounce_started"] = nil
|
|
643
|
+
task["gate_redispatch_counts"] = {}
|
|
644
|
+
task["last_redispatch_at"] = nil
|
|
645
|
+
task["dispatched_at"] ||= Time.now.iso8601
|
|
646
|
+
when "in_review"
|
|
647
|
+
# Clear gate approvals but keep PR info
|
|
648
|
+
task["completed_at"] = nil
|
|
649
|
+
task["gate_approvals"] = []
|
|
650
|
+
task["changes_requested_by"] = []
|
|
651
|
+
task["gates_dispatched_at"] = nil
|
|
652
|
+
task["awaiting_final_decision"] = nil
|
|
653
|
+
task["changes_debounce_started"] = nil
|
|
654
|
+
task["gate_redispatch_counts"] = {}
|
|
655
|
+
task["last_redispatch_at"] = nil
|
|
656
|
+
when "final_decision"
|
|
657
|
+
task["completed_at"] = nil
|
|
658
|
+
task["awaiting_final_decision"] = true
|
|
659
|
+
when "complete"
|
|
660
|
+
task["completed_at"] ||= Time.now.iso8601
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
def load_epics
|
|
665
|
+
epics_file = File.join(BRAINIAC_DIR, "basecamp_epics.json")
|
|
666
|
+
return [] unless File.exist?(epics_file)
|
|
667
|
+
|
|
668
|
+
data = JSON.parse(File.read(epics_file))
|
|
669
|
+
data["epics"] || []
|
|
670
|
+
rescue JSON::ParserError
|
|
671
|
+
[]
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
def save_epics(epics)
|
|
675
|
+
epics_file = File.join(BRAINIAC_DIR, "basecamp_epics.json")
|
|
676
|
+
File.write(epics_file, JSON.pretty_generate({
|
|
677
|
+
"epics" => epics,
|
|
678
|
+
"updated_at" => Time.now.iso8601
|
|
679
|
+
}))
|
|
680
|
+
end
|
|
681
|
+
|
|
388
682
|
def load_config
|
|
389
683
|
if File.exist?(CONFIG_FILE)
|
|
390
684
|
JSON.parse(File.read(CONFIG_FILE))
|
|
@@ -404,7 +698,7 @@ module Brainiac
|
|
|
404
698
|
end
|
|
405
699
|
|
|
406
700
|
def self.completions
|
|
407
|
-
%w[setup config status epics link bot projects set]
|
|
701
|
+
%w[setup config status epics link bot projects set reset]
|
|
408
702
|
end
|
|
409
703
|
end
|
|
410
704
|
end
|