@jonit-dev/night-watch-cli 1.7.27 → 1.7.30

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.
Files changed (73) hide show
  1. package/dist/shared/types.d.ts +1 -0
  2. package/dist/shared/types.d.ts.map +1 -1
  3. package/dist/src/cli.js +3 -0
  4. package/dist/src/cli.js.map +1 -1
  5. package/dist/src/commands/audit.d.ts +19 -0
  6. package/dist/src/commands/audit.d.ts.map +1 -0
  7. package/dist/src/commands/audit.js +109 -0
  8. package/dist/src/commands/audit.js.map +1 -0
  9. package/dist/src/commands/dashboard.js +1 -1
  10. package/dist/src/commands/dashboard.js.map +1 -1
  11. package/dist/src/commands/init.d.ts.map +1 -1
  12. package/dist/src/commands/init.js +6 -6
  13. package/dist/src/commands/init.js.map +1 -1
  14. package/dist/src/commands/install.d.ts +4 -0
  15. package/dist/src/commands/install.d.ts.map +1 -1
  16. package/dist/src/commands/install.js +25 -20
  17. package/dist/src/commands/install.js.map +1 -1
  18. package/dist/src/commands/logs.js +3 -3
  19. package/dist/src/commands/logs.js.map +1 -1
  20. package/dist/src/commands/prs.js +2 -2
  21. package/dist/src/commands/prs.js.map +1 -1
  22. package/dist/src/commands/review.d.ts.map +1 -1
  23. package/dist/src/commands/review.js +13 -5
  24. package/dist/src/commands/review.js.map +1 -1
  25. package/dist/src/commands/uninstall.d.ts.map +1 -1
  26. package/dist/src/commands/uninstall.js +3 -22
  27. package/dist/src/commands/uninstall.js.map +1 -1
  28. package/dist/src/config.d.ts.map +1 -1
  29. package/dist/src/config.js +30 -1
  30. package/dist/src/config.js.map +1 -1
  31. package/dist/src/constants.d.ts +10 -3
  32. package/dist/src/constants.d.ts.map +1 -1
  33. package/dist/src/constants.js +15 -2
  34. package/dist/src/constants.js.map +1 -1
  35. package/dist/src/server/index.d.ts.map +1 -1
  36. package/dist/src/server/index.js +50 -3
  37. package/dist/src/server/index.js.map +1 -1
  38. package/dist/src/slack/client.d.ts +3 -2
  39. package/dist/src/slack/client.d.ts.map +1 -1
  40. package/dist/src/slack/client.js +5 -6
  41. package/dist/src/slack/client.js.map +1 -1
  42. package/dist/src/slack/deliberation.d.ts +13 -1
  43. package/dist/src/slack/deliberation.d.ts.map +1 -1
  44. package/dist/src/slack/deliberation.js +585 -71
  45. package/dist/src/slack/deliberation.js.map +1 -1
  46. package/dist/src/slack/interaction-listener.d.ts +27 -9
  47. package/dist/src/slack/interaction-listener.d.ts.map +1 -1
  48. package/dist/src/slack/interaction-listener.js +418 -201
  49. package/dist/src/slack/interaction-listener.js.map +1 -1
  50. package/dist/src/storage/repositories/sqlite/agent-persona-repository.d.ts +3 -2
  51. package/dist/src/storage/repositories/sqlite/agent-persona-repository.d.ts.map +1 -1
  52. package/dist/src/storage/repositories/sqlite/agent-persona-repository.js +14 -11
  53. package/dist/src/storage/repositories/sqlite/agent-persona-repository.js.map +1 -1
  54. package/dist/src/types.d.ts +13 -0
  55. package/dist/src/types.d.ts.map +1 -1
  56. package/dist/src/utils/notify.d.ts.map +1 -1
  57. package/dist/src/utils/notify.js +5 -1
  58. package/dist/src/utils/notify.js.map +1 -1
  59. package/dist/src/utils/status-data.d.ts +2 -2
  60. package/dist/src/utils/status-data.d.ts.map +1 -1
  61. package/dist/src/utils/status-data.js +78 -123
  62. package/dist/src/utils/status-data.js.map +1 -1
  63. package/package.json +3 -1
  64. package/scripts/night-watch-audit-cron.sh +165 -0
  65. package/scripts/night-watch-cron.sh +33 -14
  66. package/scripts/night-watch-helpers.sh +10 -2
  67. package/scripts/night-watch-pr-reviewer-cron.sh +224 -18
  68. package/templates/night-watch-audit.md +87 -0
  69. package/web/dist/assets/index-BiJf9LFT.js +458 -0
  70. package/web/dist/assets/index-OpSgvsYu.css +1 -0
  71. package/web/dist/index.html +2 -2
  72. package/web/dist/assets/index-CndIPm_F.js +0 -473
  73. package/web/dist/assets/index-w6Q6gxCS.css +0 -1
