fix: feat: persistent Claude tmux session for reviewer (#78)

Rewrite review-pr.sh to use persistent tmux sessions instead of one-shot
claude -p. Re-reviews inject incremental diffs into the same session so
Claude remembers what it flagged and can verify fixes were addressed.

- review-pr.sh: tmux session review-{project}-{pr}, phase protocol
  (PHASE:review_complete), JSON output via file, retry on invalid JSON
- review-poll.sh: session lifecycle cleanup (merged/closed PRs, 4h idle)
- matrix_listener.sh: route human questions to review sessions via
  /tmp/review-thread-map

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-17 23:56:04 +00:00
parent 5ec587a7b6
commit 85d05cdee2
3 changed files with 263 additions and 100 deletions

View file

@ -145,6 +145,36 @@ while true; do
printf '%s\t%s\t%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$SENDER" "$BODY" >> /tmp/dev-escalation-reply
matrix_send "dev" "✓ received, will inject on next poll" "$THREAD_ROOT" >/dev/null 2>&1 || true
;;
review)
# Route human questions to persistent review tmux session
REVIEW_PR_NUM=$(awk -F'\t' -v id="$THREAD_ROOT" '$1 == id {print $2}' /tmp/review-thread-map 2>/dev/null || true)
if [ -n "$REVIEW_PR_NUM" ]; then
REVIEW_SESSION="review-${PROJECT_NAME}-${REVIEW_PR_NUM}"
if tmux has-session -t "$REVIEW_SESSION" 2>/dev/null; then
REVIEW_INJECT_MSG="Human question from ${SENDER} in Matrix:
${BODY}
Please answer this question about your review. Explain your reasoning."
REVIEW_INJECT_TMP=$(mktemp /tmp/review-q-inject-XXXXXX)
printf '%s' "$REVIEW_INJECT_MSG" > "$REVIEW_INJECT_TMP"
tmux load-buffer -b "review-q-${REVIEW_PR_NUM}" "$REVIEW_INJECT_TMP" || true
tmux paste-buffer -t "$REVIEW_SESSION" -b "review-q-${REVIEW_PR_NUM}" || true
sleep 0.5
tmux send-keys -t "$REVIEW_SESSION" "" Enter || true
tmux delete-buffer -b "review-q-${REVIEW_PR_NUM}" 2>/dev/null || true
rm -f "$REVIEW_INJECT_TMP"
log "review question from ${SENDER} injected into ${REVIEW_SESSION}"
matrix_send "review" "✓ question forwarded to reviewer session" "$THREAD_ROOT" >/dev/null 2>&1 || true
else
log "review session ${REVIEW_SESSION} not found for PR #${REVIEW_PR_NUM}"
matrix_send "review" "review session not active for PR #${REVIEW_PR_NUM}" "$THREAD_ROOT" >/dev/null 2>&1 || true
fi
else
log "review thread ${THREAD_ROOT:0:20} has no PR mapping"
matrix_send "review" "review session not available" "$THREAD_ROOT" >/dev/null 2>&1 || true
fi
;;
vault)
# Parse APPROVE <id> or REJECT <id> from reply
VAULT_CMD=$(echo "$BODY" | tr '[:lower:]' '[:upper:]' | grep -oP '^\s*(APPROVE|REJECT)\s+\S+' | head -1 || true)