@jonit-dev/night-watch-cli 1.8.8-beta.0 → 1.8.8-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.
Files changed (38) hide show
  1. package/dist/cli.js +640 -24
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/init.js +38 -6
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/install.d.ts +4 -0
  7. package/dist/commands/install.d.ts.map +1 -1
  8. package/dist/commands/install.js +25 -0
  9. package/dist/commands/install.js.map +1 -1
  10. package/dist/commands/qa.d.ts.map +1 -1
  11. package/dist/commands/qa.js +5 -0
  12. package/dist/commands/qa.js.map +1 -1
  13. package/dist/commands/queue.d.ts.map +1 -1
  14. package/dist/commands/queue.js +27 -4
  15. package/dist/commands/queue.js.map +1 -1
  16. package/dist/commands/resolve.d.ts +26 -0
  17. package/dist/commands/resolve.d.ts.map +1 -0
  18. package/dist/commands/resolve.js +186 -0
  19. package/dist/commands/resolve.js.map +1 -0
  20. package/dist/commands/review.d.ts +5 -0
  21. package/dist/commands/review.d.ts.map +1 -1
  22. package/dist/commands/review.js +18 -5
  23. package/dist/commands/review.js.map +1 -1
  24. package/dist/commands/summary.d.ts +14 -0
  25. package/dist/commands/summary.d.ts.map +1 -0
  26. package/dist/commands/summary.js +193 -0
  27. package/dist/commands/summary.js.map +1 -0
  28. package/dist/commands/uninstall.d.ts.map +1 -1
  29. package/dist/commands/uninstall.js +14 -2
  30. package/dist/commands/uninstall.js.map +1 -1
  31. package/dist/scripts/night-watch-helpers.sh +10 -1
  32. package/dist/scripts/night-watch-pr-resolver-cron.sh +402 -0
  33. package/dist/scripts/night-watch-pr-reviewer-cron.sh +22 -5
  34. package/dist/scripts/night-watch-qa-cron.sh +107 -38
  35. package/dist/scripts/test-helpers.bats +45 -0
  36. package/dist/templates/night-watch-pr-reviewer.md +2 -1
  37. package/dist/templates/pr-reviewer.md +2 -1
  38. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../src/commands/uninstall.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../src/commands/uninstall.ts"],"names":[],"mappings":"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"}