@@ -305,17 +305,25 @@ find_eligible_prd() {
305
305
  }
306
306
 
307
307
  # ── Clean up worktrees ───────────────────────────────────────────────────────
308
- # Removes any worktrees with "-nw-" in the path (night-watch worktrees).
308
+ # Removes night-watch worktrees for this project.
309
+ # Optional second argument narrows cleanup to worktrees containing that token.
310
+ # This prevents parallel reviewer workers from deleting each other's worktrees.
309
311
 
310
312
  cleanup_worktrees() {
311
313
  local project_dir="${1:?project_dir required}"
314
+ local scope="${2:-}"
312
315
  local project_name
313
316
  project_name=$(basename "${project_dir}")
314
317
 
318
+ local match_token="${project_name}-nw"
319
+ if [ -n "${scope}" ]; then
320
+ match_token="${scope}"
321
+ fi
322
+
315
323
  git -C "${project_dir}" worktree list --porcelain 2>/dev/null \
316
324
  | grep '^worktree ' \
317
325
  | awk '{print $2}' \
318
- | grep "${project_name}-nw" \
326
+ | grep -F "${match_token}" \
319
327
  | while read -r wt; do
320
328
  log "CLEANUP: Removing leftover worktree ${wt}"
321
329
  git -C "${project_dir}" worktree remove --force "${wt}" 2>/dev/null || true
@@ -16,7 +16,7 @@ set -euo pipefail
16
16
  PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
17
17
  PROJECT_NAME=$(basename "${PROJECT_DIR}")
18
18
  LOG_DIR="${PROJECT_DIR}/logs"
19
- LOG_FILE="${LOG_DIR}/night-watch-pr-reviewer.log"
19
+ LOG_FILE="${LOG_DIR}/reviewer.log"
20
20
  MAX_RUNTIME="${NW_REVIEWER_MAX_RUNTIME:-3600}" # 1 hour
21
21
  MAX_LOG_SIZE="524288" # 512 KB
22
22
  PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
@@ -25,6 +25,8 @@ BRANCH_PATTERNS_RAW="${NW_BRANCH_PATTERNS:-feat/,night-watch/}"
25
25
  AUTO_MERGE="${NW_AUTO_MERGE:-0}"
26
26
  AUTO_MERGE_METHOD="${NW_AUTO_MERGE_METHOD:-squash}"
27
27
  TARGET_PR="${NW_TARGET_PR:-}"
28
+ PARALLEL_ENABLED="${NW_REVIEWER_PARALLEL:-1}"
29
+ WORKER_MODE="${NW_REVIEWER_WORKER_MODE:-0}"
28
30
 
29
31
  # Ensure NVM / Node / Claude are on PATH
30
32
  export NVM_DIR="${HOME}/.nvm"
@@ -39,8 +41,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
39
41
  # shellcheck source=night-watch-helpers.sh
40
42
  source "${SCRIPT_DIR}/night-watch-helpers.sh"
41
43
  PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
42
- # NOTE: Lock file path must match reviewerLockPath() in src/utils/status-data.ts
43
- LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}.lock"
44
+ GLOBAL_LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}.lock"
45
+ if [ "${WORKER_MODE}" = "1" ] && [ -n "${TARGET_PR}" ]; then
46
+ LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}-pr-${TARGET_PR}.lock"
47
+ else
48
+ # NOTE: Lock file path must match reviewerLockPath() in src/utils/status-data.ts
49
+ LOCK_FILE="${GLOBAL_LOCK_FILE}"
50
+ fi
44
51
 
