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

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 (209) 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-audit-cron.sh +6 -0
  6. package/dist/scripts/night-watch-cron.sh +352 -53
  7. package/dist/scripts/night-watch-helpers.sh +212 -12
  8. package/dist/scripts/night-watch-merger-cron.sh +8 -2
  9. package/dist/scripts/night-watch-plan-cron.sh +1 -1
  10. package/dist/scripts/night-watch-pr-resolver-cron.sh +7 -3
  11. package/dist/scripts/night-watch-pr-reviewer-cron.sh +11 -0
  12. package/dist/scripts/night-watch-qa-cron.sh +11 -0
  13. package/dist/scripts/night-watch-slicer-cron.sh +1 -1
  14. package/dist/web/assets/index-B6E6kOoR.js +406 -0
  15. package/dist/web/assets/index-DIMUXIP8.css +1 -0
  16. package/dist/web/assets/index-Ds8OqaCa.css +1 -0
  17. package/dist/web/assets/index-NR27JE3b.js +406 -0
  18. package/dist/web/index.html +2 -2
  19. package/package.json +1 -1
  20. package/dist/cli.d.ts +0 -3
  21. package/dist/cli.js.map +0 -1
  22. package/dist/commands/analytics.d.ts +0 -14
  23. package/dist/commands/analytics.js +0 -69
  24. package/dist/commands/analytics.js.map +0 -1
  25. package/dist/commands/audit.d.ts +0 -19
  26. package/dist/commands/audit.js +0 -144
  27. package/dist/commands/audit.js.map +0 -1
  28. package/dist/commands/board.d.ts +0 -9
  29. package/dist/commands/board.js +0 -702
  30. package/dist/commands/board.js.map +0 -1
  31. package/dist/commands/cancel.d.ts +0 -46
  32. package/dist/commands/cancel.js +0 -239
  33. package/dist/commands/cancel.js.map +0 -1
  34. package/dist/commands/cron.d.ts +0 -8
  35. package/dist/commands/cron.js +0 -134
  36. package/dist/commands/cron.js.map +0 -1
  37. package/dist/commands/dashboard/tab-actions.d.ts +0 -10
  38. package/dist/commands/dashboard/tab-actions.js +0 -247
  39. package/dist/commands/dashboard/tab-actions.js.map +0 -1
  40. package/dist/commands/dashboard/tab-config.d.ts +0 -21
  41. package/dist/commands/dashboard/tab-config.js +0 -873
  42. package/dist/commands/dashboard/tab-config.js.map +0 -1
  43. package/dist/commands/dashboard/tab-logs.d.ts +0 -10
  44. package/dist/commands/dashboard/tab-logs.js +0 -202
  45. package/dist/commands/dashboard/tab-logs.js.map +0 -1
  46. package/dist/commands/dashboard/tab-schedules.d.ts +0 -21
  47. package/dist/commands/dashboard/tab-schedules.js +0 -320
  48. package/dist/commands/dashboard/tab-schedules.js.map +0 -1
  49. package/dist/commands/dashboard/tab-status.d.ts +0 -32
  50. package/dist/commands/dashboard/tab-status.js +0 -424
  51. package/dist/commands/dashboard/tab-status.js.map +0 -1
  52. package/dist/commands/dashboard/types.d.ts +0 -42
  53. package/dist/commands/dashboard/types.js +0 -5
  54. package/dist/commands/dashboard/types.js.map +0 -1
  55. package/dist/commands/dashboard.d.ts +0 -11
  56. package/dist/commands/dashboard.js +0 -242
  57. package/dist/commands/dashboard.js.map +0 -1
  58. package/dist/commands/doctor.d.ts +0 -16
  59. package/dist/commands/doctor.js +0 -195
  60. package/dist/commands/doctor.js.map +0 -1
  61. package/dist/commands/history.d.ts +0 -7
  62. package/dist/commands/history.js +0 -49
  63. package/dist/commands/history.js.map +0 -1
  64. package/dist/commands/init.d.ts +0 -45
  65. package/dist/commands/init.js +0 -777
  66. package/dist/commands/init.js.map +0 -1
  67. package/dist/commands/install.d.ts +0 -65
  68. package/dist/commands/install.js +0 -405
  69. package/dist/commands/install.js.map +0 -1
  70. package/dist/commands/logs.d.ts +0 -15
  71. package/dist/commands/logs.js +0 -155
  72. package/dist/commands/logs.js.map +0 -1
  73. package/dist/commands/merge.d.ts +0 -26
  74. package/dist/commands/merge.js +0 -159
  75. package/dist/commands/merge.js.map +0 -1
  76. package/dist/commands/notify.d.ts +0 -7
  77. package/dist/commands/notify.js +0 -43
  78. package/dist/commands/notify.js.map +0 -1
  79. package/dist/commands/plan.d.ts +0 -19
  80. package/dist/commands/plan.js +0 -88
  81. package/dist/commands/plan.js.map +0 -1
  82. package/dist/commands/prd-state.d.ts +0 -12
  83. package/dist/commands/prd-state.js +0 -47
  84. package/dist/commands/prd-state.js.map +0 -1
  85. package/dist/commands/prd.d.ts +0 -18
  86. package/dist/commands/prd.js +0 -363
  87. package/dist/commands/prd.js.map +0 -1
  88. package/dist/commands/prds.d.ts +0 -13
  89. package/dist/commands/prds.js +0 -194
  90. package/dist/commands/prds.js.map +0 -1
  91. package/dist/commands/prs.d.ts +0 -14
  92. package/dist/commands/prs.js +0 -104
  93. package/dist/commands/prs.js.map +0 -1
  94. package/dist/commands/qa.d.ts +0 -34
  95. package/dist/commands/qa.js +0 -214
  96. package/dist/commands/qa.js.map +0 -1
  97. package/dist/commands/queue.d.ts +0 -8
  98. package/dist/commands/queue.js +0 -376
  99. package/dist/commands/queue.js.map +0 -1
  100. package/dist/commands/resolve.d.ts +0 -26
  101. package/dist/commands/resolve.js +0 -186
  102. package/dist/commands/resolve.js.map +0 -1
  103. package/dist/commands/retry.d.ts +0 -9
  104. package/dist/commands/retry.js +0 -71
  105. package/dist/commands/retry.js.map +0 -1
  106. package/dist/commands/review.d.ts +0 -82
  107. package/dist/commands/review.js +0 -479
  108. package/dist/commands/review.js.map +0 -1
  109. package/dist/commands/run.d.ts +0 -73
  110. package/dist/commands/run.js +0 -509
  111. package/dist/commands/run.js.map +0 -1
  112. package/dist/commands/serve.d.ts +0 -19
  113. package/dist/commands/serve.js +0 -142
  114. package/dist/commands/serve.js.map +0 -1
  115. package/dist/commands/shared/env-builder.d.ts +0 -49
  116. package/dist/commands/shared/env-builder.js +0 -150
  117. package/dist/commands/shared/env-builder.js.map +0 -1
  118. package/dist/commands/slice.d.ts +0 -35
  119. package/dist/commands/slice.js +0 -316
  120. package/dist/commands/slice.js.map +0 -1
  121. package/dist/commands/state.d.ts +0 -8
  122. package/dist/commands/state.js +0 -54
  123. package/dist/commands/state.js.map +0 -1
  124. package/dist/commands/status.d.ts +0 -14
  125. package/dist/commands/status.js +0 -297
  126. package/dist/commands/status.js.map +0 -1
  127. package/dist/commands/summary.d.ts +0 -14
  128. package/dist/commands/summary.js +0 -193
  129. package/dist/commands/summary.js.map +0 -1
  130. package/dist/commands/uninstall.d.ts +0 -25
  131. package/dist/commands/uninstall.js +0 -134
  132. package/dist/commands/uninstall.js.map +0 -1
  133. package/dist/commands/update.d.ts +0 -22
  134. package/dist/commands/update.js +0 -90
  135. package/dist/commands/update.js.map +0 -1
  136. package/dist/web/assets/index-2JY0x_Ij.js +0 -381
  137. package/dist/web/assets/index-3h8pgmqL.css +0 -1
  138. package/dist/web/assets/index-B-wbyZq7.js +0 -386
  139. package/dist/web/assets/index-B1BnOpiO.css +0 -1
  140. package/dist/web/assets/index-B3CnV08_.js +0 -365
  141. package/dist/web/assets/index-B5QjuFh9.css +0 -1
  142. package/dist/web/assets/index-B8FW2ecQ.js +0 -370
  143. package/dist/web/assets/index-BFxPiKyy.js +0 -381
  144. package/dist/web/assets/index-BGqNh_Da.js +0 -365
  145. package/dist/web/assets/index-BIONU0qz.css +0 -1
  146. package/dist/web/assets/index-B_l_3wnA.js +0 -370
  147. package/dist/web/assets/index-Ba-4YvTQ.js +0 -365
  148. package/dist/web/assets/index-Bbb4-39N.js +0 -370
  149. package/dist/web/assets/index-BdgdShEN.js +0 -365
  150. package/dist/web/assets/index-BhiC4Z-G.js +0 -381
  151. package/dist/web/assets/index-BjhCFjZi.js +0 -381
  152. package/dist/web/assets/index-BlRxmrnQ.css +0 -1
  153. package/dist/web/assets/index-BqwbXsHS.js +0 -365
  154. package/dist/web/assets/index-BsC7RT48.css +0 -1
  155. package/dist/web/assets/index-Bvh8XI8_.js +0 -370
  156. package/dist/web/assets/index-C01r2ymn.js +0 -381
  157. package/dist/web/assets/index-C51Rbsmk.js +0 -381
  158. package/dist/web/assets/index-C7lMNxRE.js +0 -370
  159. package/dist/web/assets/index-CJLObgsn.js +0 -386
  160. package/dist/web/assets/index-CLuRf7Zt.js +0 -381
  161. package/dist/web/assets/index-CM3xFd3e.css +0 -1
  162. package/dist/web/assets/index-CNkBtDK7.js +0 -370
  163. package/dist/web/assets/index-CPQbZ1BL.css +0 -1
  164. package/dist/web/assets/index-CTy5dUDU.css +0 -1
  165. package/dist/web/assets/index-CU15COKs.js +0 -370
  166. package/dist/web/assets/index-CiRJZI4z.js +0 -386
  167. package/dist/web/assets/index-Cp7RYjoy.css +0 -1
  168. package/dist/web/assets/index-CvPkZOWT.js +0 -381
  169. package/dist/web/assets/index-CvUk-33B.css +0 -1
  170. package/dist/web/assets/index-Cvmj-oF6.css +0 -1
  171. package/dist/web/assets/index-CxE5iQVO.js +0 -381
  172. package/dist/web/assets/index-D7lZQpFV.js +0 -365
  173. package/dist/web/assets/index-DAyP4GOi.css +0 -1
  174. package/dist/web/assets/index-DCG0n8Kg.js +0 -386
  175. package/dist/web/assets/index-DEEI8cyF.css +0 -1
  176. package/dist/web/assets/index-DF99BowV.js +0 -381
  177. package/dist/web/assets/index-DGWsvFj6.css +0 -1
  178. package/dist/web/assets/index-DGpU39Cp.css +0 -1
  179. package/dist/web/assets/index-DI4kFgOi.js +0 -370
  180. package/dist/web/assets/index-DIyTcPw5.css +0 -1
  181. package/dist/web/assets/index-DTsfDC7m.js +0 -381
  182. package/dist/web/assets/index-DcgNAi4A.js +0 -386
  183. package/dist/web/assets/index-DgOAgkZy.css +0 -1
  184. package/dist/web/assets/index-DnHkqbOa.js +0 -386
  185. package/dist/web/assets/index-DnR7Idcf.css +0 -1
  186. package/dist/web/assets/index-DpVirMEe.css +0 -1
  187. package/dist/web/assets/index-DsYIWZ86.css +0 -1
  188. package/dist/web/assets/index-DtrDkci5.js +0 -381
  189. package/dist/web/assets/index-DyjIth5M.js +0 -386
  190. package/dist/web/assets/index-FwIKfHPL.css +0 -1
  191. package/dist/web/assets/index-IKrZymWk.css +0 -1
  192. package/dist/web/assets/index-MA6fM0ab.js +0 -381
  193. package/dist/web/assets/index-N_QxaSEg.css +0 -1
  194. package/dist/web/assets/index-OcU-0TCQ.css +0 -1
  195. package/dist/web/assets/index-OyhrmG-L.js +0 -381
  196. package/dist/web/assets/index-SQlBKu_s.js +0 -386
  197. package/dist/web/assets/index-Sv2B60J4.js +0 -370
  198. package/dist/web/assets/index-Vgyivb5u.js +0 -365
  199. package/dist/web/assets/index-ZABWMEZR.js +0 -381
  200. package/dist/web/assets/index-ZE5lOeJp.js +0 -386
  201. package/dist/web/assets/index-aCHmkAcJ.css +0 -1
  202. package/dist/web/assets/index-bFijnpuU.js +0 -381
  203. package/dist/web/assets/index-bUPZgSoZ.css +0 -1
  204. package/dist/web/assets/index-mz1VIYsP.css +0 -1
  205. package/dist/web/assets/index-oOp_MFeE.js +0 -376
  206. package/dist/web/assets/index-rfU713Zm.js +0 -386
  207. package/dist/web/assets/index-tuNH9gmb.js +0 -448
  208. package/dist/web/assets/index-viSwHyDD.js +0 -365
  209. package/dist/web/assets/index-yKEQysks.js +0 -365
