@camaradesuk/git-worktree-tools 1.8.0 → 1.10.0

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 (353) hide show
  1. package/README.md +48 -27
  2. package/dist/cli/cleanpr.js +74 -53
  3. package/dist/cli/cleanpr.js.map +1 -1
  4. package/dist/cli/cleanpr.test.js +2 -0
  5. package/dist/cli/cleanpr.test.js.map +1 -1
  6. package/dist/cli/lswt.js +32 -56
  7. package/dist/cli/lswt.js.map +1 -1
  8. package/dist/cli/lswt.test.js +17 -27
  9. package/dist/cli/lswt.test.js.map +1 -1
  10. package/dist/cli/newpr.d.ts +13 -1
  11. package/dist/cli/newpr.d.ts.map +1 -1
  12. package/dist/cli/newpr.js +350 -151
  13. package/dist/cli/newpr.js.map +1 -1
  14. package/dist/cli/newpr.test.js +314 -5
  15. package/dist/cli/newpr.test.js.map +1 -1
  16. package/dist/cli/prs.d.ts +3 -10
  17. package/dist/cli/prs.d.ts.map +1 -1
  18. package/dist/cli/prs.js +6 -168
  19. package/dist/cli/prs.js.map +1 -1
  20. package/dist/cli/prs.test.js +55 -0
  21. package/dist/cli/prs.test.js.map +1 -1
  22. package/dist/cli/wt/clean.d.ts +6 -2
  23. package/dist/cli/wt/clean.d.ts.map +1 -1
  24. package/dist/cli/wt/clean.js +401 -20
  25. package/dist/cli/wt/clean.js.map +1 -1
  26. package/dist/cli/wt/clean.test.d.ts +8 -0
  27. package/dist/cli/wt/clean.test.d.ts.map +1 -0
  28. package/dist/cli/wt/clean.test.js +624 -0
  29. package/dist/cli/wt/clean.test.js.map +1 -0
  30. package/dist/cli/wt/completion.d.ts +3 -0
  31. package/dist/cli/wt/completion.d.ts.map +1 -1
  32. package/dist/cli/wt/completion.js +80 -9
  33. package/dist/cli/wt/completion.js.map +1 -1
  34. package/dist/cli/wt/completion.test.js +102 -0
  35. package/dist/cli/wt/completion.test.js.map +1 -1
  36. package/dist/cli/wt/config.d.ts +3 -1
  37. package/dist/cli/wt/config.d.ts.map +1 -1
  38. package/dist/cli/wt/config.js +323 -32
  39. package/dist/cli/wt/config.js.map +1 -1
  40. package/dist/cli/wt/config.test.d.ts +2 -0
  41. package/dist/cli/wt/config.test.d.ts.map +1 -1
  42. package/dist/cli/wt/config.test.js +206 -26
  43. package/dist/cli/wt/config.test.js.map +1 -1
  44. package/dist/cli/wt/interactive-menu.d.ts +2 -0
  45. package/dist/cli/wt/interactive-menu.d.ts.map +1 -1
  46. package/dist/cli/wt/interactive-menu.js +346 -73
  47. package/dist/cli/wt/interactive-menu.js.map +1 -1
  48. package/dist/cli/wt/interactive-menu.test.d.ts +4 -2
  49. package/dist/cli/wt/interactive-menu.test.d.ts.map +1 -1
  50. package/dist/cli/wt/interactive-menu.test.js +383 -323
  51. package/dist/cli/wt/interactive-menu.test.js.map +1 -1
  52. package/dist/cli/wt/link.d.ts +3 -1
  53. package/dist/cli/wt/link.d.ts.map +1 -1
  54. package/dist/cli/wt/link.js +125 -38
  55. package/dist/cli/wt/link.js.map +1 -1
  56. package/dist/cli/wt/list.d.ts +4 -1
  57. package/dist/cli/wt/list.d.ts.map +1 -1
  58. package/dist/cli/wt/list.js +85 -16
  59. package/dist/cli/wt/list.js.map +1 -1
  60. package/dist/cli/wt/list.test.d.ts +10 -0
  61. package/dist/cli/wt/list.test.d.ts.map +1 -0
  62. package/dist/cli/wt/list.test.js +157 -0
  63. package/dist/cli/wt/list.test.js.map +1 -0
  64. package/dist/cli/wt/new.d.ts +8 -2
  65. package/dist/cli/wt/new.d.ts.map +1 -1
  66. package/dist/cli/wt/new.js +91 -46
  67. package/dist/cli/wt/new.js.map +1 -1
  68. package/dist/cli/wt/prs.d.ts +2 -1
  69. package/dist/cli/wt/prs.d.ts.map +1 -1
  70. package/dist/cli/wt/prs.js +3 -164
  71. package/dist/cli/wt/prs.js.map +1 -1
  72. package/dist/cli/wt/run-command.d.ts +4 -2
  73. package/dist/cli/wt/run-command.d.ts.map +1 -1
  74. package/dist/cli/wt/run-command.js +6 -4
  75. package/dist/cli/wt/run-command.js.map +1 -1
  76. package/dist/cli/wt/state.d.ts +3 -1
  77. package/dist/cli/wt/state.d.ts.map +1 -1
  78. package/dist/cli/wt/state.js +74 -10
  79. package/dist/cli/wt/state.js.map +1 -1
  80. package/dist/cli/wt/state.test.d.ts +9 -0
  81. package/dist/cli/wt/state.test.d.ts.map +1 -0
  82. package/dist/cli/wt/state.test.js +127 -0
  83. package/dist/cli/wt/state.test.js.map +1 -0
  84. package/dist/cli/wt/wt.test.d.ts +2 -2
  85. package/dist/cli/wt/wt.test.js +430 -212
  86. package/dist/cli/wt/wt.test.js.map +1 -1
  87. package/dist/cli/wt.d.ts.map +1 -1
  88. package/dist/cli/wt.js +50 -36
  89. package/dist/cli/wt.js.map +1 -1
  90. package/dist/cli/wt.unit.test.js +16 -38
  91. package/dist/cli/wt.unit.test.js.map +1 -1
  92. package/dist/cli/wtconfig.d.ts +1 -0
  93. package/dist/cli/wtconfig.d.ts.map +1 -1
  94. package/dist/cli/wtconfig.js +213 -21
  95. package/dist/cli/wtconfig.js.map +1 -1
  96. package/dist/cli/wtconfig.test.js +3 -0
  97. package/dist/cli/wtconfig.test.js.map +1 -1
  98. package/dist/cli/wtlink.js +116 -73
  99. package/dist/cli/wtlink.js.map +1 -1
  100. package/dist/cli/wtstate.js +21 -2
  101. package/dist/cli/wtstate.js.map +1 -1
  102. package/dist/e2e/wt/interactive-menu.e2e.test.js +17 -17
  103. package/dist/e2e/wt/interactive-menu.e2e.test.js.map +1 -1
  104. package/dist/lib/ai/types.d.ts +12 -0
  105. package/dist/lib/ai/types.d.ts.map +1 -1
  106. package/dist/lib/ai/types.js.map +1 -1
  107. package/dist/lib/cleanpr/args.d.ts.map +1 -1
  108. package/dist/lib/cleanpr/args.js +20 -0
  109. package/dist/lib/cleanpr/args.js.map +1 -1
  110. package/dist/lib/cleanpr/types.d.ts +6 -0
  111. package/dist/lib/cleanpr/types.d.ts.map +1 -1
  112. package/dist/lib/cleanpr/worktree-info.d.ts.map +1 -1
  113. package/dist/lib/cleanpr/worktree-info.js +1 -6
  114. package/dist/lib/cleanpr/worktree-info.js.map +1 -1
  115. package/dist/lib/cleanpr/worktree-info.test.js +10 -13
  116. package/dist/lib/cleanpr/worktree-info.test.js.map +1 -1
  117. package/dist/lib/colors.d.ts +5 -0
  118. package/dist/lib/colors.d.ts.map +1 -1
  119. package/dist/lib/colors.js +13 -6
  120. package/dist/lib/colors.js.map +1 -1
  121. package/dist/lib/config-editor.d.ts.map +1 -1
  122. package/dist/lib/config-editor.js.map +1 -1
  123. package/dist/lib/config-migration/detector.d.ts +25 -0
  124. package/dist/lib/config-migration/detector.d.ts.map +1 -0
  125. package/dist/lib/config-migration/detector.js +372 -0
  126. package/dist/lib/config-migration/detector.js.map +1 -0
  127. package/dist/lib/config-migration/detector.test.d.ts +5 -0
  128. package/dist/lib/config-migration/detector.test.d.ts.map +1 -0
  129. package/dist/lib/config-migration/detector.test.js +201 -0
  130. package/dist/lib/config-migration/detector.test.js.map +1 -0
  131. package/dist/lib/config-migration/index.d.ts +29 -0
  132. package/dist/lib/config-migration/index.d.ts.map +1 -0
  133. package/dist/lib/config-migration/index.js +33 -0
  134. package/dist/lib/config-migration/index.js.map +1 -0
  135. package/dist/lib/config-migration/reporter.d.ts +53 -0
  136. package/dist/lib/config-migration/reporter.d.ts.map +1 -0
  137. package/dist/lib/config-migration/reporter.js +257 -0
  138. package/dist/lib/config-migration/reporter.js.map +1 -0
  139. package/dist/lib/config-migration/reporter.test.d.ts +5 -0
  140. package/dist/lib/config-migration/reporter.test.d.ts.map +1 -0
  141. package/dist/lib/config-migration/reporter.test.js +305 -0
  142. package/dist/lib/config-migration/reporter.test.js.map +1 -0
  143. package/dist/lib/config-migration/runner.d.ts +46 -0
  144. package/dist/lib/config-migration/runner.d.ts.map +1 -0
  145. package/dist/lib/config-migration/runner.js +364 -0
  146. package/dist/lib/config-migration/runner.js.map +1 -0
  147. package/dist/lib/config-migration/runner.test.d.ts +5 -0
  148. package/dist/lib/config-migration/runner.test.d.ts.map +1 -0
  149. package/dist/lib/config-migration/runner.test.js +235 -0
  150. package/dist/lib/config-migration/runner.test.js.map +1 -0
  151. package/dist/lib/config-migration/types.d.ts +120 -0
  152. package/dist/lib/config-migration/types.d.ts.map +1 -0
  153. package/dist/lib/config-migration/types.js +70 -0
  154. package/dist/lib/config-migration/types.js.map +1 -0
  155. package/dist/lib/config-validation.d.ts.map +1 -1
  156. package/dist/lib/config-validation.js +6 -0
  157. package/dist/lib/config-validation.js.map +1 -1
  158. package/dist/lib/config-validation.test.js +25 -0
  159. package/dist/lib/config-validation.test.js.map +1 -1
  160. package/dist/lib/config.d.ts +31 -7
  161. package/dist/lib/config.d.ts.map +1 -1
  162. package/dist/lib/config.js +2 -0
  163. package/dist/lib/config.js.map +1 -1
  164. package/dist/lib/config.test.js +3 -15
  165. package/dist/lib/config.test.js.map +1 -1
  166. package/dist/lib/constants.d.ts +12 -4
  167. package/dist/lib/constants.d.ts.map +1 -1
  168. package/dist/lib/constants.js +24 -5
  169. package/dist/lib/constants.js.map +1 -1
  170. package/dist/lib/constants.test.js +88 -29
  171. package/dist/lib/constants.test.js.map +1 -1
  172. package/dist/lib/deprecation.d.ts +18 -0
  173. package/dist/lib/deprecation.d.ts.map +1 -0
  174. package/dist/lib/deprecation.js +28 -0
  175. package/dist/lib/deprecation.js.map +1 -0
  176. package/dist/lib/deprecation.test.d.ts +2 -0
  177. package/dist/lib/deprecation.test.d.ts.map +1 -0
  178. package/dist/lib/deprecation.test.js +71 -0
  179. package/dist/lib/deprecation.test.js.map +1 -0
  180. package/dist/lib/hooks/confirmation.d.ts +49 -0
  181. package/dist/lib/hooks/confirmation.d.ts.map +1 -0
  182. package/dist/lib/hooks/confirmation.js +147 -0
  183. package/dist/lib/hooks/confirmation.js.map +1 -0
  184. package/dist/lib/hooks/confirmation.test.d.ts +7 -0
  185. package/dist/lib/hooks/confirmation.test.d.ts.map +1 -0
  186. package/dist/lib/hooks/confirmation.test.js +300 -0
  187. package/dist/lib/hooks/confirmation.test.js.map +1 -0
  188. package/dist/lib/hooks/executor.d.ts +16 -1
  189. package/dist/lib/hooks/executor.d.ts.map +1 -1
  190. package/dist/lib/hooks/executor.js +53 -4
  191. package/dist/lib/hooks/executor.js.map +1 -1
  192. package/dist/lib/hooks/index.d.ts +4 -2
  193. package/dist/lib/hooks/index.d.ts.map +1 -1
  194. package/dist/lib/hooks/index.js +3 -2
  195. package/dist/lib/hooks/index.js.map +1 -1
  196. package/dist/lib/hooks/types.d.ts +16 -0
  197. package/dist/lib/hooks/types.d.ts.map +1 -1
  198. package/dist/lib/hooks/types.js +12 -0
  199. package/dist/lib/hooks/types.js.map +1 -1
  200. package/dist/lib/logger.d.ts +40 -155
  201. package/dist/lib/logger.d.ts.map +1 -1
  202. package/dist/lib/logger.js +349 -420
  203. package/dist/lib/logger.js.map +1 -1
  204. package/dist/lib/logger.test.d.ts +10 -1
  205. package/dist/lib/logger.test.d.ts.map +1 -1
  206. package/dist/lib/logger.test.js +658 -258
  207. package/dist/lib/logger.test.js.map +1 -1
  208. package/dist/lib/lswt/action-executors.d.ts +2 -0
  209. package/dist/lib/lswt/action-executors.d.ts.map +1 -1
  210. package/dist/lib/lswt/action-executors.js +4 -3
  211. package/dist/lib/lswt/action-executors.js.map +1 -1
  212. package/dist/lib/lswt/action-executors.test.js +7 -0
  213. package/dist/lib/lswt/action-executors.test.js.map +1 -1
  214. package/dist/lib/lswt/args.d.ts.map +1 -1
  215. package/dist/lib/lswt/args.js +15 -1
  216. package/dist/lib/lswt/args.js.map +1 -1
  217. package/dist/lib/lswt/environment.d.ts +21 -2
  218. package/dist/lib/lswt/environment.d.ts.map +1 -1
  219. package/dist/lib/lswt/environment.js +73 -32
  220. package/dist/lib/lswt/environment.js.map +1 -1
  221. package/dist/lib/lswt/environment.test.js +79 -1
  222. package/dist/lib/lswt/environment.test.js.map +1 -1
  223. package/dist/lib/lswt/index.d.ts +1 -0
  224. package/dist/lib/lswt/index.d.ts.map +1 -1
  225. package/dist/lib/lswt/index.js +2 -0
  226. package/dist/lib/lswt/index.js.map +1 -1
  227. package/dist/lib/lswt/table.d.ts +15 -0
  228. package/dist/lib/lswt/table.d.ts.map +1 -0
  229. package/dist/lib/lswt/table.js +61 -0
  230. package/dist/lib/lswt/table.js.map +1 -0
  231. package/dist/lib/lswt/table.test.d.ts +5 -0
  232. package/dist/lib/lswt/table.test.d.ts.map +1 -0
  233. package/dist/lib/lswt/table.test.js +262 -0
  234. package/dist/lib/lswt/table.test.js.map +1 -0
  235. package/dist/lib/lswt/types.d.ts +4 -0
  236. package/dist/lib/lswt/types.d.ts.map +1 -1
  237. package/dist/lib/lswt/worktree-info.d.ts.map +1 -1
  238. package/dist/lib/lswt/worktree-info.js +1 -6
  239. package/dist/lib/lswt/worktree-info.js.map +1 -1
  240. package/dist/lib/lswt/worktree-info.test.js +5 -17
  241. package/dist/lib/lswt/worktree-info.test.js.map +1 -1
  242. package/dist/lib/newpr/args.d.ts.map +1 -1
  243. package/dist/lib/newpr/args.js +36 -1
  244. package/dist/lib/newpr/args.js.map +1 -1
  245. package/dist/lib/newpr/hook-runner.d.ts +11 -0
  246. package/dist/lib/newpr/hook-runner.d.ts.map +1 -1
  247. package/dist/lib/newpr/hook-runner.js +49 -1
  248. package/dist/lib/newpr/hook-runner.js.map +1 -1
  249. package/dist/lib/newpr/hook-runner.test.js +121 -0
  250. package/dist/lib/newpr/hook-runner.test.js.map +1 -1
  251. package/dist/lib/newpr/plan-generator.d.ts +121 -0
  252. package/dist/lib/newpr/plan-generator.d.ts.map +1 -0
  253. package/dist/lib/newpr/plan-generator.js +185 -0
  254. package/dist/lib/newpr/plan-generator.js.map +1 -0
  255. package/dist/lib/newpr/plan-generator.test.d.ts +7 -0
  256. package/dist/lib/newpr/plan-generator.test.d.ts.map +1 -0
  257. package/dist/lib/newpr/plan-generator.test.js +387 -0
  258. package/dist/lib/newpr/plan-generator.test.js.map +1 -0
  259. package/dist/lib/newpr/types.d.ts +12 -0
  260. package/dist/lib/newpr/types.d.ts.map +1 -1
  261. package/dist/lib/prs/actions.d.ts +5 -1
  262. package/dist/lib/prs/actions.d.ts.map +1 -1
  263. package/dist/lib/prs/actions.js +12 -10
  264. package/dist/lib/prs/actions.js.map +1 -1
  265. package/dist/lib/prs/actions.test.js +48 -5
  266. package/dist/lib/prs/actions.test.js.map +1 -1
  267. package/dist/lib/prs/command.d.ts +21 -0
  268. package/dist/lib/prs/command.d.ts.map +1 -0
  269. package/dist/lib/prs/command.js +175 -0
  270. package/dist/lib/prs/command.js.map +1 -0
  271. package/dist/lib/prs/command.test.d.ts +11 -0
  272. package/dist/lib/prs/command.test.d.ts.map +1 -0
  273. package/dist/lib/prs/command.test.js +409 -0
  274. package/dist/lib/prs/command.test.js.map +1 -0
  275. package/dist/lib/prs/interactive.d.ts.map +1 -1
  276. package/dist/lib/prs/interactive.js +15 -2
  277. package/dist/lib/prs/interactive.js.map +1 -1
  278. package/dist/lib/prs/interactive.test.js +153 -0
  279. package/dist/lib/prs/interactive.test.js.map +1 -1
  280. package/dist/lib/prs/types.d.ts +15 -0
  281. package/dist/lib/prs/types.d.ts.map +1 -1
  282. package/dist/lib/ui/error.d.ts +31 -0
  283. package/dist/lib/ui/error.d.ts.map +1 -0
  284. package/dist/lib/ui/error.js +47 -0
  285. package/dist/lib/ui/error.js.map +1 -0
  286. package/dist/lib/ui/error.test.d.ts +2 -0
  287. package/dist/lib/ui/error.test.d.ts.map +1 -0
  288. package/dist/lib/ui/error.test.js +143 -0
  289. package/dist/lib/ui/error.test.js.map +1 -0
  290. package/dist/lib/ui/index.d.ts +15 -0
  291. package/dist/lib/ui/index.d.ts.map +1 -0
  292. package/dist/lib/ui/index.js +19 -0
  293. package/dist/lib/ui/index.js.map +1 -0
  294. package/dist/lib/ui/output.d.ts +18 -0
  295. package/dist/lib/ui/output.d.ts.map +1 -0
  296. package/dist/lib/ui/output.js +31 -0
  297. package/dist/lib/ui/output.js.map +1 -0
  298. package/dist/lib/ui/output.test.d.ts +2 -0
  299. package/dist/lib/ui/output.test.d.ts.map +1 -0
  300. package/dist/lib/ui/output.test.js +59 -0
  301. package/dist/lib/ui/output.test.js.map +1 -0
  302. package/dist/lib/ui/spinner.d.ts +10 -0
  303. package/dist/lib/ui/spinner.d.ts.map +1 -0
  304. package/dist/lib/ui/spinner.js +10 -0
  305. package/dist/lib/ui/spinner.js.map +1 -0
  306. package/dist/lib/ui/status.d.ts +65 -0
  307. package/dist/lib/ui/status.d.ts.map +1 -0
  308. package/dist/lib/ui/status.js +100 -0
  309. package/dist/lib/ui/status.js.map +1 -0
  310. package/dist/lib/ui/status.test.d.ts +2 -0
  311. package/dist/lib/ui/status.test.d.ts.map +1 -0
  312. package/dist/lib/ui/status.test.js +158 -0
  313. package/dist/lib/ui/status.test.js.map +1 -0
  314. package/dist/lib/ui/table.d.ts +39 -0
  315. package/dist/lib/ui/table.d.ts.map +1 -0
  316. package/dist/lib/ui/table.js +45 -0
  317. package/dist/lib/ui/table.js.map +1 -0
  318. package/dist/lib/ui/table.test.d.ts +2 -0
  319. package/dist/lib/ui/table.test.d.ts.map +1 -0
  320. package/dist/lib/ui/table.test.js +115 -0
  321. package/dist/lib/ui/table.test.js.map +1 -0
  322. package/dist/lib/ui/theme.d.ts +34 -0
  323. package/dist/lib/ui/theme.d.ts.map +1 -0
  324. package/dist/lib/ui/theme.js +37 -0
  325. package/dist/lib/ui/theme.js.map +1 -0
  326. package/dist/lib/ui/theme.test.d.ts +2 -0
  327. package/dist/lib/ui/theme.test.d.ts.map +1 -0
  328. package/dist/lib/ui/theme.test.js +76 -0
  329. package/dist/lib/ui/theme.test.js.map +1 -0
  330. package/dist/lib/wtconfig/environment.d.ts +18 -1
  331. package/dist/lib/wtconfig/environment.d.ts.map +1 -1
  332. package/dist/lib/wtconfig/environment.js +60 -24
  333. package/dist/lib/wtconfig/environment.js.map +1 -1
  334. package/dist/lib/wtconfig/environment.test.js +45 -1
  335. package/dist/lib/wtconfig/environment.test.js.map +1 -1
  336. package/dist/lib/wtlink/config-manifest.test.js +26 -0
  337. package/dist/lib/wtlink/config-manifest.test.js.map +1 -1
  338. package/dist/lib/wtlink/link-configs.js +7 -7
  339. package/dist/lib/wtlink/link-configs.js.map +1 -1
  340. package/dist/lib/wtlink/validate-manifest.d.ts.map +1 -1
  341. package/dist/lib/wtlink/validate-manifest.js +5 -5
  342. package/dist/lib/wtlink/validate-manifest.js.map +1 -1
  343. package/dist/lib/wtstate/args.d.ts.map +1 -1
  344. package/dist/lib/wtstate/args.js +2 -0
  345. package/dist/lib/wtstate/args.js.map +1 -1
  346. package/dist/mcp/server.d.ts +2 -1
  347. package/dist/mcp/server.d.ts.map +1 -1
  348. package/dist/mcp/server.js +264 -44
  349. package/dist/mcp/server.js.map +1 -1
  350. package/dist/mcp/server.test.js +111 -0
  351. package/dist/mcp/server.test.js.map +1 -1
  352. package/package.json +3 -1
  353. package/schemas/worktreerc.schema.json +23 -0
