diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index 02e46ab..8bda468 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -545,6 +545,10 @@ else cd "$WORKTREE" git checkout -B "$BRANCH" origin/master 2>/dev/null git submodule update --init --recursive 2>/dev/null || true + + # Write STATE.md entry — will be included in the first commit + write_state_entry "in-progress" + # Symlink lib node_modules from main repo (submodule init doesn't run npm install) for lib_dir in "$REPO_ROOT"/onchain/lib/*/; do lib_name=$(basename "$lib_dir") @@ -856,45 +860,32 @@ fi # ============================================================================= # STATE.MD APPEND # ============================================================================= -# After each merge, append one line to STATE.md describing what now exists. -# Format: - [YYYY-MM-DD] what is now true about harb (#PR) -# The planner will collapse this into a compact snapshot later. -append_state_log() { - local state_file="${REPO_ROOT}/STATE.md" +# Write STATE.md entry in the worktree BEFORE implementation starts. +# This ensures the STATE.md update is part of the first commit, not a separate +# push that would dismiss stale approvals. +# Format: - [YYYY-MM-DD] description (#ISSUE) +write_state_entry() { + local status_word="${1:-in-progress}" # "in-progress" or "done" + local target="${WORKTREE:-$REPO_ROOT}" + local state_file="${target}/STATE.md" local today today=$(date -u +%Y-%m-%d) - # Derive a "what now exists" description from the issue title. - # Strip prefixes (feat:/fix:/refactor:) to get the essence. local description description=$(echo "$ISSUE_TITLE" | sed 's/^feat:\s*//i;s/^fix:\s*//i;s/^refactor:\s*//i') - local line="- [${today}] ${description} (#${PR_NUMBER})" - - # Create file with header if it doesn't exist - if [ ! -f "$state_file" ]; then - echo "# STATE.md — What harb currently is and does" > "$state_file" - echo "" >> "$state_file" - fi - - echo "$line" >> "$state_file" - - # Append to STATE.md on the PR branch, push before merge - local worktree="${WORKTREE:-}" - local target="${worktree:-$REPO_ROOT}" - local state_file="${target}/STATE.md" + local line="- [${today}] [${status_word}] ${description} (#${ISSUE})" if [ ! -f "$state_file" ]; then printf '# STATE.md — What harb currently is and does\n\n' > "$state_file" fi echo "$line" >> "$state_file" + log "STATE.md: ${line}" +} - cd "$target" - git add STATE.md 2>/dev/null || true - git diff --cached --quiet && return 0 - git commit -m "state: ${description} (#${PR_NUMBER})" --no-verify 2>/dev/null || true - git push origin "${BRANCH}" 2>/dev/null || log "STATE.md push failed — will be missing from this merge" - log "STATE.md updated: ${line}" +# Alias for backward compat +append_state_log() { + write_state_entry "done" } # MERGE HELPER @@ -948,6 +939,10 @@ do_merge() { if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then log "PR #${PR_NUMBER} merged!" + # Update STATE.md on master (pull merged changes first) + (cd "$REPO_ROOT" && git checkout master 2>/dev/null && git pull --ff-only origin master 2>/dev/null) || true + append_state_log || log "WARNING: STATE.md update failed (non-fatal)" + curl -sf -X DELETE \ -H "Authorization: token ${CODEBERG_TOKEN}" \ "${API}/branches/${BRANCH}" >/dev/null 2>&1 || true diff --git a/dev/dev-poll.sh b/dev/dev-poll.sh index fb20191..0c5950a 100755 --- a/dev/dev-poll.sh +++ b/dev/dev-poll.sh @@ -150,7 +150,7 @@ if [ "$ORPHAN_COUNT" -gt 0 ]; then "${API}/pulls/${HAS_PR}/merge" \ -d '{"Do":"merge","delete_branch_after_merge":true}') - if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ] || [ "$MERGE_CODE" = "405" ]; then + if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ] ; then log "PR #${HAS_PR} merged! Closing #${ISSUE_NUM}" curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \ -H "Content-Type: application/json" \ @@ -186,6 +186,63 @@ if [ "$ORPHAN_COUNT" -gt 0 ]; then fi fi +# ============================================================================= +# PRIORITY 1.5: any open PR with REQUEST_CHANGES or CI failure (stuck PRs) +# ============================================================================= +log "checking for stuck PRs" +OPEN_PRS=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${API}/pulls?state=open&limit=20") + +for i in $(seq 0 $(($(echo "$OPEN_PRS" | jq 'length') - 1))); do + PR_NUM=$(echo "$OPEN_PRS" | jq -r ".[$i].number") + PR_BRANCH=$(echo "$OPEN_PRS" | jq -r ".[$i].head.ref") + PR_SHA=$(echo "$OPEN_PRS" | jq -r ".[$i].head.sha") + + # Extract issue number from branch name (fix/issue-NNN) + STUCK_ISSUE=$(echo "$PR_BRANCH" | grep -oP '(?<=fix/issue-)\d+' || true) + [ -z "$STUCK_ISSUE" ] && continue + + CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${API}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"') || true + HAS_CHANGES=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${API}/pulls/${PR_NUM}/reviews" | \ + jq -r '[.[] | select(.state == "REQUEST_CHANGES") | select(.stale == false)] | length') || true + HAS_APPROVE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \ + "${API}/pulls/${PR_NUM}/reviews" | \ + jq -r '[.[] | select(.state == "APPROVED") | select(.stale == false)] | length') || true + + # Try merge if approved + CI green + if [ "$CI_STATE" = "success" ] && [ "${HAS_APPROVE:-0}" -gt 0 ]; then + log "PR #${PR_NUM} (issue #${STUCK_ISSUE}) approved + CI green → merging" + MERGE_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H "Content-Type: application/json" \ + "${API}/pulls/${PR_NUM}/merge" \ + -d '{"Do":"merge","delete_branch_after_merge":true}') + if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ]; then + log "PR #${PR_NUM} merged! Closing #${STUCK_ISSUE}" + curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \ + -H "Content-Type: application/json" \ + "${API}/issues/${STUCK_ISSUE}" -d '{"state":"closed"}' >/dev/null 2>&1 || true + openclaw system event --text "✅ PR #${PR_NUM} merged! Issue #${STUCK_ISSUE} done." --mode now 2>/dev/null || true + fi + continue + fi + + # Stuck: REQUEST_CHANGES or CI failure → spawn agent + if [ "$CI_STATE" = "success" ] && [ "${HAS_CHANGES:-0}" -gt 0 ]; then + log "PR #${PR_NUM} (issue #${STUCK_ISSUE}) has REQUEST_CHANGES — fixing first" + nohup "${SCRIPT_DIR}/dev-agent.sh" "$STUCK_ISSUE" >> "$LOGFILE" 2>&1 & + log "started dev-agent PID $! for stuck PR #${PR_NUM}" + exit 0 + elif [ "$CI_STATE" = "failure" ] || [ "$CI_STATE" = "error" ]; then + log "PR #${PR_NUM} (issue #${STUCK_ISSUE}) CI failed — fixing first" + nohup "${SCRIPT_DIR}/dev-agent.sh" "$STUCK_ISSUE" >> "$LOGFILE" 2>&1 & + log "started dev-agent PID $! for stuck PR #${PR_NUM}" + exit 0 + fi +done + # ============================================================================= # PRIORITY 2: find ready backlog issues (pull system) # ============================================================================= @@ -236,7 +293,7 @@ for i in $(seq 0 $((BACKLOG_COUNT - 1))); do -H "Content-Type: application/json" \ "${API}/pulls/${EXISTING_PR}/merge" \ -d '{"Do":"merge","delete_branch_after_merge":true}') - if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ] || [ "$MERGE_CODE" = "405" ]; then + if [ "$MERGE_CODE" = "200" ] || [ "$MERGE_CODE" = "204" ] ; then log "PR #${EXISTING_PR} merged! Closing #${ISSUE_NUM}" curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \ -H "Content-Type: application/json" \