@@ -1 +1 @@
1
- {"version":3,"file":"tab-config.d.ts","sourceRoot":"","sources":["../../../src/commands/dashboard/tab-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAGL,iBAAiB,EAKlB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,IAAI,EAAe,MAAM,YAAY,CAAC;AAE/C,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjG,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,iBAAiB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC7C;AAiDD,eAAO,MAAM,aAAa,EAAE,YAAY,EA+CvC,CAAC;AA0BF;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAyzBtC"}
1
+ {"version":3,"file":"tab-config.d.ts","sourceRoot":"","sources":["../../../src/commands/dashboard/tab-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAGL,iBAAiB,EAKlB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,IAAI,EAAe,MAAM,YAAY,CAAC;AAE/C,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjG,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,iBAAiB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC7C;AAiDD,eAAO,MAAM,aAAa,EAAE,YAAY,EAgDvC,CAAC;AA0BF;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAyzBtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/commands/queue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+DpC,wBAAgB,kBAAkB,IAAI,OAAO,CAsU5C;AAkED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEnD"}
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/commands/queue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+DpC,wBAAgB,kBAAkB,IAAI,OAAO,CAyU5C;AAkED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEnD"}
@@ -1 +1 @@
1
- {"version":3,"file":"env-builder.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/env-builder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEL,iBAAiB,EAEjB,WAAW,EAEX,iBAAiB,EAKlB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,mBAAmB,CAAC;AAmB3D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,OAAO,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkExB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,+CAGnB;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAY/C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAO1F;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAG5E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,GACxB,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAW7C"}
1
+ {"version":3,"file":"env-builder.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/env-builder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEL,iBAAiB,EAEjB,WAAW,EAEX,iBAAiB,EAKlB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,mBAAmB,CAAC;AAmB3D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,OAAO,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoExB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,+CAGnB;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAY/C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAO1F;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAG5E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,iBAAiB,GACxB,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAW7C"}
@@ -116,6 +116,12 @@ fi
116
116
  AUDIT_WORKTREE_BASENAME="${PROJECT_NAME}-nw-audit-runner"
117
117
  AUDIT_WORKTREE_DIR="$(dirname "${PROJECT_DIR}")/${AUDIT_WORKTREE_BASENAME}"
118
118
 
119
+ cleanup_audit_worktree_on_exit() {
120
+ cleanup_worktree_path "${PROJECT_DIR}" "${AUDIT_WORKTREE_DIR}"
121
+ }
122
+
123
+ append_exit_trap "cleanup_audit_worktree_on_exit"
124
+
119
125
  cleanup_worktrees "${PROJECT_DIR}" "${AUDIT_WORKTREE_BASENAME}"
120
126
 
121
127
  if ! prepare_detached_worktree "${PROJECT_DIR}" "${AUDIT_WORKTREE_DIR}" "${DEFAULT_BRANCH}" "${LOG_FILE}"; then
@@ -140,6 +140,12 @@ ISSUE_NUMBER="" # board mode: GitHub issue number
140
140
  ISSUE_BODY="" # board mode: issue body (PRD content)
141
141
  ISSUE_TITLE_RAW="" # board mode: issue title
142
142
  NW_CLI="" # board mode: resolved night-watch CLI binary
143
+ EXECUTOR_PR_JSON=""
144
+ EXECUTOR_PR_NUMBER=""
145
+ EXECUTOR_PR_URL=""
146
+ EXECUTOR_PR_DRAFT=""
147
+ RESUME_FROM_EXISTING_PR=0
148
+ RESUME_BRANCH_NAME=""
143
149
 
144
150
  restore_issue_to_ready() {
145
151
  local reason="${1:-Execution failed before implementation started.}"
@@ -149,6 +155,26 @@ restore_issue_to_ready() {
149
155
  fi
150
156
  }
151
157
 
158
+ if [ -z "${NW_TARGET_ISSUE:-}" ]; then
159
+ EXECUTOR_PR_JSON=$(find_executor_resume_pr "${BRANCH_PREFIX}" || true)
160
+ if [ -n "${EXECUTOR_PR_JSON}" ]; then
161
+ RESUME_FROM_EXISTING_PR=1
162
+ RESUME_BRANCH_NAME=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.headRefName // empty' 2>/dev/null || true)
163
+ EXECUTOR_PR_NUMBER=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.number // empty' 2>/dev/null || true)
164
+ EXECUTOR_PR_URL=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.url // empty' 2>/dev/null || true)
165
+ EXECUTOR_PR_DRAFT=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.isDraft // false' 2>/dev/null || true)
166
+ if [ -n "${RESUME_BRANCH_NAME}" ]; then
167
+ log "RESUME: Prioritizing resumable PR #${EXECUTOR_PR_NUMBER:-unknown} on ${RESUME_BRANCH_NAME}"
168
+ else
169
+ RESUME_FROM_EXISTING_PR=0
170
+ EXECUTOR_PR_JSON=""
171
+ EXECUTOR_PR_NUMBER=""
172
+ EXECUTOR_PR_URL=""
173
+ EXECUTOR_PR_DRAFT=""
174
+ fi
175
+ fi
176
+ fi
177
+
152
178
  if [ "${NW_BOARD_ENABLED:-}" = "true" ]; then
153
179
  # Board mode: discover next task from GitHub Projects board
154
180
  NW_CLI=$(resolve_night_watch_cli 2>/dev/null || true)
@@ -169,37 +195,52 @@ if [ "${NW_BOARD_ENABLED:-}" = "true" ]; then
169
195
  ISSUE_TITLE_RAW=$(printf '%s' "${ISSUE_JSON}" | jq -r '.title // empty' 2>/dev/null || true)
170
196
  ISSUE_BODY=$(printf '%s' "${ISSUE_JSON}" | jq -r '.body // empty' 2>/dev/null || true)
171
197
  else
172
- BOARD_DISCOVERY_STATUS=0
173
- if ISSUE_JSON=$(find_eligible_board_issue "${PROJECT_DIR}" "${MAX_RUNTIME}"); then
174
- BOARD_DISCOVERY_STATUS=0
175
- else
176
- BOARD_DISCOVERY_STATUS=$?
177
- fi
178
- if [ -z "${ISSUE_JSON}" ]; then
179
- if [ "${BOARD_DISCOVERY_STATUS}" -eq 2 ]; then
180
- log "INFO: Ready board issues were found, but all are in cooldown; skipping this run"
198
+ if [ "${RESUME_FROM_EXISTING_PR}" = "1" ] && [ -n "${RESUME_BRANCH_NAME}" ]; then
199
+ ELIGIBLE_PRD="${RESUME_BRANCH_NAME#*/}"
200
+ ISSUE_NUMBER=$(printf '%s' "${ELIGIBLE_PRD}" | grep -oE '^[0-9]+' || true)
201
+ if [ -n "${ISSUE_NUMBER}" ]; then
202
+ ISSUE_JSON=$(gh issue view "${ISSUE_NUMBER}" --json number,title,body 2>/dev/null || true)
203
+ ISSUE_TITLE_RAW=$(printf '%s' "${ISSUE_JSON}" | jq -r '.title // empty' 2>/dev/null || true)
204
+ ISSUE_BODY=$(printf '%s' "${ISSUE_JSON}" | jq -r '.body // empty' 2>/dev/null || true)
205
+ "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "In Progress" 2>>"${LOG_FILE}" || \
206
+ log "WARN: Failed to move resumed issue #${ISSUE_NUMBER} to In Progress"
181
207
  else
182
- log "INFO: No Ready board issues found; skipping this run"
208
+ ISSUE_TITLE_RAW=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.title // empty' 2>/dev/null || true)
183
209
  fi
184
210
  else
185
- ISSUE_NUMBER=$(printf '%s' "${ISSUE_JSON}" | jq -r '.number // empty' 2>/dev/null || true)
186
- ISSUE_TITLE_RAW=$(printf '%s' "${ISSUE_JSON}" | jq -r '.title // empty' 2>/dev/null || true)
187
- ISSUE_BODY=$(printf '%s' "${ISSUE_JSON}" | jq -r '.body // empty' 2>/dev/null || true)
188
- if [ -z "${ISSUE_NUMBER}" ]; then
189
- log "ERROR: Board mode: failed to parse issue number from JSON"
190
- exit 1
211
+ BOARD_DISCOVERY_STATUS=0
212
+ if ISSUE_JSON=$(find_eligible_board_issue "${PROJECT_DIR}" "${MAX_RUNTIME}"); then
213
+ BOARD_DISCOVERY_STATUS=0
214
+ else
215
+ BOARD_DISCOVERY_STATUS=$?
216
+ fi
217
+ if [ -z "${ISSUE_JSON}" ]; then
218
+ if [ "${BOARD_DISCOVERY_STATUS}" -eq 2 ]; then
219
+ log "INFO: Ready board issues were found, but all are in cooldown; skipping this run"
220
+ else
221
+ log "INFO: No Ready board issues found; skipping this run"
222
+ fi
223
+ else
224
+ ISSUE_NUMBER=$(printf '%s' "${ISSUE_JSON}" | jq -r '.number // empty' 2>/dev/null || true)
225
+ ISSUE_TITLE_RAW=$(printf '%s' "${ISSUE_JSON}" | jq -r '.title // empty' 2>/dev/null || true)
226
+ ISSUE_BODY=$(printf '%s' "${ISSUE_JSON}" | jq -r '.body // empty' 2>/dev/null || true)
227
+ if [ -z "${ISSUE_NUMBER}" ]; then
228
+ log "ERROR: Board mode: failed to parse issue number from JSON"
229
+ exit 1
230
+ fi
231
+ # Move issue to In Progress (claim it on the board)
232
+ "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "In Progress" 2>>"${LOG_FILE}" || \
233
+ log "WARN: Failed to move issue #${ISSUE_NUMBER} to In Progress"
191
234
  fi
192
- # Move issue to In Progress (claim it on the board)
193
- "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "In Progress" 2>>"${LOG_FILE}" || \
194
- log "WARN: Failed to move issue #${ISSUE_NUMBER} to In Progress"
195
235
  fi
196
236
  fi
197
237
 
198
238
  if [ -n "${ISSUE_NUMBER}" ]; then
199
- # Slugify title for branch naming
200
- ELIGIBLE_PRD="${ISSUE_NUMBER}-$(printf '%s' "${ISSUE_TITLE_RAW}" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-\|-$//g')"
239
+ # Slugify title for branch naming unless we're resuming an existing PR branch.
240
+ if [ "${RESUME_FROM_EXISTING_PR}" != "1" ] || [ -z "${ELIGIBLE_PRD:-}" ]; then
241
+ ELIGIBLE_PRD="${ISSUE_NUMBER}-$(printf '%s' "${ISSUE_TITLE_RAW}" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-\|-$//g')"
242
+ fi
201
243
  log "BOARD: Processing issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}"
202
- trap "rm -f '${LOCK_FILE}'" EXIT
203
244
  fi
204
245
  fi
205
246
 
@@ -209,8 +250,25 @@ if [ -z "${ISSUE_NUMBER}" ]; then
209
250
  emit_result "skip_no_eligible_prd"
210
251
  exit 0
211
252
  fi
253
+ if [ "${RESUME_FROM_EXISTING_PR}" = "1" ] && [ -n "${RESUME_BRANCH_NAME}" ]; then
254
+ RESUME_PRD_NAME="${RESUME_BRANCH_NAME#*/}"
255
+ if [ -f "${PRD_DIR}/${RESUME_PRD_NAME}.md" ]; then
256
+ ELIGIBLE_PRD="${RESUME_PRD_NAME}.md"
257
+ log "RESUME: Using resumable filesystem PRD ${ELIGIBLE_PRD}"
258
+ else
259
+ log "WARN: Resumable PR branch ${RESUME_BRANCH_NAME} has no matching PRD file in ${PRD_DIR}; falling back to normal selection"
260
+ RESUME_FROM_EXISTING_PR=0
261
+ EXECUTOR_PR_JSON=""
262
+ EXECUTOR_PR_NUMBER=""
263
+ EXECUTOR_PR_URL=""
264
+ EXECUTOR_PR_DRAFT=""
265
+ RESUME_BRANCH_NAME=""
266
+ fi
267
+ fi
212
268
  # Filesystem mode: scan PRD directory
213
- ELIGIBLE_PRD=$(find_eligible_prd "${PRD_DIR}" "${MAX_RUNTIME}" "${PROJECT_DIR}")
269
+ if [ -z "${ELIGIBLE_PRD:-}" ]; then
270
+ ELIGIBLE_PRD=$(find_eligible_prd "${PRD_DIR}" "${MAX_RUNTIME}" "${PROJECT_DIR}")
271
+ fi
214
272
  if [ -z "${ELIGIBLE_PRD}" ]; then
215
273
  log "SKIP: No eligible PRDs (all done, in-progress, or blocked)"
216
274
  emit_result "skip_no_eligible_prd"
@@ -218,12 +276,15 @@ if [ -z "${ISSUE_NUMBER}" ]; then
218
276
  fi
219
277
  # Claim the PRD to prevent other runs from selecting it
220
278
  claim_prd "${PRD_DIR}" "${ELIGIBLE_PRD}"
221
- # Update EXIT trap to also release claim
222
- trap "rm -f '${LOCK_FILE}'; release_claim '${PRD_DIR}' '${ELIGIBLE_PRD}'" EXIT
279
+ append_exit_trap "release_claim '${PRD_DIR}' '${ELIGIBLE_PRD}'"
223
280
  fi
224
281
 
225
282
  PRD_NAME="${ELIGIBLE_PRD%.md}"
226
- BRANCH_NAME="${BRANCH_PREFIX}/${PRD_NAME}"
283
+ if [ -n "${RESUME_BRANCH_NAME}" ]; then
284
+ BRANCH_NAME="${RESUME_BRANCH_NAME}"
285
+ else
286
+ BRANCH_NAME="${BRANCH_PREFIX}/${PRD_NAME}"
287
+ fi
227
288
  WORKTREE_DIR="$(dirname "${PROJECT_DIR}")/${PROJECT_NAME}-nw-${PRD_NAME}"
228
289
  BOOKKEEP_WORKTREE_DIR="$(dirname "${PROJECT_DIR}")/${PROJECT_NAME}-nw-bookkeeping"
229
290
  if [ -n "${NW_DEFAULT_BRANCH:-}" ]; then
@@ -231,6 +292,14 @@ if [ -n "${NW_DEFAULT_BRANCH:-}" ]; then
231
292
  else
232
293
  DEFAULT_BRANCH=$(detect_default_branch "${PROJECT_DIR}")
233
294
  fi
295
+
296
+ cleanup_executor_worktrees_on_exit() {
297
+ cleanup_worktree_path "${PROJECT_DIR}" "${WORKTREE_DIR}"
298
+ cleanup_worktree_path "${PROJECT_DIR}" "${BOOKKEEP_WORKTREE_DIR}"
299
+ }
300
+
301
+ append_exit_trap "cleanup_executor_worktrees_on_exit"
302
+
234
303
  if [[ "${PRD_DIR_REL}" = /* ]]; then
235
304
  BOOKKEEP_PRD_DIR="${PRD_DIR_REL}"
236
305
  else
@@ -250,6 +319,194 @@ count_prs_for_branch() {
250
319
  echo "${count:-0}"
251
320
  }
252
321
 
322
+ extract_prd_title() {
323
+ local prd_path=""
324
+ local title=""
325
+
326
+ if [ -n "${ISSUE_TITLE_RAW}" ]; then
327
+ printf '%s' "${ISSUE_TITLE_RAW}"
328
+ return 0
329
+ fi
330
+
331
+ if [ -f "${PRD_DIR}/${ELIGIBLE_PRD}" ]; then
332
+ prd_path="${PRD_DIR}/${ELIGIBLE_PRD}"
333
+ elif [ -f "${PROJECT_DIR}/${PRD_DIR_REL}/${ELIGIBLE_PRD}" ]; then
334
+ prd_path="${PROJECT_DIR}/${PRD_DIR_REL}/${ELIGIBLE_PRD}"
335
+ fi
336
+
337
+ if [ -n "${prd_path}" ]; then
338
+ title=$(awk '/^[[:space:]]*#[[:space:]]+/ { sub(/^[[:space:]]*#[[:space:]]+/, "", $0); print; exit }' "${prd_path}" 2>/dev/null || true)
339
+ fi
340
+
341
+ if [ -n "${title}" ]; then
342
+ printf '%s' "${title}"
343
+ else
344
+ printf '%s' "${PRD_NAME}"
345
+ fi
346
+ }
347
+
348
+ build_executor_pr_title() {
349
+ local raw_title=""
350
+
351
+ raw_title=$(extract_prd_title)
352
+ raw_title=$(printf '%s' "${raw_title}" | tr '\r\n' ' ' | sed -E 's/[[:space:]]+/ /g; s/^[[:space:]]+//; s/[[:space:]]+$//')
353
+ if [ -z "${raw_title}" ]; then
354
+ raw_title="${PRD_NAME}"
355
+ fi
356
+
357
+ if printf '%s' "${raw_title}" | grep -Eqi '^feat:'; then
358
+ printf '%s' "${raw_title}"
359
+ else
360
+ printf 'feat: %s' "${raw_title}"
361
+ fi
362
+ }
363
+
364
+ build_executor_pr_body() {
365
+ local status_blurb=""
366
+
367
+ status_blurb="Status labels:
368
+ - ${NW_EXECUTOR_PARTIAL_LABEL}: implementation is in progress and intentionally incomplete
369
+ - ${NW_EXECUTOR_RESUMABLE_LABEL}: resume this PR before starting new work
370
+ - ${NW_EXECUTOR_READY_REVIEW_LABEL}: implementation is complete and ready for review"
371
+
372
+ if [ -n "${ISSUE_NUMBER}" ]; then
373
+ printf 'Closes #%s\n\nNight Watch manages this draft PR automatically so progress is preserved across retries and timeouts.\n\n%s\n' \
374
+ "${ISSUE_NUMBER}" \
375
+ "${status_blurb}"
376
+ else
377
+ printf 'Source PRD: `%s/%s`\n\nNight Watch manages this draft PR automatically so progress is preserved across retries and timeouts.\n\n%s\n' \
378
+ "${PRD_DIR_REL}" \
379
+ "${ELIGIBLE_PRD}" \
380
+ "${status_blurb}"
381
+ fi
382
+ }
383
+
384
+ refresh_executor_pr_metadata() {
385
+ EXECUTOR_PR_JSON=$(find_open_pr_for_branch "${BRANCH_NAME}" || true)
386
+ EXECUTOR_PR_NUMBER=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.number // empty' 2>/dev/null || true)
387
+ EXECUTOR_PR_URL=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.url // empty' 2>/dev/null || true)
388
+ EXECUTOR_PR_DRAFT=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.isDraft // false' 2>/dev/null || true)
389
+ }
390
+
391
+ sync_executor_pr_status() {
392
+ local add_labels="${1:-}"
393
+ local remove_labels="${2:-}"
394
+ local mark_ready="${3:-0}"
395
+
396
+ if [ -z "${EXECUTOR_PR_NUMBER}" ]; then
397
+ return 1
398
+ fi
399
+
400
+ ensure_executor_status_labels
401
+
402
+ if [ -n "${add_labels}" ]; then
403
+ gh pr edit "${EXECUTOR_PR_NUMBER}" --add-label "${add_labels}" >> "${LOG_FILE}" 2>&1 || true
404
+ fi
405
+ if [ -n "${remove_labels}" ]; then
406
+ gh pr edit "${EXECUTOR_PR_NUMBER}" --remove-label "${remove_labels}" >> "${LOG_FILE}" 2>&1 || true
407
+ fi
408
+ if [ "${mark_ready}" = "1" ]; then
409
+ gh pr ready "${EXECUTOR_PR_NUMBER}" >> "${LOG_FILE}" 2>&1 || true
410
+ fi
411
+
412
+ refresh_executor_pr_metadata
413
+ return 0
414
+ }
415
+
416
+ mark_executor_pr_incomplete() {
417
+ sync_executor_pr_status \
418
+ "${NW_EXECUTOR_PARTIAL_LABEL},${NW_EXECUTOR_RESUMABLE_LABEL}" \
419
+ "${NW_EXECUTOR_READY_REVIEW_LABEL}" \
420
+ "0"
421
+ }
422
+
423
+ mark_executor_pr_ready_for_review() {
424
+ sync_executor_pr_status \
425
+ "${NW_EXECUTOR_READY_REVIEW_LABEL}" \
426
+ "${NW_EXECUTOR_PARTIAL_LABEL},${NW_EXECUTOR_RESUMABLE_LABEL}" \
427
+ "1"
428
+ }
429
+
430
+ push_executor_branch() {
431
+ local push_mode="${1:-update}"
432
+
433
+ if [ "${push_mode}" = "initial" ]; then
434
+ if git_push_for_project "${WORKTREE_DIR}" -u origin "${BRANCH_NAME}" >> "${LOG_FILE}" 2>&1; then
435
+ return 0
436
+ fi
437
+ fi
438
+
439
+ if git_push_for_project "${WORKTREE_DIR}" origin "${BRANCH_NAME}" --force-with-lease >> "${LOG_FILE}" 2>&1; then
440
+ return 0
441
+ fi
442
+
443
+ git_push_for_project "${WORKTREE_DIR}" -u origin "${BRANCH_NAME}" >> "${LOG_FILE}" 2>&1
444
+ }
445
+
446
+ executor_branch_has_commits_ahead_of_base() {
447
+ local base_ref=""
448
+ local ahead_count="0"
449
+
450
+ if ! base_ref=$(resolve_worktree_base_ref "${WORKTREE_DIR}" "${DEFAULT_BRANCH}" 2>/dev/null); then
451
+ return 1
452
+ fi
453
+
454
+ ahead_count=$(git -C "${WORKTREE_DIR}" rev-list --count "${base_ref}..HEAD" 2>/dev/null || echo "0")
455
+ [ "${ahead_count}" -gt 0 ]
456
+ }
457
+
458
+ ensure_executor_pr() {
459
+ local pr_title=""
460
+ local pr_body=""
461
+ local create_output=""
462
+
463
+ refresh_executor_pr_metadata
464
+ if [ -n "${EXECUTOR_PR_NUMBER}" ]; then
465
+ log "PR: Reusing existing open PR #${EXECUTOR_PR_NUMBER} for ${BRANCH_NAME}"
466
+ mark_executor_pr_incomplete
467
+ return 0
468
+ fi
469
+
470
+ pr_title=$(build_executor_pr_title)
471
+ pr_body=$(build_executor_pr_body)
472
+
473
+ if ! executor_branch_has_commits_ahead_of_base; then
474
+ log "PR: Deferring draft PR creation for ${BRANCH_NAME} until it has commits beyond ${DEFAULT_BRANCH}"
475
+ return 0
476
+ fi
477
+
478
+ log "PR: Creating draft PR for ${BRANCH_NAME}"
479
+ if ! push_executor_branch "initial"; then
480
+ log "WARN: Initial push for ${BRANCH_NAME} failed before PR creation"
481
+ fi
482
+
483
+ ensure_executor_status_labels
484
+ if ! create_output=$(
485
+ gh pr create \
486
+ --draft \
487
+ --base "${DEFAULT_BRANCH}" \
488
+ --head "${BRANCH_NAME}" \
489
+ --title "${pr_title}" \
490
+ --body "${pr_body}" 2>> "${LOG_FILE}"
491
+ ); then
492
+ log "FAIL: gh pr create failed for ${BRANCH_NAME}"
493
+ return 1
494
+ fi
495
+
496
+ refresh_executor_pr_metadata
497
+ if [ -z "${EXECUTOR_PR_URL}" ]; then
498
+ EXECUTOR_PR_URL=$(printf '%s' "${create_output}" | grep -Eo 'https://[^[:space:]]+/pull/[0-9]+' | tail -n 1 || true)
499
+ fi
500
+ if [ -n "${EXECUTOR_PR_NUMBER}" ]; then
501
+ mark_executor_pr_incomplete
502
+ log "PR: Draft PR ready at ${EXECUTOR_PR_URL:-unknown} for ${BRANCH_NAME}"
503
+ else
504
+ log "WARN: gh pr create succeeded for ${BRANCH_NAME}, but PR metadata lookup did not resolve a number yet"
505
+ fi
506
+
507
+ return 0
508
+ }
509
+
253
510
  checkpoint_timeout_progress() {
254
511
  local worktree_dir="${1:?worktree_dir required}"
255
512
  local branch_name="${2:?branch_name required}"
@@ -360,7 +617,7 @@ finalize_prd_done() {
360
617
  git -C "${BOOKKEEP_WORKTREE_DIR}" commit -m "chore: mark ${ELIGIBLE_PRD} as done (${reason})
361
618
 
362
619
  Co-Authored-By: Night Watch [${EFFECTIVE_PROVIDER_LABEL}] <noreply@anthropic.com>" || true
363
- git -C "${BOOKKEEP_WORKTREE_DIR}" push origin "HEAD:${DEFAULT_BRANCH}" || true
620
+ git_push_for_project "${BOOKKEEP_WORKTREE_DIR}" origin "HEAD:${DEFAULT_BRANCH}" || true
364
621
  log "DONE: ${ELIGIBLE_PRD} ${reason}, PRD moved to done/"
365
622
  return 0
366
623
  fi
@@ -396,6 +653,7 @@ if [ -z "${EXECUTOR_PROMPT_PATH}" ]; then
396
653
  exit 1
397
654
  fi
398
655
  EXECUTOR_PROMPT_REF=$(instruction_ref_for_prompt "${PROJECT_DIR}" "${EXECUTOR_PROMPT_PATH}")
656
+ PROGRESS_PUSH_CMD=$(project_git_push_command "${BRANCH_NAME}")
399
657
 
400
658
  if [ -n "${ISSUE_NUMBER}" ]; then
401
659
  PROMPT="Implement the following PRD (GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}):
@@ -418,13 +676,13 @@ Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline:
418
676
  5. Run the project's verify/test command between waves to catch issues early
419
677
  Follow all CLAUDE.md conventions (if present).
420
678
 
421
- ## Finalize — open the PR FIRST, then verify
422
- - Commit all changes, push, and open the PR immediately:
423
- git push -u origin ${BRANCH_NAME}
424
- gh pr create --title \"feat: <short title>\" --body \"Closes #${ISSUE_NUMBER}
425
-
426
- <summary>\"
427
- - After the PR is open, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
679
+ ## PR Lifecycle
680
+ - The controller owns PR lifecycle and labels for this branch
681
+ - If a draft PR already exists, do NOT create another one and do NOT edit its labels
682
+ - If no PR exists yet, do NOT create one manually; keep pushing to ${BRANCH_NAME} and the controller will open/update it automatically
683
+ - After each completed phase/wave milestone, commit and push progress to the existing branch:
684
+ ${PROGRESS_PUSH_CMD}
685
+ - When all implementation is complete, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
428
686
  - STOP immediately after the final push — do NOT do any additional work, visual checks, or exploration.
429
687
  - Do NOT process any other issues — only issue #${ISSUE_NUMBER}"
430
688
  else
@@ -447,11 +705,13 @@ Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline:
447
705
  5. Run the project's verify/test command between waves to catch issues early
448
706
  Follow all CLAUDE.md conventions (if present).
449
707
 
450
- ## Finalize — open the PR FIRST, then verify
451
- - Commit all changes, push, and open the PR immediately:
452
- git push -u origin ${BRANCH_NAME}
453
- gh pr create --title \"feat: <short title>\" --body \"<summary referencing PRD>\"
454
- - After the PR is open, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
708
+ ## PR Lifecycle
709
+ - The controller owns PR lifecycle and labels for this branch
710
+ - If a draft PR already exists, do NOT create another one and do NOT edit its labels
711
+ - If no PR exists yet, do NOT create one manually; keep pushing to ${BRANCH_NAME} and the controller will open/update it automatically
712
+ - After each completed phase/wave milestone, commit and push progress to the existing branch:
713
+ ${PROGRESS_PUSH_CMD}
714
+ - When all implementation is complete, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
455
715
  - STOP immediately after the final push — do NOT do any additional work, visual checks, or exploration.
456
716
  - Do NOT move the PRD to done/ — the cron script handles that
457
717
  - Do NOT process any other PRDs — only ${ELIGIBLE_PRD}"
@@ -507,6 +767,19 @@ if ! assert_isolated_worktree "${PROJECT_DIR}" "${WORKTREE_DIR}" "executor"; the
507
767
  exit 1
508
768
  fi
509
769
 
770
+ if ! ensure_executor_pr; then
771
+ log "FAIL: Unable to create or reuse executor PR for ${BRANCH_NAME}"
772
+ restore_issue_to_ready "Failed to create or reuse the draft PR for branch ${BRANCH_NAME}. Moved back to Ready for retry."
773
+ night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
774
+ emit_result "failure" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}|reason=pr_setup_failed|detail=$(latest_failure_detail "${LOG_FILE}")"
775
+ exit 1
776
+ fi
777
+
778
+ if [ -n "${ISSUE_NUMBER}" ] && [ -n "${NW_CLI}" ] && [ -n "${EXECUTOR_PR_URL}" ] && [ "${RESUME_FROM_EXISTING_PR}" != "1" ]; then
779
+ "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
780
+ --body "Draft PR opened at executor start: ${EXECUTOR_PR_URL} (labels: \`${NW_EXECUTOR_PARTIAL_LABEL}\`, \`${NW_EXECUTOR_RESUMABLE_LABEL}\`)." 2>>"${LOG_FILE}" || true
781
+ fi
782
+
510
783
  # Sandbox: prevent the agent from modifying crontab during execution
511
784
  export NW_EXECUTION_CONTEXT=agent
512
785
 
@@ -567,6 +840,15 @@ while [ "${ATTEMPT}" -lt "${MAX_RETRIES}" ]; do
567
840
  BACKOFF_MIN=$(( BACKOFF / 60 ))
568
841
  log "RATE-LIMITED: Attempt ${ATTEMPT}/${MAX_RETRIES}, retrying in ${BACKOFF_MIN}m"
569
842
  sleep "${BACKOFF}"
843
+ elif check_network_error "${LOG_FILE}" "${LOG_LINE_BEFORE}"; then
844
+ # Transient API network error — retry with a short backoff
845
+ ATTEMPT=$((ATTEMPT + 1))
846
+ if [ "${ATTEMPT}" -ge "${MAX_RETRIES}" ]; then
847
+ log "NETWORK-ERROR: All ${MAX_RETRIES} attempts exhausted for ${ELIGIBLE_PRD}"
848
+ break
849
+ fi
850
+ log "NETWORK-ERROR: Attempt ${ATTEMPT}/${MAX_RETRIES}, retrying in 60s"
851
+ sleep 60
570
852
  elif check_context_exhausted "${LOG_FILE}" "${LOG_LINE_BEFORE}"; then
571
853
  # Context window exhausted — checkpoint progress and resume in a fresh session
572
854
  ATTEMPT=$((ATTEMPT + 1))
@@ -576,7 +858,8 @@ while [ "${ATTEMPT}" -lt "${MAX_RETRIES}" ]; do
576
858
  fi
577
859
  log "CONTEXT-EXHAUSTED: Session ${ATTEMPT_NUM} hit context limit — checkpointing and resuming (${ATTEMPT}/${MAX_RETRIES})"
578
860
  checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}"
579
- git -C "${WORKTREE_DIR}" push origin "${BRANCH_NAME}" --force-with-lease >> "${LOG_FILE}" 2>&1 || true
861
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
862
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
580
863
  # Switch prompt to "continue" mode for the next attempt (fresh context)
581
864
  if [ -n "${ISSUE_NUMBER}" ]; then
582
865
  PROMPT="Continue implementing PRD (GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}).
@@ -599,13 +882,13 @@ The previous session ran out of context window. Progress has been committed on b
599
882
  Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline for remaining phases only.
600
883
  Follow all CLAUDE.md conventions (if present).
601
884
 
602
- ## Finalize — open the PR FIRST, then verify
603
- - Commit all changes, push, and open the PR immediately:
604
- git push -u origin ${BRANCH_NAME}
605
- gh pr create --title \"feat: <short title>\" --body \"Closes #${ISSUE_NUMBER}
606
-
607
- <summary>\"
608
- - After the PR is open, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
885
+ ## PR Lifecycle
886
+ - The controller owns PR lifecycle and labels for this branch
887
+ - If a draft PR already exists, do NOT create another one and do NOT edit its labels
888
+ - If no PR exists yet, do NOT create one manually; keep pushing to ${BRANCH_NAME} and the controller will open/update it automatically
889
+ - After each completed phase/wave milestone, commit and push progress to the existing branch:
890
+ ${PROGRESS_PUSH_CMD}
891
+ - When all implementation is complete, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
609
892
  - STOP immediately after the final push — do NOT do any additional work, visual checks, or exploration.
610
893
  - Do NOT process any other issues — only issue #${ISSUE_NUMBER}"
611
894
  else
@@ -629,11 +912,13 @@ The previous session ran out of context window. Progress has been committed on b
629
912
  Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline for remaining phases only.
630
913
  Follow all CLAUDE.md conventions (if present).
631
914
 
632
- ## Finalize — open the PR FIRST, then verify
633
- - Commit all changes, push, and open the PR immediately:
634
- git push -u origin ${BRANCH_NAME}
635
- gh pr create --title \"feat: <short title>\" --body \"<summary referencing PRD>\"
636
- - After the PR is open, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
915
+ ## PR Lifecycle
916
+ - The controller owns PR lifecycle and labels for this branch
917
+ - If a draft PR already exists, do NOT create another one and do NOT edit its labels
918
+ - If no PR exists yet, do NOT create one manually; keep pushing to ${BRANCH_NAME} and the controller will open/update it automatically
919
+ - After each completed phase/wave milestone, commit and push progress to the existing branch:
920
+ ${PROGRESS_PUSH_CMD}
921
+ - When all implementation is complete, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
637
922
  - STOP immediately after the final push — do NOT do any additional work, visual checks, or exploration.
638
923
  - Do NOT move the PRD to done/ — the cron script handles that
639
924
  - Do NOT process any other PRDs — only ${ELIGIBLE_PRD}"
@@ -793,9 +1078,20 @@ if [ "${EXIT_CODE}" -eq 0 ] && grep -qiF 'Exceeded USD budget' "${LOG_FILE}" 2>/
793
1078
  EXIT_CODE=1
794
1079
  fi
795
1080
 
1081
+ if [ ${EXIT_CODE} -eq 0 ]; then
1082
+ if ! ensure_executor_pr; then
1083
+ log "FAIL: Unable to create or reuse executor PR for ${BRANCH_NAME} after provider completion"
1084
+ night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
1085
+ emit_result "failure" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}|reason=pr_setup_failed|detail=$(latest_failure_detail "${LOG_FILE}")"
1086
+ EXIT_CODE=1
1087
+ fi
1088
+ fi
1089
+
796
1090
  if [ ${EXIT_CODE} -eq 0 ]; then
797
1091
  OPEN_PR_COUNT=$(count_prs_for_branch open "${BRANCH_NAME}")
798
1092
  if [ "${OPEN_PR_COUNT}" -gt 0 ]; then
1093
+ refresh_executor_pr_metadata
1094
+ mark_executor_pr_ready_for_review || true
799
1095
  if [ -n "${ISSUE_NUMBER}" ]; then
800
1096
  # Board mode: comment with PR URL, then close issue and move to Done
801
1097
  PR_URL=$(gh pr list --state open --json headRefName,url \
@@ -851,6 +1147,8 @@ if [ ${EXIT_CODE} -eq 0 ]; then
851
1147
  elif [ ${EXIT_CODE} -eq 124 ]; then
852
1148
  log "TIMEOUT: Session limit hit after ${SESSION_MAX_RUNTIME}s while processing ${ELIGIBLE_PRD}"
853
1149
  checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}"
1150
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
1151
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
854
1152
  if [ -n "${ISSUE_NUMBER}" ]; then
855
1153
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
856
1154
  if [ "${SESSION_MAX_RUNTIME}" != "${MAX_RUNTIME}" ]; then
@@ -881,7 +1179,8 @@ elif check_context_exhausted "${LOG_FILE}" "${LOG_LINE_BEFORE}"; then
881
1179
  # All resume attempts for context exhaustion were used up
882
1180
  log "FAIL: Context window exhausted after ${MAX_RETRIES} resume attempts for ${ELIGIBLE_PRD}"
883
1181
  checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}"
884
- git -C "${WORKTREE_DIR}" push origin "${BRANCH_NAME}" --force-with-lease >> "${LOG_FILE}" 2>&1 || true
1182
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
1183
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
885
1184
  if [ -n "${ISSUE_NUMBER}" ]; then
886
1185
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
887
1186
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" \