@jonit-dev/night-watch-cli 1.8.12-beta.1 → 1.8.12-beta.10
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 +3729 -2027
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts +12 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +307 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/analytics.d.ts.map +1 -1
- package/dist/commands/analytics.js +60 -0
- package/dist/commands/analytics.js.map +1 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +45 -0
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/merge.js +30 -3
- package/dist/commands/merge.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +45 -0
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/qa.d.ts.map +1 -1
- package/dist/commands/qa.js +24 -0
- package/dist/commands/qa.js.map +1 -1
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/queue.js +20 -0
- package/dist/commands/queue.js.map +1 -1
- package/dist/commands/resolve.d.ts.map +1 -1
- package/dist/commands/resolve.js +26 -0
- package/dist/commands/resolve.js.map +1 -1
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +46 -1
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts +16 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +85 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/shared/env-builder.d.ts.map +1 -1
- package/dist/commands/shared/env-builder.js +1 -0
- package/dist/commands/shared/env-builder.js.map +1 -1
- package/dist/commands/shared/feedback.d.ts +24 -0
- package/dist/commands/shared/feedback.d.ts.map +1 -0
- package/dist/commands/shared/feedback.js +38 -0
- package/dist/commands/shared/feedback.js.map +1 -0
- package/dist/commands/slice.d.ts.map +1 -1
- package/dist/commands/slice.js +48 -1
- package/dist/commands/slice.js.map +1 -1
- package/dist/scripts/night-watch-audit-cron.sh +1 -0
- package/dist/scripts/night-watch-cron.sh +186 -23
- package/dist/scripts/night-watch-helpers.sh +65 -2
- package/dist/scripts/night-watch-merger-cron.sh +137 -36
- package/dist/scripts/night-watch-plan-cron.sh +2 -0
- package/dist/scripts/night-watch-pr-resolver-cron.sh +6 -0
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +16 -5
- package/dist/scripts/night-watch-qa-cron.sh +8 -1
- package/dist/scripts/night-watch-slicer-cron.sh +3 -0
- package/dist/templates/night-watch.config.json +21 -0
- package/dist/templates/slicer.md +3 -0
- package/dist/web/assets/index-B6E6kOoR.js +406 -0
- package/dist/web/assets/index-C-xpWpS8.css +1 -0
- package/dist/web/assets/index-CEYe-290.js +412 -0
- package/dist/web/assets/index-DIMUXIP8.css +1 -0
- package/dist/web/assets/index-DpvzoXEv.js +442 -0
- package/dist/web/assets/index-DyME41HV.css +1 -0
- package/dist/web/assets/index-NR27JE3b.js +406 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -1
|
@@ -14,6 +14,8 @@ set -euo pipefail
|
|
|
14
14
|
# NW_MERGER_BRANCH_PATTERNS= - Comma-separated branch prefixes (empty = all)
|
|
15
15
|
# NW_MERGER_REBASE_BEFORE_MERGE=1 - Set to 1 to rebase before merging
|
|
16
16
|
# NW_MERGER_MAX_PRS_PER_RUN=0 - Max PRs to merge per run (0 = unlimited)
|
|
17
|
+
# NW_MERGER_CI_MAX_WAIT=300 - Max seconds to wait for checks after rebase
|
|
18
|
+
# NW_MERGER_CI_POLL_INTERVAL=15 - Seconds between check polls after rebase
|
|
17
19
|
# NW_DRY_RUN=0 - Set to 1 for dry-run mode
|
|
18
20
|
|
|
19
21
|
PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
|
|
@@ -26,7 +28,10 @@ MERGE_METHOD="${NW_MERGER_MERGE_METHOD:-squash}"
|
|
|
26
28
|
MIN_REVIEW_SCORE="${NW_MERGER_MIN_REVIEW_SCORE:-80}"
|
|
27
29
|
REBASE_BEFORE_MERGE="${NW_MERGER_REBASE_BEFORE_MERGE:-1}"
|
|
28
30
|
MAX_PRS_PER_RUN="${NW_MERGER_MAX_PRS_PER_RUN:-0}"
|
|
31
|
+
CI_MAX_WAIT="${NW_MERGER_CI_MAX_WAIT:-300}"
|
|
32
|
+
CI_POLL_INTERVAL="${NW_MERGER_CI_POLL_INTERVAL:-15}"
|
|
29
33
|
BRANCH_PATTERNS_RAW="${NW_MERGER_BRANCH_PATTERNS:-}"
|
|
34
|
+
READY_TO_MERGE_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
|
|
30
35
|
SCRIPT_START_TIME=$(date +%s)
|
|
31
36
|
DRY_RUN="${NW_DRY_RUN:-0}"
|
|
32
37
|
PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
|
|
@@ -39,6 +44,12 @@ fi
|
|
|
39
44
|
if ! [[ "${MIN_REVIEW_SCORE}" =~ ^[0-9]+$ ]]; then
|
|
40
45
|
MIN_REVIEW_SCORE="80"
|
|
41
46
|
fi
|
|
47
|
+
if ! [[ "${CI_MAX_WAIT}" =~ ^[0-9]+$ ]]; then
|
|
48
|
+
CI_MAX_WAIT="300"
|
|
49
|
+
fi
|
|
50
|
+
if ! [[ "${CI_POLL_INTERVAL}" =~ ^[0-9]+$ ]] || [ "${CI_POLL_INTERVAL}" = "0" ]; then
|
|
51
|
+
CI_POLL_INTERVAL="15"
|
|
52
|
+
fi
|
|
42
53
|
# Clamp merge method to valid values
|
|
43
54
|
case "${MERGE_METHOD}" in
|
|
44
55
|
squash|merge|rebase) ;;
|
|
@@ -55,6 +66,7 @@ PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
|
55
66
|
# NOTE: Lock file path must match mergerLockPath() in src/utils/status-data.ts
|
|
56
67
|
LOCK_FILE="/tmp/night-watch-merger-${PROJECT_RUNTIME_KEY}.lock"
|
|
57
68
|
SCRIPT_TYPE="merger"
|
|
69
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
58
70
|
|
|
59
71
|
MERGED_PRS=0
|
|
60
72
|
FAILED_PRS=0
|
|
@@ -103,29 +115,83 @@ get_review_score() {
|
|
|
103
115
|
# Look for review score comment from night-watch
|
|
104
116
|
local score
|
|
105
117
|
score=$(gh pr view "${pr_number}" --json comments \
|
|
106
|
-
--jq '[.comments[].body | select(test("
|
|
118
|
+
--jq '[.comments[].body | select(test("score[^0-9]{0,30}[0-9]+/100"; "i")) | capture("(?i)score[^0-9]{0,30}(?<s>[0-9]+)/100") | .s] | last | tonumber // -1' \
|
|
107
119
|
2>/dev/null || echo "-1")
|
|
108
120
|
echo "${score}"
|
|
109
121
|
}
|
|
110
122
|
|
|
111
|
-
#
|
|
112
|
-
|
|
123
|
+
# Get the current head OID for a PR.
|
|
124
|
+
get_pr_head_oid() {
|
|
113
125
|
local pr_number="${1}"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return
|
|
126
|
+
gh pr view "${pr_number}" --json headRefOid --jq '.headRefOid // ""' 2>/dev/null || echo ""
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Return the CI state for the PR's status rollup on the expected head OID.
|
|
130
|
+
ci_status_for_head() {
|
|
131
|
+
local pr_number="${1}"
|
|
132
|
+
local expected_head="${2:-}"
|
|
133
|
+
local status_json
|
|
134
|
+
|
|
135
|
+
status_json=$(gh pr view "${pr_number}" --json headRefOid,statusCheckRollup 2>/dev/null || echo "")
|
|
136
|
+
if [ -z "${status_json}" ]; then
|
|
137
|
+
echo "unknown"
|
|
138
|
+
return 0
|
|
127
139
|
fi
|
|
128
|
-
|
|
140
|
+
|
|
141
|
+
echo "${status_json}" | jq -r --arg expected_head "${expected_head}" '
|
|
142
|
+
def in_list($values): . as $value | $values | index($value);
|
|
143
|
+
def check_run_failure:
|
|
144
|
+
.__typename == "CheckRun"
|
|
145
|
+
and ((.conclusion // "") | in_list(["FAILURE", "TIMED_OUT", "CANCELLED", "ACTION_REQUIRED", "STALE", "STARTUP_FAILURE"]));
|
|
146
|
+
def status_context_failure:
|
|
147
|
+
.__typename == "StatusContext"
|
|
148
|
+
and ((.state // "") | in_list(["FAILURE", "ERROR"]));
|
|
149
|
+
def check_run_pending:
|
|
150
|
+
.__typename == "CheckRun"
|
|
151
|
+
and ((.status // "") != "COMPLETED" or .conclusion == null);
|
|
152
|
+
def status_context_pending:
|
|
153
|
+
.__typename == "StatusContext"
|
|
154
|
+
and ((.state // "") | in_list(["PENDING", "EXPECTED"]));
|
|
155
|
+
def check_run_nonpassing:
|
|
156
|
+
.__typename == "CheckRun"
|
|
157
|
+
and ((.conclusion // "") | in_list(["SUCCESS", "NEUTRAL", "SKIPPED"]) | not);
|
|
158
|
+
def status_context_nonpassing:
|
|
159
|
+
.__typename == "StatusContext"
|
|
160
|
+
and (.state // "") != "SUCCESS";
|
|
161
|
+
if ($expected_head != "" and (.headRefOid // "") != $expected_head) then
|
|
162
|
+
"head_mismatch"
|
|
163
|
+
elif ((.statusCheckRollup // []) | length) == 0 then
|
|
164
|
+
"absent"
|
|
165
|
+
elif any((.statusCheckRollup // [])[]; check_run_failure or status_context_failure) then
|
|
166
|
+
"failed"
|
|
167
|
+
elif any((.statusCheckRollup // [])[]; check_run_pending or status_context_pending) then
|
|
168
|
+
"pending"
|
|
169
|
+
elif any((.statusCheckRollup // [])[]; check_run_nonpassing or status_context_nonpassing) then
|
|
170
|
+
"failed"
|
|
171
|
+
else
|
|
172
|
+
"passing"
|
|
173
|
+
end
|
|
174
|
+
' 2>/dev/null || echo "unknown"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
wait_for_ci_passing_on_head() {
|
|
178
|
+
local pr_number="${1}"
|
|
179
|
+
local expected_head="${2}"
|
|
180
|
+
local ci_waited=0
|
|
181
|
+
LAST_CI_STATUS="unknown"
|
|
182
|
+
|
|
183
|
+
while [ "${ci_waited}" -lt "${CI_MAX_WAIT}" ]; do
|
|
184
|
+
LAST_CI_STATUS=$(ci_status_for_head "${pr_number}" "${expected_head}")
|
|
185
|
+
if [ "${LAST_CI_STATUS}" = "passing" ]; then
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
log "INFO: PR #${pr_number}: Waiting for fresh CI on head ${expected_head} (${LAST_CI_STATUS}, ${ci_waited}s/${CI_MAX_WAIT}s)..."
|
|
189
|
+
sleep "${CI_POLL_INTERVAL}"
|
|
190
|
+
ci_waited=$((ci_waited + CI_POLL_INTERVAL))
|
|
191
|
+
done
|
|
192
|
+
|
|
193
|
+
LAST_CI_STATUS=$(ci_status_for_head "${pr_number}" "${expected_head}")
|
|
194
|
+
[ "${LAST_CI_STATUS}" = "passing" ]
|
|
129
195
|
}
|
|
130
196
|
|
|
131
197
|
# Rebase a PR against its base branch
|
|
@@ -140,6 +206,23 @@ rebase_pr() {
|
|
|
140
206
|
return $?
|
|
141
207
|
}
|
|
142
208
|
|
|
209
|
+
cleanup_watchdog() {
|
|
210
|
+
local pid="${1:-}"
|
|
211
|
+
local child_pids=""
|
|
212
|
+
|
|
213
|
+
if [ -z "${pid}" ]; then
|
|
214
|
+
return 0
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
child_pids=$(pgrep -P "${pid}" 2>/dev/null || true)
|
|
218
|
+
if [ -n "${child_pids}" ]; then
|
|
219
|
+
kill ${child_pids} 2>/dev/null || true
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
kill "${pid}" 2>/dev/null || true
|
|
223
|
+
wait "${pid}" 2>/dev/null || true
|
|
224
|
+
}
|
|
225
|
+
|
|
143
226
|
log() {
|
|
144
227
|
echo "[$(date '+%Y-%m-%dT%H:%M:%S')] $*" | tee -a "${LOG_FILE}"
|
|
145
228
|
}
|
|
@@ -158,7 +241,7 @@ cd "${PROJECT_DIR}"
|
|
|
158
241
|
|
|
159
242
|
log "========================================"
|
|
160
243
|
log "RUN-START: merger invoked project=${PROJECT_DIR} dry_run=${DRY_RUN}"
|
|
161
|
-
log "CONFIG: merge_method=${MERGE_METHOD} min_review_score=${MIN_REVIEW_SCORE} rebase_before_merge=${REBASE_BEFORE_MERGE} max_prs=${MAX_PRS_PER_RUN} max_runtime=${MAX_RUNTIME}s branch_patterns=${BRANCH_PATTERNS_RAW:-<all>}"
|
|
244
|
+
log "CONFIG: merge_method=${MERGE_METHOD} min_review_score=${MIN_REVIEW_SCORE} rebase_before_merge=${REBASE_BEFORE_MERGE} max_prs=${MAX_PRS_PER_RUN} max_runtime=${MAX_RUNTIME}s ready_label=${READY_TO_MERGE_LABEL} branch_patterns=${BRANCH_PATTERNS_RAW:-<all>}"
|
|
162
245
|
log "========================================"
|
|
163
246
|
|
|
164
247
|
if ! acquire_lock "${LOCK_FILE}"; then
|
|
@@ -187,7 +270,7 @@ fi
|
|
|
187
270
|
kill -TERM $$ 2>/dev/null || true
|
|
188
271
|
) &
|
|
189
272
|
WATCHDOG_PID=$!
|
|
190
|
-
append_exit_trap "
|
|
273
|
+
append_exit_trap "cleanup_watchdog ${WATCHDOG_PID}"
|
|
191
274
|
|
|
192
275
|
# Discover open PRs sorted by creation date (oldest first = FIFO)
|
|
193
276
|
log "INFO: Scanning open PRs..."
|
|
@@ -224,15 +307,27 @@ while IFS= read -r pr_json; do
|
|
|
224
307
|
continue
|
|
225
308
|
fi
|
|
226
309
|
|
|
310
|
+
if csv_has_label "${pr_labels:-}" "${READY_TO_MERGE_LABEL}"; then
|
|
311
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Skipping PR labeled ${READY_TO_MERGE_LABEL}"
|
|
312
|
+
continue
|
|
313
|
+
fi
|
|
314
|
+
|
|
227
315
|
# Check branch pattern filter
|
|
228
316
|
if ! matches_branch_patterns "${pr_branch}"; then
|
|
229
317
|
log "DEBUG: PR #${pr_number} (${pr_branch}): Branch pattern mismatch, skipping"
|
|
230
318
|
continue
|
|
231
319
|
fi
|
|
232
320
|
|
|
321
|
+
pr_head_oid=$(get_pr_head_oid "${pr_number}")
|
|
322
|
+
if [ -z "${pr_head_oid}" ]; then
|
|
323
|
+
log "INFO: PR #${pr_number} (${pr_branch}): Unable to determine PR head, skipping"
|
|
324
|
+
continue
|
|
325
|
+
fi
|
|
326
|
+
|
|
233
327
|
# Check CI status
|
|
234
|
-
|
|
235
|
-
|
|
328
|
+
ci_status=$(ci_status_for_head "${pr_number}" "${pr_head_oid}")
|
|
329
|
+
if [ "${ci_status}" != "passing" ]; then
|
|
330
|
+
log "INFO: PR #${pr_number} (${pr_branch}): CI not passing on head ${pr_head_oid} (${ci_status}), skipping"
|
|
236
331
|
continue
|
|
237
332
|
fi
|
|
238
333
|
|
|
@@ -249,6 +344,7 @@ while IFS= read -r pr_json; do
|
|
|
249
344
|
|
|
250
345
|
# Rebase before merge if configured
|
|
251
346
|
if [ "${REBASE_BEFORE_MERGE}" = "1" ]; then
|
|
347
|
+
pr_head_before_rebase="${pr_head_oid}"
|
|
252
348
|
if ! rebase_pr "${pr_number}"; then
|
|
253
349
|
log "WARN: PR #${pr_number}: Rebase failed, skipping"
|
|
254
350
|
FAILED_PRS=$((FAILED_PRS + 1))
|
|
@@ -256,20 +352,20 @@ while IFS= read -r pr_json; do
|
|
|
256
352
|
fi
|
|
257
353
|
log "INFO: PR #${pr_number}: Rebase successful"
|
|
258
354
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if !
|
|
272
|
-
log "INFO: PR #${pr_number}: CI not passing after rebase (waited ${
|
|
355
|
+
pr_head_after_rebase=$(get_pr_head_oid "${pr_number}")
|
|
356
|
+
if [ -z "${pr_head_after_rebase}" ]; then
|
|
357
|
+
log "INFO: PR #${pr_number}: Unable to determine PR head after rebase, skipping"
|
|
358
|
+
continue
|
|
359
|
+
fi
|
|
360
|
+
if [ "${pr_head_after_rebase}" != "${pr_head_before_rebase}" ]; then
|
|
361
|
+
log "INFO: PR #${pr_number}: Head changed after rebase ${pr_head_before_rebase} -> ${pr_head_after_rebase}; waiting for fresh CI"
|
|
362
|
+
else
|
|
363
|
+
log "INFO: PR #${pr_number}: Head unchanged after rebase (${pr_head_after_rebase}); confirming CI"
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
# Poll CI until all checks attached to the post-rebase head are complete and passing.
|
|
367
|
+
if ! wait_for_ci_passing_on_head "${pr_number}" "${pr_head_after_rebase}"; then
|
|
368
|
+
log "INFO: PR #${pr_number}: Fresh CI not passing on head ${pr_head_after_rebase} after rebase (${LAST_CI_STATUS}, waited ${CI_MAX_WAIT}s), skipping"
|
|
273
369
|
continue
|
|
274
370
|
fi
|
|
275
371
|
fi
|
|
@@ -284,12 +380,17 @@ while IFS= read -r pr_json; do
|
|
|
284
380
|
# Rebase remaining PRs after each successful merge
|
|
285
381
|
log "INFO: Rebasing remaining open PRs after merging #${pr_number}..."
|
|
286
382
|
REMAINING_JSON=$(gh pr list --state open \
|
|
287
|
-
--json number,headRefName \
|
|
383
|
+
--json number,headRefName,labels \
|
|
288
384
|
2>/dev/null || echo "[]")
|
|
289
385
|
while IFS= read -r remaining_pr; do
|
|
290
386
|
remaining_number=$(echo "${remaining_pr}" | jq -r '.number')
|
|
291
387
|
remaining_branch=$(echo "${remaining_pr}" | jq -r '.headRefName')
|
|
388
|
+
remaining_labels=$(echo "${remaining_pr}" | jq -r '[.labels[]?.name] | join(",")')
|
|
292
389
|
if [ "${remaining_number}" != "${pr_number}" ]; then
|
|
390
|
+
if csv_has_label "${remaining_labels:-}" "${READY_TO_MERGE_LABEL}"; then
|
|
391
|
+
log "INFO: Skipping post-merge rebase for PR #${remaining_number} (${remaining_branch}) because it is labeled ${READY_TO_MERGE_LABEL}"
|
|
392
|
+
continue
|
|
393
|
+
fi
|
|
293
394
|
log "INFO: Rebasing remaining PR #${remaining_number} (${remaining_branch})"
|
|
294
395
|
gh pr update-branch --rebase "${remaining_number}" 2>/dev/null || \
|
|
295
396
|
log "WARN: PR #${remaining_number}: Rebase failed (continuing)"
|
|
@@ -61,6 +61,8 @@ PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PRO
|
|
|
61
61
|
PRD_DIR="${NW_PRD_DIR:-docs/PRDs}"
|
|
62
62
|
PLAN_TASK="${NW_PLAN_TASK:-}"
|
|
63
63
|
|
|
64
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
65
|
+
|
|
64
66
|
rotate_log
|
|
65
67
|
log_separator
|
|
66
68
|
log "RUN-START: planner invoked project=${PROJECT_DIR} provider=${PROVIDER_CMD} dry_run=${NW_DRY_RUN:-0}"
|
|
@@ -63,6 +63,7 @@ PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PRO
|
|
|
63
63
|
# NOTE: Lock file path must match resolverLockPath() in src/utils/status-data.ts
|
|
64
64
|
LOCK_FILE="/tmp/night-watch-pr-resolver-${PROJECT_RUNTIME_KEY}.lock"
|
|
65
65
|
SCRIPT_TYPE="pr-resolver"
|
|
66
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
66
67
|
|
|
67
68
|
emit_result() {
|
|
68
69
|
local status="${1:?status required}"
|
|
@@ -359,6 +360,11 @@ while IFS= read -r pr_line; do
|
|
|
359
360
|
continue
|
|
360
361
|
fi
|
|
361
362
|
|
|
363
|
+
if csv_has_label "${labels:-}" "${READY_LABEL}"; then
|
|
364
|
+
log "INFO: Skipping PR #${pr_number} (${pr_branch}) because it is labeled ${READY_LABEL}"
|
|
365
|
+
continue
|
|
366
|
+
fi
|
|
367
|
+
|
|
362
368
|
# Apply branch pattern filter
|
|
363
369
|
if ! matches_branch_patterns "${pr_branch}"; then
|
|
364
370
|
log "DEBUG: Skipping PR #${pr_number} — branch '${pr_branch}' does not match patterns" "patterns=${BRANCH_PATTERNS_RAW}"
|
|
@@ -63,11 +63,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
63
63
|
# shellcheck source=night-watch-helpers.sh
|
|
64
64
|
source "${SCRIPT_DIR}/night-watch-helpers.sh"
|
|
65
65
|
|
|
66
|
-
# Ensure provider CLI is on PATH (nvm, fnm, volta, common bin dirs)
|
|
67
|
-
if ! ensure_provider_on_path "${PROVIDER_CMD}"; then
|
|
68
|
-
echo "ERROR: Provider '${PROVIDER_CMD}' not found in PATH or common installation locations" >&2
|
|
69
|
-
exit 127
|
|
70
|
-
fi
|
|
71
66
|
PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
72
67
|
PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PROVIDER_LABEL}")
|
|
73
68
|
GLOBAL_LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}.lock"
|
|
@@ -79,6 +74,7 @@ else
|
|
|
79
74
|
fi
|
|
80
75
|
|
|
81
76
|
SCRIPT_TYPE="reviewer"
|
|
77
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
82
78
|
READY_FOR_REVIEW_LABEL="${NW_READY_FOR_REVIEW_LABEL:-ready-for-review}"
|
|
83
79
|
READY_FOR_REVIEW_MARKER_NAME="night-watch-ready-for-review"
|
|
84
80
|
READY_TO_MERGE_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
|
|
@@ -93,6 +89,13 @@ emit_result() {
|
|
|
93
89
|
fi
|
|
94
90
|
}
|
|
95
91
|
|
|
92
|
+
require_provider_on_path() {
|
|
93
|
+
if ! ensure_provider_on_path "${PROVIDER_CMD}"; then
|
|
94
|
+
echo "ERROR: Provider '${PROVIDER_CMD}' not found in PATH or common installation locations" >&2
|
|
95
|
+
exit 127
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
|
|
96
99
|
extract_review_score_from_text() {
|
|
97
100
|
local review_text="${1:-}"
|
|
98
101
|
printf '%s' "${review_text}" \
|
|
@@ -853,6 +856,8 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
|
|
|
853
856
|
exit 0
|
|
854
857
|
fi
|
|
855
858
|
|
|
859
|
+
require_provider_on_path
|
|
860
|
+
|
|
856
861
|
log "PARALLEL: Launching ${#PR_NUMBER_ARRAY[@]} reviewer worker(s)"
|
|
857
862
|
|
|
858
863
|
declare -a WORKER_PIDS=()
|
|
@@ -1022,6 +1027,8 @@ if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
|
1022
1027
|
exit 0
|
|
1023
1028
|
fi
|
|
1024
1029
|
|
|
1030
|
+
require_provider_on_path
|
|
1031
|
+
|
|
1025
1032
|
if ! prepare_detached_worktree "${PROJECT_DIR}" "${REVIEW_WORKTREE_DIR}" "${DEFAULT_BRANCH}" "${LOG_FILE}"; then
|
|
1026
1033
|
log "FAIL: Unable to create isolated reviewer worktree ${REVIEW_WORKTREE_DIR}"
|
|
1027
1034
|
exit 1
|
|
@@ -1226,6 +1233,10 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
1226
1233
|
LOG_LINE_BEFORE=$(wc -l < "${LOG_FILE}" 2>/dev/null || echo 0)
|
|
1227
1234
|
REVIEWER_ATTEMPT_START=$(date +%s)
|
|
1228
1235
|
REVIEWER_PROMPT="${REVIEWER_PROMPT_BASE}${TARGET_SCOPE_PROMPT}${PRD_CONTEXT_PROMPT}"
|
|
1236
|
+
if [ -n "${NW_PROJECT_FEEDBACK_PROMPT:-}" ]; then
|
|
1237
|
+
REVIEWER_PROMPT="${REVIEWER_PROMPT}"$'\n\n'"${NW_PROJECT_FEEDBACK_PROMPT}"
|
|
1238
|
+
log "INFO: Added project feedback prompt context"
|
|
1239
|
+
fi
|
|
1229
1240
|
|
|
1230
1241
|
# Build provider command array using generic helper
|
|
1231
1242
|
mapfile -d '' -t PROVIDER_CMD_PARTS < <(build_provider_cmd "${REVIEW_WORKTREE_DIR}" "${REVIEWER_PROMPT}")
|
|
@@ -26,6 +26,7 @@ PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
|
|
|
26
26
|
BRANCH_PATTERNS_RAW="${NW_BRANCH_PATTERNS:-feat/,night-watch/}"
|
|
27
27
|
SKIP_LABEL="${NW_QA_SKIP_LABEL:-skip-qa}"
|
|
28
28
|
VALIDATED_LABEL="${NW_QA_VALIDATED_LABEL:-e2e-validated}"
|
|
29
|
+
READY_TO_MERGE_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
|
|
29
30
|
QA_ARTIFACTS="${NW_QA_ARTIFACTS:-both}"
|
|
30
31
|
QA_AUTO_INSTALL_PLAYWRIGHT="${NW_QA_AUTO_INSTALL_PLAYWRIGHT:-1}"
|
|
31
32
|
SCRIPT_START_TIME=$(date +%s)
|
|
@@ -45,6 +46,7 @@ PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
|
45
46
|
# NOTE: Lock file path must match qaLockPath() in src/utils/status-data.ts
|
|
46
47
|
LOCK_FILE="/tmp/night-watch-qa-${PROJECT_RUNTIME_KEY}.lock"
|
|
47
48
|
SCRIPT_TYPE="qa"
|
|
49
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
48
50
|
|
|
49
51
|
emit_result() {
|
|
50
52
|
local status="${1:?status required}"
|
|
@@ -366,7 +368,7 @@ fi
|
|
|
366
368
|
rotate_log
|
|
367
369
|
log_separator
|
|
368
370
|
log "RUN-START: qa invoked project=${PROJECT_DIR} provider=${PROVIDER_CMD} dry_run=${NW_DRY_RUN:-0}"
|
|
369
|
-
log "CONFIG: max_runtime=${MAX_RUNTIME}s artifacts=${QA_ARTIFACTS} skip_label=${SKIP_LABEL} branch_patterns=${BRANCH_PATTERNS_RAW}"
|
|
371
|
+
log "CONFIG: max_runtime=${MAX_RUNTIME}s artifacts=${QA_ARTIFACTS} skip_label=${SKIP_LABEL} ready_label=${READY_TO_MERGE_LABEL} branch_patterns=${BRANCH_PATTERNS_RAW}"
|
|
370
372
|
|
|
371
373
|
if ! acquire_lock "${LOCK_FILE}"; then
|
|
372
374
|
emit_result "skip_locked"
|
|
@@ -431,6 +433,11 @@ while IFS=$'\t' read -r pr_number pr_branch pr_title pr_labels; do
|
|
|
431
433
|
continue
|
|
432
434
|
fi
|
|
433
435
|
|
|
436
|
+
if csv_has_label "${pr_labels:-}" "${READY_TO_MERGE_LABEL}"; then
|
|
437
|
+
log "SKIP-QA: PR #${pr_number} (${pr_branch}) is labeled ${READY_TO_MERGE_LABEL}"
|
|
438
|
+
continue
|
|
439
|
+
fi
|
|
440
|
+
|
|
434
441
|
# Skip PRs with the skip label
|
|
435
442
|
if echo "${pr_labels}" | grep -q "${SKIP_LABEL}"; then
|
|
436
443
|
log "SKIP-QA: PR #${pr_number} (${pr_branch}) has '${SKIP_LABEL}' label"
|
|
@@ -35,6 +35,7 @@ source "${SCRIPT_DIR}/night-watch-helpers.sh"
|
|
|
35
35
|
ensure_provider_on_path "${PROVIDER_CMD}" || true
|
|
36
36
|
PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
|
|
37
37
|
LOCK_FILE="/tmp/night-watch-slicer-${PROJECT_RUNTIME_KEY}.lock"
|
|
38
|
+
SCRIPT_TYPE="slicer"
|
|
38
39
|
PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PROVIDER_LABEL}")
|
|
39
40
|
|
|
40
41
|
emit_result() {
|
|
@@ -53,6 +54,8 @@ if ! validate_provider "${PROVIDER_CMD}"; then
|
|
|
53
54
|
exit 1
|
|
54
55
|
fi
|
|
55
56
|
|
|
57
|
+
skip_if_job_paused "${SCRIPT_TYPE}" "${PROJECT_DIR}"
|
|
58
|
+
|
|
56
59
|
rotate_log
|
|
57
60
|
log_separator
|
|
58
61
|
log "RUN-START: slicer invoked project=${PROJECT_DIR} provider=${PROVIDER_CMD} dry_run=${NW_DRY_RUN:-0}"
|
|
@@ -55,6 +55,27 @@
|
|
|
55
55
|
"audit": 10
|
|
56
56
|
}
|
|
57
57
|
},
|
|
58
|
+
"webhookTriggers": {
|
|
59
|
+
"enabled": false,
|
|
60
|
+
"secretEnv": "NIGHT_WATCH_WEBHOOK_SECRET",
|
|
61
|
+
"allowedJobIds": [
|
|
62
|
+
"executor",
|
|
63
|
+
"reviewer",
|
|
64
|
+
"pr-resolver",
|
|
65
|
+
"slicer",
|
|
66
|
+
"qa",
|
|
67
|
+
"audit",
|
|
68
|
+
"analytics",
|
|
69
|
+
"merger"
|
|
70
|
+
],
|
|
71
|
+
"requireTimestamp": false,
|
|
72
|
+
"maxSkewSeconds": 300,
|
|
73
|
+
"github": {
|
|
74
|
+
"enabled": false,
|
|
75
|
+
"events": [],
|
|
76
|
+
"rules": []
|
|
77
|
+
}
|
|
78
|
+
},
|
|
58
79
|
"jobProviders": {},
|
|
59
80
|
"autoMerge": false,
|
|
60
81
|
"autoMergeMethod": "squash",
|
package/dist/templates/slicer.md
CHANGED
|
@@ -32,6 +32,8 @@ The PRD directory is: `{{PRD_DIR}}`
|
|
|
32
32
|
|
|
33
33
|
3. **Write a Complete PRD** — Follow the exact template structure below. Every section must be filled with concrete information. No placeholder text.
|
|
34
34
|
|
|
35
|
+
The PRD MUST include these sections: Context (with Problem, Files Analyzed, Current Behavior, Integration Points), Solution (with Approach, Key Decisions), Execution Phases (with Files, Implementation steps, Tests), and Acceptance Criteria.
|
|
36
|
+
|
|
35
37
|
4. **Write the PRD File** — Use the Write tool to create the PRD file at `{{OUTPUT_FILE_PATH}}`.
|
|
36
38
|
|
|
37
39
|
---
|
|
@@ -146,6 +148,7 @@ sequenceDiagram
|
|
|
146
148
|
## 4. Execution Phases
|
|
147
149
|
|
|
148
150
|
Critical rules:
|
|
151
|
+
|
|
149
152
|
1. Each phase = ONE user-testable vertical slice
|
|
150
153
|
2. Max 5 files per phase (split if larger)
|
|
151
154
|
3. Each phase MUST include concrete tests
|