package/dist/cli/newpr.js CHANGED
@@ -6,18 +6,20 @@
6
6
  */
7
7
  import path from 'path';
8
8
  import fs from 'fs';
9
+ import { printDeprecationNotice } from '../lib/deprecation.js';
9
10
  import * as git from '../lib/git.js';
10
11
  import * as github from '../lib/github.js';
11
- import * as colors from '../lib/colors.js';
12
- import { promptChoiceIndex, withSpinner } from '../lib/prompts.js';
12
+ import { setColorEnabled } from '../lib/colors.js';
13
+ import { logger, initializeLogger, setAuditContext } from '../lib/logger.js';
14
+ import { promptChoiceIndex, promptConfirm, withSpinner } from '../lib/prompts.js';
15
+ import { getEnabledFiles } from '../lib/wtlink/config-manifest.js';
16
+ import { run as runWtlink } from '../lib/wtlink/link-configs.js';
13
17
  import { loadConfig, generateBranchNameAsync, generateWorktreePath, generatePRContentAsync, } from '../lib/config.js';
14
18
  import { analyzeGitState, detectScenario } from '../lib/state-detection.js';
15
19
  import { parseArgs, getHelpText, getScenarioContext, isPrWorktreeScenario, isExistingBranchAction, executeStateAction, getBranchPoint, getScenarioMessageLevel, createHookRunner, createActionDeps, } from '../lib/newpr/index.js';
