@jonit-dev/night-watch-cli 1.8.10-beta.1 → 1.8.10-beta.11

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 (208) hide show
  1. package/dist/cli.js +2453 -1378
  2. package/dist/commands/dashboard/tab-config.d.ts.map +1 -1
  3. package/dist/commands/queue.d.ts.map +1 -1
  4. package/dist/commands/shared/env-builder.d.ts.map +1 -1
  5. package/dist/scripts/night-watch-cron.sh +344 -53
  6. package/dist/scripts/night-watch-helpers.sh +182 -7
  7. package/dist/scripts/night-watch-merger-cron.sh +8 -2
  8. package/dist/scripts/night-watch-plan-cron.sh +1 -1
  9. package/dist/scripts/night-watch-pr-resolver-cron.sh +7 -3
  10. package/dist/scripts/night-watch-pr-reviewer-cron.sh +5 -0
  11. package/dist/scripts/night-watch-qa-cron.sh +5 -0
  12. package/dist/scripts/night-watch-slicer-cron.sh +1 -1
  13. package/dist/web/assets/index-B6E6kOoR.js +406 -0
  14. package/dist/web/assets/index-DIMUXIP8.css +1 -0
  15. package/dist/web/assets/index-Ds8OqaCa.css +1 -0
  16. package/dist/web/assets/index-NR27JE3b.js +406 -0
  17. package/dist/web/index.html +2 -2
  18. package/package.json +1 -1
  19. package/dist/cli.d.ts +0 -3
  20. package/dist/cli.js.map +0 -1
  21. package/dist/commands/analytics.d.ts +0 -14
  22. package/dist/commands/analytics.js +0 -69
  23. package/dist/commands/analytics.js.map +0 -1
  24. package/dist/commands/audit.d.ts +0 -19
  25. package/dist/commands/audit.js +0 -144
  26. package/dist/commands/audit.js.map +0 -1
  27. package/dist/commands/board.d.ts +0 -9
  28. package/dist/commands/board.js +0 -702
  29. package/dist/commands/board.js.map +0 -1
  30. package/dist/commands/cancel.d.ts +0 -46
  31. package/dist/commands/cancel.js +0 -239
  32. package/dist/commands/cancel.js.map +0 -1
  33. package/dist/commands/cron.d.ts +0 -8
  34. package/dist/commands/cron.js +0 -134
  35. package/dist/commands/cron.js.map +0 -1
  36. package/dist/commands/dashboard/tab-actions.d.ts +0 -10
  37. package/dist/commands/dashboard/tab-actions.js +0 -247
  38. package/dist/commands/dashboard/tab-actions.js.map +0 -1
  39. package/dist/commands/dashboard/tab-config.d.ts +0 -21
  40. package/dist/commands/dashboard/tab-config.js +0 -873
  41. package/dist/commands/dashboard/tab-config.js.map +0 -1
  42. package/dist/commands/dashboard/tab-logs.d.ts +0 -10
  43. package/dist/commands/dashboard/tab-logs.js +0 -202
  44. package/dist/commands/dashboard/tab-logs.js.map +0 -1
  45. package/dist/commands/dashboard/tab-schedules.d.ts +0 -21
  46. package/dist/commands/dashboard/tab-schedules.js +0 -320
  47. package/dist/commands/dashboard/tab-schedules.js.map +0 -1
  48. package/dist/commands/dashboard/tab-status.d.ts +0 -32
  49. package/dist/commands/dashboard/tab-status.js +0 -424
  50. package/dist/commands/dashboard/tab-status.js.map +0 -1
  51. package/dist/commands/dashboard/types.d.ts +0 -42
  52. package/dist/commands/dashboard/types.js +0 -5
  53. package/dist/commands/dashboard/types.js.map +0 -1
  54. package/dist/commands/dashboard.d.ts +0 -11
  55. package/dist/commands/dashboard.js +0 -242
  56. package/dist/commands/dashboard.js.map +0 -1
  57. package/dist/commands/doctor.d.ts +0 -16
  58. package/dist/commands/doctor.js +0 -195
  59. package/dist/commands/doctor.js.map +0 -1
  60. package/dist/commands/history.d.ts +0 -7
  61. package/dist/commands/history.js +0 -49
  62. package/dist/commands/history.js.map +0 -1
  63. package/dist/commands/init.d.ts +0 -45
  64. package/dist/commands/init.js +0 -777
  65. package/dist/commands/init.js.map +0 -1
  66. package/dist/commands/install.d.ts +0 -65
  67. package/dist/commands/install.js +0 -405
  68. package/dist/commands/install.js.map +0 -1
  69. package/dist/commands/logs.d.ts +0 -15
  70. package/dist/commands/logs.js +0 -155
  71. package/dist/commands/logs.js.map +0 -1
  72. package/dist/commands/merge.d.ts +0 -26
  73. package/dist/commands/merge.js +0 -159
  74. package/dist/commands/merge.js.map +0 -1
  75. package/dist/commands/notify.d.ts +0 -7
  76. package/dist/commands/notify.js +0 -43
  77. package/dist/commands/notify.js.map +0 -1
  78. package/dist/commands/plan.d.ts +0 -19
  79. package/dist/commands/plan.js +0 -88
  80. package/dist/commands/plan.js.map +0 -1
  81. package/dist/commands/prd-state.d.ts +0 -12
  82. package/dist/commands/prd-state.js +0 -47
  83. package/dist/commands/prd-state.js.map +0 -1
  84. package/dist/commands/prd.d.ts +0 -18
  85. package/dist/commands/prd.js +0 -363
  86. package/dist/commands/prd.js.map +0 -1
  87. package/dist/commands/prds.d.ts +0 -13
  88. package/dist/commands/prds.js +0 -194
  89. package/dist/commands/prds.js.map +0 -1
  90. package/dist/commands/prs.d.ts +0 -14
  91. package/dist/commands/prs.js +0 -104
  92. package/dist/commands/prs.js.map +0 -1
  93. package/dist/commands/qa.d.ts +0 -34
  94. package/dist/commands/qa.js +0 -214
  95. package/dist/commands/qa.js.map +0 -1
  96. package/dist/commands/queue.d.ts +0 -8
  97. package/dist/commands/queue.js +0 -376
  98. package/dist/commands/queue.js.map +0 -1
  99. package/dist/commands/resolve.d.ts +0 -26
  100. package/dist/commands/resolve.js +0 -186
  101. package/dist/commands/resolve.js.map +0 -1
  102. package/dist/commands/retry.d.ts +0 -9
  103. package/dist/commands/retry.js +0 -71
  104. package/dist/commands/retry.js.map +0 -1
  105. package/dist/commands/review.d.ts +0 -82
  106. package/dist/commands/review.js +0 -479
  107. package/dist/commands/review.js.map +0 -1
  108. package/dist/commands/run.d.ts +0 -73
  109. package/dist/commands/run.js +0 -509
  110. package/dist/commands/run.js.map +0 -1
  111. package/dist/commands/serve.d.ts +0 -19
  112. package/dist/commands/serve.js +0 -142
  113. package/dist/commands/serve.js.map +0 -1
  114. package/dist/commands/shared/env-builder.d.ts +0 -49
  115. package/dist/commands/shared/env-builder.js +0 -150
  116. package/dist/commands/shared/env-builder.js.map +0 -1
  117. package/dist/commands/slice.d.ts +0 -35
  118. package/dist/commands/slice.js +0 -316
  119. package/dist/commands/slice.js.map +0 -1
  120. package/dist/commands/state.d.ts +0 -8
  121. package/dist/commands/state.js +0 -54
  122. package/dist/commands/state.js.map +0 -1
  123. package/dist/commands/status.d.ts +0 -14
  124. package/dist/commands/status.js +0 -297
  125. package/dist/commands/status.js.map +0 -1
  126. package/dist/commands/summary.d.ts +0 -14
  127. package/dist/commands/summary.js +0 -193
  128. package/dist/commands/summary.js.map +0 -1
  129. package/dist/commands/uninstall.d.ts +0 -25
  130. package/dist/commands/uninstall.js +0 -134
  131. package/dist/commands/uninstall.js.map +0 -1
  132. package/dist/commands/update.d.ts +0 -22
  133. package/dist/commands/update.js +0 -90
  134. package/dist/commands/update.js.map +0 -1
  135. package/dist/web/assets/index-2JY0x_Ij.js +0 -381
  136. package/dist/web/assets/index-3h8pgmqL.css +0 -1
  137. package/dist/web/assets/index-B-wbyZq7.js +0 -386
  138. package/dist/web/assets/index-B1BnOpiO.css +0 -1
  139. package/dist/web/assets/index-B3CnV08_.js +0 -365
  140. package/dist/web/assets/index-B5QjuFh9.css +0 -1
  141. package/dist/web/assets/index-B8FW2ecQ.js +0 -370
  142. package/dist/web/assets/index-BFxPiKyy.js +0 -381
  143. package/dist/web/assets/index-BGqNh_Da.js +0 -365
  144. package/dist/web/assets/index-BIONU0qz.css +0 -1
  145. package/dist/web/assets/index-B_l_3wnA.js +0 -370
  146. package/dist/web/assets/index-Ba-4YvTQ.js +0 -365
  147. package/dist/web/assets/index-Bbb4-39N.js +0 -370
  148. package/dist/web/assets/index-BdgdShEN.js +0 -365
  149. package/dist/web/assets/index-BhiC4Z-G.js +0 -381
  150. package/dist/web/assets/index-BjhCFjZi.js +0 -381
  151. package/dist/web/assets/index-BlRxmrnQ.css +0 -1
  152. package/dist/web/assets/index-BqwbXsHS.js +0 -365
  153. package/dist/web/assets/index-BsC7RT48.css +0 -1
  154. package/dist/web/assets/index-Bvh8XI8_.js +0 -370
  155. package/dist/web/assets/index-C01r2ymn.js +0 -381
  156. package/dist/web/assets/index-C51Rbsmk.js +0 -381
  157. package/dist/web/assets/index-C7lMNxRE.js +0 -370
  158. package/dist/web/assets/index-CJLObgsn.js +0 -386
  159. package/dist/web/assets/index-CLuRf7Zt.js +0 -381
  160. package/dist/web/assets/index-CM3xFd3e.css +0 -1
  161. package/dist/web/assets/index-CNkBtDK7.js +0 -370
  162. package/dist/web/assets/index-CPQbZ1BL.css +0 -1
  163. package/dist/web/assets/index-CTy5dUDU.css +0 -1
  164. package/dist/web/assets/index-CU15COKs.js +0 -370
  165. package/dist/web/assets/index-CiRJZI4z.js +0 -386
  166. package/dist/web/assets/index-Cp7RYjoy.css +0 -1
  167. package/dist/web/assets/index-CvPkZOWT.js +0 -381
  168. package/dist/web/assets/index-CvUk-33B.css +0 -1
  169. package/dist/web/assets/index-Cvmj-oF6.css +0 -1
  170. package/dist/web/assets/index-CxE5iQVO.js +0 -381
  171. package/dist/web/assets/index-D7lZQpFV.js +0 -365
  172. package/dist/web/assets/index-DAyP4GOi.css +0 -1
  173. package/dist/web/assets/index-DCG0n8Kg.js +0 -386
  174. package/dist/web/assets/index-DEEI8cyF.css +0 -1
  175. package/dist/web/assets/index-DF99BowV.js +0 -381
  176. package/dist/web/assets/index-DGWsvFj6.css +0 -1
  177. package/dist/web/assets/index-DGpU39Cp.css +0 -1
  178. package/dist/web/assets/index-DI4kFgOi.js +0 -370
  179. package/dist/web/assets/index-DIyTcPw5.css +0 -1
  180. package/dist/web/assets/index-DTsfDC7m.js +0 -381
  181. package/dist/web/assets/index-DcgNAi4A.js +0 -386
  182. package/dist/web/assets/index-DgOAgkZy.css +0 -1
  183. package/dist/web/assets/index-DnHkqbOa.js +0 -386
  184. package/dist/web/assets/index-DnR7Idcf.css +0 -1
  185. package/dist/web/assets/index-DpVirMEe.css +0 -1
  186. package/dist/web/assets/index-DsYIWZ86.css +0 -1
  187. package/dist/web/assets/index-DtrDkci5.js +0 -381
  188. package/dist/web/assets/index-DyjIth5M.js +0 -386
  189. package/dist/web/assets/index-FwIKfHPL.css +0 -1
  190. package/dist/web/assets/index-IKrZymWk.css +0 -1
  191. package/dist/web/assets/index-MA6fM0ab.js +0 -381
  192. package/dist/web/assets/index-N_QxaSEg.css +0 -1
  193. package/dist/web/assets/index-OcU-0TCQ.css +0 -1
  194. package/dist/web/assets/index-OyhrmG-L.js +0 -381
  195. package/dist/web/assets/index-SQlBKu_s.js +0 -386
  196. package/dist/web/assets/index-Sv2B60J4.js +0 -370
  197. package/dist/web/assets/index-Vgyivb5u.js +0 -365
  198. package/dist/web/assets/index-ZABWMEZR.js +0 -381
  199. package/dist/web/assets/index-ZE5lOeJp.js +0 -386
  200. package/dist/web/assets/index-aCHmkAcJ.css +0 -1
  201. package/dist/web/assets/index-bFijnpuU.js +0 -381
  202. package/dist/web/assets/index-bUPZgSoZ.css +0 -1
  203. package/dist/web/assets/index-mz1VIYsP.css +0 -1
  204. package/dist/web/assets/index-oOp_MFeE.js +0 -376
  205. package/dist/web/assets/index-rfU713Zm.js +0 -386
  206. package/dist/web/assets/index-tuNH9gmb.js +0 -448
  207. package/dist/web/assets/index-viSwHyDD.js +0 -365
  208. package/dist/web/assets/index-yKEQysks.js +0 -365
