@jonit-dev/night-watch-cli 1.8.12-beta.1 → 1.8.12-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +3729 -2027
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts +12 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +307 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/analytics.d.ts.map +1 -1
- package/dist/commands/analytics.js +60 -0
- package/dist/commands/analytics.js.map +1 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +45 -0
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/merge.js +30 -3
- package/dist/commands/merge.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +45 -0
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/qa.d.ts.map +1 -1
- package/dist/commands/qa.js +24 -0
- package/dist/commands/qa.js.map +1 -1
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/queue.js +20 -0
- package/dist/commands/queue.js.map +1 -1
- package/dist/commands/resolve.d.ts.map +1 -1
- package/dist/commands/resolve.js +26 -0
- package/dist/commands/resolve.js.map +1 -1
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +46 -1
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts +16 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +85 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/shared/env-builder.d.ts.map +1 -1
- package/dist/commands/shared/env-builder.js +1 -0
- package/dist/commands/shared/env-builder.js.map +1 -1
- package/dist/commands/shared/feedback.d.ts +24 -0
- package/dist/commands/shared/feedback.d.ts.map +1 -0
- package/dist/commands/shared/feedback.js +38 -0
- package/dist/commands/shared/feedback.js.map +1 -0
- package/dist/commands/slice.d.ts.map +1 -1
- package/dist/commands/slice.js +48 -1
- package/dist/commands/slice.js.map +1 -1
- package/dist/scripts/night-watch-audit-cron.sh +1 -0
- package/dist/scripts/night-watch-cron.sh +186 -23
- package/dist/scripts/night-watch-helpers.sh +65 -2
- package/dist/scripts/night-watch-merger-cron.sh +210 -36
- package/dist/scripts/night-watch-plan-cron.sh +2 -0
- package/dist/scripts/night-watch-pr-resolver-cron.sh +6 -0
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +96 -5
- package/dist/scripts/night-watch-qa-cron.sh +8 -1
- package/dist/scripts/night-watch-slicer-cron.sh +3 -0
- package/dist/templates/night-watch-pr-reviewer.md +7 -6
- package/dist/templates/night-watch.config.json +21 -0
- package/dist/templates/pr-reviewer.md +7 -6
- package/dist/templates/slicer.md +3 -0
- package/dist/web/assets/index-B6E6kOoR.js +406 -0
- package/dist/web/assets/index-C-xpWpS8.css +1 -0
- package/dist/web/assets/index-CEYe-290.js +412 -0
- package/dist/web/assets/index-DIMUXIP8.css +1 -0
- package/dist/web/assets/index-DpvzoXEv.js +442 -0
- package/dist/web/assets/index-DyME41HV.css +1 -0
- package/dist/web/assets/index-NR27JE3b.js +406 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -1
|
@@ -14,6 +14,8 @@ set -euo pipefail
|
|
|
14
14
|
# NW_MERGER_BRANCH_PATTERNS= - Comma-separated branch prefixes (empty = all)
|
|
15
15
|
# NW_MERGER_REBASE_BEFORE_MERGE=1 - Set to 1 to rebase before merging
|
|
16
16
|
# NW_MERGER_MAX_PRS_PER_RUN=0 - Max PRs to merge per run (0 = unlimited)
|
|
17
|
+
# NW_MERGER_CI_MAX_WAIT=300 - Max seconds to wait for checks after rebase
|
|
18
|
+
# NW_MERGER_CI_POLL_INTERVAL=15 - Seconds between check polls after rebase
|
|
17
19
|
# NW_DRY_RUN=0 - Set to 1 for dry-run mode
|
|
18
20
|
|
|
19
21
|
PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
|
|
@@ -26,7 +28,10 @@ MERGE_METHOD="${NW_MERGER_MERGE_METHOD:-squash}"
|
|
|
26
28
|
MIN_REVIEW_SCORE="${NW_MERGER_MIN_REVIEW_SCORE:-80}"
|
|
27
29
|
REBASE_BEFORE_MERGE="${NW_MERGER_REBASE_BEFORE_MERGE:-1}"
|
|
28
30
|
MAX_PRS_PER_RUN="${NW_MERGER_MAX_PRS_PER_RUN:-0}"
|
|
31
|
+
CI_MAX_WAIT="${NW_MERGER_CI_MAX_WAIT:-300}"
|
|
32
|
+
CI_POLL_INTERVAL="${NW_MERGER_CI_POLL_INTERVAL:-15}"
|
|
29
33
|
BRANCH_PATTERNS_RAW="${NW_MERGER_BRANCH_PATTERNS:-}"
|
|
34
|
+
READY_TO_MERGE_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
|
|
30
35
|
SCRIPT_START_TIME=$(date +%s)
|
|
31
36
|
DRY_RUN="${NW_DRY_RUN:-0}"
|
|
32
37
|
PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
|
|
@@ -39,6 +44,12 @@ fi
|
|
|
39
44
|
if ! [[ "${MIN_REVIEW_SCORE}" =~ ^[0-9]+$ ]]; then
|
|
40
45
|
MIN_REVIEW_SCORE="80"
|
|
41
46
|
fi
|
|
47
|
+
if ! [[ "${CI_MAX_WAIT}" =~ ^[0-9]+$ ]]; then
|
|
48
|
+
CI_MAX_WAIT="300"
|
|
49
|
+
fi
|
|
50
|
+
if ! [[ "${CI_POLL_INTERVAL}" =~ ^[0-9]+$ ]] || [ "${CI_POLL_INTERVAL}" = "0" ]; then
|
|
51
|
+
CI_POLL_INTERVAL="15"
|
|
52
|
+
fi
|
|
42
53
|
# Clamp merge method to valid values
|
|
43
54
|
case "${MERGE_METHOD}" in
|
|
44
55
|
squash|merge|rebase) ;;
|
|
@@ -55,6 +66,7 @@ PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
|
55
66
|
# NOTE: Lock file path must match mergerLockPath() in src/utils/status-data.ts
|
|
56
67
|
LOCK_FILE="/tmp/night-watch-merger-${PROJECT_RUNTIME_KEY}.lock"
|
|
57
68
|
SCRIPT_TYPE="merger"
|
|
69
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
58
70
|
|
|
59
71
|
MERGED_PRS=0
|
|
60
72
|
FAILED_PRS=0
|
|
@@ -103,29 +115,150 @@ get_review_score() {
|
|
|
103
115
|
# Look for review score comment from night-watch
|
|
104
116
|
local score
|
|
105
117
|
score=$(gh pr view "${pr_number}" --json comments \
|
|
106
|
-
--jq '[.comments[].body | select(test("
|
|
118
|
+
--jq '[.comments[].body | select(test("score[^0-9]{0,30}[0-9]+/100"; "i")) | capture("(?i)score[^0-9]{0,30}(?<s>[0-9]+)/100") | .s] | last | tonumber // -1' \
|
|
107
119
|
2>/dev/null || echo "-1")
|
|
108
120
|
echo "${score}"
|
|
109
121
|
}
|
|
110
122
|
|
|
111
|
-
#
|
|
112
|
-
|
|
123
|
+
# Count pending review feedback that should send the PR back through reviewer
|
|
124
|
+
# instead of allowing the merger to land it. We combine GitHub's review decision
|
|
125
|
+
# with unresolved review threads because some providers leave actionable feedback
|
|
126
|
+
# as thread comments without lowering the score.
|
|
127
|
+
get_pending_review_feedback_count() {
|
|
113
128
|
local pr_number="${1}"
|
|
114
|
-
local
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
local
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
local review_decision=""
|
|
130
|
+
local change_request_count="0"
|
|
131
|
+
local unresolved_thread_count="0"
|
|
132
|
+
local repo=""
|
|
133
|
+
local owner=""
|
|
134
|
+
local name=""
|
|
135
|
+
|
|
136
|
+
review_decision=$(gh pr view "${pr_number}" --json reviewDecision --jq '.reviewDecision // ""' 2>/dev/null || echo "")
|
|
137
|
+
|
|
138
|
+
change_request_count=$(gh api "repos/{owner}/{repo}/pulls/${pr_number}/reviews" \
|
|
139
|
+
--jq '[.[] | select(.state == "CHANGES_REQUESTED")] | length' 2>/dev/null || echo "0")
|
|
140
|
+
if ! [[ "${change_request_count}" =~ ^[0-9]+$ ]]; then
|
|
141
|
+
change_request_count="0"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
repo=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
|
|
145
|
+
if [[ "${repo}" == */* ]]; then
|
|
146
|
+
owner="${repo%%/*}"
|
|
147
|
+
name="${repo#*/}"
|
|
148
|
+
unresolved_thread_count=$(gh api graphql \
|
|
149
|
+
-F owner="${owner}" \
|
|
150
|
+
-F name="${name}" \
|
|
151
|
+
-F number="${pr_number}" \
|
|
152
|
+
-f query='
|
|
153
|
+
query($owner: String!, $name: String!, $number: Int!) {
|
|
154
|
+
repository(owner: $owner, name: $name) {
|
|
155
|
+
pullRequest(number: $number) {
|
|
156
|
+
reviewThreads(first: 100) {
|
|
157
|
+
nodes {
|
|
158
|
+
isResolved
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
' \
|
|
165
|
+
--jq '[.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved == false)] | length' \
|
|
166
|
+
2>/dev/null || echo "0")
|
|
167
|
+
if ! [[ "${unresolved_thread_count}" =~ ^[0-9]+$ ]]; then
|
|
168
|
+
unresolved_thread_count="0"
|
|
169
|
+
fi
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
if [ "${unresolved_thread_count}" -gt 0 ]; then
|
|
173
|
+
echo "${unresolved_thread_count}"
|
|
174
|
+
return 0
|
|
121
175
|
fi
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return 1
|
|
176
|
+
|
|
177
|
+
if [ "${change_request_count}" -gt 0 ]; then
|
|
178
|
+
echo "${change_request_count}"
|
|
179
|
+
return 0
|
|
127
180
|
fi
|
|
128
|
-
|
|
181
|
+
|
|
182
|
+
if [ "${review_decision}" = "CHANGES_REQUESTED" ]; then
|
|
183
|
+
echo "1"
|
|
184
|
+
return 0
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
echo "0"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Get the current head OID for a PR.
|
|
191
|
+
get_pr_head_oid() {
|
|
192
|
+
local pr_number="${1}"
|
|
193
|
+
gh pr view "${pr_number}" --json headRefOid --jq '.headRefOid // ""' 2>/dev/null || echo ""
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Return the CI state for the PR's status rollup on the expected head OID.
|
|
197
|
+
ci_status_for_head() {
|
|
198
|
+
local pr_number="${1}"
|
|
199
|
+
local expected_head="${2:-}"
|
|
200
|
+
local status_json
|
|
201
|
+
|
|
202
|
+
status_json=$(gh pr view "${pr_number}" --json headRefOid,statusCheckRollup 2>/dev/null || echo "")
|
|
203
|
+
if [ -z "${status_json}" ]; then
|
|
204
|
+
echo "unknown"
|
|
205
|
+
return 0
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
echo "${status_json}" | jq -r --arg expected_head "${expected_head}" '
|
|
209
|
+
def in_list($values): . as $value | $values | index($value);
|
|
210
|
+
def check_run_failure:
|
|
211
|
+
.__typename == "CheckRun"
|
|
212
|
+
and ((.conclusion // "") | in_list(["FAILURE", "TIMED_OUT", "CANCELLED", "ACTION_REQUIRED", "STALE", "STARTUP_FAILURE"]));
|
|
213
|
+
def status_context_failure:
|
|
214
|
+
.__typename == "StatusContext"
|
|
215
|
+
and ((.state // "") | in_list(["FAILURE", "ERROR"]));
|
|
216
|
+
def check_run_pending:
|
|
217
|
+
.__typename == "CheckRun"
|
|
218
|
+
and ((.status // "") != "COMPLETED" or .conclusion == null);
|
|
219
|
+
def status_context_pending:
|
|
220
|
+
.__typename == "StatusContext"
|
|
221
|
+
and ((.state // "") | in_list(["PENDING", "EXPECTED"]));
|
|
222
|
+
def check_run_nonpassing:
|
|
223
|
+
.__typename == "CheckRun"
|
|
224
|
+
and ((.conclusion // "") | in_list(["SUCCESS", "NEUTRAL", "SKIPPED"]) | not);
|
|
225
|
+
def status_context_nonpassing:
|
|
226
|
+
.__typename == "StatusContext"
|
|
227
|
+
and (.state // "") != "SUCCESS";
|
|
228
|
+
if ($expected_head != "" and (.headRefOid // "") != $expected_head) then
|
|
229
|
+
"head_mismatch"
|
|
230
|
+
elif ((.statusCheckRollup // []) | length) == 0 then
|
|
231
|
+
"absent"
|
|
232
|
+
elif any((.statusCheckRollup // [])[]; check_run_failure or status_context_failure) then
|
|
233
|
+
"failed"
|
|
234
|
+
elif any((.statusCheckRollup // [])[]; check_run_pending or status_context_pending) then
|
|
235
|
+
"pending"
|
|
236
|
+
elif any((.statusCheckRollup // [])[]; check_run_nonpassing or status_context_nonpassing) then
|
|
237
|
+
"failed"
|
|
238
|
+
else
|
|
239
|
+
"passing"
|
|
240
|
+
end
|
|
241
|
+
' 2>/dev/null || echo "unknown"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
wait_for_ci_passing_on_head() {
|
|
245
|
+
local pr_number="${1}"
|
|
246
|
+
local expected_head="${2}"
|
|
247
|
+
local ci_waited=0
|
|
248
|
+
LAST_CI_STATUS="unknown"
|
|
249
|
+
|
|
250
|
+
while [ "${ci_waited}" -lt "${CI_MAX_WAIT}" ]; do
|
|
251
|
+
LAST_CI_STATUS=$(ci_status_for_head "${pr_number}" "${expected_head}")
|
|
252
|
+
if [ "${LAST_CI_STATUS}" = "passing" ]; then
|
|
253
|
+
return 0
|
|
254
|
+
fi
|
|
255
|
+
log "INFO: PR #${pr_number}: Waiting for fresh CI on head ${expected_head} (${LAST_CI_STATUS}, ${ci_waited}s/${CI_MAX_WAIT}s)..."
|
|
256
|
+
sleep "${CI_POLL_INTERVAL}"
|
|
257
|
+
ci_waited=$((ci_waited + CI_POLL_INTERVAL))
|
|
258
|
+
done
|
|
259
|
+
|
|
260
|
+
LAST_CI_STATUS=$(ci_status_for_head "${pr_number}" "${expected_head}")
|
|
261
|
+
[ "${LAST_CI_STATUS}" = "passing" ]
|
|
129
262
|
}
|
|
130
263
|
|
|
131
264
|
# Rebase a PR against its base branch
|
|
@@ -140,6 +273,23 @@ rebase_pr() {
|
|
|
140
273
|
return $?
|
|
141
274
|
}
|
|
142
275
|
|
|
276
|
+
cleanup_watchdog() {
|
|
277
|
+
local pid="${1:-}"
|
|
278
|
+
local child_pids=""
|
|
279
|
+
|
|
280
|
+
if [ -z "${pid}" ]; then
|
|
281
|
+
return 0
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
child_pids=$(pgrep -P "${pid}" 2>/dev/null || true)
|
|
285
|
+
if [ -n "${child_pids}" ]; then
|
|
286
|
+
kill ${child_pids} 2>/dev/null || true
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
kill "${pid}" 2>/dev/null || true
|
|
290
|
+
wait "${pid}" 2>/dev/null || true
|
|
291
|
+
}
|
|
292
|
+
|
|
143
293
|
log() {
|
|
144
294
|
echo "[$(date '+%Y-%m-%dT%H:%M:%S')] $*" | tee -a "${LOG_FILE}"
|
|
145
295
|
}
|
|
@@ -158,7 +308,7 @@ cd "${PROJECT_DIR}"
|
|
|
158
308
|
|
|
159
309
|
log "========================================"
|
|
160
310
|
log "RUN-START: merger invoked project=${PROJECT_DIR} dry_run=${DRY_RUN}"
|
|
161
|
-
log "CONFIG: merge_method=${MERGE_METHOD} min_review_score=${MIN_REVIEW_SCORE} rebase_before_merge=${REBASE_BEFORE_MERGE} max_prs=${MAX_PRS_PER_RUN} max_runtime=${MAX_RUNTIME}s branch_patterns=${BRANCH_PATTERNS_RAW:-<all>}"
|
|
311
|
+
log "CONFIG: merge_method=${MERGE_METHOD} min_review_score=${MIN_REVIEW_SCORE} rebase_before_merge=${REBASE_BEFORE_MERGE} max_prs=${MAX_PRS_PER_RUN} max_runtime=${MAX_RUNTIME}s ready_label=${READY_TO_MERGE_LABEL} branch_patterns=${BRANCH_PATTERNS_RAW:-<all>}"
|
|
162
312
|
log "========================================"
|
|
163
313
|
|
|
164
314
|
if ! acquire_lock "${LOCK_FILE}"; then
|
|
@@ -187,7 +337,7 @@ fi
|
|
|
187
337
|
kill -TERM $$ 2>/dev/null || true
|
|
188
338
|
) &
|
|
189
339
|
WATCHDOG_PID=$!
|
|
190
|
-
append_exit_trap "
|
|
340
|
+
append_exit_trap "cleanup_watchdog ${WATCHDOG_PID}"
|
|
191
341
|
|
|
192
342
|
# Discover open PRs sorted by creation date (oldest first = FIFO)
|
|
193
343
|
log "INFO: Scanning open PRs..."
|
|
@@ -224,15 +374,27 @@ while IFS= read -r pr_json; do
|
|
|
224
374
|
continue
|
|
225
375
|
fi
|
|
226
376
|
|
|
377
|
+
if csv_has_label "${pr_labels:-}" "${READY_TO_MERGE_LABEL}"; then
|
|
378
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Skipping PR labeled ${READY_TO_MERGE_LABEL}"
|
|
379
|
+
continue
|
|
380
|
+
fi
|
|
381
|
+
|
|
227
382
|
# Check branch pattern filter
|
|
228
383
|
if ! matches_branch_patterns "${pr_branch}"; then
|
|
229
384
|
log "DEBUG: PR #${pr_number} (${pr_branch}): Branch pattern mismatch, skipping"
|
|
230
385
|
continue
|
|
231
386
|
fi
|
|
232
387
|
|
|
388
|
+
pr_head_oid=$(get_pr_head_oid "${pr_number}")
|
|
389
|
+
if [ -z "${pr_head_oid}" ]; then
|
|
390
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Unable to determine PR head, skipping"
|
|
391
|
+
continue
|
|
392
|
+
fi
|
|
393
|
+
|
|
233
394
|
# Check CI status
|
|
234
|
-
|
|
235
|
-
|
|
395
|
+
ci_status=$(ci_status_for_head "${pr_number}" "${pr_head_oid}")
|
|
396
|
+
if [ "${ci_status}" != "passing" ]; then
|
|
397
|
+
log "INFO: PR #${pr_number} (${pr_branch}): CI not passing on head ${pr_head_oid} (${ci_status}), skipping"
|
|
236
398
|
continue
|
|
237
399
|
fi
|
|
238
400
|
|
|
@@ -240,15 +402,22 @@ while IFS= read -r pr_json; do
|
|
|
240
402
|
if [ "${MIN_REVIEW_SCORE}" -gt "0" ]; then
|
|
241
403
|
score=$(get_review_score "${pr_number}")
|
|
242
404
|
if [ "${score}" -lt "0" ] || [ "${score}" -lt "${MIN_REVIEW_SCORE}" ]; then
|
|
243
|
-
log "INFO: PR #${pr_number} (${pr_branch}): Review score ${score} < ${MIN_REVIEW_SCORE} (or no score found),
|
|
405
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Review score ${score} < ${MIN_REVIEW_SCORE} (or no score found), reviewer job required before merge"
|
|
244
406
|
continue
|
|
245
407
|
fi
|
|
246
408
|
fi
|
|
247
409
|
|
|
410
|
+
pending_review_feedback_count=$(get_pending_review_feedback_count "${pr_number}")
|
|
411
|
+
if [ "${pending_review_feedback_count}" -gt "0" ]; then
|
|
412
|
+
log "INFO: PR #${pr_number} (${pr_branch}): ${pending_review_feedback_count} pending review feedback item(s), reviewer job required before merge"
|
|
413
|
+
continue
|
|
414
|
+
fi
|
|
415
|
+
|
|
248
416
|
log "INFO: PR #${pr_number} (${pr_branch}): Eligible for merge"
|
|
249
417
|
|
|
250
418
|
# Rebase before merge if configured
|
|
251
419
|
if [ "${REBASE_BEFORE_MERGE}" = "1" ]; then
|
|
420
|
+
pr_head_before_rebase="${pr_head_oid}"
|
|
252
421
|
if ! rebase_pr "${pr_number}"; then
|
|
253
422
|
log "WARN: PR #${pr_number}: Rebase failed, skipping"
|
|
254
423
|
FAILED_PRS=$((FAILED_PRS + 1))
|
|
@@ -256,20 +425,20 @@ while IFS= read -r pr_json; do
|
|
|
256
425
|
fi
|
|
257
426
|
log "INFO: PR #${pr_number}: Rebase successful"
|
|
258
427
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if !
|
|
272
|
-
log "INFO: PR #${pr_number}: CI not passing after rebase (waited ${
|
|
428
|
+
pr_head_after_rebase=$(get_pr_head_oid "${pr_number}")
|
|
429
|
+
if [ -z "${pr_head_after_rebase}" ]; then
|
|
430
|
+
log "INFO: PR #${pr_number}: Unable to determine PR head after rebase, skipping"
|
|
431
|
+
continue
|
|
432
|
+
fi
|
|
433
|
+
if [ "${pr_head_after_rebase}" != "${pr_head_before_rebase}" ]; then
|
|
434
|
+
log "INFO: PR #${pr_number}: Head changed after rebase ${pr_head_before_rebase} -> ${pr_head_after_rebase}; waiting for fresh CI"
|
|
435
|
+
else
|
|
436
|
+
log "INFO: PR #${pr_number}: Head unchanged after rebase (${pr_head_after_rebase}); confirming CI"
|
|
437
|
+
fi
|
|
438
|
+
|
|
439
|
+
# Poll CI until all checks attached to the post-rebase head are complete and passing.
|
|
440
|
+
if ! wait_for_ci_passing_on_head "${pr_number}" "${pr_head_after_rebase}"; then
|
|
441
|
+
log "INFO: PR #${pr_number}: Fresh CI not passing on head ${pr_head_after_rebase} after rebase (${LAST_CI_STATUS}, waited ${CI_MAX_WAIT}s), skipping"
|
|
273
442
|
continue
|
|
274
443
|
fi
|
|
275
444
|
fi
|
|
@@ -284,12 +453,17 @@ while IFS= read -r pr_json; do
|
|
|
284
453
|
# Rebase remaining PRs after each successful merge
|
|
285
454
|
log "INFO: Rebasing remaining open PRs after merging #${pr_number}..."
|
|
286
455
|
REMAINING_JSON=$(gh pr list --state open \
|
|
287
|
-
--json number,headRefName \
|
|
456
|
+
--json number,headRefName,labels \
|
|
288
457
|
2>/dev/null || echo "[]")
|
|
289
458
|
while IFS= read -r remaining_pr; do
|
|
290
459
|
remaining_number=$(echo "${remaining_pr}" | jq -r '.number')
|
|
291
460
|
remaining_branch=$(echo "${remaining_pr}" | jq -r '.headRefName')
|
|
461
|
+
remaining_labels=$(echo "${remaining_pr}" | jq -r '[.labels[]?.name] | join(",")')
|
|
292
462
|
if [ "${remaining_number}" != "${pr_number}" ]; then
|
|
463
|
+
if csv_has_label "${remaining_labels:-}" "${READY_TO_MERGE_LABEL}"; then
|
|
464
|
+
log "INFO: Skipping post-merge rebase for PR #${remaining_number} (${remaining_branch}) because it is labeled ${READY_TO_MERGE_LABEL}"
|
|
465
|
+
continue
|
|
466
|
+
fi
|
|
293
467
|
log "INFO: Rebasing remaining PR #${remaining_number} (${remaining_branch})"
|
|
294
468
|
gh pr update-branch --rebase "${remaining_number}" 2>/dev/null || \
|
|
295
469
|
log "WARN: PR #${remaining_number}: Rebase failed (continuing)"
|
|
@@ -61,6 +61,8 @@ PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PRO
|
|
|
61
61
|
PRD_DIR="${NW_PRD_DIR:-docs/PRDs}"
|
|
62
62
|
PLAN_TASK="${NW_PLAN_TASK:-}"
|
|
63
63
|
|
|
64
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
65
|
+
|
|
64
66
|
rotate_log
|
|
65
67
|
log_separator
|
|
66
68
|
log "RUN-START: planner invoked project=${PROJECT_DIR} provider=${PROVIDER_CMD} dry_run=${NW_DRY_RUN:-0}"
|
|
@@ -63,6 +63,7 @@ PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PRO
|
|
|
63
63
|
# NOTE: Lock file path must match resolverLockPath() in src/utils/status-data.ts
|
|
64
64
|
LOCK_FILE="/tmp/night-watch-pr-resolver-${PROJECT_RUNTIME_KEY}.lock"
|
|
65
65
|
SCRIPT_TYPE="pr-resolver"
|
|
66
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
66
67
|
|
|
67
68
|
emit_result() {
|
|
68
69
|
local status="${1:?status required}"
|
|
@@ -359,6 +360,11 @@ while IFS= read -r pr_line; do
|
|
|
359
360
|
continue
|
|
360
361
|
fi
|
|
361
362
|
|
|
363
|
+
if csv_has_label "${labels:-}" "${READY_LABEL}"; then
|
|
364
|
+
log "INFO: Skipping PR #${pr_number} (${pr_branch}) because it is labeled ${READY_LABEL}"
|
|
365
|
+
continue
|
|
366
|
+
fi
|
|
367
|
+
|
|
362
368
|
# Apply branch pattern filter
|
|
363
369
|
if ! matches_branch_patterns "${pr_branch}"; then
|
|
364
370
|
log "DEBUG: Skipping PR #${pr_number} — branch '${pr_branch}' does not match patterns" "patterns=${BRANCH_PATTERNS_RAW}"
|
|
@@ -63,11 +63,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
63
63
|
# shellcheck source=night-watch-helpers.sh
|
|
64
64
|
source "${SCRIPT_DIR}/night-watch-helpers.sh"
|
|
65
65
|
|
|
66
|
-
# Ensure provider CLI is on PATH (nvm, fnm, volta, common bin dirs)
|
|
67
|
-
if ! ensure_provider_on_path "${PROVIDER_CMD}"; then
|
|
68
|
-
echo "ERROR: Provider '${PROVIDER_CMD}' not found in PATH or common installation locations" >&2
|
|
69
|
-
exit 127
|
|
70
|
-
fi
|
|
71
66
|
PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
72
67
|
PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PROVIDER_LABEL}")
|
|
73
68
|
GLOBAL_LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}.lock"
|
|
@@ -79,6 +74,7 @@ else
|
|
|
79
74
|
fi
|
|
80
75
|
|
|
81
76
|
SCRIPT_TYPE="reviewer"
|
|
77
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
82
78
|
READY_FOR_REVIEW_LABEL="${NW_READY_FOR_REVIEW_LABEL:-ready-for-review}"
|
|
83
79
|
READY_FOR_REVIEW_MARKER_NAME="night-watch-ready-for-review"
|
|
84
80
|
READY_TO_MERGE_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
|
|
@@ -93,6 +89,13 @@ emit_result() {
|
|
|
93
89
|
fi
|
|
94
90
|
}
|
|
95
91
|
|
|
92
|
+
require_provider_on_path() {
|
|
93
|
+
if ! ensure_provider_on_path "${PROVIDER_CMD}"; then
|
|
94
|
+
echo "ERROR: Provider '${PROVIDER_CMD}' not found in PATH or common installation locations" >&2
|
|
95
|
+
exit 127
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
|
|
96
99
|
extract_review_score_from_text() {
|
|
97
100
|
local review_text="${1:-}"
|
|
98
101
|
printf '%s' "${review_text}" \
|
|
@@ -129,6 +132,72 @@ get_pr_comments() {
|
|
|
129
132
|
} | awk '!seen[$0]++'
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
get_pr_pending_review_feedback_count() {
|
|
136
|
+
local pr_number="${1:?PR number required}"
|
|
137
|
+
local review_decision=""
|
|
138
|
+
local change_request_count="0"
|
|
139
|
+
local unresolved_thread_count="0"
|
|
140
|
+
local repo="${REPO:-}"
|
|
141
|
+
local owner=""
|
|
142
|
+
local name=""
|
|
143
|
+
|
|
144
|
+
review_decision=$(gh pr view "${pr_number}" --json reviewDecision --jq '.reviewDecision // ""' 2>/dev/null || echo "")
|
|
145
|
+
|
|
146
|
+
change_request_count=$(gh api "repos/{owner}/{repo}/pulls/${pr_number}/reviews" \
|
|
147
|
+
--jq '[.[] | select(.state == "CHANGES_REQUESTED")] | length' 2>/dev/null || echo "0")
|
|
148
|
+
if ! [[ "${change_request_count}" =~ ^[0-9]+$ ]]; then
|
|
149
|
+
change_request_count="0"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if [ -z "${repo}" ]; then
|
|
153
|
+
repo=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "")
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
if [[ "${repo}" == */* ]]; then
|
|
157
|
+
owner="${repo%%/*}"
|
|
158
|
+
name="${repo#*/}"
|
|
159
|
+
unresolved_thread_count=$(gh api graphql \
|
|
160
|
+
-F owner="${owner}" \
|
|
161
|
+
-F name="${name}" \
|
|
162
|
+
-F number="${pr_number}" \
|
|
163
|
+
-f query='
|
|
164
|
+
query($owner: String!, $name: String!, $number: Int!) {
|
|
165
|
+
repository(owner: $owner, name: $name) {
|
|
166
|
+
pullRequest(number: $number) {
|
|
167
|
+
reviewThreads(first: 100) {
|
|
168
|
+
nodes {
|
|
169
|
+
isResolved
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
' \
|
|
176
|
+
--jq '[.data.repository.pullRequest.reviewThreads.nodes[]? | select(.isResolved == false)] | length' \
|
|
177
|
+
2>/dev/null || echo "0")
|
|
178
|
+
if ! [[ "${unresolved_thread_count}" =~ ^[0-9]+$ ]]; then
|
|
179
|
+
unresolved_thread_count="0"
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
if [ "${unresolved_thread_count}" -gt 0 ]; then
|
|
184
|
+
echo "${unresolved_thread_count}"
|
|
185
|
+
return 0
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
if [ "${change_request_count}" -gt 0 ]; then
|
|
189
|
+
echo "${change_request_count}"
|
|
190
|
+
return 0
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
if [ "${review_decision}" = "CHANGES_REQUESTED" ]; then
|
|
194
|
+
echo "1"
|
|
195
|
+
return 0
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
echo "0"
|
|
199
|
+
}
|
|
200
|
+
|
|
132
201
|
get_pr_head_ref_oid() {
|
|
133
202
|
local pr_number="${1:?PR number required}"
|
|
134
203
|
gh pr view "${pr_number}" --json headRefOid --jq '.headRefOid' 2>/dev/null || echo ""
|
|
@@ -757,6 +826,18 @@ while IFS=$'\t' read -r pr_number pr_branch pr_labels; do
|
|
|
757
826
|
continue
|
|
758
827
|
fi
|
|
759
828
|
|
|
829
|
+
PENDING_REVIEW_FEEDBACK_COUNT=$(get_pr_pending_review_feedback_count "${pr_number}")
|
|
830
|
+
if [ "${PENDING_REVIEW_FEEDBACK_COUNT}" -gt 0 ]; then
|
|
831
|
+
if [ "${local_ready_for_review_label_present}" -eq 1 ]; then
|
|
832
|
+
log "INFO: PR #${pr_number} (${pr_branch}) has pending review feedback; removing stale ${READY_FOR_REVIEW_LABEL} label"
|
|
833
|
+
clear_ready_for_human_review_label "${pr_number}"
|
|
834
|
+
fi
|
|
835
|
+
log "INFO: PR #${pr_number} (${pr_branch}) has ${PENDING_REVIEW_FEEDBACK_COUNT} pending review feedback item(s)"
|
|
836
|
+
NEEDS_WORK=1
|
|
837
|
+
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
838
|
+
continue
|
|
839
|
+
fi
|
|
840
|
+
|
|
760
841
|
if has_ready_for_human_review_marker "${all_comments}" "${current_head_sha}"; then
|
|
761
842
|
SKIPPED_ALREADY_REVIEWED_CURRENT_HEAD=1
|
|
762
843
|
log "INFO: PR #${pr_number} (${pr_branch}) is already marked ready for human review at head ${current_head_sha:0:12}; skipping repeat automated review"
|
|
@@ -853,6 +934,8 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
853
934
|
exit 0
|
|
854
935
|
fi
|
|
855
936
|
|
|
937
|
+
require_provider_on_path
|
|
938
|
+
|
|
856
939
|
log "PARALLEL: Launching ${#PR_NUMBER_ARRAY[@]} reviewer worker(s)"
|
|
857
940
|
|
|
858
941
|
declare -a WORKER_PIDS=()
|
|
@@ -1022,6 +1105,8 @@ if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
|
1022
1105
|
exit 0
|
|
1023
1106
|
fi
|
|
1024
1107
|
|
|
1108
|
+
require_provider_on_path
|
|
1109
|
+
|
|
1025
1110
|
if ! prepare_detached_worktree "${PROJECT_DIR}" "${REVIEW_WORKTREE_DIR}" "${DEFAULT_BRANCH}" "${LOG_FILE}"; then
|
|
1026
1111
|
log "FAIL: Unable to create isolated reviewer worktree ${REVIEW_WORKTREE_DIR}"
|
|
1027
1112
|
exit 1
|
|
@@ -1070,6 +1155,7 @@ if [ -n "${TARGET_PR}" ]; then
|
|
|
1070
1155
|
TARGET_MERGE_STATE=$(gh pr view "${TARGET_PR}" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null || echo "UNKNOWN")
|
|
1071
1156
|
TARGET_FAILED_CHECKS=$(get_pr_failed_ci_summary "${TARGET_PR}")
|
|
1072
1157
|
TARGET_SCORE=$(get_pr_score "${TARGET_PR}")
|
|
1158
|
+
TARGET_PENDING_REVIEW_FEEDBACK=$(get_pr_pending_review_feedback_count "${TARGET_PR}")
|
|
1073
1159
|
|
|
1074
1160
|
TARGET_SCOPE_PROMPT+=$'\n## Preflight Data (from CLI)\n- mergeStateStatus: '"${TARGET_MERGE_STATE}"$'\n'
|
|
1075
1161
|
if [ -n "${TARGET_FAILED_CHECKS}" ]; then
|
|
@@ -1077,6 +1163,7 @@ if [ -n "${TARGET_PR}" ]; then
|
|
|
1077
1163
|
else
|
|
1078
1164
|
TARGET_SCOPE_PROMPT+=$'- failing checks: none detected\n'
|
|
1079
1165
|
fi
|
|
1166
|
+
TARGET_SCOPE_PROMPT+=$'- pending review feedback: '"${TARGET_PENDING_REVIEW_FEEDBACK}"$' item(s)\n'
|
|
1080
1167
|
if [ -n "${TARGET_SCORE}" ]; then
|
|
1081
1168
|
TARGET_SCOPE_PROMPT+=$'- latest review score: '"${TARGET_SCORE}"$'/100\n'
|
|
1082
1169
|
TARGET_SCOPE_PROMPT+=$'- action: fix\n'
|
|
@@ -1226,6 +1313,10 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
1226
1313
|
LOG_LINE_BEFORE=$(wc -l < "${LOG_FILE}" 2>/dev/null || echo 0)
|
|
1227
1314
|
REVIEWER_ATTEMPT_START=$(date +%s)
|
|
1228
1315
|
REVIEWER_PROMPT="${REVIEWER_PROMPT_BASE}${TARGET_SCOPE_PROMPT}${PRD_CONTEXT_PROMPT}"
|
|
1316
|
+
if [ -n "${NW_PROJECT_FEEDBACK_PROMPT:-}" ]; then
|
|
1317
|
+
REVIEWER_PROMPT="${REVIEWER_PROMPT}"$'\n\n'"${NW_PROJECT_FEEDBACK_PROMPT}"
|
|
1318
|
+
log "INFO: Added project feedback prompt context"
|
|
1319
|
+
fi
|
|
1229
1320
|
|
|
1230
1321
|
# Build provider command array using generic helper
|
|
1231
1322
|
mapfile -d '' -t PROVIDER_CMD_PARTS < <(build_provider_cmd "${REVIEW_WORKTREE_DIR}" "${REVIEWER_PROMPT}")
|
|
@@ -26,6 +26,7 @@ PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
|
|
|
26
26
|
BRANCH_PATTERNS_RAW="${NW_BRANCH_PATTERNS:-feat/,night-watch/}"
|
|
27
27
|
SKIP_LABEL="${NW_QA_SKIP_LABEL:-skip-qa}"
|
|
28
28
|
VALIDATED_LABEL="${NW_QA_VALIDATED_LABEL:-e2e-validated}"
|
|
29
|
+
READY_TO_MERGE_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
|
|
29
30
|
QA_ARTIFACTS="${NW_QA_ARTIFACTS:-both}"
|
|
30
31
|
QA_AUTO_INSTALL_PLAYWRIGHT="${NW_QA_AUTO_INSTALL_PLAYWRIGHT:-1}"
|
|
31
32
|
SCRIPT_START_TIME=$(date +%s)
|
|
@@ -45,6 +46,7 @@ PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
|
45
46
|
# NOTE: Lock file path must match qaLockPath() in src/utils/status-data.ts
|
|
46
47
|
LOCK_FILE="/tmp/night-watch-qa-${PROJECT_RUNTIME_KEY}.lock"
|
|
47
48
|
SCRIPT_TYPE="qa"
|
|
49
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
48
50
|
|
|
49
51
|
emit_result() {
|
|
50
52
|
local status="${1:?status required}"
|
|
@@ -366,7 +368,7 @@ fi
|
|
|
366
368
|
rotate_log
|
|
367
369
|
log_separator
|
|
368
370
|
log "RUN-START: qa invoked project=${PROJECT_DIR} provider=${PROVIDER_CMD} dry_run=${NW_DRY_RUN:-0}"
|
|
369
|
-
log "CONFIG: max_runtime=${MAX_RUNTIME}s artifacts=${QA_ARTIFACTS} skip_label=${SKIP_LABEL} branch_patterns=${BRANCH_PATTERNS_RAW}"
|
|
371
|
+
log "CONFIG: max_runtime=${MAX_RUNTIME}s artifacts=${QA_ARTIFACTS} skip_label=${SKIP_LABEL} ready_label=${READY_TO_MERGE_LABEL} branch_patterns=${BRANCH_PATTERNS_RAW}"
|
|
370
372
|
|
|
371
373
|
if ! acquire_lock "${LOCK_FILE}"; then
|
|
372
374
|
emit_result "skip_locked"
|
|
@@ -431,6 +433,11 @@ while IFS=$'\t' read -r pr_number pr_branch pr_title pr_labels; do
|
|
|
431
433
|
continue
|
|
432
434
|
fi
|
|
433
435
|
|
|
436
|
+
if csv_has_label "${pr_labels:-}" "${READY_TO_MERGE_LABEL}"; then
|
|
437
|
+
log "SKIP-QA: PR #${pr_number} (${pr_branch}) is labeled ${READY_TO_MERGE_LABEL}"
|
|
438
|
+
continue
|
|
439
|
+
fi
|
|
440
|
+
|
|
434
441
|
# Skip PRs with the skip label
|
|
435
442
|
if echo "${pr_labels}" | grep -q "${SKIP_LABEL}"; then
|
|
436
443
|
log "SKIP-QA: PR #${pr_number} (${pr_branch}) has '${SKIP_LABEL}' label"
|
|
@@ -35,6 +35,7 @@ source "${SCRIPT_DIR}/night-watch-helpers.sh"
|
|
|
35
35
|
ensure_provider_on_path "${PROVIDER_CMD}" || true
|
|
36
36
|
PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
37
37
|
LOCK_FILE="/tmp/night-watch-slicer-${PROJECT_RUNTIME_KEY}.lock"
|
|
38
|
+
SCRIPT_TYPE="slicer"
|
|
38
39
|
PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PROVIDER_LABEL}")
|
|
39
40
|
|
|
40
41
|
emit_result() {
|
|
@@ -53,6 +54,8 @@ if ! validate_provider "${PROVIDER_CMD}"; then
|
|
|
53
54
|
exit 1
|
|
54
55
|
fi
|
|
55
56
|
|
|
57
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
58
|
+
|
|
56
59
|
rotate_log
|
|
57
60
|
log_separator
|
|
58
61
|
log "RUN-START: slicer invoked project=${PROJECT_DIR} provider=${PROVIDER_CMD} dry_run=${NW_DRY_RUN:-0}"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
You are the Night Watch PR Reviewer agent. Your job is to check open PRs for three things:
|
|
2
2
|
|
|
3
3
|
1. Merge conflicts -- rebase onto the base branch and resolve them.
|
|
4
|
-
2. Review comments with a score below 80 -- address the feedback.
|
|
4
|
+
2. Review comments with a score below 80 or unresolved review feedback -- address the feedback.
|
|
5
5
|
3. Failed CI jobs -- diagnose and fix the failures.
|
|
6
6
|
|
|
7
7
|
## Context
|
|
@@ -10,7 +10,7 @@ The repo can have multiple PR checks/workflows (project CI plus Night Watch auto
|
|
|
10
10
|
Common examples include `typecheck`, `lint`, `test`, `build`, `verify`, `executor`, `qa`, and `audit`.
|
|
11
11
|
Treat `gh pr checks <number> --json name,state,conclusion` as the source of truth for which checks failed.
|
|
12
12
|
|
|
13
|
-
A PR needs attention if **any** of the following: merge conflicts present, review score below 80, or any CI job failed.
|
|
13
|
+
A PR needs attention if **any** of the following: merge conflicts present, no review score yet, review score below 80, unresolved review feedback, or any CI job failed.
|
|
14
14
|
|
|
15
15
|
## PRD Context
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ If current PR code or review feedback conflicts with the PRD context, call out t
|
|
|
21
21
|
## Important: Early Exit
|
|
22
22
|
|
|
23
23
|
- If there are **no open PRs** on `night-watch/` or `feat/` branches, **stop immediately** and report "No PRs to review."
|
|
24
|
-
- If all open PRs have **no merge conflicts**, **passing CI**,
|
|
24
|
+
- If all open PRs have **no merge conflicts**, **passing CI**, **review score >= 80**, and **no unresolved review feedback**, **stop immediately** and report "All PRs are in good shape."
|
|
25
25
|
- If a PR has no review score yet, it needs a first review — do NOT skip it.
|
|
26
26
|
- Do **NOT** loop or retry. Process each PR **once** per run. After processing all PRs, stop.
|
|
27
27
|
- Do **NOT** re-check PRs after pushing fixes -- the CI will re-run automatically on the next push.
|
|
@@ -90,8 +90,8 @@ Parse the review score from the comment body. Look for patterns like:
|
|
|
90
90
|
Extract the numeric score. If multiple comments have scores, use the **most recent** one.
|
|
91
91
|
|
|
92
92
|
3. **Determine if PR needs work**:
|
|
93
|
-
- If no merge conflicts **AND** score >= 80 **AND** all CI checks pass --> skip this PR.
|
|
94
|
-
- If merge conflicts present **OR** score < 80 **OR** any CI check failed --> fix the issues.
|
|
93
|
+
- If no merge conflicts **AND** score >= 80 **AND** no unresolved review feedback **AND** all CI checks pass --> skip this PR.
|
|
94
|
+
- If merge conflicts present **OR** score < 80 **OR** unresolved review feedback exists **OR** any CI check failed --> fix the issues.
|
|
95
95
|
|
|
96
96
|
4. **Fix the PR**:
|
|
97
97
|
|
|
@@ -122,8 +122,9 @@ Parse the review score from the comment body. Look for patterns like:
|
|
|
122
122
|
- Push the clean branch: `git push --force-with-lease origin <branch-name>`
|
|
123
123
|
- **Do NOT leave any conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) in any file.**
|
|
124
124
|
|
|
125
|
-
c. **Address review feedback** (if score < 80):
|
|
125
|
+
c. **Address review feedback** (if score < 80 or pending review feedback exists):
|
|
126
126
|
- Read the review comments carefully. Extract areas for improvement, bugs found, issues found, and specific file/line suggestions.
|
|
127
|
+
- If `pending review feedback` is greater than 0 in the cron-provided preflight data, inspect review conversations with `gh pr view <number> --comments` and the GitHub PR review UI/API as needed.
|
|
127
128
|
- For each review suggestion:
|
|
128
129
|
- If you agree, implement the change.
|
|
129
130
|
- If you do not agree, do not implement it blindly. Capture a short technical reason and include that reason in the PR comment.
|