16
20
  import { createSuccessResult, createErrorResult, formatJsonResult, ErrorCode, getErrorCodeFromError, getErrorSuggestion, } from '../lib/json-output.js';
17
- /**
18
- * Debug logging - enabled with DEBUG=newpr or DEBUG=*
19
- */
20
- const DEBUG_ENABLED = process.env.DEBUG === 'newpr' || process.env.DEBUG === '*' || process.env.DEBUG === '1';
21
+ import { shouldGeneratePlan, resolvePlanPath, buildPathTemplateVars, generatePlanDocument, } from '../lib/newpr/plan-generator.js';
22
+ import { printStatus, printDim, printSummaryBox, printNextSteps, printError, errorToDisplay, setJsonMode, } from '../lib/ui/index.js';
21
23
  /**
22
24
  * Error class for non-interactive mode failures
23
25
  */
@@ -29,47 +31,26 @@ class NonInteractiveError extends Error {
29
31
  this.name = 'NonInteractiveError';
30
32
  }
31
33
  }
32
- function debug(message, data) {
33
- if (!DEBUG_ENABLED)
34
- return;
35
- const timestamp = new Date().toISOString();
36
- console.error(colors.dim(`[DEBUG ${timestamp}] ${message}`));
37
- if (data) {
38
- for (const [key, value] of Object.entries(data)) {
39
- console.error(colors.dim(` ${key}: ${JSON.stringify(value)}`));
40
- }
41
- }
42
- }
43
- /**
44
- * Progress logging - suppressed in JSON mode for clean output
45
- */
46
- function progress(options, ...args) {
47
- if (!options.json) {
48
- console.log(...args);
49
- }
50
- }
51
- /**
52
- * Progress error logging - suppressed in JSON mode
53
- */
54
- function progressError(options, ...args) {
55
- if (!options.json) {
56
- console.error(...args);
57
- }
58
- }
59
34
  /**
60
35
  * Check prerequisites
61
36
  */
