@jonit-dev/night-watch-cli 1.8.10-beta.0 → 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.
- package/dist/cli.js +2501 -1387
- package/dist/commands/dashboard/tab-config.d.ts.map +1 -1
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/shared/env-builder.d.ts.map +1 -1
- package/dist/scripts/night-watch-cron.sh +344 -53
- package/dist/scripts/night-watch-helpers.sh +208 -7
- package/dist/scripts/night-watch-merger-cron.sh +8 -2
- package/dist/scripts/night-watch-plan-cron.sh +1 -1
- package/dist/scripts/night-watch-pr-resolver-cron.sh +7 -3
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +11 -8
- package/dist/scripts/night-watch-qa-cron.sh +8 -3
- package/dist/scripts/night-watch-slicer-cron.sh +1 -1
- package/dist/web/assets/index-B6E6kOoR.js +406 -0
- package/dist/web/assets/index-DIMUXIP8.css +1 -0
- package/dist/web/assets/index-Ds8OqaCa.css +1 -0
- package/dist/web/assets/index-NR27JE3b.js +406 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.js.map +0 -1
- package/dist/commands/analytics.d.ts +0 -14
- package/dist/commands/analytics.js +0 -69
- package/dist/commands/analytics.js.map +0 -1
- package/dist/commands/audit.d.ts +0 -19
- package/dist/commands/audit.js +0 -144
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/board.d.ts +0 -9
- package/dist/commands/board.js +0 -702
- package/dist/commands/board.js.map +0 -1
- package/dist/commands/cancel.d.ts +0 -46
- package/dist/commands/cancel.js +0 -239
- package/dist/commands/cancel.js.map +0 -1
- package/dist/commands/cron.d.ts +0 -8
- package/dist/commands/cron.js +0 -134
- package/dist/commands/cron.js.map +0 -1
- package/dist/commands/dashboard/tab-actions.d.ts +0 -10
- package/dist/commands/dashboard/tab-actions.js +0 -247
- package/dist/commands/dashboard/tab-actions.js.map +0 -1
- package/dist/commands/dashboard/tab-config.d.ts +0 -21
- package/dist/commands/dashboard/tab-config.js +0 -873
- package/dist/commands/dashboard/tab-config.js.map +0 -1
- package/dist/commands/dashboard/tab-logs.d.ts +0 -10
- package/dist/commands/dashboard/tab-logs.js +0 -202
- package/dist/commands/dashboard/tab-logs.js.map +0 -1
- package/dist/commands/dashboard/tab-schedules.d.ts +0 -21
- package/dist/commands/dashboard/tab-schedules.js +0 -320
- package/dist/commands/dashboard/tab-schedules.js.map +0 -1
- package/dist/commands/dashboard/tab-status.d.ts +0 -32
- package/dist/commands/dashboard/tab-status.js +0 -424
- package/dist/commands/dashboard/tab-status.js.map +0 -1
- package/dist/commands/dashboard/types.d.ts +0 -42
- package/dist/commands/dashboard/types.js +0 -5
- package/dist/commands/dashboard/types.js.map +0 -1
- package/dist/commands/dashboard.d.ts +0 -11
- package/dist/commands/dashboard.js +0 -242
- package/dist/commands/dashboard.js.map +0 -1
- package/dist/commands/doctor.d.ts +0 -16
- package/dist/commands/doctor.js +0 -195
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/history.d.ts +0 -7
- package/dist/commands/history.js +0 -49
- package/dist/commands/history.js.map +0 -1
- package/dist/commands/init.d.ts +0 -45
- package/dist/commands/init.js +0 -777
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/install.d.ts +0 -65
- package/dist/commands/install.js +0 -405
- package/dist/commands/install.js.map +0 -1
- package/dist/commands/logs.d.ts +0 -15
- package/dist/commands/logs.js +0 -155
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/merge.d.ts +0 -26
- package/dist/commands/merge.js +0 -159
- package/dist/commands/merge.js.map +0 -1
- package/dist/commands/notify.d.ts +0 -7
- package/dist/commands/notify.js +0 -43
- package/dist/commands/notify.js.map +0 -1
- package/dist/commands/plan.d.ts +0 -19
- package/dist/commands/plan.js +0 -88
- package/dist/commands/plan.js.map +0 -1
- package/dist/commands/prd-state.d.ts +0 -12
- package/dist/commands/prd-state.js +0 -47
- package/dist/commands/prd-state.js.map +0 -1
- package/dist/commands/prd.d.ts +0 -18
- package/dist/commands/prd.js +0 -363
- package/dist/commands/prd.js.map +0 -1
- package/dist/commands/prds.d.ts +0 -13
- package/dist/commands/prds.js +0 -194
- package/dist/commands/prds.js.map +0 -1
- package/dist/commands/prs.d.ts +0 -14
- package/dist/commands/prs.js +0 -104
- package/dist/commands/prs.js.map +0 -1
- package/dist/commands/qa.d.ts +0 -34
- package/dist/commands/qa.js +0 -214
- package/dist/commands/qa.js.map +0 -1
- package/dist/commands/queue.d.ts +0 -8
- package/dist/commands/queue.js +0 -363
- package/dist/commands/queue.js.map +0 -1
- package/dist/commands/resolve.d.ts +0 -26
- package/dist/commands/resolve.js +0 -186
- package/dist/commands/resolve.js.map +0 -1
- package/dist/commands/retry.d.ts +0 -9
- package/dist/commands/retry.js +0 -71
- package/dist/commands/retry.js.map +0 -1
- package/dist/commands/review.d.ts +0 -82
- package/dist/commands/review.js +0 -479
- package/dist/commands/review.js.map +0 -1
- package/dist/commands/run.d.ts +0 -73
- package/dist/commands/run.js +0 -509
- package/dist/commands/run.js.map +0 -1
- package/dist/commands/serve.d.ts +0 -19
- package/dist/commands/serve.js +0 -142
- package/dist/commands/serve.js.map +0 -1
- package/dist/commands/shared/env-builder.d.ts +0 -49
- package/dist/commands/shared/env-builder.js +0 -150
- package/dist/commands/shared/env-builder.js.map +0 -1
- package/dist/commands/slice.d.ts +0 -35
- package/dist/commands/slice.js +0 -316
- package/dist/commands/slice.js.map +0 -1
- package/dist/commands/state.d.ts +0 -8
- package/dist/commands/state.js +0 -54
- package/dist/commands/state.js.map +0 -1
- package/dist/commands/status.d.ts +0 -14
- package/dist/commands/status.js +0 -297
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/summary.d.ts +0 -14
- package/dist/commands/summary.js +0 -193
- package/dist/commands/summary.js.map +0 -1
- package/dist/commands/uninstall.d.ts +0 -25
- package/dist/commands/uninstall.js +0 -134
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.d.ts +0 -22
- package/dist/commands/update.js +0 -90
- package/dist/commands/update.js.map +0 -1
- package/dist/web/assets/index-2JY0x_Ij.js +0 -381
- package/dist/web/assets/index-3h8pgmqL.css +0 -1
- package/dist/web/assets/index-B-wbyZq7.js +0 -386
- package/dist/web/assets/index-B1BnOpiO.css +0 -1
- package/dist/web/assets/index-B3CnV08_.js +0 -365
- package/dist/web/assets/index-B5QjuFh9.css +0 -1
- package/dist/web/assets/index-B8FW2ecQ.js +0 -370
- package/dist/web/assets/index-BFxPiKyy.js +0 -381
- package/dist/web/assets/index-BGqNh_Da.js +0 -365
- package/dist/web/assets/index-BIONU0qz.css +0 -1
- package/dist/web/assets/index-B_l_3wnA.js +0 -370
- package/dist/web/assets/index-Ba-4YvTQ.js +0 -365
- package/dist/web/assets/index-Bbb4-39N.js +0 -370
- package/dist/web/assets/index-BdgdShEN.js +0 -365
- package/dist/web/assets/index-BhiC4Z-G.js +0 -381
- package/dist/web/assets/index-BjhCFjZi.js +0 -381
- package/dist/web/assets/index-BlRxmrnQ.css +0 -1
- package/dist/web/assets/index-BqwbXsHS.js +0 -365
- package/dist/web/assets/index-BsC7RT48.css +0 -1
- package/dist/web/assets/index-Bvh8XI8_.js +0 -370
- package/dist/web/assets/index-C01r2ymn.js +0 -381
- package/dist/web/assets/index-C51Rbsmk.js +0 -381
- package/dist/web/assets/index-C7lMNxRE.js +0 -370
- package/dist/web/assets/index-CJLObgsn.js +0 -386
- package/dist/web/assets/index-CLuRf7Zt.js +0 -381
- package/dist/web/assets/index-CM3xFd3e.css +0 -1
- package/dist/web/assets/index-CNkBtDK7.js +0 -370
- package/dist/web/assets/index-CPQbZ1BL.css +0 -1
- package/dist/web/assets/index-CTy5dUDU.css +0 -1
- package/dist/web/assets/index-CU15COKs.js +0 -370
- package/dist/web/assets/index-CiRJZI4z.js +0 -386
- package/dist/web/assets/index-Cp7RYjoy.css +0 -1
- package/dist/web/assets/index-CvPkZOWT.js +0 -381
- package/dist/web/assets/index-CvUk-33B.css +0 -1
- package/dist/web/assets/index-Cvmj-oF6.css +0 -1
- package/dist/web/assets/index-CxE5iQVO.js +0 -381
- package/dist/web/assets/index-D7lZQpFV.js +0 -365
- package/dist/web/assets/index-DAyP4GOi.css +0 -1
- package/dist/web/assets/index-DCG0n8Kg.js +0 -386
- package/dist/web/assets/index-DEEI8cyF.css +0 -1
- package/dist/web/assets/index-DF99BowV.js +0 -381
- package/dist/web/assets/index-DGWsvFj6.css +0 -1
- package/dist/web/assets/index-DGpU39Cp.css +0 -1
- package/dist/web/assets/index-DI4kFgOi.js +0 -370
- package/dist/web/assets/index-DIyTcPw5.css +0 -1
- package/dist/web/assets/index-DTsfDC7m.js +0 -381
- package/dist/web/assets/index-DcgNAi4A.js +0 -386
- package/dist/web/assets/index-DgOAgkZy.css +0 -1
- package/dist/web/assets/index-DnHkqbOa.js +0 -386
- package/dist/web/assets/index-DnR7Idcf.css +0 -1
- package/dist/web/assets/index-DpVirMEe.css +0 -1
- package/dist/web/assets/index-DsYIWZ86.css +0 -1
- package/dist/web/assets/index-DtrDkci5.js +0 -381
- package/dist/web/assets/index-DyjIth5M.js +0 -386
- package/dist/web/assets/index-FwIKfHPL.css +0 -1
- package/dist/web/assets/index-IKrZymWk.css +0 -1
- package/dist/web/assets/index-MA6fM0ab.js +0 -381
- package/dist/web/assets/index-N_QxaSEg.css +0 -1
- package/dist/web/assets/index-OcU-0TCQ.css +0 -1
- package/dist/web/assets/index-OyhrmG-L.js +0 -381
- package/dist/web/assets/index-SQlBKu_s.js +0 -386
- package/dist/web/assets/index-Sv2B60J4.js +0 -370
- package/dist/web/assets/index-Vgyivb5u.js +0 -365
- package/dist/web/assets/index-ZABWMEZR.js +0 -381
- package/dist/web/assets/index-ZE5lOeJp.js +0 -386
- package/dist/web/assets/index-aCHmkAcJ.css +0 -1
- package/dist/web/assets/index-bFijnpuU.js +0 -381
- package/dist/web/assets/index-bUPZgSoZ.css +0 -1
- package/dist/web/assets/index-mz1VIYsP.css +0 -1
- package/dist/web/assets/index-oOp_MFeE.js +0 -376
- package/dist/web/assets/index-rfU713Zm.js +0 -386
- package/dist/web/assets/index-tuNH9gmb.js +0 -448
- package/dist/web/assets/index-viSwHyDD.js +0 -365
- 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
|
-
|
|
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
|
-
|
|
367
|
-
|
|
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
|
|
|
@@ -587,7 +749,10 @@ cleanup_worktrees() {
|
|
|
587
749
|
local project_dir="${1:?project_dir required}"
|
|
588
750
|
local scope="${2:-}"
|
|
589
751
|
local project_name
|
|
752
|
+
local project_parent
|
|
753
|
+
local registered_worktrees
|
|
590
754
|
project_name=$(basename "${project_dir}")
|
|
755
|
+
project_parent=$(dirname "${project_dir}")
|
|
591
756
|
|
|
592
757
|
# Clear stale worktree registrations first. This fixes cases where a
|
|
593
758
|
# worktree directory was deleted out-of-band (for example by an agent
|
|
@@ -599,6 +764,8 @@ cleanup_worktrees() {
|
|
|
599
764
|
match_token="${scope}"
|
|
600
765
|
fi
|
|
601
766
|
|
|
767
|
+
registered_worktrees=$(git -C "${project_dir}" worktree list --porcelain 2>/dev/null || true)
|
|
768
|
+
|
|
602
769
|
git -C "${project_dir}" worktree list --porcelain 2>/dev/null \
|
|
603
770
|
| grep '^worktree ' \
|
|
604
771
|
| awk '{print $2}' \
|
|
@@ -608,6 +775,27 @@ cleanup_worktrees() {
|
|
|
608
775
|
git -C "${project_dir}" worktree remove --force "${wt}" 2>/dev/null || true
|
|
609
776
|
done || true
|
|
610
777
|
|
|
778
|
+
# Also remove stale worktree directories on disk that Git no longer tracks.
|
|
779
|
+
# These can accumulate after hard crashes or interrupted prompt-driven runs.
|
|
780
|
+
find "${project_parent}" -maxdepth 1 -mindepth 1 -type d -name "${match_token}*" -print0 2>/dev/null \
|
|
781
|
+
| while IFS= read -r -d '' wt; do
|
|
782
|
+
local wt_basename
|
|
783
|
+
wt_basename=$(basename "${wt}")
|
|
784
|
+
|
|
785
|
+
if [ "${wt}" = "${project_dir}" ]; then
|
|
786
|
+
continue
|
|
787
|
+
fi
|
|
788
|
+
if ! printf '%s\n' "${wt_basename}" | grep -Fq "${match_token}"; then
|
|
789
|
+
continue
|
|
790
|
+
fi
|
|
791
|
+
if printf '%s\n' "${registered_worktrees}" | grep -qF "worktree ${wt}"; then
|
|
792
|
+
continue
|
|
793
|
+
fi
|
|
794
|
+
|
|
795
|
+
log "CLEANUP: Removing unregistered stale worktree directory ${wt}"
|
|
796
|
+
rm -rf "${wt}" 2>/dev/null || true
|
|
797
|
+
done || true
|
|
798
|
+
|
|
611
799
|
# Prune again after removals so Git drops any admin entries left behind by
|
|
612
800
|
# force-removal or previously broken worktrees outside Night Watch naming.
|
|
613
801
|
git -C "${project_dir}" worktree prune >/dev/null 2>&1 || true
|
|
@@ -776,6 +964,19 @@ check_rate_limited() {
|
|
|
776
964
|
fi
|
|
777
965
|
}
|
|
778
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
|
+
|
|
779
980
|
# Detect context window exhaustion from Claude API logs.
|
|
780
981
|
# Usage: check_context_exhausted <log_file> [start_line]
|
|
781
982
|
# Returns 0 if context exhausted, 1 otherwise.
|
|
@@ -1079,7 +1280,7 @@ claim_or_enqueue() {
|
|
|
1079
1280
|
}
|
|
1080
1281
|
|
|
1081
1282
|
local claim_id
|
|
1082
|
-
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
|
|
1083
1284
|
NW_QUEUE_ENTRY_ID="${claim_id}"
|
|
1084
1285
|
export NW_QUEUE_ENTRY_ID
|
|
1085
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
|
-
|
|
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"
|
|
@@ -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:
|
|
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
|
-
|
|
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:
|
|
237
|
+
4) Push with: ${review_push_cmd} \
|
|
234
238
|
Work in the directory: ${review_workdir}"
|
|
235
239
|
|
|
236
240
|
local -a review_cmd_parts
|
|
@@ -172,7 +172,7 @@ Auto-merge failed: ${auto_merge_failed_summary}"
|
|
|
172
172
|
if [ -n "${final_score}" ]; then
|
|
173
173
|
details="${details}|final_score=${final_score}"
|
|
174
174
|
fi
|
|
175
|
-
log "TIMEOUT: PR reviewer
|
|
175
|
+
log "TIMEOUT: PR reviewer timed out (runtime budget ${MAX_RUNTIME}s)"
|
|
176
176
|
if [ "${WORKER_MODE}" != "1" ]; then
|
|
177
177
|
send_telegram_status_message "🔍 Night Watch Reviewer: timeout" "Project: ${PROJECT_NAME}
|
|
178
178
|
Provider (model): ${PROVIDER_MODEL_DISPLAY}
|
|
@@ -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
|
|
@@ -1048,7 +1053,10 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
1048
1053
|
|
|
1049
1054
|
ATTEMPT_TIMEOUT="${MAX_RUNTIME}"
|
|
1050
1055
|
if [ -n "${TARGET_PR}" ]; then
|
|
1051
|
-
#
|
|
1056
|
+
# Give each targeted attempt the full remaining runtime budget.
|
|
1057
|
+
# Retries only happen after a quick return (low score / invalid output / rate limit);
|
|
1058
|
+
# a timed-out provider run is not retried, so pre-splitting the budget would
|
|
1059
|
+
# incorrectly cap a 1h review to ~20m on attempt 1.
|
|
1052
1060
|
NOW_TS=$(date +%s)
|
|
1053
1061
|
ELAPSED=$((NOW_TS - RUN_STARTED_AT))
|
|
1054
1062
|
REMAINING_BUDGET=$((MAX_RUNTIME - ELAPSED))
|
|
@@ -1057,12 +1065,7 @@ for ATTEMPT in $(seq 1 "${TOTAL_ATTEMPTS}"); do
|
|
|
1057
1065
|
log "RETRY: Runtime budget exhausted before attempt ${ATTEMPT}"
|
|
1058
1066
|
break
|
|
1059
1067
|
fi
|
|
1060
|
-
|
|
1061
|
-
REMAINING_ATTEMPTS=$((TOTAL_ATTEMPTS - ATTEMPT + 1))
|
|
1062
|
-
ATTEMPT_TIMEOUT=$((REMAINING_BUDGET / REMAINING_ATTEMPTS))
|
|
1063
|
-
if [ "${ATTEMPT_TIMEOUT}" -lt 1 ]; then
|
|
1064
|
-
ATTEMPT_TIMEOUT=1
|
|
1065
|
-
fi
|
|
1068
|
+
ATTEMPT_TIMEOUT="${REMAINING_BUDGET}"
|
|
1066
1069
|
fi
|
|
1067
1070
|
|
|
1068
1071
|
# Recreate worktree if it was removed unexpectedly between attempts.
|
|
@@ -313,7 +313,7 @@ classify_qa_comment_outcome() {
|
|
|
313
313
|
pr_has_qa_generated_files() {
|
|
314
314
|
local pr_number="${1:?PR number required}"
|
|
315
315
|
gh pr view "${pr_number}" --json files --jq '.files[]?.path' 2>/dev/null \
|
|
316
|
-
| grep -Eq '^(qa-artifacts/|tests/.*/qa
|
|
316
|
+
| grep -Eq '^(qa-artifacts/|tests/.*/qa/|(.*/)?(__tests__|tests?|spec|specs|e2e|playwright)/.*\.(test|spec)\.[[:alnum:]]+$|(.*/)?[^/]+\.(test|spec)\.[[:alnum:]]+$)'
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
provider_output_looks_invalid() {
|
|
@@ -341,7 +341,7 @@ validate_qa_evidence() {
|
|
|
341
341
|
fi
|
|
342
342
|
|
|
343
343
|
if ! pr_has_qa_generated_files "${pr_number}"; then
|
|
344
|
-
log "WARN-QA-EVIDENCE: PR #${pr_number} has QA marker comment but no qa-artifacts/ or
|
|
344
|
+
log "WARN-QA-EVIDENCE: PR #${pr_number} has QA marker comment but no qa-artifacts/ or generated test files in standard locations"
|
|
345
345
|
return 2
|
|
346
346
|
fi
|
|
347
347
|
|
|
@@ -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"
|
|
@@ -617,7 +622,7 @@ for pr_ref in ${PRS_NEEDING_QA}; do
|
|
|
617
622
|
fi
|
|
618
623
|
if [ ${QA_EVIDENCE_STATUS} -eq 2 ]; then
|
|
619
624
|
WARNING_PRS_CSV=$(append_csv "${WARNING_PRS_CSV}" "#${pr_num}")
|
|
620
|
-
QA_WARNING_SUMMARY="${QA_WARNING_SUMMARY}${QA_WARNING_SUMMARY:+$'\n'}#${pr_num}: no qa-artifacts/ or
|
|
625
|
+
QA_WARNING_SUMMARY="${QA_WARNING_SUMMARY}${QA_WARNING_SUMMARY:+$'\n'}#${pr_num}: no qa-artifacts/ or generated test files in standard locations"
|
|
621
626
|
log "QA: PR #${pr_num} — provider completed with warning-only QA evidence"
|
|
622
627
|
elif [ ${QA_EVIDENCE_STATUS} -ne 0 ]; then
|
|
623
628
|
FAILED_AUTOMATION_PRS_CSV=$(append_csv "${FAILED_AUTOMATION_PRS_CSV}" "#${pr_num}")
|
|
@@ -75,7 +75,7 @@ cleanup_on_exit() {
|
|
|
75
75
|
rm -f "${LOCK_FILE}"
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
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}
|