@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.
Files changed (208) hide show
  1. package/dist/cli.js +2501 -1387
  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-cron.sh +344 -53
  6. package/dist/scripts/night-watch-helpers.sh +208 -7
  7. package/dist/scripts/night-watch-merger-cron.sh +8 -2
  8. package/dist/scripts/night-watch-plan-cron.sh +1 -1
  9. package/dist/scripts/night-watch-pr-resolver-cron.sh +7 -3
  10. package/dist/scripts/night-watch-pr-reviewer-cron.sh +11 -8
  11. package/dist/scripts/night-watch-qa-cron.sh +8 -3
  12. package/dist/scripts/night-watch-slicer-cron.sh +1 -1
  13. package/dist/web/assets/index-B6E6kOoR.js +406 -0
  14. package/dist/web/assets/index-DIMUXIP8.css +1 -0
  15. package/dist/web/assets/index-Ds8OqaCa.css +1 -0
  16. package/dist/web/assets/index-NR27JE3b.js +406 -0
  17. package/dist/web/index.html +2 -2
  18. package/package.json +1 -1
  19. package/dist/cli.d.ts +0 -3
  20. package/dist/cli.js.map +0 -1
  21. package/dist/commands/analytics.d.ts +0 -14
  22. package/dist/commands/analytics.js +0 -69
  23. package/dist/commands/analytics.js.map +0 -1
  24. package/dist/commands/audit.d.ts +0 -19
  25. package/dist/commands/audit.js +0 -144
  26. package/dist/commands/audit.js.map +0 -1
  27. package/dist/commands/board.d.ts +0 -9
  28. package/dist/commands/board.js +0 -702
  29. package/dist/commands/board.js.map +0 -1
  30. package/dist/commands/cancel.d.ts +0 -46
  31. package/dist/commands/cancel.js +0 -239
  32. package/dist/commands/cancel.js.map +0 -1
  33. package/dist/commands/cron.d.ts +0 -8
  34. package/dist/commands/cron.js +0 -134
  35. package/dist/commands/cron.js.map +0 -1
  36. package/dist/commands/dashboard/tab-actions.d.ts +0 -10
  37. package/dist/commands/dashboard/tab-actions.js +0 -247
  38. package/dist/commands/dashboard/tab-actions.js.map +0 -1
  39. package/dist/commands/dashboard/tab-config.d.ts +0 -21
  40. package/dist/commands/dashboard/tab-config.js +0 -873
  41. package/dist/commands/dashboard/tab-config.js.map +0 -1
  42. package/dist/commands/dashboard/tab-logs.d.ts +0 -10
  43. package/dist/commands/dashboard/tab-logs.js +0 -202
  44. package/dist/commands/dashboard/tab-logs.js.map +0 -1
  45. package/dist/commands/dashboard/tab-schedules.d.ts +0 -21
  46. package/dist/commands/dashboard/tab-schedules.js +0 -320
  47. package/dist/commands/dashboard/tab-schedules.js.map +0 -1
  48. package/dist/commands/dashboard/tab-status.d.ts +0 -32
  49. package/dist/commands/dashboard/tab-status.js +0 -424
  50. package/dist/commands/dashboard/tab-status.js.map +0 -1
  51. package/dist/commands/dashboard/types.d.ts +0 -42
  52. package/dist/commands/dashboard/types.js +0 -5
  53. package/dist/commands/dashboard/types.js.map +0 -1
  54. package/dist/commands/dashboard.d.ts +0 -11
  55. package/dist/commands/dashboard.js +0 -242
  56. package/dist/commands/dashboard.js.map +0 -1
  57. package/dist/commands/doctor.d.ts +0 -16
  58. package/dist/commands/doctor.js +0 -195
  59. package/dist/commands/doctor.js.map +0 -1
  60. package/dist/commands/history.d.ts +0 -7
  61. package/dist/commands/history.js +0 -49
  62. package/dist/commands/history.js.map +0 -1
  63. package/dist/commands/init.d.ts +0 -45
  64. package/dist/commands/init.js +0 -777
  65. package/dist/commands/init.js.map +0 -1
  66. package/dist/commands/install.d.ts +0 -65
  67. package/dist/commands/install.js +0 -405
  68. package/dist/commands/install.js.map +0 -1
  69. package/dist/commands/logs.d.ts +0 -15
  70. package/dist/commands/logs.js +0 -155
  71. package/dist/commands/logs.js.map +0 -1
  72. package/dist/commands/merge.d.ts +0 -26
  73. package/dist/commands/merge.js +0 -159
  74. package/dist/commands/merge.js.map +0 -1
  75. package/dist/commands/notify.d.ts +0 -7
  76. package/dist/commands/notify.js +0 -43
  77. package/dist/commands/notify.js.map +0 -1
  78. package/dist/commands/plan.d.ts +0 -19
  79. package/dist/commands/plan.js +0 -88
  80. package/dist/commands/plan.js.map +0 -1
  81. package/dist/commands/prd-state.d.ts +0 -12
  82. package/dist/commands/prd-state.js +0 -47
  83. package/dist/commands/prd-state.js.map +0 -1
  84. package/dist/commands/prd.d.ts +0 -18
  85. package/dist/commands/prd.js +0 -363
  86. package/dist/commands/prd.js.map +0 -1
  87. package/dist/commands/prds.d.ts +0 -13
  88. package/dist/commands/prds.js +0 -194
  89. package/dist/commands/prds.js.map +0 -1
  90. package/dist/commands/prs.d.ts +0 -14
  91. package/dist/commands/prs.js +0 -104
  92. package/dist/commands/prs.js.map +0 -1
  93. package/dist/commands/qa.d.ts +0 -34
  94. package/dist/commands/qa.js +0 -214
  95. package/dist/commands/qa.js.map +0 -1
  96. package/dist/commands/queue.d.ts +0 -8
  97. package/dist/commands/queue.js +0 -363
  98. package/dist/commands/queue.js.map +0 -1
  99. package/dist/commands/resolve.d.ts +0 -26
  100. package/dist/commands/resolve.js +0 -186
  101. package/dist/commands/resolve.js.map +0 -1
  102. package/dist/commands/retry.d.ts +0 -9
  103. package/dist/commands/retry.js +0 -71
  104. package/dist/commands/retry.js.map +0 -1
  105. package/dist/commands/review.d.ts +0 -82
  106. package/dist/commands/review.js +0 -479
  107. package/dist/commands/review.js.map +0 -1
  108. package/dist/commands/run.d.ts +0 -73
  109. package/dist/commands/run.js +0 -509
  110. package/dist/commands/run.js.map +0 -1
  111. package/dist/commands/serve.d.ts +0 -19
  112. package/dist/commands/serve.js +0 -142
  113. package/dist/commands/serve.js.map +0 -1
  114. package/dist/commands/shared/env-builder.d.ts +0 -49
  115. package/dist/commands/shared/env-builder.js +0 -150
  116. package/dist/commands/shared/env-builder.js.map +0 -1
  117. package/dist/commands/slice.d.ts +0 -35
  118. package/dist/commands/slice.js +0 -316
  119. package/dist/commands/slice.js.map +0 -1
  120. package/dist/commands/state.d.ts +0 -8
  121. package/dist/commands/state.js +0 -54
  122. package/dist/commands/state.js.map +0 -1
  123. package/dist/commands/status.d.ts +0 -14
  124. package/dist/commands/status.js +0 -297
  125. package/dist/commands/status.js.map +0 -1
  126. package/dist/commands/summary.d.ts +0 -14
  127. package/dist/commands/summary.js +0 -193
  128. package/dist/commands/summary.js.map +0 -1
  129. package/dist/commands/uninstall.d.ts +0 -25
  130. package/dist/commands/uninstall.js +0 -134
  131. package/dist/commands/uninstall.js.map +0 -1
  132. package/dist/commands/update.d.ts +0 -22
  133. package/dist/commands/update.js +0 -90
  134. package/dist/commands/update.js.map +0 -1
  135. package/dist/web/assets/index-2JY0x_Ij.js +0 -381
  136. package/dist/web/assets/index-3h8pgmqL.css +0 -1
  137. package/dist/web/assets/index-B-wbyZq7.js +0 -386
  138. package/dist/web/assets/index-B1BnOpiO.css +0 -1
  139. package/dist/web/assets/index-B3CnV08_.js +0 -365
  140. package/dist/web/assets/index-B5QjuFh9.css +0 -1
  141. package/dist/web/assets/index-B8FW2ecQ.js +0 -370
  142. package/dist/web/assets/index-BFxPiKyy.js +0 -381
  143. package/dist/web/assets/index-BGqNh_Da.js +0 -365
  144. package/dist/web/assets/index-BIONU0qz.css +0 -1
  145. package/dist/web/assets/index-B_l_3wnA.js +0 -370
  146. package/dist/web/assets/index-Ba-4YvTQ.js +0 -365
  147. package/dist/web/assets/index-Bbb4-39N.js +0 -370
  148. package/dist/web/assets/index-BdgdShEN.js +0 -365
  149. package/dist/web/assets/index-BhiC4Z-G.js +0 -381
  150. package/dist/web/assets/index-BjhCFjZi.js +0 -381
  151. package/dist/web/assets/index-BlRxmrnQ.css +0 -1
  152. package/dist/web/assets/index-BqwbXsHS.js +0 -365
  153. package/dist/web/assets/index-BsC7RT48.css +0 -1
  154. package/dist/web/assets/index-Bvh8XI8_.js +0 -370
  155. package/dist/web/assets/index-C01r2ymn.js +0 -381
  156. package/dist/web/assets/index-C51Rbsmk.js +0 -381
  157. package/dist/web/assets/index-C7lMNxRE.js +0 -370
  158. package/dist/web/assets/index-CJLObgsn.js +0 -386
  159. package/dist/web/assets/index-CLuRf7Zt.js +0 -381
  160. package/dist/web/assets/index-CM3xFd3e.css +0 -1
  161. package/dist/web/assets/index-CNkBtDK7.js +0 -370
  162. package/dist/web/assets/index-CPQbZ1BL.css +0 -1
  163. package/dist/web/assets/index-CTy5dUDU.css +0 -1
  164. package/dist/web/assets/index-CU15COKs.js +0 -370
  165. package/dist/web/assets/index-CiRJZI4z.js +0 -386
  166. package/dist/web/assets/index-Cp7RYjoy.css +0 -1
  167. package/dist/web/assets/index-CvPkZOWT.js +0 -381
  168. package/dist/web/assets/index-CvUk-33B.css +0 -1
  169. package/dist/web/assets/index-Cvmj-oF6.css +0 -1
  170. package/dist/web/assets/index-CxE5iQVO.js +0 -381
  171. package/dist/web/assets/index-D7lZQpFV.js +0 -365
  172. package/dist/web/assets/index-DAyP4GOi.css +0 -1
  173. package/dist/web/assets/index-DCG0n8Kg.js +0 -386
  174. package/dist/web/assets/index-DEEI8cyF.css +0 -1
  175. package/dist/web/assets/index-DF99BowV.js +0 -381
  176. package/dist/web/assets/index-DGWsvFj6.css +0 -1
  177. package/dist/web/assets/index-DGpU39Cp.css +0 -1
  178. package/dist/web/assets/index-DI4kFgOi.js +0 -370
  179. package/dist/web/assets/index-DIyTcPw5.css +0 -1
  180. package/dist/web/assets/index-DTsfDC7m.js +0 -381
  181. package/dist/web/assets/index-DcgNAi4A.js +0 -386
  182. package/dist/web/assets/index-DgOAgkZy.css +0 -1
  183. package/dist/web/assets/index-DnHkqbOa.js +0 -386
  184. package/dist/web/assets/index-DnR7Idcf.css +0 -1
  185. package/dist/web/assets/index-DpVirMEe.css +0 -1
  186. package/dist/web/assets/index-DsYIWZ86.css +0 -1
  187. package/dist/web/assets/index-DtrDkci5.js +0 -381
  188. package/dist/web/assets/index-DyjIth5M.js +0 -386
  189. package/dist/web/assets/index-FwIKfHPL.css +0 -1
  190. package/dist/web/assets/index-IKrZymWk.css +0 -1
  191. package/dist/web/assets/index-MA6fM0ab.js +0 -381
  192. package/dist/web/assets/index-N_QxaSEg.css +0 -1
  193. package/dist/web/assets/index-OcU-0TCQ.css +0 -1
  194. package/dist/web/assets/index-OyhrmG-L.js +0 -381
  195. package/dist/web/assets/index-SQlBKu_s.js +0 -386
  196. package/dist/web/assets/index-Sv2B60J4.js +0 -370
  197. package/dist/web/assets/index-Vgyivb5u.js +0 -365
  198. package/dist/web/assets/index-ZABWMEZR.js +0 -381
  199. package/dist/web/assets/index-ZE5lOeJp.js +0 -386
  200. package/dist/web/assets/index-aCHmkAcJ.css +0 -1
  201. package/dist/web/assets/index-bFijnpuU.js +0 -381
  202. package/dist/web/assets/index-bUPZgSoZ.css +0 -1
  203. package/dist/web/assets/index-mz1VIYsP.css +0 -1
  204. package/dist/web/assets/index-oOp_MFeE.js +0 -376
  205. package/dist/web/assets/index-rfU713Zm.js +0 -386
  206. package/dist/web/assets/index-tuNH9gmb.js +0 -448
  207. package/dist/web/assets/index-viSwHyDD.js +0 -365
  208. 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;AAsDpC,wBAAgB,kBAAkB,IAAI,OAAO,CAmU5C;AA8DD,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"}