62
37
  function checkPrerequisites() {
63
- console.log(colors.info('Checking prerequisites...'));
38
+ printStatus('info', 'Checking prerequisites...');
64
39
  if (!github.isGhInstalled()) {
65
- console.error(colors.error('GitHub CLI (gh) is required. See: https://cli.github.com'));
40
+ printError({
41
+ title: 'GitHub CLI (gh) is required.',
42
+ hint: 'Install: https://cli.github.com',
43
+ });
66
44
  process.exit(1);
67
45
  }
68
46
  if (!github.isAuthenticated()) {
69
- console.error(colors.error('GitHub CLI not authenticated. Run: gh auth login'));
47
+ printError({
48
+ title: 'GitHub CLI not authenticated.',
49
+ hint: 'Run: gh auth login',
50
+ });
70
51
  process.exit(1);
71
52
  }
72
- console.log(colors.success('Prerequisites OK'));
53
+ printStatus('success', 'Prerequisites OK');
73
54
  }
74
55
  /**
75
56
  * Show local commits not in base branch
@@ -133,7 +114,7 @@ async function handleScenario(state, baseBranch, options) {
133
114
  // In non-interactive mode, cannot proceed from PR worktree
134
115
  throw new NonInteractiveError('Cannot create PR from a PR worktree in non-interactive mode. Switch to the main worktree first.', ErrorCode.INVALID_ARGUMENT);
135
116
  }
136
- console.log(colors.warning('You are in a PR worktree, not the main worktree.'));
117
+ printStatus('warning', 'You are in a PR worktree, not the main worktree.');
137
118
  console.log();
138
119
  console.log('Creating a new PR is best done from the main worktree.');
139
120
  const choice = await promptChoiceIndex('How would you like to proceed?', [
@@ -178,10 +159,10 @@ async function handleScenario(state, baseBranch, options) {
178
159
  // Interactive mode: display info and prompt
179
160
  const level = getScenarioMessageLevel(scenario);
180
161
  if (level === 'warning') {
181
- console.log(colors.warning(context.message));
162
+ printStatus('warning', context.message);
182
163
  }
183
164
  else {
184
- console.log(colors.info(context.message));
165
+ printStatus('info', context.message);
185
166
  }
186
167
  if (context.subMessage) {
187
168
  console.log();
@@ -230,13 +211,17 @@ async function handleScenario(state, baseBranch, options) {
230
211
  }
231
212
  /**
232
213
  * Setup worktree (symlinks, wtlink, deps)
214
+ *
215
+ * @param worktreePath - Path to the newly created worktree
216
+ * @param config - Resolved configuration
217
+ * @param options - CLI options
218
+ * @param repoRoot - Repository root (required to avoid CWD issues when running from subdirectories)
233
219
  */