45
52
  emit_result() {
46
53
  local status="${1:?status required}"
@@ -52,6 +59,38 @@ emit_result() {
52
59
  fi
53
60
  }
54
61
 
62
+ emit_final_status() {
63
+ local exit_code="${1:?exit code required}"
64
+ local prs_csv="${2:-}"
65
+ local auto_merged="${3:-}"
66
+ local auto_merge_failed="${4:-}"
67
+
68
+ if [ "${exit_code}" -eq 0 ]; then
69
+ log "DONE: PR reviewer completed successfully"
70
+ emit_result "success_reviewed" "prs=${prs_csv}|auto_merged=${auto_merged}|auto_merge_failed=${auto_merge_failed}"
71
+ elif [ "${exit_code}" -eq 124 ]; then
72
+ log "TIMEOUT: PR reviewer killed after ${MAX_RUNTIME}s"
73
+ emit_result "timeout" "prs=${prs_csv}"
74
+ else
75
+ log "FAIL: PR reviewer exited with code ${exit_code}"
76
+ emit_result "failure" "prs=${prs_csv}"
77
+ fi
78
+ }
79
+
80
+ append_csv() {
81
+ local current="${1:-}"
82
+ local incoming="${2:-}"
83
+ if [ -z "${incoming}" ]; then
84
+ printf "%s" "${current}"
85
+ return 0
86
+ fi
87
+ if [ -z "${current}" ]; then
88
+ printf "%s" "${incoming}"
89
+ else
90
+ printf "%s,%s" "${current}" "${incoming}"
91
+ fi
92
+ }
93
+
55
94
  # Validate provider
56
95
  if ! validate_provider "${PROVIDER_CMD}"; then
57
96
  echo "ERROR: Unknown provider: ${PROVIDER_CMD}" >&2
@@ -159,6 +198,62 @@ done < <(gh pr list --state open --json number,headRefName --jq '.[] | [.number,
159
198
 
160
199
  if [ "${NEEDS_WORK}" -eq 0 ]; then
161
200
  log "SKIP: All ${OPEN_PRS} open PR(s) have passing CI and review score >= ${MIN_REVIEW_SCORE} (or no score yet)"
201
+
202
+ # ── Auto-merge eligible PRs ───────────────────────────────
203
+ if [ "${NW_AUTO_MERGE:-0}" = "1" ]; then
204
+ AUTO_MERGE_METHOD="${NW_AUTO_MERGE_METHOD:-squash}"
205
+ AUTO_MERGED_COUNT=0
206
+
207
+ log "AUTO-MERGE: Checking for merge-ready PRs (method: ${AUTO_MERGE_METHOD})"
208
+
209
+ while IFS=$'\t' read -r pr_number pr_branch; do
210
+ [ -z "${pr_number}" ] || [ -z "${pr_branch}" ] && continue
211
+ printf '%s\n' "${pr_branch}" | grep -Eq "${BRANCH_REGEX}" || continue
212
+
213
+ # Check CI status
214
+ FAILED_CHECKS=$(gh pr checks "${pr_number}" 2>/dev/null | grep -ci 'fail' || true)
215
+ [ "${FAILED_CHECKS}" -gt 0 ] && continue
216
+
217
+ # Check review score
218
+ PR_COMMENTS=$(
219
+ {
220
+ gh pr view "${pr_number}" --json comments --jq '.comments[].body' 2>/dev/null || true
221
+ if [ -n "${REPO}" ]; then
222
+ gh api "repos/${REPO}/issues/${pr_number}/comments" --jq '.[].body' 2>/dev/null || true
223
+ fi
224
+ } | sort -u
225
+ )
226
+ PR_SCORE=$(echo "${PR_COMMENTS}" \
227
+ | grep -oP 'Overall Score:\*?\*?\s*(\d+)/100' \
228
+ | tail -1 \
229
+ | grep -oP '\d+(?=/100)' || echo "")
230
+
231
+ # Skip PRs without a score or with score below threshold
232
+ [ -z "${PR_SCORE}" ] && continue
233
+ [ "${PR_SCORE}" -lt "${MIN_REVIEW_SCORE}" ] && continue
234
+
235
+ # PR is merge-ready
236
+ log "AUTO-MERGE: PR #${pr_number} (${pr_branch}) — score ${PR_SCORE}/100, CI passing"
237
+
238
+ # Dry-run mode: show what would be merged
239
+ if [ "${NW_DRY_RUN:-0}" = "1" ]; then
240
+ log "AUTO-MERGE (dry-run): Would queue merge for PR #${pr_number} using ${AUTO_MERGE_METHOD}"
241
+ continue
242
+ fi
243
+
244
+ if gh pr merge "${pr_number}" --"${AUTO_MERGE_METHOD}" --auto --delete-branch 2>>"${LOG_FILE}"; then
245
+ log "AUTO-MERGE: Successfully queued merge for PR #${pr_number}"
246
+ AUTO_MERGED_COUNT=$((AUTO_MERGED_COUNT + 1))
247
+ else
248
+ log "WARN: Auto-merge failed for PR #${pr_number}"
249
+ fi
250
+ done < <(gh pr list --state open --json number,headRefName --jq '.[] | [.number, .headRefName] | @tsv' 2>/dev/null || true)
251
+
252
+ if [ "${AUTO_MERGED_COUNT}" -gt 0 ]; then
253
+ log "AUTO-MERGE: Queued ${AUTO_MERGED_COUNT} PR(s) for merge"
254
+ fi
255
+ fi
256
+
162
257
  emit_result "skip_all_passing"
163
258
  exit 0
164
259
  fi
@@ -172,11 +267,121 @@ if [ -n "${NW_DEFAULT_BRANCH:-}" ]; then
172
267
  else
173
268
  DEFAULT_BRANCH=$(detect_default_branch "${PROJECT_DIR}")
174
269
  fi
175
- REVIEW_WORKTREE_DIR="$(dirname "${PROJECT_DIR}")/${PROJECT_NAME}-nw-review-runner"
176
270
 
177
271
  log "START: Found PR(s) needing work:${PRS_NEEDING_WORK}"
178
272
 
179
- cleanup_worktrees "${PROJECT_DIR}"
273
+ # Convert "#12 #34" into ["12", "34"] for worker fan-out.
274
+ PR_NUMBER_ARRAY=()
275
+ for pr_token in ${PRS_NEEDING_WORK}; do
276
+ PR_NUMBER_ARRAY+=("${pr_token#\#}")
277
+ done
278
+
279
+ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED}" = "1" ] && [ "${#PR_NUMBER_ARRAY[@]}" -gt 1 ]; then
280
+ # Dry-run mode: print diagnostics and exit
281
+ if [ "${NW_DRY_RUN:-0}" = "1" ]; then
282
+ echo "=== Dry Run: PR Reviewer ==="
283
+ echo "Provider: ${PROVIDER_CMD}"
284
+ echo "Branch Patterns: ${BRANCH_PATTERNS_RAW}"
285
+ echo "Min Review Score: ${MIN_REVIEW_SCORE}"
286
+ echo "Auto-merge: ${AUTO_MERGE}"
287
+ if [ "${AUTO_MERGE}" = "1" ]; then
288
+ echo "Auto-merge Method: ${AUTO_MERGE_METHOD}"
289
+ fi
290
+ echo "Open PRs needing work:${PRS_NEEDING_WORK}"
291
+ echo "Default Branch: ${DEFAULT_BRANCH}"
292
+ echo "Parallel Workers: ${#PR_NUMBER_ARRAY[@]}"
293
+ echo "Timeout: ${MAX_RUNTIME}s"
294
+ exit 0
295
+ fi
296
+
297
+ log "PARALLEL: Launching ${#PR_NUMBER_ARRAY[@]} reviewer worker(s)"
298
+
299
+ declare -a WORKER_PIDS=()
300
+ declare -a WORKER_PRS=()
301
+ declare -a WORKER_OUTPUTS=()
302
+
303
+ for pr_number in "${PR_NUMBER_ARRAY[@]}"; do
304
+ worker_output=$(mktemp "/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}-pr-${pr_number}.XXXXXX")
305
+ WORKER_OUTPUTS+=("${worker_output}")
306
+ WORKER_PRS+=("${pr_number}")
307
+
308
+ (
309
+ NW_TARGET_PR="${pr_number}" \
310
+ NW_REVIEWER_WORKER_MODE="1" \
311
+ NW_REVIEWER_PARALLEL="0" \
312
+ bash "${SCRIPT_DIR}/night-watch-pr-reviewer-cron.sh" "${PROJECT_DIR}" > "${worker_output}" 2>&1
313
+ ) &
314
+
315
+ worker_pid=$!
316
+ WORKER_PIDS+=("${worker_pid}")
317
+ log "PARALLEL: Worker PID ${worker_pid} started for PR #${pr_number}"
318
+ done
319
+
320
+ EXIT_CODE=0
321
+ AUTO_MERGED_PRS=""
322
+ AUTO_MERGE_FAILED_PRS=""
323
+
324
+ for idx in "${!WORKER_PIDS[@]}"; do
325
+ worker_pid="${WORKER_PIDS[$idx]}"
326
+ worker_pr="${WORKER_PRS[$idx]}"
327
+ worker_output="${WORKER_OUTPUTS[$idx]}"
328
+
329
+ worker_exit_code=0
330
+ if wait "${worker_pid}"; then
331
+ worker_exit_code=0
332
+ else
333
+ worker_exit_code=$?
334
+ fi
335
+
336
+ if [ -f "${worker_output}" ] && [ -s "${worker_output}" ]; then
337
+ cat "${worker_output}" >> "${LOG_FILE}"
338
+ fi
339
+
340
+ worker_result=$(grep -o 'NIGHT_WATCH_RESULT:.*' "${worker_output}" 2>/dev/null | tail -1 || true)
341
+ worker_status=$(printf '%s' "${worker_result}" | sed -n 's/^NIGHT_WATCH_RESULT:\([^|]*\).*$/\1/p')
342
+ worker_auto_merged=$(printf '%s' "${worker_result}" | grep -oP '(?<=auto_merged=)[^|]+' || true)
343
+ worker_auto_merge_failed=$(printf '%s' "${worker_result}" | grep -oP '(?<=auto_merge_failed=)[^|]+' || true)
344
+
345
+ AUTO_MERGED_PRS=$(append_csv "${AUTO_MERGED_PRS}" "${worker_auto_merged}")
346
+ AUTO_MERGE_FAILED_PRS=$(append_csv "${AUTO_MERGE_FAILED_PRS}" "${worker_auto_merge_failed}")
347
+
348
+ rm -f "${worker_output}"
349
+
350
+ if [ "${worker_status}" = "failure" ] || { [ -n "${worker_status}" ] && [ "${worker_status}" != "success_reviewed" ] && [ "${worker_status}" != "timeout" ] && [ "${worker_status#skip_}" = "${worker_status}" ]; }; then
351
+ if [ "${EXIT_CODE}" -eq 0 ] || [ "${EXIT_CODE}" -eq 124 ]; then
352
+ EXIT_CODE=1
353
+ fi
354
+ log "PARALLEL: Worker for PR #${worker_pr} reported status '${worker_status:-unknown}'"
355
+ elif [ "${worker_status}" = "timeout" ]; then
356
+ if [ "${EXIT_CODE}" -eq 0 ]; then
357
+ EXIT_CODE=124
358
+ fi
359
+ log "PARALLEL: Worker for PR #${worker_pr} timed out"
360
+ elif [ "${worker_exit_code}" -ne 0 ]; then
361
+ if [ "${worker_exit_code}" -eq 124 ]; then
362
+ if [ "${EXIT_CODE}" -eq 0 ]; then
363
+ EXIT_CODE=124
364
+ fi
365
+ elif [ "${EXIT_CODE}" -eq 0 ] || [ "${EXIT_CODE}" -eq 124 ]; then
366
+ EXIT_CODE="${worker_exit_code}"
367
+ fi
368
+ log "PARALLEL: Worker for PR #${worker_pr} exited with code ${worker_exit_code}"
369
+ else
370
+ log "PARALLEL: Worker for PR #${worker_pr} completed"
371
+ fi
372
+ done
373
+
374
+ emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}"
375
+ exit 0
376
+ fi
377
+
378
+ REVIEW_WORKTREE_BASENAME="${PROJECT_NAME}-nw-review-runner"
379
+ if [ -n "${TARGET_PR}" ]; then
380
+ REVIEW_WORKTREE_BASENAME="${REVIEW_WORKTREE_BASENAME}-pr-${TARGET_PR}"
381
+ fi
382
+ REVIEW_WORKTREE_DIR="$(dirname "${PROJECT_DIR}")/${REVIEW_WORKTREE_BASENAME}"
383
+
384
+ cleanup_worktrees "${PROJECT_DIR}" "${REVIEW_WORKTREE_BASENAME}"
180
385
 
181
386
  # Dry-run mode: print diagnostics and exit
182
387
  if [ "${NW_DRY_RUN:-0}" = "1" ]; then
@@ -191,6 +396,10 @@ if [ "${NW_DRY_RUN:-0}" = "1" ]; then
191
396
  echo "Open PRs needing work:${PRS_NEEDING_WORK}"
192
397
  echo "Default Branch: ${DEFAULT_BRANCH}"
193
398
  echo "Review Worktree: ${REVIEW_WORKTREE_DIR}"
399
+ echo "Target PR: ${TARGET_PR:-all}"
400
+ if [ -n "${TARGET_PR}" ]; then
401
+ echo "Worker Mode: ${WORKER_MODE}"
402
+ fi
194
403
  echo "Timeout: ${MAX_RUNTIME}s"
195
404
  exit 0
196
405
  fi
@@ -201,12 +410,17 @@ if ! prepare_detached_worktree "${PROJECT_DIR}" "${REVIEW_WORKTREE_DIR}" "${DEFA
201
410
  fi
202
411
 
203
412
  EXIT_CODE=0
413
+ TARGET_SCOPE_PROMPT=""
414
+ if [ -n "${TARGET_PR}" ]; then
415
+ 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'
416
+ fi
204
417
 
205
418
  case "${PROVIDER_CMD}" in
206
419
  claude)