@@ -87,6 +87,123 @@ validate_provider() {
87
87
  return 1
88
88
  }
89
89
 
90
+ night_watch_push_uses_no_verify() {
91
+ [ "${NW_GIT_PUSH_NO_VERIFY:-0}" = "1" ]
92
+ }
93
+
94
+ git_push_for_project() {
95
+ local repo_dir="${1:?repo_dir required}"
96
+ shift
97
+
98
+ if night_watch_push_uses_no_verify; then
99
+ git -C "${repo_dir}" push --no-verify "$@"
100
+ else
101
+ git -C "${repo_dir}" push "$@"
102
+ fi
103
+ }
104
+
105
+ project_git_push_command() {
106
+ local branch_name="${1:?branch_name required}"
107
+ local mode="${2:-normal}"
108
+ local no_verify=""
109
+
110
+ if night_watch_push_uses_no_verify; then
111
+ no_verify=" --no-verify"
112
+ fi
113
+
114
+ case "${mode}" in
115
+ force-with-lease)
116
+ printf 'git push%s --force-with-lease origin %s' "${no_verify}" "${branch_name}"
117
+ ;;
118
+ *)
119
+ printf 'git push%s origin %s' "${no_verify}" "${branch_name}"
120
+ ;;
121
+ esac
122
+ }
123
+
124
+ # ── Executor PR status labels ────────────────────────────────────────────────
125
+
126
+ NW_EXECUTOR_PARTIAL_LABEL="${NW_EXECUTOR_PARTIAL_LABEL:-nw:partial}"
127
+ NW_EXECUTOR_RESUMABLE_LABEL="${NW_EXECUTOR_RESUMABLE_LABEL:-nw:resumable}"
128
+ NW_EXECUTOR_READY_REVIEW_LABEL="${NW_EXECUTOR_READY_REVIEW_LABEL:-nw:ready-review}"
129
+
130
+ csv_has_label() {
131
+ local csv="${1:-}"
132
+ local label="${2:?label required}"
133
+
134
+ printf '%s\n' "${csv}" \
135
+ | tr ',' '\n' \
136
+ | sed '/^[[:space:]]*$/d' \
137
+ | grep -Fxq "${label}"
138
+ }
139
+
140
+ ensure_github_label() {
141
+ local label_name="${1:?label_name required}"
142
+ local description="${2:-}"
143
+ local color="${3:-1d76db}"
144
+
145
+ gh label create "${label_name}" \
146
+ --description "${description}" \
147
+ --color "${color}" \
148
+ --force 2>/dev/null || true
149
+ }
150
+
151
+ ensure_executor_status_labels() {
152
+ ensure_github_label \
153
+ "${NW_EXECUTOR_PARTIAL_LABEL}" \
154
+ "Executor started this PR and work is intentionally incomplete" \
155
+ "fbca04"
156
+ ensure_github_label \
157
+ "${NW_EXECUTOR_RESUMABLE_LABEL}" \
158
+ "Executor should resume this unfinished PR before starting new work" \
159
+ "d93f0b"
160
+ ensure_github_label \
161
+ "${NW_EXECUTOR_READY_REVIEW_LABEL}" \
162
+ "Executor finished implementation and the PR is ready for automated/human review" \
163
+ "0e8a16"
164
+ }
165
+
166
+ find_open_pr_for_branch() {
167
+ local branch_name="${1:?branch_name required}"
168
+ local pr_list=""
169
+
170
+ pr_list=$(gh pr list --state open --limit 200 \
171
+ --json number,headRefName,url,title,isDraft,labels,createdAt 2>/dev/null || echo "[]")
172
+
173
+ printf '%s' "${pr_list}" \
174
+ | jq -c --arg branch_name "${branch_name}" '
175
+ .[]
176
+ | select((.headRefName // "") == $branch_name)
177
+ ' 2>/dev/null \
178
+ | head -n 1 || true
179
+ }
180
+
181
+ find_executor_resume_pr() {
182
+ local branch_prefix="${1:-night-watch}"
183
+ local pr_list=""
184
+
185
+ pr_list=$(gh pr list --state open --limit 200 \
186
+ --json number,headRefName,url,title,isDraft,labels,createdAt 2>/dev/null || echo "[]")
187
+
188
+ printf '%s' "${pr_list}" \
189
+ | jq -c \
190
+ --arg primary_prefix "${branch_prefix}/" \
191
+ --arg resumable_label "${NW_EXECUTOR_RESUMABLE_LABEL}" '
192
+ [
193
+ .[]
194
+ | select(
195
+ (.headRefName // "" | startswith($primary_prefix))
196
+ or
197
+ (.headRefName // "" | startswith("feat/"))
198
+ )
199
+ | .labelNames = ((.labels // []) | map(.name))
200
+ | select((.labelNames | index($resumable_label)) != null)
201
+ ]
202
+ | sort_by(.createdAt // "")
203
+ | .[0] // empty
204
+ ' 2>/dev/null || true
205
+ }
206
+
90
207
  # ── Generic Provider Command Builder ──────────────────────────────────────────
91
208
 
92
209
  # Build a provider command from NW_PROVIDER_* environment variables.
@@ -180,6 +297,13 @@ resolve_night_watch_cli() {
180
297
 
181
298
  local bundled_bin="${script_dir}/../bin/night-watch.mjs"
182
299
  if [ -x "${bundled_bin}" ]; then
300
+ # Verify dist/ exists — prevents ERR_MODULE_NOT_FOUND crashes
301
+ local dist_dir
302
+ dist_dir="$(cd "$(dirname "${bundled_bin}")" && pwd)/../dist"
303
+ if [ ! -d "${dist_dir}" ]; then
304
+ echo "ERROR: night-watch dist/ not found at ${dist_dir}; run 'yarn build'" >&2
305
+ return 1
306
+ fi
183
307
  printf "%s" "${bundled_bin}"
184
308
  return 0
185
309
  fi
@@ -360,20 +484,58 @@ acquire_lock() {
360
484
  local lock_file="${1:?lock_file required}"
361
485
 
362
486
  if [ -f "${lock_file}" ]; then
363
- local lock_pid
364
- lock_pid=$(cat "${lock_file}" 2>/dev/null || echo "")
487
+ local lock_content lock_pid lock_ts
488
+ lock_content=$(cat "${lock_file}" 2>/dev/null || echo "")
489
+ lock_pid=$(echo "${lock_content}" | awk '{print $1}')
490
+ lock_ts=$(echo "${lock_content}" | awk '{print $2}')
491
+
365
492
  if [ -n "${lock_pid}" ] && kill -0 "${lock_pid}" 2>/dev/null; then
366
- log "SKIP: Previous run (PID ${lock_pid}) still active"
367
- return 1
493
+ # PID is alive — but guard against PID reuse via /proc start time
494
+ if _is_lock_holder_alive "${lock_pid}" "${lock_ts}"; then
495
+ log "SKIP: Previous run (PID ${lock_pid}) still active"
496
+ return 1
497
+ fi
498
+ log "WARN: PID ${lock_pid} reused by another process, lock is stale"
499
+ else
500
+ log "WARN: Stale lock file found (PID ${lock_pid}), removing"
368
501
  fi
369
- log "WARN: Stale lock file found (PID ${lock_pid}), removing"
370
502
  rm -f "${lock_file}"
371
503
  fi
372
504
 
373
505
  local quoted_lock_file=""
374
506
  printf -v quoted_lock_file '%q' "${lock_file}"
375
507
  append_exit_trap "rm -f -- ${quoted_lock_file}"
376
- echo $$ > "${lock_file}"
508
+ echo "$$ $(date +%s)" > "${lock_file}"
509
+ return 0
510
+ }
511
+
512
+ # Check if the lock holder process is genuinely the one that wrote the lock.
513
+ # Guards against PID reuse: if the process started after the lock was created,
514
+ # it's a different process that reused the same PID.
515
+ _is_lock_holder_alive() {
516
+ local pid="${1}" lock_ts="${2:-}"
517
+
518
+ # No timestamp in lock file (old format) — fall back to simple PID check
519
+ if [ -z "${lock_ts}" ]; then
520
+ return 0
521
+ fi
522
+
523
+ # On Linux, check /proc/<pid>/stat for process start time
524
+ if [ -f "/proc/${pid}/stat" ]; then
525
+ local stat_content boot_time_secs start_ticks clk_tck proc_start_secs
526
+ stat_content=$(cat "/proc/${pid}/stat" 2>/dev/null) || return 0
527
+ # Field 22 is starttime (in clock ticks since boot)
528
+ start_ticks=$(echo "${stat_content}" | awk '{print $22}')
529
+ clk_tck=$(getconf CLK_TCK 2>/dev/null || echo 100)
530
+ boot_time_secs=$(awk '/^btime/{print $2}' /proc/stat 2>/dev/null) || return 0
531
+ proc_start_secs=$(( boot_time_secs + start_ticks / clk_tck ))
532
+
533
+ # If the process started more than 2 seconds after the lock was written, PID was reused
534
+ if [ "${proc_start_secs}" -gt "$(( lock_ts + 2 ))" ]; then
535
+ return 1
536
+ fi
537
+ fi
538
+
377
539
  return 0
378
540
  }
379
541
 
@@ -802,6 +964,19 @@ check_rate_limited() {
802
964
  fi
803
965
  }
804
966
 
967
+ # Detect transient API network errors (e.g. "Network error" in a 400 response).
968
+ # Usage: check_network_error <log_file> [start_line]
969
+ # Returns 0 if a network error was detected, 1 otherwise.
970
+ check_network_error() {
971
+ local log_file="${1:?log_file required}"
972
+ local start_line="${2:-0}"
973
+ if [ "${start_line}" -gt 0 ] 2>/dev/null; then
974
+ tail -n "+$((start_line + 1))" "${log_file}" 2>/dev/null | grep -qi "Network error"
975
+ else
976
+ tail -20 "${log_file}" 2>/dev/null | grep -qi "Network error"
977
+ fi
978
+ }
979
+
805
980
  # Detect context window exhaustion from Claude API logs.
806
981
  # Usage: check_context_exhausted <log_file> [start_line]
807
982
  # Returns 0 if context exhausted, 1 otherwise.
@@ -1105,7 +1280,7 @@ claim_or_enqueue() {
1105
1280
  }
1106
1281
 
1107
1282
  local claim_id
1108
- if claim_id=$("${cli_bin}" queue claim "${script_type}" "${project_dir}" --provider-key "${provider_key}" 2>/dev/null); then
1283
+ if claim_id=$("${cli_bin}" queue claim "${script_type}" "${project_dir}" --provider-key "${provider_key}" --pid $$ 2>/dev/null); then
1109
1284
  NW_QUEUE_ENTRY_ID="${claim_id}"
1110
1285
  export NW_QUEUE_ENTRY_ID
1111
1286
  arm_global_queue_cleanup
@@ -187,12 +187,12 @@ fi
187
187
  kill -TERM $$ 2>/dev/null || true
188
188
  ) &
189
189
  WATCHDOG_PID=$!
190
- trap 'kill ${WATCHDOG_PID} 2>/dev/null || true; rm -f "${LOCK_FILE}"' EXIT
190
+ append_exit_trap "kill ${WATCHDOG_PID} 2>/dev/null || true"
191
191
 
192
192
  # Discover open PRs sorted by creation date (oldest first = FIFO)
193
193
  log "INFO: Scanning open PRs..."
194
194
  PR_LIST_JSON=$(gh pr list --state open \
195
- --json number,headRefName,createdAt,isDraft \
195
+ --json number,headRefName,createdAt,isDraft,labels \
196
196
  --jq 'sort_by(.createdAt)' \
197
197
  2>/dev/null || echo "[]")
198
198
 
@@ -211,6 +211,7 @@ while IFS= read -r pr_json; do
211
211
  pr_number=$(echo "${pr_json}" | jq -r '.number')
212
212
  pr_branch=$(echo "${pr_json}" | jq -r '.headRefName')
213
213
  is_draft=$(echo "${pr_json}" | jq -r '.isDraft')
214
+ pr_labels=$(echo "${pr_json}" | jq -r '[.labels[]?.name] | join(",")')
214
215
 
215
216
  # Skip drafts
216
217
  if [ "${is_draft}" = "true" ]; then
@@ -218,6 +219,11 @@ while IFS= read -r pr_json; do
218
219
  continue
219
220
  fi
220
221
 
222
+ if csv_has_label "${pr_labels:-}" "${NW_EXECUTOR_PARTIAL_LABEL}"; then
223
+ log "INFO: PR #${pr_number} (${pr_branch}): Skipping partial executor PR"
224
+ continue
225
+ fi
226
+
221
227
  # Check branch pattern filter
222
228
  if ! matches_branch_patterns "${pr_branch}"; then
223
229
  log "DEBUG: PR #${pr_number} (${pr_branch}): Branch pattern mismatch, skipping"
@@ -83,7 +83,7 @@ cleanup_on_exit() {
83
83
  rm -f "${LOCK_FILE}"
84
84
  }
85
85
 
86
- trap cleanup_on_exit EXIT
86
+ append_exit_trap "cleanup_on_exit"
87
87
 
88
88
  # Dry-run mode
89
89
  if [ "${NW_DRY_RUN:-0}" = "1" ]; then
@@ -167,6 +167,8 @@ process_pr() {
167
167
  log "INFO: Invoking AI to resolve conflicts for PR #${pr_number}" "branch=${pr_branch}"
168
168
 
169
169
  local ai_prompt
170
+ local force_push_cmd
171
+ force_push_cmd=$(project_git_push_command "${pr_branch}" "force-with-lease")
170
172
  ai_prompt="You are working in a git repository at ${worktree_dir}. \
171
173
  Branch '${pr_branch}' has merge conflicts with '${default_branch}'. \
172
174
  Please resolve the merge conflicts by: \
@@ -174,7 +176,7 @@ Please resolve the merge conflicts by: \
174
176
  2) Resolving any conflict markers in the affected files \
175
177
  3) Staging resolved files with: git add <files> \
176
178
  4) Continuing the rebase with: git rebase --continue \
177
- 5) Finally pushing with: git push --force-with-lease origin ${pr_branch} \
179
+ 5) Finally pushing with: ${force_push_cmd} \
178
180
  Work exclusively in the directory: ${worktree_dir}"
179
181
 
180
182
  local -a cmd_parts
@@ -203,7 +205,7 @@ Work exclusively in the directory: ${worktree_dir}"
203
205
  return 1
204
206
  fi
205
207
  # 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 || {
208
+ git_push_for_project "${worktree_dir}" --force-with-lease origin "${pr_branch}" >> "${LOG_FILE}" 2>&1 || {
207
209
  log "WARN: Push after rebase failed for PR #${pr_number}" "branch=${pr_branch}"
208
210
  }
209
211
  fi
@@ -224,13 +226,15 @@ Work exclusively in the directory: ${worktree_dir}"
224
226
  fi
225
227
 
226
228
  local review_prompt
229
+ local review_push_cmd
230
+ review_push_cmd=$(project_git_push_command "${pr_branch}")
227
231
  review_prompt="You are working in the git repository at ${review_workdir}. \
228
232
  PR #${pr_number} on branch '${pr_branch}' has unresolved review comments requesting changes. \
229
233
  Please: \
230
234
  1) Run 'gh pr view ${pr_number} --comments' to read the review comments \
231
235
  2) Implement the requested changes \
232
236
  3) Commit the changes with a descriptive message \
