@camaradesuk/git-worktree-tools 1.10.0 → 1.11.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 (426) hide show
  1. package/README.md +4 -4
  2. package/package.json +12 -3
  3. package/dist/api/list.test.d.ts +0 -5
  4. package/dist/api/list.test.d.ts.map +0 -1
  5. package/dist/api/list.test.js +0 -390
  6. package/dist/api/list.test.js.map +0 -1
  7. package/dist/cli/cleanpr.test.d.ts +0 -2
  8. package/dist/cli/cleanpr.test.d.ts.map +0 -1
  9. package/dist/cli/cleanpr.test.js +0 -954
  10. package/dist/cli/cleanpr.test.js.map +0 -1
  11. package/dist/cli/lswt.test.d.ts +0 -2
  12. package/dist/cli/lswt.test.d.ts.map +0 -1
  13. package/dist/cli/lswt.test.js +0 -376
  14. package/dist/cli/lswt.test.js.map +0 -1
  15. package/dist/cli/newpr.test.d.ts +0 -2
  16. package/dist/cli/newpr.test.d.ts.map +0 -1
  17. package/dist/cli/newpr.test.js +0 -1182
  18. package/dist/cli/newpr.test.js.map +0 -1
  19. package/dist/cli/prs.test.d.ts +0 -8
  20. package/dist/cli/prs.test.d.ts.map +0 -1
  21. package/dist/cli/prs.test.js +0 -463
  22. package/dist/cli/prs.test.js.map +0 -1
  23. package/dist/cli/wt/clean.test.d.ts +0 -8
  24. package/dist/cli/wt/clean.test.d.ts.map +0 -1
  25. package/dist/cli/wt/clean.test.js +0 -624
  26. package/dist/cli/wt/clean.test.js.map +0 -1
  27. package/dist/cli/wt/completion.test.d.ts +0 -5
  28. package/dist/cli/wt/completion.test.d.ts.map +0 -1
  29. package/dist/cli/wt/completion.test.js +0 -275
  30. package/dist/cli/wt/completion.test.js.map +0 -1
  31. package/dist/cli/wt/config.test.d.ts +0 -7
  32. package/dist/cli/wt/config.test.d.ts.map +0 -1
  33. package/dist/cli/wt/config.test.js +0 -440
  34. package/dist/cli/wt/config.test.js.map +0 -1
  35. package/dist/cli/wt/entry.test.d.ts +0 -8
  36. package/dist/cli/wt/entry.test.d.ts.map +0 -1
  37. package/dist/cli/wt/entry.test.js +0 -201
  38. package/dist/cli/wt/entry.test.js.map +0 -1
  39. package/dist/cli/wt/init.test.d.ts +0 -5
  40. package/dist/cli/wt/init.test.d.ts.map +0 -1
  41. package/dist/cli/wt/init.test.js +0 -165
  42. package/dist/cli/wt/init.test.js.map +0 -1
  43. package/dist/cli/wt/init.unit.test.d.ts +0 -5
  44. package/dist/cli/wt/init.unit.test.d.ts.map +0 -1
  45. package/dist/cli/wt/init.unit.test.js +0 -432
  46. package/dist/cli/wt/init.unit.test.js.map +0 -1
  47. package/dist/cli/wt/interactive-menu.test.d.ts +0 -12
  48. package/dist/cli/wt/interactive-menu.test.d.ts.map +0 -1
  49. package/dist/cli/wt/interactive-menu.test.js +0 -796
  50. package/dist/cli/wt/interactive-menu.test.js.map +0 -1
  51. package/dist/cli/wt/list.test.d.ts +0 -10
  52. package/dist/cli/wt/list.test.d.ts.map +0 -1
  53. package/dist/cli/wt/list.test.js +0 -157
  54. package/dist/cli/wt/list.test.js.map +0 -1
  55. package/dist/cli/wt/prs.test.d.ts +0 -5
  56. package/dist/cli/wt/prs.test.d.ts.map +0 -1
  57. package/dist/cli/wt/prs.test.js +0 -410
  58. package/dist/cli/wt/prs.test.js.map +0 -1
  59. package/dist/cli/wt/run-command.test.d.ts +0 -5
  60. package/dist/cli/wt/run-command.test.d.ts.map +0 -1
  61. package/dist/cli/wt/run-command.test.js +0 -88
  62. package/dist/cli/wt/run-command.test.js.map +0 -1
  63. package/dist/cli/wt/state.test.d.ts +0 -9
  64. package/dist/cli/wt/state.test.d.ts.map +0 -1
  65. package/dist/cli/wt/state.test.js +0 -127
  66. package/dist/cli/wt/state.test.js.map +0 -1
  67. package/dist/cli/wt/wt.test.d.ts +0 -8
  68. package/dist/cli/wt/wt.test.d.ts.map +0 -1
  69. package/dist/cli/wt/wt.test.js +0 -739
  70. package/dist/cli/wt/wt.test.js.map +0 -1
  71. package/dist/cli/wt.unit.test.d.ts +0 -7
  72. package/dist/cli/wt.unit.test.d.ts.map +0 -1
  73. package/dist/cli/wt.unit.test.js +0 -160
  74. package/dist/cli/wt.unit.test.js.map +0 -1
  75. package/dist/cli/wtconfig.test.d.ts +0 -5
  76. package/dist/cli/wtconfig.test.d.ts.map +0 -1
  77. package/dist/cli/wtconfig.test.js +0 -1289
  78. package/dist/cli/wtconfig.test.js.map +0 -1
  79. package/dist/cli/wtlink.test.d.ts +0 -2
  80. package/dist/cli/wtlink.test.d.ts.map +0 -1
  81. package/dist/cli/wtlink.test.js +0 -249
  82. package/dist/cli/wtlink.test.js.map +0 -1
  83. package/dist/cli/wtstate.test.d.ts +0 -5
  84. package/dist/cli/wtstate.test.d.ts.map +0 -1
  85. package/dist/cli/wtstate.test.js +0 -193
  86. package/dist/cli/wtstate.test.js.map +0 -1
  87. package/dist/e2e/cleanpr/cleanpr.e2e.test.d.ts +0 -2
  88. package/dist/e2e/cleanpr/cleanpr.e2e.test.d.ts.map +0 -1
  89. package/dist/e2e/cleanpr/cleanpr.e2e.test.js +0 -326
  90. package/dist/e2e/cleanpr/cleanpr.e2e.test.js.map +0 -1
  91. package/dist/e2e/cli.e2e.test.d.ts +0 -2
  92. package/dist/e2e/cli.e2e.test.d.ts.map +0 -1
  93. package/dist/e2e/cli.e2e.test.js +0 -417
  94. package/dist/e2e/cli.e2e.test.js.map +0 -1
  95. package/dist/e2e/lswt/lswt.e2e.test.d.ts +0 -2
  96. package/dist/e2e/lswt/lswt.e2e.test.d.ts.map +0 -1
  97. package/dist/e2e/lswt/lswt.e2e.test.js +0 -361
  98. package/dist/e2e/lswt/lswt.e2e.test.js.map +0 -1
  99. package/dist/e2e/newpr/newpr.e2e.test.d.ts +0 -2
  100. package/dist/e2e/newpr/newpr.e2e.test.d.ts.map +0 -1
  101. package/dist/e2e/newpr/newpr.e2e.test.js +0 -286
  102. package/dist/e2e/newpr/newpr.e2e.test.js.map +0 -1
  103. package/dist/e2e/newpr/scenarios.e2e.test.d.ts +0 -2
  104. package/dist/e2e/newpr/scenarios.e2e.test.d.ts.map +0 -1
  105. package/dist/e2e/newpr/scenarios.e2e.test.js +0 -426
  106. package/dist/e2e/newpr/scenarios.e2e.test.js.map +0 -1
  107. package/dist/e2e/newpr-full-flow.e2e.test.d.ts +0 -2
  108. package/dist/e2e/newpr-full-flow.e2e.test.d.ts.map +0 -1
  109. package/dist/e2e/newpr-full-flow.e2e.test.js +0 -280
  110. package/dist/e2e/newpr-full-flow.e2e.test.js.map +0 -1
  111. package/dist/e2e/prs/prs.e2e.test.d.ts +0 -7
  112. package/dist/e2e/prs/prs.e2e.test.d.ts.map +0 -1
  113. package/dist/e2e/prs/prs.e2e.test.js +0 -606
  114. package/dist/e2e/prs/prs.e2e.test.js.map +0 -1
  115. package/dist/e2e/workflows/pr-lifecycle.e2e.test.d.ts +0 -2
  116. package/dist/e2e/workflows/pr-lifecycle.e2e.test.d.ts.map +0 -1
  117. package/dist/e2e/workflows/pr-lifecycle.e2e.test.js +0 -298
  118. package/dist/e2e/workflows/pr-lifecycle.e2e.test.js.map +0 -1
  119. package/dist/e2e/wt/interactive-menu.e2e.test.d.ts +0 -8
  120. package/dist/e2e/wt/interactive-menu.e2e.test.d.ts.map +0 -1
  121. package/dist/e2e/wt/interactive-menu.e2e.test.js +0 -583
  122. package/dist/e2e/wt/interactive-menu.e2e.test.js.map +0 -1
  123. package/dist/e2e/wt/wt.e2e.test.d.ts +0 -9
  124. package/dist/e2e/wt/wt.e2e.test.d.ts.map +0 -1
  125. package/dist/e2e/wt/wt.e2e.test.js +0 -597
  126. package/dist/e2e/wt/wt.e2e.test.js.map +0 -1
  127. package/dist/e2e/wtlink/wtlink.e2e.test.d.ts +0 -2
  128. package/dist/e2e/wtlink/wtlink.e2e.test.d.ts.map +0 -1
  129. package/dist/e2e/wtlink/wtlink.e2e.test.js +0 -416
  130. package/dist/e2e/wtlink/wtlink.e2e.test.js.map +0 -1
  131. package/dist/integration/git.integration.test.d.ts +0 -2
  132. package/dist/integration/git.integration.test.d.ts.map +0 -1
  133. package/dist/integration/git.integration.test.js +0 -336
  134. package/dist/integration/git.integration.test.js.map +0 -1
  135. package/dist/integration/lswt-remote-pr.integration.test.d.ts +0 -2
  136. package/dist/integration/lswt-remote-pr.integration.test.d.ts.map +0 -1
  137. package/dist/integration/lswt-remote-pr.integration.test.js +0 -222
  138. package/dist/integration/lswt-remote-pr.integration.test.js.map +0 -1
  139. package/dist/integration/newpr-branchfrom-head.integration.test.d.ts +0 -2
  140. package/dist/integration/newpr-branchfrom-head.integration.test.d.ts.map +0 -1
  141. package/dist/integration/newpr-branchfrom-head.integration.test.js +0 -498
  142. package/dist/integration/newpr-branchfrom-head.integration.test.js.map +0 -1
  143. package/dist/integration/newpr.integration.test.d.ts +0 -2
  144. package/dist/integration/newpr.integration.test.d.ts.map +0 -1
  145. package/dist/integration/newpr.integration.test.js +0 -460
  146. package/dist/integration/newpr.integration.test.js.map +0 -1
  147. package/dist/integration/prs.integration.test.d.ts +0 -8
  148. package/dist/integration/prs.integration.test.d.ts.map +0 -1
  149. package/dist/integration/prs.integration.test.js +0 -478
  150. package/dist/integration/prs.integration.test.js.map +0 -1
  151. package/dist/lib/ai/base-provider.test.d.ts +0 -7
  152. package/dist/lib/ai/base-provider.test.d.ts.map +0 -1
  153. package/dist/lib/ai/base-provider.test.js +0 -319
  154. package/dist/lib/ai/base-provider.test.js.map +0 -1
  155. package/dist/lib/ai/cli-provider.test.d.ts +0 -5
  156. package/dist/lib/ai/cli-provider.test.d.ts.map +0 -1
  157. package/dist/lib/ai/cli-provider.test.js +0 -460
  158. package/dist/lib/ai/cli-provider.test.js.map +0 -1
  159. package/dist/lib/ai/fallback-provider.test.d.ts +0 -7
  160. package/dist/lib/ai/fallback-provider.test.d.ts.map +0 -1
  161. package/dist/lib/ai/fallback-provider.test.js +0 -165
  162. package/dist/lib/ai/fallback-provider.test.js.map +0 -1
  163. package/dist/lib/ai/generation-service.test.d.ts +0 -7
  164. package/dist/lib/ai/generation-service.test.d.ts.map +0 -1
  165. package/dist/lib/ai/generation-service.test.js +0 -213
  166. package/dist/lib/ai/generation-service.test.js.map +0 -1
  167. package/dist/lib/ai/provider-manager.test.d.ts +0 -5
  168. package/dist/lib/ai/provider-manager.test.d.ts.map +0 -1
  169. package/dist/lib/ai/provider-manager.test.js +0 -312
  170. package/dist/lib/ai/provider-manager.test.js.map +0 -1
  171. package/dist/lib/ai/repo-docs.test.d.ts +0 -5
  172. package/dist/lib/ai/repo-docs.test.d.ts.map +0 -1
  173. package/dist/lib/ai/repo-docs.test.js +0 -357
  174. package/dist/lib/ai/repo-docs.test.js.map +0 -1
  175. package/dist/lib/cleanpr/args.test.d.ts +0 -2
  176. package/dist/lib/cleanpr/args.test.d.ts.map +0 -1
  177. package/dist/lib/cleanpr/args.test.js +0 -269
  178. package/dist/lib/cleanpr/args.test.js.map +0 -1
  179. package/dist/lib/cleanpr/cleanup.test.d.ts +0 -2
  180. package/dist/lib/cleanpr/cleanup.test.d.ts.map +0 -1
  181. package/dist/lib/cleanpr/cleanup.test.js +0 -296
  182. package/dist/lib/cleanpr/cleanup.test.js.map +0 -1
  183. package/dist/lib/cleanpr/worktree-info.test.d.ts +0 -2
  184. package/dist/lib/cleanpr/worktree-info.test.d.ts.map +0 -1
  185. package/dist/lib/cleanpr/worktree-info.test.js +0 -228
  186. package/dist/lib/cleanpr/worktree-info.test.js.map +0 -1
  187. package/dist/lib/colors.test.d.ts +0 -2
  188. package/dist/lib/colors.test.d.ts.map +0 -1
  189. package/dist/lib/colors.test.js +0 -142
  190. package/dist/lib/colors.test.js.map +0 -1
  191. package/dist/lib/config-editor.test.d.ts +0 -11
  192. package/dist/lib/config-editor.test.d.ts.map +0 -1
  193. package/dist/lib/config-editor.test.js +0 -526
  194. package/dist/lib/config-editor.test.js.map +0 -1
  195. package/dist/lib/config-migration/detector.test.d.ts +0 -5
  196. package/dist/lib/config-migration/detector.test.d.ts.map +0 -1
  197. package/dist/lib/config-migration/detector.test.js +0 -201
  198. package/dist/lib/config-migration/detector.test.js.map +0 -1
  199. package/dist/lib/config-migration/reporter.test.d.ts +0 -5
  200. package/dist/lib/config-migration/reporter.test.d.ts.map +0 -1
  201. package/dist/lib/config-migration/reporter.test.js +0 -305
  202. package/dist/lib/config-migration/reporter.test.js.map +0 -1
  203. package/dist/lib/config-migration/runner.test.d.ts +0 -5
  204. package/dist/lib/config-migration/runner.test.d.ts.map +0 -1
  205. package/dist/lib/config-migration/runner.test.js +0 -235
  206. package/dist/lib/config-migration/runner.test.js.map +0 -1
  207. package/dist/lib/config-validation.test.d.ts +0 -5
  208. package/dist/lib/config-validation.test.d.ts.map +0 -1
  209. package/dist/lib/config-validation.test.js +0 -423
  210. package/dist/lib/config-validation.test.js.map +0 -1
  211. package/dist/lib/config.test.d.ts +0 -2
  212. package/dist/lib/config.test.d.ts.map +0 -1
  213. package/dist/lib/config.test.js +0 -554
  214. package/dist/lib/config.test.js.map +0 -1
  215. package/dist/lib/constants.test.d.ts +0 -5
  216. package/dist/lib/constants.test.d.ts.map +0 -1
  217. package/dist/lib/constants.test.js +0 -180
  218. package/dist/lib/constants.test.js.map +0 -1
  219. package/dist/lib/deprecation.test.d.ts +0 -2
  220. package/dist/lib/deprecation.test.d.ts.map +0 -1
  221. package/dist/lib/deprecation.test.js +0 -71
  222. package/dist/lib/deprecation.test.js.map +0 -1
  223. package/dist/lib/errors.test.d.ts +0 -2
  224. package/dist/lib/errors.test.d.ts.map +0 -1
  225. package/dist/lib/errors.test.js +0 -117
  226. package/dist/lib/errors.test.js.map +0 -1
  227. package/dist/lib/git.test.d.ts +0 -2
  228. package/dist/lib/git.test.d.ts.map +0 -1
  229. package/dist/lib/git.test.js +0 -608
  230. package/dist/lib/git.test.js.map +0 -1
  231. package/dist/lib/github.test.d.ts +0 -2
  232. package/dist/lib/github.test.d.ts.map +0 -1
  233. package/dist/lib/github.test.js +0 -441
  234. package/dist/lib/github.test.js.map +0 -1
  235. package/dist/lib/global-check.test.d.ts +0 -5
  236. package/dist/lib/global-check.test.d.ts.map +0 -1
  237. package/dist/lib/global-check.test.js +0 -150
  238. package/dist/lib/global-check.test.js.map +0 -1
  239. package/dist/lib/global-config.test.d.ts +0 -5
  240. package/dist/lib/global-config.test.d.ts.map +0 -1
  241. package/dist/lib/global-config.test.js +0 -282
  242. package/dist/lib/global-config.test.js.map +0 -1
  243. package/dist/lib/hooks/confirmation.test.d.ts +0 -7
  244. package/dist/lib/hooks/confirmation.test.d.ts.map +0 -1
  245. package/dist/lib/hooks/confirmation.test.js +0 -300
  246. package/dist/lib/hooks/confirmation.test.js.map +0 -1
  247. package/dist/lib/hooks/executor.test.d.ts +0 -5
  248. package/dist/lib/hooks/executor.test.d.ts.map +0 -1
  249. package/dist/lib/hooks/executor.test.js +0 -648
  250. package/dist/lib/hooks/executor.test.js.map +0 -1
  251. package/dist/lib/hooks/templates.test.d.ts +0 -5
  252. package/dist/lib/hooks/templates.test.d.ts.map +0 -1
  253. package/dist/lib/hooks/templates.test.js +0 -163
  254. package/dist/lib/hooks/templates.test.js.map +0 -1
  255. package/dist/lib/hooks/types.test.d.ts +0 -5
  256. package/dist/lib/hooks/types.test.d.ts.map +0 -1
  257. package/dist/lib/hooks/types.test.js +0 -132
  258. package/dist/lib/hooks/types.test.js.map +0 -1
  259. package/dist/lib/json-output.test.d.ts +0 -5
  260. package/dist/lib/json-output.test.d.ts.map +0 -1
  261. package/dist/lib/json-output.test.js +0 -261
  262. package/dist/lib/json-output.test.js.map +0 -1
  263. package/dist/lib/logger.test.d.ts +0 -14
  264. package/dist/lib/logger.test.d.ts.map +0 -1
  265. package/dist/lib/logger.test.js +0 -692
  266. package/dist/lib/logger.test.js.map +0 -1
  267. package/dist/lib/lswt/action-executors.test.d.ts +0 -2
  268. package/dist/lib/lswt/action-executors.test.d.ts.map +0 -1
  269. package/dist/lib/lswt/action-executors.test.js +0 -1127
  270. package/dist/lib/lswt/action-executors.test.js.map +0 -1
  271. package/dist/lib/lswt/actions.test.d.ts +0 -2
  272. package/dist/lib/lswt/actions.test.d.ts.map +0 -1
  273. package/dist/lib/lswt/actions.test.js +0 -497
  274. package/dist/lib/lswt/actions.test.js.map +0 -1
  275. package/dist/lib/lswt/args.test.d.ts +0 -2
  276. package/dist/lib/lswt/args.test.d.ts.map +0 -1
  277. package/dist/lib/lswt/args.test.js +0 -195
  278. package/dist/lib/lswt/args.test.js.map +0 -1
  279. package/dist/lib/lswt/environment.test.d.ts +0 -2
  280. package/dist/lib/lswt/environment.test.d.ts.map +0 -1
  281. package/dist/lib/lswt/environment.test.js +0 -544
  282. package/dist/lib/lswt/environment.test.js.map +0 -1
  283. package/dist/lib/lswt/formatters.test.d.ts +0 -2
  284. package/dist/lib/lswt/formatters.test.d.ts.map +0 -1
  285. package/dist/lib/lswt/formatters.test.js +0 -323
  286. package/dist/lib/lswt/formatters.test.js.map +0 -1
  287. package/dist/lib/lswt/fuzzy-search.test.d.ts +0 -5
  288. package/dist/lib/lswt/fuzzy-search.test.d.ts.map +0 -1
  289. package/dist/lib/lswt/fuzzy-search.test.js +0 -207
  290. package/dist/lib/lswt/fuzzy-search.test.js.map +0 -1
  291. package/dist/lib/lswt/interactive.test.d.ts +0 -2
  292. package/dist/lib/lswt/interactive.test.d.ts.map +0 -1
  293. package/dist/lib/lswt/interactive.test.js +0 -771
  294. package/dist/lib/lswt/interactive.test.js.map +0 -1
  295. package/dist/lib/lswt/table.test.d.ts +0 -5
  296. package/dist/lib/lswt/table.test.d.ts.map +0 -1
  297. package/dist/lib/lswt/table.test.js +0 -262
  298. package/dist/lib/lswt/table.test.js.map +0 -1
  299. package/dist/lib/lswt/worktree-info.test.d.ts +0 -2
  300. package/dist/lib/lswt/worktree-info.test.d.ts.map +0 -1
  301. package/dist/lib/lswt/worktree-info.test.js +0 -484
  302. package/dist/lib/lswt/worktree-info.test.js.map +0 -1
  303. package/dist/lib/newpr/action-deps.test.d.ts +0 -5
  304. package/dist/lib/newpr/action-deps.test.d.ts.map +0 -1
  305. package/dist/lib/newpr/action-deps.test.js +0 -111
  306. package/dist/lib/newpr/action-deps.test.js.map +0 -1
  307. package/dist/lib/newpr/actions.test.d.ts +0 -2
  308. package/dist/lib/newpr/actions.test.d.ts.map +0 -1
  309. package/dist/lib/newpr/actions.test.js +0 -254
  310. package/dist/lib/newpr/actions.test.js.map +0 -1
  311. package/dist/lib/newpr/args.test.d.ts +0 -2
  312. package/dist/lib/newpr/args.test.d.ts.map +0 -1
  313. package/dist/lib/newpr/args.test.js +0 -479
  314. package/dist/lib/newpr/args.test.js.map +0 -1
  315. package/dist/lib/newpr/hook-runner.test.d.ts +0 -7
  316. package/dist/lib/newpr/hook-runner.test.d.ts.map +0 -1
  317. package/dist/lib/newpr/hook-runner.test.js +0 -422
  318. package/dist/lib/newpr/hook-runner.test.js.map +0 -1
  319. package/dist/lib/newpr/plan-generator.test.d.ts +0 -7
  320. package/dist/lib/newpr/plan-generator.test.d.ts.map +0 -1
  321. package/dist/lib/newpr/plan-generator.test.js +0 -387
  322. package/dist/lib/newpr/plan-generator.test.js.map +0 -1
  323. package/dist/lib/newpr/scenario-handler.test.d.ts +0 -2
  324. package/dist/lib/newpr/scenario-handler.test.d.ts.map +0 -1
  325. package/dist/lib/newpr/scenario-handler.test.js +0 -256
  326. package/dist/lib/newpr/scenario-handler.test.js.map +0 -1
  327. package/dist/lib/prompts.test.d.ts +0 -2
  328. package/dist/lib/prompts.test.d.ts.map +0 -1
  329. package/dist/lib/prompts.test.js +0 -807
  330. package/dist/lib/prompts.test.js.map +0 -1
  331. package/dist/lib/prs/actions.test.d.ts +0 -5
  332. package/dist/lib/prs/actions.test.d.ts.map +0 -1
  333. package/dist/lib/prs/actions.test.js +0 -356
  334. package/dist/lib/prs/actions.test.js.map +0 -1
  335. package/dist/lib/prs/command.test.d.ts +0 -11
  336. package/dist/lib/prs/command.test.d.ts.map +0 -1
  337. package/dist/lib/prs/command.test.js +0 -409
  338. package/dist/lib/prs/command.test.js.map +0 -1
  339. package/dist/lib/prs/data.test.d.ts +0 -5
  340. package/dist/lib/prs/data.test.d.ts.map +0 -1
  341. package/dist/lib/prs/data.test.js +0 -417
  342. package/dist/lib/prs/data.test.js.map +0 -1
  343. package/dist/lib/prs/details.test.d.ts +0 -5
  344. package/dist/lib/prs/details.test.d.ts.map +0 -1
  345. package/dist/lib/prs/details.test.js +0 -325
  346. package/dist/lib/prs/details.test.js.map +0 -1
  347. package/dist/lib/prs/filters.test.d.ts +0 -5
  348. package/dist/lib/prs/filters.test.d.ts.map +0 -1
  349. package/dist/lib/prs/filters.test.js +0 -312
  350. package/dist/lib/prs/filters.test.js.map +0 -1
  351. package/dist/lib/prs/formatters.test.d.ts +0 -2
  352. package/dist/lib/prs/formatters.test.d.ts.map +0 -1
  353. package/dist/lib/prs/formatters.test.js +0 -387
  354. package/dist/lib/prs/formatters.test.js.map +0 -1
  355. package/dist/lib/prs/interactive.test.d.ts +0 -5
  356. package/dist/lib/prs/interactive.test.d.ts.map +0 -1
  357. package/dist/lib/prs/interactive.test.js +0 -517
  358. package/dist/lib/prs/interactive.test.js.map +0 -1
  359. package/dist/lib/schema.test.d.ts +0 -10
  360. package/dist/lib/schema.test.d.ts.map +0 -1
  361. package/dist/lib/schema.test.js +0 -309
  362. package/dist/lib/schema.test.js.map +0 -1
  363. package/dist/lib/state-detection.test.d.ts +0 -2
  364. package/dist/lib/state-detection.test.d.ts.map +0 -1
  365. package/dist/lib/state-detection.test.js +0 -451
  366. package/dist/lib/state-detection.test.js.map +0 -1
  367. package/dist/lib/ui/error.test.d.ts +0 -2
  368. package/dist/lib/ui/error.test.d.ts.map +0 -1
  369. package/dist/lib/ui/error.test.js +0 -143
  370. package/dist/lib/ui/error.test.js.map +0 -1
  371. package/dist/lib/ui/output.test.d.ts +0 -2
  372. package/dist/lib/ui/output.test.d.ts.map +0 -1
  373. package/dist/lib/ui/output.test.js +0 -59
  374. package/dist/lib/ui/output.test.js.map +0 -1
  375. package/dist/lib/ui/status.test.d.ts +0 -2
  376. package/dist/lib/ui/status.test.d.ts.map +0 -1
  377. package/dist/lib/ui/status.test.js +0 -158
  378. package/dist/lib/ui/status.test.js.map +0 -1
  379. package/dist/lib/ui/table.test.d.ts +0 -2
  380. package/dist/lib/ui/table.test.d.ts.map +0 -1
  381. package/dist/lib/ui/table.test.js +0 -115
  382. package/dist/lib/ui/table.test.js.map +0 -1
  383. package/dist/lib/ui/theme.test.d.ts +0 -2
  384. package/dist/lib/ui/theme.test.d.ts.map +0 -1
  385. package/dist/lib/ui/theme.test.js +0 -76
  386. package/dist/lib/ui/theme.test.js.map +0 -1
  387. package/dist/lib/wtconfig/config-manager.test.d.ts +0 -5
  388. package/dist/lib/wtconfig/config-manager.test.d.ts.map +0 -1
  389. package/dist/lib/wtconfig/config-manager.test.js +0 -501
  390. package/dist/lib/wtconfig/config-manager.test.js.map +0 -1
  391. package/dist/lib/wtconfig/environment.test.d.ts +0 -5
  392. package/dist/lib/wtconfig/environment.test.d.ts.map +0 -1
  393. package/dist/lib/wtconfig/environment.test.js +0 -285
  394. package/dist/lib/wtconfig/environment.test.js.map +0 -1
  395. package/dist/lib/wtlink/config-manifest.test.d.ts +0 -2
  396. package/dist/lib/wtlink/config-manifest.test.d.ts.map +0 -1
  397. package/dist/lib/wtlink/config-manifest.test.js +0 -486
  398. package/dist/lib/wtlink/config-manifest.test.js.map +0 -1
  399. package/dist/lib/wtlink/link-configs.test.d.ts +0 -2
  400. package/dist/lib/wtlink/link-configs.test.d.ts.map +0 -1
  401. package/dist/lib/wtlink/link-configs.test.js +0 -612
  402. package/dist/lib/wtlink/link-configs.test.js.map +0 -1
  403. package/dist/lib/wtlink/main-menu.test.d.ts +0 -5
  404. package/dist/lib/wtlink/main-menu.test.d.ts.map +0 -1
  405. package/dist/lib/wtlink/main-menu.test.js +0 -126
  406. package/dist/lib/wtlink/main-menu.test.js.map +0 -1
  407. package/dist/lib/wtlink/manage-manifest.test.d.ts +0 -2
  408. package/dist/lib/wtlink/manage-manifest.test.d.ts.map +0 -1
  409. package/dist/lib/wtlink/manage-manifest.test.js +0 -714
  410. package/dist/lib/wtlink/manage-manifest.test.js.map +0 -1
  411. package/dist/lib/wtlink/validate-manifest.test.d.ts +0 -2
  412. package/dist/lib/wtlink/validate-manifest.test.d.ts.map +0 -1
  413. package/dist/lib/wtlink/validate-manifest.test.js +0 -220
  414. package/dist/lib/wtlink/validate-manifest.test.js.map +0 -1
  415. package/dist/lib/wtstate/analyze.test.d.ts +0 -5
  416. package/dist/lib/wtstate/analyze.test.d.ts.map +0 -1
  417. package/dist/lib/wtstate/analyze.test.js +0 -282
  418. package/dist/lib/wtstate/analyze.test.js.map +0 -1
  419. package/dist/lib/wtstate/args.test.d.ts +0 -5
  420. package/dist/lib/wtstate/args.test.d.ts.map +0 -1
  421. package/dist/lib/wtstate/args.test.js +0 -120
  422. package/dist/lib/wtstate/args.test.js.map +0 -1
  423. package/dist/mcp/server.test.d.ts +0 -9
  424. package/dist/mcp/server.test.d.ts.map +0 -1
  425. package/dist/mcp/server.test.js +0 -550
  426. package/dist/mcp/server.test.js.map +0 -1