@@ -589,6 +589,11 @@ cleanup_worktrees() {
589
589
  local project_name
590
590
  project_name=$(basename "${project_dir}")
591
591
 
592
+ # Clear stale worktree registrations first. This fixes cases where a
593
+ # worktree directory was deleted out-of-band (for example by an agent
594
+ # runtime crash), leaving Git thinking the branch is still in use.
595
+ git -C "${project_dir}" worktree prune >/dev/null 2>&1 || true
596
+
592
597
  local match_token="${project_name}-nw"
593
598
  if [ -n "${scope}" ]; then
594
599
  match_token="${scope}"
@@ -602,6 +607,10 @@ cleanup_worktrees() {
602
607
  log "CLEANUP: Removing leftover worktree ${wt}"
603
608
  git -C "${project_dir}" worktree remove --force "${wt}" 2>/dev/null || true
604
609
  done || true
610
+
611
+ # Prune again after removals so Git drops any admin entries left behind by
612
+ # force-removal or previously broken worktrees outside Night Watch naming.
613
+ git -C "${project_dir}" worktree prune >/dev/null 2>&1 || true
605
614
  }
606
615
 
607
616
  # Pick the best available ref for creating a new detached worktree.
@@ -1149,7 +1158,7 @@ dispatch_next_queued_job() {
1149
1158
  log "QUEUE: Checking for pending jobs to dispatch"
1150
1159
 
1151
1160
  # Call CLI to dispatch next job (this handles priority, expiration, and spawning)
1152
- "${cli_bin}" queue dispatch --log "${LOG_FILE:-/dev/null}" 2>/dev/null || true
1161
+ "${cli_bin}" queue dispatch --project-dir "${PROJECT_DIR:-$(pwd)}" --log "${LOG_FILE:-/dev/null}" 2>/dev/null || true
1153
1162
  }
1154
1163
 
1155
1164
  complete_queued_job() {
@@ -0,0 +1,402 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Night Watch PR Resolver Cron Runner (project-agnostic)
5
+ # Usage: night-watch-pr-resolver-cron.sh /path/to/project
6
+ #
7
+ # NOTE: This script expects environment variables to be set by the caller.
8
+ # The Node.js CLI will inject config values via environment variables.
9
+ # Required env vars (with defaults shown):
10
+ # NW_PR_RESOLVER_MAX_RUNTIME=3600 - Maximum runtime in seconds (1 hour)
11
+ # NW_PROVIDER_CMD=claude - AI provider CLI to use (claude, codex, etc.)
12
+ # NW_DRY_RUN=0 - Set to 1 for dry-run mode (prints diagnostics only)
13
+ # NW_PR_RESOLVER_MAX_PRS_PER_RUN=0 - Max PRs to process per run (0 = unlimited)
14
+ # NW_PR_RESOLVER_PER_PR_TIMEOUT=600 - Per-PR AI timeout in seconds
15
+ # NW_PR_RESOLVER_AI_CONFLICT_RESOLUTION=1 - Set to 1 to use AI for conflict resolution
16
+ # NW_PR_RESOLVER_AI_REVIEW_RESOLUTION=0 - Set to 1 to also address review comments
17
+ # NW_PR_RESOLVER_READY_LABEL=ready-to-merge - Label to add when PR is conflict-free
18
+ # NW_PR_RESOLVER_BRANCH_PATTERNS= - Comma-separated branch prefixes to filter (empty = all)
19
+
20
+ PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
21
+ PROJECT_NAME=$(basename "${PROJECT_DIR}")
22
+ LOG_DIR="${PROJECT_DIR}/logs"
23
+ LOG_FILE="${LOG_DIR}/pr-resolver.log"
24
+ MAX_RUNTIME="${NW_PR_RESOLVER_MAX_RUNTIME:-3600}" # 1 hour
25
+ MAX_LOG_SIZE="524288" # 512 KB
26
+ PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
27
+ PROVIDER_LABEL="${NW_PROVIDER_LABEL:-}"
28
+ MAX_PRS_PER_RUN="${NW_PR_RESOLVER_MAX_PRS_PER_RUN:-0}"
29
+ PER_PR_TIMEOUT="${NW_PR_RESOLVER_PER_PR_TIMEOUT:-600}"
30
+ AI_CONFLICT_RESOLUTION="${NW_PR_RESOLVER_AI_CONFLICT_RESOLUTION:-1}"
31
+ AI_REVIEW_RESOLUTION="${NW_PR_RESOLVER_AI_REVIEW_RESOLUTION:-0}"
32
+ READY_LABEL="${NW_PR_RESOLVER_READY_LABEL:-ready-to-merge}"
33
+ BRANCH_PATTERNS_RAW="${NW_PR_RESOLVER_BRANCH_PATTERNS:-}"
34
+ SCRIPT_START_TIME=$(date +%s)
35
+
36
+ # Normalize numeric settings to safe ranges
37
+ if ! [[ "${MAX_PRS_PER_RUN}" =~ ^[0-9]+$ ]]; then
38
+ MAX_PRS_PER_RUN="0"
39
+ fi
40
+ if ! [[ "${PER_PR_TIMEOUT}" =~ ^[0-9]+$ ]]; then
41
+ PER_PR_TIMEOUT="600"
42
+ fi
43
+ if [ "${MAX_PRS_PER_RUN}" -gt 100 ]; then
44
+ MAX_PRS_PER_RUN="100"
45
+ fi
46
+ if [ "${PER_PR_TIMEOUT}" -gt 3600 ]; then
47
+ PER_PR_TIMEOUT="3600"
48
+ fi
49
+
50
+ mkdir -p "${LOG_DIR}"
51
+
52
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
53
+ # shellcheck source=night-watch-helpers.sh
54
+ source "${SCRIPT_DIR}/night-watch-helpers.sh"
55
+
56
+ # Ensure provider CLI is on PATH (nvm, fnm, volta, common bin dirs)
57
+ if ! ensure_provider_on_path "${PROVIDER_CMD}"; then
58
+ echo "ERROR: Provider '${PROVIDER_CMD}' not found in PATH or common installation locations" >&2
59
+ exit 127
60
+ fi
61
+ PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
62
+ PROVIDER_MODEL_DISPLAY=$(resolve_provider_model_display "${PROVIDER_CMD}" "${PROVIDER_LABEL}")
63
+ # NOTE: Lock file path must match resolverLockPath() in src/utils/status-data.ts
64
+ LOCK_FILE="/tmp/night-watch-pr-resolver-${PROJECT_RUNTIME_KEY}.lock"
65
+ SCRIPT_TYPE="pr-resolver"
66
+
67
+ emit_result() {
68
+ local status="${1:?status required}"
69
+ local details="${2:-}"
70
+ if [ -n "${details}" ]; then
71
+ echo "NIGHT_WATCH_RESULT:${status}|${details}"
72
+ else
73
+ echo "NIGHT_WATCH_RESULT:${status}"
74
+ fi
75
+ }
76
+
77
+ # ── Global Job Queue Gate ────────────────────────────────────────────────────
78
+ # Atomically claim a DB slot or enqueue for later dispatch — no flock needed.
79
+ if [ "${NW_QUEUE_ENABLED:-0}" = "1" ]; then
80
+ if [ "${NW_QUEUE_DISPATCHED:-0}" = "1" ]; then
81
+ arm_global_queue_cleanup
82
+ else
83
+ claim_or_enqueue "${SCRIPT_TYPE}" "${PROJECT_DIR}"
84
+ fi
85
+ fi
86
+ # ──────────────────────────────────────────────────────────────────────────────
87
+
88
+ # PR discovery: returns JSON array of open PRs with required fields
89
+ discover_open_prs() {
90
+ gh pr list --state open \
91
+ --json number,title,headRefName,mergeable,isDraft,labels \
92
+ 2>/dev/null || echo "[]"
93
+ }
94
+
95
+ # Check if a branch matches any configured branch prefix patterns.
96
+ # Returns 0 (match/pass) or 1 (no match, skip PR).
97
+ matches_branch_patterns() {
98
+ local branch="${1}"
99
+ if [ -z "${BRANCH_PATTERNS_RAW}" ]; then
100
+ return 0 # No filter configured = match all
101
+ fi
102
+ IFS=',' read -ra patterns <<< "${BRANCH_PATTERNS_RAW}"
103
+ for pattern in "${patterns[@]}"; do
104
+ pattern="${pattern# }" # trim leading space
105
+ if [[ "${branch}" == ${pattern}* ]]; then
106
+ return 0
107
+ fi
108
+ done
109
+ return 1
110
+ }
111
+
112
+ # Process a single PR: resolve conflicts and/or review comments, then label.
113
+ # Echoes "ready" if the PR ends up conflict-free, "conflicted" otherwise.
114
+ # Returns 0 on success, 1 on unrecoverable failure.
115
+ process_pr() {
116
+ local pr_number="${1:?pr_number required}"
117
+ local pr_branch="${2:?pr_branch required}"
118
+ local pr_title="${3:-}"
119
+ local worktree_dir="/tmp/nw-resolver-pr${pr_number}-$$"
120
+
121
+ log "INFO: Processing PR #${pr_number}: ${pr_title}" "branch=${pr_branch}"
122
+
123
+ # Inner cleanup for worktree created during this PR's processing
124
+ cleanup_pr_worktree() {
125
+ if git -C "${PROJECT_DIR}" worktree list --porcelain 2>/dev/null \
126
+ | grep -qF "worktree ${worktree_dir}"; then
127
+ git -C "${PROJECT_DIR}" worktree remove --force "${worktree_dir}" 2>/dev/null || true
128
+ fi
129
+ rm -rf "${worktree_dir}" 2>/dev/null || true
130
+ }
131
+
132
+ # ── Determine default branch ─────────────────────────────────────────────
133
+ local default_branch
134
+ default_branch="${NW_DEFAULT_BRANCH:-}"
135
+ if [ -z "${default_branch}" ]; then
136
+ default_branch=$(detect_default_branch "${PROJECT_DIR}")
137
+ fi
138
+
139
+ # ── Check current mergeable status ──────────────────────────────────────
140
+ local mergeable
141
+ mergeable=$(gh pr view "${pr_number}" --json mergeable --jq '.mergeable' 2>/dev/null || echo "UNKNOWN")
142
+
143
+ if [ "${mergeable}" = "CONFLICTING" ]; then
144
+ log "INFO: PR #${pr_number} has conflicts, attempting resolution" "branch=${pr_branch}"
145
+
146
+ # Fetch the PR branch so we have an up-to-date ref
147
+ git -C "${PROJECT_DIR}" fetch --quiet origin "${pr_branch}" 2>/dev/null || true
148
+
149
+ # Create an isolated worktree on the PR branch
150
+ if ! prepare_branch_worktree "${PROJECT_DIR}" "${worktree_dir}" "${pr_branch}" "${default_branch}" "${LOG_FILE}"; then
151
+ log "WARN: Failed to create worktree for PR #${pr_number}" "branch=${pr_branch}"
152
+ cleanup_pr_worktree
153
+ return 1
154
+ fi
155
+
156
+ local rebase_success=0
157
+
158
+ # Attempt a clean rebase first (no AI needed if it auto-resolves)
159
+ if git -C "${worktree_dir}" rebase "origin/${default_branch}" --quiet 2>/dev/null; then
160
+ rebase_success=1
161
+ log "INFO: PR #${pr_number} rebased cleanly (no conflicts)" "branch=${pr_branch}"
162
+ else
163
+ # Clean up the failed rebase state
164
+ git -C "${worktree_dir}" rebase --abort 2>/dev/null || true
165
+
166
+ if [ "${AI_CONFLICT_RESOLUTION}" = "1" ]; then
167
+ log "INFO: Invoking AI to resolve conflicts for PR #${pr_number}" "branch=${pr_branch}"
168
+
169
+ local ai_prompt
170
+ ai_prompt="You are working in a git repository at ${worktree_dir}. \
171
+ Branch '${pr_branch}' has merge conflicts with '${default_branch}'. \
172
+ Please resolve the merge conflicts by: \
173
+ 1) Running: git rebase origin/${default_branch} \
174
+ 2) Resolving any conflict markers in the affected files \
175
+ 3) Staging resolved files with: git add <files> \
176
+ 4) Continuing the rebase with: git rebase --continue \
177
+ 5) Finally pushing with: git push --force-with-lease origin ${pr_branch} \
178
+ Work exclusively in the directory: ${worktree_dir}"
179
+
180
+ local -a cmd_parts
181
+ mapfile -d '' -t cmd_parts < <(build_provider_cmd "${worktree_dir}" "${ai_prompt}")
182
+
183
+ if timeout "${PER_PR_TIMEOUT}" "${cmd_parts[@]}" >> "${LOG_FILE}" 2>&1; then
184
+ rebase_success=1
185
+ log "INFO: AI resolved conflicts for PR #${pr_number}" "branch=${pr_branch}"
186
+ else
187
+ log "WARN: AI failed to resolve conflicts for PR #${pr_number}" "branch=${pr_branch}"
188
+ cleanup_pr_worktree
189
+ return 1
190
+ fi
191
+ else
192
+ log "WARN: Skipping PR #${pr_number} — conflicts exist and AI resolution is disabled" "branch=${pr_branch}"
193
+ cleanup_pr_worktree
194
+ return 1
195
+ fi
196
+ fi
197
+
198
+ if [ "${rebase_success}" = "1" ]; then
199
+ # Safety: never force-push to the default branch
200
+ if [ "${pr_branch}" = "${default_branch}" ]; then
201
+ log "WARN: Refusing to force-push to default branch ${default_branch} for PR #${pr_number}"
202
+ cleanup_pr_worktree
203
+ return 1
204
+ fi
205
+ # Push the rebased branch (AI may have already pushed; --force-with-lease is idempotent)
206
+ git -C "${worktree_dir}" push --force-with-lease origin "${pr_branch}" >> "${LOG_FILE}" 2>&1 || {
207
+ log "WARN: Push after rebase failed for PR #${pr_number}" "branch=${pr_branch}"
208
+ }
209
+ fi
210
+ fi
211
+
212
+ # ── Secondary: AI review comment resolution (opt-in) ────────────────────
213
+ if [ "${AI_REVIEW_RESOLUTION}" = "1" ]; then
214
+ local unresolved_count
215
+ unresolved_count=$(gh api "repos/{owner}/{repo}/pulls/${pr_number}/reviews" \
216
+ --jq '[.[] | select(.state == "CHANGES_REQUESTED")] | length' 2>/dev/null || echo "0")
217
+
218
+ if [ "${unresolved_count}" -gt "0" ]; then
219
+ log "INFO: PR #${pr_number} has ${unresolved_count} change request(s), invoking AI" "branch=${pr_branch}"
220
+
221
+ local review_workdir="${worktree_dir}"
222
+ if [ ! -d "${review_workdir}" ]; then
223
+ review_workdir="${PROJECT_DIR}"
224
+ fi
225
+
226
+ local review_prompt
227
+ review_prompt="You are working in the git repository at ${review_workdir}. \
228
+ PR #${pr_number} on branch '${pr_branch}' has unresolved review comments requesting changes. \
229
+ Please: \
230
+ 1) Run 'gh pr view ${pr_number} --comments' to read the review comments \
231
+ 2) Implement the requested changes \
232
+ 3) Commit the changes with a descriptive message \
233
+ 4) Push with: git push origin ${pr_branch} \
234
+ Work in the directory: ${review_workdir}"
235
+
236
+ local -a review_cmd_parts
237
+ mapfile -d '' -t review_cmd_parts < <(build_provider_cmd "${review_workdir}" "${review_prompt}")
238
+
239
+ if timeout "${PER_PR_TIMEOUT}" "${review_cmd_parts[@]}" >> "${LOG_FILE}" 2>&1; then
240
+ log "INFO: AI addressed review comments for PR #${pr_number}" "branch=${pr_branch}"
241
+ else
242
+ log "WARN: AI failed to address review comments for PR #${pr_number}" "branch=${pr_branch}"
243
+ fi
244
+ fi
245
+ fi
246
+
247
+ # ── Re-check mergeable status after processing ──────────────────────────
248
+ # Brief wait for GitHub to propagate the push and recompute mergeability
249
+ sleep 3
250
+ local final_mergeable
251
+ final_mergeable=$(gh pr view "${pr_number}" --json mergeable --jq '.mergeable' 2>/dev/null || echo "UNKNOWN")
252
+
253
+ # ── Labeling ─────────────────────────────────────────────────────────────
254
+ local result
255
+ if [ "${final_mergeable}" != "CONFLICTING" ]; then
256
+ # Ensure the ready label exists in the repo (idempotent)
257
+ gh label create "${READY_LABEL}" \
258
+ --color "0075ca" \
259
+ --description "PR is conflict-free and ready to merge" \
260
+ 2>/dev/null || true
261
+ gh pr edit "${pr_number}" --add-label "${READY_LABEL}" 2>/dev/null || true
262
+ log "INFO: PR #${pr_number} marked as '${READY_LABEL}'" "branch=${pr_branch}"
263
+ result="ready"
264
+ else
265
+ gh pr edit "${pr_number}" --remove-label "${READY_LABEL}" 2>/dev/null || true
266
+ log "WARN: PR #${pr_number} still has conflicts after processing" "branch=${pr_branch}"
267
+ result="conflicted"
268
+ fi
269
+
270
+ cleanup_pr_worktree
271
+ echo "${result}"
272
+ }
273
+
274
+ # ── Validate provider ────────────────────────────────────────────────────────
275
+ if ! validate_provider "${PROVIDER_CMD}"; then
276
+ echo "ERROR: Unknown provider: ${PROVIDER_CMD}" >&2
277
+ exit 1
278
+ fi
279
+
280
+ rotate_log
281
+ log_separator
282
+ log "RUN-START: pr-resolver invoked project=${PROJECT_DIR} provider=${PROVIDER_CMD} dry_run=${NW_DRY_RUN:-0}"
283
+ log "CONFIG: max_runtime=${MAX_RUNTIME}s max_prs=${MAX_PRS_PER_RUN} per_pr_timeout=${PER_PR_TIMEOUT}s ai_conflict=${AI_CONFLICT_RESOLUTION} ai_review=${AI_REVIEW_RESOLUTION} ready_label=${READY_LABEL} branch_patterns=${BRANCH_PATTERNS_RAW:-<all>}"
284
+
285
+ if ! acquire_lock "${LOCK_FILE}"; then
286
+ emit_result "skip_locked"
287
+ exit 0
288
+ fi
289
+
290
+ cd "${PROJECT_DIR}"
291
+
292
+ # ── Dry-run mode ────────────────────────────────────────────────────────────
293
+ if [ "${NW_DRY_RUN:-0}" = "1" ]; then
294
+ echo "=== Dry Run: PR Resolver ==="
295
+ echo "Provider (model): ${PROVIDER_MODEL_DISPLAY}"
296
+ echo "Branch Patterns: ${BRANCH_PATTERNS_RAW:-<all>}"
297
+ echo "Max PRs Per Run: ${MAX_PRS_PER_RUN}"
298
+ echo "Per-PR Timeout: ${PER_PR_TIMEOUT}s"
299
+ echo "AI Conflict Resolution: ${AI_CONFLICT_RESOLUTION}"
300
+ echo "AI Review Resolution: ${AI_REVIEW_RESOLUTION}"
301
+ echo "Ready Label: ${READY_LABEL}"
302
+ echo "Max Runtime: ${MAX_RUNTIME}s"
303
+ log "INFO: Dry run mode — exiting without processing"
304
+ emit_result "skip_dry_run"
305
+ exit 0
306
+ fi
307
+
308
+ send_telegram_status_message "Night Watch PR Resolver: started" "Project: ${PROJECT_NAME}
309
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
310
+ Branch patterns: ${BRANCH_PATTERNS_RAW:-all}
311
+ Action: scanning open PRs for merge conflicts."
312
+
313
+ # ── Discover open PRs ────────────────────────────────────────────────────────
314
+ pr_json=$(discover_open_prs)
315
+
316
+ if [ -z "${pr_json}" ] || [ "${pr_json}" = "[]" ]; then
317
+ log "SKIP: No open PRs found"
318
+ send_telegram_status_message "Night Watch PR Resolver: nothing to do" "Project: ${PROJECT_NAME}
319
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
320
+ Result: no open PRs found."
321
+ emit_result "skip_no_open_prs"
322
+ exit 0
323
+ fi
324
+
325
+ pr_count=$(printf '%s' "${pr_json}" | jq 'length' 2>/dev/null || echo "0")
326
+ log "INFO: Found ${pr_count} open PR(s) to evaluate"
327
+
328
+ # ── Main processing loop ─────────────────────────────────────────────────────
329
+ processed=0
330
+ conflicts_resolved=0
331
+ reviews_addressed=0
332
+ prs_ready=0
333
+ prs_failed=0
334
+
335
+ while IFS= read -r pr_line; do
336
+ [ -z "${pr_line}" ] && continue
337
+
338
+ pr_number=$(printf '%s' "${pr_line}" | jq -r '.number')
339
+ pr_branch=$(printf '%s' "${pr_line}" | jq -r '.headRefName')
340
+ pr_title=$(printf '%s' "${pr_line}" | jq -r '.title')
341
+ is_draft=$(printf '%s' "${pr_line}" | jq -r '.isDraft')
342
+ labels=$(printf '%s' "${pr_line}" | jq -r '[.labels[].name] | join(",")')
343
+
344
+ [ -z "${pr_number}" ] || [ -z "${pr_branch}" ] && continue
345
+
346
+ # Skip draft PRs
347
+ if [ "${is_draft}" = "true" ]; then
348
+ log "INFO: Skipping draft PR #${pr_number}" "branch=${pr_branch}"
349
+ continue
350
+ fi
351
+
352
+ # Skip PRs labelled skip-resolver
353
+ if [[ "${labels}" == *"skip-resolver"* ]]; then
354
+ log "INFO: Skipping PR #${pr_number} (skip-resolver label)" "branch=${pr_branch}"
355
+ continue
356
+ fi
357
+
358
+ # Apply branch pattern filter
359
+ if ! matches_branch_patterns "${pr_branch}"; then
360
+ log "DEBUG: Skipping PR #${pr_number} — branch '${pr_branch}' does not match patterns" "patterns=${BRANCH_PATTERNS_RAW}"
361
+ continue
362
+ fi
363
+
364
+ # Enforce max PRs per run
365
+ if [ "${MAX_PRS_PER_RUN}" -gt "0" ] && [ "${processed}" -ge "${MAX_PRS_PER_RUN}" ]; then
366
+ log "INFO: Reached max PRs per run (${MAX_PRS_PER_RUN}), stopping"
367
+ break
368
+ fi
369
+
370
+ # Enforce global timeout
371
+ elapsed=$(( $(date +%s) - SCRIPT_START_TIME ))
372
+ if [ "${elapsed}" -ge "${MAX_RUNTIME}" ]; then
373
+ log "WARN: Global timeout reached (${MAX_RUNTIME}s), stopping early"
374
+ break
375
+ fi
376
+
377
+ processed=$(( processed + 1 ))
378
+
379
+ result=""
380
+ if result=$(process_pr "${pr_number}" "${pr_branch}" "${pr_title}" 2>&1); then
381
+ # process_pr echoes "ready" or "conflicted" on the last line; extract it
382
+ last_line=$(printf '%s' "${result}" | tail -1)
383
+ if [ "${last_line}" = "ready" ]; then
384
+ prs_ready=$(( prs_ready + 1 ))
385
+ conflicts_resolved=$(( conflicts_resolved + 1 ))
386
+ fi
387
+ else
388
+ prs_failed=$(( prs_failed + 1 ))
389
+ fi
390
+
391
+ done < <(printf '%s' "${pr_json}" | jq -c '.[]')
392
+
393
+ log "RUN-END: pr-resolver complete processed=${processed} conflicts_resolved=${conflicts_resolved} prs_ready=${prs_ready} prs_failed=${prs_failed}"
394
+
395
+ send_telegram_status_message "Night Watch PR Resolver: completed" "Project: ${PROJECT_NAME}
396
+ Provider (model): ${PROVIDER_MODEL_DISPLAY}
397
+ PRs processed: ${processed}
398
+ Conflicts resolved: ${conflicts_resolved}
399
+ PRs marked '${READY_LABEL}': ${prs_ready}
400
+ PRs failed: ${prs_failed}"
401
+
402
+ emit_result "success" "prs_processed=${processed}|conflicts_resolved=${conflicts_resolved}|reviews_addressed=${reviews_addressed}|prs_ready=${prs_ready}|prs_failed=${prs_failed}"
@@ -105,7 +105,9 @@ extract_review_score_from_text() {
105
105
  # ── Global Job Queue Gate ────────────────────────────────────────────────────
106
106
  # Atomically claim a DB slot or enqueue for later dispatch — no flock needed.
107
107
  if [ "${NW_QUEUE_ENABLED:-0}" = "1" ]; then
108
- if [ "${NW_QUEUE_DISPATCHED:-0}" = "1" ]; then
108
+ if [ "${NW_QUEUE_INHERITED_SLOT:-0}" = "1" ]; then
109
+ :
110
+ elif [ "${NW_QUEUE_DISPATCHED:-0}" = "1" ]; then
109
111
  arm_global_queue_cleanup
110
112
  else
111
113
  claim_or_enqueue "${SCRIPT_TYPE}" "${PROJECT_DIR}"
@@ -664,7 +666,11 @@ while IFS=$'\t' read -r pr_number pr_branch pr_labels; do
664
666
  } | awk '!seen[$0]++'