420
+ CLAUDE_PROMPT="/night-watch-pr-reviewer${TARGET_SCOPE_PROMPT}"
207
421
  if (
208
422
  cd "${REVIEW_WORKTREE_DIR}" && timeout "${MAX_RUNTIME}" \
209
- claude -p "/night-watch-pr-reviewer" \
423
+ claude -p "${CLAUDE_PROMPT}" \
210
424
  --dangerously-skip-permissions \
211
425
  >> "${LOG_FILE}" 2>&1
212
426
  ); then
@@ -216,11 +430,12 @@ case "${PROVIDER_CMD}" in
216
430
  fi
217
431
  ;;
218
432
  codex)
433
+ CODEX_PROMPT="$(cat "${REVIEW_WORKTREE_DIR}/.claude/commands/night-watch-pr-reviewer.md")${TARGET_SCOPE_PROMPT}"
219
434
  if (
220
435
  cd "${REVIEW_WORKTREE_DIR}" && timeout "${MAX_RUNTIME}" \
221
436
  codex --quiet \
222
437
  --yolo \
223
- --prompt "$(cat "${REVIEW_WORKTREE_DIR}/.claude/commands/night-watch-pr-reviewer.md")" \
438
+ --prompt "${CODEX_PROMPT}" \
224
439
  >> "${LOG_FILE}" 2>&1
225
440
  ); then
226
441
  EXIT_CODE=0
@@ -234,7 +449,7 @@ case "${PROVIDER_CMD}" in
234
449
  ;;
235
450
  esac
236
451
 
237
- cleanup_worktrees "${PROJECT_DIR}"
452
+ cleanup_worktrees "${PROJECT_DIR}" "${REVIEW_WORKTREE_BASENAME}"
238
453
 
239
454
  # ── Auto-merge eligible PRs ─────────────────────────────────────────────────────
240
455
  # After the reviewer completes, check for PRs that are merge-ready and queue them
@@ -310,13 +525,4 @@ if [ "${AUTO_MERGE}" = "1" ] && [ ${EXIT_CODE} -eq 0 ]; then
310
525
  done < <(gh pr list --state open --json number,headRefName --jq '.[] | [.number, .headRefName] | @tsv' 2>/dev/null || true)
311
526
  fi
312
527
 
313
- if [ ${EXIT_CODE} -eq 0 ]; then
314
- log "DONE: PR reviewer completed successfully"
315
- emit_result "success_reviewed" "prs=${PRS_NEEDING_WORK_CSV}|auto_merged=${AUTO_MERGED_PRS}|auto_merge_failed=${AUTO_MERGE_FAILED_PRS}"
316
- elif [ ${EXIT_CODE} -eq 124 ]; then
317
- log "TIMEOUT: PR reviewer killed after ${MAX_RUNTIME}s"
318
- emit_result "timeout" "prs=${PRS_NEEDING_WORK_CSV}"
319
- else
320
- log "FAIL: PR reviewer exited with code ${EXIT_CODE}"
321
- emit_result "failure" "prs=${PRS_NEEDING_WORK_CSV}"
322
- fi
528
+ emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}"
@@ -0,0 +1,87 @@
1
+ You are the Night Watch Code Auditor. Your job is to scan the codebase for real engineering risks and write a structured, high-signal report.
2
+
3
+ ## What to look for
4
+
5
+ ### 1) Critical runtime and security risks
6
+ 1. **Empty or swallowed catches** - `catch` blocks that discard meaningful errors in non-trivial paths.
7
+ 2. **Critical TODOs/FIXMEs/HACKs** - comments mentioning `bug`, `security`, `race`, `leak`, `crash`, `hotfix`, `rollback`, `unsafe`.
8
+ 3. **Hardcoded secrets or tokens** - API keys, passwords, tokens in source (exclude env var references).
9
+ 4. **Unhandled promise rejections** - async flows with missing error handling.
10
+ 5. **Unsafe type assertions** - `as any`, `as unknown as X`, dangerous non-null assertions (`!`) on uncertain input.
11
+
12
+ ### 2) Scalability and performance hotspots
13
+ 1. **N+1 / repeated expensive work** - repeated DB/API/file operations in loops.
14
+ 2. **Unbounded processing** - full in-memory loading of large datasets, missing pagination/streaming/chunking.
15
+ 3. **Blocking work on hot paths** - sync I/O or CPU-heavy work in frequent request/loop paths.
16
+ 4. **Missing backpressure/limits** - unbounded queues, retries, fan-out, or concurrency.
17
+
18
+ ### 3) Architecture and maintainability risks
19
+ 1. **Architecture violations** - business logic mixed into transport/UI/glue layers; hidden cross-layer dependencies.
20
+ 2. **SRP violations** - modules/functions/classes doing multiple unrelated responsibilities.
21
+ 3. **DRY violations** - duplicated logic likely to drift and cause inconsistent behavior.
22
+ 4. **KISS violations** - unnecessary complexity where simple solutions suffice.
23
+ 5. **SOLID violations** - violations that materially reduce extensibility/testability and cause real risk.
24
+ 6. **YAGNI violations** - speculative abstractions/features not needed by current behavior, adding maintenance cost.
25
+
26
+ ## What to SKIP
27
+
28
+ - `node_modules/`, `dist/`, `.git/`, `coverage/`, generated files.
29
+ - Test files (`*.test.ts`, `*.spec.ts`, `__tests__/`) unless they expose production design flaws.
30
+ - Intentional no-op catches in file walkers/read-only probing paths (e.g., `catch { continue }`, `catch { return null }` when clearly harmless).
31
+ - Cosmetic style-only nits (formatting, naming preference, import order).
32
+ - Hypothetical principle violations without concrete impact.
33
+
34
+ ## How to scan
35
+
36
+ Use file-reading/search tools and scan systematically, prioritizing:
37
+ - `src/` (core TypeScript implementation)
38
+ - `scripts/` (automation and shell execution paths)
39
+
40
+ For each potential issue, verify:
41
+ 1. It is real and actionable.
42
+ 2. It has concrete impact (correctness, security, scalability, operability, maintainability).
43
+ 3. The fix direction is clear.
44
+
45
+ ## Severity model
46
+
47
+ - **critical**: likely production outage/data loss/security exposure or severe architectural risk.
48
+ - **high**: significant bug/risk with near-term impact.
49
+ - **medium**: clear risk/smell that should be addressed soon.
50
+ - **low**: valid but lower urgency.
51
+
52
+ ## Report format
53
+
54
+ Write findings to `logs/audit-report.md` using this exact format:
55
+
56
+ ```markdown
57
+ # Code Audit Report
58
+
59
+ Generated: <ISO timestamp>
60
+
61
+ ## Findings
62
+
63
+ ### Finding 1
64
+ - **Location**: `src/path/to/file.ts:42`
65
+ - **Severity**: critical | high | medium | low
66
+ - **Category**: empty_catch | critical_todo | hardcoded_secret | unhandled_promise | unsafe_assertion | scalability_hotspot | architecture_violation | srp_violation | dry_violation | kiss_violation | solid_violation | yagni_violation
67
+ - **Description**: What the issue is, why it matters, and concrete impact
68
+ - **Snippet**: `the offending code`
69
+ - **Suggested Fix**: Specific fix direction (minimal, pragmatic)
70
+
71
+ ### Finding 2
72
+ ...
73
+ ```
74
+
75
+ If you find **no actionable issues**, write exactly this to `logs/audit-report.md`:
76
+
77
+ ```
78
+ NO_ISSUES_FOUND
79
+ ```
80
+
81
+ ## Rules
82
+
83
+ - Prioritize high-impact findings over volume. 3 strong findings beat 15 weak ones.
84
+ - Report principle violations (SRP/DRY/KISS/SOLID/YAGNI) only when they create concrete risk.
85
+ - Avoid theoretical architecture criticism without code evidence.
86
+ - Be decisive: skip noisy false positives.
87
+ - After writing the report, stop. Do NOT open PRs, push code, or make changes.