234
- async function setupWorktree(worktreePath, config, options) {
235
- const repoRoot = git.getRepoRoot();
220
+ async function setupWorktree(worktreePath, config, options, repoRoot) {
236
221
  const parentDir = path.dirname(repoRoot);
237
222
  // Create symlinks for shared repos
238
223
  if (config.sharedRepos.length > 0) {
239
- progress(options, colors.info('Creating symlinks for shared repositories...'));
224
+ printStatus('info', 'Creating symlinks for shared repositories...');
240
225
  for (const repo of config.sharedRepos) {
241
226
  const target = path.join(parentDir, repo);
242
227
  const link = path.join(worktreePath, repo);
@@ -244,18 +229,74 @@ async function setupWorktree(worktreePath, config, options) {
244
229
  if (!fs.existsSync(link)) {
245
230
  try {
246
231
  fs.symlinkSync(target, link, 'dir');
247
- progress(options, colors.success(`Linked ${repo}`));
232
+ printStatus('success', `Linked ${repo}`);
248
233
  }
249
234
  catch (error) {
250
- progress(options, colors.warning(`Failed to link ${repo}: ${error}`));
235
+ printStatus('warning', `Failed to link ${repo}: ${error}`);
251
236
  }
252
237
  }
253
238
  else {
254
- progress(options, colors.warning(`${repo} already exists in worktree`));
239
+ printStatus('warning', `${repo} already exists in worktree`);
255
240
  }
256
241
  }
257
242
  else {
258
- progress(options, colors.warning(`${repo} not found at ${target}`));
243
+ printStatus('warning', `${repo} not found at ${target}`);
244
+ }
245
+ }
246
+ }
247
+ // Auto-link config files from main worktree
248
+ let mainWorktreeRoot;
249
+ try {
250
+ mainWorktreeRoot = git.getMainWorktreeRoot(repoRoot);
251
+ }
252
+ catch {
253
+ // If we can't determine main worktree, skip linking
254
+ logger.debug('Could not determine main worktree root, skipping config file linking');
255
+ return;
256
+ }
257
+ const enabledFiles = getEnabledFiles(mainWorktreeRoot);
258
+ if (enabledFiles.length > 0) {
259
+ let shouldLink = false;
260
+ if (config.linkConfigFiles === false) {
261
+ // Explicitly disabled - skip
262
+ logger.debug('linkConfigFiles is false, skipping auto-link');
263
+ }
264
+ else if (config.linkConfigFiles === true) {
265
+ // Explicitly enabled - auto-link
266
+ shouldLink = true;
267
+ logger.debug('linkConfigFiles is true, auto-linking');
268
+ }
269
+ else if (!options.nonInteractive && !options.json) {
270
+ // Not configured - prompt user
271
+ printStatus('info', `Found ${enabledFiles.length} config file(s) to link:`);
272
+ for (const file of enabledFiles.slice(0, 5)) {
273
+ printDim(` - ${file}`);
274
+ }
275
+ if (enabledFiles.length > 5) {
276
+ printDim(` ... and ${enabledFiles.length - 5} more`);
277
+ }
278
+ shouldLink = await promptConfirm('Link these config files from the main worktree?', true);
279
+ }
280
+ else {
281
+ // Non-interactive/JSON mode - default to linking
282
+ shouldLink = true;
283
+ logger.debug('Non-interactive mode, defaulting to auto-link');
284
+ }
285
+ if (shouldLink) {
286
+ try {
287
+ await runWtlink({
288
+ source: mainWorktreeRoot,
289
+ destination: worktreePath,
290
+ dryRun: false,
291
+ manifestFile: '.wtlinkrc',
292
+ type: 'hard',
293
+ yes: true,
294
+ });
295
+ printStatus('success', `Linked ${enabledFiles.length} config file(s)`);
296
+ }
297
+ catch (error) {
298
+ const errorMessage = error instanceof Error ? error.message : String(error);
299
+ printStatus('warning', `Failed to link config files: ${errorMessage}`);
259
300
  }
260
301
  }
261
302
  }
@@ -277,41 +318,127 @@ function printSummary(prNumber, branchName, worktreePath, prUrl, options, extra)
277
318
  console.log(formatJsonResult(createSuccessResult('newpr', data)));
278
319
  return;
279
320
  }
280
- console.log();
281
- console.log(colors.green('════════════════════════════════════════════════════════════'));
282
- console.log(colors.green(` PR #${prNumber} worktree ready!`));
283
- console.log(colors.green('════════════════════════════════════════════════════════════'));
284
- console.log();
285
- console.log(` Branch: ${branchName}`);
286
- console.log(` Worktree: ${worktreePath}`);
287
- console.log(` PR URL: ${prUrl}`);
288
- console.log();
289
- console.log(colors.dim(' Next steps:'));
290
- console.log(colors.dim(` cd ${worktreePath}`));
291
- console.log(colors.dim(` gh pr view ${prNumber} --web # Open PR in browser`));
292
- console.log(colors.dim(` wtlink link # Link config files from main worktree`));
293
- console.log();
321
+ printSummaryBox(`PR #${prNumber} worktree ready!`, [
322
+ { label: 'Branch', value: branchName },
323
+ { label: 'Worktree', value: worktreePath },
324
+ { label: 'PR URL', value: prUrl },
325
+ ]);
326
+ printNextSteps([
327
+ { command: `cd ${worktreePath}` },
328
+ { command: `gh pr view ${prNumber} --web`, description: 'Open PR in browser' },
329
+ { command: 'wtlink link', description: 'Link config files from main worktree' },
330
+ ]);
331
+ }
332
+ /**
333
+ * Handle plan generation based on options and config
334
+ */
335
+ async function handlePlanGeneration(options, config, context) {
336
+ const aiConfig = config.ai ?? {};
337
+ // Check if AI provider is configured (simple check without initialization)
338
+ const aiAvailable = aiConfig.provider !== undefined &&
339
+ aiConfig.provider !== 'none' &&
340
+ aiConfig.planDocument === true;
341
+ // Determine if we should generate a plan
342
+ const decision = shouldGeneratePlan({
343
+ cliFlag: options.generatePlan,
344
+ noFlag: options.noPlan,
345
+ configEnabled: aiConfig.planDocument,
346
+ aiAvailable,
347
+ nonInteractive: options.nonInteractive ?? false,
348
+ });
349
+ // If prompting is needed and we're interactive, ask the user
350
+ let shouldGenerate = decision.generate;
351
+ if (decision.prompt && !options.json) {
352
+ shouldGenerate = await promptConfirm('Generate AI implementation plan document?', false);
353
+ }
354
+ if (!shouldGenerate) {
355
+ logger.debug('Plan generation skipped', { reason: decision.reason });
356
+ return undefined;
357
+ }
358
+ printStatus('info', 'Generating AI implementation plan...');
359
+ // Build path template variables
360
+ const vars = buildPathTemplateVars({
361
+ prNumber: context.prNumber,
362
+ description: context.description,
363
+ branchName: context.branchName,
364
+ });
365
+ // Resolve plan path
366
+ const planPath = resolvePlanPath(context.worktreePath, aiConfig, vars);
367
+ // Generate the plan
368
+ const result = await generatePlanDocument({
369
+ description: context.description,
370
+ branchName: context.branchName,
371
+ }, planPath, aiConfig, {
372
+ prNumber: context.prNumber,
373
+ baseBranch: options.baseBranch,
374
+ });
375
+ if (result.generated) {
376
+ printStatus('success', `Created plan: ${result.path}`);
377
+ }
378
+ else if (result.error) {
379
+ printStatus('warning', `Plan generation failed: ${result.error}`);
380
+ }
381
+ return result;
382
+ }
383
+ /**
384
+ * Execute the unified post-worktree sequence:
385
+ * 1. Plan generation (if enabled)
386
+ * 2. Post-worktree hook
387
+ *
388
+ * This ensures consistent behavior across all three newpr modes.
389
+ */
390
+ async function executePostWorktreeSequence(worktreePath, config, options, context, hookRunner) {
391
+ const result = {};
392
+ // Generate plan (if enabled) - happens BEFORE post-worktree hook
393
+ result.planResult = await handlePlanGeneration(options, config, {
394
+ ...context,
395
+ worktreePath,
396
+ });
397
+ // Run post-worktree hook
398
+ if (hookRunner) {
399
+ hookRunner.updateContext({ worktreePath });
400
+ await hookRunner.runHook('post-worktree');
401
+ }
402
+ return result;
294
403
  }
295
404
  /**
296
405
  * Mode: Setup worktree for existing PR
297
406
  */
298
407
  async function modeExistingPr(prNumber, options) {
299
- progress(options, colors.info(`Setting up worktree for existing PR #${prNumber}...`));
408
+ printStatus('info', `Setting up worktree for existing PR #${prNumber}...`);
300
409
  const repoRoot = git.getRepoRoot();
301
410
  const repoName = git.getRepoName(repoRoot);
302
411
  const config = loadConfig(repoRoot);
412
+ // Initialize hook runner for post-worktree hook
413
+ const hookRunner = createHookRunner(options.noHooks ? {} : (config.hooks ?? {}), {
414
+ repoRoot,
415
+ baseBranch: options.baseBranch,
416
+ }, {
417
+ verbose: options.verbose ?? false,
418
+ showOutput: true,
419
+ defaultTimeout: config.hookDefaults?.timeout,
420
+ maxTimeout: config.hookDefaults?.maxTimeout,
421
+ confirmHooks: options.confirmHooks,
422
+ });
303
423
  const pr = github.getPr(prNumber);
304
424
  if (!pr) {
305
425
  exitWithError(`Could not find PR #${prNumber}`, ErrorCode.PR_NOT_FOUND, options.json);
306
426
  }
307
427
  if (pr.state !== 'OPEN') {
308
- progress(options, colors.warning(`PR #${prNumber} is ${pr.state}`));
428
+ printStatus('warning', `PR #${prNumber} is ${pr.state}`);
309
429
  }
310
- progress(options, colors.info(`PR branch: ${pr.headBranch}`));
430
+ printStatus('info', `PR branch: ${pr.headBranch}`);
311
431
  const worktreePath = generateWorktreePath(config, repoRoot, repoName, prNumber);
312
432
  if (fs.existsSync(worktreePath)) {
313
433
  exitWithError(`Worktree already exists: ${worktreePath}`, ErrorCode.WORKTREE_EXISTS, options.json);
314
434
  }
435
+ // Update hook context
436
+ hookRunner.updateContext({
437
+ branchName: pr.headBranch,
438
+ prNumber,
439
+ prUrl: pr.url,
440
+ worktreePath,
441
+ });
315
442
  // Use spinner for fetch (only in non-JSON mode)
316
443
  if (options.json) {
317
444
  git.fetch('origin');
@@ -346,18 +473,41 @@ async function modeExistingPr(prNumber, options) {
346
473
  }
347
474
  });
