@jonit-dev/night-watch-cli 1.7.47 → 1.7.48
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 +1097 -709
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -36
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/prd.d.ts.map +1 -1
- package/dist/commands/prd.js +2 -6
- package/dist/commands/prd.js.map +1 -1
- package/dist/commands/prds.d.ts.map +1 -1
- package/dist/commands/prds.js +1 -1
- package/dist/commands/prds.js.map +1 -1
- package/dist/commands/review.d.ts +10 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +34 -0
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/scripts/night-watch-helpers.sh +1 -1
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +168 -35
- package/dist/web/assets/index-Ba-4YvTQ.js +365 -0
- package/dist/web/assets/index-DpVirMEe.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
|
@@ -28,6 +28,24 @@ TARGET_PR="${NW_TARGET_PR:-}"
|
|
|
28
28
|
PARALLEL_ENABLED="${NW_REVIEWER_PARALLEL:-1}"
|
|
29
29
|
WORKER_MODE="${NW_REVIEWER_WORKER_MODE:-0}"
|
|
30
30
|
|
|
31
|
+
# Retry configuration
|
|
32
|
+
REVIEWER_MAX_RETRIES="${NW_REVIEWER_MAX_RETRIES:-2}"
|
|
33
|
+
REVIEWER_RETRY_DELAY="${NW_REVIEWER_RETRY_DELAY:-30}"
|
|
34
|
+
|
|
35
|
+
# Normalize retry settings to safe numeric ranges
|
|
36
|
+
if ! [[ "${REVIEWER_MAX_RETRIES}" =~ ^[0-9]+$ ]]; then
|
|
37
|
+
REVIEWER_MAX_RETRIES="2"
|
|
38
|
+
fi
|
|
39
|
+
if ! [[ "${REVIEWER_RETRY_DELAY}" =~ ^[0-9]+$ ]]; then
|
|
40
|
+
REVIEWER_RETRY_DELAY="30"
|
|
41
|
+
fi
|
|
42
|
+
if [ "${REVIEWER_MAX_RETRIES}" -gt 10 ]; then
|
|
43
|
+
REVIEWER_MAX_RETRIES="10"
|
|
44
|
+
fi
|
|
45
|
+
if [ "${REVIEWER_RETRY_DELAY}" -gt 300 ]; then
|
|
46
|
+
REVIEWER_RETRY_DELAY="300"
|
|
47
|
+
fi
|
|
48
|
+
|
|
31
49
|
# Ensure NVM / Node / Claude are on PATH
|
|
32
50
|
export NVM_DIR="${HOME}/.nvm"
|
|
33
51
|
[ -s "${NVM_DIR}/nvm.sh" ] && . "${NVM_DIR}/nvm.sh"
|
|
@@ -64,16 +82,31 @@ emit_final_status() {
|
|
|
64
82
|
local prs_csv="${2:-}"
|
|
65
83
|
local auto_merged="${3:-}"
|
|
66
84
|
local auto_merge_failed="${4:-}"
|
|
85
|
+
local attempts="${5:-1}"
|
|
86
|
+
local final_score="${6:-}"
|
|
87
|
+
local details=""
|
|
67
88
|
|
|
68
89
|
if [ "${exit_code}" -eq 0 ]; then
|
|
90
|
+
details="prs=${prs_csv}|auto_merged=${auto_merged}|auto_merge_failed=${auto_merge_failed}|attempts=${attempts}"
|
|
91
|
+
if [ -n "${final_score}" ]; then
|
|
92
|
+
details="${details}|final_score=${final_score}"
|
|
93
|
+
fi
|
|
69
94
|
log "DONE: PR reviewer completed successfully"
|
|
70
|
-
emit_result "success_reviewed" "
|
|
95
|
+
emit_result "success_reviewed" "${details}"
|
|
71
96
|
elif [ "${exit_code}" -eq 124 ]; then
|
|
97
|
+
details="prs=${prs_csv}|attempts=${attempts}"
|
|
98
|
+
if [ -n "${final_score}" ]; then
|
|
99
|
+
details="${details}|final_score=${final_score}"
|
|
100
|
+
fi
|
|
72
101
|
log "TIMEOUT: PR reviewer killed after ${MAX_RUNTIME}s"
|
|
73
|
-
emit_result "timeout" "
|
|
102
|
+
emit_result "timeout" "${details}"
|
|
74
103
|
else
|
|
104
|
+
details="prs=${prs_csv}|attempts=${attempts}"
|
|
105
|
+
if [ -n "${final_score}" ]; then
|
|
106
|
+
details="${details}|final_score=${final_score}"
|
|
107
|
+
fi
|
|
75
108
|
log "FAIL: PR reviewer exited with code ${exit_code}"
|
|
76
|
-
emit_result "failure" "
|
|
109
|
+
emit_result "failure" "${details}"
|
|
77
110
|
fi
|
|
78
111
|
}
|
|
79
112
|
|
|
@@ -91,6 +124,25 @@ append_csv() {
|
|
|
91
124
|
fi
|
|
92
125
|
}
|
|
93
126
|
|
|
127
|
+
# Extract the latest review score from PR comments
|
|
128
|
+
# Returns empty string if no score found
|
|
129
|
+
get_pr_score() {
|
|
130
|
+
local pr_number="${1:?PR number required}"
|
|
131
|
+
local all_comments
|
|
132
|
+
all_comments=$(
|
|
133
|
+
{
|
|
134
|
+
gh pr view "${pr_number}" --json comments --jq '.comments[].body' 2>/dev/null || true
|
|
135
|
+
if [ -n "${REPO:-}" ]; then
|
|
136
|
+
gh api "repos/${REPO}/issues/${pr_number}/comments" --jq '.[].body' 2>/dev/null || true
|
|
137
|
+
fi
|
|
138
|
+
} | sort -u
|
|
139
|
+
)
|
|
140
|
+
echo "${all_comments}" \
|
|
141
|
+
| grep -oP 'Overall Score:\*?\*?\s*(\d+)/100' \
|
|
142
|
+
| tail -1 \
|
|
143
|
+
| grep -oP '\d+(?=/100)' || echo ""
|
|
144
|
+
}
|
|
145
|
+
|
|
94
146
|
# Validate provider
|
|
95
147
|
if ! validate_provider "${PROVIDER_CMD}"; then
|
|
96
148
|
echo "ERROR: Unknown provider: ${PROVIDER_CMD}" >&2
|
|
@@ -323,6 +375,8 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
323
375
|
EXIT_CODE=0
|
|
324
376
|
AUTO_MERGED_PRS=""
|
|
325
377
|
AUTO_MERGE_FAILED_PRS=""
|
|
378
|
+
MAX_WORKER_ATTEMPTS=1
|
|
379
|
+
MAX_WORKER_FINAL_SCORE=""
|
|
326
380
|
|
|
327
381
|
for idx in "${!WORKER_PIDS[@]}"; do
|
|
328
382
|
worker_pid="${WORKER_PIDS[$idx]}"
|
|
@@ -344,10 +398,21 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
344
398
|
worker_status=$(printf '%s' "${worker_result}" | sed -n 's/^NIGHT_WATCH_RESULT:\([^|]*\).*$/\1/p')
|
|
345
399
|
worker_auto_merged=$(printf '%s' "${worker_result}" | grep -oP '(?<=auto_merged=)[^|]+' || true)
|
|
346
400
|
worker_auto_merge_failed=$(printf '%s' "${worker_result}" | grep -oP '(?<=auto_merge_failed=)[^|]+' || true)
|
|
401
|
+
worker_attempts=$(printf '%s' "${worker_result}" | grep -oP '(?<=attempts=)[^|]+' || true)
|
|
402
|
+
worker_final_score=$(printf '%s' "${worker_result}" | grep -oP '(?<=final_score=)[^|]+' || true)
|
|
347
403
|
|
|
348
404
|
AUTO_MERGED_PRS=$(append_csv "${AUTO_MERGED_PRS}" "${worker_auto_merged}")
|
|
349
405
|
AUTO_MERGE_FAILED_PRS=$(append_csv "${AUTO_MERGE_FAILED_PRS}" "${worker_auto_merge_failed}")
|
|
350
406
|
|
|
407
|
+
if [[ "${worker_attempts}" =~ ^[0-9]+$ ]] && [ "${worker_attempts}" -gt "${MAX_WORKER_ATTEMPTS}" ]; then
|
|
408
|
+
MAX_WORKER_ATTEMPTS="${worker_attempts}"
|
|
409
|
+
fi
|
|
410
|
+
if [[ "${worker_final_score}" =~ ^[0-9]+$ ]]; then
|
|
411
|
+
if [ -z "${MAX_WORKER_FINAL_SCORE}" ] || [ "${worker_final_score}" -gt "${MAX_WORKER_FINAL_SCORE}" ]; then
|
|
412
|
+
MAX_WORKER_FINAL_SCORE="${worker_final_score}"
|
|
413
|
+
fi
|
|
414
|
+
fi
|
|
415
|
+
|
|
351
416
|
rm -f "${worker_output}"
|
|
352
417
|
|
|
353
418
|
if [ "${worker_status}" = "failure" ] || { [ -n "${worker_status}" ] && [ "${worker_status}" != "success_reviewed" ] && [ "${worker_status}" != "timeout" ] && [ "${worker_status#skip_}" = "${worker_status}" ]; }; then
|
|
@@ -374,7 +439,7 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
374
439
|
fi
|
|
375
440
|
done
|
|
376
441
|
|
|
377
|
-
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}"
|
|
442
|
+
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}" "${MAX_WORKER_ATTEMPTS}" "${MAX_WORKER_FINAL_SCORE}"
|
|
378
443
|
exit 0
|
|
379
444
|
fi
|
|
380
445
|
|
|
@@ -396,6 +461,8 @@ if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
|
396
461
|
if [ "${AUTO_MERGE}" = "1" ]; then
|
|
397
462
|
echo "Auto-merge Method: ${AUTO_MERGE_METHOD}"
|
|
398
463
|
fi
|
|
464
|
+
echo "Max Retries: ${REVIEWER_MAX_RETRIES}"
|
|
465
|
+
echo "Retry Delay: ${REVIEWER_RETRY_DELAY}s"
|
|
399
466
|
echo "Open PRs needing work:${PRS_NEEDING_WORK}"
|
|
400
467
|
echo "Default Branch: ${DEFAULT_BRANCH}"
|
|
401
468
|
echo "Review Worktree: ${REVIEW_WORKTREE_DIR}"
|
|
@@ -413,44 +480,110 @@ if ! prepare_detached_worktree "${PROJECT_DIR}" "${REVIEW_WORKTREE_DIR}" "${DEFA
|
|
|
413
480
|
fi
|
|
414
481
|
|
|
415
482
|
EXIT_CODE=0
|
|
483
|
+
ATTEMPTS_MADE=1
|
|
484
|
+
FINAL_SCORE=""
|
|
416
485
|
TARGET_SCOPE_PROMPT=""
|
|
417
486
|
if [ -n "${TARGET_PR}" ]; then
|
|
418
487
|
TARGET_SCOPE_PROMPT=$'\n\n## Target Scope\n- Only process PR #'"${TARGET_PR}"$'.\n- Ignore all other PRs.\n- If this PR no longer needs work, stop immediately.\n'
|
|
419
488
|
fi
|
|
420
489
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
490
|
+
# ── Retry Loop for Targeted PR Review ──────────────────────────────────────────
|
|
491
|
+
# Only retry when targeting a specific PR. Non-targeted mode handles all PRs in one shot.
|
|
492
|
+
TOTAL_ATTEMPTS=1
|
|
493
|
+
if [ -n "${TARGET_PR}" ]; then
|
|
494
|
+
TOTAL_ATTEMPTS=$((REVIEWER_MAX_RETRIES + 1))
|
|
495
|
+
fi
|
|
496
|
+
RUN_STARTED_AT=$(date +%s)
|
|
497
|
+
|
|
498
|
+
for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
499
|
+
ATTEMPTS_MADE="${ATTEMPT}"
|
|
500
|
+
|
|
501
|
+
ATTEMPT_TIMEOUT="${MAX_RUNTIME}"
|
|
502
|
+
if [ -n "${TARGET_PR}" ]; then
|
|
503
|
+
# Calculate timeout from remaining runtime budget.
|
|
504
|
+
NOW_TS=$(date +%s)
|
|
505
|
+
ELAPSED=$((NOW_TS - RUN_STARTED_AT))
|
|
506
|
+
REMAINING_BUDGET=$((MAX_RUNTIME - ELAPSED))
|
|
507
|
+
if [ "${REMAINING_BUDGET}" -le 0 ]; then
|
|
508
|
+
EXIT_CODE=124
|
|
509
|
+
log "RETRY: Runtime budget exhausted before attempt ${ATTEMPT}"
|
|
510
|
+
break
|
|
433
511
|
fi
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
512
|
+
|
|
513
|
+
REMAINING_ATTEMPTS=$((TOTAL_ATTEMPTS - ATTEMPT + 1))
|
|
514
|
+
ATTEMPT_TIMEOUT=$((REMAINING_BUDGET / REMAINING_ATTEMPTS))
|
|
515
|
+
if [ "${ATTEMPT_TIMEOUT}" -lt 1 ]; then
|
|
516
|
+
ATTEMPT_TIMEOUT=1
|
|
517
|
+
fi
|
|
518
|
+
fi
|
|
519
|
+
|
|
520
|
+
log "RETRY: Starting attempt ${ATTEMPT}/${TOTAL_ATTEMPTS} (timeout: ${ATTEMPT_TIMEOUT}s)"
|
|
521
|
+
|
|
522
|
+
case "${PROVIDER_CMD}" in
|
|
523
|
+
claude)
|
|
524
|
+
CLAUDE_PROMPT="/night-watch-pr-reviewer${TARGET_SCOPE_PROMPT}"
|
|
525
|
+
if (
|
|
526
|
+
cd "${REVIEW_WORKTREE_DIR}" && timeout "${ATTEMPT_TIMEOUT}" \
|
|
527
|
+
claude -p "${CLAUDE_PROMPT}" \
|
|
528
|
+
--dangerously-skip-permissions \
|
|
529
|
+
>> "${LOG_FILE}" 2>&1
|
|
530
|
+
); then
|
|
531
|
+
EXIT_CODE=0
|
|
532
|
+
else
|
|
533
|
+
EXIT_CODE=$?
|
|
534
|
+
fi
|
|
535
|
+
;;
|
|
536
|
+
codex)
|
|
537
|
+
CODEX_PROMPT="$(cat "${REVIEW_WORKTREE_DIR}/.claude/commands/night-watch-pr-reviewer.md")${TARGET_SCOPE_PROMPT}"
|
|
538
|
+
if (
|
|
539
|
+
cd "${REVIEW_WORKTREE_DIR}" && timeout "${ATTEMPT_TIMEOUT}" \
|
|
540
|
+
codex --quiet \
|
|
541
|
+
--yolo \
|
|
542
|
+
--prompt "${CODEX_PROMPT}" \
|
|
543
|
+
>> "${LOG_FILE}" 2>&1
|
|
544
|
+
); then
|
|
545
|
+
EXIT_CODE=0
|
|
546
|
+
else
|
|
547
|
+
EXIT_CODE=$?
|
|
548
|
+
fi
|
|
549
|
+
;;
|
|
550
|
+
*)
|
|
551
|
+
log "ERROR: Unknown provider: ${PROVIDER_CMD}"
|
|
552
|
+
exit 1
|
|
553
|
+
;;
|
|
554
|
+
esac
|
|
555
|
+
|
|
556
|
+
# If provider failed (non-zero exit), don't retry
|
|
557
|
+
if [ "${EXIT_CODE}" -ne 0 ]; then
|
|
558
|
+
log "RETRY: Provider exited with code ${EXIT_CODE}, not retrying"
|
|
559
|
+
break
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
# Re-check score for the target PR (only in targeted mode)
|
|
563
|
+
if [ -n "${TARGET_PR}" ]; then
|
|
564
|
+
CURRENT_SCORE=$(get_pr_score "${TARGET_PR}")
|
|
565
|
+
if [ -z "${CURRENT_SCORE}" ]; then
|
|
566
|
+
log "RETRY: No review score found for PR #${TARGET_PR} after attempt ${ATTEMPT}; not retrying"
|
|
567
|
+
break
|
|
568
|
+
fi
|
|
569
|
+
|
|
570
|
+
FINAL_SCORE="${CURRENT_SCORE}"
|
|
571
|
+
if [ "${CURRENT_SCORE}" -ge "${MIN_REVIEW_SCORE}" ]; then
|
|
572
|
+
log "RETRY: PR #${TARGET_PR} now scores ${CURRENT_SCORE}/100 (>= ${MIN_REVIEW_SCORE}) after attempt ${ATTEMPT}"
|
|
573
|
+
break
|
|
574
|
+
fi
|
|
575
|
+
if [ "${ATTEMPT}" -lt "${TOTAL_ATTEMPTS}" ]; then
|
|
576
|
+
log "RETRY: PR #${TARGET_PR} scores ${CURRENT_SCORE:-unknown}/100 after attempt ${ATTEMPT}/${TOTAL_ATTEMPTS}, retrying in ${REVIEWER_RETRY_DELAY}s..."
|
|
577
|
+
sleep "${REVIEWER_RETRY_DELAY}"
|
|
445
578
|
else
|
|
446
|
-
|
|
579
|
+
log "RETRY: PR #${TARGET_PR} still at ${CURRENT_SCORE:-unknown}/100 after ${TOTAL_ATTEMPTS} attempts - giving up"
|
|
580
|
+
gh pr edit "${TARGET_PR}" --add-label "needs-human-review" 2>/dev/null || true
|
|
447
581
|
fi
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
esac
|
|
582
|
+
else
|
|
583
|
+
# Non-targeted mode: no retry (reviewer handles all PRs in one shot)
|
|
584
|
+
break
|
|
585
|
+
fi
|
|
586
|
+
done
|
|
454
587
|
|
|
455
588
|
cleanup_worktrees "${PROJECT_DIR}" "${REVIEW_WORKTREE_BASENAME}"
|
|
456
589
|
|
|
@@ -529,4 +662,4 @@ if [ "${AUTO_MERGE}" = "1" ] && [ ${EXIT_CODE} -eq 0 ]; then
|
|
|
529
662
|
done < <(gh pr list --state open --json number,headRefName --jq '.[] | [.number, .headRefName] | @tsv' 2>/dev/null || true)
|
|
530
663
|
fi
|
|
531
664
|
|
|
532
|
-
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}"
|
|
665
|
+
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}" "${ATTEMPTS_MADE}" "${FINAL_SCORE}"
|