665
667
  )
666
668
  LATEST_SCORE=$(extract_review_score_from_text "${ALL_COMMENTS}")
667
- if [ -n "${LATEST_SCORE}" ] && [ "${LATEST_SCORE}" -lt "${MIN_REVIEW_SCORE}" ]; then
669
+ if [ -z "${LATEST_SCORE}" ]; then
670
+ log "INFO: PR #${pr_number} (${pr_branch}) has no review score yet — needs initial review"
671
+ NEEDS_WORK=1
672
+ PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
673
+ elif [ "${LATEST_SCORE}" -lt "${MIN_REVIEW_SCORE}" ]; then
668
674
  log "INFO: PR #${pr_number} (${pr_branch}) has review score ${LATEST_SCORE}/100 (threshold: ${MIN_REVIEW_SCORE})"
669
675
  NEEDS_WORK=1
670
676
  PRS_NEEDING_WORK="${PRS_NEEDING_WORK} #${pr_number}"
@@ -675,7 +681,7 @@ done < <(
675
681
  )
676
682
 
677
683
  if [ "${NEEDS_WORK}" -eq 0 ]; then
678
- log "SKIP: All ${OPEN_PRS} open PR(s) have passing CI and review score >= ${MIN_REVIEW_SCORE} (or no score yet)"
684
+ log "SKIP: All ${OPEN_PRS} open PR(s) have passing CI and review score >= ${MIN_REVIEW_SCORE}"
679
685
 
