@jonit-dev/night-watch-cli 1.7.46 → 1.7.48
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 +1097 -705
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +21 -1
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -37
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +20 -12
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +37 -5
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/prd.d.ts.map +1 -1
- package/dist/commands/prd.js +2 -6
- package/dist/commands/prd.js.map +1 -1
- package/dist/commands/prds.d.ts.map +1 -1
- package/dist/commands/prds.js +1 -1
- package/dist/commands/prds.js.map +1 -1
- package/dist/commands/qa.d.ts.map +1 -1
- package/dist/commands/qa.js +20 -0
- package/dist/commands/qa.js.map +1 -1
- package/dist/commands/review.d.ts +10 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +38 -0
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +7 -2
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/slice.d.ts.map +1 -1
- package/dist/commands/slice.js +88 -17
- package/dist/commands/slice.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +75 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/scripts/night-watch-audit-cron.sh +19 -0
- package/dist/scripts/night-watch-cron.sh +2 -2
- package/dist/scripts/night-watch-helpers.sh +47 -1
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +168 -35
- package/dist/scripts/night-watch-qa-cron.sh +19 -0
- package/dist/scripts/night-watch-slicer-cron.sh +10 -1
- package/dist/templates/night-watch-slicer.md +18 -3
- package/dist/templates/night-watch.config.json +1 -0
- package/dist/templates/night-watch.md +10 -37
- package/dist/web/assets/index-Ba-4YvTQ.js +365 -0
- package/dist/web/assets/index-DpVirMEe.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
|
@@ -28,6 +28,24 @@ TARGET_PR="${NW_TARGET_PR:-}"
|
|
|
28
28
|
PARALLEL_ENABLED="${NW_REVIEWER_PARALLEL:-1}"
|
|
29
29
|
WORKER_MODE="${NW_REVIEWER_WORKER_MODE:-0}"
|
|
30
30
|
|
|
31
|
+
# Retry configuration
|
|
32
|
+
REVIEWER_MAX_RETRIES="${NW_REVIEWER_MAX_RETRIES:-2}"
|
|
33
|
+
REVIEWER_RETRY_DELAY="${NW_REVIEWER_RETRY_DELAY:-30}"
|
|
34
|
+
|
|
35
|
+
# Normalize retry settings to safe numeric ranges
|
|
36
|
+
if ! [[ "${REVIEWER_MAX_RETRIES}" =~ ^[0-9]+$ ]]; then
|
|
37
|
+
REVIEWER_MAX_RETRIES="2"
|
|
38
|
+
fi
|
|
39
|
+
if ! [[ "${REVIEWER_RETRY_DELAY}" =~ ^[0-9]+$ ]]; then
|
|
40
|
+
REVIEWER_RETRY_DELAY="30"
|
|
41
|
+
fi
|
|
42
|
+
if [ "${REVIEWER_MAX_RETRIES}" -gt 10 ]; then
|
|
43
|
+
REVIEWER_MAX_RETRIES="10"
|
|
44
|
+
fi
|
|
45
|
+
if [ "${REVIEWER_RETRY_DELAY}" -gt 300 ]; then
|
|
46
|
+
REVIEWER_RETRY_DELAY="300"
|
|
47
|
+
fi
|
|
48
|
+
|
|
31
49
|
# Ensure NVM / Node / Claude are on PATH
|
|
32
50
|
export NVM_DIR="${HOME}/.nvm"
|
|
33
51
|
[ -s "${NVM_DIR}/nvm.sh" ] && . "${NVM_DIR}/nvm.sh"
|
|
@@ -64,16 +82,31 @@ emit_final_status() {
|
|
|
64
82
|
local prs_csv="${2:-}"
|
|
65
83
|
local auto_merged="${3:-}"
|
|
66
84
|
local auto_merge_failed="${4:-}"
|
|
85
|
+
local attempts="${5:-1}"
|
|
86
|
+
local final_score="${6:-}"
|
|
87
|
+
local details=""
|
|
67
88
|
|
|
68
89
|
if [ "${exit_code}" -eq 0 ]; then
|
|
90
|
+
details="prs=${prs_csv}|auto_merged=${auto_merged}|auto_merge_failed=${auto_merge_failed}|attempts=${attempts}"
|
|
91
|
+
if [ -n "${final_score}" ]; then
|
|
92
|
+
details="${details}|final_score=${final_score}"
|
|
93
|
+
fi
|
|
69
94
|
log "DONE: PR reviewer completed successfully"
|
|
70
|
-
emit_result "success_reviewed" "
|
|
95
|
+
emit_result "success_reviewed" "${details}"
|
|
71
96
|
elif [ "${exit_code}" -eq 124 ]; then
|
|
97
|
+
details="prs=${prs_csv}|attempts=${attempts}"
|
|
98
|
+
if [ -n "${final_score}" ]; then
|
|
99
|
+
details="${details}|final_score=${final_score}"
|
|
100
|
+
fi
|
|
72
101
|
log "TIMEOUT: PR reviewer killed after ${MAX_RUNTIME}s"
|
|
73
|
-
emit_result "timeout" "
|
|
102
|
+
emit_result "timeout" "${details}"
|
|
74
103
|
else
|
|
104
|
+
details="prs=${prs_csv}|attempts=${attempts}"
|
|
105
|
+
if [ -n "${final_score}" ]; then
|
|
106
|
+
details="${details}|final_score=${final_score}"
|
|
107
|
+
fi
|
|
75
108
|
log "FAIL: PR reviewer exited with code ${exit_code}"
|
|
76
|
-
emit_result "failure" "
|
|
109
|
+
emit_result "failure" "${details}"
|
|
77
110
|
fi
|
|
78
111
|
}
|
|
79
112
|
|
|
@@ -91,6 +124,25 @@ append_csv() {
|
|
|
91
124
|
fi
|
|
92
125
|
}
|
|
93
126
|
|
|
127
|
+
# Extract the latest review score from PR comments
|
|
128
|
+
# Returns empty string if no score found
|
|
129
|
+
get_pr_score() {
|
|
130
|
+
local pr_number="${1:?PR number required}"
|
|
131
|
+
local all_comments
|
|
132
|
+
all_comments=$(
|
|
133
|
+
{
|
|
134
|
+
gh pr view "${pr_number}" --json comments --jq '.comments[].body' 2>/dev/null || true
|
|
135
|
+
if [ -n "${REPO:-}" ]; then
|
|
136
|
+
gh api "repos/${REPO}/issues/${pr_number}/comments" --jq '.[].body' 2>/dev/null || true
|
|
137
|
+
fi
|
|
138
|
+
} | sort -u
|
|
139
|
+
)
|
|
140
|
+
echo "${all_comments}" \
|
|
141
|
+
| grep -oP 'Overall Score:\*?\*?\s*(\d+)/100' \
|
|
142
|
+
| tail -1 \
|
|
143
|
+
| grep -oP '\d+(?=/100)' || echo ""
|
|
144
|
+
}
|
|
145
|
+
|
|
94
146
|
# Validate provider
|
|
95
147
|
if ! validate_provider "${PROVIDER_CMD}"; then
|
|
96
148
|
echo "ERROR: Unknown provider: ${PROVIDER_CMD}" >&2
|
|
@@ -323,6 +375,8 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
323
375
|
EXIT_CODE=0
|
|
324
376
|
AUTO_MERGED_PRS=""
|
|
325
377
|
AUTO_MERGE_FAILED_PRS=""
|
|
378
|
+
MAX_WORKER_ATTEMPTS=1
|
|
379
|
+
MAX_WORKER_FINAL_SCORE=""
|
|
326
380
|
|
|
327
381
|
for idx in "${!WORKER_PIDS[@]}"; do
|
|
328
382
|
worker_pid="${WORKER_PIDS[$idx]}"
|
|
@@ -344,10 +398,21 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
344
398
|
worker_status=$(printf '%s' "${worker_result}" | sed -n 's/^NIGHT_WATCH_RESULT:\([^|]*\).*$/\1/p')
|
|
345
399
|
worker_auto_merged=$(printf '%s' "${worker_result}" | grep -oP '(?<=auto_merged=)[^|]+' || true)
|
|
346
400
|
worker_auto_merge_failed=$(printf '%s' "${worker_result}" | grep -oP '(?<=auto_merge_failed=)[^|]+' || true)
|
|
401
|
+
worker_attempts=$(printf '%s' "${worker_result}" | grep -oP '(?<=attempts=)[^|]+' || true)
|
|
402
|
+
worker_final_score=$(printf '%s' "${worker_result}" | grep -oP '(?<=final_score=)[^|]+' || true)
|
|
347
403
|
|
|
348
404
|
AUTO_MERGED_PRS=$(append_csv "${AUTO_MERGED_PRS}" "${worker_auto_merged}")
|
|
349
405
|
AUTO_MERGE_FAILED_PRS=$(append_csv "${AUTO_MERGE_FAILED_PRS}" "${worker_auto_merge_failed}")
|
|
350
406
|
|
|
407
|
+
if [[ "${worker_attempts}" =~ ^[0-9]+$ ]] && [ "${worker_attempts}" -gt "${MAX_WORKER_ATTEMPTS}" ]; then
|
|
408
|
+
MAX_WORKER_ATTEMPTS="${worker_attempts}"
|
|
409
|
+
fi
|
|
410
|
+
if [[ "${worker_final_score}" =~ ^[0-9]+$ ]]; then
|
|
411
|
+
if [ -z "${MAX_WORKER_FINAL_SCORE}" ] || [ "${worker_final_score}" -gt "${MAX_WORKER_FINAL_SCORE}" ]; then
|
|
412
|
+
MAX_WORKER_FINAL_SCORE="${worker_final_score}"
|
|
413
|
+
fi
|
|
414
|
+
fi
|
|
415
|
+
|
|
351
416
|
rm -f "${worker_output}"
|
|
352
417
|
|
|
353
418
|
if [ "${worker_status}" = "failure" ] || { [ -n "${worker_status}" ] && [ "${worker_status}" != "success_reviewed" ] && [ "${worker_status}" != "timeout" ] && [ "${worker_status#skip_}" = "${worker_status}" ]; }; then
|
|
@@ -374,7 +439,7 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
374
439
|
fi
|
|
375
440
|
done
|
|
376
441
|
|
|
377
|
-
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}"
|
|
442
|
+
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}" "${MAX_WORKER_ATTEMPTS}" "${MAX_WORKER_FINAL_SCORE}"
|
|
378
443
|
exit 0
|
|
379
444
|
fi
|
|
380
445
|
|
|
@@ -396,6 +461,8 @@ if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
|
396
461
|
if [ "${AUTO_MERGE}" = "1" ]; then
|
|
397
462
|
echo "Auto-merge Method: ${AUTO_MERGE_METHOD}"
|
|
398
463
|
fi
|
|
464
|
+
echo "Max Retries: ${REVIEWER_MAX_RETRIES}"
|
|
465
|
+
echo "Retry Delay: ${REVIEWER_RETRY_DELAY}s"
|
|
399
466
|
echo "Open PRs needing work:${PRS_NEEDING_WORK}"
|
|
400
467
|
echo "Default Branch: ${DEFAULT_BRANCH}"
|
|
401
468
|
echo "Review Worktree: ${REVIEW_WORKTREE_DIR}"
|
|
@@ -413,44 +480,110 @@ if ! prepare_detached_worktree "${PROJECT_DIR}" "${REVIEW_WORKTREE_DIR}" "${DEFA
|
|
|
413
480
|
fi
|
|
414
481
|
|
|
415
482
|
EXIT_CODE=0
|
|
483
|
+
ATTEMPTS_MADE=1
|
|
484
|
+
FINAL_SCORE=""
|
|
416
485
|
TARGET_SCOPE_PROMPT=""
|
|
417
486
|
if [ -n "${TARGET_PR}" ]; then
|
|
418
487
|
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'
|
|
419
488
|
fi
|
|
420
489
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
490
|
+
# ── Retry Loop for Targeted PR Review ──────────────────────────────────────────
|
|
491
|
+
# Only retry when targeting a specific PR. Non-targeted mode handles all PRs in one shot.
|
|
492
|
+
TOTAL_ATTEMPTS=1
|
|
493
|
+
if [ -n "${TARGET_PR}" ]; then
|
|
494
|
+
TOTAL_ATTEMPTS=$((REVIEWER_MAX_RETRIES + 1))
|
|
495
|
+
fi
|
|
496
|
+
RUN_STARTED_AT=$(date +%s)
|
|
497
|
+
|
|
498
|
+
for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
499
|
+
ATTEMPTS_MADE="${ATTEMPT}"
|
|
500
|
+
|
|
501
|
+
ATTEMPT_TIMEOUT="${MAX_RUNTIME}"
|
|
502
|
+
if [ -n "${TARGET_PR}" ]; then
|
|
503
|
+
# Calculate timeout from remaining runtime budget.
|
|
504
|
+
NOW_TS=$(date +%s)
|
|
505
|
+
ELAPSED=$((NOW_TS - RUN_STARTED_AT))
|
|
506
|
+
REMAINING_BUDGET=$((MAX_RUNTIME - ELAPSED))
|
|
507
|
+
if [ "${REMAINING_BUDGET}" -le 0 ]; then
|
|
508
|
+
EXIT_CODE=124
|
|
509
|
+
log "RETRY: Runtime budget exhausted before attempt ${ATTEMPT}"
|
|
510
|
+
break
|
|
433
511
|
fi
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
512
|
+
|
|
513
|
+
REMAINING_ATTEMPTS=$((TOTAL_ATTEMPTS - ATTEMPT + 1))
|
|
514
|
+
ATTEMPT_TIMEOUT=$((REMAINING_BUDGET / REMAINING_ATTEMPTS))
|
|
515
|
+
if [ "${ATTEMPT_TIMEOUT}" -lt 1 ]; then
|
|
516
|
+
ATTEMPT_TIMEOUT=1
|
|
517
|
+
fi
|
|
518
|
+
fi
|
|
519
|
+
|
|
520
|
+
log "RETRY: Starting attempt ${ATTEMPT}/${TOTAL_ATTEMPTS} (timeout: ${ATTEMPT_TIMEOUT}s)"
|
|
521
|
+
|
|
522
|
+
case "${PROVIDER_CMD}" in
|
|
523
|
+
claude)
|
|
524
|
+
CLAUDE_PROMPT="/night-watch-pr-reviewer${TARGET_SCOPE_PROMPT}"
|
|
525
|
+
if (
|
|
526
|
+
cd "${REVIEW_WORKTREE_DIR}" && timeout "${ATTEMPT_TIMEOUT}" \
|
|
527
|
+
claude -p "${CLAUDE_PROMPT}" \
|
|
528
|
+
--dangerously-skip-permissions \
|
|
529
|
+
>> "${LOG_FILE}" 2>&1
|
|
530
|
+
); then
|
|
531
|
+
EXIT_CODE=0
|
|
532
|
+
else
|
|
533
|
+
EXIT_CODE=$?
|
|
534
|
+
fi
|
|
535
|
+
;;
|
|
536
|
+
codex)
|
|
537
|
+
CODEX_PROMPT="$(cat "${REVIEW_WORKTREE_DIR}/.claude/commands/night-watch-pr-reviewer.md")${TARGET_SCOPE_PROMPT}"
|
|
538
|
+
if (
|
|
539
|
+
cd "${REVIEW_WORKTREE_DIR}" && timeout "${ATTEMPT_TIMEOUT}" \
|
|
540
|
+
codex --quiet \
|
|
541
|
+
--yolo \
|
|
542
|
+
--prompt "${CODEX_PROMPT}" \
|
|
543
|
+
>> "${LOG_FILE}" 2>&1
|
|
544
|
+
); then
|
|
545
|
+
EXIT_CODE=0
|
|
546
|
+
else
|
|
547
|
+
EXIT_CODE=$?
|
|
548
|
+
fi
|
|
549
|
+
;;
|
|
550
|
+
*)
|
|
551
|
+
log "ERROR: Unknown provider: ${PROVIDER_CMD}"
|
|
552
|
+
exit 1
|
|
553
|
+
;;
|
|
554
|
+
esac
|
|
555
|
+
|
|
556
|
+
# If provider failed (non-zero exit), don't retry
|
|
557
|
+
if [ "${EXIT_CODE}" -ne 0 ]; then
|
|
558
|
+
log "RETRY: Provider exited with code ${EXIT_CODE}, not retrying"
|
|
559
|
+
break
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
# Re-check score for the target PR (only in targeted mode)
|
|
563
|
+
if [ -n "${TARGET_PR}" ]; then
|
|
564
|
+
CURRENT_SCORE=$(get_pr_score "${TARGET_PR}")
|
|
565
|
+
if [ -z "${CURRENT_SCORE}" ]; then
|
|
566
|
+
log "RETRY: No review score found for PR #${TARGET_PR} after attempt ${ATTEMPT}; not retrying"
|
|
567
|
+
break
|
|
568
|
+
fi
|
|
569
|
+
|
|
570
|
+
FINAL_SCORE="${CURRENT_SCORE}"
|
|
571
|
+
if [ "${CURRENT_SCORE}" -ge "${MIN_REVIEW_SCORE}" ]; then
|
|
572
|
+
log "RETRY: PR #${TARGET_PR} now scores ${CURRENT_SCORE}/100 (>= ${MIN_REVIEW_SCORE}) after attempt ${ATTEMPT}"
|
|
573
|
+
break
|
|
574
|
+
fi
|
|
575
|
+
if [ "${ATTEMPT}" -lt "${TOTAL_ATTEMPTS}" ]; then
|
|
576
|
+
log "RETRY: PR #${TARGET_PR} scores ${CURRENT_SCORE:-unknown}/100 after attempt ${ATTEMPT}/${TOTAL_ATTEMPTS}, retrying in ${REVIEWER_RETRY_DELAY}s..."
|
|
577
|
+
sleep "${REVIEWER_RETRY_DELAY}"
|
|
445
578
|
else
|
|
446
|
-
|
|
579
|
+
log "RETRY: PR #${TARGET_PR} still at ${CURRENT_SCORE:-unknown}/100 after ${TOTAL_ATTEMPTS} attempts - giving up"
|
|
580
|
+
gh pr edit "${TARGET_PR}" --add-label "needs-human-review" 2>/dev/null || true
|
|
447
581
|
fi
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
esac
|
|
582
|
+
else
|
|
583
|
+
# Non-targeted mode: no retry (reviewer handles all PRs in one shot)
|
|
584
|
+
break
|
|
585
|
+
fi
|
|
586
|
+
done
|
|
454
587
|
|
|
455
588
|
cleanup_worktrees "${PROJECT_DIR}" "${REVIEW_WORKTREE_BASENAME}"
|
|
456
589
|
|
|
@@ -529,4 +662,4 @@ if [ "${AUTO_MERGE}" = "1" ] && [ ${EXIT_CODE} -eq 0 ]; then
|
|
|
529
662
|
done < <(gh pr list --state open --json number,headRefName --jq '.[] | [.number, .headRefName] | @tsv' 2>/dev/null || true)
|
|
530
663
|
fi
|
|
531
664
|
|
|
532
|
-
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}"
|
|
665
|
+
emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}" "${ATTEMPTS_MADE}" "${FINAL_SCORE}"
|
|
@@ -68,6 +68,10 @@ fi
|
|
|
68
68
|
|
|
69
69
|
cd "${PROJECT_DIR}"
|
|
70
70
|
|
|
71
|
+
send_telegram_status_message "🧪 Night Watch QA: started" "Project: ${PROJECT_NAME}
|
|
72
|
+
Provider: ${PROVIDER_CMD}
|
|
73
|
+
Scanning open PRs for QA candidates."
|
|
74
|
+
|
|
71
75
|
# Convert comma-separated branch prefixes into a regex that matches branch starts.
|
|
72
76
|
BRANCH_REGEX=""
|
|
73
77
|
IFS=',' read -r -a BRANCH_PATTERNS <<< "${BRANCH_PATTERNS_RAW}"
|
|
@@ -96,6 +100,8 @@ OPEN_PRS=$(
|
|
|
96
100
|
|
|
97
101
|
if [ "${OPEN_PRS}" -eq 0 ]; then
|
|
98
102
|
log "SKIP: No open PRs matching branch patterns (${BRANCH_PATTERNS_RAW})"
|
|
103
|
+
send_telegram_status_message "🧪 Night Watch QA: no matching PRs" "Project: ${PROJECT_NAME}
|
|
104
|
+
Branch patterns: ${BRANCH_PATTERNS_RAW}"
|
|
99
105
|
emit_result "skip_no_open_prs"
|
|
100
106
|
exit 0
|
|
101
107
|
fi
|
|
@@ -151,6 +157,8 @@ done < <(
|
|
|
151
157
|
|
|
152
158
|
if [ "${QA_NEEDED}" -eq 0 ]; then
|
|
153
159
|
log "SKIP: All ${OPEN_PRS} open PR(s) matching patterns already have QA comments"
|
|
160
|
+
send_telegram_status_message "🧪 Night Watch QA: nothing to do" "Project: ${PROJECT_NAME}
|
|
161
|
+
All matching PRs already have QA results."
|
|
154
162
|
emit_result "skip_all_qa_done"
|
|
155
163
|
exit 0
|
|
156
164
|
fi
|
|
@@ -190,6 +198,9 @@ EXIT_CODE=0
|
|
|
190
198
|
# Process each PR that needs QA
|
|
191
199
|
for pr_ref in ${PRS_NEEDING_QA}; do
|
|
192
200
|
pr_num="${pr_ref#\#}"
|
|
201
|
+
send_telegram_status_message "🧪 Night Watch QA: processing PR #${pr_num}" "Project: ${PROJECT_NAME}
|
|
202
|
+
Provider: ${PROVIDER_CMD}
|
|
203
|
+
Artifacts: ${QA_ARTIFACTS}"
|
|
193
204
|
|
|
194
205
|
cleanup_worktrees "${PROJECT_DIR}"
|
|
195
206
|
if ! prepare_detached_worktree "${PROJECT_DIR}" "${QA_WORKTREE_DIR}" "${DEFAULT_BRANCH}" "${LOG_FILE}"; then
|
|
@@ -257,6 +268,8 @@ cleanup_worktrees "${PROJECT_DIR}"
|
|
|
257
268
|
|
|
258
269
|
if [ ${EXIT_CODE} -eq 0 ]; then
|
|
259
270
|
log "DONE: QA runner completed successfully"
|
|
271
|
+
send_telegram_status_message "🧪 Night Watch QA: completed" "Project: ${PROJECT_NAME}
|
|
272
|
+
Processed PRs: ${PRS_NEEDING_QA_CSV}"
|
|
260
273
|
if [ -n "${REPO}" ]; then
|
|
261
274
|
emit_result "success_qa" "prs=${PRS_NEEDING_QA_CSV}|repo=${REPO}"
|
|
262
275
|
else
|
|
@@ -264,6 +277,9 @@ if [ ${EXIT_CODE} -eq 0 ]; then
|
|
|
264
277
|
fi
|
|
265
278
|
elif [ ${EXIT_CODE} -eq 124 ]; then
|
|
266
279
|
log "TIMEOUT: QA runner killed after ${MAX_RUNTIME}s"
|
|
280
|
+
send_telegram_status_message "🧪 Night Watch QA: timeout" "Project: ${PROJECT_NAME}
|
|
281
|
+
Timeout: ${MAX_RUNTIME}s
|
|
282
|
+
Processed PRs: ${PRS_NEEDING_QA_CSV}"
|
|
267
283
|
if [ -n "${REPO}" ]; then
|
|
268
284
|
emit_result "timeout" "prs=${PRS_NEEDING_QA_CSV}|repo=${REPO}"
|
|
269
285
|
else
|
|
@@ -271,6 +287,9 @@ elif [ ${EXIT_CODE} -eq 124 ]; then
|
|
|
271
287
|
fi
|
|
272
288
|
else
|
|
273
289
|
log "FAIL: QA runner exited with code ${EXIT_CODE}"
|
|
290
|
+
send_telegram_status_message "🧪 Night Watch QA: failed" "Project: ${PROJECT_NAME}
|
|
291
|
+
Exit code: ${EXIT_CODE}
|
|
292
|
+
Processed PRs: ${PRS_NEEDING_QA_CSV}"
|
|
274
293
|
if [ -n "${REPO}" ]; then
|
|
275
294
|
emit_result "failure" "prs=${PRS_NEEDING_QA_CSV}|repo=${REPO}"
|
|
276
295
|
else
|
|
@@ -17,7 +17,7 @@ set -euo pipefail
|
|
|
17
17
|
PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
|
|
18
18
|
PROJECT_NAME=$(basename "${PROJECT_DIR}")
|
|
19
19
|
LOG_DIR="${PROJECT_DIR}/logs"
|
|
20
|
-
LOG_FILE="${LOG_DIR}/
|
|
20
|
+
LOG_FILE="${LOG_DIR}/slicer.log"
|
|
21
21
|
LOCK_FILE=""
|
|
22
22
|
MAX_RUNTIME="${NW_SLICER_MAX_RUNTIME:-600}" # 10 minutes
|
|
23
23
|
MAX_LOG_SIZE="524288" # 512 KB
|
|
@@ -54,6 +54,9 @@ cleanup_on_exit() {
|
|
|
54
54
|
trap cleanup_on_exit EXIT
|
|
55
55
|
|
|
56
56
|
log "START: Running roadmap slicer for ${PROJECT_DIR}"
|
|
57
|
+
send_telegram_status_message "📋 Night Watch Planner: started" "Project: ${PROJECT_NAME}
|
|
58
|
+
Provider: ${PROVIDER_CMD}
|
|
59
|
+
Planning next roadmap item into a PRD."
|
|
57
60
|
|
|
58
61
|
# Dry-run mode: print diagnostics and exit
|
|
59
62
|
if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
@@ -81,10 +84,16 @@ fi
|
|
|
81
84
|
|
|
82
85
|
if [ ${EXIT_CODE} -eq 0 ]; then
|
|
83
86
|
log "DONE: Slicer completed successfully"
|
|
87
|
+
send_telegram_status_message "📋 Night Watch Planner: completed" "Project: ${PROJECT_NAME}
|
|
88
|
+
PRD planning run finished successfully."
|
|
84
89
|
elif [ ${EXIT_CODE} -eq 124 ]; then
|
|
85
90
|
log "TIMEOUT: Slicer killed after ${MAX_RUNTIME}s"
|
|
91
|
+
send_telegram_status_message "📋 Night Watch Planner: timeout" "Project: ${PROJECT_NAME}
|
|
92
|
+
Timeout: ${MAX_RUNTIME}s"
|
|
86
93
|
else
|
|
87
94
|
log "FAIL: Slicer exited with code ${EXIT_CODE}"
|
|
95
|
+
send_telegram_status_message "📋 Night Watch Planner: failed" "Project: ${PROJECT_NAME}
|
|
96
|
+
Exit code: ${EXIT_CODE}"
|
|
88
97
|
fi
|
|
89
98
|
|
|
90
99
|
exit ${EXIT_CODE}
|
|
@@ -21,6 +21,8 @@ The PRD directory is: `{{PRD_DIR}}`
|
|
|
21
21
|
|
|
22
22
|
## Your Task
|
|
23
23
|
|
|
24
|
+
0. **Load Planner Skill** - Read and apply `.claude/skills/prd-creator/SKILL.md` before writing the PRD. If unavailable, continue with the instructions in this template.
|
|
25
|
+
|
|
24
26
|
1. **Explore the Codebase** - Read relevant existing files to understand the project structure, patterns, and conventions. Look for:
|
|
25
27
|
- CLAUDE.md or similar AI assistant documentation files
|
|
26
28
|
- Existing code patterns in the area you'll be modifying
|
|
@@ -61,7 +63,7 @@ COMPLEXITY SCORE (sum all that apply):
|
|
|
61
63
|
|
|
62
64
|
Your PRD MUST follow this exact structure:
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
````markdown
|
|
65
67
|
# PRD: [Title from roadmap item]
|
|
66
68
|
|
|
67
69
|
**Depends on:** [List any prerequisite PRDs/files, or omit if none]
|
|
@@ -77,24 +79,29 @@ Your PRD MUST follow this exact structure:
|
|
|
77
79
|
**Problem:** [1-2 sentences describing the issue being solved]
|
|
78
80
|
|
|
79
81
|
**Files Analyzed:**
|
|
82
|
+
|
|
80
83
|
- `path/to/file.ts` — [what the file does]
|
|
81
84
|
- [List all files you inspected before planning]
|
|
82
85
|
|
|
83
86
|
**Current Behavior:**
|
|
87
|
+
|
|
84
88
|
- [3-5 bullets describing current state]
|
|
85
89
|
|
|
86
90
|
### Integration Points Checklist
|
|
87
91
|
|
|
88
92
|
**How will this feature be reached?**
|
|
93
|
+
|
|
89
94
|
- [ ] Entry point identified: [e.g., route, event, cron, CLI command]
|
|
90
95
|
- [ ] Caller file identified: [file that will invoke this new code]
|
|
91
96
|
- [ ] Registration/wiring needed: [e.g., add route to router, register handler, add menu item]
|
|
92
97
|
|
|
93
98
|
**Is this user-facing?**
|
|
99
|
+
|
|
94
100
|
- [ ] YES → UI components required (list them)
|
|
95
101
|
- [ ] NO → Internal/background feature (explain how it's triggered)
|
|
96
102
|
|
|
97
103
|
**Full user flow:**
|
|
104
|
+
|
|
98
105
|
1. User does: [action]
|
|
99
106
|
2. Triggers: [what code path]
|
|
100
107
|
3. Reaches new feature via: [specific connection point]
|
|
@@ -105,6 +112,7 @@ Your PRD MUST follow this exact structure:
|
|
|
105
112
|
## 2. Solution
|
|
106
113
|
|
|
107
114
|
**Approach:**
|
|
115
|
+
|
|
108
116
|
- [3-5 bullets explaining the chosen solution]
|
|
109
117
|
|
|
110
118
|
**Architecture Diagram** <!-- (MEDIUM/HIGH complexity) -->:
|
|
@@ -113,8 +121,10 @@ Your PRD MUST follow this exact structure:
|
|
|
113
121
|
flowchart LR
|
|
114
122
|
A[Component A] --> B[Component B] --> C[Component C]
|
|
115
123
|
```
|
|
124
|
+
````
|
|
116
125
|
|
|
117
126
|
**Key Decisions:**
|
|
127
|
+
|
|
118
128
|
- [Library/framework choices, error-handling strategy, reused utilities]
|
|
119
129
|
|
|
120
130
|
**Data Changes:** [New schemas/migrations, or "None"]
|
|
@@ -140,6 +150,7 @@ sequenceDiagram
|
|
|
140
150
|
## 4. Execution Phases
|
|
141
151
|
|
|
142
152
|
**CRITICAL RULES:**
|
|
153
|
+
|
|
143
154
|
1. Each phase = ONE user-testable vertical slice
|
|
144
155
|
2. Max 5 files per phase (split if larger)
|
|
145
156
|
3. Each phase MUST include concrete tests
|
|
@@ -148,9 +159,11 @@ sequenceDiagram
|
|
|
148
159
|
### Phase 1: [Name] — [User-visible outcome in 1 sentence]
|
|
149
160
|
|
|
150
161
|
**Files (max 5):**
|
|
162
|
+
|
|
151
163
|
- `src/path/file.ts` — [what changes]
|
|
152
164
|
|
|
153
165
|
**Implementation:**
|
|
166
|
+
|
|
154
167
|
- [ ] Step 1
|
|
155
168
|
- [ ] Step 2
|
|
156
169
|
|
|
@@ -160,6 +173,7 @@ sequenceDiagram
|
|
|
160
173
|
| `src/__tests__/feature.test.ts` | `should do X when Y` | `expect(result).toBe(Z)` |
|
|
161
174
|
|
|
162
175
|
**Verification Plan:**
|
|
176
|
+
|
|
163
177
|
1. **Unit Tests:** File and test names
|
|
164
178
|
2. **Integration Test:** (if applicable)
|
|
165
179
|
3. **User Verification:**
|
|
@@ -183,7 +197,8 @@ sequenceDiagram
|
|
|
183
197
|
- [ ] Feature is reachable (entry point connected, not orphaned code)
|
|
184
198
|
- [ ] [additional criterion specific to this feature]
|
|
185
199
|
- [ ] [additional criterion specific to this feature]
|
|
186
|
-
|
|
200
|
+
|
|
201
|
+
````
|
|
187
202
|
|
|
188
203
|
---
|
|
189
204
|
|
|
@@ -216,4 +231,4 @@ After writing the PRD file, report:
|
|
|
216
231
|
|
|
217
232
|
### Summary
|
|
218
233
|
[1-2 sentences summarizing the PRD content]
|
|
219
|
-
|
|
234
|
+
````
|
|
@@ -2,9 +2,9 @@ You are the Night Watch agent. Your job is to autonomously pick up PRD tickets a
|
|
|
2
2
|
|
|
3
3
|
## Instructions
|
|
4
4
|
|
|
5
|
-
1. **Scan for PRDs**:
|
|
5
|
+
1. **Scan for PRDs**: Use `night-watch prd list --json` to get available PRDs. Each PRD is a ticket.
|
|
6
6
|
|
|
7
|
-
2. **Check dependencies**:
|
|
7
|
+
2. **Check dependencies**: For each PRD, verify its dependencies are satisfied (depended-on PRD is marked as done). Skip PRDs with unmet dependencies.
|
|
8
8
|
|
|
9
9
|
3. **Check for already-in-progress PRDs**: Before processing any PRD, check if a PR already exists for it:
|
|
10
10
|
|
|
@@ -30,11 +30,11 @@ You are the Night Watch agent. Your job is to autonomously pick up PRD tickets a
|
|
|
30
30
|
d. `cd` into the worktree and run package install (npm install, yarn install, or pnpm install as appropriate). Keep all implementation steps inside this worktree.
|
|
31
31
|
|
|
32
32
|
e. **Implement the PRD using the PRD Executor workflow**:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
- Read `.claude/skills/prd-executor/SKILL.md` (preferred) or `.claude/commands/prd-executor.md` (fallback), and follow the full execution pipeline.
|
|
34
|
+
- This means: parse the PRD phases, build a dependency graph, create a task list, and execute phases in parallel waves using agent swarms.
|
|
35
|
+
- Maximize parallelism — launch all independent phases concurrently.
|
|
36
|
+
- Run the project's verify/test command between waves to catch issues early.
|
|
37
|
+
- Follow all project conventions from AI assistant documentation files (e.g., CLAUDE.md, AGENTS.md, or similar).
|
|
38
38
|
|
|
39
39
|
f. **Write tests** as specified in each PRD phase (the prd-executor agents handle this per-phase).
|
|
40
40
|
|
|
@@ -58,37 +58,10 @@ You are the Night Watch agent. Your job is to autonomously pick up PRD tickets a
|
|
|
58
58
|
gh pr create --title "feat: <short title>" --body "<summary with PRD reference>"
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
j. **
|
|
61
|
+
j. **Mark PRD as done**: `night-watch prd done <filename>`
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
cd ${PROJECT_DIR}
|
|
65
|
-
git checkout ${DEFAULT_BRANCH}
|
|
66
|
-
mkdir -p docs/PRDs/night-watch/done
|
|
67
|
-
mv docs/PRDs/night-watch/<file>.md docs/PRDs/night-watch/done/
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
k. **Update summary**: Append to `docs/PRDs/night-watch/NIGHT-WATCH-SUMMARY.md`:
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
## <Title>
|
|
74
|
-
- **PRD**: <filename>
|
|
75
|
-
- **Branch**: night-watch/<name>
|
|
76
|
-
- **PR**: <url>
|
|
77
|
-
- **Date**: <YYYY-MM-DD>
|
|
78
|
-
- **Status**: PR Opened
|
|
79
|
-
### What was done
|
|
80
|
-
<bullet points>
|
|
81
|
-
### Files changed
|
|
82
|
-
<list>
|
|
83
|
-
---
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
l. **Commit** the move + summary update, push ${DEFAULT_BRANCH}.
|
|
87
|
-
|
|
88
|
-
m. **Clean up worktree**: `git worktree remove ../${PROJECT_NAME}-nw-<prd-name>`
|
|
89
|
-
|
|
90
|
-
n. **STOP after this PRD**. Do NOT continue to the next PRD. One PRD per run prevents timeouts and reduces risk. The next cron trigger will pick up the next PRD.
|
|
63
|
+
k. **STOP after this PRD**. Do NOT continue to the next PRD. One PRD per run prevents timeouts and reduces risk. The next cron trigger will pick up the next PRD.
|
|
91
64
|
|
|
92
|
-
5. **On failure**: Do NOT
|
|
65
|
+
5. **On failure**: Do NOT mark the PRD as done. Log the failure and clean up worktree. **Stop** -- do not attempt the next PRD.
|
|
93
66
|
|
|
94
67
|
Start now. Scan for available PRDs and process the first eligible one.
|