@jonit-dev/night-watch-cli 1.5.0 → 1.5.2

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 (65) hide show
  1. package/dist/cli.js +6 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/cancel.d.ts +46 -0
  4. package/dist/commands/cancel.d.ts.map +1 -0
  5. package/dist/commands/cancel.js +239 -0
  6. package/dist/commands/cancel.js.map +1 -0
  7. package/dist/commands/doctor.d.ts +1 -1
  8. package/dist/commands/doctor.d.ts.map +1 -1
  9. package/dist/commands/doctor.js +101 -58
  10. package/dist/commands/doctor.js.map +1 -1
  11. package/dist/commands/history.d.ts +7 -0
  12. package/dist/commands/history.d.ts.map +1 -0
  13. package/dist/commands/history.js +56 -0
  14. package/dist/commands/history.js.map +1 -0
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +11 -73
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/commands/install.d.ts +11 -0
  19. package/dist/commands/install.d.ts.map +1 -1
  20. package/dist/commands/install.js +47 -11
  21. package/dist/commands/install.js.map +1 -1
  22. package/dist/commands/prds.d.ts +13 -0
  23. package/dist/commands/prds.d.ts.map +1 -0
  24. package/dist/commands/prds.js +192 -0
  25. package/dist/commands/prds.js.map +1 -0
  26. package/dist/commands/prs.d.ts +13 -0
  27. package/dist/commands/prs.d.ts.map +1 -0
  28. package/dist/commands/prs.js +101 -0
  29. package/dist/commands/prs.js.map +1 -0
  30. package/dist/commands/retry.d.ts +9 -0
  31. package/dist/commands/retry.d.ts.map +1 -0
  32. package/dist/commands/retry.js +72 -0
  33. package/dist/commands/retry.js.map +1 -0
  34. package/dist/commands/review.d.ts.map +1 -1
  35. package/dist/commands/review.js +6 -0
  36. package/dist/commands/review.js.map +1 -1
  37. package/dist/commands/run.d.ts.map +1 -1
  38. package/dist/commands/run.js +11 -0
  39. package/dist/commands/run.js.map +1 -1
  40. package/dist/commands/update.d.ts +21 -0
  41. package/dist/commands/update.d.ts.map +1 -0
  42. package/dist/commands/update.js +87 -0
  43. package/dist/commands/update.js.map +1 -0
  44. package/dist/config.d.ts.map +1 -1
  45. package/dist/config.js +36 -1
  46. package/dist/config.js.map +1 -1
  47. package/dist/constants.d.ts +4 -0
  48. package/dist/constants.d.ts.map +1 -1
  49. package/dist/constants.js +7 -0
  50. package/dist/constants.js.map +1 -1
  51. package/dist/types.d.ts +4 -0
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/utils/crontab.d.ts.map +1 -1
  54. package/dist/utils/crontab.js +4 -0
  55. package/dist/utils/crontab.js.map +1 -1
  56. package/dist/utils/execution-history.d.ts +48 -0
  57. package/dist/utils/execution-history.d.ts.map +1 -0
  58. package/dist/utils/execution-history.js +183 -0
  59. package/dist/utils/execution-history.js.map +1 -0
  60. package/package.json +1 -1
  61. package/scripts/night-watch-cron.sh +142 -55
  62. package/scripts/night-watch-helpers.sh +189 -1
  63. package/scripts/night-watch-pr-reviewer-cron.sh +64 -15
  64. package/templates/night-watch-pr-reviewer.md +7 -11
  65. package/templates/night-watch.md +9 -20
@@ -19,6 +19,44 @@ validate_provider() {
19
19
  esac
20
20
  }
21
21
 
