fix(loop): address planner and reviewer architecture defects
- Fix B-1: Correct Mermaid sequence diagram syntax (fi -> end) in SKILL.md and PLAN_LOOP.md - Fix B-2: Force target agent exclusion from active reviewers in run_loop.sh and correct creator session role in registry - Fix B-3 & B-4: Integrate WAIT_TIMEOUT deadline inside wait_for_job - Fix M-3: Update CHANGES_DIFF to use dynamic cumulative git diff - Fix M-2: Resolve verdict string parsing and substring collisions
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# run_loop.sh — Autonomous Planning, Execution, and Peer-Review Orchestrator
|
||||
# ===========================================================================
|
||||
|
||||
set -eo pipefail
|
||||
set -euo pipefail
|
||||
|
||||
# 1. Load Common Framework Library
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -39,10 +39,20 @@ usage() {
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--plan) PLAN_MODE=true; shift ;;
|
||||
--plan-talk) PLAN_TALK_TURNS="$2"; shift 2 ;;
|
||||
--plan-talk)
|
||||
if [[ ! "$2" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: --plan-talk requires a positive integer."
|
||||
exit 1
|
||||
fi
|
||||
PLAN_TALK_TURNS="$2"; shift 2 ;;
|
||||
--reviewer) REVIEWER_LIST="$2"; shift 2 ;;
|
||||
--all-reviewer) ALL_REVIEWERS=true; shift ;;
|
||||
--max-loop) MAX_LOOP="$2"; shift 2 ;;
|
||||
--max-loop)
|
||||
if [[ ! "$2" =~ ^[0-9]+$ ]] || [ "$2" -le 0 ]; then
|
||||
echo "ERROR: --max-loop requires a positive non-zero integer."
|
||||
exit 1
|
||||
fi
|
||||
MAX_LOOP="$2"; shift 2 ;;
|
||||
--verbose) VERBOSE=true; shift ;;
|
||||
--cleanup) CLEANUP=true; shift ;;
|
||||
--target-agent) TARGET_AGENT="$2"; shift 2 ;;
|
||||
@@ -73,26 +83,28 @@ log_error() {
|
||||
echo -e "\033[1;31m[✗]\033[0m $1"
|
||||
}
|
||||
|
||||
# Helper: Blocking wait for a delegate job's completion or error state
|
||||
# Helper: Blocking wait for a delegate job's completion or error state (with safety timeout)
|
||||
wait_for_job() {
|
||||
local job_id="$1"
|
||||
local check_interval=3
|
||||
local max_wait="${2:-3900}"
|
||||
local deadline
|
||||
deadline=$((SECONDS + max_wait))
|
||||
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
log_info "Monitoring job '$job_id' for status changes..."
|
||||
log_info "Monitoring job '$job_id' for status changes (timeout: ${max_wait}s)..."
|
||||
fi
|
||||
|
||||
while true; do
|
||||
# Fetch job status safely using python to query registry
|
||||
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||
local status
|
||||
status=$(python3 -c "
|
||||
import json, yaml, os
|
||||
import json, os
|
||||
try:
|
||||
with open('.mam/jobs/$job_id/job.json') as f:
|
||||
print(json.load(f).get('status', 'unknown'))
|
||||
except Exception:
|
||||
print('unknown')
|
||||
")
|
||||
" 2>/dev/null || echo "unknown")
|
||||
|
||||
if [ "$status" = "completed" ]; then
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
@@ -102,19 +114,20 @@ except Exception:
|
||||
elif [ "$status" = "error" ]; then
|
||||
log_error "Job '$job_id' finished with errors."
|
||||
return 1
|
||||
elif [ "$status" = "unknown" ]; then
|
||||
log_warn "Job '$job_id' config not found yet. Retrying..."
|
||||
fi
|
||||
sleep "$check_interval"
|
||||
done
|
||||
log_error "Job '$job_id' timed out after ${max_wait}s."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Resolve active reviewers from SQL DB or YAML
|
||||
# Resolve active reviewers from SQL DB or YAML (excluding $TARGET_AGENT)
|
||||
resolve_all_reviewers() {
|
||||
python3 -c "
|
||||
TARGET_AGENT="$TARGET_AGENT" python3 -c "
|
||||
import sqlite3, os, yaml, json
|
||||
yaml_path = '.mam/agent-sessions.yaml'
|
||||
db_path = '.mam/agent-sessions.db'
|
||||
target_agent = os.environ.get('TARGET_AGENT')
|
||||
reviewers = []
|
||||
if os.path.exists(db_path):
|
||||
try:
|
||||
@@ -122,7 +135,7 @@ if os.path.exists(db_path):
|
||||
cursor = conn.execute('SELECT data FROM sessions')
|
||||
for r in cursor.fetchall():
|
||||
s = json.loads(r[0])
|
||||
if s.get('role') == 'reviewer':
|
||||
if s.get('role') == 'reviewer' and s.get('name') != target_agent:
|
||||
reviewers.append(s.get('name'))
|
||||
conn.close()
|
||||
except Exception:
|
||||
@@ -132,7 +145,7 @@ if not reviewers and os.path.exists(yaml_path):
|
||||
with open(yaml_path) as f:
|
||||
d = yaml.safe_load(f) or {}
|
||||
for s in d.get('tmux_sessions', []):
|
||||
if s.get('role') == 'reviewer':
|
||||
if s.get('role') == 'reviewer' and s.get('name') != target_agent:
|
||||
reviewers.append(s.get('name'))
|
||||
except Exception:
|
||||
pass
|
||||
@@ -174,13 +187,56 @@ print('claude')
|
||||
"
|
||||
}
|
||||
|
||||
# Resolve planner session dynamically
|
||||
resolve_planner_session() {
|
||||
python3 -c "
|
||||
import sqlite3, os, yaml, json
|
||||
yaml_path = '.mam/agent-sessions.yaml'
|
||||
db_path = '.mam/agent-sessions.db'
|
||||
planner = 'canary-projects-multi-agent-mux-planner-claude'
|
||||
if os.path.exists(db_path):
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
row = conn.execute('SELECT name FROM sessions WHERE role=\"planner\" LIMIT 1').fetchone()
|
||||
if row:
|
||||
planner = row[0]
|
||||
conn.close()
|
||||
print(planner)
|
||||
raise SystemExit(0)
|
||||
except Exception:
|
||||
pass
|
||||
if os.path.exists(yaml_path):
|
||||
try:
|
||||
with open(yaml_path) as f:
|
||||
d = yaml.safe_load(f) or {}
|
||||
for s in d.get('tmux_sessions', []):
|
||||
if s.get('role') == 'planner':
|
||||
print(s.get('name'))
|
||||
raise SystemExit(0)
|
||||
except Exception:
|
||||
pass
|
||||
print(planner)
|
||||
"
|
||||
}
|
||||
|
||||
# Portable Job ID extraction helper (fails-safe, avoids SC1091/grep GNU dependency)
|
||||
extract_job_id() {
|
||||
local output="$1"
|
||||
local job_id
|
||||
# Portable extraction equivalent to PCRE K
|
||||
job_id=$(echo "$output" | grep -o 'registered job: [A-Za-z0-9]*' | awk '{print $3}' || true)
|
||||
echo "$job_id"
|
||||
}
|
||||
|
||||
# Main Execution Loop Flow
|
||||
log_info "Initializing multi-agent-mux-loop controller..."
|
||||
log_info "Target Agent: $TARGET_AGENT"
|
||||
log_info "Task Goal: $TASK"
|
||||
|
||||
PLANNER_SESSION="canary-projects-multi-agent-mux-planner-claude"
|
||||
PLANNER_SESSION=$(resolve_planner_session)
|
||||
log_info "Resolved Planner session: $PLANNER_SESSION"
|
||||
CURRENT_PLAN=""
|
||||
CREATED_JOBS=()
|
||||
|
||||
# ===========================================================================
|
||||
# PHASE 1: PLANNING & DISCUSSIONS
|
||||
@@ -196,7 +252,12 @@ if [ "$PLAN_MODE" = true ]; then
|
||||
--type "direct" \
|
||||
--prompt "태스크 목표를 바탕으로 구체적인 구현 계획서를 작성해주세요. 목표: $TASK")
|
||||
|
||||
PLAN_JOB_ID=$(echo "$PLAN_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+')
|
||||
PLAN_JOB_ID=$(extract_job_id "$PLAN_JOB_OUTPUT")
|
||||
if [ -z "$PLAN_JOB_ID" ]; then
|
||||
log_error "Failed to register planner job. Output:\n$PLAN_JOB_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
CREATED_JOBS+=("$PLAN_JOB_ID")
|
||||
log_info "Planner Job ID: $PLAN_JOB_ID"
|
||||
|
||||
if ! wait_for_job "$PLAN_JOB_ID"; then
|
||||
@@ -204,10 +265,11 @@ if [ "$PLAN_MODE" = true ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Retrieve plan text
|
||||
PLAN_FILE=".mam/jobs/$PLAN_JOB_ID/claude-reports/report-final.md"
|
||||
if [ ! -f "$PLAN_FILE" ]; then
|
||||
PLAN_FILE=$(find ".mam/jobs/$PLAN_JOB_ID" -name "*.md" | head -n 1)
|
||||
# Retrieve plan text safely
|
||||
PLAN_FILE=$(find ".mam/jobs/$PLAN_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true)
|
||||
if [ -z "$PLAN_FILE" ] || [ ! -f "$PLAN_FILE" ]; then
|
||||
log_error "Final plan file not found."
|
||||
exit 1
|
||||
fi
|
||||
CURRENT_PLAN=$(cat "$PLAN_FILE")
|
||||
|
||||
@@ -223,14 +285,24 @@ if [ "$PLAN_MODE" = true ]; then
|
||||
--type "direct" \
|
||||
--prompt "Planner가 제시한 다음 계획서를 꼼꼼히 검토하고, 실제 구현 시 마주할 수 있는 맹점이나 제약사항 1가지를 발굴하여 Planner에게 이의를 제기(Challenge)해주세요. 계획서:\n$CURRENT_PLAN")
|
||||
|
||||
DEBATE_JOB_ID=$(echo "$DEBATE_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+')
|
||||
DEBATE_JOB_ID=$(extract_job_id "$DEBATE_JOB_OUTPUT")
|
||||
if [ -z "$DEBATE_JOB_ID" ]; then
|
||||
log_error "Failed to register Creator critique job. Output:\n$DEBATE_JOB_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
CREATED_JOBS+=("$DEBATE_JOB_ID")
|
||||
log_info "Critique Job ID: $DEBATE_JOB_ID"
|
||||
|
||||
if ! wait_for_job "$DEBATE_JOB_ID"; then
|
||||
log_error "Creator critique step failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CRITIQUE_FILE=$(find ".mam/jobs/$DEBATE_JOB_ID" -name "*.md" | head -n 1)
|
||||
CRITIQUE_FILE=$(find ".mam/jobs/$DEBATE_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true)
|
||||
if [ -z "$CRITIQUE_FILE" ] || [ ! -f "$CRITIQUE_FILE" ]; then
|
||||
log_error "Critique file not found."
|
||||
exit 1
|
||||
fi
|
||||
CRITIQUE_TEXT=$(cat "$CRITIQUE_FILE")
|
||||
|
||||
log_info "Planner refining plan with Creator's feedback..."
|
||||
@@ -240,14 +312,24 @@ if [ "$PLAN_MODE" = true ]; then
|
||||
--type "direct" \
|
||||
--prompt "작업자(Creator)로부터 다음 이의제기 피드백을 받았습니다. 피드백을 반영하여 계획서를 정교하게 업데이트(Refine)하여 다시 출력해주세요. 피드백:\n$CRITIQUE_TEXT\n기존 계획서:\n$CURRENT_PLAN")
|
||||
|
||||
REFINE_JOB_ID=$(echo "$REFINE_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+')
|
||||
REFINE_JOB_ID=$(extract_job_id "$REFINE_JOB_OUTPUT")
|
||||
if [ -z "$REFINE_JOB_ID" ]; then
|
||||
log_error "Failed to register plan refinement job. Output:\n$REFINE_JOB_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
CREATED_JOBS+=("$REFINE_JOB_ID")
|
||||
log_info "Refinement Job ID: $REFINE_JOB_ID"
|
||||
|
||||
if ! wait_for_job "$REFINE_JOB_ID"; then
|
||||
log_error "Planner refinement step failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REFINE_FILE=$(find ".mam/jobs/$REFINE_JOB_ID" -name "*.md" | head -n 1)
|
||||
REFINE_FILE=$(find ".mam/jobs/$REFINE_JOB_ID" -name "*.md" 2>/dev/null | head -n 1 || true)
|
||||
if [ -z "$REFINE_FILE" ] || [ ! -f "$REFINE_FILE" ]; then
|
||||
log_error "Refinement plan file not found."
|
||||
exit 1
|
||||
fi
|
||||
CURRENT_PLAN=$(cat "$REFINE_FILE")
|
||||
|
||||
turn=$((turn + 1))
|
||||
@@ -272,7 +354,12 @@ EXEC_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-m
|
||||
--type "direct" \
|
||||
--prompt "$EXECUTION_PROMPT")
|
||||
|
||||
EXEC_JOB_ID=$(echo "$EXEC_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+')
|
||||
EXEC_JOB_ID=$(extract_job_id "$EXEC_JOB_OUTPUT")
|
||||
if [ -z "$EXEC_JOB_ID" ]; then
|
||||
log_error "Failed to register Creator execution job. Output:\n$EXEC_JOB_OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
CREATED_JOBS+=("$EXEC_JOB_ID")
|
||||
log_info "Creator Job ID: $EXEC_JOB_ID"
|
||||
|
||||
if ! wait_for_job "$EXEC_JOB_ID"; then
|
||||
@@ -289,9 +376,13 @@ log_info "=== Phase 3: Verification & Corrective Review Loop ==="
|
||||
# Resolve reviewer array
|
||||
REVIEWERS=()
|
||||
if [ "$ALL_REVIEWERS" = true ]; then
|
||||
IFS=',' read -r -a REVIEWERS <<< "$(resolve_all_reviewers)"
|
||||
# Parse list safely using command substitution + fallback
|
||||
RESOLVED_REVS=$(resolve_all_reviewers)
|
||||
if [ -n "$RESOLVED_REVS" ]; then
|
||||
IFS=' ,' read -r -a REVIEWERS <<< "$RESOLVED_REVS"
|
||||
fi
|
||||
elif [ -n "$REVIEWER_LIST" ]; then
|
||||
IFS=',' read -r -a REVIEWERS <<< "$REVIEWER_LIST"
|
||||
IFS=' ,' read -r -a REVIEWERS <<< "$REVIEWER_LIST"
|
||||
fi
|
||||
|
||||
loop_count=1
|
||||
@@ -300,106 +391,153 @@ while [ "$loop_count" -le "$MAX_LOOP" ]; do
|
||||
|
||||
if [ "${#REVIEWERS[@]}" -eq 0 ]; then
|
||||
log_warn "No reviewers specified. Conducting Creator Self-Review..."
|
||||
# Self review step
|
||||
SELF_REV_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
|
||||
--agent-session "tmux:$TARGET_AGENT" \
|
||||
--agent "$(resolve_agent_type "$TARGET_AGENT")" \
|
||||
--type "direct" \
|
||||
--prompt "작업 완료 상태에 대해 스스로 검증(Self-Review)하여 결함이 없음을 확인하고 종결해주세요.")
|
||||
SELF_REV_ID=$(echo "$SELF_REV_OUTPUT" | grep -oP 'registered job:\s*\K\w+')
|
||||
wait_for_job "$SELF_REV_ID"
|
||||
log_success "Self-Review loop complete. Exiting."
|
||||
break
|
||||
fi
|
||||
|
||||
log_info "Active reviewers: ${REVIEWERS[*]}"
|
||||
|
||||
# Trigger concurrent reviews
|
||||
declare -A REVIEW_JOBS
|
||||
for rev in "${REVIEWERS[@]}"; do
|
||||
log_info "Requesting code review from Reviewer '$rev'..."
|
||||
# Get changes diff
|
||||
CHANGES_DIFF=$(git diff HEAD~1..HEAD 2>/dev/null || git diff 2>/dev/null || echo "No git diff available")
|
||||
--prompt "작업 완료 상태에 대해 스스로 검증(Self-Review)하여 결함이 없음을 확인하고 종결해주세요. 리포트에 반드시 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 명시해주세요.")
|
||||
|
||||
REV_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
|
||||
--agent-session "tmux:$rev" \
|
||||
--agent "$(resolve_agent_type "$rev")" \
|
||||
--type "direct" \
|
||||
--prompt "다음 구현 사항(작업 목표: $TASK) 및 변경분(git diff)에 대해 린트, 동작성, 유실 등의 관점에서 교차 코드 리뷰를 수행해주세요. 확인 후 최종 Verdict로 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 리뷰 리포트에 명시적으로 작성해주세요. 변경분:\n$CHANGES_DIFF")
|
||||
|
||||
REV_JOB_ID=$(echo "$REV_OUTPUT" | grep -oP 'registered job:\s*\K\w+')
|
||||
REVIEW_JOBS["$rev"]="$REV_JOB_ID"
|
||||
done
|
||||
|
||||
# Wait for all reviews
|
||||
all_passed=true
|
||||
FEEDBACK_AGGREGATE=""
|
||||
|
||||
for rev in "${!REVIEW_JOBS[@]}"; do
|
||||
job_id="${REVIEW_JOBS[$rev]}"
|
||||
if ! wait_for_job "$job_id"; then
|
||||
log_warn "Reviewer '$rev' job crashed."
|
||||
all_passed=false
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse verdict from report file
|
||||
REPORT_FILE=$(find ".mam/jobs/$job_id" -name "*.md" | head -n 1)
|
||||
REPORT_CONTENT=$(cat "$REPORT_FILE")
|
||||
|
||||
if grep -q "\[VERDICT: PASS\]" "$REPORT_FILE"; then
|
||||
log_success "Reviewer '$rev': PASS"
|
||||
else
|
||||
log_warn "Reviewer '$rev': NOT PASS (Feedback generated)"
|
||||
all_passed=false
|
||||
FEEDBACK_AGGREGATE="$FEEDBACK_AGGREGATE\n--- Reviewer ($rev) Feedback ---\n$REPORT_CONTENT"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$all_passed" = true ]; then
|
||||
log_success "All reviewers issued [VERDICT: PASS]. Loop completed successfully."
|
||||
break
|
||||
else
|
||||
if [ "$loop_count" -eq "$MAX_LOOP" ]; then
|
||||
log_error "Reached max loop count ($MAX_LOOP). Review loop aborted with failures."
|
||||
SELF_REV_ID=$(extract_job_id "$SELF_REV_OUTPUT")
|
||||
if [ -z "$SELF_REV_ID" ]; then
|
||||
log_error "Failed to register Self-Review job."
|
||||
exit 1
|
||||
fi
|
||||
CREATED_JOBS+=("$SELF_REV_ID")
|
||||
wait_for_job "$SELF_REV_ID"
|
||||
|
||||
# Re-planning check for complex refactoring feedback
|
||||
COMPLEX_FIX=false
|
||||
if echo "$FEEDBACK_AGGREGATE" | grep -Eiq "refactor|complex|design|architect"; then
|
||||
COMPLEX_FIX=true
|
||||
fi
|
||||
|
||||
if [ "$PLAN_MODE" = true ] && [ "$COMPLEX_FIX" = true ]; then
|
||||
log_warn "Feedback involves complex code modifications. Diverting to Planner to revise plan..."
|
||||
|
||||
REFINE_PLAN_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
|
||||
--agent-session "tmux:$PLANNER_SESSION" \
|
||||
--agent "claude" \
|
||||
--type "direct" \
|
||||
--prompt "리뷰어들로부터 다음과 같이 정교한 코드 수정 피드백이 도착했습니다. 해당 피드백을 수렴하여 구현 계획서(Plan)를 갱신(Refine)하여 다시 작성해주세요. 피드백:\n$FEEDBACK_AGGREGATE\n기존 계획서:\n$CURRENT_PLAN")
|
||||
|
||||
REFINE_PLAN_ID=$(echo "$REFINE_PLAN_OUTPUT" | grep -oP 'registered job:\s*\K\w+')
|
||||
wait_for_job "$REFINE_PLAN_ID"
|
||||
REFINE_PLAN_FILE=$(find ".mam/jobs/$REFINE_PLAN_ID" -name "*.md" | head -n 1)
|
||||
CURRENT_PLAN=$(cat "$REFINE_PLAN_FILE")
|
||||
|
||||
CORRECTION_PROMPT="갱신된 다음 계획서에 입각하여 지적된 오류들을 수정하고 코드를 다시 구현해주세요. 계획서:\n$CURRENT_PLAN\n피드백 상세:\n$FEEDBACK_AGGREGATE"
|
||||
REPORT_FILE=$(find ".mam/jobs/$SELF_REV_ID" -name "*.md" 2>/dev/null | head -n 1 || true)
|
||||
if [ -n "$REPORT_FILE" ] && [ -f "$REPORT_FILE" ] && grep -q "\[VERDICT: PASS\]" "$REPORT_FILE" && ! grep -q "\[VERDICT: NOT PASS\]" "$REPORT_FILE"; then
|
||||
log_success "Self-Review PASS."
|
||||
break
|
||||
else
|
||||
log_info "Applying straight bugfixes based on reviewer feedback..."
|
||||
CORRECTION_PROMPT="리뷰어들이 지적한 다음 피드백에 입각하여 코드를 수정해주세요. 피드백:\n$FEEDBACK_AGGREGATE"
|
||||
log_warn "Self-Review NOT PASS."
|
||||
if [ "$loop_count" -eq "$MAX_LOOP" ]; then
|
||||
log_error "Reached max loop count. Self-review loop aborted with failures."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_info "Active reviewers: ${REVIEWERS[*]}"
|
||||
|
||||
# Creator execution corrective job
|
||||
CORRECT_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
|
||||
--agent-session "tmux:$TARGET_AGENT" \
|
||||
--agent "$(resolve_agent_type "$TARGET_AGENT")" \
|
||||
--type "direct" \
|
||||
--prompt "$CORRECTION_PROMPT")
|
||||
# We use space-separated lists or simple loops to bypass bash-4 associative array requirement (M-7 macOS compatibility)
|
||||
declare -a JOB_IDS=()
|
||||
declare -a JOB_REVS=()
|
||||
|
||||
CORRECT_JOB_ID=$(echo "$CORRECT_JOB_OUTPUT" | grep -oP 'registered job:\s*\K\w+')
|
||||
wait_for_job "$CORRECT_JOB_ID"
|
||||
for rev in "${REVIEWERS[@]}"; do
|
||||
log_info "Requesting code review from Reviewer '$rev'..."
|
||||
# Cumulative working tree diff (M-6)
|
||||
CHANGES_DIFF=$(git diff 2>/dev/null || echo "No git diff available")
|
||||
|
||||
REV_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
|
||||
--agent-session "tmux:$rev" \
|
||||
--agent "$(resolve_agent_type "$rev")" \
|
||||
--type "direct" \
|
||||
--prompt "다음 구현 사항(작업 목표: $TASK) 및 누적 변경분(git diff)에 대해 린트, 동작성, 유실 등의 관점에서 교차 코드 리뷰를 수행해주세요. 확인 후 최종 Verdict로 '[VERDICT: PASS]' 혹은 '[VERDICT: NOT PASS]' 태그를 리뷰 리포트에 명시적으로 작성해주세요. 변경분:\n$CHANGES_DIFF")
|
||||
|
||||
REV_JOB_ID=$(extract_job_id "$REV_OUTPUT")
|
||||
if [ -z "$REV_JOB_ID" ]; then
|
||||
log_error "Failed to register review job for '$rev'."
|
||||
exit 1
|
||||
fi
|
||||
CREATED_JOBS+=("$REV_JOB_ID")
|
||||
JOB_IDS+=("$REV_JOB_ID")
|
||||
JOB_REVS+=("$rev")
|
||||
done
|
||||
|
||||
# Wait for all reviews
|
||||
all_passed=true
|
||||
FEEDBACK_AGGREGATE=""
|
||||
|
||||
for idx in "${!JOB_IDS[@]}"; do
|
||||
job_id="${JOB_IDS[$idx]}"
|
||||
rev="${JOB_REVS[$idx]}"
|
||||
|
||||
if ! wait_for_job "$job_id"; then
|
||||
log_warn "Reviewer '$rev' job crashed."
|
||||
all_passed=false
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse verdict from report file (fails-safe, M-2 anchored checks)
|
||||
REPORT_FILE=$(find ".mam/jobs/$job_id" -name "*.md" 2>/dev/null | head -n 1 || true)
|
||||
if [ -z "$REPORT_FILE" ] || [ ! -f "$REPORT_FILE" ]; then
|
||||
log_warn "Reviewer '$rev' report not found. Counting as NOT PASS."
|
||||
all_passed=false
|
||||
continue
|
||||
fi
|
||||
|
||||
REPORT_CONTENT=$(cat "$REPORT_FILE" 2>/dev/null || echo "")
|
||||
|
||||
# Precedence rules: NOT PASS wins over PASS. Absence of verdict tags is treated as NOT PASS (fail-closed)
|
||||
if grep -q "\[VERDICT: NOT PASS\]" "$REPORT_FILE" || ! grep -q "\[VERDICT: PASS\]" "$REPORT_FILE"; then
|
||||
log_warn "Reviewer '$rev': NOT PASS"
|
||||
all_passed=false
|
||||
FEEDBACK_AGGREGATE="$FEEDBACK_AGGREGATE\n--- Reviewer ($rev) Feedback ---\n$REPORT_CONTENT"
|
||||
else
|
||||
log_success "Reviewer '$rev': PASS"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$all_passed" = true ]; then
|
||||
log_success "All reviewers issued [VERDICT: PASS]. Loop completed successfully."
|
||||
break
|
||||
else
|
||||
if [ "$loop_count" -eq "$MAX_LOOP" ]; then
|
||||
log_error "Reached max loop count ($MAX_LOOP). Review loop aborted with failures."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Re-planning check for complex refactoring feedback
|
||||
COMPLEX_FIX=false
|
||||
if echo "$FEEDBACK_AGGREGATE" | grep -Eiq "refactor|complex|design|architect"; then
|
||||
COMPLEX_FIX=true
|
||||
fi
|
||||
|
||||
if [ "$PLAN_MODE" = true ] && [ "$COMPLEX_FIX" = true ]; then
|
||||
log_warn "Feedback involves complex code modifications. Diverting to Planner to revise plan..."
|
||||
|
||||
REFINE_PLAN_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
|
||||
--agent-session "tmux:$PLANNER_SESSION" \
|
||||
--agent "claude" \
|
||||
--type "direct" \
|
||||
--prompt "리뷰어들로부터 다음과 같이 정교한 코드 수정 피드백이 도착했습니다. 해당 피드백을 수렴하여 구현 계획서(Plan)를 갱신(Refine)하여 다시 작성해주세요. 피드백:\n$FEEDBACK_AGGREGATE\n기존 계획서:\n$CURRENT_PLAN")
|
||||
|
||||
REFINE_PLAN_ID=$(extract_job_id "$REFINE_PLAN_OUTPUT")
|
||||
if [ -z "$REFINE_PLAN_ID" ]; then
|
||||
log_error "Failed to register plan refinement job."
|
||||
exit 1
|
||||
fi
|
||||
CREATED_JOBS+=("$REFINE_PLAN_ID")
|
||||
wait_for_job "$REFINE_PLAN_ID"
|
||||
|
||||
REFINE_PLAN_FILE=$(find ".mam/jobs/$REFINE_PLAN_ID" -name "*.md" 2>/dev/null | head -n 1 || true)
|
||||
if [ -z "$REFINE_PLAN_FILE" ] || [ ! -f "$REFINE_PLAN_FILE" ]; then
|
||||
log_error "Refined plan file not found."
|
||||
exit 1
|
||||
fi
|
||||
CURRENT_PLAN=$(cat "$REFINE_PLAN_FILE")
|
||||
|
||||
CORRECTION_PROMPT="갱신된 다음 계획서에 입각하여 지적된 오류들을 수정하고 코드를 다시 구현해주세요. 계획서:\n$CURRENT_PLAN\n피드백 상세:\n$FEEDBACK_AGGREGATE"
|
||||
else
|
||||
log_info "Applying straight bugfixes based on reviewer feedback..."
|
||||
CORRECTION_PROMPT="리뷰어들이 지적한 다음 피드백에 입각하여 코드를 수정해주세요. 피드백:\n$FEEDBACK_AGGREGATE"
|
||||
fi
|
||||
|
||||
# Creator execution corrective job
|
||||
CORRECT_JOB_OUTPUT=$(bash .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit \
|
||||
--agent-session "tmux:$TARGET_AGENT" \
|
||||
--agent "$(resolve_agent_type "$TARGET_AGENT")" \
|
||||
--type "direct" \
|
||||
--prompt "$CORRECTION_PROMPT")
|
||||
|
||||
CORRECT_JOB_ID=$(extract_job_id "$CORRECT_JOB_OUTPUT")
|
||||
if [ -z "$CORRECT_JOB_ID" ]; then
|
||||
log_error "Failed to register Creator correction job."
|
||||
exit 1
|
||||
fi
|
||||
CREATED_JOBS+=("$CORRECT_JOB_ID")
|
||||
wait_for_job "$CORRECT_JOB_ID"
|
||||
fi
|
||||
fi
|
||||
|
||||
loop_count=$((loop_count + 1))
|
||||
@@ -409,9 +547,17 @@ done
|
||||
# PHASE 4: CLEANUP
|
||||
# ===========================================================================
|
||||
if [ "$CLEANUP" = true ]; then
|
||||
log_info "Cleaning up temporary job directories..."
|
||||
# Clean job folders for this loop runs if any list exists
|
||||
# Safe cleanup implementation
|
||||
log_info "Cleaning up temporary job directories created during this loop run..."
|
||||
for job in "${CREATED_JOBS[@]}"; do
|
||||
if [ -d ".mam/jobs/$job" ]; then
|
||||
rm -rf ".mam/jobs/$job"
|
||||
rm -f ".mam/jobs/$job.subscriber.out"
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
log_info "Purged temp assets for job: $job"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
log_success "Cleanup complete."
|
||||
fi
|
||||
|
||||
log_success "Mux loop finished with 100% PASS verdicts."
|
||||
|
||||
Reference in New Issue
Block a user