680
686
  # ── Auto-merge eligible PRs ───────────────────────────────
681
687
  if [ "${NW_AUTO_MERGE:-0}" = "1" ]; then
@@ -814,6 +820,7 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
814
820
  NW_TARGET_PR="${pr_number}" \
815
821
  NW_REVIEWER_WORKER_MODE="1" \
816
822
  NW_REVIEWER_PARALLEL="0" \
823
+ NW_QUEUE_INHERITED_SLOT="1" \
817
824
  bash "${SCRIPT_DIR}/night-watch-pr-reviewer-cron.sh" "${PROJECT_DIR}" > "${worker_output}" 2>&1
818
825
  ) &
819
826
 
@@ -922,7 +929,7 @@ if [ -z "${TARGET_PR}" ] && [ "${WORKER_MODE}" != "1" ] && [ "${PARALLEL_ENABLED
922
929
  cleanup_reviewer_worktrees
923
930
 
924
931
  emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}" "${MAX_WORKER_ATTEMPTS}" "${MAX_WORKER_FINAL_SCORE}" "0" "${NO_CHANGES_PRS}"
925
- exit 0
932
+ exit "${EXIT_CODE}"
926
933
  fi
927
934
 
928
935
  REVIEW_RUN_TOKEN="${PROJECT_RUNTIME_KEY}-$$"