@@ -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
@@ -250,6 +311,194 @@ count_prs_for_branch() {
250
311
  echo "${count:-0}"
251
312
  }
252
313
 
314
+ extract_prd_title() {
315
+ local prd_path=""
316
+ local title=""
317
+
318
+ if [ -n "${ISSUE_TITLE_RAW}" ]; then
319
+ printf '%s' "${ISSUE_TITLE_RAW}"
320
+ return 0
321
+ fi
322
+
323
+ if [ -f "${PRD_DIR}/${ELIGIBLE_PRD}" ]; then
324
+ prd_path="${PRD_DIR}/${ELIGIBLE_PRD}"
325
+ elif [ -f "${PROJECT_DIR}/${PRD_DIR_REL}/${ELIGIBLE_PRD}" ]; then
326
+ prd_path="${PROJECT_DIR}/${PRD_DIR_REL}/${ELIGIBLE_PRD}"
327
+ fi
328
+
329
+ if [ -n "${prd_path}" ]; then
330
+ title=$(awk '/^[[:space:]]*#[[:space:]]+/ { sub(/^[[:space:]]*#[[:space:]]+/, "", $0); print; exit }' "${prd_path}" 2>/dev/null || true)
331
+ fi
332
+
333
+ if [ -n "${title}" ]; then
334
+ printf '%s' "${title}"
335
+ else
336
+ printf '%s' "${PRD_NAME}"
337
+ fi
338
+ }
339
+
340
+ build_executor_pr_title() {
341
+ local raw_title=""
342
+
343
+ raw_title=$(extract_prd_title)
344
+ raw_title=$(printf '%s' "${raw_title}" | tr '\r\n' ' ' | sed -E 's/[[:space:]]+/ /g; s/^[[:space:]]+//; s/[[:space:]]+$//')
345
+ if [ -z "${raw_title}" ]; then
346
+ raw_title="${PRD_NAME}"
347
+ fi
348
+
349
+ if printf '%s' "${raw_title}" | grep -Eqi '^feat:'; then
350
+ printf '%s' "${raw_title}"
351
+ else
352
+ printf 'feat: %s' "${raw_title}"
353
+ fi
354
+ }
355
+
356
+ build_executor_pr_body() {
357
+ local status_blurb=""
358
+
359
+ status_blurb="Status labels:
360
+ - ${NW_EXECUTOR_PARTIAL_LABEL}: implementation is in progress and intentionally incomplete
361
+ - ${NW_EXECUTOR_RESUMABLE_LABEL}: resume this PR before starting new work
362
+ - ${NW_EXECUTOR_READY_REVIEW_LABEL}: implementation is complete and ready for review"
363
+
364
+ if [ -n "${ISSUE_NUMBER}" ]; then
365
+ printf 'Closes #%s\n\nNight Watch manages this draft PR automatically so progress is preserved across retries and timeouts.\n\n%s\n' \
366
+ "${ISSUE_NUMBER}" \
367
+ "${status_blurb}"
368
+ else
369
+ 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' \
370
+ "${PRD_DIR_REL}" \
371
+ "${ELIGIBLE_PRD}" \
372
+ "${status_blurb}"
373
+ fi
374
+ }
375
+
376
+ refresh_executor_pr_metadata() {
377
+ EXECUTOR_PR_JSON=$(find_open_pr_for_branch "${BRANCH_NAME}" || true)
378
+ EXECUTOR_PR_NUMBER=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.number // empty' 2>/dev/null || true)
379
+ EXECUTOR_PR_URL=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.url // empty' 2>/dev/null || true)
380
+ EXECUTOR_PR_DRAFT=$(printf '%s' "${EXECUTOR_PR_JSON}" | jq -r '.isDraft // false' 2>/dev/null || true)
381
+ }
382
+
383
+ sync_executor_pr_status() {
384
+ local add_labels="${1:-}"
385
+ local remove_labels="${2:-}"
386
+ local mark_ready="${3:-0}"
387
+
388
+ if [ -z "${EXECUTOR_PR_NUMBER}" ]; then
389
+ return 1
390
+ fi
391
+
392
+ ensure_executor_status_labels
393
+
394
+ if [ -n "${add_labels}" ]; then
395
+ gh pr edit "${EXECUTOR_PR_NUMBER}" --add-label "${add_labels}" >> "${LOG_FILE}" 2>&1 || true
396
+ fi
397
+ if [ -n "${remove_labels}" ]; then
398
+ gh pr edit "${EXECUTOR_PR_NUMBER}" --remove-label "${remove_labels}" >> "${LOG_FILE}" 2>&1 || true
399
+ fi
400
+ if [ "${mark_ready}" = "1" ]; then
401
+ gh pr ready "${EXECUTOR_PR_NUMBER}" >> "${LOG_FILE}" 2>&1 || true
402
+ fi
403
+
404
+ refresh_executor_pr_metadata
405
+ return 0
406
+ }
407
+
408
+ mark_executor_pr_incomplete() {
409
+ sync_executor_pr_status \
410
+ "${NW_EXECUTOR_PARTIAL_LABEL},${NW_EXECUTOR_RESUMABLE_LABEL}" \
411
+ "${NW_EXECUTOR_READY_REVIEW_LABEL}" \
412
+ "0"
413
+ }
414
+
415
+ mark_executor_pr_ready_for_review() {
416
+ sync_executor_pr_status \
417
+ "${NW_EXECUTOR_READY_REVIEW_LABEL}" \
418
+ "${NW_EXECUTOR_PARTIAL_LABEL},${NW_EXECUTOR_RESUMABLE_LABEL}" \
419
+ "1"
420
+ }
421
+
422
+ push_executor_branch() {
423
+ local push_mode="${1:-update}"
424
+
425
+ if [ "${push_mode}" = "initial" ]; then
426
+ if git_push_for_project "${WORKTREE_DIR}" -u origin "${BRANCH_NAME}" >> "${LOG_FILE}" 2>&1; then
427
+ return 0
428
+ fi
429
+ fi
430
+
431
+ if git_push_for_project "${WORKTREE_DIR}" origin "${BRANCH_NAME}" --force-with-lease >> "${LOG_FILE}" 2>&1; then
432
+ return 0
433
+ fi
434
+
435
+ git_push_for_project "${WORKTREE_DIR}" -u origin "${BRANCH_NAME}" >> "${LOG_FILE}" 2>&1
436
+ }
437
+
438
+ executor_branch_has_commits_ahead_of_base() {
439
+ local base_ref=""
440
+ local ahead_count="0"
441
+
442
+ if ! base_ref=$(resolve_worktree_base_ref "${WORKTREE_DIR}" "${DEFAULT_BRANCH}" 2>/dev/null); then
443
+ return 1
444
+ fi
445
+
446
+ ahead_count=$(git -C "${WORKTREE_DIR}" rev-list --count "${base_ref}..HEAD" 2>/dev/null || echo "0")
447
+ [ "${ahead_count}" -gt 0 ]
448
+ }
449
+
450
+ ensure_executor_pr() {
451
+ local pr_title=""
452
+ local pr_body=""
453
+ local create_output=""
454
+
455
+ refresh_executor_pr_metadata
456
+ if [ -n "${EXECUTOR_PR_NUMBER}" ]; then
457
+ log "PR: Reusing existing open PR #${EXECUTOR_PR_NUMBER} for ${BRANCH_NAME}"
458
+ mark_executor_pr_incomplete
459
+ return 0
460
+ fi
461
+
462
+ pr_title=$(build_executor_pr_title)
463
+ pr_body=$(build_executor_pr_body)
464
+
465
+ if ! executor_branch_has_commits_ahead_of_base; then
466
+ log "PR: Deferring draft PR creation for ${BRANCH_NAME} until it has commits beyond ${DEFAULT_BRANCH}"
467
+ return 0
468
+ fi
469
+
470
+ log "PR: Creating draft PR for ${BRANCH_NAME}"
471
+ if ! push_executor_branch "initial"; then
472
+ log "WARN: Initial push for ${BRANCH_NAME} failed before PR creation"
473
+ fi
474
+
475
+ ensure_executor_status_labels
476
+ if ! create_output=$(
477
+ gh pr create \
478
+ --draft \
479
+ --base "${DEFAULT_BRANCH}" \
480
+ --head "${BRANCH_NAME}" \
481
+ --title "${pr_title}" \
482
+ --body "${pr_body}" 2>> "${LOG_FILE}"
483
+ ); then
484
+ log "FAIL: gh pr create failed for ${BRANCH_NAME}"
485
+ return 1
486
+ fi
487
+
488
+ refresh_executor_pr_metadata
489
+ if [ -z "${EXECUTOR_PR_URL}" ]; then
490
+ EXECUTOR_PR_URL=$(printf '%s' "${create_output}" | grep -Eo 'https://[^[:space:]]+/pull/[0-9]+' | tail -n 1 || true)
491
+ fi
492
+ if [ -n "${EXECUTOR_PR_NUMBER}" ]; then
493
+ mark_executor_pr_incomplete
494
+ log "PR: Draft PR ready at ${EXECUTOR_PR_URL:-unknown} for ${BRANCH_NAME}"
495
+ else
496
+ log "WARN: gh pr create succeeded for ${BRANCH_NAME}, but PR metadata lookup did not resolve a number yet"
497
+ fi
498
+
499
+ return 0
500
+ }
501
+
253
502
  checkpoint_timeout_progress() {
254
503
  local worktree_dir="${1:?worktree_dir required}"
255
504
  local branch_name="${2:?branch_name required}"
@@ -360,7 +609,7 @@ finalize_prd_done() {
360
609
  git -C "${BOOKKEEP_WORKTREE_DIR}" commit -m "chore: mark ${ELIGIBLE_PRD} as done (${reason})
361
610
 
362
611
  Co-Authored-By: Night Watch [${EFFECTIVE_PROVIDER_LABEL}] <noreply@anthropic.com>" || true
363
- git -C "${BOOKKEEP_WORKTREE_DIR}" push origin "HEAD:${DEFAULT_BRANCH}" || true
612
+ git_push_for_project "${BOOKKEEP_WORKTREE_DIR}" origin "HEAD:${DEFAULT_BRANCH}" || true
364
613
  log "DONE: ${ELIGIBLE_PRD} ${reason}, PRD moved to done/"
365
614
  return 0
366
615
  fi
@@ -396,6 +645,7 @@ if [ -z "${EXECUTOR_PROMPT_PATH}" ]; then
396
645
  exit 1
397
646
  fi
398
647
  EXECUTOR_PROMPT_REF=$(instruction_ref_for_prompt "${PROJECT_DIR}" "${EXECUTOR_PROMPT_PATH}")
648
+ PROGRESS_PUSH_CMD=$(project_git_push_command "${BRANCH_NAME}")
399
649
 
400
650
  if [ -n "${ISSUE_NUMBER}" ]; then
401
651
  PROMPT="Implement the following PRD (GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}):
@@ -418,13 +668,13 @@ Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline:
418
668
  5. Run the project's verify/test command between waves to catch issues early
419
669
  Follow all CLAUDE.md conventions (if present).
420
670
 
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.
671
+ ## PR Lifecycle
672
+ - The controller owns PR lifecycle and labels for this branch
673
+ - If a draft PR already exists, do NOT create another one and do NOT edit its labels
674
+ - If no PR exists yet, do NOT create one manually; keep pushing to ${BRANCH_NAME} and the controller will open/update it automatically
675
+ - After each completed phase/wave milestone, commit and push progress to the existing branch:
676
+ ${PROGRESS_PUSH_CMD}
677
+ - When all implementation is complete, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
428
678
  - STOP immediately after the final push — do NOT do any additional work, visual checks, or exploration.
429
679
  - Do NOT process any other issues — only issue #${ISSUE_NUMBER}"
430
680
  else
@@ -447,11 +697,13 @@ Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline:
447
697
  5. Run the project's verify/test command between waves to catch issues early
448
698
  Follow all CLAUDE.md conventions (if present).
449
699
 
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.
700
+ ## PR Lifecycle
701
+ - The controller owns PR lifecycle and labels for this branch
702
+ - If a draft PR already exists, do NOT create another one and do NOT edit its labels
703
+ - If no PR exists yet, do NOT create one manually; keep pushing to ${BRANCH_NAME} and the controller will open/update it automatically
704
+ - After each completed phase/wave milestone, commit and push progress to the existing branch:
705
+ ${PROGRESS_PUSH_CMD}
706
+ - When all implementation is complete, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
455
707
  - STOP immediately after the final push — do NOT do any additional work, visual checks, or exploration.
456
708
  - Do NOT move the PRD to done/ — the cron script handles that
457
709
  - Do NOT process any other PRDs — only ${ELIGIBLE_PRD}"
@@ -507,6 +759,19 @@ if ! assert_isolated_worktree "${PROJECT_DIR}" "${WORKTREE_DIR}" "executor"; the
507
759
  exit 1
508
760
  fi
509
761
 
762
+ if ! ensure_executor_pr; then
763
+ log "FAIL: Unable to create or reuse executor PR for ${BRANCH_NAME}"
764
+ restore_issue_to_ready "Failed to create or reuse the draft PR for branch ${BRANCH_NAME}. Moved back to Ready for retry."
765
+ night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
766
+ emit_result "failure" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}|reason=pr_setup_failed|detail=$(latest_failure_detail "${LOG_FILE}")"
767
+ exit 1
768
+ fi
769
+
770
+ if [ -n "${ISSUE_NUMBER}" ] && [ -n "${NW_CLI}" ] && [ -n "${EXECUTOR_PR_URL}" ] && [ "${RESUME_FROM_EXISTING_PR}" != "1" ]; then
771
+ "${NW_CLI}" board comment "${ISSUE_NUMBER}" \
772
+ --body "Draft PR opened at executor start: ${EXECUTOR_PR_URL} (labels: \`${NW_EXECUTOR_PARTIAL_LABEL}\`, \`${NW_EXECUTOR_RESUMABLE_LABEL}\`)." 2>>"${LOG_FILE}" || true
773
+ fi
774
+
510
775
  # Sandbox: prevent the agent from modifying crontab during execution
511
776
  export NW_EXECUTION_CONTEXT=agent
512
777
 
@@ -567,6 +832,15 @@ while [ "${ATTEMPT}" -lt "${MAX_RETRIES}" ]; do
567
832
  BACKOFF_MIN=$(( BACKOFF / 60 ))
568
833
  log "RATE-LIMITED: Attempt ${ATTEMPT}/${MAX_RETRIES}, retrying in ${BACKOFF_MIN}m"
569
834
  sleep "${BACKOFF}"
835
+ elif check_network_error "${LOG_FILE}" "${LOG_LINE_BEFORE}"; then
836
+ # Transient API network error — retry with a short backoff
837
+ ATTEMPT=$((ATTEMPT + 1))
838
+ if [ "${ATTEMPT}" -ge "${MAX_RETRIES}" ]; then
839
+ log "NETWORK-ERROR: All ${MAX_RETRIES} attempts exhausted for ${ELIGIBLE_PRD}"
840
+ break
841
+ fi
842
+ log "NETWORK-ERROR: Attempt ${ATTEMPT}/${MAX_RETRIES}, retrying in 60s"
843
+ sleep 60
570
844
  elif check_context_exhausted "${LOG_FILE}" "${LOG_LINE_BEFORE}"; then
571
845
  # Context window exhausted — checkpoint progress and resume in a fresh session
572
846
  ATTEMPT=$((ATTEMPT + 1))
@@ -576,7 +850,8 @@ while [ "${ATTEMPT}" -lt "${MAX_RETRIES}" ]; do
576
850
  fi
577
851
  log "CONTEXT-EXHAUSTED: Session ${ATTEMPT_NUM} hit context limit — checkpointing and resuming (${ATTEMPT}/${MAX_RETRIES})"
578
852
  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
853
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
854
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
580
855
  # Switch prompt to "continue" mode for the next attempt (fresh context)
581
856
  if [ -n "${ISSUE_NUMBER}" ]; then
582
857
  PROMPT="Continue implementing PRD (GitHub issue #${ISSUE_NUMBER}: ${ISSUE_TITLE_RAW}).
@@ -599,13 +874,13 @@ The previous session ran out of context window. Progress has been committed on b
599
874
  Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline for remaining phases only.
600
875
  Follow all CLAUDE.md conventions (if present).
601
876
 
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.
877
+ ## PR Lifecycle
878
+ - The controller owns PR lifecycle and labels for this branch
879
+ - If a draft PR already exists, do NOT create another one and do NOT edit its labels
880
+ - If no PR exists yet, do NOT create one manually; keep pushing to ${BRANCH_NAME} and the controller will open/update it automatically
881
+ - After each completed phase/wave milestone, commit and push progress to the existing branch:
882
+ ${PROGRESS_PUSH_CMD}
883
+ - When all implementation is complete, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
609
884
  - STOP immediately after the final push — do NOT do any additional work, visual checks, or exploration.
610
885
  - Do NOT process any other issues — only issue #${ISSUE_NUMBER}"
611
886
  else
@@ -629,11 +904,13 @@ The previous session ran out of context window. Progress has been committed on b
629
904
  Read ${EXECUTOR_PROMPT_REF} and follow the FULL execution pipeline for remaining phases only.
630
905
  Follow all CLAUDE.md conventions (if present).
631
906
 
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.
907
+ ## PR Lifecycle
908
+ - The controller owns PR lifecycle and labels for this branch
909
+ - If a draft PR already exists, do NOT create another one and do NOT edit its labels
910
+ - If no PR exists yet, do NOT create one manually; keep pushing to ${BRANCH_NAME} and the controller will open/update it automatically
911
+ - After each completed phase/wave milestone, commit and push progress to the existing branch:
912
+ ${PROGRESS_PUSH_CMD}
913
+ - When all implementation is complete, run final verification (lint, typecheck, tests). If anything fails, fix it, commit, and push again.
637
914
  - STOP immediately after the final push — do NOT do any additional work, visual checks, or exploration.
638
915
  - Do NOT move the PRD to done/ — the cron script handles that
639
916
  - Do NOT process any other PRDs — only ${ELIGIBLE_PRD}"
@@ -793,9 +1070,20 @@ if [ "${EXIT_CODE}" -eq 0 ] && grep -qiF 'Exceeded USD budget' "${LOG_FILE}" 2>/
793
1070
  EXIT_CODE=1
794
1071
  fi
795
1072
 
1073
+ if [ ${EXIT_CODE} -eq 0 ]; then
1074
+ if ! ensure_executor_pr; then
1075
+ log "FAIL: Unable to create or reuse executor PR for ${BRANCH_NAME} after provider completion"
1076
+ night_watch_history record "${PROJECT_DIR}" "${ELIGIBLE_PRD}" failure --exit-code 1 2>/dev/null || true
1077
+ emit_result "failure" "prd=${ELIGIBLE_PRD}|branch=${BRANCH_NAME}|reason=pr_setup_failed|detail=$(latest_failure_detail "${LOG_FILE}")"
1078
+ EXIT_CODE=1
1079
+ fi
1080
+ fi
1081
+
796
1082
  if [ ${EXIT_CODE} -eq 0 ]; then
797
1083
  OPEN_PR_COUNT=$(count_prs_for_branch open "${BRANCH_NAME}")
798
1084
  if [ "${OPEN_PR_COUNT}" -gt 0 ]; then
1085
+ refresh_executor_pr_metadata
1086
+ mark_executor_pr_ready_for_review || true
799
1087
  if [ -n "${ISSUE_NUMBER}" ]; then
800
1088
  # Board mode: comment with PR URL, then close issue and move to Done
801
1089
  PR_URL=$(gh pr list --state open --json headRefName,url \
@@ -851,6 +1139,8 @@ if [ ${EXIT_CODE} -eq 0 ]; then
851
1139
  elif [ ${EXIT_CODE} -eq 124 ]; then
852
1140
  log "TIMEOUT: Session limit hit after ${SESSION_MAX_RUNTIME}s while processing ${ELIGIBLE_PRD}"
853
1141
  checkpoint_timeout_progress "${WORKTREE_DIR}" "${BRANCH_NAME}" "${ELIGIBLE_PRD}"
1142
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
1143
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
854
1144
  if [ -n "${ISSUE_NUMBER}" ]; then
855
1145
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
856
1146
  if [ "${SESSION_MAX_RUNTIME}" != "${MAX_RUNTIME}" ]; then
@@ -881,7 +1171,8 @@ elif check_context_exhausted "${LOG_FILE}" "${LOG_LINE_BEFORE}"; then
881
1171
  # All resume attempts for context exhaustion were used up
882
1172
  log "FAIL: Context window exhausted after ${MAX_RETRIES} resume attempts for ${ELIGIBLE_PRD}"
883
1173
  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
1174
+ push_executor_branch "update" >> "${LOG_FILE}" 2>&1 || true
1175
+ ensure_executor_pr >> "${LOG_FILE}" 2>&1 || true
885
1176
  if [ -n "${ISSUE_NUMBER}" ]; then
886
1177
  "${NW_CLI}" board move-issue "${ISSUE_NUMBER}" --column "Ready" 2>>"${LOG_FILE}" || true
887
1178
  "${NW_CLI}" board comment "${ISSUE_NUMBER}" \