348
475
  }
349
- progress(options, colors.success(`Created worktree: ${worktreePath}`));
350
- await setupWorktree(worktreePath, config, options);
476
+ printStatus('success', `Created worktree: ${worktreePath}`);
477
+ await setupWorktree(worktreePath, config, options, repoRoot);
478
+ // Generate description from branch name for plan context
479
+ const descriptionFromBranch = pr.headBranch
480
+ .replace(/^(feat|fix|chore)\//, '')
481
+ .replace(/-/g, ' ')
482
+ .replace(/\b\w/g, (c) => c.toUpperCase());
483
+ // Execute unified post-worktree sequence (plan generation + hook)
484
+ await executePostWorktreeSequence(worktreePath, config, options, {
485
+ prNumber,
486
+ branchName: pr.headBranch,
487
+ description: descriptionFromBranch,
488
+ }, hookRunner);
489
+ setAuditContext({ prNumber, worktreePath, gitBranch: pr.headBranch });
351
490
  printSummary(prNumber, pr.headBranch, worktreePath, pr.url, options, { draft: pr.isDraft });
352
491
  }
353
492
  /**
354
493
  * Mode: Create PR for existing branch
355
494
  */
356
495
  async function modeExistingBranch(branchName, options) {
357
- progress(options, colors.info(`Creating PR for existing branch: ${branchName}...`));
496
+ printStatus('info', `Creating PR for existing branch: ${branchName}...`);
358
497
  const repoRoot = git.getRepoRoot();
359
498
  const repoName = git.getRepoName(repoRoot);
360
499
  const config = loadConfig(repoRoot);
500
+ // Initialize hook runner for post-worktree hook
501
+ const hookRunner = createHookRunner(options.noHooks ? {} : (config.hooks ?? {}), {
502
+ repoRoot,
503
+ baseBranch: options.baseBranch,
504
+ }, {
505
+ verbose: options.verbose ?? false,
506
+ showOutput: true,
507
+ defaultTimeout: config.hookDefaults?.timeout,
508
+ maxTimeout: config.hookDefaults?.maxTimeout,
509
+ confirmHooks: options.confirmHooks,
510
+ });
361
511
  // Use spinner for fetch
362
512
  if (options.json) {
363
513
  git.fetch('origin');
@@ -385,11 +535,11 @@ async function modeExistingBranch(branchName, options) {
385
535
  }
386
536
  const existingPr = github.getPrByBranch(branchName);
387
537
  if (existingPr) {
388
- progress(options, colors.info(`PR #${existingPr.number} already exists for branch ${branchName}`));
538
+ printStatus('info', `PR #${existingPr.number} already exists for branch ${branchName}`);
389
539
  await modeExistingPr(existingPr.number, options);
390
540
  return;
391
541
  }
392
- progress(options, colors.info('Creating pull request...'));
542
+ printStatus('info', 'Creating pull request...');
393
543
  // Generate description from branch name for AI context
394
544
  const descriptionFromBranch = branchName
395
545
  .replace(/^(feat|fix|chore)\//, '')
@@ -404,7 +554,7 @@ async function modeExistingBranch(branchName, options) {
404
554
  commitMessages: git.getCommitMessages(`origin/${options.baseBranch}`, branchName),
405
555
  });
406
556
  if (prContent.aiGenerated) {
407
- progress(options, colors.info('✨ AI-generated PR content'));
557
+ printStatus('info', '✨ AI-generated PR content');
408
558
  }
409
559
  const defaultBody = `## Summary
410
560
 
@@ -427,7 +577,13 @@ PR created from existing branch: \`${branchName}\`
427
577
  head: branchName,
428
578
  draft: options.draft,
429
579
  });
430
- progress(options, colors.success(`Created PR #${pr.number}: ${pr.url}`));
580
+ printStatus('success', `Created PR #${pr.number}: ${pr.url}`);
581
+ // Update hook context
582
+ hookRunner.updateContext({
583
+ branchName,
584
+ prNumber: pr.number,
585
+ prUrl: pr.url,
586
+ });
431
587
  const worktreePath = generateWorktreePath(config, repoRoot, repoName, pr.number);
432
588
  // Use spinner for worktree creation
433
589
  if (options.json) {
@@ -454,8 +610,15 @@ PR created from existing branch: \`${branchName}\`
454
610
  }
455
611
  });
456
612
  }
457
- progress(options, colors.success(`Created worktree: ${worktreePath}`));
458
- await setupWorktree(worktreePath, config, options);
613
+ printStatus('success', `Created worktree: ${worktreePath}`);
614
+ await setupWorktree(worktreePath, config, options, repoRoot);
615
+ // Execute unified post-worktree sequence (plan generation + hook)
616
+ await executePostWorktreeSequence(worktreePath, config, options, {
617
+ prNumber: pr.number,
618
+ branchName,
619
+ description: descriptionFromBranch,
620
+ }, hookRunner);
621
+ setAuditContext({ prNumber: pr.number, worktreePath, gitBranch: branchName });
459
622
  printSummary(pr.number, branchName, worktreePath, pr.url, options);
460
623
  }
461
624
  /**
@@ -472,10 +635,11 @@ async function modeNewFeature(description, options) {
472
635
  baseBranch: options.baseBranch,
473
636
  description,
474
637
  }, {
475
- verbose: DEBUG_ENABLED,
638
+ verbose: options.verbose ?? false,
476
639
  showOutput: true,
477
640
  defaultTimeout: config.hookDefaults?.timeout,
478
641
  maxTimeout: config.hookDefaults?.maxTimeout,
642
+ confirmHooks: options.confirmHooks,
479
643
  });
480
644
  // Run pre-analyze hook
481
645
  if (!(await hookRunner.runHook('pre-analyze'))) {
@@ -493,7 +657,7 @@ async function modeNewFeature(description, options) {
493
657
  }
494
658
  }
495
659
  catch {
496
- progress(options, colors.warning('Could not fetch from origin (network unavailable?)'));
660
+ printStatus('warning', 'Could not fetch from origin (network unavailable?)');
497
661
  }
498
662
  const state = analyzeGitState(options.baseBranch);
499
663
  const scenario = detectScenario(state);
@@ -507,7 +671,7 @@ async function modeNewFeature(description, options) {
507
671
  if (!(await hookRunner.runHook('post-analyze'))) {
508
672
  exitWithError('Aborted by post-analyze hook.', ErrorCode.HOOK_FAILED, options.json);
509
673
  }
510
- debug('State analysis complete', {
674
+ logger.debug('State analysis complete', {
511
675
  scenario,
512
676
  branchType: state.branchType,
513
677
  currentBranch: state.currentBranch,
@@ -523,10 +687,10 @@ async function modeNewFeature(description, options) {
523
687
  console.log(formatJsonResult(createErrorResult('newpr', ErrorCode.USER_CANCELLED, 'User cancelled')));
524
688
  process.exit(1);
525
689
  }
526
- console.log(colors.error('Aborted by user.'));
690
+ printStatus('error', 'Aborted by user.');
527
691
  process.exit(1);
528
692
  }
529
- debug('User selected action', {
693
+ logger.debug('User selected action', {
530
694
  action: action.action,
531
695
  branchFrom: action.branchFrom,
532
696
  stashUnstaged: action.stashUnstaged,
@@ -545,36 +709,36 @@ async function modeNewFeature(description, options) {
545
709
  const deps = createActionDeps(repoRoot);
546
710
  executeStateAction(action, description, currentBranch, deps, repoRoot);
547
711
  if (!git.remoteBranchExists(currentBranch)) {
548
- progress(options, colors.info('Pushing branch to origin...'));
712
+ printStatus('info', 'Pushing branch to origin...');
549
713
  git.push({ setUpstream: true, remote: 'origin', branch: currentBranch });
550
714
  }
551
715
  await modeExistingBranch(currentBranch, options);
552
716
  return;
553
717
  }
554
- progress(options, colors.info(`Creating feature branch: ${branchName}`));
718
+ printStatus('info', `Creating feature branch: ${branchName}`);
555
719
  if (git.remoteBranchExists(branchName)) {
556
- progress(options, colors.warning(`Branch ${branchName} already exists on remote`));
720
+ printStatus('warning', `Branch ${branchName} already exists on remote`);
557
721
  const existingPr = github.getPrByBranch(branchName);
558
722
  if (existingPr) {
559
- progress(options, colors.info(`PR #${existingPr.number} already exists, setting up worktree...`));
723
+ printStatus('info', `PR #${existingPr.number} already exists, setting up worktree...`);
560
724
  await modeExistingPr(existingPr.number, options);
561
725
  }
562
726
  else {
563
- progress(options, colors.info('No PR exists, creating one...'));
727
+ printStatus('info', 'No PR exists, creating one...');
564
728
  await modeExistingBranch(branchName, options);
565
729
  }
566
730
  return;
567
731
  }
568
732
  const originalBranch = git.getCurrentBranch() || 'main';
569
733
  const deps = createActionDeps(repoRoot);
570
- debug('Before executeStateAction', {
734
+ logger.debug('Before executeStateAction', {
571
735
  originalBranch,
572
736
  branchName,
573
737
  stagedFilesBefore: git.getStagedFiles(),
574
738
  unstagedFilesBefore: git.getUnstagedFiles(),
575
739
  });
576
740
  const actionResult = executeStateAction(action, description, branchName, deps, repoRoot);
577
- debug('After executeStateAction', {
741
+ logger.debug('After executeStateAction', {
578
742
  success: actionResult.success,
579
743
  stashRef: actionResult.stashRef,
580
744
  stagedFilesAfter: git.getStagedFiles(),
@@ -586,7 +750,7 @@ async function modeNewFeature(description, options) {
586
750
  // Stash unstaged changes if needed
587
751
  let unstagedStashRef = null;
588
752
  if (action.stashUnstaged) {
589
- progress(options, colors.info('Stashing unstaged changes (will move to worktree)...'));
753
+ printStatus('info', 'Stashing unstaged changes (will move to worktree)...');
590
754
  unstagedStashRef = git.stash({
591
755
  keepIndex: true,
592
756
  message: 'newpr: unstaged changes for worktree',
@@ -598,31 +762,34 @@ async function modeNewFeature(description, options) {
598
762
  if (!(await hookRunner.runHook('pre-branch'))) {
599
763
  exitWithError('Aborted by pre-branch hook.', ErrorCode.HOOK_FAILED, options.json);
600
764
  }
601
- progress(options, colors.info(`Creating branch from ${branchFrom}...`));
602
- debug('Before checkout', {
765
+ printStatus('info', `Creating branch from ${branchFrom}...`);
766
+ logger.debug('Before checkout', {
603
767
  branchFrom,
604
768
  branchName,
605
769
  currentBranch: git.getCurrentBranch(),
606
770
  stagedFilesBeforeCheckout: git.getStagedFiles(),
607
771
  });
608
772
  try {
609
- git.exec(['checkout', '-b', branchName, branchFrom]);
773
+ git.exec(['checkout', '-b', branchName, branchFrom], { cwd: repoRoot });
610
774
  }
611
775
  catch (checkoutError) {
612
776
  // When checkout fails (e.g., due to conflicting changes), git preserves
613
777
  // the staged files in the index - no data is lost. Provide a helpful message.
614
778
  const errorMessage = checkoutError instanceof Error ? checkoutError.message : String(checkoutError);
615
779
  if (errorMessage.includes('overwritten') || errorMessage.includes('conflict')) {
616
- progressError(options, colors.error('Checkout failed due to conflicting changes.'));
617
- progressError(options, colors.info('Your staged changes are preserved. To resolve this, either:'));
618
- progressError(options, colors.info(' 1. Commit your changes first, then run newpr again'));
619
- progressError(options, colors.info(' 2. Stash your changes: git stash push'));
620
- progressError(options, colors.info(' 3. Use a different branch point (e.g., HEAD instead of origin/main)'));
780
+ printError({
781
+ title: 'Checkout failed due to conflicting changes.',
782
+ detail: 'Your staged changes are preserved in the index.',
783
+ hint: 'To resolve this, either:\n' +
784
+ ' 1. Commit your changes first, then run newpr again\n' +
785
+ ' 2. Stash your changes: git stash push\n' +
786
+ ' 3. Use a different branch point (e.g., HEAD instead of origin/main)',
787
+ });
621
788
  }
622
789
  throw checkoutError;
623
790
  }
624
791
  const stagedFiles = git.getStagedFiles();
625
- debug('After checkout', {
792
+ logger.debug('After checkout', {
626
793
  newBranch: git.getCurrentBranch(),
627
794
  stagedFilesAfterCheckout: stagedFiles,
628
795
  stagedFilesCount: stagedFiles.length,
@@ -636,9 +803,9 @@ async function modeNewFeature(description, options) {
636
803
  if (!(await hookRunner.runHook('pre-commit'))) {
637
804
  exitWithError('Aborted by pre-commit hook.', ErrorCode.HOOK_FAILED, options.json);
638
805
  }
639
- progress(options, colors.info('Committing staged changes...'));
806
+ printStatus('info', 'Committing staged changes...');
640
807
  git.commit({ message: `feat: ${description}\n\n🤖 Created with newpr` });
641
- debug('Committed staged changes');
808
+ logger.debug('Committed staged changes');
642
809
  // Run post-commit hook
643
810
  await hookRunner.runHook('post-commit');
644
811
  }
@@ -647,12 +814,12 @@ async function modeNewFeature(description, options) {
647
814
  if (!(await hookRunner.runHook('pre-commit'))) {
648
815
  exitWithError('Aborted by pre-commit hook.', ErrorCode.HOOK_FAILED, options.json);
649
816
  }
650
- progress(options, colors.info('Creating initial commit (required for PR creation)...'));
817
+ printStatus('info', 'Creating initial commit (required for PR creation)...');
651
818
  git.commit({
652
819
  message: `chore: initialize ${branchName}\n\nBranch created for: ${description}\n\n🤖 Created with newpr`,
653
820
  allowEmpty: true,
654
821
  });
655
- debug('Created empty commit (no staged files found)');
822
+ logger.debug('Created empty commit (no staged files found)');
656
823
  // Run post-commit hook
657
824
  await hookRunner.runHook('post-commit');
658
825
  }
@@ -662,21 +829,21 @@ async function modeNewFeature(description, options) {
662
829
  }
663
830
  // Use spinner for push
664
831
  if (options.json) {
665
- git.push({ setUpstream: true, remote: 'origin', branch: branchName });
832
+ git.push({ setUpstream: true, remote: 'origin', branch: branchName }, repoRoot);
666
833
  }
667
834
  else {
668
835
  await withSpinner('Pushing branch to origin...', async () => {
669
- await git.pushAsync({ setUpstream: true, remote: 'origin', branch: branchName });
836
+ await git.pushAsync({ setUpstream: true, remote: 'origin', branch: branchName }, repoRoot);
670
837
  });
671
838
  }
672
839
  // Run post-push hook
673
840
  await hookRunner.runHook('post-push');
674
- git.checkout(originalBranch);
841
+ git.checkout(originalBranch, repoRoot);
675
842
  // Run pre-pr hook
676
843
  if (!(await hookRunner.runHook('pre-pr'))) {
677
844
  exitWithError('Aborted by pre-pr hook.', ErrorCode.HOOK_FAILED, options.json);
678
845
  }
679
- progress(options, colors.info('Creating pull request...'));
846
+ printStatus('info', 'Creating pull request...');
680
847
  // Generate AI-enhanced PR content if enabled
681
848
  // Use origin/baseBranch to compare against remote, not potentially stale local branch
682
849
  const prContent = await generatePRContentAsync(config, {
@@ -687,7 +854,7 @@ async function modeNewFeature(description, options) {
687
854
  commitMessages: git.getCommitMessages(`origin/${options.baseBranch}`, branchName),
688
855
  });
689
856
  if (prContent.aiGenerated) {
690
- progress(options, colors.info('✨ AI-generated PR content'));
857
+ printStatus('info', '✨ AI-generated PR content');
691
858
  }
692
859
  const defaultBody = `## Summary
693
860
 
@@ -710,7 +877,7 @@ ${description}
710
877
  head: branchName,
711
878
  draft: options.draft,
712
879
  });
713
- progress(options, colors.success(`Created PR #${pr.number}: ${pr.url}`));
880
+ printStatus('success', `Created PR #${pr.number}: ${pr.url}`);
714
881
  // Update context with PR info
715
882
  hookRunner.updateContext({
716
883
  prNumber: pr.number,
@@ -727,29 +894,34 @@ ${description}
727
894
  }
728
895
  // Use spinner for worktree creation
729
896
  if (options.json) {
730
- git.addWorktree(worktreePath, branchName);
897
+ git.addWorktree(worktreePath, branchName, { cwd: repoRoot });
731
898
  }
732
899
  else {
733
900
  await withSpinner(`Creating worktree at ${worktreePath}...`, async () => {
734
- await git.addWorktreeAsync(worktreePath, branchName);
901
+ await git.addWorktreeAsync(worktreePath, branchName, { cwd: repoRoot });
735
902
  });
736
903
  }
737
- progress(options, colors.success(`Created worktree: ${worktreePath}`));
904
+ printStatus('success', `Created worktree: ${worktreePath}`);
738
905
  if (unstagedStashRef) {
739
- progress(options, colors.info('Moving unstaged changes to worktree...'));
906
+ printStatus('info', 'Moving unstaged changes to worktree...');
740
907
  try {
741
908
  git.stashApply(unstagedStashRef, worktreePath);
742
- progress(options, colors.success('Unstaged changes applied to worktree'));
909
+ printStatus('success', 'Unstaged changes applied to worktree');
743
910
  git.stashDrop(unstagedStashRef);
744
911
  }
745
912
  catch {
746
- progress(options, colors.warning('Failed to apply unstaged changes to worktree.'));
747
- progress(options, colors.warning("Run 'git stash pop' in main worktree to recover them."));
913
+ printStatus('warning', 'Failed to apply unstaged changes to worktree.');
914
+ printStatus('warning', "Run 'git stash pop' in main worktree to recover them.");
748
915
  }
749
916
  }
750
- await setupWorktree(worktreePath, config, options);
751
- // Run post-worktree hook
752
- await hookRunner.runHook('post-worktree');
917
+ await setupWorktree(worktreePath, config, options, repoRoot);
918
+ // Execute unified post-worktree sequence (plan generation + hook)
919
+ await executePostWorktreeSequence(worktreePath, config, options, {
920
+ prNumber: pr.number,
921
+ branchName,
922
+ description,
923
+ }, hookRunner);
924
+ setAuditContext({ prNumber: pr.number, worktreePath, gitBranch: branchName });
753
925
  printSummary(pr.number, branchName, worktreePath, pr.url, options, {
754
926
  scenario,
755
927
  actionTaken: action.action,
@@ -759,12 +931,12 @@ ${description}
759
931
  // Run cleanup hook
760
932
  await hookRunner.runCleanup(error instanceof Error ? error : undefined);
761
933
  if (actionResult.stashRef) {
762
- progress(options, colors.info('Restoring stashed changes...'));
934
+ printStatus('info', 'Restoring stashed changes...');
763
935
  try {
764
936
  git.stashPop(actionResult.stashRef);
765
937
  }
766
938
  catch {
767
- progress(options, colors.warning("Failed to restore stash. Run 'git stash pop' manually."));
939
+ printStatus('warning', "Failed to restore stash. Run 'git stash pop' manually.");
768
940
  }
769
941
  }
770
942
  throw error;
@@ -784,25 +956,23 @@ function exitWithError(message, code, useJson) {
784
956
  console.log(formatJsonResult(createErrorResult('newpr', code, message)));
785
957
  }
786
958
  else {
787
- console.error(colors.error(message));
959
+ const hint = getErrorSuggestion(code);
960
+ printError({ title: message, hint });
788
961
  }
789
962
  process.exit(1);
790
963
  }
791
964
  /**
792
- * Main entry point
965
+ * Run the newpr handler with already-parsed options.
966
+ *
967
+ * This is the core newpr workflow, callable both from the standalone CLI
968
+ * entry point and from the `wt new` handler (in-process delegation).
969
+ *
970
+ * Callers are responsible for:
971
+ * - Initializing the logger (initializeLogger)
972
+ * - Setting JSON mode (setJsonMode)
973
+ * - Setting color enabled state (setColorEnabled)
793
974
  */
794
- async function main() {
795
- const rawArgs = process.argv.slice(2);
796
- const useJson = hasJsonFlag(rawArgs);
797
- const result = parseArgs(rawArgs);
798
- if (result.kind === 'help') {
799
- console.log(getHelpText());
800
- process.exit(0);
801
- }
802
- if (result.kind === 'error') {
803
- exitWithError(result.message, ErrorCode.INVALID_ARGUMENT, useJson);
804
- }
805
- const { options } = result;
975
+ export async function runNewprHandler(options) {
806
976
  // Apply config.draftPr if user didn't explicitly set --draft or --ready
807
977
  try {
808
978
  const repoRoot = git.getRepoRoot();
@@ -839,26 +1009,55 @@ async function main() {
839
1009
  break;
840
1010
  }
841
1011
  }
842
- main().catch((error) => {
843
- // Determine if JSON output is expected
844
- const useJson = hasJsonFlag(process.argv.slice(2));
845
- if (error instanceof NonInteractiveError) {
846
- exitWithError(error.message, error.code, useJson);
847
- }
848
- const message = error instanceof Error ? error.message : String(error);
849
- const code = getErrorCodeFromError(error);
850
- const suggestion = getErrorSuggestion(code);
851
- if (useJson) {
852
- exitWithError(message, code, useJson);
1012
+ /**
1013
+ * Main entry point (standalone CLI)
1014
+ */
1015
+ async function main() {
1016
+ printDeprecationNotice('newpr', 'wt new');
1017
+ const rawArgs = process.argv.slice(2);
1018
+ const useJson = hasJsonFlag(rawArgs);
1019
+ const result = parseArgs(rawArgs);
1020
+ if (result.kind === 'help') {
1021
+ console.log(getHelpText());
1022
+ process.exit(0);
853
1023
  }
854
- else {
855
- // Show friendly error with suggestion for non-JSON mode
856
- console.error(colors.error(`Error: ${message}`));
857
- if (suggestion) {
858
- console.error('');
859
- console.error(colors.dim(suggestion));
860
- }
861
- process.exit(1);
1024
+ if (result.kind === 'error') {
1025
+ exitWithError(result.message, ErrorCode.INVALID_ARGUMENT, useJson);
1026
+ }
1027
+ const { options } = result;
1028
+ // Initialize logger (only when run as standalone CLI)
1029
+ initializeLogger({
1030
+ verbose: options.verbose,
1031
+ quiet: options.quiet,
1032
+ noColor: options.noColor,
1033
+ json: options.json,
1034
+ commandName: 'newpr',
1035
+ });
1036
+ setJsonMode(options.json);
1037
+ if (options.noColor) {
1038
+ setColorEnabled(false);
862
1039
  }
863
- });
1040
+ await runNewprHandler(options);
1041
+ }
1042
+ // Only run main() when this file is executed directly (not when imported)
1043
+ const isMain = import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, '/') || '');
1044
+ if (isMain || process.argv[1]?.endsWith('newpr.js')) {
1045
+ main().catch((error) => {
1046
+ // Determine if JSON output is expected
1047
+ const useJson = hasJsonFlag(process.argv.slice(2));
1048
+ if (error instanceof NonInteractiveError) {
1049
+ exitWithError(error.message, error.code, useJson);
1050
+ }
1051
+ if (useJson) {
1052
+ const message = error instanceof Error ? error.message : String(error);
1053
+ const code = getErrorCodeFromError(error);
1054
+ exitWithError(message, code, useJson);
1055
+ }
1056
+ else {
1057
+ const display = errorToDisplay(error);
1058
+ printError(display);
1059
+ process.exit(1);
1060
+ }
1061
+ });
1062
+ }
864
1063
  //# sourceMappingURL=newpr.js.map