@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.
- package/dist/cli.js +1098 -2189
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +6 -24
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/qa.d.ts.map +1 -1
- package/dist/commands/qa.js +16 -4
- package/dist/commands/qa.js.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +5 -4
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/shared/env-builder.d.ts +5 -0
- package/dist/commands/shared/env-builder.d.ts.map +1 -1
- package/dist/commands/shared/env-builder.js +32 -0
- package/dist/commands/shared/env-builder.js.map +1 -1
- package/dist/commands/slice.d.ts +8 -0
- package/dist/commands/slice.d.ts.map +1 -1
- package/dist/commands/slice.js +90 -2
- package/dist/commands/slice.js.map +1 -1
- package/dist/scripts/night-watch-audit-cron.sh +16 -3
- package/dist/scripts/night-watch-cron.sh +19 -5
- package/dist/scripts/night-watch-helpers.sh +89 -0
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +69 -2
- package/dist/scripts/night-watch-qa-cron.sh +321 -18
- package/dist/scripts/night-watch-slicer-cron.sh +14 -3
- package/dist/templates/night-watch-pr-reviewer.md +27 -0
- package/dist/templates/night-watch.config.json +31 -1
- package/dist/templates/night-watch.md +31 -0
- package/package.json +1 -1
|
@@ -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: ${
|
|
63
|
-
|
|
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: ${
|
|
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:
|
|
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: ${
|
|
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: ${
|
|
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=""
|