@@ -1015,8 +1022,16 @@ if [ -n "${TARGET_PR}" ]; then
1015
1022
  fi
1016
1023
  if [ -n "${TARGET_SCORE}" ]; then
1017
1024
  TARGET_SCOPE_PROMPT+=$'- latest review score: '"${TARGET_SCORE}"$'/100\n'
1025
+ TARGET_SCOPE_PROMPT+=$'- action: fix\n'
1026
+ # Inject the latest review comment body for the fix prompt
1027
+ REVIEW_BODY=$(gh api "repos/$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null)/issues/${TARGET_PR}/comments" --jq '[.[] | select(.body | test("Overall Score|Score:.*[0-9]+/100"))] | last | .body // ""' 2>/dev/null || echo "")
1028
+ if [ -n "${REVIEW_BODY}" ]; then
1029
+ TRUNCATED_REVIEW=$(printf '%s' "${REVIEW_BODY}" | head -c 6000)
1030
+ TARGET_SCOPE_PROMPT+=$'\n## Latest Review Feedback\n'"${TRUNCATED_REVIEW}"$'\n'
1031
+ fi
1018
1032
  else
1019
1033
  TARGET_SCOPE_PROMPT+=$'- latest review score: not found\n'
1034
+ TARGET_SCOPE_PROMPT+=$'- action: review\n'
1020
1035
  fi