22
+ # Resolve a usable night-watch CLI binary for nested script calls.
23
+ # Resolution order:
24
+ # 1) NW_CLI_BIN from parent environment (absolute path set by installer/runtime)
25
+ # 2) `night-watch` found in PATH
26
+ # 3) bundled bin path next to scripts/ in this package checkout/install
27
+ resolve_night_watch_cli() {
28
+ if [ -n "${NW_CLI_BIN:-}" ] && [ -x "${NW_CLI_BIN}" ]; then
29
+ printf "%s" "${NW_CLI_BIN}"
30
+ return 0
31
+ fi
32
+
33
+ if command -v night-watch >/dev/null 2>&1; then
34
+ printf "%s" "night-watch"
35
+ return 0
36
+ fi
37
+
38
+ local script_dir
39
+ if [ -n "${SCRIPT_DIR:-}" ]; then
40
+ script_dir="${SCRIPT_DIR}"
41
+ else
42
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
43
+ fi
44
+
45
+ local bundled_bin="${script_dir}/../bin/night-watch.mjs"
46
+ if [ -x "${bundled_bin}" ]; then
47
+ printf "%s" "${bundled_bin}"
48
+ return 0
49
+ fi
50
+
51
+ return 1
52
+ }
53
+
54
+ night_watch_history() {
55
+ local cli_bin
56
+ cli_bin=$(resolve_night_watch_cli) || return 127
57
+ "${cli_bin}" history "$@"
58
+ }
59
+
22
60
  # ── Logging ──────────────────────────────────────────────────────────────────
23
61
 
