@jonit-dev/night-watch-cli 1.8.14-beta.1 → 1.8.14-beta.3
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.
|
@@ -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,50 @@ 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
|
+
remaining=$(( MAX_RUNTIME - elapsed - 30 ))
|
|
414
|
+
if [ "${remaining}" -lt 120 ]; then
|
|
415
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Not enough merger runtime left for targeted reviewer repair (${remaining}s), skipping repair"
|
|
416
|
+
return 1
|
|
417
|
+
fi
|
|
418
|
+
|
|
419
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Running targeted reviewer repair after local check failure on head ${expected_head}"
|
|
420
|
+
|
|
314
421
|
set +e
|
|
315
422
|
(
|
|
316
|
-
|
|
317
|
-
|
|
423
|
+
NW_TARGET_PR="${pr_number}" \
|
|
424
|
+
NW_REVIEWER_WORKER_MODE="1" \
|
|
425
|
+
NW_REVIEWER_PARALLEL="0" \
|
|
426
|
+
NW_REVIEWER_MAX_RUNTIME="${remaining}" \
|
|
427
|
+
NW_TARGET_LOCAL_CHECK_COMMAND="${LOCAL_CHECK_COMMAND}" \
|
|
428
|
+
NW_TARGET_LOCAL_CHECK_OUTPUT="${LAST_LOCAL_CHECK_OUTPUT}" \
|
|
429
|
+
bash "${SCRIPT_DIR}/night-watch-pr-reviewer-cron.sh" "${PROJECT_DIR}"
|
|
318
430
|
) 2>&1 | tee -a "${LOG_FILE}"
|
|
319
|
-
|
|
431
|
+
reviewer_exit=${PIPESTATUS[0]}
|
|
320
432
|
set -e
|
|
321
433
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if [ "${check_exit}" -eq 0 ]; then
|
|
325
|
-
log "INFO: PR #${pr_number}: Local checks passed for head ${expected_head}"
|
|
434
|
+
if [ "${reviewer_exit}" -eq 0 ]; then
|
|
435
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Targeted reviewer repair completed"
|
|
326
436
|
return 0
|
|
327
437
|
fi
|
|
328
438
|
|
|
329
|
-
log "INFO: PR #${pr_number}:
|
|
439
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Targeted reviewer repair failed (exit ${reviewer_exit})"
|
|
330
440
|
return 1
|
|
331
441
|
}
|
|
332
442
|
|
|
@@ -349,10 +459,20 @@ ci_gate_allows_head() {
|
|
|
349
459
|
|
|
350
460
|
if [ "${CI_POLICY}" = "fallback-local" ]; then
|
|
351
461
|
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
|
|
462
|
+
if run_local_checks_for_head "${pr_number}" "${pr_branch}" "${expected_head}"; then
|
|
353
463
|
return 0
|
|
354
464
|
fi
|
|
355
|
-
|
|
465
|
+
if run_reviewer_repair_for_pr "${pr_number}" "${pr_branch}" "${expected_head}"; then
|
|
466
|
+
local repaired_head=""
|
|
467
|
+
repaired_head=$(get_pr_head_oid "${pr_number}")
|
|
468
|
+
if [ -n "${repaired_head}" ]; then
|
|
469
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Re-checking local checks after targeted repair on head ${repaired_head}"
|
|
470
|
+
if run_local_checks_for_head "${pr_number}" "${pr_branch}" "${repaired_head}"; then
|
|
471
|
+
return 0
|
|
472
|
+
fi
|
|
473
|
+
fi
|
|
474
|
+
fi
|
|
475
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Local check fallback failed after repair attempt, skipping"
|
|
356
476
|
return 1
|
|
357
477
|
fi
|
|
358
478
|
|
|
@@ -540,7 +660,7 @@ while IFS= read -r pr_json; do
|
|
|
540
660
|
if ! wait_for_ci_passing_on_head "${pr_number}" "${pr_head_after_rebase}"; then
|
|
541
661
|
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
662
|
if [ "${CI_POLICY}" = "fallback-local" ]; then
|
|
543
|
-
if ! run_local_checks_for_head "${pr_number}" "${pr_head_after_rebase}"; then
|
|
663
|
+
if ! run_local_checks_for_head "${pr_number}" "${pr_branch}" "${pr_head_after_rebase}"; then
|
|
544
664
|
log "INFO: PR #${pr_number}: Fresh local check fallback failed after rebase, skipping"
|
|
545
665
|
continue
|
|
546
666
|
fi
|
|
@@ -1202,6 +1202,15 @@ if [ -n "${TARGET_PR}" ]; then
|
|
|
1202
1202
|
TARGET_SCOPE_PROMPT+=$'- latest review score: not found\n'
|
|
1203
1203
|
TARGET_SCOPE_PROMPT+=$'- action: review\n'
|
|
1204
1204
|
fi
|
|
1205
|
+
if [ -n "${NW_TARGET_LOCAL_CHECK_COMMAND:-}" ]; then
|
|
1206
|
+
TARGET_SCOPE_PROMPT+=$'\n## Local Check Failure From Merge Gate\n'
|
|
1207
|
+
TARGET_SCOPE_PROMPT+=$'- command: '"${NW_TARGET_LOCAL_CHECK_COMMAND}"$'\n'
|
|
1208
|
+
TARGET_SCOPE_PROMPT+=$'- action: fix the PR so this local command passes before the PR can merge\n'
|
|
1209
|
+
if [ -n "${NW_TARGET_LOCAL_CHECK_OUTPUT:-}" ]; then
|
|
1210
|
+
TRUNCATED_LOCAL_CHECK_OUTPUT=$(printf '%s' "${NW_TARGET_LOCAL_CHECK_OUTPUT}" | head -c 10000)
|
|
1211
|
+
TARGET_SCOPE_PROMPT+=$'\n### Local Check Output\n```text\n'"${TRUNCATED_LOCAL_CHECK_OUTPUT}"$'\n```\n'
|
|
1212
|
+
fi
|
|
1213
|
+
fi
|
|
1205
1214
|
fi
|
|
1206
1215
|
|
|
1207
1216
|
PRD_CONTEXT_PROMPT=""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jonit-dev/night-watch-cli",
|
|
3
|
-
"version": "1.8.14-beta.
|
|
3
|
+
"version": "1.8.14-beta.3",
|
|
4
4
|
"description": "AI agent that implements your specs, opens PRs, and reviews code overnight. Queue GitHub issues or PRDs, wake up to pull requests.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|