@jonit-dev/night-watch-cli 1.7.2 → 1.7.5
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/board/factory.d.ts +3 -0
- package/dist/board/factory.d.ts.map +1 -0
- package/dist/board/factory.js +10 -0
- package/dist/board/factory.js.map +1 -0
- package/dist/board/providers/github-graphql.d.ts +16 -0
- package/dist/board/providers/github-graphql.d.ts.map +1 -0
- package/dist/board/providers/github-graphql.js +43 -0
- package/dist/board/providers/github-graphql.js.map +1 -0
- package/dist/board/providers/github-projects.d.ts +33 -0
- package/dist/board/providers/github-projects.d.ts.map +1 -0
- package/dist/board/providers/github-projects.js +443 -0
- package/dist/board/providers/github-projects.js.map +1 -0
- package/dist/board/types.d.ts +59 -0
- package/dist/board/types.d.ts.map +1 -0
- package/dist/board/types.js +4 -0
- package/dist/board/types.js.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/board.d.ts +9 -0
- package/dist/commands/board.d.ts.map +1 -0
- package/dist/commands/board.js +254 -0
- package/dist/commands/board.js.map +1 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +6 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/night-watch-cron.sh +112 -18
- package/scripts/night-watch-helpers.sh +20 -0
|
@@ -69,20 +69,52 @@ fi
|
|
|
69
69
|
|
|
70
70
|
cleanup_worktrees "${PROJECT_DIR}"
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
ISSUE_NUMBER="" # board mode: GitHub issue number
|
|
73
|
+
ISSUE_BODY="" # board mode: issue body (PRD content)
|
|
74
|
+
ISSUE_TITLE_RAW="" # board mode: issue title
|
|
75
|
+
NW_CLI="" # board mode: resolved night-watch CLI binary
|
|
76
|
+
|
|
77
|
+
if [ "${NW_BOARD_ENABLED:-}" = "true" ]; then
|
|
78
|
+
# Board mode: discover next task from GitHub Projects board
|
|
79
|
+
NW_CLI=$(resolve_night_watch_cli 2>/dev/null || true)
|
|
80
|
+
if [ -z "${NW_CLI}" ]; then
|
|
81
|
+
log "ERROR: Cannot resolve night-watch CLI for board mode"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
ISSUE_JSON=$(find_eligible_board_issue)
|
|
85
|
+
if [ -z "${ISSUE_JSON}" ]; then
|
|
86
|
+
log "SKIP: No eligible issues in Ready column (board mode)"
|
|
87
|
+
emit_result "skip_no_eligible_prd"
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
ISSUE_NUMBER=$(printf '%s' "${ISSUE_JSON}" | jq -r '.number // empty' 2>/dev/null || true)
|
|
91
|
+
ISSUE_TITLE_RAW=$(printf '%s' "${ISSUE_JSON}" | jq -r '.title // empty' 2>/dev/null || true)
|
|
92
|
+
ISSUE_BODY=$(printf '%s' "${ISSUE_JSON}" | jq -r '.body // empty' 2>/dev/null || true)
|
|
93
|
+
if [ -z "${ISSUE_NUMBER}" ]; then
|
|
94
|
+
log "ERROR: Board mode: failed to parse issue number from JSON"
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
# Slugify title for branch naming
|
|
98
|
+
ELIGIBLE_PRD="${ISSUE_NUMBER}-$(printf '%s' "${ISSUE_TITLE_RAW}" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-\|-$//g')"
|
|
99
|
+
log "BOARD: Found ready issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}"
|
|
100
|
+
# Move issue to In Progress (claim it on the board)
|
|
101
|
+
"${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "In Progress" 2>>"${LOG_FILE}" || \
|
|
102
|
+
log "WARN: Failed to move issue #${ISSUE_NUMBER} to In Progress"
|
|
103
|
+
trap "rm -f '${LOCK_FILE}'" EXIT
|
|
104
|
+
else
|
|
105
|
+
# Filesystem mode: scan PRD directory
|
|
106
|
+
ELIGIBLE_PRD=$(find_eligible_prd "${PRD_DIR}" "${MAX_RUNTIME}" "${PROJECT_DIR}")
|
|
107
|
+
if [ -z "${ELIGIBLE_PRD}" ]; then
|
|
108
|
+
log "SKIP: No eligible PRDs (all done, in-progress, or blocked)"
|
|
109
|
+
emit_result "skip_no_eligible_prd"
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
# Claim the PRD to prevent other runs from selecting it
|
|
113
|
+
claim_prd "${PRD_DIR}" "${ELIGIBLE_PRD}"
|
|
114
|
+
# Update EXIT trap to also release claim
|
|
115
|
+
trap "rm -f '${LOCK_FILE}'; release_claim '${PRD_DIR}' '${ELIGIBLE_PRD}'" EXIT
|
|
78
116
|
fi
|
|
79
117
|
|
|
80
|
-
# Claim the PRD to prevent other runs from selecting it
|
|
81
|
-
claim_prd "${PRD_DIR}" "${ELIGIBLE_PRD}"
|
|
82
|
-
|
|
83
|
-
# Update EXIT trap to also release claim
|
|
84
|
-
trap "rm -f '${LOCK_FILE}'; release_claim '${PRD_DIR}' '${ELIGIBLE_PRD}'" EXIT
|
|
85
|
-
|
|
86
118
|
PRD_NAME="${ELIGIBLE_PRD%.md}"
|
|
87
119
|
BRANCH_NAME="${BRANCH_PREFIX}/${PRD_NAME}"
|
|
88
120
|
WORKTREE_DIR="$(dirname "${PROJECT_DIR}")/${PROJECT_NAME}-nw-${PRD_NAME}"
|
|
@@ -140,11 +172,40 @@ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>" || true
|
|
|
140
172
|
return 1
|
|
141
173
|
}
|
|
142
174
|
|
|
143
|
-
PROMPT_PRD_PATH="${PRD_DIR_REL}/${ELIGIBLE_PRD}"
|
|
144
|
-
|
|
145
175
|
log "START: Processing ${ELIGIBLE_PRD} on branch ${BRANCH_NAME} (worktree: ${WORKTREE_DIR})"
|
|
146
176
|
|
|
147
|
-
|
|
177
|
+
if [ -n "${ISSUE_NUMBER}" ]; then
|
|
178
|
+
PROMPT="Implement the following PRD (GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}):
|
|
179
|
+
|
|
180
|
+
${ISSUE_BODY}
|
|
181
|
+
|
|
182
|
+
## Setup
|
|
183
|
+
- You are already inside an isolated worktree at: ${WORKTREE_DIR}
|
|
184
|
+
- Current branch is already checked out: ${BRANCH_NAME}
|
|
185
|
+
- Do NOT run git checkout/switch in ${PROJECT_DIR}
|
|
186
|
+
- Do NOT create or remove worktrees; the cron script manages that
|
|
187
|
+
- Install dependencies if needed and implement in the current worktree only
|
|
188
|
+
|
|
189
|
+
## Implementation — PRD Executor Workflow
|
|
190
|
+
Read .claude/commands/prd-executor.md and follow its FULL execution pipeline:
|
|
191
|
+
1. Parse the PRD into phases and extract dependencies
|
|
192
|
+
2. Build a dependency graph to identify parallelism
|
|
193
|
+
3. Create a task list with one task per phase
|
|
194
|
+
4. Execute phases in parallel waves using agent swarms — launch ALL independent phases concurrently
|
|
195
|
+
5. Run the project's verify/test command between waves to catch issues early
|
|
196
|
+
6. After all phases complete, run final verification and fix any issues
|
|
197
|
+
Follow all CLAUDE.md conventions (if present).
|
|
198
|
+
|
|
199
|
+
## Finalize
|
|
200
|
+
- Commit all changes, push, and open a PR:
|
|
201
|
+
git push -u origin ${BRANCH_NAME}
|
|
202
|
+
gh pr create --title \"feat: <short title>\" --body \"Closes #${ISSUE_NUMBER}
|
|
203
|
+
|
|
204
|
+
<summary>\"
|
|
205
|
+
- Do NOT process any other issues — only issue #${ISSUE_NUMBER}"
|
|
206
|
+
else
|
|
207
|
+
PROMPT_PRD_PATH="${PRD_DIR_REL}/${ELIGIBLE_PRD}"
|
|
208
|
+
PROMPT="Implement the PRD at ${PROMPT_PRD_PATH}
|
|
148
209
|
|
|
149
210
|
## Setup
|
|
150
211
|
- You are already inside an isolated worktree at: ${WORKTREE_DIR}
|
|
@@ -169,6 +230,7 @@ Follow all CLAUDE.md conventions (if present).
|
|
|
169
230
|
gh pr create --title \"feat: <short title>\" --body \"<summary referencing PRD>\"
|
|
170
231
|
- Do NOT move the PRD to done/ — the cron script handles that
|
|
171
232
|
- Do NOT process any other PRDs — only ${ELIGIBLE_PRD}"
|
|
233
|
+
fi
|
|
172
234
|
|
|
173
235
|
# Dry-run mode: print diagnostics and exit
|
|
174
236
|
if [ "${NW_DRY_RUN:-0}" = "1" ]; then
|
|
@@ -189,7 +251,12 @@ fi
|
|
|
189
251
|
MERGED_PR_COUNT=$(count_prs_for_branch merged "${BRANCH_NAME}")
|
|
190
252
|
if [ "${MERGED_PR_COUNT}" -gt 0 ]; then
|
|
191
253
|
log "INFO: Found merged PR for ${BRANCH_NAME}; skipping provider run"
|
|
192
|
-
if
|
|
254
|
+
if [ -n "${ISSUE_NUMBER}" ]; then
|
|
255
|
+
# Board mode: move issue to Done
|
|
256
|
+
"${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Done" 2>>"${LOG_FILE}" || true
|
|
257
|
+
emit_result "success_already_merged" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
258
|
+
exit 0
|
|
259
|
+
elif finalize_prd_done "already merged on ${BRANCH_NAME}"; then
|
|
193
260
|
emit_result "success_already_merged" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
194
261
|
exit 0
|
|
195
262
|
fi
|
|
@@ -276,7 +343,16 @@ done
|
|
|
276
343
|
if [ ${EXIT_CODE} -eq 0 ]; then
|
|
277
344
|
OPEN_PR_COUNT=$(count_prs_for_branch open "${BRANCH_NAME}")
|
|
278
345
|
if [ "${OPEN_PR_COUNT}" -gt 0 ]; then
|
|
279
|
-
if
|
|
346
|
+
if [ -n "${ISSUE_NUMBER}" ]; then
|
|
347
|
+
# Board mode: move to Review and comment with PR URL
|
|
348
|
+
PR_URL=$(gh pr list --state open --json headRefName,url \
|
|
349
|
+
--jq ".[] | select(.headRefName == \"${BRANCH_NAME}\") | .url" 2>/dev/null || true)
|
|
350
|
+
"${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Review" 2>>"${LOG_FILE}" || true
|
|
351
|
+
if [ -n "${PR_URL}" ]; then
|
|
352
|
+
"${NW_CLI}" board comment "${ISSUE_NUMBER}" --body "PR opened: ${PR_URL}" 2>>"${LOG_FILE}" || true
|
|
353
|
+
fi
|
|
354
|
+
emit_result "success_open_pr" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
355
|
+
elif finalize_prd_done "implemented, PR opened on ${BRANCH_NAME}"; then
|
|
280
356
|
emit_result "success_open_pr" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
281
357
|
else
|
|
282
358
|
night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
|
|
@@ -286,7 +362,10 @@ if [ ${EXIT_CODE} -eq 0 ]; then
|
|
|
286
362
|
else
|
|
287
363
|
MERGED_PR_COUNT=$(count_prs_for_branch merged "${BRANCH_NAME}")
|
|
288
364
|
if [ "${MERGED_PR_COUNT}" -gt 0 ]; then
|
|
289
|
-
if
|
|
365
|
+
if [ -n "${ISSUE_NUMBER}" ]; then
|
|
366
|
+
"${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Done" 2>>"${LOG_FILE}" || true
|
|
367
|
+
emit_result "success_already_merged" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
368
|
+
elif finalize_prd_done "already merged on ${BRANCH_NAME}"; then
|
|
290
369
|
emit_result "success_already_merged" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
291
370
|
else
|
|
292
371
|
night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
|
|
@@ -295,6 +374,11 @@ if [ ${EXIT_CODE} -eq 0 ]; then
|
|
|
295
374
|
fi
|
|
296
375
|
else
|
|
297
376
|
log "WARN: ${PROVIDER_CMD} exited 0 but no open/merged PR found on ${BRANCH_NAME} — recording cooldown to avoid repeated stuck runs"
|
|
377
|
+
if [ -n "${ISSUE_NUMBER}" ]; then
|
|
378
|
+
"${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
|
|
379
|
+
"${NW_CLI}" board comment "${ISSUE_NUMBER}" \
|
|
380
|
+
--body "Execution completed but no PR was found. Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
|
|
381
|
+
fi
|
|
298
382
|
night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
|
|
299
383
|
emit_result "failure_no_pr_after_success" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
300
384
|
EXIT_CODE=1
|
|
@@ -302,10 +386,20 @@ if [ ${EXIT_CODE} -eq 0 ]; then
|
|
|
302
386
|
fi
|
|
303
387
|
elif [ ${EXIT_CODE} -eq 124 ]; then
|
|
304
388
|
log "TIMEOUT: Night watch killed after ${MAX_RUNTIME}s while processing ${ELIGIBLE_PRD}"
|
|
389
|
+
if [ -n "${ISSUE_NUMBER}" ]; then
|
|
390
|
+
"${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
|
|
391
|
+
"${NW_CLI}" board comment "${ISSUE_NUMBER}" \
|
|
392
|
+
--body "Execution timed out after ${MAX_RUNTIME}s. Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
|
|
393
|
+
fi
|
|
305
394
|
night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" timeout --exit-code 124 2>/dev/null || true
|
|
306
395
|
emit_result "timeout" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
307
396
|
else
|
|
308
397
|
log "FAIL: Night watch exited with code ${EXIT_CODE} while processing ${ELIGIBLE_PRD}"
|
|
398
|
+
if [ -n "${ISSUE_NUMBER}" ]; then
|
|
399
|
+
"${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
|
|
400
|
+
"${NW_CLI}" board comment "${ISSUE_NUMBER}" \
|
|
401
|
+
--body "Execution failed with exit code ${EXIT_CODE}. Moved back to Ready for retry." 2>>"${LOG_FILE}" || true
|
|
402
|
+
fi
|
|
309
403
|
night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code "${EXIT_CODE}" 2>/dev/null || true
|
|
310
404
|
emit_result "failure" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}"
|
|
311
405
|
fi
|
|
@@ -412,3 +412,23 @@ check_rate_limited() {
|
|
|
412
412
|
local log_file="${1:?log_file required}"
|
|
413
413
|
tail -20 "${log_file}" 2>/dev/null | grep -q "429"
|
|
414
414
|
}
|
|
415
|
+
|
|
416
|
+
# ── Board mode issue discovery ────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
# Get the next eligible issue from the board provider.
|
|
419
|
+
# Prints the JSON of the first "Ready" issue to stdout, or nothing if none found.
|
|
420
|
+
# Returns 0 on success, 1 if no issue found or CLI unavailable.
|
|
421
|
+
find_eligible_board_issue() {
|
|
422
|
+
local cli_bin
|
|
423
|
+
cli_bin=$(resolve_night_watch_cli) || {
|
|
424
|
+
log "WARN: Cannot find night-watch CLI for board mode"
|
|
425
|
+
return 1
|
|
426
|
+
}
|
|
427
|
+
local result
|
|
428
|
+
result=$("${cli_bin}" board next-issue --column "Ready" --json 2>/dev/null) || true
|
|
429
|
+
if [ -z "${result}" ]; then
|
|
430
|
+
return 1
|
|
431
|
+
fi
|
|
432
|
+
printf '%s' "${result}"
|
|
433
|
+
return 0
|
|
434
|
+
}
|