@jonit-dev/night-watch-cli 1.7.50 → 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 +393 -229
- 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/init.d.ts.map +1 -1
- package/dist/commands/init.js +20 -23
- package/dist/commands/init.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 +6 -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 +17 -4
- package/dist/scripts/night-watch-cron.sh +19 -5
- package/dist/scripts/night-watch-helpers.sh +137 -0
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +268 -5
- package/dist/scripts/night-watch-qa-cron.sh +427 -22
- package/dist/scripts/night-watch-slicer-cron.sh +14 -3
- package/dist/templates/audit.md +87 -0
- package/dist/templates/executor.md +67 -0
- package/dist/templates/night-watch-pr-reviewer.md +33 -0
- package/dist/templates/night-watch.config.json +31 -1
- package/dist/templates/night-watch.md +31 -0
- package/dist/templates/pr-reviewer.md +203 -0
- package/dist/templates/qa.md +157 -0
- package/dist/templates/slicer.md +234 -0
- package/package.json +1 -1
|
@@ -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}"
|
|
@@ -27,6 +28,12 @@ AUTO_MERGE_METHOD="${NW_AUTO_MERGE_METHOD:-squash}"
|
|
|
27
28
|
TARGET_PR="${NW_TARGET_PR:-}"
|
|
28
29
|
PARALLEL_ENABLED="${NW_REVIEWER_PARALLEL:-1}"
|
|
29
30
|
WORKER_MODE="${NW_REVIEWER_WORKER_MODE:-0}"
|
|
31
|
+
PRD_DIR_REL="${NW_PRD_DIR:-docs/PRDs/night-watch}"
|
|
32
|
+
if [[ "${PRD_DIR_REL}" = /* ]]; then
|
|
33
|
+
PRD_DIR="${PRD_DIR_REL}"
|
|
34
|
+
else
|
|
35
|
+
PRD_DIR="${PROJECT_DIR}/${PRD_DIR_REL}"
|
|
36
|
+
fi
|
|
30
37
|
|
|
31
38
|
# Retry configuration
|
|
32
39
|
REVIEWER_MAX_RETRIES="${NW_REVIEWER_MAX_RETRIES:-2}"
|
|
@@ -59,6 +66,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
59
66
|
# shellcheck source=night-watch-helpers.sh
|
|
60
67
|
source "${SCRIPT_DIR}/night-watch-helpers.sh"
|
|
61
68
|
PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
69
|
+
PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PROVIDER_LABEL}")
|
|
62
70
|
GLOBAL_LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}.lock"
|
|
63
71
|
if [ "${WORKER_MODE}" = "1" ] && [ -n "${TARGET_PR}" ]; then
|
|
64
72
|
LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}-pr-${TARGET_PR}.lock"
|
|
@@ -85,6 +93,21 @@ emit_final_status() {
|
|
|
85
93
|
local attempts="${5:-1}"
|
|
86
94
|
local final_score="${6:-}"
|
|
87
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
|
|
88
111
|
|
|
89
112
|
if [ "${exit_code}" -eq 0 ]; then
|
|
90
113
|
details="prs=${prs_csv}|auto_merged=${auto_merged}|auto_merge_failed=${auto_merge_failed}|attempts=${attempts}"
|
|
@@ -92,6 +115,15 @@ emit_final_status() {
|
|
|
92
115
|
details="${details}|final_score=${final_score}"
|
|
93
116
|
fi
|
|
94
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
|
|
95
127
|
emit_result "success_reviewed" "${details}"
|
|
96
128
|
elif [ "${exit_code}" -eq 124 ]; then
|
|
97
129
|
details="prs=${prs_csv}|attempts=${attempts}"
|
|
@@ -99,6 +131,14 @@ emit_final_status() {
|
|
|
99
131
|
details="${details}|final_score=${final_score}"
|
|
100
132
|
fi
|
|
101
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
|
|
102
142
|
emit_result "timeout" "${details}"
|
|
103
143
|
else
|
|
104
144
|
details="prs=${prs_csv}|attempts=${attempts}"
|
|
@@ -106,6 +146,14 @@ emit_final_status() {
|
|
|
106
146
|
details="${details}|final_score=${final_score}"
|
|
107
147
|
fi
|
|
108
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
|
|
109
157
|
emit_result "failure" "${details}"
|
|
110
158
|
fi
|
|
111
159
|
}
|
|
@@ -124,6 +172,175 @@ append_csv() {
|
|
|
124
172
|
fi
|
|
125
173
|
}
|
|
126
174
|
|
|
175
|
+
truncate_for_prompt() {
|
|
176
|
+
local text="${1:-}"
|
|
177
|
+
local limit="${2:-7000}"
|
|
178
|
+
if [ "${#text}" -le "${limit}" ]; then
|
|
179
|
+
printf "%s" "${text}"
|
|
180
|
+
else
|
|
181
|
+
printf '%s\n\n[truncated to %s chars]' "${text:0:${limit}}" "${limit}"
|
|
182
|
+
fi
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
extract_linked_issue_numbers() {
|
|
186
|
+
local body="${1:-}"
|
|
187
|
+
printf '%s\n' "${body}" \
|
|
188
|
+
| grep -Eoi '(close[sd]?|fix(e[sd])?|resolve[sd]?)[[:space:]]*:?[[:space:]]*#[0-9]+' \
|
|
189
|
+
| grep -Eo '[0-9]+' \
|
|
190
|
+
| awk '!seen[$0]++' || true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
find_prd_file_by_branch() {
|
|
194
|
+
local branch_name="${1:-}"
|
|
195
|
+
local branch_slug="${branch_name#*/}"
|
|
196
|
+
local branch_number=""
|
|
197
|
+
local candidate_dirs=()
|
|
198
|
+
local candidate=""
|
|
199
|
+
local base_name=""
|
|
200
|
+
local dir=""
|
|
201
|
+
|
|
202
|
+
if [ -z "${branch_slug}" ]; then
|
|
203
|
+
branch_slug="${branch_name}"
|
|
204
|
+
fi
|
|
205
|
+
[ -z "${branch_slug}" ] && return 1
|
|
206
|
+
|
|
207
|
+
if [ -d "${PRD_DIR}" ]; then
|
|
208
|
+
candidate_dirs+=("${PRD_DIR}")
|
|
209
|
+
fi
|
|
210
|
+
if [ -d "${PRD_DIR}/done" ]; then
|
|
211
|
+
candidate_dirs+=("${PRD_DIR}/done")
|
|
212
|
+
fi
|
|
213
|
+
[ "${#candidate_dirs[@]}" -eq 0 ] && return 1
|
|
214
|
+
|
|
215
|
+
for dir in "${candidate_dirs[@]}"; do
|
|
216
|
+
if [ -f "${dir}/${branch_slug}.md" ]; then
|
|
217
|
+
printf "%s" "${dir}/${branch_slug}.md"
|
|
218
|
+
return 0
|
|
219
|
+
fi
|
|
220
|
+
done
|
|
221
|
+
|
|
222
|
+
branch_number=$(printf '%s' "${branch_slug}" | grep -oE '^[0-9]+' || true)
|
|
223
|
+
for dir in "${candidate_dirs[@]}"; do
|
|
224
|
+
while IFS= read -r candidate; do
|
|
225
|
+
[ -z "${candidate}" ] && continue
|
|
226
|
+
base_name=$(basename "${candidate}" .md)
|
|
227
|
+
if [[ "${base_name}" == "${branch_slug}"* ]] || [[ "${branch_slug}" == "${base_name}"* ]]; then
|
|
228
|
+
printf "%s" "${candidate}"
|
|
229
|
+
return 0
|
|
230
|
+
fi
|
|
231
|
+
if [ -n "${branch_number}" ] && [[ "${base_name}" == "${branch_number}-"* ]]; then
|
|
232
|
+
printf "%s" "${candidate}"
|
|
233
|
+
return 0
|
|
234
|
+
fi
|
|
235
|
+
done < <(find "${dir}" -maxdepth 1 -type f -name '*.md' 2>/dev/null | sort)
|
|
236
|
+
done
|
|
237
|
+
|
|
238
|
+
return 1
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
build_prd_context_for_pr() {
|
|
242
|
+
local pr_number="${1:?PR number required}"
|
|
243
|
+
local pr_payload=""
|
|
244
|
+
local pr_title=""
|
|
245
|
+
local pr_branch=""
|
|
246
|
+
local pr_body=""
|
|
247
|
+
local pr_url=""
|
|
248
|
+
local issue_context=""
|
|
249
|
+
local issue_count=0
|
|
250
|
+
local issue_number=""
|
|
251
|
+
local issue_payload=""
|
|
252
|
+
local issue_title=""
|
|
253
|
+
local issue_body=""
|
|
254
|
+
local issue_excerpt=""
|
|
255
|
+
local prd_file=""
|
|
256
|
+
local prd_payload=""
|
|
257
|
+
local prd_excerpt=""
|
|
258
|
+
local prd_rel_path=""
|
|
259
|
+
local section=""
|
|
260
|
+
|
|
261
|
+
pr_payload=$(gh pr view "${pr_number}" --json title,headRefName,body,url 2>/dev/null || true)
|
|
262
|
+
pr_title=$(printf '%s' "${pr_payload}" | jq -r '.title // ""' 2>/dev/null || echo "")
|
|
263
|
+
pr_branch=$(printf '%s' "${pr_payload}" | jq -r '.headRefName // ""' 2>/dev/null || echo "")
|
|
264
|
+
pr_body=$(printf '%s' "${pr_payload}" | jq -r '.body // ""' 2>/dev/null || echo "")
|
|
265
|
+
pr_url=$(printf '%s' "${pr_payload}" | jq -r '.url // ""' 2>/dev/null || echo "")
|
|
266
|
+
|
|
267
|
+
if [ -n "${pr_body}" ]; then
|
|
268
|
+
while IFS= read -r issue_number; do
|
|
269
|
+
[ -z "${issue_number}" ] && continue
|
|
270
|
+
issue_count=$((issue_count + 1))
|
|
271
|
+
if [ "${issue_count}" -gt 2 ]; then
|
|
272
|
+
break
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
issue_payload=$(gh issue view "${issue_number}" --json title,body,url 2>/dev/null || true)
|
|
276
|
+
issue_title=$(printf '%s' "${issue_payload}" | jq -r '.title // ""' 2>/dev/null || echo "")
|
|
277
|
+
issue_body=$(printf '%s' "${issue_payload}" | jq -r '.body // ""' 2>/dev/null || echo "")
|
|
278
|
+
[ -z "${issue_body}" ] && continue
|
|
279
|
+
|
|
280
|
+
issue_excerpt=$(truncate_for_prompt "${issue_body}" 4500)
|
|
281
|
+
issue_context="${issue_context}${issue_context:+$'\n\n'}Issue #${issue_number}: ${issue_title}
|
|
282
|
+
${issue_excerpt}"
|
|
283
|
+
done < <(extract_linked_issue_numbers "${pr_body}")
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
if [ -z "${issue_context}" ] && [ -n "${pr_branch}" ]; then
|
|
287
|
+
prd_file=$(find_prd_file_by_branch "${pr_branch}" || true)
|
|
288
|
+
if [ -n "${prd_file}" ] && [ -f "${prd_file}" ]; then
|
|
289
|
+
prd_payload=$(cat "${prd_file}" 2>/dev/null || true)
|
|
290
|
+
if [ -n "${prd_payload}" ]; then
|
|
291
|
+
prd_excerpt=$(truncate_for_prompt "${prd_payload}" 4500)
|
|
292
|
+
if [[ "${prd_file}" == "${PROJECT_DIR}/"* ]]; then
|
|
293
|
+
prd_rel_path="${prd_file#${PROJECT_DIR}/}"
|
|
294
|
+
else
|
|
295
|
+
prd_rel_path="${prd_file}"
|
|
296
|
+
fi
|
|
297
|
+
fi
|
|
298
|
+
fi
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
section="### PR #${pr_number}"
|
|
302
|
+
if [ -n "${pr_title}" ]; then
|
|
303
|
+
section="${section} — ${pr_title}"
|
|
304
|
+
fi
|
|
305
|
+
section="${section}
|
|
306
|
+
- branch: ${pr_branch:-unknown}"
|
|
307
|
+
if [ -n "${pr_url}" ]; then
|
|
308
|
+
section="${section}
|
|
309
|
+
- url: ${pr_url}"
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
if [ -n "${issue_context}" ]; then
|
|
313
|
+
section="${section}
|
|
314
|
+
- context source: linked GitHub issue body
|
|
315
|
+
${issue_context}"
|
|
316
|
+
elif [ -n "${prd_excerpt}" ]; then
|
|
317
|
+
section="${section}
|
|
318
|
+
- context source: ${prd_rel_path}
|
|
319
|
+
${prd_excerpt}"
|
|
320
|
+
else
|
|
321
|
+
section="${section}
|
|
322
|
+
- context source: not found"
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
printf "%s" "${section}"
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
build_prd_context_prompt() {
|
|
329
|
+
local pr_number=""
|
|
330
|
+
local entry=""
|
|
331
|
+
local combined=""
|
|
332
|
+
|
|
333
|
+
for pr_number in "$@"; do
|
|
334
|
+
[ -z "${pr_number}" ] && continue
|
|
335
|
+
entry=$(build_prd_context_for_pr "${pr_number}")
|
|
336
|
+
[ -z "${entry}" ] && continue
|
|
337
|
+
combined="${combined}${combined:+$'\n\n'}${entry}"
|
|
338
|
+
done
|
|
339
|
+
|
|
340
|
+
[ -z "${combined}" ] && return 0
|
|
341
|
+
printf '\n\n## PRD Context\nUse this product context while reviewing and fixing PRs.\n%s\n' "${combined}"
|
|
342
|
+
}
|
|
343
|
+
|
|
127
344
|
# Extract the latest review score from PR comments
|
|
128
345
|
# Returns empty string if no score found
|
|
129
346
|
get_pr_score() {
|
|
@@ -287,6 +504,14 @@ fi
|
|
|
287
504
|
|
|
288
505
|
cd "${PROJECT_DIR}"
|
|
289
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
|
+
|
|
290
515
|
# Convert comma-separated branch prefixes into a regex that matches branch starts.
|
|
291
516
|
BRANCH_REGEX=""
|
|
292
517
|
IFS=',' read -r -a BRANCH_PATTERNS <<< "${BRANCH_PATTERNS_RAW}"
|
|
@@ -320,6 +545,13 @@ fi
|
|
|
320
545
|
|
|
321
546
|
if [ "${OPEN_PRS}" -eq 0 ]; then
|
|
322
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
|
|
323
555
|
emit_result "skip_no_open_prs"
|
|
324
556
|
exit 0
|
|
325
557
|
fi
|
|
@@ -443,6 +675,11 @@ if [ "${NEEDS_WORK}" -eq 0 ]; then
|
|
|
443
675
|
fi
|
|
444
676
|
fi
|
|
445
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
|
|
446
683
|
emit_result "skip_all_passing"
|
|
447
684
|
exit 0
|
|
448
685
|
fi
|
|
@@ -473,7 +710,7 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
473
710
|
# Dry-run mode: print diagnostics and exit
|
|
474
711
|
if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
475
712
|
echo "=== Dry Run: PR Reviewer ==="
|
|
476
|
-
echo "Provider: ${
|
|
713
|
+
echo "Provider (model): ${PROVIDER_MODEL_DISPLAY}"
|
|
477
714
|
echo "Branch Patterns: ${BRANCH_PATTERNS_RAW}"
|
|
478
715
|
echo "Min Review Score: ${MIN_REVIEW_SCORE}"
|
|
479
716
|
echo "Auto-merge: ${AUTO_MERGE}"
|
|
@@ -604,7 +841,7 @@ cleanup_reviewer_worktrees "${REVIEW_WORKTREE_BASENAME}"
|
|
|
604
841
|
# Dry-run mode: print diagnostics and exit
|
|
605
842
|
if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
606
843
|
echo "=== Dry Run: PR Reviewer ==="
|
|
607
|
-
echo "Provider: ${
|
|
844
|
+
echo "Provider (model): ${PROVIDER_MODEL_DISPLAY}"
|
|
608
845
|
echo "Branch Patterns: ${BRANCH_PATTERNS_RAW}"
|
|
609
846
|
echo "Min Review Score: ${MIN_REVIEW_SCORE}"
|
|
610
847
|
echo "Auto-merge: ${AUTO_MERGE}"
|
|
@@ -629,16 +866,26 @@ if ! prepare_detached_worktree "${PROJECT_DIR}" "${REVIEW_WORKTREE_DIR}" "${DEFA
|
|
|
629
866
|
exit 1
|
|
630
867
|
fi
|
|
631
868
|
|
|
632
|
-
REVIEWER_PROMPT_PATH=$(
|
|
869
|
+
REVIEWER_PROMPT_PATH=$(resolve_instruction_path_with_fallback "${REVIEW_WORKTREE_DIR}" "pr-reviewer.md" "night-watch-pr-reviewer.md" || true)
|
|
633
870
|
if [ -z "${REVIEWER_PROMPT_PATH}" ]; then
|
|
634
|
-
log "FAIL: Missing reviewer prompt file. Checked instructions/, .claude/commands/, and bundled templates/"
|
|
871
|
+
log "FAIL: Missing reviewer prompt file. Checked pr-reviewer.md/night-watch-pr-reviewer.md in instructions/, .claude/commands/, and bundled templates/"
|
|
635
872
|
emit_result "failure" "reason=missing_reviewer_prompt"
|
|
636
873
|
exit 1
|
|
637
874
|
fi
|
|
875
|
+
REVIEWER_PROMPT_BUNDLED_NAME="pr-reviewer.md"
|
|
876
|
+
if [[ "${REVIEWER_PROMPT_PATH}" == */night-watch-pr-reviewer.md ]]; then
|
|
877
|
+
REVIEWER_PROMPT_BUNDLED_NAME="night-watch-pr-reviewer.md"
|
|
878
|
+
fi
|
|
879
|
+
REVIEWER_PROMPT_PATH=$(prefer_bundled_prompt_if_legacy_command "${REVIEW_WORKTREE_DIR}" "${REVIEWER_PROMPT_PATH}" "${REVIEWER_PROMPT_BUNDLED_NAME}")
|
|
638
880
|
REVIEWER_PROMPT_BASE=$(cat "${REVIEWER_PROMPT_PATH}")
|
|
639
881
|
REVIEWER_PROMPT_REF=$(instruction_ref_for_prompt "${REVIEW_WORKTREE_DIR}" "${REVIEWER_PROMPT_PATH}")
|
|
640
882
|
log "INFO: Using reviewer prompt from ${REVIEWER_PROMPT_REF}"
|
|
641
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
|
+
|
|
642
889
|
EXIT_CODE=0
|
|
643
890
|
ATTEMPTS_MADE=1
|
|
644
891
|
FINAL_SCORE=""
|
|
@@ -663,6 +910,22 @@ if [ -n "${TARGET_PR}" ]; then
|
|
|
663
910
|
fi
|
|
664
911
|
fi
|
|
665
912
|
|
|
913
|
+
PRD_CONTEXT_PROMPT=""
|
|
914
|
+
if [ -n "${TARGET_PR}" ]; then
|
|
915
|
+
PRD_CONTEXT_PROMPT=$(build_prd_context_prompt "${TARGET_PR}")
|
|
916
|
+
elif [ "${#PR_NUMBER_ARRAY[@]}" -gt 0 ]; then
|
|
917
|
+
PRD_CONTEXT_PROMPT=$(build_prd_context_prompt "${PR_NUMBER_ARRAY[@]}")
|
|
918
|
+
fi
|
|
919
|
+
if [ -n "${PRD_CONTEXT_PROMPT}" ]; then
|
|
920
|
+
if [ -n "${TARGET_PR}" ]; then
|
|
921
|
+
log "INFO: Added PRD context for PR #${TARGET_PR}"
|
|
922
|
+
else
|
|
923
|
+
log "INFO: Added PRD context for ${#PR_NUMBER_ARRAY[@]} PR(s)"
|
|
924
|
+
fi
|
|
925
|
+
else
|
|
926
|
+
log "WARN: No PRD context found for current reviewer scope"
|
|
927
|
+
fi
|
|
928
|
+
|
|
666
929
|
# ── Retry Loop for Targeted PR Review ──────────────────────────────────────────
|
|
667
930
|
# Only retry when targeting a specific PR. Non-targeted mode handles all PRs in one shot.
|
|
668
931
|
TOTAL_ATTEMPTS=1
|
|
@@ -695,7 +958,7 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
695
958
|
|
|
696
959
|
log "RETRY: Starting attempt ${ATTEMPT}/${TOTAL_ATTEMPTS} (timeout: ${ATTEMPT_TIMEOUT}s)"
|
|
697
960
|
LOG_LINE_BEFORE=$(wc -l < "${LOG_FILE}" 2>/dev/null || echo 0)
|
|
698
|
-
REVIEWER_PROMPT="${REVIEWER_PROMPT_BASE}${TARGET_SCOPE_PROMPT}"
|
|
961
|
+
REVIEWER_PROMPT="${REVIEWER_PROMPT_BASE}${TARGET_SCOPE_PROMPT}${PRD_CONTEXT_PROMPT}"
|
|
699
962
|
|
|
700
963
|
case "${PROVIDER_CMD}" in
|
|
701
964
|
claude)
|