From 6ed03cfc695b46cc6c1e29dfb102f467e7f5de6e Mon Sep 17 00:00:00 2001 From: m3taversal Date: Sun, 8 Mar 2026 19:46:43 +0000 Subject: [PATCH] Auto: ops/auto-fix-trigger.sh | 1 file changed, 290 insertions(+) --- ops/auto-fix-trigger.sh | 290 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 ops/auto-fix-trigger.sh diff --git a/ops/auto-fix-trigger.sh b/ops/auto-fix-trigger.sh new file mode 100644 index 0000000..9ffaa21 --- /dev/null +++ b/ops/auto-fix-trigger.sh @@ -0,0 +1,290 @@ +#!/usr/bin/env bash +# auto-fix-trigger.sh — Find PRs with requested changes, auto-fix mechanical issues. +# +# Two-tier response to review feedback: +# 1. AUTO-FIX: Broken wiki links, missing frontmatter fields, schema compliance +# 2. FLAG: Domain classification, claim reframing, confidence changes → notify proposer +# +# Mechanical issues are fixed by a headless Claude agent on the PR branch. +# New commits trigger re-review on the next evaluate-trigger.sh cron cycle. +# +# Usage: +# ./ops/auto-fix-trigger.sh # fix all PRs with requested changes +# ./ops/auto-fix-trigger.sh 66 # fix a specific PR +# ./ops/auto-fix-trigger.sh --dry-run # show what would be fixed, don't run +# +# Requirements: +# - claude CLI (claude -p for headless mode) +# - gh CLI authenticated with repo access +# - Run from the teleo-codex repo root +# +# Safety: +# - Lockfile prevents concurrent runs (separate from evaluate-trigger) +# - Only fixes mechanical issues — never changes claim substance +# - Max one fix cycle per PR per run (prevents infinite loops) +# - Tracks fix attempts to avoid re-fixing already-attempted PRs + +set -euo pipefail + +# Allow nested Claude Code sessions +unset CLAUDECODE 2>/dev/null || true + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_ROOT" + +LOCKFILE="/tmp/auto-fix-trigger.lock" +LOG_DIR="$REPO_ROOT/ops/sessions" +TIMEOUT_SECONDS=300 # 5 min — fixes should be fast +DRY_RUN=false +SPECIFIC_PR="" +FIX_MARKER="" + +# --- Parse arguments --- +for arg in "$@"; do + case "$arg" in + --dry-run) DRY_RUN=true ;; + [0-9]*) SPECIFIC_PR="$arg" ;; + --help|-h) + head -20 "$0" | tail -18 + exit 0 + ;; + *) + echo "Unknown argument: $arg" + exit 1 + ;; + esac +done + +# --- Pre-flight checks --- +if ! gh auth status >/dev/null 2>&1; then + echo "ERROR: gh CLI not authenticated." + exit 1 +fi + +if ! command -v claude >/dev/null 2>&1; then + echo "ERROR: claude CLI not found." + exit 1 +fi + +# --- Lockfile --- +if [ -f "$LOCKFILE" ]; then + LOCK_PID=$(cat "$LOCKFILE" 2>/dev/null || echo "") + if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then + echo "Another auto-fix-trigger is running (PID $LOCK_PID). Exiting." + exit 1 + else + rm -f "$LOCKFILE" + fi +fi +echo $$ > "$LOCKFILE" +trap 'rm -f "$LOCKFILE"' EXIT + +mkdir -p "$LOG_DIR" + +# --- Find PRs needing fixes --- +if [ -n "$SPECIFIC_PR" ]; then + PRS_TO_FIX="$SPECIFIC_PR" +else + OPEN_PRS=$(gh pr list --state open --json number --jq '.[].number' 2>/dev/null || echo "") + + if [ -z "$OPEN_PRS" ]; then + echo "No open PRs found." + exit 0 + fi + + PRS_TO_FIX="" + for pr in $OPEN_PRS; do + # Check if PR has request_changes reviews + HAS_CHANGES_REQUESTED=$(gh api "repos/{owner}/{repo}/pulls/$pr/reviews" \ + --jq '[.[] | select(.state == "CHANGES_REQUESTED")] | length' 2>/dev/null || echo "0") + + if [ "$HAS_CHANGES_REQUESTED" -eq 0 ]; then + continue + fi + + # Check if auto-fix was already attempted (marker comment exists) + ALREADY_ATTEMPTED=$(gh pr view "$pr" --json comments \ + --jq "[.comments[].body | select(contains(\"$FIX_MARKER\"))] | length" 2>/dev/null || echo "0") + + # Check if there are new commits since the last auto-fix attempt + if [ "$ALREADY_ATTEMPTED" -gt 0 ]; then + LAST_FIX_DATE=$(gh pr view "$pr" --json comments \ + --jq "[.comments[] | select(.body | contains(\"$FIX_MARKER\")) | .createdAt] | last" 2>/dev/null || echo "") + LAST_COMMIT_DATE=$(gh pr view "$pr" --json commits --jq '.commits[-1].committedDate' 2>/dev/null || echo "") + + if [ -n "$LAST_FIX_DATE" ] && [ -n "$LAST_COMMIT_DATE" ] && [[ "$LAST_COMMIT_DATE" < "$LAST_FIX_DATE" ]]; then + echo "PR #$pr: Auto-fix already attempted, no new commits. Skipping." + continue + fi + fi + + PRS_TO_FIX="$PRS_TO_FIX $pr" + done + + PRS_TO_FIX=$(echo "$PRS_TO_FIX" | xargs) + + if [ -z "$PRS_TO_FIX" ]; then + echo "No PRs need auto-fixing." + exit 0 + fi +fi + +echo "PRs to auto-fix: $PRS_TO_FIX" + +if [ "$DRY_RUN" = true ]; then + for pr in $PRS_TO_FIX; do + echo "[DRY RUN] Would attempt auto-fix on PR #$pr" + # Show the review feedback summary + gh pr view "$pr" --json comments \ + --jq '.comments[] | select(.body | test("Verdict.*request_changes|request changes"; "i")) | .body' 2>/dev/null \ + | grep -iE "broken|missing|schema|field|link" | head -10 || echo " (no mechanical issues detected in comments)" + done + exit 0 +fi + +# --- Auto-fix each PR --- +FIXED=0 +FLAGGED=0 + +for pr in $PRS_TO_FIX; do + echo "" + echo "=== Auto-fix PR #$pr ===" + + # Get the review feedback + REVIEW_TEXT=$(gh pr view "$pr" --json comments \ + --jq '.comments[].body' 2>/dev/null || echo "") + + if [ -z "$REVIEW_TEXT" ]; then + echo " No review comments found. Skipping." + continue + fi + + # Classify issues as mechanical vs substantive + # Mechanical: broken links, missing fields, schema compliance + MECHANICAL_PATTERNS="broken wiki link|broken link|missing.*challenged_by|missing.*field|schema compliance|link.*needs to match|link text needs|missing wiki.link|add.*wiki.link|BROKEN WIKI LINK" + # Substantive: domain classification, reframing, confidence, consider + SUBSTANTIVE_PATTERNS="domain classification|consider.*reframing|soften.*to|confidence.*recalibrat|consider whether|territory violation|evaluator-as-proposer|conflict.of.interest" + + HAS_MECHANICAL=$(echo "$REVIEW_TEXT" | grep -ciE "$MECHANICAL_PATTERNS" || echo "0") + HAS_SUBSTANTIVE=$(echo "$REVIEW_TEXT" | grep -ciE "$SUBSTANTIVE_PATTERNS" || echo "0") + + echo " Mechanical issues: $HAS_MECHANICAL" + echo " Substantive issues: $HAS_SUBSTANTIVE" + + # --- Handle mechanical fixes --- + if [ "$HAS_MECHANICAL" -gt 0 ]; then + echo " Attempting mechanical auto-fix..." + + # Extract just the mechanical feedback lines for the fix agent + MECHANICAL_FEEDBACK=$(echo "$REVIEW_TEXT" | grep -iE "$MECHANICAL_PATTERNS" | head -20) + + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + FIX_LOG="$LOG_DIR/autofix-pr${pr}-${TIMESTAMP}.log" + + PR_BRANCH=$(gh pr view "$pr" --json headRefName --jq '.headRefName' 2>/dev/null || echo "") + + FIX_PROMPT="You are a mechanical fix agent. Your ONLY job is to fix objective, mechanical issues in PR #${pr}. + +RULES: +- Fix ONLY broken wiki links, missing frontmatter fields, and schema compliance issues. +- NEVER change claim titles, arguments, confidence levels, or domain classification. +- NEVER add new claims or remove existing ones. +- NEVER rewrite prose or change the substance of any argument. +- If you're unsure whether something is mechanical, SKIP IT. + +STEPS: +1. Run: gh pr checkout ${pr} +2. Read the review feedback below to understand what needs fixing. +3. For each mechanical issue: + a. BROKEN WIKI LINKS: Find the correct filename with Glob, update the [[link]] text to match exactly. + b. MISSING challenged_by: If a claim is rated 'likely' or higher and reviewers noted missing challenged_by, + add a challenged_by field to the frontmatter. Use the counter-argument already mentioned in the claim body. + c. MISSING WIKI LINKS: If reviewers named specific claims that should be linked, verify the file exists + with Glob, then add to the Relevant Notes section. +4. Stage and commit changes: + git add -A + git commit -m 'auto-fix: mechanical fixes from review feedback + + - What was fixed (list each fix) + + Auto-Fix-Agent: teleo-eval-orchestrator' +5. Push: git push origin ${PR_BRANCH} + +REVIEW FEEDBACK (fix only the mechanical issues): +${MECHANICAL_FEEDBACK} + +FULL REVIEW CONTEXT: +$(echo "$REVIEW_TEXT" | head -200) + +Work autonomously. Do not ask for confirmation. If there's nothing mechanical to fix, just exit." + + if perl -e "alarm $TIMEOUT_SECONDS; exec @ARGV" claude -p \ + --model "sonnet" \ + --allowedTools "Read,Write,Edit,Bash,Glob,Grep" \ + --permission-mode bypassPermissions \ + "$FIX_PROMPT" \ + > "$FIX_LOG" 2>&1; then + echo " Auto-fix agent completed." + + # Check if any commits were actually pushed + NEW_COMMIT_DATE=$(gh pr view "$pr" --json commits --jq '.commits[-1].committedDate' 2>/dev/null || echo "") + echo " Latest commit: $NEW_COMMIT_DATE" + FIXED=$((FIXED + 1)) + else + EXIT_CODE=$? + if [ "$EXIT_CODE" -eq 142 ] || [ "$EXIT_CODE" -eq 124 ]; then + echo " Auto-fix: TIMEOUT after ${TIMEOUT_SECONDS}s." + else + echo " Auto-fix: FAILED (exit code $EXIT_CODE)." + fi + fi + + echo " Log: $FIX_LOG" + fi + + # --- Flag substantive issues to proposer --- + if [ "$HAS_SUBSTANTIVE" -gt 0 ]; then + echo " Flagging substantive issues for proposer..." + + SUBSTANTIVE_FEEDBACK=$(echo "$REVIEW_TEXT" | grep -iE "$SUBSTANTIVE_PATTERNS" | head -15) + + # Determine proposer from branch name + PROPOSER=$(gh pr view "$pr" --json headRefName --jq '.headRefName' 2>/dev/null | cut -d'/' -f1) + + FLAG_COMMENT="## Substantive Feedback — Needs Proposer Input + +The following review feedback requires the proposer's judgment and cannot be auto-fixed: + +\`\`\` +${SUBSTANTIVE_FEEDBACK} +\`\`\` + +**Proposer:** ${PROPOSER} +**Action needed:** Review the feedback above, make changes if you agree, then push to trigger re-review. + +$FIX_MARKER +*Auto-fix agent — mechanical issues were ${HAS_MECHANICAL:+addressed}${HAS_MECHANICAL:-not found}, substantive issues flagged for human/agent review.*" + + gh pr comment "$pr" --body "$FLAG_COMMENT" 2>/dev/null + echo " Flagged to proposer: $PROPOSER" + FLAGGED=$((FLAGGED + 1)) + elif [ "$HAS_MECHANICAL" -gt 0 ]; then + # Only mechanical issues — post marker comment so we don't re-attempt + MARKER_COMMENT="$FIX_MARKER +*Auto-fix agent ran — mechanical fixes attempted. Substantive issues: none. Awaiting re-review.*" + gh pr comment "$pr" --body "$MARKER_COMMENT" 2>/dev/null + fi + + # Clean up branch + git checkout main 2>/dev/null || git checkout -f main + PR_BRANCH=$(gh pr view "$pr" --json headRefName --jq '.headRefName' 2>/dev/null || echo "") + [ -n "$PR_BRANCH" ] && git branch -D "$PR_BRANCH" 2>/dev/null || true + + echo " Done." +done + +echo "" +echo "=== Auto-Fix Summary ===" +echo "Fixed: $FIXED" +echo "Flagged: $FLAGGED" +echo "Logs: $LOG_DIR"