@@ -1,1182 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- // Mock modules before importing the CLI
3
- vi.mock('../lib/git.js', () => ({
4
- getRepoRoot: vi.fn(),
5
- getRepoName: vi.fn(),
6
- getMainWorktreeRoot: vi.fn(),
7
- fetch: vi.fn(),
8
- fetchAsync: vi.fn().mockResolvedValue(undefined),
9
- getCurrentBranch: vi.fn(),
10
- checkout: vi.fn(),
11
- exec: vi.fn(),
12
- push: vi.fn(),
13
- pushAsync: vi.fn().mockResolvedValue(undefined),
14
- commit: vi.fn(),
15
- add: vi.fn(),
16
- stash: vi.fn(),
17
- stashApply: vi.fn(),
18
- stashDrop: vi.fn(),
19
- stashPop: vi.fn(),
20
- addWorktree: vi.fn(),
21
- addWorktreeAsync: vi.fn().mockResolvedValue(undefined),
22
- getStagedFiles: vi.fn(),
23
- getUnstagedFiles: vi.fn(),
24
- getStatusOutput: vi.fn(),
25
- getCommitsAhead: vi.fn(),
26
- remoteBranchExists: vi.fn(),
27
- branchExists: vi.fn(),
28
- getChangedFiles: vi.fn(),
29
- getCommitMessages: vi.fn(),
30
- }));
31
- vi.mock('../lib/github.js', () => ({
32
- isGhInstalled: vi.fn(),
33
- isAuthenticated: vi.fn(),
34
- getPr: vi.fn(),
35
- getPrByBranch: vi.fn(),
36
- createPr: vi.fn(),
37
- }));
38
- vi.mock('../lib/prompts.js', () => ({
39
- promptChoiceIndex: vi.fn(),
40
- promptConfirm: vi.fn(),
41
- withSpinner: vi.fn(async (message, fn) => {
42
- return await fn();
43
- }),
44
- }));
45
- vi.mock('../lib/config.js', () => ({
46
- loadConfig: vi.fn(),
47
- generateBranchNameAsync: vi.fn(),
48
- generateWorktreePath: vi.fn(),
49
- generatePRContentAsync: vi.fn(),
50
- }));
51
- vi.mock('../lib/state-detection.js', () => ({
52
- analyzeGitState: vi.fn(),
53
- detectScenario: vi.fn(),
54
- }));
55
- // Create a mock hook runner that always allows hooks to pass
56
- const mockHookRunner = {
57
- runHook: vi.fn().mockResolvedValue(true),
58
- runCleanup: vi.fn().mockResolvedValue(undefined),
59
- updateContext: vi.fn(),
60
- hasConfiguredHooks: vi.fn().mockReturnValue(false),
61
- getConfiguredHooks: vi.fn().mockReturnValue([]),
62
- getContext: vi.fn().mockReturnValue({}),
63
- };
64
- vi.mock('../lib/newpr/index.js', () => ({
65
- parseArgs: vi.fn(),
66
- getHelpText: vi.fn(),
67
- getScenarioContext: vi.fn(),
68
- isPrWorktreeScenario: vi.fn(),
69
- isExistingBranchAction: vi.fn(),
70
- executeStateAction: vi.fn(),
71
- getBranchPoint: vi.fn(),
72
- getScenarioMessageLevel: vi.fn(),
73
- createHookRunner: vi.fn(() => mockHookRunner),
74
- createActionDeps: vi.fn(() => ({
75
- gitAdd: vi.fn(),
76
- gitStash: vi.fn(),
77
- gitPush: vi.fn(),
78
- gitCommit: vi.fn(),
79
- })),
80
- HookRunner: vi.fn(),
81
- runLifecycleHook: vi.fn().mockResolvedValue(true),
82
- }));
83
- vi.mock('fs', () => ({
84
- default: {
85
- existsSync: vi.fn(),
86
- symlinkSync: vi.fn(),
87
- },
88
- existsSync: vi.fn(),
89
- symlinkSync: vi.fn(),
90
- }));
91
- vi.mock('../lib/wtlink/config-manifest.js', () => ({
92
- getEnabledFiles: vi.fn(),
93
- }));
94
- vi.mock('../lib/wtlink/link-configs.js', () => ({
95
- run: vi.fn(),
96
- }));
97
- // Import after mocking
98
- import * as git from '../lib/git.js';
99
- import * as github from '../lib/github.js';
100
- import * as prompts from '../lib/prompts.js';
101
- import { loadConfig, generateBranchNameAsync, generateWorktreePath, generatePRContentAsync, } from '../lib/config.js';
102
- import { analyzeGitState, detectScenario } from '../lib/state-detection.js';
103
- import * as newpr from '../lib/newpr/index.js';
104
- import fs from 'fs';
105
- import { getEnabledFiles } from '../lib/wtlink/config-manifest.js';
106
- import { run as runWtlink } from '../lib/wtlink/link-configs.js';
107
- describe('cli/newpr', () => {
108
- let mockConsoleLog;
109
- let mockConsoleError;
110
- let mockProcessExit;
111
- let originalArgv;
112
- const defaultConfig = {
113
- configVersion: 1,
114
- baseBranch: 'main',
115
- worktreePattern: '{repo}.pr{number}',
116
- worktreeParent: '..',
117
- draftPr: false,
118
- sharedRepos: [],
119
- branchPrefix: 'feature',
120
- previewLabel: 'preview',
121
- syncPatterns: [],
122
- preferredEditor: 'auto',
123
- ai: { provider: 'none' },
124
- hooks: {},
125
- hookDefaults: { timeout: 30000, maxTimeout: 60000 },
126
- plugins: [],
127
- generators: {},
128
- integrations: {},
129
- logging: { level: 'info', timestamps: true },
130
- global: { warnNotGlobal: true },
131
- wtlink: { enabled: [], disabled: [] },
132
- linkConfigFiles: undefined,
133
- };
134
- const defaultOptions = {
135
- baseBranch: 'main',
136
- draft: false,
137
- installDeps: false,
138
- openEditor: false,
139
- runWtlink: false,
140
- json: false,
141
- nonInteractive: false,
142
- noHooks: false,
143
- };
144
- const makePrInfo = (overrides = {}) => ({
145
- number: 123,
146
- title: 'Test PR',
147
- state: 'OPEN',
148
- headBranch: 'feature-123',
149
- baseBranch: 'main',
150
- url: 'https://github.com/org/repo/pull/123',
151
- isDraft: false,
152
- ...overrides,
153
- });
154
- const makeGitState = (overrides = {}) => ({
155
- worktreeType: 'main_worktree',
156
- branchType: 'main',
157
- currentBranch: 'main',
158
- commitRelationship: 'same',
159
- workingTreeStatus: 'clean',
160
- localCommits: [],
161
- stagedFiles: [],
162
- unstagedFiles: [],
163
- repoRoot: '/repo',
164
- repoName: 'repo',
165
- ...overrides,
166
- });
167
- beforeEach(() => {
168
- vi.resetAllMocks();
169
- // Reset async git mocks (resetAllMocks clears implementations)
170
- vi.mocked(git.fetchAsync).mockResolvedValue(undefined);
171
- vi.mocked(git.pushAsync).mockResolvedValue(undefined);
172
- vi.mocked(git.addWorktreeAsync).mockResolvedValue(undefined);
173
- // Reset withSpinner mock (resetAllMocks clears the implementation)
174
- vi.mocked(prompts.withSpinner).mockImplementation(async (message, fn) => {
175
- return await fn();
176
- });
177
- // Reset the hook runner mock to allow all hooks to pass
178
- mockHookRunner.runHook.mockResolvedValue(true);
179
- mockHookRunner.runCleanup.mockResolvedValue(undefined);
180
- mockHookRunner.updateContext.mockReturnValue(undefined);
181
- mockHookRunner.hasConfiguredHooks.mockReturnValue(false);
182
- mockHookRunner.getConfiguredHooks.mockReturnValue([]);
183
- mockHookRunner.getContext.mockReturnValue({});
184
- // Reset createHookRunner to return the mock
185
- vi.mocked(newpr.createHookRunner).mockReturnValue(mockHookRunner);
186
- // Reset createActionDeps to return a mock deps object
187
- vi.mocked(newpr.createActionDeps).mockReturnValue({
188
- gitAdd: vi.fn(),
189
- gitStash: vi.fn(),
190
- gitPush: vi.fn(),
191
- gitCommit: vi.fn(),
192
- });
193
- // Default mocks for AI generation functions
194
- vi.mocked(generatePRContentAsync).mockResolvedValue({
195
- title: 'Test PR',
196
- description: '',
197
- aiGenerated: false,
198
- });
199
- vi.mocked(git.getChangedFiles).mockReturnValue([]);
200
- vi.mocked(git.getCommitMessages).mockReturnValue([]);
201
- // Default mocks for auto-link feature (throw by default to skip auto-link)
202
- vi.mocked(git.getMainWorktreeRoot).mockImplementation(() => {
203
- throw new Error('Mock: getMainWorktreeRoot not configured');
204
- });
205
- vi.mocked(getEnabledFiles).mockReturnValue([]);
206
- vi.mocked(runWtlink).mockResolvedValue(undefined);
207
- mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => { });
208
- mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => { });
209
- // @ts-expect-error - process.exit mock type is complex
210
- mockProcessExit = vi.spyOn(process, 'exit').mockImplementation((() => { }));
211
- originalArgv = process.argv;
212
- });
213
- afterEach(() => {
214
- mockConsoleLog.mockRestore();
215
- mockConsoleError.mockRestore();
216
- mockProcessExit.mockRestore();
217
- process.argv = originalArgv;
218
- vi.resetModules();
219
- });
220
- async function runCli(args = []) {
221
- process.argv = ['node', '/path/to/newpr.js', ...args];
222
- await import('./newpr.js');
223
- // Allow time for all async operations to complete
224
- await new Promise((resolve) => setTimeout(resolve, 100));
225
- }
226
- describe('help option', () => {
227
- it('prints help and exits 0 on --help', async () => {
228
- vi.mocked(newpr.parseArgs).mockReturnValue({ kind: 'help' });
229
- vi.mocked(newpr.getHelpText).mockReturnValue('Usage: newpr [options]');
230
- await runCli(['--help']);
231
- expect(newpr.getHelpText).toHaveBeenCalled();
232
- expect(mockConsoleLog).toHaveBeenCalledWith('Usage: newpr [options]');
233
- expect(mockProcessExit).toHaveBeenCalledWith(0);
234
- });
235
- });
236
- describe('error handling', () => {
237
- it('prints error and exits 1 on parse error', async () => {
238
- vi.mocked(newpr.parseArgs).mockReturnValue({
239
- kind: 'error',
240
- message: 'Invalid option: --invalid',
241
- });
242
- await runCli(['--invalid']);
243
- expect(mockConsoleError).toHaveBeenCalled();
244
- expect(mockProcessExit).toHaveBeenCalledWith(1);
245
- });
246
- it('exits 1 when gh not installed', async () => {
247
- vi.mocked(newpr.parseArgs).mockReturnValue({
248
- kind: 'success',
249
- options: { mode: 'new', description: 'test', ...defaultOptions },
250
- });
251
- vi.mocked(github.isGhInstalled).mockReturnValue(false);
252
- await runCli(['test']);
253
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('GitHub CLI'));
254
- expect(mockProcessExit).toHaveBeenCalledWith(1);
255
- });
256
- it('exits 1 when gh not authenticated', async () => {
257
- vi.mocked(newpr.parseArgs).mockReturnValue({
258
- kind: 'success',
259
- options: { mode: 'new', description: 'test', ...defaultOptions },
260
- });
261
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
262
- vi.mocked(github.isAuthenticated).mockReturnValue(false);
263
- await runCli(['test']);
264
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('not authenticated'));
265
- expect(mockProcessExit).toHaveBeenCalledWith(1);
266
- });
267
- });
268
- describe('--pr mode', () => {
269
- it('sets up worktree for existing PR', async () => {
270
- vi.mocked(newpr.parseArgs).mockReturnValue({
271
- kind: 'success',
272
- options: { mode: 'pr', prNumber: 123, ...defaultOptions },
273
- });
274
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
275
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
276
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
277
- vi.mocked(git.getRepoName).mockReturnValue('repo');
278
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
279
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
280
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
281
- vi.mocked(fs.existsSync).mockReturnValue(false);
282
- await runCli(['--pr', '123']);
283
- // Verify wiring: worktree path, branch, and options are correctly passed through
284
- // Non-JSON mode uses addWorktreeAsync
285
- expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr123', // path from generateWorktreePath
286
- 'feature-123', // branch from PR info
287
- expect.objectContaining({
288
- createBranch: true,
289
- startPoint: 'origin/feature-123',
290
- }));
291
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('PR #123'));
292
- });
293
- it('exits 1 when PR not found', async () => {
294
- vi.mocked(newpr.parseArgs).mockReturnValue({
295
- kind: 'success',
296
- options: { mode: 'pr', prNumber: 999, ...defaultOptions },
297
- });
298
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
299
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
300
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
301
- vi.mocked(git.getRepoName).mockReturnValue('repo');
302
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
303
- vi.mocked(github.getPr).mockReturnValue(null);
304
- await runCli(['--pr', '999']);
305
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Could not find PR'));
306
- expect(mockProcessExit).toHaveBeenCalledWith(1);
307
- });
308
- });
309
- describe('--branch mode', () => {
310
- it('creates PR for existing branch', async () => {
311
- vi.mocked(newpr.parseArgs).mockReturnValue({
312
- kind: 'success',
313
- options: { mode: 'branch', branchName: 'my-feature', ...defaultOptions },
314
- });
315
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
316
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
317
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
318
- vi.mocked(git.getRepoName).mockReturnValue('repo');
319
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
320
- vi.mocked(git.remoteBranchExists).mockReturnValue(true);
321
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
322
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 456 }));
323
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr456');
324
- vi.mocked(fs.existsSync).mockReturnValue(false);
325
- await runCli(['--branch', 'my-feature']);
326
- // Verify wiring: createPr receives correct branch and description
327
- expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({
328
- head: 'my-feature',
329
- base: 'main', // from config
330
- }));
331
- // Verify wiring: addWorktreeAsync receives correct path, branch, and options (non-JSON mode)
332
- expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr456', // path from generateWorktreePath
333
- 'my-feature', // the branch name
334
- expect.objectContaining({
335
- createBranch: true,
336
- startPoint: 'origin/my-feature',
337
- }));
338
- });
339
- it('uses existing PR if branch already has one', async () => {
340
- const existingPr = makePrInfo({ number: 789 });
341
- vi.mocked(newpr.parseArgs).mockReturnValue({
342
- kind: 'success',
343
- options: { mode: 'branch', branchName: 'my-feature', ...defaultOptions },
344
- });
345
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
346
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
347
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
348
- vi.mocked(git.getRepoName).mockReturnValue('repo');
349
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
350
- vi.mocked(git.remoteBranchExists).mockReturnValue(true);
351
- vi.mocked(github.getPrByBranch).mockReturnValue(existingPr);
352
- vi.mocked(github.getPr).mockReturnValue(existingPr);
353
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr789');
354
- vi.mocked(fs.existsSync).mockReturnValue(false);
355
- await runCli(['--branch', 'my-feature']);
356
- expect(github.createPr).not.toHaveBeenCalled();
357
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('PR #789 already exists'));
358
- });
359
- });
360
- describe('new feature mode', () => {
361
- it('creates new branch, PR, and worktree', async () => {
362
- vi.mocked(newpr.parseArgs).mockReturnValue({
363
- kind: 'success',
364
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
365
- });
366
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
367
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
368
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
369
- vi.mocked(git.getRepoName).mockReturnValue('repo');
370
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
371
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
372
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
373
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
374
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
375
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
376
- message: 'No changes detected',
377
- choices: [
378
- {
379
- label: 'Create empty commit',
380
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
381
- },
382
- { label: 'Cancel', action: null },
383
- ],
384
- });
385
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
386
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1); // 1-based index
387
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
388
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
389
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
390
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
391
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
392
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
393
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
394
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
395
- vi.mocked(generatePRContentAsync).mockResolvedValue({
396
- title: 'Add new feature',
397
- description: '',
398
- aiGenerated: false,
399
- });
400
- await runCli(['Add new feature']);
401
- // Verify wiring: checkout uses generated branch name and branch point with repoRoot
402
- expect(git.exec).toHaveBeenCalledWith([
403
- 'checkout',
404
- '-b',
405
- 'feature/add-new-feature', // from generateBranchNameAsync
406
- 'origin/main', // from getBranchPoint
407
- ], { cwd: '/repo' } // repoRoot from getRepoRoot()
408
- );
409
- // Verify wiring: createPr receives correct branch and title (from generatePRContentAsync)
410
- expect(github.createPr).toHaveBeenCalledWith(expect.objectContaining({
411
- head: 'feature/add-new-feature',
412
- base: 'main',
413
- title: 'Add new feature',
414
- }));
415
- // Verify wiring: addWorktreeAsync receives correct path, branch, and cwd option
416
- expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr100', // path from generateWorktreePath
417
- 'feature/add-new-feature', // the branch name
418
- { cwd: '/repo' } // repoRoot from getRepoRoot()
419
- );
420
- });
421
- it('exits 1 when user cancels', async () => {
422
- vi.mocked(newpr.parseArgs).mockReturnValue({
423
- kind: 'success',
424
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
425
- });
426
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
427
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
428
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
429
- vi.mocked(git.getRepoName).mockReturnValue('repo');
430
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
431
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
432
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
433
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
434
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
435
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
436
- message: 'No changes detected',
437
- choices: [
438
- {
439
- label: 'Create empty commit',
440
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
441
- },
442
- { label: 'Cancel', action: null },
443
- ],
444
- });
445
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
446
- // User selects Cancel (option 2 in 1-based, array index 1)
447
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(2); // 1-based index
448
- await runCli(['Add new feature']);
449
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Aborted'));
450
- expect(mockProcessExit).toHaveBeenCalledWith(1);
451
- });
452
- it('shows helpful error when checkout fails due to conflicting changes', async () => {
453
- vi.mocked(newpr.parseArgs).mockReturnValue({
454
- kind: 'success',
455
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
456
- });
457
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
458
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
459
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
460
- vi.mocked(git.getRepoName).mockReturnValue('repo');
461
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
462
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
463
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
464
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
465
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
466
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
467
- message: 'No changes detected',
468
- choices: [
469
- {
470
- label: 'Create empty commit',
471
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
472
- },
473
- { label: 'Cancel', action: null },
474
- ],
475
- });
476
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
477
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1); // 1-based index
478
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
479
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
480
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
481
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
482
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
483
- vi.mocked(git.getStagedFiles).mockReturnValue(['README.md']);
484
- vi.mocked(git.getUnstagedFiles).mockReturnValue([]);
485
- // Mock checkout to fail with a conflict error
486
- vi.mocked(git.exec).mockImplementation(() => {
487
- throw new Error("error: Your local changes to 'README.md' would be overwritten by checkout");
488
- });
489
- await runCli(['Add new feature']);
490
- // Verify helpful error messages are shown
491
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Checkout failed due to conflicting changes'));
492
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Your staged changes are preserved'));
493
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Commit your changes first'));
494
- expect(mockProcessExit).toHaveBeenCalledWith(1);
495
- });
496
- });
497
- describe('JSON output mode', () => {
498
- it('outputs JSON error when gh not installed with --json flag', async () => {
499
- vi.mocked(newpr.parseArgs).mockReturnValue({
500
- kind: 'success',
501
- options: { mode: 'new', description: 'test', ...defaultOptions, json: true },
502
- });
503
- vi.mocked(github.isGhInstalled).mockReturnValue(false);
504
- await runCli(['test', '--json']);
505
- // Should output JSON error, not plain text
506
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
507
- expect(jsonOutput).toBeDefined();
508
- expect(jsonOutput[0]).toContain('"code": "GH_NOT_INSTALLED"');
509
- expect(mockProcessExit).toHaveBeenCalledWith(1);
510
- });
511
- it('outputs JSON error when gh not authenticated with --json flag', async () => {
512
- vi.mocked(newpr.parseArgs).mockReturnValue({
513
- kind: 'success',
514
- options: { mode: 'new', description: 'test', ...defaultOptions, json: true },
515
- });
516
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
517
- vi.mocked(github.isAuthenticated).mockReturnValue(false);
518
- await runCli(['test', '--json']);
519
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
520
- expect(jsonOutput).toBeDefined();
521
- expect(jsonOutput[0]).toContain('"code": "GH_NOT_AUTHENTICATED"');
522
- expect(mockProcessExit).toHaveBeenCalledWith(1);
523
- });
524
- it('outputs JSON success for --pr mode', async () => {
525
- vi.mocked(newpr.parseArgs).mockReturnValue({
526
- kind: 'success',
527
- options: { mode: 'pr', prNumber: 123, ...defaultOptions, json: true },
528
- });
529
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
530
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
531
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
532
- vi.mocked(git.getRepoName).mockReturnValue('repo');
533
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
534
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
535
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
536
- vi.mocked(fs.existsSync).mockReturnValue(false);
537
- await runCli(['--pr', '123', '--json']);
538
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": true'));
539
- expect(jsonOutput).toBeDefined();
540
- expect(jsonOutput[0]).toContain('"prNumber": 123');
541
- });
542
- it('outputs JSON error when parse fails with --json flag', async () => {
543
- vi.mocked(newpr.parseArgs).mockReturnValue({
544
- kind: 'error',
545
- message: 'Missing required argument',
546
- });
547
- await runCli(['--json']);
548
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
549
- expect(jsonOutput).toBeDefined();
550
- expect(jsonOutput[0]).toContain('"code": "INVALID_ARGUMENT"');
551
- expect(mockProcessExit).toHaveBeenCalledWith(1);
552
- });
553
- });
554
- describe('non-interactive mode', () => {
555
- it('uses first available action when no --action specified', async () => {
556
- vi.mocked(newpr.parseArgs).mockReturnValue({
557
- kind: 'success',
558
- options: {
559
- mode: 'new',
560
- description: 'Add new feature',
561
- ...defaultOptions,
562
- nonInteractive: true,
563
- },
564
- });
565
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
566
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
567
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
568
- vi.mocked(git.getRepoName).mockReturnValue('repo');
569
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
570
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
571
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
572
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
573
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
574
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
575
- message: 'No changes detected',
576
- choices: [
577
- {
578
- label: 'Create empty commit',
579
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
580
- },
581
- { label: 'Cancel', action: null },
582
- ],
583
- });
584
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
585
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
586
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
587
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
588
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
589
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
590
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
591
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
592
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
593
- await runCli(['Add new feature', '--non-interactive']);
594
- // Should not prompt - should use first available action
595
- expect(prompts.promptChoiceIndex).not.toHaveBeenCalled();
596
- expect(newpr.executeStateAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'empty_commit' }), expect.any(String), expect.any(String), expect.any(Object), expect.any(String));
597
- });
598
- it('uses specified action when --action is provided', async () => {
599
- vi.mocked(newpr.parseArgs).mockReturnValue({
600
- kind: 'success',
601
- options: {
602
- mode: 'new',
603
- description: 'Add new feature',
604
- ...defaultOptions,
605
- nonInteractive: true,
606
- action: 'commit_staged',
607
- },
608
- });
609
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
610
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
611
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
612
- vi.mocked(git.getRepoName).mockReturnValue('repo');
613
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
614
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
615
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState({
616
- workingTreeStatus: 'has_staged',
617
- stagedFiles: ['file.ts'],
618
- }));
619
- vi.mocked(detectScenario).mockReturnValue('main_staged_same');
620
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
621
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
622
- message: 'You have staged changes',
623
- choices: [
624
- {
625
- label: 'Commit staged changes',
626
- action: { action: 'commit_staged', branchFrom: 'origin_main', stashUnstaged: false },
627
- },
628
- {
629
- label: 'Create empty commit',
630
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
631
- },
632
- { label: 'Cancel', action: null },
633
- ],
634
- });
635
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('info');
636
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
637
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
638
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
639
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
640
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
641
- vi.mocked(git.getStagedFiles).mockReturnValue(['file.ts']);
642
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
643
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
644
- await runCli(['Add new feature', '--non-interactive', '--action', 'commit_staged']);
645
- expect(prompts.promptChoiceIndex).not.toHaveBeenCalled();
646
- expect(newpr.executeStateAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'commit_staged' }), expect.any(String), expect.any(String), expect.any(Object), expect.any(String));
647
- });
648
- it('exits with error when --action specifies invalid action', async () => {
649
- vi.mocked(newpr.parseArgs).mockReturnValue({
650
- kind: 'success',
651
- options: {
652
- mode: 'new',
653
- description: 'Add new feature',
654
- ...defaultOptions,
655
- nonInteractive: true,
656
- action: 'invalid_action', // intentionally invalid for test
657
- },
658
- });
659
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
660
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
661
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
662
- vi.mocked(git.getRepoName).mockReturnValue('repo');
663
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
664
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
665
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
666
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
667
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
668
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
669
- message: 'No changes detected',
670
- choices: [
671
- {
672
- label: 'Create empty commit',
673
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
674
- },
675
- { label: 'Cancel', action: null },
676
- ],
677
- });
678
- await runCli(['Add new feature', '--non-interactive', '--action', 'invalid_action']);
679
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining("Action 'invalid_action' is not available"));
680
- expect(mockProcessExit).toHaveBeenCalledWith(1);
681
- });
682
- it('exits with error in non-interactive mode from PR worktree', async () => {
683
- vi.mocked(newpr.parseArgs).mockReturnValue({
684
- kind: 'success',
685
- options: {
686
- mode: 'new',
687
- description: 'Add new feature',
688
- ...defaultOptions,
689
- nonInteractive: true,
690
- },
691
- });
692
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
693
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
694
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo.pr123');
695
- vi.mocked(git.getRepoName).mockReturnValue('repo');
696
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
697
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
698
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState({
699
- worktreeType: 'pr_worktree',
700
- }));
701
- vi.mocked(detectScenario).mockReturnValue('pr_worktree');
702
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(true);
703
- await runCli(['Add new feature', '--non-interactive']);
704
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Cannot create PR from a PR worktree in non-interactive mode'));
705
- expect(mockProcessExit).toHaveBeenCalledWith(1);
706
- });
707
- it('outputs JSON error when --action is invalid with --json flag', async () => {
708
- vi.mocked(newpr.parseArgs).mockReturnValue({
709
- kind: 'success',
710
- options: {
711
- mode: 'new',
712
- description: 'Add new feature',
713
- ...defaultOptions,
714
- nonInteractive: true,
715
- action: 'invalid_action', // intentionally invalid for test
716
- json: true,
717
- },
718
- });
719
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
720
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
721
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
722
- vi.mocked(git.getRepoName).mockReturnValue('repo');
723
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
724
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
725
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
726
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
727
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
728
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
729
- message: 'No changes detected',
730
- choices: [
731
- {
732
- label: 'Create empty commit',
733
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
734
- },
735
- { label: 'Cancel', action: null },
736
- ],
737
- });
738
- await runCli([
739
- 'Add new feature',
740
- '--non-interactive',
741
- '--action',
742
- 'invalid_action',
743
- '--json',
744
- ]);
745
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
746
- expect(jsonOutput).toBeDefined();
747
- expect(jsonOutput[0]).toContain('"code": "INVALID_ACTION"');
748
- expect(mockProcessExit).toHaveBeenCalledWith(1);
749
- });
750
- });
751
- describe('PR not found handling', () => {
752
- it('outputs JSON error when PR not found with --json flag', async () => {
753
- vi.mocked(newpr.parseArgs).mockReturnValue({
754
- kind: 'success',
755
- options: { mode: 'pr', prNumber: 999, ...defaultOptions, json: true },
756
- });
757
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
758
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
759
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
760
- vi.mocked(git.getRepoName).mockReturnValue('repo');
761
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
762
- vi.mocked(github.getPr).mockReturnValue(null);
763
- await runCli(['--pr', '999', '--json']);
764
- // JSON mode outputs to console.log, not console.error
765
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Could not find PR'));
766
- expect(mockProcessExit).toHaveBeenCalledWith(1);
767
- });
768
- });
769
- describe('user cancellation handling', () => {
770
- it('outputs JSON error when user cancels with --json flag', async () => {
771
- vi.mocked(newpr.parseArgs).mockReturnValue({
772
- kind: 'success',
773
- options: { mode: 'new', description: 'test', ...defaultOptions, json: true },
774
- });
775
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
776
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
777
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
778
- vi.mocked(git.getRepoName).mockReturnValue('repo');
779
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
780
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/test');
781
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
782
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
783
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
784
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
785
- message: 'No changes detected',
786
- choices: [
787
- {
788
- label: 'Create empty commit',
789
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
790
- },
791
- { label: 'Cancel', action: null },
792
- ],
793
- });
794
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
795
- // User selects Cancel (index 1 in array, so 2 in 1-based indexing)
796
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(2);
797
- await runCli(['test', '--json']);
798
- const jsonOutput = mockConsoleLog.mock.calls.find((call) => String(call[0]).includes('"success": false'));
799
- expect(jsonOutput).toBeDefined();
800
- expect(jsonOutput[0]).toContain('"code": "USER_CANCELLED"');
801
- expect(mockProcessExit).toHaveBeenCalledWith(1);
802
- });
803
- });
804
- describe('executeStateAction parameter verification', () => {
805
- it('passes repoRoot to executeStateAction for existing branch actions', async () => {
806
- const repoRoot = '/repo';
807
- vi.mocked(newpr.parseArgs).mockReturnValue({
808
- kind: 'success',
809
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
810
- });
811
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
812
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
813
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
814
- vi.mocked(git.getRepoName).mockReturnValue('repo');
815
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
816
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
817
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState({
818
- currentBranch: 'feature/existing-branch',
819
- branchType: 'feature',
820
- workingTreeStatus: 'has_staged',
821
- stagedFiles: ['file.ts'],
822
- }));
823
- vi.mocked(detectScenario).mockReturnValue('branch_with_changes');
824
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
825
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
826
- message: 'You have uncommitted changes',
827
- choices: [
828
- {
829
- label: 'Commit all and create PR',
830
- action: { action: 'commit_all', branchFrom: 'head', stashUnstaged: false },
831
- },
832
- { label: 'Cancel', action: null },
833
- ],
834
- });
835
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('info');
836
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1); // 1-based index
837
- // KEY: This is the existing branch action path
838
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(true);
839
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
840
- vi.mocked(git.remoteBranchExists).mockReturnValue(true);
841
- vi.mocked(git.getCurrentBranch).mockReturnValue('feature/existing-branch');
842
- vi.mocked(github.getPrByBranch).mockReturnValue(null);
843
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
844
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
845
- await runCli(['Add new feature']);
846
- // Verify executeStateAction was called with repoRoot as the 5th parameter
847
- expect(newpr.executeStateAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'commit_all' }), expect.any(String), 'feature/existing-branch', expect.any(Object), repoRoot // This is the critical parameter that was missing
848
- );
849
- });
850
- it('passes repoRoot to executeStateAction for new branch actions', async () => {
851
- const repoRoot = '/repo';
852
- vi.mocked(newpr.parseArgs).mockReturnValue({
853
- kind: 'success',
854
- options: { mode: 'new', description: 'Add new feature', ...defaultOptions },
855
- });
856
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
857
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
858
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
859
- vi.mocked(git.getRepoName).mockReturnValue('repo');
860
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
861
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feature/add-new-feature');
862
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
863
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
864
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
865
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
866
- message: 'No changes detected',
867
- choices: [
868
- {
869
- label: 'Create empty commit',
870
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
871
- },
872
- { label: 'Cancel', action: null },
873
- ],
874
- });
875
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
876
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1); // 1-based index
877
- // This is the new branch action path
878
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
879
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
880
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
881
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
882
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
883
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
884
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
885
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
886
- await runCli(['Add new feature']);
887
- // Verify executeStateAction was called with repoRoot as the 5th parameter
888
- expect(newpr.executeStateAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'empty_commit' }), expect.any(String), 'feature/add-new-feature', expect.any(Object), repoRoot // This ensures git operations run from repo root
889
- );
890
- });
891
- });
892
- describe('Bug fix: git operations must use repoRoot (empty commit worktree bug)', () => {
893
- // These tests verify the fix for the bug where creating a PR worktree
894
- // with an empty commit failed because git.checkout() didn't switch back
895
- // to main (missing repoRoot parameter), causing git.addWorktree() to fail
896
- // with "branch already checked out" error.
897
- it('git.checkout after push must use repoRoot parameter', async () => {
898
- const repoRoot = '/repo';
899
- vi.mocked(newpr.parseArgs).mockReturnValue({
900
- kind: 'success',
901
- options: { mode: 'new', description: 'Test feature', ...defaultOptions },
902
- });
903
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
904
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
905
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
906
- vi.mocked(git.getRepoName).mockReturnValue('repo');
907
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
908
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
909
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
910
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
911
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
912
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
913
- message: 'No changes detected',
914
- choices: [
915
- {
916
- label: 'Continue with empty initial commit',
917
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
918
- },
919
- { label: 'Cancel', action: null },
920
- ],
921
- });
922
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
923
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
924
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
925
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
926
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
927
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
928
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
929
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
930
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
931
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
932
- await runCli(['Test feature']);
933
- // CRITICAL: git.checkout must be called with repoRoot to switch back to main
934
- // Without this, the branch remains checked out and worktree creation fails
935
- expect(git.checkout).toHaveBeenCalledWith('main', repoRoot);
936
- });
937
- it('git.addWorktreeAsync must use { cwd: repoRoot } option', async () => {
938
- const repoRoot = '/repo';
939
- vi.mocked(newpr.parseArgs).mockReturnValue({
940
- kind: 'success',
941
- options: { mode: 'new', description: 'Test feature', ...defaultOptions },
942
- });
943
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
944
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
945
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
946
- vi.mocked(git.getRepoName).mockReturnValue('repo');
947
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
948
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
949
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
950
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
951
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
952
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
953
- message: 'No changes detected',
954
- choices: [
955
- {
956
- label: 'Continue with empty initial commit',
957
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
958
- },
959
- { label: 'Cancel', action: null },
960
- ],
961
- });
962
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
963
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
964
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
965
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
966
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
967
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
968
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
969
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
970
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
971
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
972
- await runCli(['Test feature']);
973
- // CRITICAL: git.addWorktreeAsync must be called with { cwd: repoRoot }
974
- // to ensure worktree is created from the correct directory
975
- expect(git.addWorktreeAsync).toHaveBeenCalledWith('/repo.pr100', 'feat/test-feature', {
976
- cwd: repoRoot,
977
- });
978
- });
979
- it('git.pushAsync must use repoRoot parameter', async () => {
980
- const repoRoot = '/repo';
981
- vi.mocked(newpr.parseArgs).mockReturnValue({
982
- kind: 'success',
983
- options: { mode: 'new', description: 'Test feature', ...defaultOptions },
984
- });
985
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
986
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
987
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
988
- vi.mocked(git.getRepoName).mockReturnValue('repo');
989
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
990
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
991
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
992
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
993
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
994
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
995
- message: 'No changes detected',
996
- choices: [
997
- {
998
- label: 'Continue with empty initial commit',
999
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
1000
- },
1001
- { label: 'Cancel', action: null },
1002
- ],
1003
- });
1004
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
1005
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
1006
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
1007
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
1008
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
1009
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
1010
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
1011
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
1012
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
1013
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
1014
- await runCli(['Test feature']);
1015
- // git.pushAsync must be called with repoRoot to push from correct directory
1016
- expect(git.pushAsync).toHaveBeenCalledWith({ setUpstream: true, remote: 'origin', branch: 'feat/test-feature' }, repoRoot);
1017
- });
1018
- it('git.exec for branch checkout must use { cwd: repoRoot } option', async () => {
1019
- const repoRoot = '/repo';
1020
- vi.mocked(newpr.parseArgs).mockReturnValue({
1021
- kind: 'success',
1022
- options: { mode: 'new', description: 'Test feature', ...defaultOptions },
1023
- });
1024
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
1025
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
1026
- vi.mocked(git.getRepoRoot).mockReturnValue(repoRoot);
1027
- vi.mocked(git.getRepoName).mockReturnValue('repo');
1028
- vi.mocked(loadConfig).mockReturnValue(defaultConfig);
1029
- vi.mocked(generateBranchNameAsync).mockResolvedValue('feat/test-feature');
1030
- vi.mocked(analyzeGitState).mockReturnValue(makeGitState());
1031
- vi.mocked(detectScenario).mockReturnValue('main_clean_same');
1032
- vi.mocked(newpr.isPrWorktreeScenario).mockReturnValue(false);
1033
- vi.mocked(newpr.getScenarioContext).mockReturnValue({
1034
- message: 'No changes detected',
1035
- choices: [
1036
- {
1037
- label: 'Continue with empty initial commit',
1038
- action: { action: 'empty_commit', branchFrom: 'origin_main', stashUnstaged: false },
1039
- },
1040
- { label: 'Cancel', action: null },
1041
- ],
1042
- });
1043
- vi.mocked(newpr.getScenarioMessageLevel).mockReturnValue('warning');
1044
- vi.mocked(prompts.promptChoiceIndex).mockResolvedValue(1);
1045
- vi.mocked(newpr.isExistingBranchAction).mockReturnValue(false);
1046
- vi.mocked(newpr.executeStateAction).mockReturnValue({ success: true, stashRef: null });
1047
- vi.mocked(newpr.getBranchPoint).mockReturnValue('origin/main');
1048
- vi.mocked(git.remoteBranchExists).mockReturnValue(false);
1049
- vi.mocked(git.getCurrentBranch).mockReturnValue('main');
1050
- vi.mocked(git.getStagedFiles).mockReturnValue([]);
1051
- vi.mocked(github.createPr).mockReturnValue(makePrInfo({ number: 100 }));
1052
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr100');
1053
- await runCli(['Test feature']);
1054
- // git.exec for branch creation must use cwd option to ensure
1055
- // branch is created in the correct worktree
1056
- expect(git.exec).toHaveBeenCalledWith(['checkout', '-b', 'feat/test-feature', 'origin/main'], { cwd: repoRoot });
1057
- });
1058
- });
1059
- describe('auto-link config files (linkConfigFiles feature)', () => {
1060
- // Helper to set up all the mocks for a successful PR creation
1061
- const setupPrCreationMocks = (configOverrides = {}) => {
1062
- vi.mocked(newpr.parseArgs).mockReturnValue({
1063
- kind: 'success',
1064
- options: { mode: 'pr', prNumber: 123, ...defaultOptions },
1065
- });
1066
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
1067
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
1068
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
1069
- vi.mocked(git.getRepoName).mockReturnValue('repo');
1070
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
1071
- vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, ...configOverrides });
1072
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
1073
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
1074
- vi.mocked(fs.existsSync).mockReturnValue(false);
1075
- };
1076
- it('auto-links config files when linkConfigFiles is true', async () => {
1077
- setupPrCreationMocks({ linkConfigFiles: true });
1078
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local', '.vscode/settings.json']);
1079
- vi.mocked(runWtlink).mockResolvedValue(undefined);
1080
- await runCli(['--pr', '123']);
1081
- expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
1082
- expect(runWtlink).toHaveBeenCalledWith(expect.objectContaining({
1083
- source: '/main-repo',
1084
- destination: '/repo.pr123',
1085
- dryRun: false,
1086
- yes: true,
1087
- }));
1088
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1089
- });
1090
- it('skips linking when linkConfigFiles is false', async () => {
1091
- setupPrCreationMocks({ linkConfigFiles: false });
1092
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local', '.vscode/settings.json']);
1093
- await runCli(['--pr', '123']);
1094
- expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
1095
- expect(runWtlink).not.toHaveBeenCalled();
1096
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1097
- });
1098
- it('prompts user when linkConfigFiles is undefined in interactive mode', async () => {
1099
- setupPrCreationMocks({ linkConfigFiles: undefined });
1100
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1101
- vi.mocked(prompts.promptConfirm).mockResolvedValue(true);
1102
- vi.mocked(runWtlink).mockResolvedValue(undefined);
1103
- await runCli(['--pr', '123']);
1104
- expect(getEnabledFiles).toHaveBeenCalledWith('/main-repo');
1105
- expect(prompts.promptConfirm).toHaveBeenCalledWith(expect.stringContaining('Link these config files'), true);
1106
- expect(runWtlink).toHaveBeenCalled();
1107
- });
1108
- it('skips linking when user declines the prompt', async () => {
1109
- setupPrCreationMocks({ linkConfigFiles: undefined });
1110
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1111
- vi.mocked(prompts.promptConfirm).mockResolvedValue(false);
1112
- await runCli(['--pr', '123']);
1113
- expect(prompts.promptConfirm).toHaveBeenCalled();
1114
- expect(runWtlink).not.toHaveBeenCalled();
1115
- });
1116
- it('defaults to linking in non-interactive mode when linkConfigFiles is undefined', async () => {
1117
- vi.mocked(newpr.parseArgs).mockReturnValue({
1118
- kind: 'success',
1119
- options: { mode: 'pr', prNumber: 123, ...defaultOptions, nonInteractive: true },
1120
- });
1121
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
1122
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
1123
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
1124
- vi.mocked(git.getRepoName).mockReturnValue('repo');
1125
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
1126
- vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, linkConfigFiles: undefined });
1127
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
1128
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
1129
- vi.mocked(fs.existsSync).mockReturnValue(false);
1130
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1131
- vi.mocked(runWtlink).mockResolvedValue(undefined);
1132
- await runCli(['--pr', '123', '--non-interactive']);
1133
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1134
- expect(runWtlink).toHaveBeenCalled();
1135
- });
1136
- it('defaults to linking in JSON mode when linkConfigFiles is undefined', async () => {
1137
- vi.mocked(newpr.parseArgs).mockReturnValue({
1138
- kind: 'success',
1139
- options: { mode: 'pr', prNumber: 123, ...defaultOptions, json: true },
1140
- });
1141
- vi.mocked(github.isGhInstalled).mockReturnValue(true);
1142
- vi.mocked(github.isAuthenticated).mockReturnValue(true);
1143
- vi.mocked(git.getRepoRoot).mockReturnValue('/repo');
1144
- vi.mocked(git.getRepoName).mockReturnValue('repo');
1145
- vi.mocked(git.getMainWorktreeRoot).mockReturnValue('/main-repo');
1146
- vi.mocked(loadConfig).mockReturnValue({ ...defaultConfig, linkConfigFiles: undefined });
1147
- vi.mocked(github.getPr).mockReturnValue(makePrInfo());
1148
- vi.mocked(generateWorktreePath).mockReturnValue('/repo.pr123');
1149
- vi.mocked(fs.existsSync).mockReturnValue(false);
1150
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1151
- vi.mocked(runWtlink).mockResolvedValue(undefined);
1152
- await runCli(['--pr', '123', '--json']);
1153
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1154
- expect(runWtlink).toHaveBeenCalled();
1155
- });
1156
- it('does not link when there are no enabled files', async () => {
1157
- setupPrCreationMocks({ linkConfigFiles: true });
1158
- vi.mocked(getEnabledFiles).mockReturnValue([]);
1159
- await runCli(['--pr', '123']);
1160
- expect(runWtlink).not.toHaveBeenCalled();
1161
- expect(prompts.promptConfirm).not.toHaveBeenCalled();
1162
- });
1163
- it('handles linking errors gracefully', async () => {
1164
- setupPrCreationMocks({ linkConfigFiles: true });
1165
- vi.mocked(getEnabledFiles).mockReturnValue(['.env.local']);
1166
- vi.mocked(runWtlink).mockRejectedValue(new Error('Permission denied'));
1167
- await runCli(['--pr', '123']);
1168
- // Should not crash, just warn about the failure
1169
- expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Failed to link config files'));
1170
- });
1171
- it('skips config linking when main worktree cannot be determined', async () => {
1172
- setupPrCreationMocks({ linkConfigFiles: true });
1173
- vi.mocked(git.getMainWorktreeRoot).mockImplementation(() => {
1174
- throw new Error('Not in a git worktree');
1175
- });
1176
- await runCli(['--pr', '123']);
1177
- expect(getEnabledFiles).not.toHaveBeenCalled();
1178
- expect(runWtlink).not.toHaveBeenCalled();
1179
- });
1180
- });
1181
- });
1182
- //# sourceMappingURL=newpr.test.js.map