brainiac-basecamp 0.0.24 → 0.0.26
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 527e9e3b326e216a5080ae89148317e15530ac84d07ec01e30a90a3676528f9c
|
|
4
|
+
data.tar.gz: 436a083dda6818c8328df04179f3789d9febc0f4f6bd3016f744d7cd09921bc8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5cfd4aba7b96d8fdc49c368dab4dc76c8f21f2a29052ff6f8271dcee4cc824e95f9036aeb0325a540c9c082194b9d3854705092629ea9020fb16797ffe3d413f
|
|
7
|
+
data.tar.gz: 97ee914315cd7f978a3b736553db059e572a3bd629950cc7ed7220d644bdf6b6d89bda0d013e317a87b9b70c21069e7b4e3776563b82f20e062f59c67f260bfa
|
|
@@ -986,9 +986,15 @@ module Brainiac
|
|
|
986
986
|
# Reset the task
|
|
987
987
|
reset_task_to(task, target_status)
|
|
988
988
|
|
|
989
|
+
# If completing this task leaves every task in the epic complete,
|
|
990
|
+
# seal the epic in the same atomic write so the reconcile loop never
|
|
991
|
+
# re-fires the completion notification.
|
|
992
|
+
finalized = finalize_epic_if_all_complete!(epic)
|
|
993
|
+
|
|
989
994
|
save_epics(epics)
|
|
990
995
|
puts "✓ Reset card ##{card_number} from '#{old_status}' → '#{target_status}'"
|
|
991
996
|
puts " Epic: #{epic['title']}"
|
|
997
|
+
puts "✓ All tasks complete — epic finalized (status=complete, notification suppressed)" if finalized
|
|
992
998
|
|
|
993
999
|
return unless target_status == "pending"
|
|
994
1000
|
|
|
@@ -1096,8 +1102,11 @@ module Brainiac
|
|
|
1096
1102
|
tasks_to_reset.each { |t| reset_task_to(t, target_status) }
|
|
1097
1103
|
epic["updated_at"] = Time.now.iso8601
|
|
1098
1104
|
|
|
1105
|
+
finalized = finalize_epic_if_all_complete!(epic)
|
|
1106
|
+
|
|
1099
1107
|
save_epics(epics)
|
|
1100
1108
|
puts "✓ Reset #{tasks_to_reset.size} task(s) → '#{target_status}' in epic '#{epic['title']}'"
|
|
1109
|
+
puts "✓ All tasks complete — epic finalized (status=complete, notification suppressed)" if finalized
|
|
1101
1110
|
puts ""
|
|
1102
1111
|
epic["tasks"].each do |t|
|
|
1103
1112
|
icon = case t["status"]
|
|
@@ -1169,9 +1178,31 @@ module Brainiac
|
|
|
1169
1178
|
task["awaiting_final_decision"] = true
|
|
1170
1179
|
when "complete"
|
|
1171
1180
|
task["completed_at"] ||= Time.now.iso8601
|
|
1181
|
+
# A completed task is no longer awaiting a final decision. Leaving
|
|
1182
|
+
# this true lets reconcile_final_decision_task treat the task as
|
|
1183
|
+
# unsettled and re-dispatch/re-complete it on the next sweep.
|
|
1184
|
+
task["awaiting_final_decision"] = false
|
|
1172
1185
|
end
|
|
1173
1186
|
end
|
|
1174
1187
|
|
|
1188
|
+
# Seal an epic when a reset leaves every task complete. Without this, an
|
|
1189
|
+
# epic can sit status="active" with all tasks done — exactly the
|
|
1190
|
+
# thrash-prone state where the 90s reconcile loop re-detects "all
|
|
1191
|
+
# complete", re-runs complete_epic, and re-posts the "🎉 Epic completed"
|
|
1192
|
+
# notification on every sweep. Setting completion_notified suppresses a
|
|
1193
|
+
# notification for this manual, operator-driven completion. Returns true
|
|
1194
|
+
# if the epic was finalized.
|
|
1195
|
+
def finalize_epic_if_all_complete!(epic)
|
|
1196
|
+
return false unless epic["tasks"]&.any?
|
|
1197
|
+
return false unless epic["tasks"].all? { |t| t["status"] == "complete" }
|
|
1198
|
+
return false if epic["status"] == "complete"
|
|
1199
|
+
|
|
1200
|
+
epic["status"] = "complete"
|
|
1201
|
+
epic["completed_at"] ||= Time.now.iso8601
|
|
1202
|
+
epic["completion_notified"] = true
|
|
1203
|
+
true
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1175
1206
|
def load_epics
|
|
1176
1207
|
epics_file = File.join(BRAINIAC_DIR, "basecamp_epics.json")
|
|
1177
1208
|
return [] unless File.exist?(epics_file)
|
|
@@ -790,12 +790,12 @@ module Brainiac
|
|
|
790
790
|
return unless project_config && project_config["repo_path"]
|
|
791
791
|
|
|
792
792
|
agent_name = Orchestrator.send(:resolve_agent_for_project, project_key) || epic["agent"]
|
|
793
|
-
prompt
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
793
|
+
# Only build a "changes requested" prompt when there is a real PR to
|
|
794
|
+
# act on. A stale in_flight card recovered by the orchestrator has no
|
|
795
|
+
# PR number yet — handing an agent "...fixes on PR #" with a blank
|
|
796
|
+
# number is nonsense and the session dies asking for clarification.
|
|
797
|
+
# In that case dispatch a clean "start implementation" prompt instead.
|
|
798
|
+
prompt = build_impl_prompt(card_number: card_number, pr_number: task["pr_number"])
|
|
799
799
|
github_repo = project_config["github_repo"]
|
|
800
800
|
agent_env = github_repo ? ReviewGate.send(:resolve_agent_github_env, agent_name, github_repo) : {}
|
|
801
801
|
pid, log_file = method(:run_agent).call(
|
|
@@ -824,6 +824,36 @@ module Brainiac
|
|
|
824
824
|
LOG.error "[Basecamp:Hooks] Direct implementation dispatch failed: #{e.message}" if defined?(LOG)
|
|
825
825
|
end
|
|
826
826
|
|
|
827
|
+
# Build the implementation prompt for a direct dispatch.
|
|
828
|
+
#
|
|
829
|
+
# When +pr_number+ is present the card already has an open PR, so the
|
|
830
|
+
# agent is asked to address review feedback. When it is absent (a stale
|
|
831
|
+
# in_flight card the orchestrator recovered, or a first dispatch) the
|
|
832
|
+
# agent is asked to start the implementation from scratch — never with a
|
|
833
|
+
# blank "PR #" that a real PR would have filled in.
|
|
834
|
+
#
|
|
835
|
+
# @param card_number [Integer, String] Fizzy card number
|
|
836
|
+
# @param pr_number [Integer, String, nil] PR number, if one exists
|
|
837
|
+
# @return [String] the prompt text
|
|
838
|
+
def build_impl_prompt(card_number:, pr_number:)
|
|
839
|
+
if pr_number.to_s.strip.empty?
|
|
840
|
+
<<~PROMPT
|
|
841
|
+
## Implement — Fizzy Card ##{card_number}
|
|
842
|
+
|
|
843
|
+
Pick up Fizzy card ##{card_number} and implement it. Read the card for
|
|
844
|
+
requirements, do the work in a branch, commit and push, open a PR, then
|
|
845
|
+
update the Fizzy card. There is no existing PR for this card yet.
|
|
846
|
+
PROMPT
|
|
847
|
+
else
|
|
848
|
+
<<~PROMPT
|
|
849
|
+
## Changes Requested — Fizzy Card ##{card_number}
|
|
850
|
+
|
|
851
|
+
Review feedback needs attention on PR ##{pr_number}. Read the review,
|
|
852
|
+
make the requested fixes, commit and push them, then update the Fizzy card.
|
|
853
|
+
PROMPT
|
|
854
|
+
end
|
|
855
|
+
end
|
|
856
|
+
|
|
827
857
|
# Find a PR number by branch name pattern.
|
|
828
858
|
def find_pr_number(repo_path:, branch:)
|
|
829
859
|
# Try exact match first
|
|
@@ -593,6 +593,25 @@ module Brainiac
|
|
|
593
593
|
nil
|
|
594
594
|
end
|
|
595
595
|
|
|
596
|
+
# Whether a card's completion is backed by real evidence of shipped work.
|
|
597
|
+
#
|
|
598
|
+
# A card genuinely completes when it has an associated PR that merged (or,
|
|
599
|
+
# at minimum, a tracked PR number). Without any PR evidence the completion
|
|
600
|
+
# is almost certainly spurious — a stale ledger flag left behind by a
|
|
601
|
+
# basecamp re-sync that dropped merged cards — and we should not treat it
|
|
602
|
+
# as a real completion that warrants an epic-review checkpoint.
|
|
603
|
+
#
|
|
604
|
+
# @param epic [Hash] epic state
|
|
605
|
+
# @param card_number [Integer, String] the card claimed complete
|
|
606
|
+
# @return [Boolean]
|
|
607
|
+
def card_completion_verified?(epic, card_number)
|
|
608
|
+
task = epic["tasks"].find { |t| t["fizzy_card"] == card_number.to_i }
|
|
609
|
+
return false unless task
|
|
610
|
+
|
|
611
|
+
# A tracked PR number is our recorded evidence that work shipped.
|
|
612
|
+
!task["pr_number"].to_s.strip.empty?
|
|
613
|
+
end
|
|
614
|
+
|
|
596
615
|
# Dispatch an agent to review the epic state after a task completes.
|
|
597
616
|
# This ensures the remaining plan still makes sense given implementation decisions.
|
|
598
617
|
# The callback is called after the review completes.
|
|
@@ -606,6 +625,22 @@ module Brainiac
|
|
|
606
625
|
return
|
|
607
626
|
end
|
|
608
627
|
|
|
628
|
+
# Only post a "just completed" checkpoint when the card actually shipped
|
|
629
|
+
# work. A ledger flagged `complete` with no PR (and no merge) means the
|
|
630
|
+
# completion was spurious — usually the basecamp re-sync dropping merged
|
|
631
|
+
# cards and leaving a stale flag. Posting "Card #N just completed" in that
|
|
632
|
+
# case is a false claim that spawns a pointless epic-review reflex.
|
|
633
|
+
# Still run the callback so the resolver can advance (it correctly does
|
|
634
|
+
# nothing for cards that remain blocked).
|
|
635
|
+
unless card_completion_verified?(epic, completed_card_number)
|
|
636
|
+
if defined?(LOG)
|
|
637
|
+
LOG.warn "[Basecamp:Orchestrator] Skipping epic review after card " \
|
|
638
|
+
"##{completed_card_number} — no PR/merge evidence of completion"
|
|
639
|
+
end
|
|
640
|
+
callback&.call
|
|
641
|
+
return
|
|
642
|
+
end
|
|
643
|
+
|
|
609
644
|
LOG.info "[Basecamp:Orchestrator] Dispatching epic review after card ##{completed_card_number}" if defined?(LOG)
|
|
610
645
|
|
|
611
646
|
# Ensure epic memory exists (in case this epic started before the feature)
|