@jonit-dev/night-watch-cli 1.8.14-beta.0 → 1.8.14-beta.10
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 +2230 -577
- package/dist/cli.js.map +1 -1
- package/dist/commands/dashboard/tab-config.js +2 -2
- package/dist/commands/dashboard/tab-config.js.map +1 -1
- package/dist/commands/dashboard/tab-schedules.d.ts +1 -1
- package/dist/commands/dashboard/tab-schedules.d.ts.map +1 -1
- package/dist/commands/dashboard/tab-schedules.js +15 -6
- package/dist/commands/dashboard/tab-schedules.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +50 -3
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +24 -4
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/manager.d.ts +23 -0
- package/dist/commands/manager.d.ts.map +1 -0
- package/dist/commands/manager.js +220 -0
- package/dist/commands/manager.js.map +1 -0
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/merge.js +4 -0
- package/dist/commands/merge.js.map +1 -1
- package/dist/commands/plan.js +1 -1
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/prd.d.ts.map +1 -1
- package/dist/commands/prd.js +6 -4
- package/dist/commands/prd.js.map +1 -1
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/queue.js +15 -7
- package/dist/commands/queue.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +2 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +10 -4
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +24 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +2 -0
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/ux.d.ts +14 -0
- package/dist/commands/ux.d.ts.map +1 -0
- package/dist/commands/ux.js +169 -0
- package/dist/commands/ux.js.map +1 -0
- package/dist/scripts/night-watch-audit-cron.sh +3 -3
- package/dist/scripts/night-watch-cron.sh +8 -7
- package/dist/scripts/night-watch-helpers.sh +23 -0
- package/dist/scripts/night-watch-manager-cron.sh +61 -0
- package/dist/scripts/night-watch-merger-cron.sh +254 -19
- package/dist/scripts/night-watch-plan-cron.sh +3 -3
- package/dist/scripts/night-watch-pr-resolver-cron.sh +8 -11
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +51 -21
- package/dist/scripts/night-watch-qa-cron.sh +3 -3
- package/dist/scripts/night-watch-slicer-cron.sh +3 -3
- package/dist/templates/night-watch.config.json +14 -0
- package/dist/web/assets/index-6Yf-Q6Di.js +442 -0
- package/dist/web/assets/index-BbiKFOgi.css +1 -0
- package/dist/web/assets/index-BheLL2O2.css +1 -0
- package/dist/web/assets/index-BsTuwxzi.js +447 -0
- package/dist/web/assets/index-DatF4suf.css +1 -0
- package/dist/web/assets/index-Q3IYCcdZ.js +447 -0
- package/dist/web/assets/index-uBao8iYf.js +447 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
|
@@ -8,12 +8,14 @@ set -euo pipefail
|
|
|
8
8
|
# (oldest PR first by creation date). Rebases remaining PRs after each merge.
|
|
9
9
|
#
|
|
10
10
|
# Required env vars (with defaults shown):
|
|
11
|
-
# NW_MERGER_MAX_RUNTIME=
|
|
11
|
+
# NW_MERGER_MAX_RUNTIME=0 - Maximum runtime in seconds (0 = no timeout)
|
|
12
12
|
# NW_MERGER_MERGE_METHOD=squash - Merge method: squash|merge|rebase
|
|
13
13
|
# NW_MERGER_MIN_REVIEW_SCORE=80 - Minimum review score threshold
|
|
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_POLICY=fallback-local - CI gate: ci-only|fallback-local|ignore
|
|
18
|
+
# NW_MERGER_LOCAL_CHECK_COMMAND=... - Command run in temp PR worktree for local fallback
|
|
17
19
|
# NW_MERGER_CI_MAX_WAIT=300 - Max seconds to wait for checks after rebase
|
|
18
20
|
# NW_MERGER_CI_POLL_INTERVAL=15 - Seconds between check polls after rebase
|
|
19
21
|
# NW_DRY_RUN=0 - Set to 1 for dry-run mode
|
|
@@ -22,7 +24,7 @@ PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
|
|
|
22
24
|
PROJECT_NAME=$(basename "${PROJECT_DIR}")
|
|
23
25
|
LOG_DIR="${PROJECT_DIR}/logs"
|
|
24
26
|
LOG_FILE="${LOG_DIR}/merger.log"
|
|
25
|
-
MAX_RUNTIME="${NW_MERGER_MAX_RUNTIME:-
|
|
27
|
+
MAX_RUNTIME="${NW_MERGER_MAX_RUNTIME:-0}"
|
|
26
28
|
MAX_LOG_SIZE="524288" # 512 KB
|
|
27
29
|
MERGE_METHOD="${NW_MERGER_MERGE_METHOD:-squash}"
|
|
28
30
|
MIN_REVIEW_SCORE="${NW_MERGER_MIN_REVIEW_SCORE:-80}"
|
|
@@ -30,6 +32,8 @@ REBASE_BEFORE_MERGE="${NW_MERGER_REBASE_BEFORE_MERGE:-1}"
|
|
|
30
32
|
MAX_PRS_PER_RUN="${NW_MERGER_MAX_PRS_PER_RUN:-0}"
|
|
31
33
|
CI_MAX_WAIT="${NW_MERGER_CI_MAX_WAIT:-300}"
|
|
32
34
|
CI_POLL_INTERVAL="${NW_MERGER_CI_POLL_INTERVAL:-15}"
|
|
35
|
+
CI_POLICY="${NW_MERGER_CI_POLICY:-fallback-local}"
|
|
36
|
+
LOCAL_CHECK_COMMAND="${NW_MERGER_LOCAL_CHECK_COMMAND:-yarn install --frozen-lockfile && yarn verify && yarn test}"
|
|
33
37
|
BRANCH_PATTERNS_RAW="${NW_MERGER_BRANCH_PATTERNS:-}"
|
|
34
38
|
READY_TO_MERGE_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
|
|
35
39
|
SCRIPT_START_TIME=$(date +%s)
|
|
@@ -55,6 +59,10 @@ case "${MERGE_METHOD}" in
|
|
|
55
59
|
squash|merge|rebase) ;;
|
|
56
60
|
*) MERGE_METHOD="squash" ;;
|
|
57
61
|
esac
|
|
62
|
+
case "${CI_POLICY}" in
|
|
63
|
+
ci-only|fallback-local|ignore) ;;
|
|
64
|
+
*) CI_POLICY="fallback-local" ;;
|
|
65
|
+
esac
|
|
58
66
|
|
|
59
67
|
mkdir -p "${LOG_DIR}"
|
|
60
68
|
|
|
@@ -71,6 +79,7 @@ skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
|
71
79
|
MERGED_PRS=0
|
|
72
80
|
FAILED_PRS=0
|
|
73
81
|
MERGED_PR_LIST=""
|
|
82
|
+
LAST_LOCAL_CHECK_OUTPUT=""
|
|
74
83
|
|
|
75
84
|
emit_result() {
|
|
76
85
|
local status="${1:?status required}"
|
|
@@ -261,6 +270,220 @@ wait_for_ci_passing_on_head() {
|
|
|
261
270
|
[ "${LAST_CI_STATUS}" = "passing" ]
|
|
262
271
|
}
|
|
263
272
|
|
|
273
|
+
find_existing_worktree_for_head() {
|
|
274
|
+
local pr_branch="${1}"
|
|
275
|
+
local expected_head="${2}"
|
|
276
|
+
local project_real=""
|
|
277
|
+
local worktree_path=""
|
|
278
|
+
local branch=""
|
|
279
|
+
local head=""
|
|
280
|
+
|
|
281
|
+
project_real=$(cd "${PROJECT_DIR}" && pwd -P 2>/dev/null || echo "${PROJECT_DIR}")
|
|
282
|
+
|
|
283
|
+
while IFS= read -r line; do
|
|
284
|
+
if [[ "${line}" == worktree\ * ]]; then
|
|
285
|
+
worktree_path="${line#worktree }"
|
|
286
|
+
branch=""
|
|
287
|
+
head=""
|
|
288
|
+
continue
|
|
289
|
+
fi
|
|
290
|
+
if [[ "${line}" == HEAD\ * ]]; then
|
|
291
|
+
head="${line#HEAD }"
|
|
292
|
+
continue
|
|
293
|
+
fi
|
|
294
|
+
if [[ "${line}" == branch\ * ]]; then
|
|
295
|
+
branch="${line#branch refs/heads/}"
|
|
296
|
+
if [ -n "${worktree_path}" ] \
|
|
297
|
+
&& [ "${branch}" = "${pr_branch}" ] \
|
|
298
|
+
&& [ "${head}" = "${expected_head}" ] \
|
|
299
|
+
&& [ -d "${worktree_path}" ]; then
|
|
300
|
+
local worktree_real=""
|
|
301
|
+
worktree_real=$(cd "${worktree_path}" && pwd -P 2>/dev/null || echo "${worktree_path}")
|
|
302
|
+
if [ "${worktree_real}" = "${project_real}" ]; then
|
|
303
|
+
continue
|
|
304
|
+
fi
|
|
305
|
+
if git -C "${worktree_path}" diff --quiet >/dev/null 2>&1 \
|
|
306
|
+
&& git -C "${worktree_path}" diff --cached --quiet >/dev/null 2>&1; then
|
|
307
|
+
printf "%s" "${worktree_path}"
|
|
308
|
+
return 0
|
|
309
|
+
fi
|
|
310
|
+
fi
|
|
311
|
+
fi
|
|
312
|
+
done < <(git worktree list --porcelain 2>/dev/null || true)
|
|
313
|
+
|
|
314
|
+
return 1
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
run_local_check_command_in_dir() {
|
|
318
|
+
local pr_number="${1}"
|
|
319
|
+
local expected_head="${2}"
|
|
320
|
+
local check_dir="${3}"
|
|
321
|
+
local check_exit=1
|
|
322
|
+
local output_file=""
|
|
323
|
+
|
|
324
|
+
output_file=$(mktemp "${TMPDIR:-/tmp}/night-watch-merger-local-check-${pr_number}.XXXXXX")
|
|
325
|
+
LAST_LOCAL_CHECK_OUTPUT=""
|
|
326
|
+
|
|
327
|
+
set +e
|
|
328
|
+
(
|
|
329
|
+
cd "${check_dir}"
|
|
330
|
+
bash -lc "${LOCAL_CHECK_COMMAND}"
|
|
331
|
+
) 2>&1 | tee "${output_file}" | tee -a "${LOG_FILE}"
|
|
332
|
+
check_exit=${PIPESTATUS[0]}
|
|
333
|
+
set -e
|
|
334
|
+
|
|
335
|
+
LAST_LOCAL_CHECK_OUTPUT=$(head -c 10000 "${output_file}" 2>/dev/null || true)
|
|
336
|
+
rm -f "${output_file}" 2>/dev/null || true
|
|
337
|
+
|
|
338
|
+
if [ "${check_exit}" -eq 0 ]; then
|
|
339
|
+
log "INFO: PR #${pr_number}: Local checks passed for head ${expected_head}"
|
|
340
|
+
return 0
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
log "INFO: PR #${pr_number}: Local checks failed for head ${expected_head} (exit ${check_exit})"
|
|
344
|
+
return 1
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
run_local_checks_for_head() {
|
|
348
|
+
local pr_number="${1}"
|
|
349
|
+
local pr_branch="${2}"
|
|
350
|
+
local expected_head="${3}"
|
|
351
|
+
local temp_parent=""
|
|
352
|
+
local worktree_dir=""
|
|
353
|
+
local existing_worktree_dir=""
|
|
354
|
+
local temp_ref=""
|
|
355
|
+
|
|
356
|
+
if [ -z "${LOCAL_CHECK_COMMAND}" ]; then
|
|
357
|
+
log "INFO: PR #${pr_number}: Local check command is empty, treating fallback as failed"
|
|
358
|
+
return 1
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
if existing_worktree_dir=$(find_existing_worktree_for_head "${pr_branch}" "${expected_head}"); then
|
|
362
|
+
log "INFO: PR #${pr_number}: Reusing existing worktree for local checks: ${existing_worktree_dir}"
|
|
363
|
+
run_local_check_command_in_dir "${pr_number}" "${expected_head}" "${existing_worktree_dir}"
|
|
364
|
+
return $?
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
temp_parent=$(mktemp -d "${TMPDIR:-/tmp}/night-watch-merger-${pr_number}.XXXXXX")
|
|
368
|
+
worktree_dir="${temp_parent}/worktree"
|
|
369
|
+
temp_ref="refs/night-watch/merger/${pr_number}-${expected_head}"
|
|
370
|
+
|
|
371
|
+
cleanup_local_check_worktree() {
|
|
372
|
+
git worktree remove --force "${worktree_dir}" >/dev/null 2>&1 || true
|
|
373
|
+
git update-ref -d "${temp_ref}" >/dev/null 2>&1 || true
|
|
374
|
+
rm -rf "${temp_parent}" >/dev/null 2>&1 || true
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
log "INFO: PR #${pr_number}: Running local checks for head ${expected_head}: ${LOCAL_CHECK_COMMAND}"
|
|
378
|
+
|
|
379
|
+
if ! git cat-file -e "${expected_head}^{commit}" >/dev/null 2>&1; then
|
|
380
|
+
log "INFO: PR #${pr_number}: Fetching PR head ${expected_head} for local checks"
|
|
381
|
+
git fetch --quiet --force origin "pull/${pr_number}/head:${temp_ref}" >/dev/null 2>&1 || {
|
|
382
|
+
log "INFO: PR #${pr_number}: Unable to fetch PR head for local checks"
|
|
383
|
+
cleanup_local_check_worktree
|
|
384
|
+
return 1
|
|
385
|
+
}
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
if ! git worktree add --detach --quiet "${worktree_dir}" "${expected_head}" >/dev/null 2>&1; then
|
|
389
|
+
if ! git worktree add --detach --quiet "${worktree_dir}" "${temp_ref}" >/dev/null 2>&1; then
|
|
390
|
+
log "INFO: PR #${pr_number}: Unable to create local check worktree"
|
|
391
|
+
cleanup_local_check_worktree
|
|
392
|
+
return 1
|
|
393
|
+
fi
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
run_local_check_command_in_dir "${pr_number}" "${expected_head}" "${worktree_dir}"
|
|
397
|
+
local check_result=$?
|
|
398
|
+
|
|
399
|
+
cleanup_local_check_worktree
|
|
400
|
+
|
|
401
|
+
return "${check_result}"
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
run_reviewer_repair_for_pr() {
|
|
405
|
+
local pr_number="${1}"
|
|
406
|
+
local pr_branch="${2}"
|
|
407
|
+
local expected_head="${3}"
|
|
408
|
+
local elapsed=0
|
|
409
|
+
local remaining=0
|
|
410
|
+
local reviewer_exit=1
|
|
411
|
+
|
|
412
|
+
elapsed=$(( $(date +%s) - SCRIPT_START_TIME ))
|
|
413
|
+
if is_runtime_limited "${MAX_RUNTIME}"; then
|
|
414
|
+
remaining=$(( MAX_RUNTIME - elapsed - 30 ))
|
|
415
|
+
else
|
|
416
|
+
remaining=0
|
|
417
|
+
fi
|
|
418
|
+
if is_runtime_limited "${MAX_RUNTIME}" && [ "${remaining}" -lt 120 ]; then
|
|
419
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Not enough merger runtime left for targeted reviewer repair (${remaining}s), skipping repair"
|
|
420
|
+
return 1
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Running targeted reviewer repair after local check failure on head ${expected_head}"
|
|
424
|
+
|
|
425
|
+
set +e
|
|
426
|
+
(
|
|
427
|
+
NW_TARGET_PR="${pr_number}" \
|
|
428
|
+
NW_REVIEWER_WORKER_MODE="1" \
|
|
429
|
+
NW_REVIEWER_PARALLEL="0" \
|
|
430
|
+
NW_REVIEWER_MAX_RUNTIME="${remaining}" \
|
|
431
|
+
NW_TARGET_LOCAL_CHECK_COMMAND="${LOCAL_CHECK_COMMAND}" \
|
|
432
|
+
NW_TARGET_LOCAL_CHECK_OUTPUT="${LAST_LOCAL_CHECK_OUTPUT}" \
|
|
433
|
+
bash "${SCRIPT_DIR}/night-watch-pr-reviewer-cron.sh" "${PROJECT_DIR}"
|
|
434
|
+
) 2>&1 | tee -a "${LOG_FILE}"
|
|
435
|
+
reviewer_exit=${PIPESTATUS[0]}
|
|
436
|
+
set -e
|
|
437
|
+
|
|
438
|
+
if [ "${reviewer_exit}" -eq 0 ]; then
|
|
439
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Targeted reviewer repair completed"
|
|
440
|
+
return 0
|
|
441
|
+
fi
|
|
442
|
+
|
|
443
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Targeted reviewer repair failed (exit ${reviewer_exit})"
|
|
444
|
+
return 1
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
ci_gate_allows_head() {
|
|
448
|
+
local pr_number="${1}"
|
|
449
|
+
local pr_branch="${2}"
|
|
450
|
+
local expected_head="${3}"
|
|
451
|
+
local context="${4:-CI}"
|
|
452
|
+
local ci_status
|
|
453
|
+
|
|
454
|
+
if [ "${CI_POLICY}" = "ignore" ]; then
|
|
455
|
+
log "INFO: PR #${pr_number} (${pr_branch}): CI policy is ignore; skipping ${context} gate for head ${expected_head}"
|
|
456
|
+
return 0
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
ci_status=$(ci_status_for_head "${pr_number}" "${expected_head}")
|
|
460
|
+
if [ "${ci_status}" = "passing" ]; then
|
|
461
|
+
return 0
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
if [ "${CI_POLICY}" = "fallback-local" ]; then
|
|
465
|
+
log "INFO: PR #${pr_number} (${pr_branch}): ${context} not passing on head ${expected_head} (${ci_status}); trying local checks"
|
|
466
|
+
if run_local_checks_for_head "${pr_number}" "${pr_branch}" "${expected_head}"; then
|
|
467
|
+
return 0
|
|
468
|
+
fi
|
|
469
|
+
if run_reviewer_repair_for_pr "${pr_number}" "${pr_branch}" "${expected_head}"; then
|
|
470
|
+
local repaired_head=""
|
|
471
|
+
repaired_head=$(get_pr_head_oid "${pr_number}")
|
|
472
|
+
if [ -n "${repaired_head}" ]; then
|
|
473
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Re-checking local checks after targeted repair on head ${repaired_head}"
|
|
474
|
+
if run_local_checks_for_head "${pr_number}" "${pr_branch}" "${repaired_head}"; then
|
|
475
|
+
return 0
|
|
476
|
+
fi
|
|
477
|
+
fi
|
|
478
|
+
fi
|
|
479
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Local check fallback failed after repair attempt, skipping"
|
|
480
|
+
return 1
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
log "INFO: PR #${pr_number} (${pr_branch}): ${context} not passing on head ${expected_head} (${ci_status}), skipping"
|
|
484
|
+
return 1
|
|
485
|
+
}
|
|
486
|
+
|
|
264
487
|
# Rebase a PR against its base branch
|
|
265
488
|
rebase_pr() {
|
|
266
489
|
local pr_number="${1}"
|
|
@@ -308,7 +531,7 @@ cd "${PROJECT_DIR}"
|
|
|
308
531
|
|
|
309
532
|
log "========================================"
|
|
310
533
|
log "RUN-START: merger invoked project=${PROJECT_DIR} dry_run=${DRY_RUN}"
|
|
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>}"
|
|
534
|
+
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 ci_policy=${CI_POLICY} local_check_command=${LOCAL_CHECK_COMMAND} ready_label=${READY_TO_MERGE_LABEL} branch_patterns=${BRANCH_PATTERNS_RAW:-<all>}"
|
|
312
535
|
log "========================================"
|
|
313
536
|
|
|
314
537
|
if ! acquire_lock "${LOCK_FILE}"; then
|
|
@@ -331,13 +554,15 @@ if [ "${DRY_RUN}" = "1" ]; then
|
|
|
331
554
|
fi
|
|
332
555
|
|
|
333
556
|
# Timeout watchdog
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
557
|
+
if is_runtime_limited "${MAX_RUNTIME}"; then
|
|
558
|
+
(
|
|
559
|
+
sleep "${MAX_RUNTIME}"
|
|
560
|
+
log "TIMEOUT: Merger exceeded ${MAX_RUNTIME}s, terminating"
|
|
561
|
+
kill -TERM $$ 2>/dev/null || true
|
|
562
|
+
) &
|
|
563
|
+
WATCHDOG_PID=$!
|
|
564
|
+
append_exit_trap "cleanup_watchdog ${WATCHDOG_PID}"
|
|
565
|
+
fi
|
|
341
566
|
|
|
342
567
|
# Discover open PRs sorted by creation date (oldest first = FIFO)
|
|
343
568
|
log "INFO: Scanning open PRs..."
|
|
@@ -391,10 +616,8 @@ while IFS= read -r pr_json; do
|
|
|
391
616
|
continue
|
|
392
617
|
fi
|
|
393
618
|
|
|
394
|
-
# Check CI status
|
|
395
|
-
|
|
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"
|
|
619
|
+
# Check CI status, optionally falling back to local checks for provider/billing failures.
|
|
620
|
+
if ! ci_gate_allows_head "${pr_number}" "${pr_branch}" "${pr_head_oid}" "CI"; then
|
|
398
621
|
continue
|
|
399
622
|
fi
|
|
400
623
|
|
|
@@ -436,10 +659,22 @@ while IFS= read -r pr_json; do
|
|
|
436
659
|
log "INFO: PR #${pr_number}: Head unchanged after rebase (${pr_head_after_rebase}); confirming CI"
|
|
437
660
|
fi
|
|
438
661
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
662
|
+
if [ "${CI_POLICY}" = "ignore" ]; then
|
|
663
|
+
log "INFO: PR #${pr_number}: CI policy is ignore; skipping fresh CI gate after rebase"
|
|
664
|
+
else
|
|
665
|
+
# Poll CI until all checks attached to the post-rebase head are complete and passing.
|
|
666
|
+
if ! wait_for_ci_passing_on_head "${pr_number}" "${pr_head_after_rebase}"; then
|
|
667
|
+
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)"
|
|
668
|
+
if [ "${CI_POLICY}" = "fallback-local" ]; then
|
|
669
|
+
if ! run_local_checks_for_head "${pr_number}" "${pr_branch}" "${pr_head_after_rebase}"; then
|
|
670
|
+
log "INFO: PR #${pr_number}: Fresh local check fallback failed after rebase, skipping"
|
|
671
|
+
continue
|
|
672
|
+
fi
|
|
673
|
+
else
|
|
674
|
+
log "INFO: PR #${pr_number}: Skipping because CI policy requires GitHub CI"
|
|
675
|
+
continue
|
|
676
|
+
fi
|
|
677
|
+
fi
|
|
443
678
|
fi
|
|
444
679
|
fi
|
|
445
680
|
|
|
@@ -484,7 +719,7 @@ while IFS= read -r pr_json; do
|
|
|
484
719
|
|
|
485
720
|
# Enforce global timeout
|
|
486
721
|
elapsed=$(( $(date +%s) - SCRIPT_START_TIME ))
|
|
487
|
-
if [ "${elapsed}" -ge "${MAX_RUNTIME}" ]; then
|
|
722
|
+
if is_runtime_limited "${MAX_RUNTIME}" && [ "${elapsed}" -ge "${MAX_RUNTIME}" ]; then
|
|
488
723
|
log "WARN: Global timeout reached (${MAX_RUNTIME}s), stopping early"
|
|
489
724
|
break
|
|
490
725
|
fi
|
|
@@ -9,7 +9,7 @@ set -euo pipefail
|
|
|
9
9
|
# Required env vars (with defaults shown):
|
|
10
10
|
# NW_PLAN_TASK='' - Task/feature description to plan (required)
|
|
11
11
|
# NW_PRD_DIR=docs/PRDs - PRD output directory (relative to project)
|
|
12
|
-
# NW_PLAN_MAX_RUNTIME=
|
|
12
|
+
# NW_PLAN_MAX_RUNTIME=0 - Maximum runtime in seconds (0 = no timeout)
|
|
13
13
|
# NW_PROVIDER_CMD=claude - AI provider CLI to use
|
|
14
14
|
# NW_DRY_RUN=0 - Set to 1 for dry-run mode
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ PROJECT_NAME=$(basename "${PROJECT_DIR}")
|
|
|
18
18
|
LOG_DIR="${PROJECT_DIR}/logs"
|
|
19
19
|
LOG_FILE="${LOG_DIR}/plan.log"
|
|
20
20
|
LOCK_FILE=""
|
|
21
|
-
MAX_RUNTIME="${NW_PLAN_MAX_RUNTIME:-
|
|
21
|
+
MAX_RUNTIME="${NW_PLAN_MAX_RUNTIME:-0}"
|
|
22
22
|
MAX_LOG_SIZE="524288" # 512 KB
|
|
23
23
|
PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
|
|
24
24
|
PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
|
|
@@ -132,7 +132,7 @@ EXIT_CODE=0
|
|
|
132
132
|
mapfile -d '' -t PROVIDER_CMD_PARTS < <(build_provider_cmd "${PROJECT_DIR}" "${CREATOR_PROMPT}")
|
|
133
133
|
|
|
134
134
|
# Execute in the project directory so the provider can explore the codebase
|
|
135
|
-
if (cd "${PROJECT_DIR}" &&
|
|
135
|
+
if (cd "${PROJECT_DIR}" && run_with_optional_timeout "${MAX_RUNTIME}" "${PROVIDER_CMD_PARTS[@]}" 2>&1 | tee -a "${LOG_FILE}"); then
|
|
136
136
|
EXIT_CODE=0
|
|
137
137
|
else
|
|
138
138
|
EXIT_CODE=$?
|
|
@@ -7,11 +7,11 @@ set -euo pipefail
|
|
|
7
7
|
# NOTE: This script expects environment variables to be set by the caller.
|
|
8
8
|
# The Node.js CLI will inject config values via environment variables.
|
|
9
9
|
# Required env vars (with defaults shown):
|
|
10
|
-
# NW_PR_RESOLVER_MAX_RUNTIME=
|
|
10
|
+
# NW_PR_RESOLVER_MAX_RUNTIME=0 - Maximum runtime in seconds (0 = no timeout)
|
|
11
11
|
# NW_PROVIDER_CMD=claude - AI provider CLI to use (claude, codex, etc.)
|
|
12
12
|
# NW_DRY_RUN=0 - Set to 1 for dry-run mode (prints diagnostics only)
|
|
13
13
|
# NW_PR_RESOLVER_MAX_PRS_PER_RUN=0 - Max PRs to process per run (0 = unlimited)
|
|
14
|
-
# NW_PR_RESOLVER_PER_PR_TIMEOUT=
|
|
14
|
+
# NW_PR_RESOLVER_PER_PR_TIMEOUT=0 - Per-PR AI timeout in seconds (0 = no timeout)
|
|
15
15
|
# NW_PR_RESOLVER_AI_CONFLICT_RESOLUTION=1 - Set to 1 to use AI for conflict resolution
|
|
16
16
|
# NW_PR_RESOLVER_AI_REVIEW_RESOLUTION=0 - Set to 1 to also address review comments
|
|
17
17
|
# NW_PR_RESOLVER_READY_LABEL=ready-to-merge - Label to add when PR is conflict-free
|
|
@@ -21,12 +21,12 @@ PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
|
|
|
21
21
|
PROJECT_NAME=$(basename "${PROJECT_DIR}")
|
|
22
22
|
LOG_DIR="${PROJECT_DIR}/logs"
|
|
23
23
|
LOG_FILE="${LOG_DIR}/pr-resolver.log"
|
|
24
|
-
MAX_RUNTIME="${NW_PR_RESOLVER_MAX_RUNTIME:-
|
|
24
|
+
MAX_RUNTIME="${NW_PR_RESOLVER_MAX_RUNTIME:-0}" # 0 = no global timeout
|
|
25
25
|
MAX_LOG_SIZE="524288" # 512 KB
|
|
26
26
|
PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
|
|
27
27
|
PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
|
|
28
28
|
MAX_PRS_PER_RUN="${NW_PR_RESOLVER_MAX_PRS_PER_RUN:-0}"
|
|
29
|
-
PER_PR_TIMEOUT="${NW_PR_RESOLVER_PER_PR_TIMEOUT:-
|
|
29
|
+
PER_PR_TIMEOUT="${NW_PR_RESOLVER_PER_PR_TIMEOUT:-0}"
|
|
30
30
|
AI_CONFLICT_RESOLUTION="${NW_PR_RESOLVER_AI_CONFLICT_RESOLUTION:-1}"
|
|
31
31
|
AI_REVIEW_RESOLUTION="${NW_PR_RESOLVER_AI_REVIEW_RESOLUTION:-0}"
|
|
32
32
|
READY_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
|
|
@@ -38,14 +38,11 @@ if ! [[ "${MAX_PRS_PER_RUN}" =~ ^[0-9]+$ ]]; then
|
|
|
38
38
|
MAX_PRS_PER_RUN="0"
|
|
39
39
|
fi
|
|
40
40
|
if ! [[ "${PER_PR_TIMEOUT}" =~ ^[0-9]+$ ]]; then
|
|
41
|
-
PER_PR_TIMEOUT="
|
|
41
|
+
PER_PR_TIMEOUT="0"
|
|
42
42
|
fi
|
|
43
43
|
if [ "${MAX_PRS_PER_RUN}" -gt 100 ]; then
|
|
44
44
|
MAX_PRS_PER_RUN="100"
|
|
45
45
|
fi
|
|
46
|
-
if [ "${PER_PR_TIMEOUT}" -gt 3600 ]; then
|
|
47
|
-
PER_PR_TIMEOUT="3600"
|
|
48
|
-
fi
|
|
49
46
|
|
|
50
47
|
mkdir -p "${LOG_DIR}"
|
|
51
48
|
|
|
@@ -183,7 +180,7 @@ Work exclusively in the directory: ${worktree_dir}"
|
|
|
183
180
|
local -a cmd_parts
|
|
184
181
|
mapfile -d '' -t cmd_parts < <(build_provider_cmd "${worktree_dir}" "${ai_prompt}")
|
|
185
182
|
|
|
186
|
-
if
|
|
183
|
+
if run_with_optional_timeout "${PER_PR_TIMEOUT}" "${cmd_parts[@]}" >> "${LOG_FILE}" 2>&1; then
|
|
187
184
|
rebase_success=1
|
|
188
185
|
log "INFO: AI resolved conflicts for PR #${pr_number}" "branch=${pr_branch}"
|
|
189
186
|
else
|
|
@@ -241,7 +238,7 @@ Work in the directory: ${review_workdir}"
|
|
|
241
238
|
local -a review_cmd_parts
|
|
242
239
|
mapfile -d '' -t review_cmd_parts < <(build_provider_cmd "${review_workdir}" "${review_prompt}")
|
|
243
240
|
|
|
244
|
-
if
|
|
241
|
+
if run_with_optional_timeout "${PER_PR_TIMEOUT}" "${review_cmd_parts[@]}" >> "${LOG_FILE}" 2>&1; then
|
|
245
242
|
log "INFO: AI addressed review comments for PR #${pr_number}" "branch=${pr_branch}"
|
|
246
243
|
else
|
|
247
244
|
log "WARN: AI failed to address review comments for PR #${pr_number}" "branch=${pr_branch}"
|
|
@@ -379,7 +376,7 @@ while IFS= read -r pr_line; do
|
|
|
379
376
|
|
|
380
377
|
# Enforce global timeout
|
|
381
378
|
elapsed=$(( $(date +%s) - SCRIPT_START_TIME ))
|
|
382
|
-
if [ "${elapsed}" -ge "${MAX_RUNTIME}" ]; then
|
|
379
|
+
if is_runtime_limited "${MAX_RUNTIME}" && [ "${elapsed}" -ge "${MAX_RUNTIME}" ]; then
|
|
383
380
|
log "WARN: Global timeout reached (${MAX_RUNTIME}s), stopping early"
|
|
384
381
|
break
|
|
385
382
|
fi
|
|
@@ -7,7 +7,7 @@ set -euo pipefail
|
|
|
7
7
|
# NOTE: This script expects environment variables to be set by the caller.
|
|
8
8
|
# The Node.js CLI will inject config values via environment variables.
|
|
9
9
|
# Required env vars (with defaults shown):
|
|
10
|
-
# NW_REVIEWER_MAX_RUNTIME=
|
|
10
|
+
# NW_REVIEWER_MAX_RUNTIME=0 - Maximum runtime in seconds (0 = no timeout)
|
|
11
11
|
# NW_PROVIDER_CMD=claude - AI provider CLI to use (claude, codex, etc.)
|
|
12
12
|
# NW_DRY_RUN=0 - Set to 1 for dry-run mode (prints diagnostics only)
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
|
|
|
15
15
|
PROJECT_NAME=$(basename "${PROJECT_DIR}")
|
|
16
16
|
LOG_DIR="${PROJECT_DIR}/logs"
|
|
17
17
|
LOG_FILE="${LOG_DIR}/reviewer.log"
|
|
18
|
-
MAX_RUNTIME="${NW_REVIEWER_MAX_RUNTIME:-
|
|
18
|
+
MAX_RUNTIME="${NW_REVIEWER_MAX_RUNTIME:-0}" # 0 = no provider timeout
|
|
19
19
|
MAX_LOG_SIZE="524288" # 512 KB
|
|
20
20
|
PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
|
|
21
21
|
PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
|
|
@@ -858,6 +858,17 @@ while IFS=$'\t' read -r pr_number pr_branch pr_labels; do
|
|
|
858
858
|
continue
|
|
859
859
|
fi
|
|
860
860
|
|
|
861
|
+
if [ -n "${TARGET_PR}" ] && [ "${pr_number}" = "${TARGET_PR}" ] && [ -n "${NW_TARGET_LOCAL_CHECK_COMMAND:-}" ]; then
|
|
862
|
+
if [ "${local_ready_for_review_label_present}" -eq 1 ]; then
|
|
863
|
+
log "INFO: PR #${pr_number} (${pr_branch}) failed merge-gate local checks; removing stale ${READY_FOR_REVIEW_LABEL} label"
|
|
864
|
+
clear_ready_for_human_review_label "${pr_number}"
|
|
865
|
+
fi
|
|
866
|
+
log "INFO: PR #${pr_number} (${pr_branch}) failed merge-gate local checks; targeted repair required"
|
|
867
|
+
NEEDS_WORK=1
|
|
868
|
+
PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
|
|
869
|
+
continue
|
|
870
|
+
fi
|
|
871
|
+
|
|
861
872
|
if has_ready_for_human_review_marker "${all_comments}" "${current_head_sha}"; then
|
|
862
873
|
SKIPPED_ALREADY_REVIEWED_CURRENT_HEAD=1
|
|
863
874
|
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"
|
|
@@ -1001,21 +1012,24 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
1001
1012
|
worker_pr="${WORKER_PRS[$idx]}"
|
|
1002
1013
|
worker_output="${WORKER_OUTPUTS[$idx]}"
|
|
1003
1014
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
+
watchdog_pid=""
|
|
1016
|
+
if is_runtime_limited "${MAX_RUNTIME}"; then
|
|
1017
|
+
# Guard: abort the wait loop when the global budget is exhausted
|
|
1018
|
+
PARENT_ELAPSED=$(( $(date +%s) - SCRIPT_START_TIME ))
|
|
1019
|
+
PARENT_REMAINING=$(( MAX_RUNTIME - PARENT_ELAPSED ))
|
|
1020
|
+
if [ "${PARENT_REMAINING}" -le 0 ]; then
|
|
1021
|
+
log "PARALLEL: global timeout exhausted — killing remaining workers"
|
|
1022
|
+
for remaining_idx in $(seq "${idx}" $(( ${#WORKER_PIDS[@]} - 1 ))); do
|
|
1023
|
+
kill "${WORKER_PIDS[$remaining_idx]}" 2>/dev/null || true
|
|
1024
|
+
done
|
|
1025
|
+
EXIT_CODE=124
|
|
1026
|
+
break
|
|
1027
|
+
fi
|
|
1015
1028
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1029
|
+
# Watchdog: kill the worker if it outlives the remaining budget
|
|
1030
|
+
( sleep "${PARENT_REMAINING}" 2>/dev/null; kill "${worker_pid}" 2>/dev/null || true ) &
|
|
1031
|
+
watchdog_pid=$!
|
|
1032
|
+
fi
|
|
1019
1033
|
|
|
1020
1034
|
worker_exit_code=0
|
|
1021
1035
|
if wait "${worker_pid}" 2>/dev/null; then
|
|
@@ -1025,8 +1039,10 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
1025
1039
|
fi
|
|
1026
1040
|
|
|
1027
1041
|
# Cancel the watchdog — the worker finished in time
|
|
1028
|
-
|
|
1029
|
-
|
|
1042
|
+
if [ -n "${watchdog_pid}" ]; then
|
|
1043
|
+
kill "${watchdog_pid}" 2>/dev/null || true
|
|
1044
|
+
wait "${watchdog_pid}" 2>/dev/null || true
|
|
1045
|
+
fi
|
|
1030
1046
|
|
|
1031
1047
|
if [ -f "${worker_output}" ] && [ -s "${worker_output}" ]; then
|
|
1032
1048
|
cat "${worker_output}" >> "${LOG_FILE}"
|
|
@@ -1202,6 +1218,15 @@ if [ -n "${TARGET_PR}" ]; then
|
|
|
1202
1218
|
TARGET_SCOPE_PROMPT+=$'- latest review score: not found\n'
|
|
1203
1219
|
TARGET_SCOPE_PROMPT+=$'- action: review\n'
|
|
1204
1220
|
fi
|
|
1221
|
+
if [ -n "${NW_TARGET_LOCAL_CHECK_COMMAND:-}" ]; then
|
|
1222
|
+
TARGET_SCOPE_PROMPT+=$'\n## Local Check Failure From Merge Gate\n'
|
|
1223
|
+
TARGET_SCOPE_PROMPT+=$'- command: '"${NW_TARGET_LOCAL_CHECK_COMMAND}"$'\n'
|
|
1224
|
+
TARGET_SCOPE_PROMPT+=$'- action: fix the PR so this local command passes before the PR can merge\n'
|
|
1225
|
+
if [ -n "${NW_TARGET_LOCAL_CHECK_OUTPUT:-}" ]; then
|
|
1226
|
+
TRUNCATED_LOCAL_CHECK_OUTPUT=$(printf '%s' "${NW_TARGET_LOCAL_CHECK_OUTPUT}" | head -c 10000)
|
|
1227
|
+
TARGET_SCOPE_PROMPT+=$'\n### Local Check Output\n```text\n'"${TRUNCATED_LOCAL_CHECK_OUTPUT}"$'\n```\n'
|
|
1228
|
+
fi
|
|
1229
|
+
fi
|
|
1205
1230
|
fi
|
|
1206
1231
|
|
|
1207
1232
|
PRD_CONTEXT_PROMPT=""
|
|
@@ -1248,6 +1273,11 @@ if [ -n "${TARGET_PR}" ]; then
|
|
|
1248
1273
|
fi
|
|
1249
1274
|
|
|
1250
1275
|
remaining_runtime_budget() {
|
|
1276
|
+
if ! is_runtime_limited "${MAX_RUNTIME}"; then
|
|
1277
|
+
printf "0"
|
|
1278
|
+
return 0
|
|
1279
|
+
fi
|
|
1280
|
+
|
|
1251
1281
|
local now_ms
|
|
1252
1282
|
local elapsed_ms
|
|
1253
1283
|
local remaining_ms
|
|
@@ -1279,7 +1309,7 @@ sleep_with_runtime_budget() {
|
|
|
1279
1309
|
return 0
|
|
1280
1310
|
fi
|
|
1281
1311
|
|
|
1282
|
-
if [ -z "${TARGET_PR}" ]; then
|
|
1312
|
+
if [ -z "${TARGET_PR}" ] || ! is_runtime_limited "${MAX_RUNTIME}"; then
|
|
1283
1313
|
sleep "${requested_sleep}"
|
|
1284
1314
|
return 0
|
|
1285
1315
|
fi
|
|
@@ -1305,7 +1335,7 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
1305
1335
|
ATTEMPTS_MADE="${ATTEMPT}"
|
|
1306
1336
|
|
|
1307
1337
|
ATTEMPT_TIMEOUT="${MAX_RUNTIME}"
|
|
1308
|
-
if [ -n "${TARGET_PR}" ]; then
|
|
1338
|
+
if [ -n "${TARGET_PR}" ] && is_runtime_limited "${MAX_RUNTIME}"; then
|
|
1309
1339
|
# Give each targeted attempt the full remaining runtime budget.
|
|
1310
1340
|
# Retries only happen after a quick return (low score / invalid output / rate limit);
|
|
1311
1341
|
# a timed-out provider run is not retried, so pre-splitting the budget would
|
|
@@ -1347,7 +1377,7 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
1347
1377
|
mapfile -d '' -t PROVIDER_CMD_PARTS < <(build_provider_cmd "${REVIEW_WORKTREE_DIR}" "${REVIEWER_PROMPT}")
|
|
1348
1378
|
|
|
1349
1379
|
# Execute — always cd into worktree so provider tools resolve project files correctly
|
|
1350
|
-
if (cd "${REVIEW_WORKTREE_DIR}" &&
|
|
1380
|
+
if (cd "${REVIEW_WORKTREE_DIR}" && run_with_optional_timeout "${ATTEMPT_TIMEOUT}" "${PROVIDER_CMD_PARTS[@]}" 2>&1 | tee -a "${LOG_FILE}"); then
|
|
1351
1381
|
EXIT_CODE=0
|
|
1352
1382
|
else
|
|
1353
1383
|
EXIT_CODE=$?
|
|
@@ -7,7 +7,7 @@ set -euo pipefail
|
|
|
7
7
|
# NOTE: This script expects environment variables to be set by the caller.
|
|
8
8
|
# The Node.js CLI will inject config values via environment variables.
|
|
9
9
|
# Required env vars (with defaults shown):
|
|
10
|
-
# NW_QA_MAX_RUNTIME=
|
|
10
|
+
# NW_QA_MAX_RUNTIME=0 - Maximum runtime in seconds (0 = no timeout)
|
|
11
11
|
# NW_PROVIDER_CMD=claude - AI provider CLI to use (claude, codex, etc.)
|
|
12
12
|
# NW_BRANCH_PATTERNS=feat/,night-watch/ - Comma-separated branch prefixes to match
|
|
13
13
|
# NW_QA_SKIP_LABEL=skip-qa - Label to skip QA on a PR
|
|
@@ -19,7 +19,7 @@ PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
|
|
|
19
19
|
PROJECT_NAME=$(basename "${PROJECT_DIR}")
|
|
20
20
|
LOG_DIR="${PROJECT_DIR}/logs"
|
|
21
21
|
LOG_FILE="${LOG_DIR}/night-watch-qa.log"
|
|
22
|
-
MAX_RUNTIME="${NW_QA_MAX_RUNTIME:-
|
|
22
|
+
MAX_RUNTIME="${NW_QA_MAX_RUNTIME:-0}" # 0 = no provider timeout
|
|
23
23
|
MAX_LOG_SIZE="524288" # 512 KB
|
|
24
24
|
PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
|
|
25
25
|
PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
|
|
@@ -599,7 +599,7 @@ for pr_ref in ${PRS_NEEDING_QA}; do
|
|
|
599
599
|
mapfile -d '' -t PROVIDER_CMD_PARTS < <(build_provider_cmd "${QA_WORKTREE_DIR}" "${QA_PROMPT}")
|
|
600
600
|
|
|
601
601
|
# Execute — always cd into worktree so provider tools resolve project files correctly
|
|
602
|
-
if (cd "${QA_WORKTREE_DIR}" &&
|
|
602
|
+
if (cd "${QA_WORKTREE_DIR}" && run_with_optional_timeout "${MAX_RUNTIME}" "${PROVIDER_CMD_PARTS[@]}" 2>&1 | tee -a "${LOG_FILE}"); then
|
|
603
603
|
PROVIDER_OK=1
|
|
604
604
|
else
|
|
605
605
|
local_exit=$?
|
|
@@ -10,7 +10,7 @@ set -euo pipefail
|
|
|
10
10
|
# NOTE: This script expects environment variables to be set by the caller.
|
|
11
11
|
# The Node.js CLI will inject config values via environment variables.
|
|
12
12
|
# Required env vars (with defaults shown):
|
|
13
|
-
# NW_SLICER_MAX_RUNTIME=
|
|
13
|
+
# NW_SLICER_MAX_RUNTIME=0 - Maximum runtime in seconds (0 = no timeout)
|
|
14
14
|
# NW_PROVIDER_CMD=claude - AI provider CLI to use (claude, codex, etc.)
|
|
15
15
|
# NW_DRY_RUN=0 - Set to 1 for dry-run mode (prints diagnostics only)
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ PROJECT_NAME=$(basename "${PROJECT_DIR}")
|
|
|
19
19
|
LOG_DIR="${PROJECT_DIR}/logs"
|
|
20
20
|
LOG_FILE="${LOG_DIR}/slicer.log"
|
|
21
21
|
LOCK_FILE=""
|
|
22
|
-
MAX_RUNTIME="${NW_SLICER_MAX_RUNTIME:-
|
|
22
|
+
MAX_RUNTIME="${NW_SLICER_MAX_RUNTIME:-0}" # 0 = no timeout
|
|
23
23
|
MAX_LOG_SIZE="524288" # 512 KB
|
|
24
24
|
PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
|
|
25
25
|
PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
|
|
@@ -110,7 +110,7 @@ fi
|
|
|
110
110
|
EXIT_CODE=0
|
|
111
111
|
SLICER_RUN_START=$(date +%s)
|
|
112
112
|
log "SLICER: Starting night-watch slice timeout=${MAX_RUNTIME}s"
|
|
113
|
-
if
|
|
113
|
+
if run_with_optional_timeout "${MAX_RUNTIME}" "${CLI_BIN}" slice >> "${LOG_FILE}" 2>&1; then
|
|
114
114
|
EXIT_CODE=0
|
|
115
115
|
else
|
|
116
116
|
EXIT_CODE=$?
|