233
- 4) Push with: git push origin ${pr_branch} \
237
+ 4) Push with: ${review_push_cmd} \
234
238
  Work in the directory: ${review_workdir}"
235
239
 
236
240
  local -a review_cmd_parts
@@ -631,6 +631,11 @@ while IFS=$'\t' read -r pr_number pr_branch pr_labels; do
631
631
  continue
632
632
  fi
633
633
 
634
+ if csv_has_label "${pr_labels:-}" "${NW_EXECUTOR_PARTIAL_LABEL}"; then
635
+ log "INFO: PR #${pr_number} (${pr_branch}) is labeled ${NW_EXECUTOR_PARTIAL_LABEL}; waiting for executor to finish"
636
+ continue
637
+ fi
638
+
634
639
  # Merge-conflict signal: this PR needs action even if CI and score look fine.
635
640
  MERGE_STATE=$(gh pr view "${pr_number}" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null || echo "")
636
641
  if [ "${MERGE_STATE}" = "DIRTY" ] || [ "${MERGE_STATE}" = "CONFLICTING" ]; then
@@ -426,6 +426,11 @@ while IFS=$'\t' read -r pr_number pr_branch pr_title pr_labels; do
426
426
  continue
427
427
  fi
428
428
 
429
+ if csv_has_label "${pr_labels:-}" "${NW_EXECUTOR_PARTIAL_LABEL}"; then
430
+ log "SKIP-QA: PR #${pr_number} (${pr_branch}) is labeled ${NW_EXECUTOR_PARTIAL_LABEL}"
431
+ continue
432
+ fi
433
+
429
434
  # Skip PRs with the skip label
430
435
  if echo "${pr_labels}" | grep -q "${SKIP_LABEL}"; then
431
436
  log "SKIP-QA: PR #${pr_number} (${pr_branch}) has '${SKIP_LABEL}' label"
@@ -75,7 +75,7 @@ cleanup_on_exit() {
75
75
  rm -f "${LOCK_FILE}"
76
76
  }
77
77
 
78
- trap cleanup_on_exit EXIT
78
+ append_exit_trap "cleanup_on_exit"
79
79
 
80
80
  log "START: Running roadmap slicer for ${PROJECT_DIR}"
81
81
  send_telegram_status_message "📋 Night Watch Planner: started" "Project: ${PROJECT_NAME}