@jonit-dev/night-watch-cli 1.7.2 → 1.7.4

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.
@@ -69,20 +69,52 @@ fi
69
69
 
70
70
  cleanup_worktrees "${PROJECT_DIR}"
71
71
 
72
- ELIGIBLE_PRD=$(find_eligible_prd "${PRD_DIR}" "${MAX_RUNTIME}" "${PROJECT_DIR}")
73
-
74
- if [ -z "${ELIGIBLE_PRD}" ]; then
75
- log "SKIP: No eligible PRDs (all done, in-progress, or blocked)"
76
- emit_result "skip_no_eligible_prd"
77
- exit 0
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
- PROMPT="Implement the PRD at ${PROMPT_PRD_PATH}
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 finalize_prd_done "already merged on ${BRANCH_NAME}"; then
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 finalize_prd_done "implemented, PR opened on ${BRANCH_NAME}"; then
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 finalize_prd_done "already merged on ${BRANCH_NAME}"; then
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
+ }