@jonit-dev/night-watch-cli 1.7.49 → 1.7.50
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 +960 -379
- package/dist/commands/cron.d.ts +8 -0
- package/dist/commands/cron.d.ts.map +1 -0
- package/dist/commands/cron.js +214 -0
- package/dist/commands/cron.js.map +1 -0
- package/dist/commands/init.js +16 -16
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/qa.d.ts.map +1 -1
- package/dist/commands/qa.js +3 -27
- package/dist/commands/qa.js.map +1 -1
- package/dist/commands/review.d.ts +20 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +97 -18
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +3 -18
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/shared/env-builder.d.ts +25 -0
- package/dist/commands/shared/env-builder.d.ts.map +1 -0
- package/dist/commands/shared/env-builder.js +48 -0
- package/dist/commands/shared/env-builder.js.map +1 -0
- package/dist/commands/slice.d.ts.map +1 -1
- package/dist/commands/slice.js +3 -23
- package/dist/commands/slice.js.map +1 -1
- package/dist/scripts/night-watch-audit-cron.sh +55 -32
- package/dist/scripts/night-watch-cron.sh +12 -2
- package/dist/scripts/night-watch-helpers.sh +36 -0
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +193 -9
- package/dist/scripts/night-watch-qa-cron.sh +12 -2
- package/dist/templates/night-watch-pr-reviewer.md +147 -135
- package/dist/templates/night-watch-slicer.md +1 -1
- package/dist/templates/night-watch.md +1 -1
- package/package.json +1 -1
|
@@ -304,6 +304,16 @@ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" || true
|
|
|
304
304
|
|
|
305
305
|
log "START: Processing ${ELIGIBLE_PRD} on branch ${BRANCH_NAME} (worktree: ${WORKTREE_DIR})"
|
|
306
306
|
|
|
307
|
+
EXECUTOR_PROMPT_PATH=$(resolve_instruction_path "${PROJECT_DIR}" "prd-executor.md" || true)
|
|
308
|
+
if [ -z "${EXECUTOR_PROMPT_PATH}" ]; then
|
|
309
|
+
log "FAIL: Missing PRD executor instructions. Checked instructions/, .claude/commands/, and bundled templates/"
|
|
310
|
+
restore_issue_to_ready "Failed to locate PRD executor instructions. Checked instructions/, .claude/commands/, and bundled templates/."
|
|
311
|
+
night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
|
|
312
|
+
emit_result "failure" "reason=missing_executor_prompt"
|
|
313
|
+
exit 1
|
|
314
|
+
fi
|
|
315
|
+
EXECUTOR_PROMPT_REF=$(instruction_ref_for_prompt "${PROJECT_DIR}" "${EXECUTOR_PROMPT_PATH}")
|
|
316
|
+
|
|
307
317
|
if [ -n "${ISSUE_NUMBER}" ]; then
|
|
308
318
|
PROMPT="Implement the following PRD (GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}):
|
|
309
319
|
|
|
@@ -317,7 +327,7 @@ ${ISSUE_BODY}
|
|
|
317
327
|
- Install dependencies if needed and implement in the current worktree only
|
|
318
328
|
|
|
319
329
|
## Implementation — PRD Executor Workflow
|
|
320
|
-
Read
|
|
330
|
+
Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline:
|
|
321
331
|
1. Parse the PRD into phases and extract dependencies
|
|
322
332
|
2. Build a dependency graph to identify parallelism
|
|
323
333
|
3. Create a task list with one task per phase
|
|
@@ -345,7 +355,7 @@ else
|
|
|
345
355
|
- Install dependencies if needed and implement in the current worktree only
|
|
346
356
|
|
|
347
357
|
## Implementation — PRD Executor Workflow
|
|
348
|
-
Read
|
|
358
|
+
Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline:
|
|
349
359
|
1. Parse the PRD into phases and extract dependencies
|
|
350
360
|
2. Build a dependency graph to identify parallelism
|
|
351
361
|
3. Create a task list with one task per phase
|
|
@@ -51,6 +51,42 @@ resolve_night_watch_cli() {
|
|
|
51
51
|
return 1
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
resolve_instruction_path() {
|
|
55
|
+
local project_dir="${1:?project_dir required}"
|
|
56
|
+
local instruction_file="${2:?instruction_file required}"
|
|
57
|
+
local script_dir=""
|
|
58
|
+
local candidate=""
|
|
59
|
+
|
|
60
|
+
if [ -n "${SCRIPT_DIR:-}" ]; then
|
|
61
|
+
script_dir="${SCRIPT_DIR}"
|
|
62
|
+
else
|
|
63
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
for candidate in \
|
|
67
|
+
"${project_dir}/instructions/${instruction_file}" \
|
|
68
|
+
"${project_dir}/.claude/commands/${instruction_file}" \
|
|
69
|
+
"${script_dir}/../templates/${instruction_file}"; do
|
|
70
|
+
if [ -f "${candidate}" ]; then
|
|
71
|
+
printf "%s" "${candidate}"
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
return 1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
instruction_ref_for_prompt() {
|
|
80
|
+
local root_dir="${1:?root_dir required}"
|
|
81
|
+
local instruction_path="${2:?instruction_path required}"
|
|
82
|
+
|
|
83
|
+
if [[ "${instruction_path}" == "${root_dir}/"* ]]; then
|
|
84
|
+
printf "%s" "${instruction_path#${root_dir}/}"
|
|
85
|
+
else
|
|
86
|
+
printf "%s" "${instruction_path}"
|
|
87
|
+
fi
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
night_watch_history() {
|
|
55
91
|
local cli_bin
|
|
56
92
|
cli_bin=$(resolve_night_watch_cli) || return 127
|
|
@@ -143,6 +143,135 @@ get_pr_score() {
|
|
|
143
143
|
| grep -oP '\d+(?=/100)' || echo ""
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
# Count failed CI checks for a PR.
|
|
147
|
+
# Uses JSON fields when available (more reliable across check name/status formats),
|
|
148
|
+
# then falls back to text parsing for older/mocked gh outputs.
|
|
149
|
+
get_pr_failed_ci_checks() {
|
|
150
|
+
local pr_number="${1:?PR number required}"
|
|
151
|
+
local failed_count=""
|
|
152
|
+
|
|
153
|
+
failed_count="$(
|
|
154
|
+
gh pr checks "${pr_number}" --json bucket,state,conclusion --jq '
|
|
155
|
+
[ .[]
|
|
156
|
+
| (.bucket // "" | ascii_downcase) as $bucket
|
|
157
|
+
| (.state // "" | ascii_downcase) as $state
|
|
158
|
+
| (.conclusion // "" | ascii_downcase) as $conclusion
|
|
159
|
+
| select(
|
|
160
|
+
$bucket == "fail" or
|
|
161
|
+
$bucket == "cancel" or
|
|
162
|
+
$state == "failure" or
|
|
163
|
+
$state == "error" or
|
|
164
|
+
$state == "cancelled" or
|
|
165
|
+
$conclusion == "failure" or
|
|
166
|
+
$conclusion == "error" or
|
|
167
|
+
$conclusion == "cancelled" or
|
|
168
|
+
$conclusion == "timed_out" or
|
|
169
|
+
$conclusion == "action_required" or
|
|
170
|
+
$conclusion == "startup_failure" or
|
|
171
|
+
$conclusion == "stale"
|
|
172
|
+
)
|
|
173
|
+
] | length
|
|
174
|
+
' 2>/dev/null || true
|
|
175
|
+
)"
|
|
176
|
+
|
|
177
|
+
if [[ "${failed_count}" =~ ^[0-9]+$ ]]; then
|
|
178
|
+
echo "${failed_count}"
|
|
179
|
+
return 0
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
failed_count=$(
|
|
183
|
+
gh pr checks "${pr_number}" 2>/dev/null \
|
|
184
|
+
| grep -Eci 'fail|error|cancel|timed[_ -]?out|action_required|startup_failure|stale' || true
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if [[ "${failed_count}" =~ ^[0-9]+$ ]]; then
|
|
188
|
+
echo "${failed_count}"
|
|
189
|
+
else
|
|
190
|
+
echo "0"
|
|
191
|
+
fi
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Return a semicolon-separated summary of failing CI checks for a PR.
|
|
195
|
+
# Format: "<check name> [state=<state>, conclusion=<conclusion>]"
|
|
196
|
+
get_pr_failed_ci_summary() {
|
|
197
|
+
local pr_number="${1:?PR number required}"
|
|
198
|
+
local failed_summary=""
|
|
199
|
+
|
|
200
|
+
failed_summary="$(
|
|
201
|
+
gh pr checks "${pr_number}" --json name,bucket,state,conclusion --jq '
|
|
202
|
+
[ .[]
|
|
203
|
+
| (.bucket // "" | ascii_downcase) as $bucket
|
|
204
|
+
| (.state // "" | ascii_downcase) as $state
|
|
205
|
+
| (.conclusion // "" | ascii_downcase) as $conclusion
|
|
206
|
+
| select(
|
|
207
|
+
$bucket == "fail" or
|
|
208
|
+
$bucket == "cancel" or
|
|
209
|
+
$state == "failure" or
|
|
210
|
+
$state == "error" or
|
|
211
|
+
$state == "cancelled" or
|
|
212
|
+
$conclusion == "failure" or
|
|
213
|
+
$conclusion == "error" or
|
|
214
|
+
$conclusion == "cancelled" or
|
|
215
|
+
$conclusion == "timed_out" or
|
|
216
|
+
$conclusion == "action_required" or
|
|
217
|
+
$conclusion == "startup_failure" or
|
|
218
|
+
$conclusion == "stale"
|
|
219
|
+
)
|
|
220
|
+
| "\(.name // "unknown") [state=\(.state // "unknown"), conclusion=\(.conclusion // "unknown")]"
|
|
221
|
+
] | join("; ")
|
|
222
|
+
' 2>/dev/null || true
|
|
223
|
+
)"
|
|
224
|
+
|
|
225
|
+
if [ -n "${failed_summary}" ]; then
|
|
226
|
+
echo "${failed_summary}"
|
|
227
|
+
return 0
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# Fallback for older/mocked outputs where JSON fields aren't available.
|
|
231
|
+
failed_summary=$(
|
|
232
|
+
gh pr checks "${pr_number}" 2>/dev/null \
|
|
233
|
+
| grep -Ei 'fail|error|cancel|timed[_ -]?out|action_required|startup_failure|stale' \
|
|
234
|
+
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]][[:space:]]*/ /g' \
|
|
235
|
+
| paste -sd '; ' - || true
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
echo "${failed_summary}"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Clean up reviewer-managed worktrees.
|
|
242
|
+
# - Always removes the caller's runner worktree when runner_scope is provided.
|
|
243
|
+
# - Only non-worker/controller processes perform broad cleanup to avoid
|
|
244
|
+
# parallel workers deleting each other's active worktrees.
|
|
245
|
+
cleanup_reviewer_worktrees() {
|
|
246
|
+
local runner_scope="${1:-}"
|
|
247
|
+
|
|
248
|
+
if [ -n "${runner_scope}" ]; then
|
|
249
|
+
cleanup_worktrees "${PROJECT_DIR}" "${runner_scope}"
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
if [ "${WORKER_MODE}" = "1" ]; then
|
|
253
|
+
return 0
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# Remove per-PR reviewer worktrees created by prompts from older runs.
|
|
257
|
+
cleanup_worktrees "${PROJECT_DIR}" "${PROJECT_NAME}-nw-review-"
|
|
258
|
+
|
|
259
|
+
# Remove legacy reviewer worktree naming used in some older prompt variants.
|
|
260
|
+
local escaped_project_name
|
|
261
|
+
escaped_project_name=$(printf '%s\n' "${PROJECT_NAME}" | sed 's/[][(){}.^$*+?|\\/]/\\&/g')
|
|
262
|
+
git -C "${PROJECT_DIR}" worktree list --porcelain 2>/dev/null \
|
|
263
|
+
| grep '^worktree ' \
|
|
264
|
+
| awk '{print $2}' \
|
|
265
|
+
| while read -r wt; do
|
|
266
|
+
local wt_basename
|
|
267
|
+
wt_basename=$(basename "${wt}")
|
|
268
|
+
if printf '%s\n' "${wt_basename}" | grep -Eq "^${escaped_project_name}-pr-?[0-9]+$"; then
|
|
269
|
+
log "CLEANUP: Removing legacy reviewer worktree ${wt}"
|
|
270
|
+
git -C "${PROJECT_DIR}" worktree remove --force "${wt}" 2>/dev/null || true
|
|
271
|
+
fi
|
|
272
|
+
done || true
|
|
273
|
+
}
|
|
274
|
+
|
|
146
275
|
# Validate provider
|
|
147
276
|
if ! validate_provider "${PROVIDER_CMD}"; then
|
|
148
277
|
echo "ERROR: Unknown provider: ${PROVIDER_CMD}" >&2
|
|
@@ -221,9 +350,14 @@ while IFS=$'\t' read -r pr_number pr_branch; do
|
|
|
221
350
|
continue
|
|
222
351
|
fi
|
|
223
352
|
|
|
224
|
-
FAILED_CHECKS=$(
|
|
353
|
+
FAILED_CHECKS=$(get_pr_failed_ci_checks "${pr_number}")
|
|
225
354
|
if [ "${FAILED_CHECKS}" -gt 0 ]; then
|
|
226
|
-
|
|
355
|
+
FAILED_SUMMARY=$(get_pr_failed_ci_summary "${pr_number}")
|
|
356
|
+
if [ -n "${FAILED_SUMMARY}" ]; then
|
|
357
|
+
log "INFO: PR #${pr_number} (${pr_branch}) has ${FAILED_CHECKS} failed CI check(s): ${FAILED_SUMMARY}"
|
|
358
|
+
else
|
|
359
|
+
log "INFO: PR #${pr_number} (${pr_branch}) has ${FAILED_CHECKS} failed CI check(s)"
|
|
360
|
+
fi
|
|
227
361
|
NEEDS_WORK=1
|
|
228
362
|
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
229
363
|
continue
|
|
@@ -325,6 +459,10 @@ fi
|
|
|
325
459
|
|
|
326
460
|
log "START: Found PR(s) needing work:${PRS_NEEDING_WORK}"
|
|
327
461
|
|
|
462
|
+
# Remove stale reviewer worktrees from previous interrupted runs.
|
|
463
|
+
# Worker processes skip broad cleanup to avoid parallel interference.
|
|
464
|
+
cleanup_reviewer_worktrees
|
|
465
|
+
|
|
328
466
|
# Convert "#12 #34" into ["12", "34"] for worker fan-out.
|
|
329
467
|
PR_NUMBER_ARRAY=()
|
|
330
468
|
for pr_token in ${PRS_NEEDING_WORK}; do
|
|
@@ -355,7 +493,14 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
355
493
|
declare -a WORKER_PRS=()
|
|
356
494
|
declare -a WORKER_OUTPUTS=()
|
|
357
495
|
|
|
496
|
+
WORKER_IDX=0
|
|
497
|
+
WORKER_STAGGER_DELAY="${NW_REVIEWER_WORKER_STAGGER:-60}"
|
|
358
498
|
for pr_number in "${PR_NUMBER_ARRAY[@]}"; do
|
|
499
|
+
if [ "${WORKER_IDX}" -gt 0 ]; then
|
|
500
|
+
log "PARALLEL: Staggering worker launch by ${WORKER_STAGGER_DELAY}s (worker $((WORKER_IDX + 1))/${#PR_NUMBER_ARRAY[@]})"
|
|
501
|
+
sleep "${WORKER_STAGGER_DELAY}"
|
|
502
|
+
fi
|
|
503
|
+
|
|
359
504
|
worker_output=$(mktemp "/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}-pr-${pr_number}.XXXXXX")
|
|
360
505
|
WORKER_OUTPUTS+=("${worker_output}")
|
|
361
506
|
WORKER_PRS+=("${pr_number}")
|
|
@@ -370,6 +515,7 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
370
515
|
worker_pid=$!
|
|
371
516
|
WORKER_PIDS+=("${worker_pid}")
|
|
372
517
|
log "PARALLEL: Worker PID ${worker_pid} started for PR #${pr_number}"
|
|
518
|
+
WORKER_IDX=$((WORKER_IDX + 1))
|
|
373
519
|
done
|
|
374
520
|
|
|
375
521
|
EXIT_CODE=0
|
|
@@ -439,6 +585,10 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
439
585
|
fi
|
|
440
586
|
done
|
|
441
587
|
|
|
588
|
+
# Parent/controller process cleans up any per-PR reviewer worktrees that
|
|
589
|
+
# worker runs may have left behind.
|
|
590
|
+
cleanup_reviewer_worktrees
|
|
591
|
+
|
|
442
592
|
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}" "${MAX_WORKER_ATTEMPTS}" "${MAX_WORKER_FINAL_SCORE}"
|
|
443
593
|
exit 0
|
|
444
594
|
fi
|
|
@@ -449,7 +599,7 @@ if [ -n "${TARGET_PR}" ]; then
|
|
|
449
599
|
fi
|
|
450
600
|
REVIEW_WORKTREE_DIR="$(dirname "${PROJECT_DIR}")/${REVIEW_WORKTREE_BASENAME}"
|
|
451
601
|
|
|
452
|
-
|
|
602
|
+
cleanup_reviewer_worktrees "${REVIEW_WORKTREE_BASENAME}"
|
|
453
603
|
|
|
454
604
|
# Dry-run mode: print diagnostics and exit
|
|
455
605
|
if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
@@ -479,12 +629,38 @@ if ! prepare_detached_worktree "${PROJECT_DIR}" "${REVIEW_WORKTREE_DIR}" "${DEFA
|
|
|
479
629
|
exit 1
|
|
480
630
|
fi
|
|
481
631
|
|
|
632
|
+
REVIEWER_PROMPT_PATH=$(resolve_instruction_path "${REVIEW_WORKTREE_DIR}" "night-watch-pr-reviewer.md" || true)
|
|
633
|
+
if [ -z "${REVIEWER_PROMPT_PATH}" ]; then
|
|
634
|
+
log "FAIL: Missing reviewer prompt file. Checked instructions/, .claude/commands/, and bundled templates/"
|
|
635
|
+
emit_result "failure" "reason=missing_reviewer_prompt"
|
|
636
|
+
exit 1
|
|
637
|
+
fi
|
|
638
|
+
REVIEWER_PROMPT_BASE=$(cat "${REVIEWER_PROMPT_PATH}")
|
|
639
|
+
REVIEWER_PROMPT_REF=$(instruction_ref_for_prompt "${REVIEW_WORKTREE_DIR}" "${REVIEWER_PROMPT_PATH}")
|
|
640
|
+
log "INFO: Using reviewer prompt from ${REVIEWER_PROMPT_REF}"
|
|
641
|
+
|
|
482
642
|
EXIT_CODE=0
|
|
483
643
|
ATTEMPTS_MADE=1
|
|
484
644
|
FINAL_SCORE=""
|
|
485
645
|
TARGET_SCOPE_PROMPT=""
|
|
486
646
|
if [ -n "${TARGET_PR}" ]; then
|
|
487
647
|
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'
|
|
648
|
+
|
|
649
|
+
TARGET_MERGE_STATE=$(gh pr view "${TARGET_PR}" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null || echo "UNKNOWN")
|
|
650
|
+
TARGET_FAILED_CHECKS=$(get_pr_failed_ci_summary "${TARGET_PR}")
|
|
651
|
+
TARGET_SCORE=$(get_pr_score "${TARGET_PR}")
|
|
652
|
+
|
|
653
|
+
TARGET_SCOPE_PROMPT+=$'\n## Preflight Data (from CLI)\n- mergeStateStatus: '"${TARGET_MERGE_STATE}"$'\n'
|
|
654
|
+
if [ -n "${TARGET_FAILED_CHECKS}" ]; then
|
|
655
|
+
TARGET_SCOPE_PROMPT+=$'- failing checks: '"${TARGET_FAILED_CHECKS}"$'\n'
|
|
656
|
+
else
|
|
657
|
+
TARGET_SCOPE_PROMPT+=$'- failing checks: none detected\n'
|
|
658
|
+
fi
|
|
659
|
+
if [ -n "${TARGET_SCORE}" ]; then
|
|
660
|
+
TARGET_SCOPE_PROMPT+=$'- latest review score: '"${TARGET_SCORE}"$'/100\n'
|
|
661
|
+
else
|
|
662
|
+
TARGET_SCOPE_PROMPT+=$'- latest review score: not found\n'
|
|
663
|
+
fi
|
|
488
664
|
fi
|
|
489
665
|
|
|
490
666
|
# ── Retry Loop for Targeted PR Review ──────────────────────────────────────────
|
|
@@ -518,13 +694,14 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
518
694
|
fi
|
|
519
695
|
|
|
520
696
|
log "RETRY: Starting attempt ${ATTEMPT}/${TOTAL_ATTEMPTS} (timeout: ${ATTEMPT_TIMEOUT}s)"
|
|
697
|
+
LOG_LINE_BEFORE=$(wc -l < "${LOG_FILE}" 2>/dev/null || echo 0)
|
|
698
|
+
REVIEWER_PROMPT="${REVIEWER_PROMPT_BASE}${TARGET_SCOPE_PROMPT}"
|
|
521
699
|
|
|
522
700
|
case "${PROVIDER_CMD}" in
|
|
523
701
|
claude)
|
|
524
|
-
CLAUDE_PROMPT="/night-watch-pr-reviewer${TARGET_SCOPE_PROMPT}"
|
|
525
702
|
if (
|
|
526
703
|
cd "${REVIEW_WORKTREE_DIR}" && timeout "${ATTEMPT_TIMEOUT}" \
|
|
527
|
-
claude -p "${
|
|
704
|
+
claude -p "${REVIEWER_PROMPT}" \
|
|
528
705
|
--dangerously-skip-permissions \
|
|
529
706
|
>> "${LOG_FILE}" 2>&1
|
|
530
707
|
); then
|
|
@@ -534,12 +711,11 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
534
711
|
fi
|
|
535
712
|
;;
|
|
536
713
|
codex)
|
|
537
|
-
CODEX_PROMPT="$(cat "${REVIEW_WORKTREE_DIR}/.claude/commands/night-watch-pr-reviewer.md")${TARGET_SCOPE_PROMPT}"
|
|
538
714
|
if (
|
|
539
715
|
cd "${REVIEW_WORKTREE_DIR}" && timeout "${ATTEMPT_TIMEOUT}" \
|
|
540
716
|
codex --quiet \
|
|
541
717
|
--yolo \
|
|
542
|
-
--prompt "${
|
|
718
|
+
--prompt "${REVIEWER_PROMPT}" \
|
|
543
719
|
>> "${LOG_FILE}" 2>&1
|
|
544
720
|
); then
|
|
545
721
|
EXIT_CODE=0
|
|
@@ -553,8 +729,16 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
553
729
|
;;
|
|
554
730
|
esac
|
|
555
731
|
|
|
556
|
-
# If provider failed (non-zero exit),
|
|
732
|
+
# If provider failed (non-zero exit), check for rate limit before giving up
|
|
557
733
|
if [ "${EXIT_CODE}" -ne 0 ]; then
|
|
734
|
+
if [ "${EXIT_CODE}" -ne 124 ] && \
|
|
735
|
+
check_rate_limited "${LOG_FILE}" "${LOG_LINE_BEFORE}" && \
|
|
736
|
+
[ -n "${TARGET_PR}" ] && \
|
|
737
|
+
[ "${ATTEMPT}" -lt "${TOTAL_ATTEMPTS}" ]; then
|
|
738
|
+
log "RATE-LIMITED: 429 detected for PR #${TARGET_PR} (attempt ${ATTEMPT}/${TOTAL_ATTEMPTS}), retrying in 120s..."
|
|
739
|
+
sleep 120
|
|
740
|
+
continue
|
|
741
|
+
fi
|
|
558
742
|
log "RETRY: Provider exited with code ${EXIT_CODE}, not retrying"
|
|
559
743
|
break
|
|
560
744
|
fi
|
|
@@ -585,7 +769,7 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
585
769
|
fi
|
|
586
770
|
done
|
|
587
771
|
|
|
588
|
-
|
|
772
|
+
cleanup_reviewer_worktrees "${REVIEW_WORKTREE_BASENAME}"
|
|
589
773
|
|
|
590
774
|
# ── Auto-merge eligible PRs ─────────────────────────────────────────────────────
|
|
591
775
|
# After the reviewer completes, check for PRs that are merge-ready and queue them
|
|
@@ -217,11 +217,21 @@ Artifacts: ${QA_ARTIFACTS}"
|
|
|
217
217
|
continue
|
|
218
218
|
fi
|
|
219
219
|
|
|
220
|
+
QA_PROMPT_PATH=$(resolve_instruction_path "${QA_WORKTREE_DIR}" "night-watch-qa.md" || true)
|
|
221
|
+
if [ -z "${QA_PROMPT_PATH}" ]; then
|
|
222
|
+
log "FAIL: Missing QA prompt file for PR #${pr_num}. Checked instructions/, .claude/commands/, and bundled templates/"
|
|
223
|
+
EXIT_CODE=1
|
|
224
|
+
break
|
|
225
|
+
fi
|
|
226
|
+
QA_PROMPT=$(cat "${QA_PROMPT_PATH}")
|
|
227
|
+
QA_PROMPT_REF=$(instruction_ref_for_prompt "${QA_WORKTREE_DIR}" "${QA_PROMPT_PATH}")
|
|
228
|
+
log "QA: PR #${pr_num} — using prompt from ${QA_PROMPT_REF}"
|
|
229
|
+
|
|
220
230
|
case "${PROVIDER_CMD}" in
|
|
221
231
|
claude)
|
|
222
232
|
if (
|
|
223
233
|
cd "${QA_WORKTREE_DIR}" && timeout "${MAX_RUNTIME}" \
|
|
224
|
-
claude -p "
|
|
234
|
+
claude -p "${QA_PROMPT}" \
|
|
225
235
|
--dangerously-skip-permissions \
|
|
226
236
|
>> "${LOG_FILE}" 2>&1
|
|
227
237
|
); then
|
|
@@ -241,7 +251,7 @@ Artifacts: ${QA_ARTIFACTS}"
|
|
|
241
251
|
cd "${QA_WORKTREE_DIR}" && timeout "${MAX_RUNTIME}" \
|
|
242
252
|
codex --quiet \
|
|
243
253
|
--yolo \
|
|
244
|
-
--prompt "$
|
|
254
|
+
--prompt "${QA_PROMPT}" \
|
|
245
255
|
>> "${LOG_FILE}" 2>&1
|
|
246
256
|
); then
|
|
247
257
|
log "QA: PR #${pr_num} — provider completed successfully"
|