diff --git a/.agents/scripts/full-loop-helper.sh b/.agents/scripts/full-loop-helper.sh index 22440567a..060b20ea5 100755 --- a/.agents/scripts/full-loop-helper.sh +++ b/.agents/scripts/full-loop-helper.sh @@ -86,31 +86,31 @@ HEADLESS="${FULL_LOOP_HEADLESS:-false}" # Check if running in headless worker mode (t174) is_headless() { - [[ "$HEADLESS" == "true" ]] + [[ "$HEADLESS" == "true" ]] } # Install a git pre-commit hook that blocks TODO.md changes in headless mode (t173) # This is a hard guard — even if the AI agent tries to commit TODO.md, git rejects it. install_headless_todo_guard() { - local git_dir - git_dir=$(git rev-parse --git-dir 2>/dev/null || echo "") - if [[ -z "$git_dir" ]]; then - return 0 - fi - - local hooks_dir="$git_dir/hooks" - local hook_file="$hooks_dir/pre-commit" - local guard_marker="# t173-headless-todo-guard" - - mkdir -p "$hooks_dir" - - # If a pre-commit hook already exists, append our guard (if not already present) - if [[ -f "$hook_file" ]]; then - if grep -q "$guard_marker" "$hook_file" 2>/dev/null; then - return 0 # Already installed - fi - # Append to existing hook - cat >> "$hook_file" << 'GUARD' + local git_dir + git_dir=$(git rev-parse --git-dir 2>/dev/null || echo "") + if [[ -z "$git_dir" ]]; then + return 0 + fi + + local hooks_dir="$git_dir/hooks" + local hook_file="$hooks_dir/pre-commit" + local guard_marker="# t173-headless-todo-guard" + + mkdir -p "$hooks_dir" + + # If a pre-commit hook already exists, append our guard (if not already present) + if [[ -f "$hook_file" ]]; then + if grep -q "$guard_marker" "$hook_file" 2>/dev/null; then + return 0 # Already installed + fi + # Append to existing hook + cat >>"$hook_file" <<'GUARD' # t173-headless-todo-guard # Block TODO.md and planning file commits in headless worker mode @@ -122,9 +122,9 @@ if [[ "${FULL_LOOP_HEADLESS:-false}" == "true" ]]; then fi fi GUARD - else - # Create new hook - cat > "$hook_file" << 'GUARD' + else + # Create new hook + cat >"$hook_file" <<'GUARD' #!/usr/bin/env bash # t173-headless-todo-guard # Block TODO.md and planning file commits in headless worker mode @@ -136,48 +136,68 @@ if [[ "${FULL_LOOP_HEADLESS:-false}" == "true" ]]; then fi fi GUARD - chmod +x "$hook_file" - fi + chmod +x "$hook_file" + fi - return 0 + return 0 } # Remove the t173 headless guard from pre-commit hook (cleanup) remove_headless_todo_guard() { - local git_dir - git_dir=$(git rev-parse --git-dir 2>/dev/null || echo "") - if [[ -z "$git_dir" ]]; then - return 0 - fi - - local hook_file="$git_dir/hooks/pre-commit" - if [[ ! -f "$hook_file" ]]; then - return 0 - fi - - # Remove the guard block (from marker to end of guard) - if grep -q "t173-headless-todo-guard" "$hook_file" 2>/dev/null; then - # Use sed to remove the guard block - local tmp_file - tmp_file=$(mktemp) - _save_cleanup_scope; trap '_run_cleanups' RETURN - push_cleanup "rm -f '${tmp_file}'" - awk '/# t173-headless-todo-guard/{skip=1} /^fi$/ && skip{skip=0; next} !skip' "$hook_file" > "$tmp_file" - mv "$tmp_file" "$hook_file" - chmod +x "$hook_file" - fi + local git_dir + git_dir=$(git rev-parse --git-dir 2>/dev/null || echo "") + if [[ -z "$git_dir" ]]; then + return 0 + fi + + local hook_file="$git_dir/hooks/pre-commit" + if [[ ! -f "$hook_file" ]]; then + return 0 + fi + + # Remove the guard block (from marker to end of guard) + if grep -q "t173-headless-todo-guard" "$hook_file" 2>/dev/null; then + # Use sed to remove the guard block + local tmp_file + tmp_file=$(mktemp) + _save_cleanup_scope + trap '_run_cleanups' RETURN + push_cleanup "rm -f '${tmp_file}'" + awk '/# t173-headless-todo-guard/{skip=1} /^fi$/ && skip{skip=0; next} !skip' "$hook_file" >"$tmp_file" + mv "$tmp_file" "$hook_file" + chmod +x "$hook_file" + fi + + return 0 +} - return 0 +# Validate that a PR title contains a task ID (t318.2) +# Called before PR creation to ensure audit traceability. +# Returns 0 if valid, 1 if missing task ID (warning only — does not block). +validate_pr_task_id() { + local pr_title="$1" + local branch="${2:-}" + + if echo "$pr_title" | grep -qE '\bt[0-9]+(\.[0-9]+)*\b'; then + return 0 + fi + + print_warning "PR title missing task ID (tNNN): '$pr_title'" + if [[ -n "$branch" ]]; then + print_warning "Branch '$branch' does not contain a task ID pattern" + fi + print_warning "Every PR should reference a task from TODO.md for traceability" + return 1 } print_phase() { - local phase="$1" - local description="$2" - echo "" - echo -e "${BOLD}${CYAN}=== Phase: ${phase} ===${NC}" - echo -e "${CYAN}${description}${NC}" - echo "" - return 0 + local phase="$1" + local description="$2" + echo "" + echo -e "${BOLD}${CYAN}=== Phase: ${phase} ===${NC}" + echo -e "${CYAN}${description}${NC}" + echo "" + return 0 } # ============================================================================= @@ -185,19 +205,19 @@ print_phase() { # ============================================================================= init_state_dir() { - mkdir -p "$STATE_DIR" - return 0 + mkdir -p "$STATE_DIR" + return 0 } save_state() { - local phase="$1" - local prompt="$2" - local pr_number="${3:-}" - local started_at="${4:-$(date -u '+%Y-%m-%dT%H:%M:%SZ')}" - - init_state_dir - - cat > "$STATE_FILE" << EOF + local phase="$1" + local prompt="$2" + local pr_number="${3:-}" + local started_at="${4:-$(date -u '+%Y-%m-%dT%H:%M:%SZ')}" + + init_state_dir + + cat >"$STATE_FILE" </dev/null || echo "") - - # Check if repo name contains aidevops - if [[ "$repo_root" == *"/aidevops"* ]]; then - return 0 - fi - - # Check for marker file - if [[ -f "$repo_root/.aidevops-repo" ]]; then - return 0 - fi - - # Check if setup.sh exists and contains aidevops marker - if [[ -f "$repo_root/setup.sh" ]] && grep -q "aidevops" "$repo_root/setup.sh" 2>/dev/null; then - return 0 - fi - - return 1 + local repo_root + repo_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + + # Check if repo name contains aidevops + if [[ "$repo_root" == *"/aidevops"* ]]; then + return 0 + fi + + # Check for marker file + if [[ -f "$repo_root/.aidevops-repo" ]]; then + return 0 + fi + + # Check if setup.sh exists and contains aidevops marker + if [[ -f "$repo_root/setup.sh" ]] && grep -q "aidevops" "$repo_root/setup.sh" 2>/dev/null; then + return 0 + fi + + return 1 } get_current_branch() { - git branch --show-current 2>/dev/null || echo "" + git branch --show-current 2>/dev/null || echo "" } is_on_feature_branch() { - local branch - branch=$(get_current_branch) - [[ -n "$branch" && "$branch" != "main" && "$branch" != "master" ]] + local branch + branch=$(get_current_branch) + [[ -n "$branch" && "$branch" != "main" && "$branch" != "master" ]] } # ============================================================================= @@ -294,369 +314,376 @@ is_on_feature_branch() { # ============================================================================= run_task_phase() { - local prompt="$1" - local max_iterations="${MAX_TASK_ITERATIONS:-$DEFAULT_MAX_TASK_ITERATIONS}" - - # Auto-detect AI tool environment if RALPH_TOOL not explicitly set - # Priority: RALPH_TOOL env > OPENCODE env > CLAUDE_CODE env > command availability - local tool="${RALPH_TOOL:-}" - if [[ -z "$tool" ]]; then - if [[ -n "${OPENCODE:-}" ]] || [[ "${TERM_PROGRAM:-}" == "opencode" ]]; then - tool="opencode" - elif [[ -n "${CLAUDE_CODE:-}" ]]; then - tool="claude" - elif command -v opencode &>/dev/null; then - tool="opencode" - elif command -v claude &>/dev/null; then - tool="claude" - else - tool="opencode" # Default fallback name (will trigger legacy mode) - fi - fi - - print_info "Detected AI tool: $tool" - - print_phase "Task Development" "Running Ralph loop for task implementation" - - # Check if ralph-loop-helper.sh exists - if [[ ! -x "$SCRIPT_DIR/ralph-loop-helper.sh" ]]; then - print_error "ralph-loop-helper.sh not found or not executable" - return 1 - fi - - # Check if loop-common.sh exists (v2 infrastructure) - local use_v2=false - if [[ -f "$SCRIPT_DIR/loop-common.sh" ]]; then - use_v2=true - fi - - if [[ "$use_v2" == "true" ]] && command -v "$tool" &>/dev/null; then - # v2: External loop with fresh sessions per iteration - print_info "Using v2 architecture (fresh context per iteration)" - "$SCRIPT_DIR/ralph-loop-helper.sh" run "$prompt" \ - --tool "$tool" \ - --max-iterations "$max_iterations" \ - --completion-promise "TASK_COMPLETE" - else - # Legacy: Same-session loop (tool not available or no v2 infrastructure) - print_warning "Using legacy mode (same session). $tool CLI not found for v2 external loop." - "$SCRIPT_DIR/ralph-loop-helper.sh" setup "$prompt" \ - --max-iterations "$max_iterations" \ - --completion-promise "TASK_COMPLETE" - - print_info "Task loop initialized. AI will iterate until TASK_COMPLETE promise." - if ! is_headless; then - print_info "After task completion, run: full-loop-helper.sh resume" - fi - fi - - return 0 + local prompt="$1" + local max_iterations="${MAX_TASK_ITERATIONS:-$DEFAULT_MAX_TASK_ITERATIONS}" + + # Auto-detect AI tool environment if RALPH_TOOL not explicitly set + # Priority: RALPH_TOOL env > OPENCODE env > CLAUDE_CODE env > command availability + local tool="${RALPH_TOOL:-}" + if [[ -z "$tool" ]]; then + if [[ -n "${OPENCODE:-}" ]] || [[ "${TERM_PROGRAM:-}" == "opencode" ]]; then + tool="opencode" + elif [[ -n "${CLAUDE_CODE:-}" ]]; then + tool="claude" + elif command -v opencode &>/dev/null; then + tool="opencode" + elif command -v claude &>/dev/null; then + tool="claude" + else + tool="opencode" # Default fallback name (will trigger legacy mode) + fi + fi + + print_info "Detected AI tool: $tool" + + print_phase "Task Development" "Running Ralph loop for task implementation" + + # Check if ralph-loop-helper.sh exists + if [[ ! -x "$SCRIPT_DIR/ralph-loop-helper.sh" ]]; then + print_error "ralph-loop-helper.sh not found or not executable" + return 1 + fi + + # Check if loop-common.sh exists (v2 infrastructure) + local use_v2=false + if [[ -f "$SCRIPT_DIR/loop-common.sh" ]]; then + use_v2=true + fi + + if [[ "$use_v2" == "true" ]] && command -v "$tool" &>/dev/null; then + # v2: External loop with fresh sessions per iteration + print_info "Using v2 architecture (fresh context per iteration)" + "$SCRIPT_DIR/ralph-loop-helper.sh" run "$prompt" \ + --tool "$tool" \ + --max-iterations "$max_iterations" \ + --completion-promise "TASK_COMPLETE" + else + # Legacy: Same-session loop (tool not available or no v2 infrastructure) + print_warning "Using legacy mode (same session). $tool CLI not found for v2 external loop." + "$SCRIPT_DIR/ralph-loop-helper.sh" setup "$prompt" \ + --max-iterations "$max_iterations" \ + --completion-promise "TASK_COMPLETE" + + print_info "Task loop initialized. AI will iterate until TASK_COMPLETE promise." + if ! is_headless; then + print_info "After task completion, run: full-loop-helper.sh resume" + fi + fi + + return 0 } run_preflight_phase() { - print_phase "Preflight" "Running quality checks before commit" - - if [[ "${SKIP_PREFLIGHT:-false}" == "true" ]]; then - print_warning "Preflight skipped by user request" - echo "PREFLIGHT_PASS" - return 0 - fi - - # Run quality loop for preflight - local preflight_ran=false - if [[ -x "$SCRIPT_DIR/quality-loop-helper.sh" ]]; then - "$SCRIPT_DIR/quality-loop-helper.sh" preflight --auto-fix --max-iterations "${MAX_PREFLIGHT_ITERATIONS:-$DEFAULT_MAX_PREFLIGHT_ITERATIONS}" - preflight_ran=true - else - # Fallback to linters-local.sh - if [[ -x "$SCRIPT_DIR/linters-local.sh" ]]; then - "$SCRIPT_DIR/linters-local.sh" - preflight_ran=true - else - print_warning "No preflight script found, skipping checks" - print_info "Proceeding without preflight validation" - fi - fi - - # Only emit promise if checks actually ran - if [[ "$preflight_ran" == "true" ]]; then - echo "PREFLIGHT_PASS" - else - echo "PREFLIGHT_SKIPPED" - fi - return 0 + print_phase "Preflight" "Running quality checks before commit" + + if [[ "${SKIP_PREFLIGHT:-false}" == "true" ]]; then + print_warning "Preflight skipped by user request" + echo "PREFLIGHT_PASS" + return 0 + fi + + # Run quality loop for preflight + local preflight_ran=false + if [[ -x "$SCRIPT_DIR/quality-loop-helper.sh" ]]; then + "$SCRIPT_DIR/quality-loop-helper.sh" preflight --auto-fix --max-iterations "${MAX_PREFLIGHT_ITERATIONS:-$DEFAULT_MAX_PREFLIGHT_ITERATIONS}" + preflight_ran=true + else + # Fallback to linters-local.sh + if [[ -x "$SCRIPT_DIR/linters-local.sh" ]]; then + "$SCRIPT_DIR/linters-local.sh" + preflight_ran=true + else + print_warning "No preflight script found, skipping checks" + print_info "Proceeding without preflight validation" + fi + fi + + # Only emit promise if checks actually ran + if [[ "$preflight_ran" == "true" ]]; then + echo "PREFLIGHT_PASS" + else + echo "PREFLIGHT_SKIPPED" + fi + return 0 } run_pr_create_phase() { - print_phase "PR Creation" "Creating pull request" - - if [[ "${NO_AUTO_PR:-false}" == "true" ]]; then - if is_headless; then - # In headless mode, override --no-auto-pr since there's no human (t174) - print_warning "HEADLESS: Overriding --no-auto-pr (no human to create PR)" - else - print_warning "Auto PR creation disabled. Please create PR manually." - print_info "Run: gh pr create --fill" - print_info "Then run: full-loop-helper.sh resume" - return 0 - fi - fi - - # Verify gh CLI is authenticated before attempting PR operations (t156/t158) - if ! command -v gh &>/dev/null; then - print_error "gh CLI not found. Install with: brew install gh" - return 1 - fi - - local gh_auth_retries=3 - local gh_auth_ok=false - local i - for ((i = 1; i <= gh_auth_retries; i++)); do - if gh auth status &>/dev/null 2>&1; then - gh_auth_ok=true - break - fi - if [[ "$i" -lt "$gh_auth_retries" ]]; then - print_warning "gh auth check failed (attempt $i/$gh_auth_retries), retrying in 5s..." - sleep 5 - fi - done - - if [[ "$gh_auth_ok" != "true" ]]; then - print_error "gh CLI not authenticated after $gh_auth_retries attempts." - if is_headless; then - # In headless mode, exit cleanly so supervisor can evaluate (t174) - # The supervisor's evaluate_worker will detect auth_error pattern - print_error "HEADLESS: gh auth failure — exiting for supervisor evaluation" - return 1 - fi - print_error "Run 'gh auth login' to authenticate, then resume with: full-loop-helper.sh resume" - return 1 - fi - - # Ensure we're on a feature branch - if ! is_on_feature_branch; then - print_error "Not on a feature branch. Cannot create PR from main/master." - return 1 - fi - - local branch - branch=$(get_current_branch) - - # Pull --rebase to sync with any remote changes before push (t174) - # This handles: 1) rebasing onto latest main, 2) pulling any remote branch updates - print_info "Syncing with remote before push..." - if ! git fetch origin &>/dev/null; then - print_warning "Failed to fetch origin, proceeding with local state" - else - # Pull feature branch changes if it exists remotely (t174) - if git ls-remote --exit-code --heads origin "$branch" &>/dev/null; then - if ! git pull --rebase origin "$branch" &>/dev/null; then - print_warning "Pull --rebase on $branch failed (conflicts). Aborting rebase." - git rebase --abort &>/dev/null || true - else - print_info "Pull --rebase on $branch successful" - fi - fi - # Rebase onto latest main to avoid merge conflicts (t156) - if ! git rebase origin/main &>/dev/null; then - print_warning "Rebase onto origin/main failed (conflicts). Aborting rebase and pushing as-is." - git rebase --abort &>/dev/null || true - else - print_info "Rebase onto origin/main successful" - fi - fi - - # Push branch (force-with-lease after rebase to handle rebased history safely) - print_info "Pushing branch to origin..." - if git ls-remote --exit-code --heads origin "$branch" &>/dev/null; then - # Branch exists remotely - use force-with-lease after rebase - git push --force-with-lease origin "$branch" || { - print_error "Failed to push branch $branch" - return 1 - } - else - # New branch - initial push - git push -u origin "$branch" || { - print_error "Failed to push branch $branch" - return 1 - } - fi - - # Build PR title and body from task context (t156/t158) - local pr_title pr_body task_id_match - task_id_match=$(echo "$branch" | grep -oE 't[0-9]+(\.[0-9]+)*' | head -1 || echo "") - - if [[ -n "$task_id_match" ]]; then - # Look up task description: TODO.md first, then supervisor DB fallback (t158) - local task_desc="" - if [[ -f "TODO.md" ]]; then - task_desc=$(grep -E "^- \[( |x|-)\] $task_id_match " TODO.md 2>/dev/null | head -1 | sed -E 's/^- \[( |x|-)\] [^ ]* //' || echo "") - fi - if [[ -z "$task_desc" ]]; then - task_desc=$("$SCRIPT_DIR/supervisor-helper.sh" db \ - "SELECT description FROM tasks WHERE id = '$task_id_match';" 2>/dev/null || echo "") - fi - if [[ -n "$task_desc" ]]; then - # Extract first meaningful phrase for PR title (before #tags, ~estimates, etc.) - local short_desc - short_desc=$(echo "$task_desc" | sed -E 's/ [#~@].*//' | head -c 60) - pr_title="feat: ${short_desc} (${task_id_match})" - else - pr_title="feat: ${task_id_match}" - fi - else - pr_title="feat: ${branch#feature/}" - fi - - # Truncate title to 72 chars (GitHub convention) - if [[ ${#pr_title} -gt 72 ]]; then - pr_title="${pr_title:0:69}..." - fi - - # Build body from commit log - local commit_log - commit_log=$(git log origin/main..HEAD --pretty=format:"- %s" 2>/dev/null || echo "") - pr_body="## Summary + print_phase "PR Creation" "Creating pull request" + + if [[ "${NO_AUTO_PR:-false}" == "true" ]]; then + if is_headless; then + # In headless mode, override --no-auto-pr since there's no human (t174) + print_warning "HEADLESS: Overriding --no-auto-pr (no human to create PR)" + else + print_warning "Auto PR creation disabled. Please create PR manually." + print_info "Run: gh pr create --fill" + print_info "Then run: full-loop-helper.sh resume" + return 0 + fi + fi + + # Verify gh CLI is authenticated before attempting PR operations (t156/t158) + if ! command -v gh &>/dev/null; then + print_error "gh CLI not found. Install with: brew install gh" + return 1 + fi + + local gh_auth_retries=3 + local gh_auth_ok=false + local i + for ((i = 1; i <= gh_auth_retries; i++)); do + if gh auth status &>/dev/null 2>&1; then + gh_auth_ok=true + break + fi + if [[ "$i" -lt "$gh_auth_retries" ]]; then + print_warning "gh auth check failed (attempt $i/$gh_auth_retries), retrying in 5s..." + sleep 5 + fi + done + + if [[ "$gh_auth_ok" != "true" ]]; then + print_error "gh CLI not authenticated after $gh_auth_retries attempts." + if is_headless; then + # In headless mode, exit cleanly so supervisor can evaluate (t174) + # The supervisor's evaluate_worker will detect auth_error pattern + print_error "HEADLESS: gh auth failure — exiting for supervisor evaluation" + return 1 + fi + print_error "Run 'gh auth login' to authenticate, then resume with: full-loop-helper.sh resume" + return 1 + fi + + # Ensure we're on a feature branch + if ! is_on_feature_branch; then + print_error "Not on a feature branch. Cannot create PR from main/master." + return 1 + fi + + local branch + branch=$(get_current_branch) + + # Pull --rebase to sync with any remote changes before push (t174) + # This handles: 1) rebasing onto latest main, 2) pulling any remote branch updates + print_info "Syncing with remote before push..." + if ! git fetch origin &>/dev/null; then + print_warning "Failed to fetch origin, proceeding with local state" + else + # Pull feature branch changes if it exists remotely (t174) + if git ls-remote --exit-code --heads origin "$branch" &>/dev/null; then + if ! git pull --rebase origin "$branch" &>/dev/null; then + print_warning "Pull --rebase on $branch failed (conflicts). Aborting rebase." + git rebase --abort &>/dev/null || true + else + print_info "Pull --rebase on $branch successful" + fi + fi + # Rebase onto latest main to avoid merge conflicts (t156) + if ! git rebase origin/main &>/dev/null; then + print_warning "Rebase onto origin/main failed (conflicts). Aborting rebase and pushing as-is." + git rebase --abort &>/dev/null || true + else + print_info "Rebase onto origin/main successful" + fi + fi + + # Push branch (force-with-lease after rebase to handle rebased history safely) + print_info "Pushing branch to origin..." + if git ls-remote --exit-code --heads origin "$branch" &>/dev/null; then + # Branch exists remotely - use force-with-lease after rebase + git push --force-with-lease origin "$branch" || { + print_error "Failed to push branch $branch" + return 1 + } + else + # New branch - initial push + git push -u origin "$branch" || { + print_error "Failed to push branch $branch" + return 1 + } + fi + + # Build PR title and body from task context (t156/t158/t318.2) + local pr_title pr_body task_id_match + task_id_match=$(echo "$branch" | grep -oE 't[0-9]+(\.[0-9]+)*' | head -1 || echo "") + + if [[ -n "$task_id_match" ]]; then + # Look up task description: TODO.md first, then supervisor DB fallback (t158) + local task_desc="" + if [[ -f "TODO.md" ]]; then + task_desc=$(grep -E "^- \[( |x|-)\] $task_id_match " TODO.md 2>/dev/null | head -1 | sed -E 's/^- \[( |x|-)\] [^ ]* //' || echo "") + fi + if [[ -z "$task_desc" ]]; then + task_desc=$("$SCRIPT_DIR/supervisor-helper.sh" db \ + "SELECT description FROM tasks WHERE id = '$task_id_match';" 2>/dev/null || echo "") + fi + if [[ -n "$task_desc" ]]; then + # Extract first meaningful phrase for PR title (before #tags, ~estimates, etc.) + local short_desc + short_desc=$(echo "$task_desc" | sed -E 's/ [#~@].*//' | head -c 60) + # t318.2: MANDATORY format — task ID at start of title for audit traceability + pr_title="${task_id_match}: ${short_desc}" + else + pr_title="${task_id_match}: feat" + fi + else + # t318.2: Warn when no task ID found — every PR should be traceable to a task + print_warning "No task ID (tNNN) found in branch name '$branch'" + print_warning "PR title will lack task ID. Create a TODO entry first for traceability." + pr_title="feat: ${branch#feature/}" + fi + + # Truncate title to 72 chars (GitHub convention) + if [[ ${#pr_title} -gt 72 ]]; then + pr_title="${pr_title:0:69}..." + fi + + # t318.2: Validate PR title contains task ID before creation + validate_pr_task_id "$pr_title" "$branch" || true + + # Build body from commit log + local commit_log + commit_log=$(git log origin/main..HEAD --pretty=format:"- %s" 2>/dev/null || echo "") + pr_body="## Summary ${commit_log:-No commits yet.} --- *Created by full-loop worker*" - - # Create PR with proper title and body - print_info "Creating pull request..." - local pr_url - pr_url=$(gh pr create --title "$pr_title" --body "$pr_body" 2>&1) || { - # PR might already exist - pr_url=$(gh pr view --json url --jq '.url' 2>/dev/null || echo "") - if [[ -z "$pr_url" ]]; then - print_error "Failed to create PR" - return 1 - fi - print_info "PR already exists: $pr_url" - } - - # Extract PR number - local pr_number - pr_number=$(echo "$pr_url" | grep -oE '[0-9]+$' || gh pr view --json number --jq '.number') - - print_success "PR created: $pr_url" - - # Update state with PR number - save_state "$PHASE_PR_REVIEW" "$SAVED_PROMPT" "$pr_number" "$STARTED_AT" - - return 0 + + # Create PR with proper title and body + print_info "Creating pull request..." + local pr_url + pr_url=$(gh pr create --title "$pr_title" --body "$pr_body" 2>&1) || { + # PR might already exist + pr_url=$(gh pr view --json url --jq '.url' 2>/dev/null || echo "") + if [[ -z "$pr_url" ]]; then + print_error "Failed to create PR" + return 1 + fi + print_info "PR already exists: $pr_url" + } + + # Extract PR number + local pr_number + pr_number=$(echo "$pr_url" | grep -oE '[0-9]+$' || gh pr view --json number --jq '.number') + + print_success "PR created: $pr_url" + + # Update state with PR number + save_state "$PHASE_PR_REVIEW" "$SAVED_PROMPT" "$pr_number" "$STARTED_AT" + + return 0 } run_pr_review_phase() { - print_phase "PR Review" "Monitoring PR for approval and CI checks" - - local pr_number="${PR_NUMBER:-}" - - if [[ -z "$pr_number" ]]; then - # Try to get PR number from current branch - pr_number=$(gh pr view --json number --jq '.number' 2>/dev/null || echo "") - fi - - if [[ -z "$pr_number" ]]; then - print_error "No PR number found. Create PR first." - return 1 - fi - - print_info "Monitoring PR #$pr_number..." - - # Run quality loop for PR review - if [[ -x "$SCRIPT_DIR/quality-loop-helper.sh" ]]; then - "$SCRIPT_DIR/quality-loop-helper.sh" pr-review --pr "$pr_number" --wait-for-ci --max-iterations "${MAX_PR_ITERATIONS:-$DEFAULT_MAX_PR_ITERATIONS}" - - # Verify PR was actually merged before emitting promise - local pr_state - pr_state=$(gh pr view "$pr_number" --json state --jq '.state' 2>/dev/null || echo "UNKNOWN") - if [[ "$pr_state" == "MERGED" ]]; then - echo "PR_MERGED" - else - print_warning "PR #$pr_number is $pr_state (not merged yet)" - if ! is_headless; then - print_info "Merge PR manually, then run: full-loop-helper.sh resume" - fi - echo "PR_APPROVED" - fi - else - print_warning "quality-loop-helper.sh not found" - if is_headless; then - # In headless mode, emit PR_WAITING and let supervisor handle (t174) - print_info "HEADLESS: PR review skipped, supervisor will evaluate" - else - print_info "Merge PR manually, then run: full-loop-helper.sh resume" - fi - echo "PR_WAITING" - fi - - return 0 + print_phase "PR Review" "Monitoring PR for approval and CI checks" + + local pr_number="${PR_NUMBER:-}" + + if [[ -z "$pr_number" ]]; then + # Try to get PR number from current branch + pr_number=$(gh pr view --json number --jq '.number' 2>/dev/null || echo "") + fi + + if [[ -z "$pr_number" ]]; then + print_error "No PR number found. Create PR first." + return 1 + fi + + print_info "Monitoring PR #$pr_number..." + + # Run quality loop for PR review + if [[ -x "$SCRIPT_DIR/quality-loop-helper.sh" ]]; then + "$SCRIPT_DIR/quality-loop-helper.sh" pr-review --pr "$pr_number" --wait-for-ci --max-iterations "${MAX_PR_ITERATIONS:-$DEFAULT_MAX_PR_ITERATIONS}" + + # Verify PR was actually merged before emitting promise + local pr_state + pr_state=$(gh pr view "$pr_number" --json state --jq '.state' 2>/dev/null || echo "UNKNOWN") + if [[ "$pr_state" == "MERGED" ]]; then + echo "PR_MERGED" + else + print_warning "PR #$pr_number is $pr_state (not merged yet)" + if ! is_headless; then + print_info "Merge PR manually, then run: full-loop-helper.sh resume" + fi + echo "PR_APPROVED" + fi + else + print_warning "quality-loop-helper.sh not found" + if is_headless; then + # In headless mode, emit PR_WAITING and let supervisor handle (t174) + print_info "HEADLESS: PR review skipped, supervisor will evaluate" + else + print_info "Merge PR manually, then run: full-loop-helper.sh resume" + fi + echo "PR_WAITING" + fi + + return 0 } run_postflight_phase() { - print_phase "Postflight" "Verifying release health" - - if [[ "${SKIP_POSTFLIGHT:-false}" == "true" ]]; then - print_warning "Postflight skipped by user request" - echo "POSTFLIGHT_SKIPPED" - return 0 - fi - - # Run quality loop for postflight - local postflight_ran=false - if [[ -x "$SCRIPT_DIR/quality-loop-helper.sh" ]]; then - "$SCRIPT_DIR/quality-loop-helper.sh" postflight --monitor-duration 5m - postflight_ran=true - else - # Fallback to postflight-check.sh - if [[ -x "$SCRIPT_DIR/postflight-check.sh" ]]; then - "$SCRIPT_DIR/postflight-check.sh" - postflight_ran=true - else - print_warning "No postflight script found, skipping verification" - print_info "Proceeding without postflight validation" - fi - fi - - # Only emit promise if checks actually ran - if [[ "$postflight_ran" == "true" ]]; then - echo "RELEASE_HEALTHY" - else - echo "POSTFLIGHT_SKIPPED" - fi - return 0 + print_phase "Postflight" "Verifying release health" + + if [[ "${SKIP_POSTFLIGHT:-false}" == "true" ]]; then + print_warning "Postflight skipped by user request" + echo "POSTFLIGHT_SKIPPED" + return 0 + fi + + # Run quality loop for postflight + local postflight_ran=false + if [[ -x "$SCRIPT_DIR/quality-loop-helper.sh" ]]; then + "$SCRIPT_DIR/quality-loop-helper.sh" postflight --monitor-duration 5m + postflight_ran=true + else + # Fallback to postflight-check.sh + if [[ -x "$SCRIPT_DIR/postflight-check.sh" ]]; then + "$SCRIPT_DIR/postflight-check.sh" + postflight_ran=true + else + print_warning "No postflight script found, skipping verification" + print_info "Proceeding without postflight validation" + fi + fi + + # Only emit promise if checks actually ran + if [[ "$postflight_ran" == "true" ]]; then + echo "RELEASE_HEALTHY" + else + echo "POSTFLIGHT_SKIPPED" + fi + return 0 } run_deploy_phase() { - print_phase "Deploy" "Deploying changes locally" - - if ! is_aidevops_repo; then - print_info "Not an aidevops repo, skipping deploy phase" - return 0 - fi - - if [[ "${NO_AUTO_DEPLOY:-false}" == "true" ]]; then - print_warning "Auto deploy disabled. Run manually: ./setup.sh" - return 0 - fi - - local repo_root - repo_root=$(git rev-parse --show-toplevel) - - print_info "Running setup.sh to deploy changes..." - - if [[ -x "$repo_root/setup.sh" ]]; then - (cd "$repo_root" && AIDEVOPS_NON_INTERACTIVE=true ./setup.sh --non-interactive) - print_success "Deployment complete!" - echo "DEPLOYED" - else - print_warning "setup.sh not found or not executable" - fi - - return 0 + print_phase "Deploy" "Deploying changes locally" + + if ! is_aidevops_repo; then + print_info "Not an aidevops repo, skipping deploy phase" + return 0 + fi + + if [[ "${NO_AUTO_DEPLOY:-false}" == "true" ]]; then + print_warning "Auto deploy disabled. Run manually: ./setup.sh" + return 0 + fi + + local repo_root + repo_root=$(git rev-parse --show-toplevel) + + print_info "Running setup.sh to deploy changes..." + + if [[ -x "$repo_root/setup.sh" ]]; then + (cd "$repo_root" && AIDEVOPS_NON_INTERACTIVE=true ./setup.sh --non-interactive) + print_success "Deployment complete!" + echo "DEPLOYED" + else + print_warning "setup.sh not found or not executable" + fi + + return 0 } # ============================================================================= @@ -664,372 +691,372 @@ run_deploy_phase() { # ============================================================================= cmd_start() { - local prompt="$1" - shift - - local background=false - - # Parse options - while [[ $# -gt 0 ]]; do - case "$1" in - --max-task-iterations) - MAX_TASK_ITERATIONS="$2" - shift 2 - ;; - --max-preflight-iterations) - MAX_PREFLIGHT_ITERATIONS="$2" - shift 2 - ;; - --max-pr-iterations) - MAX_PR_ITERATIONS="$2" - shift 2 - ;; - --skip-preflight) - SKIP_PREFLIGHT=true - shift - ;; - --skip-postflight) - SKIP_POSTFLIGHT=true - shift - ;; - --no-auto-pr) - NO_AUTO_PR=true - shift - ;; - --no-auto-deploy) - NO_AUTO_DEPLOY=true - shift - ;; - --headless) - HEADLESS=true - shift - ;; - --dry-run) - DRY_RUN=true - shift - ;; - --background|--bg) - background=true - shift - ;; - *) - print_error "Unknown option: $1" - return 1 - ;; - esac - done - - if [[ -z "$prompt" ]]; then - print_error "No prompt provided" - echo "Usage: full-loop-helper.sh start \"\" [options]" - return 1 - fi - - if is_loop_active; then - print_warning "A loop is already active. Use 'resume' to continue or 'cancel' to stop." - return 1 - fi - - # Check we're on a feature branch - if ! is_on_feature_branch; then - print_error "Must be on a feature branch to start full loop" - print_info "Create a branch first: git checkout -b feature/your-feature" - return 1 - fi - - # Install git pre-commit guard to block TODO.md commits in headless mode (t173) - if is_headless; then - install_headless_todo_guard - print_info "HEADLESS: Installed TODO.md commit guard (t173)" - fi - - # Pre-flight GitHub auth check — workers spawned via nohup/cron may lack - # SSH keys or valid gh tokens. Fail fast before burning compute. - if ! gh auth status >/dev/null 2>&1; then - print_error "GitHub auth unavailable — gh auth status failed" - print_error "Workers need 'gh auth login' with HTTPS protocol." - return 1 - fi - - # Verify remote uses HTTPS (not SSH) — cron workers can't use SSH keys - local remote_url - remote_url=$(git remote get-url origin 2>/dev/null || echo "") - if [[ "$remote_url" == git@* || "$remote_url" == ssh://* ]]; then - print_warning "Remote URL is SSH ($remote_url) — switching to HTTPS for worker compatibility" - local https_url - https_url=$(echo "$remote_url" | sed -E 's|^git@github\.com:|https://github.com/|; s|^ssh://git@github\.com/|https://github.com/|; s|\.git$||').git - git remote set-url origin "$https_url" 2>/dev/null || true - print_info "Remote URL updated to $https_url" - fi - - echo "" - echo -e "${BOLD}${BLUE}╔════════════════════════════════════════════════════════════╗${NC}" - echo -e "${BOLD}${BLUE}║ FULL DEVELOPMENT LOOP - STARTING ║${NC}" - echo -e "${BOLD}${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" - echo "" - echo -e "${CYAN}Task:${NC} $prompt" - echo -e "${CYAN}Branch:${NC} $(get_current_branch)" - echo "" - echo -e "${CYAN}Phases:${NC}" - echo " 1. Task Development (Ralph loop)" - echo " 2. Preflight (quality checks)" - echo " 3. PR Creation" - echo " 4. PR Review (CI + approval)" - echo " 5. Postflight (release health)" - if is_aidevops_repo; then - echo " 6. Deploy (setup.sh)" - fi - echo "" - - if [[ "${DRY_RUN:-false}" == "true" ]]; then - print_info "Dry run - no changes made" - return 0 - fi - - # Save initial state - save_state "$PHASE_TASK" "$prompt" - SAVED_PROMPT="$prompt" - - # Background mode: run in background with nohup - if [[ "$background" == "true" ]]; then - local log_file="${STATE_DIR}/full-loop.log" - local pid_file="${STATE_DIR}/full-loop.pid" - - mkdir -p "$STATE_DIR" - - print_info "Starting full loop in background..." - - # Export variables for background process - export MAX_TASK_ITERATIONS MAX_PREFLIGHT_ITERATIONS MAX_PR_ITERATIONS - export SKIP_PREFLIGHT SKIP_POSTFLIGHT NO_AUTO_PR NO_AUTO_DEPLOY - export FULL_LOOP_HEADLESS="$HEADLESS" - export SAVED_PROMPT="$prompt" - - # Start background process - nohup "$0" _run_foreground "$prompt" > "$log_file" 2>&1 & - local pid=$! - echo "$pid" > "$pid_file" - - print_success "Full loop started in background (PID: $pid)" - print_info "Check status: full-loop-helper.sh status" - print_info "View logs: full-loop-helper.sh logs" - print_info "Or: tail -f $log_file" - return 0 - fi - - # Start task phase (foreground) - run_task_phase "$prompt" - - return 0 + local prompt="$1" + shift + + local background=false + + # Parse options + while [[ $# -gt 0 ]]; do + case "$1" in + --max-task-iterations) + MAX_TASK_ITERATIONS="$2" + shift 2 + ;; + --max-preflight-iterations) + MAX_PREFLIGHT_ITERATIONS="$2" + shift 2 + ;; + --max-pr-iterations) + MAX_PR_ITERATIONS="$2" + shift 2 + ;; + --skip-preflight) + SKIP_PREFLIGHT=true + shift + ;; + --skip-postflight) + SKIP_POSTFLIGHT=true + shift + ;; + --no-auto-pr) + NO_AUTO_PR=true + shift + ;; + --no-auto-deploy) + NO_AUTO_DEPLOY=true + shift + ;; + --headless) + HEADLESS=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --background | --bg) + background=true + shift + ;; + *) + print_error "Unknown option: $1" + return 1 + ;; + esac + done + + if [[ -z "$prompt" ]]; then + print_error "No prompt provided" + echo "Usage: full-loop-helper.sh start \"\" [options]" + return 1 + fi + + if is_loop_active; then + print_warning "A loop is already active. Use 'resume' to continue or 'cancel' to stop." + return 1 + fi + + # Check we're on a feature branch + if ! is_on_feature_branch; then + print_error "Must be on a feature branch to start full loop" + print_info "Create a branch first: git checkout -b feature/your-feature" + return 1 + fi + + # Install git pre-commit guard to block TODO.md commits in headless mode (t173) + if is_headless; then + install_headless_todo_guard + print_info "HEADLESS: Installed TODO.md commit guard (t173)" + fi + + # Pre-flight GitHub auth check — workers spawned via nohup/cron may lack + # SSH keys or valid gh tokens. Fail fast before burning compute. + if ! gh auth status >/dev/null 2>&1; then + print_error "GitHub auth unavailable — gh auth status failed" + print_error "Workers need 'gh auth login' with HTTPS protocol." + return 1 + fi + + # Verify remote uses HTTPS (not SSH) — cron workers can't use SSH keys + local remote_url + remote_url=$(git remote get-url origin 2>/dev/null || echo "") + if [[ "$remote_url" == git@* || "$remote_url" == ssh://* ]]; then + print_warning "Remote URL is SSH ($remote_url) — switching to HTTPS for worker compatibility" + local https_url + https_url=$(echo "$remote_url" | sed -E 's|^git@github\.com:|https://github.com/|; s|^ssh://git@github\.com/|https://github.com/|; s|\.git$||').git + git remote set-url origin "$https_url" 2>/dev/null || true + print_info "Remote URL updated to $https_url" + fi + + echo "" + echo -e "${BOLD}${BLUE}╔════════════════════════════════════════════════════════════╗${NC}" + echo -e "${BOLD}${BLUE}║ FULL DEVELOPMENT LOOP - STARTING ║${NC}" + echo -e "${BOLD}${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e "${CYAN}Task:${NC} $prompt" + echo -e "${CYAN}Branch:${NC} $(get_current_branch)" + echo "" + echo -e "${CYAN}Phases:${NC}" + echo " 1. Task Development (Ralph loop)" + echo " 2. Preflight (quality checks)" + echo " 3. PR Creation" + echo " 4. PR Review (CI + approval)" + echo " 5. Postflight (release health)" + if is_aidevops_repo; then + echo " 6. Deploy (setup.sh)" + fi + echo "" + + if [[ "${DRY_RUN:-false}" == "true" ]]; then + print_info "Dry run - no changes made" + return 0 + fi + + # Save initial state + save_state "$PHASE_TASK" "$prompt" + SAVED_PROMPT="$prompt" + + # Background mode: run in background with nohup + if [[ "$background" == "true" ]]; then + local log_file="${STATE_DIR}/full-loop.log" + local pid_file="${STATE_DIR}/full-loop.pid" + + mkdir -p "$STATE_DIR" + + print_info "Starting full loop in background..." + + # Export variables for background process + export MAX_TASK_ITERATIONS MAX_PREFLIGHT_ITERATIONS MAX_PR_ITERATIONS + export SKIP_PREFLIGHT SKIP_POSTFLIGHT NO_AUTO_PR NO_AUTO_DEPLOY + export FULL_LOOP_HEADLESS="$HEADLESS" + export SAVED_PROMPT="$prompt" + + # Start background process + nohup "$0" _run_foreground "$prompt" >"$log_file" 2>&1 & + local pid=$! + echo "$pid" >"$pid_file" + + print_success "Full loop started in background (PID: $pid)" + print_info "Check status: full-loop-helper.sh status" + print_info "View logs: full-loop-helper.sh logs" + print_info "Or: tail -f $log_file" + return 0 + fi + + # Start task phase (foreground) + run_task_phase "$prompt" + + return 0 } # Internal command for background execution cmd_run_foreground() { - local prompt="$1" - run_task_phase "$prompt" - - # Auto-advance when task phase completes in v2. - # Legacy mode leaves a Ralph state file; in that case we must wait for manual completion. - if [[ -f ".agents/loop-state/ralph-loop.local.state" ]] || [[ -f ".claude/ralph-loop.local.state" ]]; then - print_warning "Task loop still active (legacy mode). Run: full-loop-helper.sh resume when complete." - return 0 - fi - - cmd_resume - return 0 + local prompt="$1" + run_task_phase "$prompt" + + # Auto-advance when task phase completes in v2. + # Legacy mode leaves a Ralph state file; in that case we must wait for manual completion. + if [[ -f ".agents/loop-state/ralph-loop.local.state" ]] || [[ -f ".claude/ralph-loop.local.state" ]]; then + print_warning "Task loop still active (legacy mode). Run: full-loop-helper.sh resume when complete." + return 0 + fi + + cmd_resume + return 0 } cmd_resume() { - if ! is_loop_active; then - print_error "No active loop to resume" - return 1 - fi - - load_state - - print_info "Resuming from phase: $CURRENT_PHASE" - - case "$CURRENT_PHASE" in - "$PHASE_TASK") - # Check if task is complete (check both new and legacy locations) - if [[ -f ".agents/loop-state/ralph-loop.local.state" ]] || [[ -f ".claude/ralph-loop.local.state" ]]; then - print_info "Task loop still active. Complete it first." - return 0 - fi - # README gate reminder before preflight transition (t174: skip in headless) - if is_headless; then - print_info "HEADLESS: Skipping interactive README gate reminder" - else - print_warning "README GATE: Did this task add/change user-facing features?" - print_info "If yes, ensure README.md is updated before proceeding." - print_info "For aidevops repo:" - print_info " Run: readme-helper.sh check" - fi - save_state "$PHASE_PREFLIGHT" "$SAVED_PROMPT" "" "$STARTED_AT" - run_preflight_phase - save_state "$PHASE_PR_CREATE" "$SAVED_PROMPT" "" "$STARTED_AT" - run_pr_create_phase - ;; - "$PHASE_PREFLIGHT") - run_preflight_phase - save_state "$PHASE_PR_CREATE" "$SAVED_PROMPT" "" "$STARTED_AT" - run_pr_create_phase - ;; - "$PHASE_PR_CREATE") - run_pr_create_phase - ;; - "$PHASE_PR_REVIEW") - run_pr_review_phase - save_state "$PHASE_POSTFLIGHT" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" - run_postflight_phase - save_state "$PHASE_DEPLOY" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" - run_deploy_phase - save_state "$PHASE_COMPLETE" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" - cmd_complete - ;; - "$PHASE_POSTFLIGHT") - run_postflight_phase - save_state "$PHASE_DEPLOY" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" - run_deploy_phase - save_state "$PHASE_COMPLETE" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" - cmd_complete - ;; - "$PHASE_DEPLOY") - run_deploy_phase - save_state "$PHASE_COMPLETE" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" - cmd_complete - ;; - "$PHASE_COMPLETE") - cmd_complete - ;; - *) - print_error "Unknown phase: $CURRENT_PHASE" - return 1 - ;; - esac - - return 0 + if ! is_loop_active; then + print_error "No active loop to resume" + return 1 + fi + + load_state + + print_info "Resuming from phase: $CURRENT_PHASE" + + case "$CURRENT_PHASE" in + "$PHASE_TASK") + # Check if task is complete (check both new and legacy locations) + if [[ -f ".agents/loop-state/ralph-loop.local.state" ]] || [[ -f ".claude/ralph-loop.local.state" ]]; then + print_info "Task loop still active. Complete it first." + return 0 + fi + # README gate reminder before preflight transition (t174: skip in headless) + if is_headless; then + print_info "HEADLESS: Skipping interactive README gate reminder" + else + print_warning "README GATE: Did this task add/change user-facing features?" + print_info "If yes, ensure README.md is updated before proceeding." + print_info "For aidevops repo:" + print_info " Run: readme-helper.sh check" + fi + save_state "$PHASE_PREFLIGHT" "$SAVED_PROMPT" "" "$STARTED_AT" + run_preflight_phase + save_state "$PHASE_PR_CREATE" "$SAVED_PROMPT" "" "$STARTED_AT" + run_pr_create_phase + ;; + "$PHASE_PREFLIGHT") + run_preflight_phase + save_state "$PHASE_PR_CREATE" "$SAVED_PROMPT" "" "$STARTED_AT" + run_pr_create_phase + ;; + "$PHASE_PR_CREATE") + run_pr_create_phase + ;; + "$PHASE_PR_REVIEW") + run_pr_review_phase + save_state "$PHASE_POSTFLIGHT" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" + run_postflight_phase + save_state "$PHASE_DEPLOY" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" + run_deploy_phase + save_state "$PHASE_COMPLETE" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" + cmd_complete + ;; + "$PHASE_POSTFLIGHT") + run_postflight_phase + save_state "$PHASE_DEPLOY" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" + run_deploy_phase + save_state "$PHASE_COMPLETE" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" + cmd_complete + ;; + "$PHASE_DEPLOY") + run_deploy_phase + save_state "$PHASE_COMPLETE" "$SAVED_PROMPT" "$PR_NUMBER" "$STARTED_AT" + cmd_complete + ;; + "$PHASE_COMPLETE") + cmd_complete + ;; + *) + print_error "Unknown phase: $CURRENT_PHASE" + return 1 + ;; + esac + + return 0 } cmd_status() { - if ! is_loop_active; then - echo "No active full loop" - return 0 - fi - - load_state - - echo "" - echo -e "${BOLD}Full Development Loop Status${NC}" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo -e "Phase: ${CYAN}$CURRENT_PHASE${NC}" - echo -e "Started: $STARTED_AT" - echo -e "PR: ${PR_NUMBER:-none}" - echo "" - echo "Prompt:" - echo "$SAVED_PROMPT" | head -5 - echo "" - - return 0 + if ! is_loop_active; then + echo "No active full loop" + return 0 + fi + + load_state + + echo "" + echo -e "${BOLD}Full Development Loop Status${NC}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -e "Phase: ${CYAN}$CURRENT_PHASE${NC}" + echo -e "Started: $STARTED_AT" + echo -e "PR: ${PR_NUMBER:-none}" + echo "" + echo "Prompt:" + echo "$SAVED_PROMPT" | head -5 + echo "" + + return 0 } cmd_cancel() { - if ! is_loop_active; then - print_warning "No active loop to cancel" - return 0 - fi - - # Kill background process if running - local pid_file="${STATE_DIR}/full-loop.pid" - if [[ -f "$pid_file" ]]; then - local pid - pid=$(cat "$pid_file") - if kill -0 "$pid" 2>/dev/null; then - print_info "Stopping background process (PID: $pid)..." - kill "$pid" 2>/dev/null || true - sleep 1 - kill -9 "$pid" 2>/dev/null || true - fi - rm -f "$pid_file" - fi - - clear_state - - # Also cancel any sub-loops (both new and legacy locations) - rm -f ".agents/loop-state/ralph-loop.local.state" 2>/dev/null - rm -f ".agents/loop-state/quality-loop.local.state" 2>/dev/null - rm -f ".claude/ralph-loop.local.state" 2>/dev/null - rm -f ".claude/quality-loop.local.state" 2>/dev/null - - # Remove headless TODO.md guard if installed (t173) - remove_headless_todo_guard - - print_success "Full loop cancelled" - return 0 + if ! is_loop_active; then + print_warning "No active loop to cancel" + return 0 + fi + + # Kill background process if running + local pid_file="${STATE_DIR}/full-loop.pid" + if [[ -f "$pid_file" ]]; then + local pid + pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + print_info "Stopping background process (PID: $pid)..." + kill "$pid" 2>/dev/null || true + sleep 1 + kill -9 "$pid" 2>/dev/null || true + fi + rm -f "$pid_file" + fi + + clear_state + + # Also cancel any sub-loops (both new and legacy locations) + rm -f ".agents/loop-state/ralph-loop.local.state" 2>/dev/null + rm -f ".agents/loop-state/quality-loop.local.state" 2>/dev/null + rm -f ".claude/ralph-loop.local.state" 2>/dev/null + rm -f ".claude/quality-loop.local.state" 2>/dev/null + + # Remove headless TODO.md guard if installed (t173) + remove_headless_todo_guard + + print_success "Full loop cancelled" + return 0 } cmd_logs() { - local log_file="${STATE_DIR}/full-loop.log" - local lines="${1:-50}" - - if [[ ! -f "$log_file" ]]; then - print_warning "No log file found. Start a loop with --background first." - return 1 - fi - - # Check if background process is still running - local pid_file="${STATE_DIR}/full-loop.pid" - if [[ -f "$pid_file" ]]; then - local pid - pid=$(cat "$pid_file") - if kill -0 "$pid" 2>/dev/null; then - print_info "Background process running (PID: $pid)" - else - print_warning "Background process not running (was PID: $pid)" - fi - fi - - echo "" - echo -e "${BOLD}Full Loop Logs (last $lines lines)${NC}" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - tail -n "$lines" "$log_file" - - return 0 + local log_file="${STATE_DIR}/full-loop.log" + local lines="${1:-50}" + + if [[ ! -f "$log_file" ]]; then + print_warning "No log file found. Start a loop with --background first." + return 1 + fi + + # Check if background process is still running + local pid_file="${STATE_DIR}/full-loop.pid" + if [[ -f "$pid_file" ]]; then + local pid + pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + print_info "Background process running (PID: $pid)" + else + print_warning "Background process not running (was PID: $pid)" + fi + fi + + echo "" + echo -e "${BOLD}Full Loop Logs (last $lines lines)${NC}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + tail -n "$lines" "$log_file" + + return 0 } cmd_complete() { - echo "" - echo -e "${BOLD}${GREEN}╔════════════════════════════════════════════════════════════╗${NC}" - echo -e "${BOLD}${GREEN}║ FULL DEVELOPMENT LOOP - COMPLETE ║${NC}" - echo -e "${BOLD}${GREEN}╚════════════════════════════════════════════════════════════╝${NC}" - echo "" - - load_state 2>/dev/null || true - - echo -e "${GREEN}All phases completed successfully!${NC}" - echo "" - echo "Summary:" - echo " - Task: Implemented" - echo " - Preflight: Passed" - echo " - PR: #${PR_NUMBER:-unknown} merged" - echo " - Postflight: Healthy" - if is_aidevops_repo; then - echo " - Deploy: Complete" - fi - echo "" - - clear_state - - # Remove headless TODO.md guard if installed (t173) - remove_headless_todo_guard - - echo "FULL_LOOP_COMPLETE" - - return 0 + echo "" + echo -e "${BOLD}${GREEN}╔════════════════════════════════════════════════════════════╗${NC}" + echo -e "${BOLD}${GREEN}║ FULL DEVELOPMENT LOOP - COMPLETE ║${NC}" + echo -e "${BOLD}${GREEN}╚════════════════════════════════════════════════════════════╝${NC}" + echo "" + + load_state 2>/dev/null || true + + echo -e "${GREEN}All phases completed successfully!${NC}" + echo "" + echo "Summary:" + echo " - Task: Implemented" + echo " - Preflight: Passed" + echo " - PR: #${PR_NUMBER:-unknown} merged" + echo " - Postflight: Healthy" + if is_aidevops_repo; then + echo " - Deploy: Complete" + fi + echo "" + + clear_state + + # Remove headless TODO.md guard if installed (t173) + remove_headless_todo_guard + + echo "FULL_LOOP_COMPLETE" + + return 0 } show_help() { - cat << 'EOF' + cat <<'EOF' Full Development Loop Orchestrator Chains the complete development workflow from task to deployment. @@ -1095,7 +1122,7 @@ ENVIRONMENT: FULL_LOOP_HEADLESS=true Same as --headless flag EOF - return 0 + return 0 } # ============================================================================= @@ -1103,40 +1130,40 @@ EOF # ============================================================================= main() { - local command="${1:-help}" - shift || true - - case "$command" in - start) - cmd_start "$@" - ;; - resume) - cmd_resume - ;; - status) - cmd_status - ;; - cancel) - cmd_cancel - ;; - logs) - cmd_logs "$@" - ;; - _run_foreground) - # Internal command for background execution - cmd_run_foreground "$@" - ;; - help|--help|-h) - show_help - ;; - *) - print_error "Unknown command: $command" - show_help - return 1 - ;; - esac - - return 0 + local command="${1:-help}" + shift || true + + case "$command" in + start) + cmd_start "$@" + ;; + resume) + cmd_resume + ;; + status) + cmd_status + ;; + cancel) + cmd_cancel + ;; + logs) + cmd_logs "$@" + ;; + _run_foreground) + # Internal command for background execution + cmd_run_foreground "$@" + ;; + help | --help | -h) + show_help + ;; + *) + print_error "Unknown command: $command" + show_help + return 1 + ;; + esac + + return 0 } main "$@" diff --git a/.agents/scripts/supervisor-helper.sh b/.agents/scripts/supervisor-helper.sh index 972009ff0..cd0a0b180 100755 --- a/.agents/scripts/supervisor-helper.sh +++ b/.agents/scripts/supervisor-helper.sh @@ -4905,6 +4905,13 @@ fi This catches CI failures 5-10 min earlier. Do NOT push .sh files with ShellCheck violations. If \`shellcheck\` is not installed, skip this gate and note it in the PR body. +**3b. PR title MUST contain task ID (MANDATORY — t318.2)** +When creating a PR, the title MUST start with the task ID: \`: \`. +Example: \`t318.2: Verify supervisor worker PRs include task ID\` +The CI pipeline and supervisor both validate this. PRs without task IDs fail the check. +If you used \`gh pr create --draft --title ': '\` as instructed above, +this is already handled. This note reinforces: NEVER omit the task ID from the PR title. + **4. Offload research to Task sub-agents (saves context for implementation)** Reading large files (500+ lines) consumes your context budget fast. Instead of reading entire files yourself, spawn a Task sub-agent with a focused question: