@jonit-dev/night-watch-cli 1.7.51 → 1.7.52

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.
@@ -19,6 +19,7 @@ REPORT_FILE="${PROJECT_DIR}/logs/audit-report.md"
19
19
  MAX_RUNTIME="${NW_AUDIT_MAX_RUNTIME:-1800}" # 30 minutes
20
20
  MAX_LOG_SIZE="524288" # 512 KB
21
21
  PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
22
+ PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
22
23
 
23
24
  # Ensure NVM / Node / Claude are on PATH
24
25
  export NVM_DIR="${HOME}/.nvm"
@@ -33,6 +34,7 @@ PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
33
34
  # NOTE: Lock file path must match auditLockPath() in src/utils/status-data.ts
34
35
  LOCK_FILE="/tmp/night-watch-audit-${PROJECT_RUNTIME_KEY}.lock"
35
36
  AUDIT_PROMPT_TEMPLATE="${SCRIPT_DIR}/../templates/audit.md"
37
+ PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PROVIDER_LABEL}")
36
38
 
37
39
  emit_result() {
38
40
  local status="${1:?status required}"
@@ -59,13 +61,13 @@ if ! acquire_lock "${LOCK_FILE}"; then
59
61
  fi
60
62
 
61
63
  send_telegram_status_message "🔎 Night Watch Auditor: started" "Project: ${PROJECT_NAME}
62
- Provider: ${PROVIDER_CMD}
63
- Running code quality audit."
64
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
65
+ Action: running code quality audit."
64
66
 
65
67
  # Dry-run mode: print diagnostics and exit
66
68
  if [ "${NW_DRY_RUN:-0}" = "1" ]; then
67
69
  echo "=== Dry Run: Code Auditor ==="
68
- echo "Provider: ${PROVIDER_CMD}"
70
+ echo "Provider (model): ${PROVIDER_MODEL_DISPLAY}"
69
71
  echo "Max Runtime: ${MAX_RUNTIME}s"
70
72
  echo "Report File: ${REPORT_FILE}"
71
73
  echo "Prompt Template: ${AUDIT_PROMPT_TEMPLATE}"
@@ -76,6 +78,8 @@ fi
76
78
  if [ ! -f "${AUDIT_PROMPT_TEMPLATE}" ]; then
77
79
  log "FAIL: Missing bundled audit prompt template at ${AUDIT_PROMPT_TEMPLATE}"
78
80
  send_telegram_status_message "🔎 Night Watch Auditor: failed" "Project: ${PROJECT_NAME}
81
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
82
+ Failure reason: missing_prompt_template
79
83
  Missing prompt template:
80
84
  ${AUDIT_PROMPT_TEMPLATE}"
81
85
  emit_result "failure_missing_prompt"
@@ -98,6 +102,8 @@ cleanup_worktrees "${PROJECT_DIR}" "${AUDIT_WORKTREE_BASENAME}"
98
102
  if ! prepare_detached_worktree "${PROJECT_DIR}" "${AUDIT_WORKTREE_DIR}" "${DEFAULT_BRANCH}" "${LOG_FILE}"; then
99
103
  log "FAIL: Unable to create isolated audit worktree ${AUDIT_WORKTREE_DIR}"
100
104
  send_telegram_status_message "🔎 Night Watch Auditor: failed" "Project: ${PROJECT_NAME}
105
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
106
+ Failure reason: worktree_setup_failed
101
107
  Failed to create audit worktree."
102
108
  emit_result "failure" "reason=worktree_setup_failed"
103
109
  exit 1
@@ -179,6 +185,8 @@ if [ "${EXIT_CODE}" -eq 0 ]; then
179
185
  if [ ! -f "${REPORT_FILE}" ]; then
180
186
  log "FAIL: Audit provider exited 0 but no report was generated at ${REPORT_FILE}"
181
187
  send_telegram_status_message "🔎 Night Watch Auditor: failed" "Project: ${PROJECT_NAME}
188
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
189
+ Failure reason: no_report_generated
182
190
  Provider exited successfully but no report file was generated."
183
191
  emit_result "failure_no_report"
184
192
  exit 1
@@ -187,22 +195,27 @@ Provider exited successfully but no report file was generated."
187
195
  if grep -q "NO_ISSUES_FOUND" "${REPORT_FILE}" 2>/dev/null; then
188
196
  log "DONE: Audit complete — no actionable issues found"
189
197
  send_telegram_status_message "🔎 Night Watch Auditor: complete (clean)" "Project: ${PROJECT_NAME}
198
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
190
199
  No actionable issues found."
191
200
  emit_result "skip_clean"
192
201
  else
193
202
  log "DONE: Audit complete — report written to ${REPORT_FILE}"
194
203
  send_telegram_status_message "🔎 Night Watch Auditor: complete" "Project: ${PROJECT_NAME}
204
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
195
205
  Report: ${REPORT_FILE}"
196
206
  emit_result "success_audit"
197
207
  fi
198
208
  elif [ "${EXIT_CODE}" -eq 124 ]; then
199
209
  log "TIMEOUT: Audit killed after ${MAX_RUNTIME}s"
200
210
  send_telegram_status_message "🔎 Night Watch Auditor: timeout" "Project: ${PROJECT_NAME}
211
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
201
212
  Timeout: ${MAX_RUNTIME}s"
202
213
  emit_result "timeout"
203
214
  else
204
215
  log "FAIL: Audit exited with code ${EXIT_CODE}"
205
216
  send_telegram_status_message "🔎 Night Watch Auditor: failed" "Project: ${PROJECT_NAME}
217
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
218
+ Failure reason: provider_exit_${EXIT_CODE}
206
219
  Exit code: ${EXIT_CODE}"
207
220
  emit_result "failure" "provider_exit=${EXIT_CODE}"
208
221
  fi
@@ -25,6 +25,11 @@ LOG_FILE="${LOG_DIR}/executor.log"
25
25
  MAX_RUNTIME="${NW_MAX_RUNTIME:-7200}" # 2 hours
26
26
  MAX_LOG_SIZE="524288" # 512 KB
27
27
  PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
28
+ # Human-friendly provider label used in PR comments, board comments, and commit attribution.
29
+ # NW_PROVIDER_LABEL is set by the Node CLI (derived from config.providerLabel or auto-detected).
30
+ # EFFECTIVE_PROVIDER_LABEL may be updated after execution if rate-limit fallback is triggered.
31
+ PROVIDER_LABEL="${NW_PROVIDER_LABEL:-${PROVIDER_CMD}}"
32
+ EFFECTIVE_PROVIDER_LABEL="${PROVIDER_LABEL}"
28
33
  BRANCH_PREFIX="${NW_BRANCH_PREFIX:-night-watch}"
29
34
 
30
35
  # Ensure NVM / Node / Claude are on PATH
@@ -289,7 +294,7 @@ finalize_prd_done() {
289
294
  fi
290
295
  git -C "${BOOKKEEP_WORKTREE_DIR}" commit -m "chore: mark ${ELIGIBLE_PRD} as done (${reason})
291
296
 
292
- Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" || true
297
+ Co-Authored-By: Night Watch [${EFFECTIVE_PROVIDER_LABEL}] <noreply@anthropic.com>" || true
293
298
  git -C "${BOOKKEEP_WORKTREE_DIR}" push origin "HEAD:${DEFAULT_BRANCH}" || true
294
299
  log "DONE: ${ELIGIBLE_PRD} ${reason}, PRD moved to done/"
295
300
  return 0
@@ -521,6 +526,8 @@ if [ "${RATE_LIMIT_FALLBACK_TRIGGERED}" = "1" ]; then
521
526
  fi
522
527
 
523
528
  log "RATE-LIMIT-FALLBACK: Native Claude exited with code ${EXIT_CODE}"
529
+ # Update effective provider label to reflect actual executor used
530
+ EFFECTIVE_PROVIDER_LABEL="Claude ${FALLBACK_MODEL} (fallback)"
524
531
  fi
525
532
 
526
533
  if [ ${EXIT_CODE} -eq 0 ]; then
@@ -531,12 +538,19 @@ if [ ${EXIT_CODE} -eq 0 ]; then
531
538
  PR_URL=$(gh pr list --state open --json headRefName,url \
532
539
  --jq ".[] | select(.headRefName == \"${BRANCH_NAME}\") | .url" 2>/dev/null || true)
533
540
  if [ -n "${PR_URL}" ]; then
534
- "${NW_CLI}" board comment "${ISSUE_NUMBER}" --body "PR opened: ${PR_URL}" 2>>"${LOG_FILE}" || true
541
+ "${NW_CLI}" board comment "${ISSUE_NUMBER}" --body "PR opened: ${PR_URL} (via ${EFFECTIVE_PROVIDER_LABEL})" 2>>"${LOG_FILE}" || true
542
+ gh pr comment "${PR_URL}" --body "> 🤖 Implemented by ${EFFECTIVE_PROVIDER_LABEL}" 2>>"${LOG_FILE}" || true
535
543
  fi
536
544
  "${NW_CLI}" board close-issue "${ISSUE_NUMBER}" 2>>"${LOG_FILE}" || \
537
545
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Done" 2>>"${LOG_FILE}" || true
538
546
  emit_result "success_open_pr" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
539
547
  elif finalize_prd_done "implemented, PR opened on ${BRANCH_NAME}"; then
548
+ # Non-board mode: post attribution comment to the PR
549
+ NON_BOARD_PR_URL=$(gh pr list --state open --json headRefName,url \
550
+ --jq ".[] | select(.headRefName == \"${BRANCH_NAME}\") | .url" 2>/dev/null || true)
551
+ if [ -n "${NON_BOARD_PR_URL}" ]; then
552
+ gh pr comment "${NON_BOARD_PR_URL}" --body "> 🤖 Implemented by ${EFFECTIVE_PROVIDER_LABEL}" 2>>"${LOG_FILE}" || true
553
+ fi
540
554
  emit_result "success_open_pr" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
541
555
  else
542
556
  night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
@@ -562,7 +576,7 @@ if [ ${EXIT_CODE} -eq 0 ]; then
562
576
  if [ -n "${ISSUE_NUMBER}" ]; then
563
577
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
564
578
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
565
- --body "Execution completed but no PR was found. Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
579
+ --body "Execution completed but no PR was found (via ${EFFECTIVE_PROVIDER_LABEL}). Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
566
580
  fi
567
581
  night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
568
582
  emit_result "failure_no_pr_after_success" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
@@ -575,7 +589,7 @@ elif [ ${EXIT_CODE} -eq 124 ]; then
575
589
  if [ -n "${ISSUE_NUMBER}" ]; then
576
590
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
577
591
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
578
- --body "Execution timed out after ${MAX_RUNTIME}s. Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
592
+ --body "Execution timed out after ${MAX_RUNTIME}s (via ${EFFECTIVE_PROVIDER_LABEL}). Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
579
593
  TIMEOUT_FOLLOWUP_COMMENT=$(build_timeout_followup_comment \
580
594
  "${MAX_RUNTIME}" \
581
595
  "${ELIGIBLE_PRD}" \
@@ -590,7 +604,7 @@ else
590
604
  if [ -n "${ISSUE_NUMBER}" ]; then
591
605
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
592
606
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
593
- --body "Execution failed with exit code ${EXIT_CODE}. Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
607
+ --body "Execution failed with exit code ${EXIT_CODE} (via ${EFFECTIVE_PROVIDER_LABEL}). Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
594
608
  fi
595
609
  night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code "${EXIT_CODE}" 2>/dev/null || true
596
610
  emit_result "failure" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
@@ -529,6 +529,95 @@ check_rate_limited() {
529
529
  fi
530
530
  }
531
531
 
532
+ # Resolve URL host from a URL-like string.
533
+ # Example: "https://api.z.ai/api/anthropic" -> "api.z.ai"
534
+ extract_url_host() {
535
+ local raw_url="${1:-}"
536
+ if [ -z "${raw_url}" ]; then
537
+ return 0
538
+ fi
539
+ printf '%s' "${raw_url}" | sed -E 's#^[[:alpha:]][[:alnum:]+.-]*://##; s#/.*$##'
540
+ }
541
+
542
+ # Resolve a Claude model hint from env vars.
543
+ # Priority:
544
+ # 1) ANTHROPIC_DEFAULT_SONNET_MODEL / ANTHROPIC_DEFAULT_OPUS_MODEL (providerEnv overrides)
545
+ # 2) NW_CLAUDE_MODEL_ID (resolved from config.claudeModel by caller)
546
+ # 3) "default"
547
+ resolve_claude_model_hint() {
548
+ local sonnet="${ANTHROPIC_DEFAULT_SONNET_MODEL:-}"
549
+ local opus="${ANTHROPIC_DEFAULT_OPUS_MODEL:-}"
550
+ local native_model="${NW_CLAUDE_MODEL_ID:-}"
551
+
552
+ if [ -n "${sonnet}" ] && [ -n "${opus}" ]; then
553
+ if [ "${sonnet}" = "${opus}" ]; then
554
+ printf "%s" "${sonnet}"
555
+ else
556
+ printf "sonnet=%s, opus=%s" "${sonnet}" "${opus}"
557
+ fi
558
+ return 0
559
+ fi
560
+ if [ -n "${sonnet}" ]; then
561
+ printf "%s" "${sonnet}"
562
+ return 0
563
+ fi
564
+ if [ -n "${opus}" ]; then
565
+ printf "%s" "${opus}"
566
+ return 0
567
+ fi
568
+ if [ -n "${native_model}" ]; then
569
+ printf "%s" "${native_model}"
570
+ return 0
571
+ fi
572
+ printf "default"
573
+ }
574
+
575
+ # Build a user-facing provider/model display string.
576
+ # Examples:
577
+ # claude (glm-5 via api.z.ai)
578
+ # claude (claude-sonnet-4-6)
579
+ # codex
580
+ # codex (Custom Label)
581
+ resolve_provider_model_display() {
582
+ local provider_cmd="${1:?provider command required}"
583
+ local provider_label="${2:-}"
584
+ local label_trimmed=""
585
+ local model_hint=""
586
+ local endpoint_host=""
587
+ local details=""
588
+
589
+ label_trimmed=$(printf '%s' "${provider_label}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
590
+
591
+ case "${provider_cmd}" in
592
+ claude)
593
+ model_hint=$(resolve_claude_model_hint)
594
+ endpoint_host=$(extract_url_host "${ANTHROPIC_BASE_URL:-}")
595
+ details="${model_hint}"
596
+ if [ -n "${endpoint_host}" ]; then
597
+ details="${details} via ${endpoint_host}"
598
+ fi
599
+ if [ -n "${label_trimmed}" ] && [ "${label_trimmed}" != "Claude" ] && [ "${label_trimmed}" != "Claude (proxy)" ]; then
600
+ details="${label_trimmed}; ${details}"
601
+ fi
602
+ printf "%s (%s)" "${provider_cmd}" "${details}"
603
+ ;;
604
+ codex)
605
+ if [ -n "${label_trimmed}" ] && [ "${label_trimmed}" != "Codex" ]; then
606
+ printf "%s (%s)" "${provider_cmd}" "${label_trimmed}"
607
+ else
608
+ printf "%s" "${provider_cmd}"
609
+ fi
610
+ ;;
611
+ *)
612
+ if [ -n "${label_trimmed}" ]; then
613
+ printf "%s (%s)" "${provider_cmd}" "${label_trimmed}"
614
+ else
615
+ printf "%s" "${provider_cmd}"
616
+ fi
617
+ ;;
618
+ esac
619
+ }
620
+
532
621
  # Send a generic Telegram status message.
533
622
  # Preferred input: NW_TELEGRAM_STATUS_WEBHOOKS (JSON array with botToken/chatId).
534
623
  # Legacy fallback: NW_TELEGRAM_BOT_TOKEN + NW_TELEGRAM_CHAT_ID.
@@ -20,6 +20,7 @@ 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}"
23
+ PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
23
24
  MIN_REVIEW_SCORE="${NW_MIN_REVIEW_SCORE:-80}"
24
25
  BRANCH_PATTERNS_RAW="${NW_BRANCH_PATTERNS:-feat/,night-watch/}"
25
26
  AUTO_MERGE="${NW_AUTO_MERGE:-0}"
@@ -65,6 +66,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
65
66
  # shellcheck source=night-watch-helpers.sh
66
67
  source "${SCRIPT_DIR}/night-watch-helpers.sh"
67
68
  PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
69
+ PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PROVIDER_LABEL}")
68
70
  GLOBAL_LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}.lock"
69
71
  if [ "${WORKER_MODE}" = "1" ] && [ -n "${TARGET_PR}" ]; then
70
72
  LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}-pr-${TARGET_PR}.lock"
@@ -91,6 +93,21 @@ emit_final_status() {
91
93
  local attempts="${5:-1}"
92
94
  local final_score="${6:-}"
93
95
  local details=""
96
+ local prs_summary=""
97
+ local auto_merged_summary=""
98
+ local auto_merge_failed_summary=""
99
+ local final_score_summary=""
100
+ local final_score_line=""
101
+
102
+ prs_summary="${prs_csv:-none}"
103
+ auto_merged_summary="${auto_merged:-none}"
104
+ auto_merge_failed_summary="${auto_merge_failed:-none}"
105
+ final_score_summary="${final_score:-n/a}"
106
+ if [ -n "${final_score}" ]; then
107
+ final_score_line="Final score: ${final_score_summary}/100"
108
+ else
109
+ final_score_line="Final score: n/a"
110
+ fi
94
111
 
95
112
  if [ "${exit_code}" -eq 0 ]; then
96
113
  details="prs=${prs_csv}|auto_merged=${auto_merged}|auto_merge_failed=${auto_merge_failed}|attempts=${attempts}"
@@ -98,6 +115,15 @@ emit_final_status() {
98
115
  details="${details}|final_score=${final_score}"
99
116
  fi
100
117
  log "DONE: PR reviewer completed successfully"
118
+ if [ "${WORKER_MODE}" != "1" ]; then
119
+ send_telegram_status_message "🔍 Night Watch Reviewer: completed" "Project: ${PROJECT_NAME}
120
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
121
+ Processed PRs: ${prs_summary}
122
+ Attempts: ${attempts}
123
+ ${final_score_line}
124
+ Auto-merged PRs: ${auto_merged_summary}
125
+ Auto-merge failed: ${auto_merge_failed_summary}"
126
+ fi
101
127
  emit_result "success_reviewed" "${details}"
102
128
  elif [ "${exit_code}" -eq 124 ]; then
103
129
  details="prs=${prs_csv}|attempts=${attempts}"
@@ -105,6 +131,14 @@ emit_final_status() {
105
131
  details="${details}|final_score=${final_score}"
106
132
  fi
107
133
  log "TIMEOUT: PR reviewer killed after ${MAX_RUNTIME}s"
134
+ if [ "${WORKER_MODE}" != "1" ]; then
135
+ send_telegram_status_message "🔍 Night Watch Reviewer: timeout" "Project: ${PROJECT_NAME}
136
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
137
+ Timeout: ${MAX_RUNTIME}s
138
+ Processed PRs: ${prs_summary}
139
+ Attempts: ${attempts}
140
+ ${final_score_line}"
141
+ fi
108
142
  emit_result "timeout" "${details}"
109
143
  else
110
144
  details="prs=${prs_csv}|attempts=${attempts}"
@@ -112,6 +146,14 @@ emit_final_status() {
112
146
  details="${details}|final_score=${final_score}"
113
147
  fi
114
148
  log "FAIL: PR reviewer exited with code ${exit_code}"
149
+ if [ "${WORKER_MODE}" != "1" ]; then
150
+ send_telegram_status_message "🔍 Night Watch Reviewer: failed" "Project: ${PROJECT_NAME}
151
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
152
+ Exit code: ${exit_code}
153
+ Processed PRs: ${prs_summary}
154
+ Attempts: ${attempts}
155
+ ${final_score_line}"
156
+ fi
115
157
  emit_result "failure" "${details}"
116
158
  fi
117
159
  }
@@ -462,6 +504,14 @@ fi
462
504
 
463
505
  cd "${PROJECT_DIR}"
464
506
 
507
+ if [ "${WORKER_MODE}" != "1" ]; then
508
+ send_telegram_status_message "🔍 Night Watch Reviewer: started" "Project: ${PROJECT_NAME}
509
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
510
+ Branch patterns: ${BRANCH_PATTERNS_RAW}
511
+ Target PR: ${TARGET_PR:-all matching}
512
+ Action: scanning open PRs for failing checks or low review scores."
513
+ fi
514
+
465
515
  # Convert comma-separated branch prefixes into a regex that matches branch starts.
466
516
  BRANCH_REGEX=""
467
517
  IFS=',' read -r -a BRANCH_PATTERNS <<< "${BRANCH_PATTERNS_RAW}"
@@ -495,6 +545,13 @@ fi
495
545
 
496
546
  if [ "${OPEN_PRS}" -eq 0 ]; then
497
547
  log "SKIP: No open PRs matching branch patterns (${BRANCH_PATTERNS_RAW})"
548
+ if [ "${WORKER_MODE}" != "1" ]; then
549
+ send_telegram_status_message "🔍 Night Watch Reviewer: no matching PRs" "Project: ${PROJECT_NAME}
550
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
551
+ Branch patterns: ${BRANCH_PATTERNS_RAW}
552
+ Target PR: ${TARGET_PR:-all matching}
553
+ Result: 0 open PRs matched."
554
+ fi
498
555
  emit_result "skip_no_open_prs"
499
556
  exit 0
500
557
  fi
@@ -618,6 +675,11 @@ if [ "${NEEDS_WORK}" -eq 0 ]; then
618
675
  fi
619
676
  fi
620
677
 
678
+ if [ "${WORKER_MODE}" != "1" ]; then
679
+ send_telegram_status_message "🔍 Night Watch Reviewer: nothing to do" "Project: ${PROJECT_NAME}
680
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
681
+ Result: all ${OPEN_PRS} matching PRs already pass CI and review threshold (${MIN_REVIEW_SCORE})."
682
+ fi
621
683
  emit_result "skip_all_passing"
622
684
  exit 0
623
685
  fi
@@ -648,7 +710,7 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
648
710
  # Dry-run mode: print diagnostics and exit
649
711
  if [ "${NW_DRY_RUN:-0}" = "1" ]; then
650
712
  echo "=== Dry Run: PR Reviewer ==="
651
- echo "Provider: ${PROVIDER_CMD}"
713
+ echo "Provider (model): ${PROVIDER_MODEL_DISPLAY}"
652
714
  echo "Branch Patterns: ${BRANCH_PATTERNS_RAW}"
653
715
  echo "Min Review Score: ${MIN_REVIEW_SCORE}"
654
716
  echo "Auto-merge: ${AUTO_MERGE}"
@@ -779,7 +841,7 @@ cleanup_reviewer_worktrees "${REVIEW_WORKTREE_BASENAME}"
779
841
  # Dry-run mode: print diagnostics and exit
780
842
  if [ "${NW_DRY_RUN:-0}" = "1" ]; then
781
843
  echo "=== Dry Run: PR Reviewer ==="
782
- echo "Provider: ${PROVIDER_CMD}"
844
+ echo "Provider (model): ${PROVIDER_MODEL_DISPLAY}"
783
845
  echo "Branch Patterns: ${BRANCH_PATTERNS_RAW}"
784
846
  echo "Min Review Score: ${MIN_REVIEW_SCORE}"
785
847
  echo "Auto-merge: ${AUTO_MERGE}"
@@ -819,6 +881,11 @@ REVIEWER_PROMPT_BASE=$(cat "${REVIEWER_PROMPT_PATH}")
819
881
  REVIEWER_PROMPT_REF=$(instruction_ref_for_prompt "${REVIEW_WORKTREE_DIR}" "${REVIEWER_PROMPT_PATH}")
820
882
  log "INFO: Using reviewer prompt from ${REVIEWER_PROMPT_REF}"
821
883
 
884
+ # Inject provider attribution requirement into the reviewer prompt.
885
+ # The AI must add a footer to every review comment it posts.
886
+ REVIEWER_PROVIDER_LABEL="${NW_PROVIDER_LABEL:-${PROVIDER_CMD}}"
887
+ REVIEWER_PROMPT_BASE="${REVIEWER_PROMPT_BASE}"$'\n\n'"## Reviewer Attribution (Required)"$'\n'"At the very end of each review comment you post, add this footer on its own line:"$'\n'"> 🔍 Reviewed by ${REVIEWER_PROVIDER_LABEL}"
888
+
822
889
  EXIT_CODE=0
823
890
  ATTEMPTS_MADE=1
824
891
  FINAL_SCORE=""