@jonit-dev/night-watch-cli 1.8.14-beta.1 → 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 +2212 -582
- 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/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 +149 -23
- 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-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,7 +8,7 @@ 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)
|
|
@@ -24,7 +24,7 @@ PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
|
|
|
24
24
|
PROJECT_NAME=$(basename "${PROJECT_DIR}")
|
|
25
25
|
LOG_DIR="${PROJECT_DIR}/logs"
|
|
26
26
|
LOG_FILE="${LOG_DIR}/merger.log"
|
|
27
|
-
MAX_RUNTIME="${NW_MERGER_MAX_RUNTIME:-
|
|
27
|
+
MAX_RUNTIME="${NW_MERGER_MAX_RUNTIME:-0}"
|
|
28
28
|
MAX_LOG_SIZE="524288" # 512 KB
|
|
29
29
|
MERGE_METHOD="${NW_MERGER_MERGE_METHOD:-squash}"
|
|
30
30
|
MIN_REVIEW_SCORE="${NW_MERGER_MIN_REVIEW_SCORE:-80}"
|
|
@@ -79,6 +79,7 @@ skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
|
79
79
|
MERGED_PRS=0
|
|
80
80
|
FAILED_PRS=0
|
|
81
81
|
MERGED_PR_LIST=""
|
|
82
|
+
LAST_LOCAL_CHECK_OUTPUT=""
|
|
82
83
|
|
|
83
84
|
emit_result() {
|
|
84
85
|
local status="${1:?status required}"
|
|
@@ -269,19 +270,100 @@ wait_for_ci_passing_on_head() {
|
|
|
269
270
|
[ "${LAST_CI_STATUS}" = "passing" ]
|
|
270
271
|
}
|
|
271
272
|
|
|
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() {
|
|
273
318
|
local pr_number="${1}"
|
|
274
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}"
|
|
275
351
|
local temp_parent=""
|
|
276
352
|
local worktree_dir=""
|
|
353
|
+
local existing_worktree_dir=""
|
|
277
354
|
local temp_ref=""
|
|
278
|
-
local check_exit=1
|
|
279
355
|
|
|
280
356
|
if [ -z "${LOCAL_CHECK_COMMAND}" ]; then
|
|
281
357
|
log "INFO: PR #${pr_number}: Local check command is empty, treating fallback as failed"
|
|
282
358
|
return 1
|
|
283
359
|
fi
|
|
284
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
|
+
|
|
285
367
|
temp_parent=$(mktemp -d "${TMPDIR:-/tmp}/night-watch-merger-${pr_number}.XXXXXX")
|
|
286
368
|
worktree_dir="${temp_parent}/worktree"
|
|
287
369
|
temp_ref="refs/night-watch/merger/${pr_number}-${expected_head}"
|
|
@@ -311,22 +393,54 @@ run_local_checks_for_head() {
|
|
|
311
393
|
fi
|
|
312
394
|
fi
|
|
313
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
|
+
|
|
314
425
|
set +e
|
|
315
426
|
(
|
|
316
|
-
|
|
317
|
-
|
|
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}"
|
|
318
434
|
) 2>&1 | tee -a "${LOG_FILE}"
|
|
319
|
-
|
|
435
|
+
reviewer_exit=${PIPESTATUS[0]}
|
|
320
436
|
set -e
|
|
321
437
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if [ "${check_exit}" -eq 0 ]; then
|
|
325
|
-
log "INFO: PR #${pr_number}: Local checks passed for head ${expected_head}"
|
|
438
|
+
if [ "${reviewer_exit}" -eq 0 ]; then
|
|
439
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Targeted reviewer repair completed"
|
|
326
440
|
return 0
|
|
327
441
|
fi
|
|
328
442
|
|
|
329
|
-
log "INFO: PR #${pr_number}:
|
|
443
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Targeted reviewer repair failed (exit ${reviewer_exit})"
|
|
330
444
|
return 1
|
|
331
445
|
}
|
|
332
446
|
|
|
@@ -349,10 +463,20 @@ ci_gate_allows_head() {
|
|
|
349
463
|
|
|
350
464
|
if [ "${CI_POLICY}" = "fallback-local" ]; then
|
|
351
465
|
log "INFO: PR #${pr_number} (${pr_branch}): ${context} not passing on head ${expected_head} (${ci_status}); trying local checks"
|
|
352
|
-
if run_local_checks_for_head "${pr_number}" "${expected_head}"; then
|
|
466
|
+
if run_local_checks_for_head "${pr_number}" "${pr_branch}" "${expected_head}"; then
|
|
353
467
|
return 0
|
|
354
468
|
fi
|
|
355
|
-
|
|
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"
|
|
356
480
|
return 1
|
|
357
481
|
fi
|
|
358
482
|
|
|
@@ -430,13 +554,15 @@ if [ "${DRY_RUN}" = "1" ]; then
|
|
|
430
554
|
fi
|
|
431
555
|
|
|
432
556
|
# Timeout watchdog
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
|
440
566
|
|
|
441
567
|
# Discover open PRs sorted by creation date (oldest first = FIFO)
|
|
442
568
|
log "INFO: Scanning open PRs..."
|
|
@@ -540,7 +666,7 @@ while IFS= read -r pr_json; do
|
|
|
540
666
|
if ! wait_for_ci_passing_on_head "${pr_number}" "${pr_head_after_rebase}"; then
|
|
541
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)"
|
|
542
668
|
if [ "${CI_POLICY}" = "fallback-local" ]; then
|
|
543
|
-
if ! run_local_checks_for_head "${pr_number}" "${pr_head_after_rebase}"; then
|
|
669
|
+
if ! run_local_checks_for_head "${pr_number}" "${pr_branch}" "${pr_head_after_rebase}"; then
|
|
544
670
|
log "INFO: PR #${pr_number}: Fresh local check fallback failed after rebase, skipping"
|
|
545
671
|
continue
|
|
546
672
|
fi
|
|
@@ -593,7 +719,7 @@ while IFS= read -r pr_json; do
|
|
|
593
719
|
|
|
594
720
|
# Enforce global timeout
|
|
595
721
|
elapsed=$(( $(date +%s) - SCRIPT_START_TIME ))
|
|
596
|
-
if [ "${elapsed}" -ge "${MAX_RUNTIME}" ]; then
|
|
722
|
+
if is_runtime_limited "${MAX_RUNTIME}" && [ "${elapsed}" -ge "${MAX_RUNTIME}" ]; then
|
|
597
723
|
log "WARN: Global timeout reached (${MAX_RUNTIME}s), stopping early"
|
|
598
724
|
break
|
|
599
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=$?
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"reviewer": 40,
|
|
53
53
|
"slicer": 30,
|
|
54
54
|
"qa": 20,
|
|
55
|
+
"ux": 10,
|
|
55
56
|
"audit": 10
|
|
56
57
|
}
|
|
57
58
|
},
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"slicer",
|
|
66
67
|
"qa",
|
|
67
68
|
"audit",
|
|
69
|
+
"ux",
|
|
68
70
|
"analytics",
|
|
69
71
|
"merger"
|
|
70
72
|
],
|
|
@@ -94,5 +96,17 @@
|
|
|
94
96
|
"enabled": true,
|
|
95
97
|
"schedule": "50 3 * * 1",
|
|
96
98
|
"maxRuntime": 1800
|
|
99
|
+
},
|
|
100
|
+
"ux": {
|
|
101
|
+
"enabled": false,
|
|
102
|
+
"schedule": "0 7 * * 1",
|
|
103
|
+
"maxRuntime": 0,
|
|
104
|
+
"targetColumn": "Draft",
|
|
105
|
+
"baseUrl": "",
|
|
106
|
+
"startUrl": "",
|
|
107
|
+
"flows": [],
|
|
108
|
+
"autoInstallPlaywright": true,
|
|
109
|
+
"maxIssues": 10,
|
|
110
|
+
"reportPrompt": ""
|
|
97
111
|
}
|
|
98
112
|
}
|