@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.
- package/dist/shared/types.d.ts +1 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/audit.d.ts +19 -0
- package/dist/src/commands/audit.d.ts.map +1 -0
- package/dist/src/commands/audit.js +109 -0
- package/dist/src/commands/audit.js.map +1 -0
- package/dist/src/commands/dashboard.js +1 -1
- package/dist/src/commands/dashboard.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +6 -6
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/install.d.ts +4 -0
- package/dist/src/commands/install.d.ts.map +1 -1
- package/dist/src/commands/install.js +25 -20
- package/dist/src/commands/install.js.map +1 -1
- package/dist/src/commands/logs.js +3 -3
- package/dist/src/commands/logs.js.map +1 -1
- package/dist/src/commands/prs.js +2 -2
- package/dist/src/commands/prs.js.map +1 -1
- package/dist/src/commands/review.d.ts.map +1 -1
- package/dist/src/commands/review.js +13 -5
- package/dist/src/commands/review.js.map +1 -1
- package/dist/src/commands/uninstall.d.ts.map +1 -1
- package/dist/src/commands/uninstall.js +3 -22
- package/dist/src/commands/uninstall.js.map +1 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +30 -1
- package/dist/src/config.js.map +1 -1
- package/dist/src/constants.d.ts +10 -3
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +15 -2
- package/dist/src/constants.js.map +1 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +50 -3
- package/dist/src/server/index.js.map +1 -1
- package/dist/src/slack/client.d.ts +3 -2
- package/dist/src/slack/client.d.ts.map +1 -1
- package/dist/src/slack/client.js +5 -6
- package/dist/src/slack/client.js.map +1 -1
- package/dist/src/slack/deliberation.d.ts +13 -1
- package/dist/src/slack/deliberation.d.ts.map +1 -1
- package/dist/src/slack/deliberation.js +585 -71
- package/dist/src/slack/deliberation.js.map +1 -1
- package/dist/src/slack/interaction-listener.d.ts +27 -9
- package/dist/src/slack/interaction-listener.d.ts.map +1 -1
- package/dist/src/slack/interaction-listener.js +418 -201
- package/dist/src/slack/interaction-listener.js.map +1 -1
- package/dist/src/storage/repositories/sqlite/agent-persona-repository.d.ts +3 -2
- package/dist/src/storage/repositories/sqlite/agent-persona-repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/sqlite/agent-persona-repository.js +14 -11
- package/dist/src/storage/repositories/sqlite/agent-persona-repository.js.map +1 -1
- package/dist/src/types.d.ts +13 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/notify.d.ts.map +1 -1
- package/dist/src/utils/notify.js +5 -1
- package/dist/src/utils/notify.js.map +1 -1
- package/dist/src/utils/status-data.d.ts +2 -2
- package/dist/src/utils/status-data.d.ts.map +1 -1
- package/dist/src/utils/status-data.js +78 -123
- package/dist/src/utils/status-data.js.map +1 -1
- package/package.json +3 -1
- package/scripts/night-watch-audit-cron.sh +165 -0
- package/scripts/night-watch-cron.sh +33 -14
- package/scripts/night-watch-helpers.sh +10 -2
- package/scripts/night-watch-pr-reviewer-cron.sh +224 -18
- package/templates/night-watch-audit.md +87 -0
- package/web/dist/assets/index-BiJf9LFT.js +458 -0
- package/web/dist/assets/index-OpSgvsYu.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-CndIPm_F.js +0 -473
- 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
|
|
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 "${
|
|
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}/
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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 "
|
|
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 "$
|
|
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
|
-
|
|
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.
|