24
62
  log() {
@@ -167,6 +205,7 @@ is_claimed() {
167
205
  find_eligible_prd() {
168
206
  local prd_dir="${1:?prd_dir required}"
169
207
  local max_runtime="${2:-7200}"
208
+ local project_dir="${3:-}"
170
209
  local done_dir="${prd_dir}/done"
171
210
 
172
211
  local prd_files
@@ -210,6 +249,12 @@ find_eligible_prd() {
210
249
  continue
211
250
  fi
212
251
 
252
+ # Skip if in cooldown after a recent failure (checked via execution history ledger)
253
+ if [ -n "${project_dir}" ] && night_watch_history check "${project_dir}" "${prd_file}" --cooldown "${max_runtime}" 2>/dev/null; then
254
+ log "SKIP-PRD: ${prd_file} — in cooldown after recent failure"
255
+ continue
256
+ fi
257
+
213
258
  # Skip if a PR already exists for this PRD
214
259
  if echo "${open_branches}" | grep -qF "${prd_name}"; then
215
260
  log "SKIP-PRD: ${prd_file} — open PR already exists"
@@ -247,17 +292,151 @@ cleanup_worktrees() {
247
292
  local project_dir="${1:?project_dir required}"
248
293
  local project_name
249
294
  project_name=$(basename "${project_dir}")
295
+ local marker="${2:-${project_name}-nw}"
250
296
 
251
297
  git -C "${project_dir}" worktree list --porcelain 2>/dev/null \
252
298
  | grep '^worktree ' \
253
299
  | awk '{print $2}' \
254
- | grep "${project_name}-nw" \
300
+ | grep "${marker}" \
255
301
  | while read -r wt; do
256
302
  log "CLEANUP: Removing leftover worktree ${wt}"
257
303
  git -C "${project_dir}" worktree remove --force "${wt}" 2>/dev/null || true
258
304
  done || true
259
305
  }
260
306
 
307
+ # ── Runtime workspace isolation ───────────────────────────────────────────────
308
+
309
+ project_runtime_key() {
310
+ local project_dir="${1:?project_dir required}"
311
+ local project_name
312
+ local hash
313
+ project_name=$(basename "${project_dir}")
314
+
315
+ if command -v sha1sum >/dev/null 2>&1; then
316
+ hash=$(printf "%s" "${project_dir}" | sha1sum | awk '{print $1}')
317
+ elif command -v shasum >/dev/null 2>&1; then
318
+ hash=$(printf "%s" "${project_dir}" | shasum | awk '{print $1}')
319
+ else
320
+ hash=$(printf "%s" "${project_dir}" | cksum | awk '{print $1}')
321
+ fi
322
+
323
+ printf "%s-%s" "${project_name}" "${hash:0:12}"
324
+ }
325
+
326
+ resolve_runtime_base_ref() {
327
+ local git_dir="${1:?git_dir required}"
328
+ local default_branch="${2:?default_branch required}"
329
+
330
+ if git -C "${git_dir}" rev-parse --verify --quiet "refs/remotes/origin/${default_branch}" >/dev/null; then
331
+ printf "%s" "refs/remotes/origin/${default_branch}"
332
+ return 0
333
+ fi
334
+
335
+ if git -C "${git_dir}" rev-parse --verify --quiet "refs/heads/${default_branch}" >/dev/null; then
336
+ printf "%s" "refs/heads/${default_branch}"
337
+ return 0
338
+ fi
339
+
340
+ if git -C "${git_dir}" rev-parse --verify --quiet "refs/remotes/origin/HEAD" >/dev/null; then
341
+ printf "%s" "refs/remotes/origin/HEAD"
342
+ return 0
343
+ fi
344
+
345
+ return 1
346
+ }
347
+
348
+ prepare_runtime_workspace() {
349
+ local project_dir="${1:?project_dir required}"
350
+ local default_branch="${2:?default_branch required}"
351
+ local log_file="${3:-${LOG_FILE:-/dev/null}}"
352
+ local runtime_root="${NW_RUNTIME_ROOT:-${HOME}/.night-watch/runtime}"
353
+ local runtime_key
354
+ local runtime_base
355
+ local mirror_dir
356
+ local runs_dir
357
+ local worktree_dir
358
+ local clone_source=""
359
+ local base_ref=""
360
+
361
+ runtime_key=$(project_runtime_key "${project_dir}")
362
+ runtime_base="${runtime_root}/${runtime_key}"
363
+ mirror_dir="${runtime_base}/mirror.git"
364
+ runs_dir="${runtime_base}/runs"
365
+ worktree_dir="${runs_dir}/run-$(date +%Y%m%d-%H%M%S)-$$"
366
+
367
+ mkdir -p "${runs_dir}"
368
+
369
+ if [ ! -d "${mirror_dir}" ]; then
370
+ clone_source=$(git -C "${project_dir}" config --get remote.origin.url 2>/dev/null || echo "")
371
+
372
+ if [ -n "${clone_source}" ]; then
373
+ if ! git clone --mirror "${clone_source}" "${mirror_dir}" >> "${log_file}" 2>&1; then
374
+ git clone --mirror "${project_dir}" "${mirror_dir}" >> "${log_file}" 2>&1
375
+ fi
376
+ else
377
+ git clone --mirror "${project_dir}" "${mirror_dir}" >> "${log_file}" 2>&1
378
+ fi
379
+ fi
380
+
381
+ git -C "${mirror_dir}" remote update --prune >> "${log_file}" 2>&1 || true
382
+
383
+ base_ref=$(resolve_runtime_base_ref "${mirror_dir}" "${default_branch}") || return 1
384
+ git -C "${mirror_dir}" worktree add --detach "${worktree_dir}" "${base_ref}" >> "${log_file}" 2>&1
385
+
386
+ printf "%s\n%s\n" "${mirror_dir}" "${worktree_dir}"
387
+ }
388
+
389
+ cleanup_runtime_workspace() {
390
+ local mirror_dir="${1:?mirror_dir required}"
391
+ local worktree_dir="${2:?worktree_dir required}"
392
+
393
+ git -C "${mirror_dir}" worktree remove --force "${worktree_dir}" 2>/dev/null || true
394
+ git -C "${mirror_dir}" worktree prune 2>/dev/null || true
395
+ }
396
+
397
+ prepare_branch_checkout() {
398
+ local repo_dir="${1:?repo_dir required}"
399
+ local branch_name="${2:?branch_name required}"
400
+ local default_branch="${3:?default_branch required}"
401
+ local log_file="${4:-${LOG_FILE:-/dev/null}}"
402
+ local base_ref=""
403
+
404
+ git -C "${repo_dir}" fetch origin "${default_branch}" "${branch_name}" >> "${log_file}" 2>&1 || true
405
+
406
+ if git -C "${repo_dir}" rev-parse --verify --quiet "refs/heads/${branch_name}" >/dev/null; then
407
+ git -C "${repo_dir}" checkout "${branch_name}" >> "${log_file}" 2>&1
408
+ return $?
409
+ fi
410
+
411
+ if git -C "${repo_dir}" rev-parse --verify --quiet "refs/remotes/origin/${branch_name}" >/dev/null; then
412
+ git -C "${repo_dir}" checkout -b "${branch_name}" "origin/${branch_name}" >> "${log_file}" 2>&1
413
+ return $?
414
+ fi
415
+
416
+ base_ref=$(resolve_runtime_base_ref "${repo_dir}" "${default_branch}") || return 1
417
+ git -C "${repo_dir}" checkout -b "${branch_name}" "${base_ref}" >> "${log_file}" 2>&1
418
+ }
419
+
420
+ checkout_default_branch() {
421
+ local repo_dir="${1:?repo_dir required}"
422
+ local default_branch="${2:?default_branch required}"
423
+ local log_file="${3:-${LOG_FILE:-/dev/null}}"
424
+
425
+ git -C "${repo_dir}" fetch origin "${default_branch}" >> "${log_file}" 2>&1 || true
426
+
427
+ if git -C "${repo_dir}" rev-parse --verify --quiet "refs/remotes/origin/${default_branch}" >/dev/null; then
428
+ git -C "${repo_dir}" checkout -B "${default_branch}" "origin/${default_branch}" >> "${log_file}" 2>&1
429
+ return $?
430
+ fi
431
+
432
+ if git -C "${repo_dir}" rev-parse --verify --quiet "refs/heads/${default_branch}" >/dev/null; then
433
+ git -C "${repo_dir}" checkout "${default_branch}" >> "${log_file}" 2>&1
434
+ return $?
435
+ fi
436
+
437
+ git -C "${repo_dir}" checkout -B "${default_branch}" HEAD >> "${log_file}" 2>&1
438
+ }
439
+
261
440
  # ── Mark PRD as done ─────────────────────────────────────────────────────────
262
441
 
263
442
  mark_prd_done() {
@@ -276,3 +455,12 @@ mark_prd_done() {
276
455
  return 1
277
456
  fi
278
457
  }
458
+
459
+ # ── Rate limit detection ────────────────────────────────────────────────────
460
+
461
+ # Check if the last N lines of the log contain a 429 rate limit error.
462
+ # Returns 0 if rate limited, 1 otherwise.
463
+ check_rate_limited() {
464
+ local log_file="${1:?log_file required}"
465
+ tail -20 "${log_file}" 2>/dev/null | grep -q "429"
466
+ }
@@ -15,12 +15,14 @@ PROJECT_DIR="${1:?Usage: $0 /path/to/project}"
15
15
  PROJECT_NAME=$(basename "${PROJECT_DIR}")
16
16
  LOG_DIR="${PROJECT_DIR}/logs"
17
17
  LOG_FILE="${LOG_DIR}/night-watch-pr-reviewer.log"
18
- LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_NAME}.lock"
18
+ LOCK_FILE=""
19
19
  MAX_RUNTIME="${NW_REVIEWER_MAX_RUNTIME:-3600}" # 1 hour
20
20
  MAX_LOG_SIZE="524288" # 512 KB
21
21
  PROVIDER_CMD="${NW_PROVIDER_CMD:-claude}"
22
22
  MIN_REVIEW_SCORE="${NW_MIN_REVIEW_SCORE:-80}"
23
23
  BRANCH_PATTERNS_RAW="${NW_BRANCH_PATTERNS:-feat/,night-watch/}"
24
+ RUNTIME_MIRROR_DIR=""
25
+ RUNTIME_PROJECT_DIR=""
24
26
 
25
27
  # Ensure NVM / Node / Claude are on PATH
26
28
  export NVM_DIR="${HOME}/.nvm"
@@ -34,6 +36,8 @@ mkdir -p "${LOG_DIR}"
34
36
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
35
37
  # shellcheck source=night-watch-helpers.sh
36
38
  source "${SCRIPT_DIR}/night-watch-helpers.sh"
39
+ PROJECT_RUNTIME_KEY=$(project_runtime_key "${PROJECT_DIR}")
40
+ LOCK_FILE="/tmp/night-watch-pr-reviewer-${PROJECT_RUNTIME_KEY}.lock"
37
41
 
38
42
  # Validate provider
39
43
  if ! validate_provider "${PROVIDER_CMD}"; then
@@ -47,7 +51,37 @@ if ! acquire_lock "${LOCK_FILE}"; then
47
51
  exit 0
48
52
  fi
49
53
 
50
- cd "${PROJECT_DIR}"
54
+ cleanup_on_exit() {
55
+ rm -f "${LOCK_FILE}"
56
+
57
+ if [ -n "${RUNTIME_MIRROR_DIR}" ] && [ -n "${RUNTIME_PROJECT_DIR}" ]; then
58
+ cleanup_runtime_workspace "${RUNTIME_MIRROR_DIR}" "${RUNTIME_PROJECT_DIR}" || true
59
+ fi
60
+ }
61
+
62
+ trap cleanup_on_exit EXIT
63
+
64
+ if [ -n "${NW_DEFAULT_BRANCH:-}" ]; then
65
+ DEFAULT_BRANCH="${NW_DEFAULT_BRANCH}"
66
+ else
67
+ DEFAULT_BRANCH=$(detect_default_branch "${PROJECT_DIR}")
68
+ fi
69
+
70
+ runtime_info=()
71
+ if mapfile -t runtime_info < <(prepare_runtime_workspace "${PROJECT_DIR}" "${DEFAULT_BRANCH}" "${LOG_FILE}"); then
72
+ RUNTIME_MIRROR_DIR="${runtime_info[0]:-}"
73
+ RUNTIME_PROJECT_DIR="${runtime_info[1]:-}"
74
+ else
75
+ log "FAIL: Could not prepare runtime workspace for reviewer"
76
+ exit 1
77
+ fi
78
+
79
+ if [ -z "${RUNTIME_MIRROR_DIR}" ] || [ -z "${RUNTIME_PROJECT_DIR}" ]; then
80
+ log "FAIL: Runtime workspace paths are missing for reviewer"
81
+ exit 1
82
+ fi
83
+
84
+ cd "${RUNTIME_PROJECT_DIR}"
51
85
 
52
86
  # Convert comma-separated branch prefixes into a regex that matches branch starts.
53
87
  BRANCH_REGEX=""
@@ -122,38 +156,55 @@ fi
122
156
 
123
157
  log "START: Found PR(s) needing work:${PRS_NEEDING_WORK}"
124
158
 
125
- cleanup_worktrees "${PROJECT_DIR}"
126
-
127
159
  # Dry-run mode: print diagnostics and exit
128
160
  if [ "${NW_DRY_RUN:-0}" = "1" ]; then
129
161
  echo "=== Dry Run: PR Reviewer ==="
130
162
  echo "Provider: ${PROVIDER_CMD}"
131
163
  echo "Branch Patterns: ${BRANCH_PATTERNS_RAW}"
132
164
  echo "Min Review Score: ${MIN_REVIEW_SCORE}"
165
+ echo "Default Branch: ${DEFAULT_BRANCH}"
166
+ echo "Runtime Dir: ${RUNTIME_PROJECT_DIR}"
133
167
  echo "Open PRs needing work:${PRS_NEEDING_WORK}"
134
168
  echo "Timeout: ${MAX_RUNTIME}s"
135
169
  exit 0
136
170
  fi
137
171
 
172
+ if [ -f "${RUNTIME_PROJECT_DIR}/.claude/commands/night-watch-pr-reviewer.md" ]; then
173
+ REVIEW_WORKFLOW=$(cat "${RUNTIME_PROJECT_DIR}/.claude/commands/night-watch-pr-reviewer.md")
174
+ else
175
+ REVIEW_WORKFLOW=$(cat "${SCRIPT_DIR}/../templates/night-watch-pr-reviewer.md")
176
+ fi
177
+
178
+ REVIEW_PROMPT="You are running in an isolated runtime workspace at ${RUNTIME_PROJECT_DIR}.
179
+ Do not run git checkout/switch in ${PROJECT_DIR}.
180
+ Do not create or remove worktrees; the runtime controller handles that.
181
+ Apply all fixes only inside the current runtime workspace.
182
+
183
+ ${REVIEW_WORKFLOW}"
184
+
138
185
  EXIT_CODE=0
139
186
 
140
187
  case "${PROVIDER_CMD}" in
141
188
  claude)
142
- if timeout "${MAX_RUNTIME}" \
143
- claude -p "/night-watch-pr-reviewer" \
144
- --dangerously-skip-permissions \
145
- >> "${LOG_FILE}" 2>&1; then
189
+ if (
190
+ cd "${RUNTIME_PROJECT_DIR}" && timeout "${MAX_RUNTIME}" \
191
+ claude -p "${REVIEW_PROMPT}" \
192
+ --dangerously-skip-permissions \
193
+ >> "${LOG_FILE}" 2>&1
194
+ ); then
146
195
  EXIT_CODE=0
147
196
  else
148
197
  EXIT_CODE=$?
149
198
  fi
150
199
  ;;
151
200
  codex)
152
- if timeout "${MAX_RUNTIME}" \
153
- codex --quiet \
154
- --yolo \
155
- --prompt "$(cat "${PROJECT_DIR}/.claude/commands/night-watch-pr-reviewer.md")" \
156
- >> "${LOG_FILE}" 2>&1; then
201
+ if (
202
+ cd "${RUNTIME_PROJECT_DIR}" && timeout "${MAX_RUNTIME}" \
203
+ codex --quiet \
204
+ --yolo \
205
+ --prompt "${REVIEW_PROMPT}" \
206
+ >> "${LOG_FILE}" 2>&1
207
+ ); then
157
208
  EXIT_CODE=0
158
209
  else
159
210
  EXIT_CODE=$?
@@ -165,8 +216,6 @@ case "${PROVIDER_CMD}" in
165
216
  ;;
166
217
  esac
167
218
 
168
- cleanup_worktrees "${PROJECT_DIR}"
169
-
170
219
  if [ ${EXIT_CODE} -eq 0 ]; then
171
220
  log "DONE: PR reviewer completed successfully"
172
221
  elif [ ${EXIT_CODE} -eq 124 ]; then
@@ -67,18 +67,18 @@ A PR needs attention if **either** the review score is below 80 **or** any CI jo
67
67
 
68
68
  4. **Fix the PR**:
69
69
 
70
- a. **Check out the PR branch**:
70
+ a. **Use the pre-provisioned runtime workspace**:
71
+ - You are already running in an isolated runtime workspace.
72
+ - Do **not** run `git checkout`/`git switch` in the original project directory.
73
+ - Do **not** create/remove worktrees manually; the runtime controller handles isolation and cleanup.
74
+
75
+ b. **Check out the PR branch inside the runtime workspace**:
71
76
  ```
72
77
  git fetch origin
73
78
  git checkout <branch-name>
74
79
  git pull origin <branch-name>
75
80
  ```
76
-
77
- b. **Create a worktree** for the fixes:
78
- ```
79
- git worktree add ../${PROJECT_NAME}-nw-review-<branch-name> <branch-name>
80
- ```
81
- `cd` into worktree, run package install (npm install, yarn install, or pnpm install as appropriate).
81
+ Run package install (npm install, yarn install, or pnpm install as appropriate).
82
82
 
83
83
  c. **Address CI failures** (if any):
84
84
  - Read the failed job logs carefully to understand the root cause.
@@ -135,10 +135,6 @@ A PR needs attention if **either** the review score is below 80 **or** any CI jo
135
135
  Night Watch PR Reviewer"
136
136
  ```
137
137
 
138
- h. **Clean up worktree**: `git worktree remove ../${PROJECT_NAME}-nw-review-<branch-name>`
139
-
140
138
  5. **Repeat** for all open PRs that need work.
141
139
 
142
- 6. When done, return to ${DEFAULT_BRANCH}: `git checkout ${DEFAULT_BRANCH}`
143
-
144
140
  Start now. Check for open PRs that need review feedback addressed or CI failures fixed.
@@ -20,20 +20,13 @@ You are the Night Watch agent. Your job is to autonomously pick up PRD tickets a
20
20
 
21
21
  b. **Branch naming**: The branch MUST be named exactly `night-watch/<prd-filename-without-.md>`. Do NOT use `feat/`, `feature/`, or any other prefix. Example: for `health-check-endpoints.md` the branch is `night-watch/health-check-endpoints`.
22
22
 
23
- c. **Create a feature branch** from ${DEFAULT_BRANCH}:
23
+ c. **Use the pre-provisioned runtime workspace**:
24
+ - You are already running in an isolated runtime workspace.
25
+ - The target branch `night-watch/<prd-filename-without-.md>` is already prepared.
26
+ - Do **not** run `git checkout`/`git switch` in the original project directory.
27
+ - Do **not** create/remove worktrees manually; the runtime controller handles isolation and cleanup.
24
28
 
25
- ```
26
- git checkout ${DEFAULT_BRANCH} && git pull origin ${DEFAULT_BRANCH}
27
- git checkout -b night-watch/<prd-filename-without-.md>
28
- ```
29
-
30
- d. **Create a git worktree** for isolated work:
31
-
32
- ```
33
- git worktree add ../${PROJECT_NAME}-nw-<prd-name> night-watch/<prd-name>
34
- ```
35
-
36
- Then `cd` into the worktree and run package install (npm install, yarn install, or pnpm install as appropriate).
29
+ d. Install dependencies in the current runtime workspace (npm install, yarn install, or pnpm install as appropriate).
37
30
 
38
31
  e. **Implement the PRD using the PRD Executor workflow**:
39
32
  - Read `.claude/commands/prd-executor.md` and follow its full execution pipeline.
@@ -64,11 +57,9 @@ You are the Night Watch agent. Your job is to autonomously pick up PRD tickets a
64
57
  gh pr create --title "feat: <short title>" --body "<summary with PRD reference>"
65
58
  ```
66
59
 
67
- j. **Move PRD to done** (back in main repo on ${DEFAULT_BRANCH}):
60
+ j. **Move PRD to done**:
68
61
 
69
62
  ```
70
- cd ${PROJECT_DIR}
71
- git checkout ${DEFAULT_BRANCH}
72
63
  mkdir -p docs/PRDs/night-watch/done
73
64
  mv docs/PRDs/night-watch/<file>.md docs/PRDs/night-watch/done/
74
65
  ```
@@ -91,10 +82,8 @@ You are the Night Watch agent. Your job is to autonomously pick up PRD tickets a
91
82
 
92
83
  l. **Commit** the move + summary update, push ${DEFAULT_BRANCH}.
93
84
 
94
- m. **Clean up worktree**: `git worktree remove ../${PROJECT_NAME}-nw-<prd-name>`
95
-
96
- n. **STOP after this PRD**. Do NOT continue to the next PRD. One PRD per run prevents timeouts and reduces risk. The next cron trigger will pick up the next PRD.
85
+ m. **STOP after this PRD**. Do NOT continue to the next PRD. One PRD per run prevents timeouts and reduces risk. The next cron trigger will pick up the next PRD.
97
86
 
98
- 5. **On failure**: Do NOT move the PRD to done. Log the failure in NIGHT-WATCH-SUMMARY.md with status "Failed" and the reason. Clean up worktree and **stop** -- do not attempt the next PRD.
87
+ 5. **On failure**: Do NOT move the PRD to done. Log the failure in NIGHT-WATCH-SUMMARY.md with status "Failed" and the reason. The runtime controller handles cleanup. Then **stop** -- do not attempt the next PRD.
99
88
 
100
89
  Start now. Scan for available PRDs and process the first eligible one.