1021
1036
  fi
1022
1037
 
@@ -1201,7 +1216,8 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
1201
1216
  fi
1202
1217
  continue
1203
1218
  fi
1204
- log "RETRY: No review score found for PR #${TARGET_PR} after ${TOTAL_ATTEMPTS} attempts; failing run."
1219
+ log "RETRY: No review score found for PR #${TARGET_PR} after ${TOTAL_ATTEMPTS} attempts; labeling needs-human-review and failing run."
1220
+ gh pr edit "${TARGET_PR}" --add-label "needs-human-review" 2>/dev/null || true
1205
1221
  EXIT_CODE=1
1206
1222
  break
1207
1223
  fi
@@ -1316,3 +1332,4 @@ fi
1316
1332
  REVIEWER_TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START_TIME ))
1317
1333
  log "OUTCOME: exit_code=${EXIT_CODE} total_elapsed=${REVIEWER_TOTAL_ELAPSED}s prs=${PRS_NEEDING_WORK_CSV:-none} attempts=${ATTEMPTS_MADE}"
1318
1334
  emit_final_status "${EXIT_CODE}" "${PRS_NEEDING_WORK_CSV}" "${AUTO_MERGED_PRS}" "${AUTO_MERGE_FAILED_PRS}" "${ATTEMPTS_MADE}" "${FINAL_SCORE}" "${NO_CHANGES_NEEDED}" "${NO_CHANGES_PRS}"
1335
+ exit "${EXIT_CODE}"