@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
- run_local_checks_for_head() {
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
- cd "${worktree_dir}"
317
- bash -lc "${LOCAL_CHECK_COMMAND}"
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
- check_exit=${PIPESTATUS[0]}
431
+ reviewer_exit=${PIPESTATUS[0]}
320
432
  set -e
321
433
 
322
- cleanup_local_check_worktree
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}: Local checks failed for head ${expected_head} (exit ${check_exit})"
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
- log "INFO: PR #${pr_number} (${pr_branch}): Local check fallback failed, skipping"
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.1",
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": {