pull-request-split-advisor 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/LICENSE +52 -0
  2. package/README.md +168 -0
  3. package/dist/ai/config-wizard.js +282 -0
  4. package/dist/ai/enricher.js +290 -0
  5. package/dist/ai/prompts.js +231 -0
  6. package/dist/ai/provider.js +265 -0
  7. package/dist/cli.js +442 -0
  8. package/dist/config/config.js +315 -0
  9. package/dist/config/default-config.js +223 -0
  10. package/dist/core/blocks.js +145 -0
  11. package/dist/core/commit-planner.js +273 -0
  12. package/dist/core/dependency.js +284 -0
  13. package/dist/core/file-stats.js +341 -0
  14. package/dist/core/history.js +72 -0
  15. package/dist/core/planner.js +25 -0
  16. package/dist/core/scoring.js +166 -0
  17. package/dist/core/strategy.js +486 -0
  18. package/dist/git/branch-naming.js +120 -0
  19. package/dist/git/executor.js +378 -0
  20. package/dist/git/git.js +239 -0
  21. package/dist/output/report-styles.generated.js +10 -0
  22. package/dist/output/report.js +726 -0
  23. package/dist/output/ui.js +417 -0
  24. package/dist/shared/constants.js +59 -0
  25. package/dist/shared/types.js +7 -0
  26. package/dist/shared/utils.js +73 -0
  27. package/node_modules/@colors/colors/LICENSE +26 -0
  28. package/node_modules/@colors/colors/README.md +219 -0
  29. package/node_modules/@colors/colors/examples/normal-usage.js +83 -0
  30. package/node_modules/@colors/colors/examples/safe-string.js +80 -0
  31. package/node_modules/@colors/colors/index.d.ts +136 -0
  32. package/node_modules/@colors/colors/lib/colors.js +211 -0
  33. package/node_modules/@colors/colors/lib/custom/trap.js +46 -0
  34. package/node_modules/@colors/colors/lib/custom/zalgo.js +110 -0
  35. package/node_modules/@colors/colors/lib/extendStringPrototype.js +110 -0
  36. package/node_modules/@colors/colors/lib/index.js +13 -0
  37. package/node_modules/@colors/colors/lib/maps/america.js +10 -0
  38. package/node_modules/@colors/colors/lib/maps/rainbow.js +12 -0
  39. package/node_modules/@colors/colors/lib/maps/random.js +11 -0
  40. package/node_modules/@colors/colors/lib/maps/zebra.js +5 -0
  41. package/node_modules/@colors/colors/lib/styles.js +95 -0
  42. package/node_modules/@colors/colors/lib/system/has-flag.js +35 -0
  43. package/node_modules/@colors/colors/lib/system/supports-colors.js +151 -0
  44. package/node_modules/@colors/colors/package.json +45 -0
  45. package/node_modules/@colors/colors/safe.d.ts +48 -0
  46. package/node_modules/@colors/colors/safe.js +10 -0
  47. package/node_modules/@colors/colors/themes/generic-logging.js +12 -0
  48. package/node_modules/ansi-align/LICENSE +13 -0
  49. package/node_modules/ansi-align/README.md +80 -0
  50. package/node_modules/ansi-align/index.js +61 -0
  51. package/node_modules/ansi-align/node_modules/ansi-regex/index.d.ts +37 -0
  52. package/node_modules/ansi-align/node_modules/ansi-regex/index.js +10 -0
  53. package/node_modules/ansi-align/node_modules/ansi-regex/license +9 -0
  54. package/node_modules/ansi-align/node_modules/ansi-regex/package.json +55 -0
  55. package/node_modules/ansi-align/node_modules/ansi-regex/readme.md +78 -0
  56. package/node_modules/ansi-align/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
  57. package/node_modules/ansi-align/node_modules/emoji-regex/README.md +73 -0
  58. package/node_modules/ansi-align/node_modules/emoji-regex/es2015/index.js +6 -0
  59. package/node_modules/ansi-align/node_modules/emoji-regex/es2015/text.js +6 -0
  60. package/node_modules/ansi-align/node_modules/emoji-regex/index.d.ts +23 -0
  61. package/node_modules/ansi-align/node_modules/emoji-regex/index.js +6 -0
  62. package/node_modules/ansi-align/node_modules/emoji-regex/package.json +50 -0
  63. package/node_modules/ansi-align/node_modules/emoji-regex/text.js +6 -0
  64. package/node_modules/ansi-align/node_modules/string-width/index.d.ts +29 -0
  65. package/node_modules/ansi-align/node_modules/string-width/index.js +47 -0
  66. package/node_modules/ansi-align/node_modules/string-width/license +9 -0
  67. package/node_modules/ansi-align/node_modules/string-width/package.json +56 -0
  68. package/node_modules/ansi-align/node_modules/string-width/readme.md +50 -0
  69. package/node_modules/ansi-align/node_modules/strip-ansi/index.d.ts +17 -0
  70. package/node_modules/ansi-align/node_modules/strip-ansi/index.js +4 -0
  71. package/node_modules/ansi-align/node_modules/strip-ansi/license +9 -0
  72. package/node_modules/ansi-align/node_modules/strip-ansi/package.json +54 -0
  73. package/node_modules/ansi-align/node_modules/strip-ansi/readme.md +46 -0
  74. package/node_modules/ansi-align/package.json +43 -0
  75. package/node_modules/ansi-regex/index.d.ts +33 -0
  76. package/node_modules/ansi-regex/index.js +14 -0
  77. package/node_modules/ansi-regex/license +9 -0
  78. package/node_modules/ansi-regex/package.json +61 -0
  79. package/node_modules/ansi-regex/readme.md +66 -0
  80. package/node_modules/ansi-styles/index.d.ts +236 -0
  81. package/node_modules/ansi-styles/index.js +223 -0
  82. package/node_modules/ansi-styles/license +9 -0
  83. package/node_modules/ansi-styles/package.json +54 -0
  84. package/node_modules/ansi-styles/readme.md +173 -0
  85. package/node_modules/boxen/index.d.ts +267 -0
  86. package/node_modules/boxen/index.js +376 -0
  87. package/node_modules/boxen/license +9 -0
  88. package/node_modules/boxen/package.json +69 -0
  89. package/node_modules/boxen/readme.md +300 -0
  90. package/node_modules/camelcase/index.d.ts +102 -0
  91. package/node_modules/camelcase/index.js +110 -0
  92. package/node_modules/camelcase/license +9 -0
  93. package/node_modules/camelcase/package.json +47 -0
  94. package/node_modules/camelcase/readme.md +135 -0
  95. package/node_modules/chalk/license +9 -0
  96. package/node_modules/chalk/package.json +83 -0
  97. package/node_modules/chalk/readme.md +297 -0
  98. package/node_modules/chalk/source/index.d.ts +325 -0
  99. package/node_modules/chalk/source/index.js +225 -0
  100. package/node_modules/chalk/source/utilities.js +33 -0
  101. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  102. package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  103. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  104. package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  105. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  106. package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  107. package/node_modules/cli-boxes/boxes.json +82 -0
  108. package/node_modules/cli-boxes/index.d.ts +127 -0
  109. package/node_modules/cli-boxes/index.js +6 -0
  110. package/node_modules/cli-boxes/license +9 -0
  111. package/node_modules/cli-boxes/package.json +42 -0
  112. package/node_modules/cli-boxes/readme.md +115 -0
  113. package/node_modules/cli-table3/LICENSE +21 -0
  114. package/node_modules/cli-table3/README.md +236 -0
  115. package/node_modules/cli-table3/index.d.ts +96 -0
  116. package/node_modules/cli-table3/index.js +1 -0
  117. package/node_modules/cli-table3/node_modules/ansi-regex/index.d.ts +37 -0
  118. package/node_modules/cli-table3/node_modules/ansi-regex/index.js +10 -0
  119. package/node_modules/cli-table3/node_modules/ansi-regex/license +9 -0
  120. package/node_modules/cli-table3/node_modules/ansi-regex/package.json +55 -0
  121. package/node_modules/cli-table3/node_modules/ansi-regex/readme.md +78 -0
  122. package/node_modules/cli-table3/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
  123. package/node_modules/cli-table3/node_modules/emoji-regex/README.md +73 -0
  124. package/node_modules/cli-table3/node_modules/emoji-regex/es2015/index.js +6 -0
  125. package/node_modules/cli-table3/node_modules/emoji-regex/es2015/text.js +6 -0
  126. package/node_modules/cli-table3/node_modules/emoji-regex/index.d.ts +23 -0
  127. package/node_modules/cli-table3/node_modules/emoji-regex/index.js +6 -0
  128. package/node_modules/cli-table3/node_modules/emoji-regex/package.json +50 -0
  129. package/node_modules/cli-table3/node_modules/emoji-regex/text.js +6 -0
  130. package/node_modules/cli-table3/node_modules/string-width/index.d.ts +29 -0
  131. package/node_modules/cli-table3/node_modules/string-width/index.js +47 -0
  132. package/node_modules/cli-table3/node_modules/string-width/license +9 -0
  133. package/node_modules/cli-table3/node_modules/string-width/package.json +56 -0
  134. package/node_modules/cli-table3/node_modules/string-width/readme.md +50 -0
  135. package/node_modules/cli-table3/node_modules/strip-ansi/index.d.ts +17 -0
  136. package/node_modules/cli-table3/node_modules/strip-ansi/index.js +4 -0
  137. package/node_modules/cli-table3/node_modules/strip-ansi/license +9 -0
  138. package/node_modules/cli-table3/node_modules/strip-ansi/package.json +54 -0
  139. package/node_modules/cli-table3/node_modules/strip-ansi/readme.md +46 -0
  140. package/node_modules/cli-table3/package.json +100 -0
  141. package/node_modules/cli-table3/src/cell.js +409 -0
  142. package/node_modules/cli-table3/src/debug.js +28 -0
  143. package/node_modules/cli-table3/src/layout-manager.js +254 -0
  144. package/node_modules/cli-table3/src/table.js +106 -0
  145. package/node_modules/cli-table3/src/utils.js +344 -0
  146. package/node_modules/commander/LICENSE +22 -0
  147. package/node_modules/commander/Readme.md +1157 -0
  148. package/node_modules/commander/esm.mjs +16 -0
  149. package/node_modules/commander/index.js +24 -0
  150. package/node_modules/commander/lib/argument.js +149 -0
  151. package/node_modules/commander/lib/command.js +2509 -0
  152. package/node_modules/commander/lib/error.js +39 -0
  153. package/node_modules/commander/lib/help.js +520 -0
  154. package/node_modules/commander/lib/option.js +330 -0
  155. package/node_modules/commander/lib/suggestSimilar.js +101 -0
  156. package/node_modules/commander/package-support.json +16 -0
  157. package/node_modules/commander/package.json +84 -0
  158. package/node_modules/commander/typings/esm.d.mts +3 -0
  159. package/node_modules/commander/typings/index.d.ts +969 -0
  160. package/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
  161. package/node_modules/emoji-regex/README.md +107 -0
  162. package/node_modules/emoji-regex/index.d.ts +3 -0
  163. package/node_modules/emoji-regex/index.js +4 -0
  164. package/node_modules/emoji-regex/index.mjs +4 -0
  165. package/node_modules/emoji-regex/package.json +45 -0
  166. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  167. package/node_modules/get-east-asian-width/index.js +30 -0
  168. package/node_modules/get-east-asian-width/license +9 -0
  169. package/node_modules/get-east-asian-width/lookup-data.js +18 -0
  170. package/node_modules/get-east-asian-width/lookup.js +135 -0
  171. package/node_modules/get-east-asian-width/package.json +71 -0
  172. package/node_modules/get-east-asian-width/readme.md +65 -0
  173. package/node_modules/get-east-asian-width/utilities.js +24 -0
  174. package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
  175. package/node_modules/is-fullwidth-code-point/index.js +50 -0
  176. package/node_modules/is-fullwidth-code-point/license +9 -0
  177. package/node_modules/is-fullwidth-code-point/package.json +42 -0
  178. package/node_modules/is-fullwidth-code-point/readme.md +39 -0
  179. package/node_modules/isbinaryfile/LICENSE.txt +22 -0
  180. package/node_modules/isbinaryfile/README.md +70 -0
  181. package/node_modules/isbinaryfile/lib/index.d.ts +3 -0
  182. package/node_modules/isbinaryfile/lib/index.js +256 -0
  183. package/node_modules/isbinaryfile/package.json +64 -0
  184. package/node_modules/string-width/index.d.ts +39 -0
  185. package/node_modules/string-width/index.js +82 -0
  186. package/node_modules/string-width/license +9 -0
  187. package/node_modules/string-width/package.json +64 -0
  188. package/node_modules/string-width/readme.md +66 -0
  189. package/node_modules/strip-ansi/index.d.ts +15 -0
  190. package/node_modules/strip-ansi/index.js +19 -0
  191. package/node_modules/strip-ansi/license +9 -0
  192. package/node_modules/strip-ansi/package.json +59 -0
  193. package/node_modules/strip-ansi/readme.md +37 -0
  194. package/node_modules/type-fest/index.d.ts +178 -0
  195. package/node_modules/type-fest/license-cc0 +121 -0
  196. package/node_modules/type-fest/license-mit +9 -0
  197. package/node_modules/type-fest/package.json +91 -0
  198. package/node_modules/type-fest/readme.md +1060 -0
  199. package/node_modules/type-fest/source/all-union-fields.d.ts +88 -0
  200. package/node_modules/type-fest/source/and.d.ts +25 -0
  201. package/node_modules/type-fest/source/array-indices.d.ts +23 -0
  202. package/node_modules/type-fest/source/array-slice.d.ts +109 -0
  203. package/node_modules/type-fest/source/array-splice.d.ts +99 -0
  204. package/node_modules/type-fest/source/array-tail.d.ts +76 -0
  205. package/node_modules/type-fest/source/array-values.d.ts +22 -0
  206. package/node_modules/type-fest/source/arrayable.d.ts +29 -0
  207. package/node_modules/type-fest/source/async-return-type.d.ts +23 -0
  208. package/node_modules/type-fest/source/asyncify.d.ts +32 -0
  209. package/node_modules/type-fest/source/basic.d.ts +68 -0
  210. package/node_modules/type-fest/source/camel-case.d.ts +89 -0
  211. package/node_modules/type-fest/source/camel-cased-properties-deep.d.ts +97 -0
  212. package/node_modules/type-fest/source/camel-cased-properties.d.ts +43 -0
  213. package/node_modules/type-fest/source/conditional-except.d.ts +45 -0
  214. package/node_modules/type-fest/source/conditional-keys.d.ts +47 -0
  215. package/node_modules/type-fest/source/conditional-pick-deep.d.ts +118 -0
  216. package/node_modules/type-fest/source/conditional-pick.d.ts +44 -0
  217. package/node_modules/type-fest/source/conditional-simplify.d.ts +32 -0
  218. package/node_modules/type-fest/source/delimiter-case.d.ts +78 -0
  219. package/node_modules/type-fest/source/delimiter-cased-properties-deep.d.ts +106 -0
  220. package/node_modules/type-fest/source/delimiter-cased-properties.d.ts +46 -0
  221. package/node_modules/type-fest/source/distributed-omit.d.ts +89 -0
  222. package/node_modules/type-fest/source/distributed-pick.d.ts +85 -0
  223. package/node_modules/type-fest/source/empty-object.d.ts +46 -0
  224. package/node_modules/type-fest/source/enforce-optional.d.ts +47 -0
  225. package/node_modules/type-fest/source/entries.d.ts +62 -0
  226. package/node_modules/type-fest/source/entry.d.ts +65 -0
  227. package/node_modules/type-fest/source/exact.d.ts +68 -0
  228. package/node_modules/type-fest/source/except.d.ts +108 -0
  229. package/node_modules/type-fest/source/find-global-type.d.ts +64 -0
  230. package/node_modules/type-fest/source/fixed-length-array.d.ts +43 -0
  231. package/node_modules/type-fest/source/get.d.ts +219 -0
  232. package/node_modules/type-fest/source/global-this.d.ts +21 -0
  233. package/node_modules/type-fest/source/greater-than-or-equal.d.ts +22 -0
  234. package/node_modules/type-fest/source/greater-than.d.ts +56 -0
  235. package/node_modules/type-fest/source/has-optional-keys.d.ts +21 -0
  236. package/node_modules/type-fest/source/has-readonly-keys.d.ts +21 -0
  237. package/node_modules/type-fest/source/has-required-keys.d.ts +59 -0
  238. package/node_modules/type-fest/source/has-writable-keys.d.ts +21 -0
  239. package/node_modules/type-fest/source/if-any.d.ts +24 -0
  240. package/node_modules/type-fest/source/if-empty-object.d.ts +26 -0
  241. package/node_modules/type-fest/source/if-never.d.ts +24 -0
  242. package/node_modules/type-fest/source/if-null.d.ts +24 -0
  243. package/node_modules/type-fest/source/if-unknown.d.ts +24 -0
  244. package/node_modules/type-fest/source/includes.d.ts +22 -0
  245. package/node_modules/type-fest/source/int-closed-range.d.ts +35 -0
  246. package/node_modules/type-fest/source/int-range.d.ts +55 -0
  247. package/node_modules/type-fest/source/internal/array.d.ts +126 -0
  248. package/node_modules/type-fest/source/internal/characters.d.ts +67 -0
  249. package/node_modules/type-fest/source/internal/index.d.ts +8 -0
  250. package/node_modules/type-fest/source/internal/keys.d.ts +97 -0
  251. package/node_modules/type-fest/source/internal/numeric.d.ts +118 -0
  252. package/node_modules/type-fest/source/internal/object.d.ts +236 -0
  253. package/node_modules/type-fest/source/internal/string.d.ts +210 -0
  254. package/node_modules/type-fest/source/internal/tuple.d.ts +90 -0
  255. package/node_modules/type-fest/source/internal/type.d.ts +139 -0
  256. package/node_modules/type-fest/source/invariant-of.d.ts +76 -0
  257. package/node_modules/type-fest/source/is-any.d.ts +33 -0
  258. package/node_modules/type-fest/source/is-equal.d.ts +31 -0
  259. package/node_modules/type-fest/source/is-float.d.ts +41 -0
  260. package/node_modules/type-fest/source/is-integer.d.ts +58 -0
  261. package/node_modules/type-fest/source/is-literal.d.ts +296 -0
  262. package/node_modules/type-fest/source/is-never.d.ts +42 -0
  263. package/node_modules/type-fest/source/is-null.d.ts +20 -0
  264. package/node_modules/type-fest/source/is-tuple.d.ts +89 -0
  265. package/node_modules/type-fest/source/is-unknown.d.ts +52 -0
  266. package/node_modules/type-fest/source/iterable-element.d.ts +64 -0
  267. package/node_modules/type-fest/source/join.d.ts +68 -0
  268. package/node_modules/type-fest/source/jsonifiable.d.ts +37 -0
  269. package/node_modules/type-fest/source/jsonify.d.ts +122 -0
  270. package/node_modules/type-fest/source/kebab-case.d.ts +44 -0
  271. package/node_modules/type-fest/source/kebab-cased-properties-deep.d.ts +63 -0
  272. package/node_modules/type-fest/source/kebab-cased-properties.d.ts +40 -0
  273. package/node_modules/type-fest/source/keys-of-union.d.ts +42 -0
  274. package/node_modules/type-fest/source/last-array-element.d.ts +38 -0
  275. package/node_modules/type-fest/source/less-than-or-equal.d.ts +22 -0
  276. package/node_modules/type-fest/source/less-than.d.ts +26 -0
  277. package/node_modules/type-fest/source/literal-to-primitive-deep.d.ts +36 -0
  278. package/node_modules/type-fest/source/literal-to-primitive.d.ts +36 -0
  279. package/node_modules/type-fest/source/literal-union.d.ts +37 -0
  280. package/node_modules/type-fest/source/merge-deep.d.ts +486 -0
  281. package/node_modules/type-fest/source/merge-exclusive.d.ts +41 -0
  282. package/node_modules/type-fest/source/merge.d.ts +48 -0
  283. package/node_modules/type-fest/source/multidimensional-array.d.ts +44 -0
  284. package/node_modules/type-fest/source/multidimensional-readonly-array.d.ts +48 -0
  285. package/node_modules/type-fest/source/non-empty-object.d.ts +35 -0
  286. package/node_modules/type-fest/source/non-empty-string.d.ts +28 -0
  287. package/node_modules/type-fest/source/non-empty-tuple.d.ts +21 -0
  288. package/node_modules/type-fest/source/numeric.d.ts +222 -0
  289. package/node_modules/type-fest/source/observable-like.d.ts +63 -0
  290. package/node_modules/type-fest/source/omit-deep.d.ts +167 -0
  291. package/node_modules/type-fest/source/omit-index-signature.d.ts +95 -0
  292. package/node_modules/type-fest/source/opaque.d.ts +1 -0
  293. package/node_modules/type-fest/source/optional-keys-of.d.ts +39 -0
  294. package/node_modules/type-fest/source/or.d.ts +25 -0
  295. package/node_modules/type-fest/source/override-properties.d.ts +36 -0
  296. package/node_modules/type-fest/source/package-json.d.ts +676 -0
  297. package/node_modules/type-fest/source/partial-deep.d.ts +151 -0
  298. package/node_modules/type-fest/source/partial-on-undefined-deep.d.ts +78 -0
  299. package/node_modules/type-fest/source/pascal-case.d.ts +42 -0
  300. package/node_modules/type-fest/source/pascal-cased-properties-deep.d.ts +62 -0
  301. package/node_modules/type-fest/source/pascal-cased-properties.d.ts +36 -0
  302. package/node_modules/type-fest/source/paths.d.ts +262 -0
  303. package/node_modules/type-fest/source/pick-deep.d.ts +149 -0
  304. package/node_modules/type-fest/source/pick-index-signature.d.ts +50 -0
  305. package/node_modules/type-fest/source/primitive.d.ts +13 -0
  306. package/node_modules/type-fest/source/promisable.d.ts +25 -0
  307. package/node_modules/type-fest/source/readonly-deep.d.ts +81 -0
  308. package/node_modules/type-fest/source/readonly-keys-of.d.ts +30 -0
  309. package/node_modules/type-fest/source/readonly-tuple.d.ts +41 -0
  310. package/node_modules/type-fest/source/replace.d.ts +85 -0
  311. package/node_modules/type-fest/source/require-all-or-none.d.ts +51 -0
  312. package/node_modules/type-fest/source/require-at-least-one.d.ts +47 -0
  313. package/node_modules/type-fest/source/require-exactly-one.d.ts +45 -0
  314. package/node_modules/type-fest/source/require-one-or-none.d.ts +46 -0
  315. package/node_modules/type-fest/source/required-deep.d.ts +78 -0
  316. package/node_modules/type-fest/source/required-keys-of.d.ts +30 -0
  317. package/node_modules/type-fest/source/schema.d.ts +114 -0
  318. package/node_modules/type-fest/source/screaming-snake-case.d.ts +28 -0
  319. package/node_modules/type-fest/source/set-field-type.d.ts +65 -0
  320. package/node_modules/type-fest/source/set-non-nullable-deep.d.ts +83 -0
  321. package/node_modules/type-fest/source/set-non-nullable.d.ts +39 -0
  322. package/node_modules/type-fest/source/set-optional.d.ts +38 -0
  323. package/node_modules/type-fest/source/set-parameter-type.d.ts +117 -0
  324. package/node_modules/type-fest/source/set-readonly.d.ts +39 -0
  325. package/node_modules/type-fest/source/set-required-deep.d.ts +68 -0
  326. package/node_modules/type-fest/source/set-required.d.ts +70 -0
  327. package/node_modules/type-fest/source/set-return-type.d.ts +29 -0
  328. package/node_modules/type-fest/source/shared-union-fields-deep.d.ts +178 -0
  329. package/node_modules/type-fest/source/shared-union-fields.d.ts +76 -0
  330. package/node_modules/type-fest/source/simplify-deep.d.ts +115 -0
  331. package/node_modules/type-fest/source/simplify.d.ts +58 -0
  332. package/node_modules/type-fest/source/single-key-object.d.ts +29 -0
  333. package/node_modules/type-fest/source/snake-case.d.ts +45 -0
  334. package/node_modules/type-fest/source/snake-cased-properties-deep.d.ts +63 -0
  335. package/node_modules/type-fest/source/snake-cased-properties.d.ts +40 -0
  336. package/node_modules/type-fest/source/split.d.ts +88 -0
  337. package/node_modules/type-fest/source/spread.d.ts +84 -0
  338. package/node_modules/type-fest/source/string-key-of.d.ts +25 -0
  339. package/node_modules/type-fest/source/string-repeat.d.ts +47 -0
  340. package/node_modules/type-fest/source/string-slice.d.ts +37 -0
  341. package/node_modules/type-fest/source/stringified.d.ts +23 -0
  342. package/node_modules/type-fest/source/structured-cloneable.d.ts +92 -0
  343. package/node_modules/type-fest/source/subtract.d.ts +83 -0
  344. package/node_modules/type-fest/source/sum.d.ts +78 -0
  345. package/node_modules/type-fest/source/tagged-union.d.ts +51 -0
  346. package/node_modules/type-fest/source/tagged.d.ts +256 -0
  347. package/node_modules/type-fest/source/trim.d.ts +27 -0
  348. package/node_modules/type-fest/source/tsconfig-json.d.ts +1294 -0
  349. package/node_modules/type-fest/source/tuple-to-object.d.ts +42 -0
  350. package/node_modules/type-fest/source/tuple-to-union.d.ts +51 -0
  351. package/node_modules/type-fest/source/typed-array.d.ts +17 -0
  352. package/node_modules/type-fest/source/undefined-on-partial-deep.d.ts +80 -0
  353. package/node_modules/type-fest/source/union-to-intersection.d.ts +61 -0
  354. package/node_modules/type-fest/source/union-to-tuple.d.ts +56 -0
  355. package/node_modules/type-fest/source/unknown-array.d.ts +25 -0
  356. package/node_modules/type-fest/source/unknown-map.d.ts +24 -0
  357. package/node_modules/type-fest/source/unknown-record.d.ts +31 -0
  358. package/node_modules/type-fest/source/unknown-set.d.ts +24 -0
  359. package/node_modules/type-fest/source/value-of.d.ts +42 -0
  360. package/node_modules/type-fest/source/words.d.ts +118 -0
  361. package/node_modules/type-fest/source/writable-deep.d.ts +83 -0
  362. package/node_modules/type-fest/source/writable-keys-of.d.ts +33 -0
  363. package/node_modules/type-fest/source/writable.d.ts +68 -0
  364. package/node_modules/widest-line/index.d.ts +12 -0
  365. package/node_modules/widest-line/index.js +11 -0
  366. package/node_modules/widest-line/license +9 -0
  367. package/node_modules/widest-line/package.json +60 -0
  368. package/node_modules/widest-line/readme.md +26 -0
  369. package/node_modules/wrap-ansi/index.d.ts +41 -0
  370. package/node_modules/wrap-ansi/index.js +222 -0
  371. package/node_modules/wrap-ansi/license +9 -0
  372. package/node_modules/wrap-ansi/package.json +69 -0
  373. package/node_modules/wrap-ansi/readme.md +75 -0
  374. package/package.json +78 -0
  375. package/scripts/postinstall.cjs +122 -0
@@ -0,0 +1,486 @@
1
+ /**
2
+ * strategy.ts — Distribución de bloques en ramas y selección del plan óptimo.
3
+ *
4
+ * Este módulo implementa el corazón del algoritmo de división de PRs:
5
+ *
6
+ * ### Flujo principal (`findBestPlan`)
7
+ * ```
8
+ * 1. Calcular minBranches: mínimo teórico de ramas necesarias.
9
+ * 2. Para branchCount en [minBranches … minBranches + maxBranchSearchRange]:
10
+ * a. distributeBlocks → asignar bloques a ramas (greedy)
11
+ * b. evaluateBranch → calcular score y validez de cada rama
12
+ * c. buildCommitPlan → plan de commits de cada rama
13
+ * d. Si todas las ramas son válidas → aceptar plan y salir (break)
14
+ * 3. Si ningún plan es válido → usar el fallback con minBranches
15
+ * 4. markExistingBaseBranch → nombrar ramas correctamente
16
+ * ```
17
+ *
18
+ * ### Algoritmo de distribución greedy (`distributeBlocks`)
19
+ * Ordena los bloques por prioridad y luego, para cada bloque, elige la rama
20
+ * con menor penalización acumulada. Las penalizaciones están definidas en
21
+ * `constants.ts` (`DIST_*`) y consideran: desbordamiento de líneas/archivos,
22
+ * bloques indivisibles en ramas ocupadas, y separación de dependencias.
23
+ *
24
+ * ### Nomenclatura de ramas (`markExistingBaseBranch`)
25
+ * La rama con mayor score (o más líneas en caso de empate) se marca como
26
+ * `isExistingBaseBranch: true` y toma el nombre de la rama activa.
27
+ * Las demás reciben nombres derivados por `buildDerivedBranchName` o el
28
+ * formato fallback `<rama>-split-<n>`.
29
+ */
30
+ import { buildDerivedBranchName } from "../git/branch-naming.js";
31
+ import { scorePullRequest } from "./scoring.js";
32
+ import { buildCommitPlan } from "./commit-planner.js";
33
+ import { EVAL_MIN_COMMITS_LINE_THRESHOLD, EVAL_MAX_PR_LINE_THRESHOLD, DIST_PENALTY_OVERFLOW_FILES, DIST_PENALTY_INDIVISIBLE_NONEMPTY, DIST_BONUS_INDIVISIBLE_EMPTY, DIST_PENALTY_LINE_FACTOR, DIST_PENALTY_SPLIT_DEPS, DIST_BONUS_COLOCATE_DEPS } from "../shared/constants.js";
34
+ // ---------------------------------------------------------------------------
35
+ // Tipos internos
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Tipos de commit excluidos del cálculo de promedios de M1.4 (arch/commit) y
39
+ * M1.5 (líneas/commit). M1.3 y M3.2 siempre incluyen todos los tipos de commit.
40
+ */
41
+ const EXCLUDED_FROM_AVERAGES = new Set(["chore", "style", "docs"]);
42
+ // ---------------------------------------------------------------------------
43
+ // Helper de totales de rama
44
+ // ---------------------------------------------------------------------------
45
+ /**
46
+ * Calcula el total de líneas y archivos de un conjunto de bloques.
47
+ * Usado tanto en `evaluateBranch` como en el bucle de penalizaciones de `distributeBlocks`.
48
+ */
49
+ function branchTotals(blocks) {
50
+ return {
51
+ lines: blocks.reduce((sum, b) => sum + b.lines, 0),
52
+ files: blocks.reduce((sum, b) => sum + b.files.length, 0)
53
+ };
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Re-score con el plan de commits real
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Re-calcula el score de una rama usando el plan de commits ya construido.
60
+ *
61
+ * Una vez que `buildCommitPlan` asigna tipos a cada commit, podemos aplicar la
62
+ * regla de exclusión: M1.4 y M1.5 se calculan solo sobre commits que NO sean
63
+ * `chore`, `style` ni `docs`. M1.3 y M3.2 usan todos los commits sin filtrar.
64
+ *
65
+ * Este re-score reemplaza el score estimado por `evaluateBranch`, que no conocía
66
+ * los tipos de commit en el momento de calcular archivos/commit y líneas/commit.
67
+ *
68
+ * @param base - Evaluación inicial de la rama (para heredar `files`, `lines`, `commits`, `reason`).
69
+ * @param commitPlan - Plan de commits con tipos ya asignados.
70
+ * @param totalLines - Total de líneas de la rama (para M3.2).
71
+ * @param config - Configuración activa.
72
+ */
73
+ function rescoreWithPlan(base, commitPlan, totalLines, config) {
74
+ // M1.4 y M1.5: excluir commits de tipo chore, style y docs
75
+ const reviewable = commitPlan.filter((c) => !EXCLUDED_FROM_AVERAGES.has(c.suggestedType));
76
+ const reviewableCount = reviewable.length || 1; // evitar división por 0
77
+ // Tipos que realmente aparecen en el plan y son excluidos
78
+ const excluded = [...new Set(commitPlan
79
+ .filter((c) => EXCLUDED_FROM_AVERAGES.has(c.suggestedType))
80
+ .map((c) => c.suggestedType))];
81
+ const reviewableFiles = reviewable.reduce((sum, c) => sum + c.files.length, 0);
82
+ const reviewableLines = reviewable.reduce((sum, c) => sum + c.totalLines, 0);
83
+ const filesPerCommit = Number((reviewableFiles / reviewableCount).toFixed(2));
84
+ const linesPerCommit = Number((reviewableLines / reviewableCount).toFixed(2));
85
+ // M1.3 y M3.2: todos los commits sin filtrar
86
+ const scoring = scorePullRequest({
87
+ commitCount: commitPlan.length,
88
+ filesPerCommit,
89
+ avgLinesPerCommit: linesPerCommit,
90
+ totalLinesChanged: totalLines
91
+ }, config);
92
+ return {
93
+ ...base,
94
+ commits: commitPlan.length,
95
+ filesPerCommit,
96
+ linesPerCommit,
97
+ score: scoring.complexity,
98
+ metrics: Object.fromEntries(Object.keys(config.metrics).map((code) => [code, scoring.metrics[code]?.points ?? 0])),
99
+ recommendation: scoring.recommendation,
100
+ ...(excluded.length > 0 ? { excludedCommitTypes: excluded } : {})
101
+ };
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Evaluación de una rama
105
+ // ---------------------------------------------------------------------------
106
+ /**
107
+ * Evalúa las métricas de calidad de una distribución de bloques para una rama.
108
+ *
109
+ * Calcula el número de commits necesarios aplicando la lógica:
110
+ * - Máximo entre `ceil(archivos / maxFilesPerCommit)` y `ceil(líneas / maxLinesPerCommitIdeal)`.
111
+ * - Si solo hay un bloque grande indivisible, fuerza 1 commit (no tiene sentido dividirlo).
112
+ * - Si la rama tiene más de un bloque o supera el umbral de líneas, mínimo 2 commits.
113
+ *
114
+ * Luego llama a `scorePullRequest` con los valores calculados y determina la razón
115
+ * del estado: `"OK"`, bloque grande, PR grande, o exceso de commits.
116
+ *
117
+ * @param blocks - Bloques asignados a esta rama.
118
+ * @param config - Configuración activa.
119
+ */
120
+ function evaluateBranch(blocks, config) {
121
+ const totals = branchTotals(blocks);
122
+ // Usar reduce en lugar de Math.max(...spread) para evitar RangeError
123
+ // si el array de bloques tiene cientos o miles de elementos.
124
+ const maxBlockLines = blocks.reduce((max, b) => (b.lines > max ? b.lines : max), 0);
125
+ // Garantizar mínimo 1 commit para evitar división por cero en el cálculo de
126
+ // promedios: puede ocurrir cuando todos los archivos de la rama son
127
+ // eliminaciones (changeType D) cuyo lineCount es 0.
128
+ let commits = Math.max(1, Math.ceil(totals.files / config.maxFilesPerCommit), Math.ceil(totals.lines / config.maxLinesPerCommitIdeal));
129
+ if (commits < 2 && (blocks.length > 1 || totals.lines > EVAL_MIN_COMMITS_LINE_THRESHOLD)) {
130
+ commits = 2;
131
+ }
132
+ const largeIndivisibleBlocks = blocks.filter((b) => !b.divisible);
133
+ if (largeIndivisibleBlocks.length === 1 &&
134
+ blocks.length === 1 &&
135
+ maxBlockLines > config.largeFileThreshold) {
136
+ commits = 1;
137
+ }
138
+ const filesPerCommit = Number((totals.files / commits).toFixed(2));
139
+ const linesPerCommit = Number((totals.lines / commits).toFixed(2));
140
+ const scoring = scorePullRequest({
141
+ commitCount: commits,
142
+ filesPerCommit,
143
+ avgLinesPerCommit: linesPerCommit,
144
+ totalLinesChanged: totals.lines
145
+ }, config);
146
+ // Las condiciones se asignan en orden ascendente de prioridad: la última que
147
+ // aplique sobreescribe a las anteriores. "Contiene bloque grande indivisible"
148
+ // y "PR demasiado grande" son las razones exentas en findBestPlan y deben
149
+ // tener mayor prioridad que "Requiere más commits" para evitar rechazar planes
150
+ // que no pueden subdividirse más.
151
+ let reason = "OK";
152
+ if (commits > config.maxCommitsPerBranchIdeal)
153
+ reason = "Requiere más commits de lo ideal";
154
+ if (totals.lines > EVAL_MAX_PR_LINE_THRESHOLD)
155
+ reason = "PR demasiado grande por total de líneas";
156
+ if (maxBlockLines > config.largeFileThreshold)
157
+ reason = "Contiene bloque grande indivisible";
158
+ return {
159
+ files: totals.files,
160
+ lines: totals.lines,
161
+ commits,
162
+ filesPerCommit,
163
+ linesPerCommit,
164
+ score: scoring.complexity,
165
+ metrics: Object.fromEntries(Object.keys(config.metrics).map((code) => [code, scoring.metrics[code]?.points ?? 0])),
166
+ recommendation: scoring.recommendation,
167
+ reason
168
+ };
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // Block distribution
172
+ // ---------------------------------------------------------------------------
173
+ /**
174
+ * Distribuye un array de bloques en exactamente `branchCount` grupos (ramas).
175
+ *
176
+ * Usa una heurística greedy: asigna cada bloque a la rama con menor carga
177
+ * acumulada (líneas), penalizando la separación de bloques que comparten
178
+ * aristas de dependencia y premiando agruparlos juntos.
179
+ *
180
+ * @param blocks - Bloques a distribuir.
181
+ * @param branchCount - Número exacto de ramas destino.
182
+ * @param config - Configuración activa del proyecto.
183
+ * @param deps - Aristas de dependencia entre archivos.
184
+ * @returns Array de arrays de bloques, uno por rama.
185
+ */
186
+ export function distributeBlocks(blocks, branchCount, config, deps = []) {
187
+ const branches = Array.from({ length: branchCount }, () => []);
188
+ const fileToBranchIndex = new Map();
189
+ // Pre-computar mapa de adyacencia de dependencias: O(D) una sola vez.
190
+ // Evita el bucle O(F×D) que antes se ejecutaba por cada bloque×rama.
191
+ const fileNeighbors = new Map();
192
+ for (const { from, to } of deps) {
193
+ if (!fileNeighbors.has(from))
194
+ fileNeighbors.set(from, []);
195
+ if (!fileNeighbors.has(to))
196
+ fileNeighbors.set(to, []);
197
+ fileNeighbors.get(from).push(to);
198
+ fileNeighbors.get(to).push(from);
199
+ }
200
+ // Totales incrementales por rama: evita re-iterar todos los bloques en
201
+ // cada asignación (branchTotals recorría la rama entera en cada iteración).
202
+ const runningTotals = Array.from({ length: branchCount }, () => ({ lines: 0, files: 0 }));
203
+ const ordered = [...blocks].sort((a, b) => {
204
+ if (a.priority !== b.priority)
205
+ return a.priority - b.priority;
206
+ if (a.depScore !== b.depScore)
207
+ return b.depScore - a.depScore;
208
+ if (a.divisible !== b.divisible)
209
+ return a.divisible ? 1 : -1;
210
+ return b.lines - a.lines;
211
+ });
212
+ for (const block of ordered) {
213
+ let bestIndex = 0;
214
+ let bestPenalty = Number.MAX_SAFE_INTEGER;
215
+ for (let i = 0; i < branches.length; i++) {
216
+ const totals = runningTotals[i];
217
+ const projectedLines = totals.lines + block.lines;
218
+ const projectedFiles = totals.files + block.files.length;
219
+ let penalty = 0;
220
+ if (projectedLines > config.idealLinesPerPR) {
221
+ penalty += projectedLines - config.idealLinesPerPR;
222
+ }
223
+ if (projectedFiles > config.maxFilesPerCommit * config.maxCommitsPerBranchIdeal) {
224
+ penalty += DIST_PENALTY_OVERFLOW_FILES;
225
+ }
226
+ if (!block.divisible && totals.lines > 0)
227
+ penalty += DIST_PENALTY_INDIVISIBLE_NONEMPTY;
228
+ if (!block.divisible && totals.lines === 0)
229
+ penalty -= DIST_BONUS_INDIVISIBLE_EMPTY;
230
+ penalty += Math.floor(projectedLines / DIST_PENALTY_LINE_FACTOR);
231
+ // Penalizar separar bloques con dependencias directas entre sí.
232
+ // Con el mapa de adyacencia pre-computado el coste es O(F) en lugar de O(F×D).
233
+ for (const file of block.files) {
234
+ for (const neighbor of (fileNeighbors.get(file) ?? [])) {
235
+ const relatedBranch = fileToBranchIndex.get(neighbor);
236
+ if (relatedBranch === undefined)
237
+ continue;
238
+ if (relatedBranch !== i) {
239
+ penalty += DIST_PENALTY_SPLIT_DEPS;
240
+ }
241
+ else {
242
+ penalty -= DIST_BONUS_COLOCATE_DEPS;
243
+ }
244
+ }
245
+ }
246
+ if (penalty < bestPenalty) {
247
+ bestPenalty = penalty;
248
+ bestIndex = i;
249
+ }
250
+ }
251
+ branches[bestIndex].push(block);
252
+ runningTotals[bestIndex].lines += block.lines;
253
+ runningTotals[bestIndex].files += block.files.length;
254
+ for (const file of block.files) {
255
+ fileToBranchIndex.set(file, bestIndex);
256
+ }
257
+ }
258
+ return branches;
259
+ }
260
+ // ---------------------------------------------------------------------------
261
+ // Branch naming
262
+ // ---------------------------------------------------------------------------
263
+ // ---------------------------------------------------------------------------
264
+ // Detección de descripción de rama y nomenclatura
265
+ // ---------------------------------------------------------------------------
266
+ /**
267
+ * Deriva una descripción genérica en inglés para la rama basada en los patrones
268
+ * de ruta de los archivos. Heurística por capas (en orden de prioridad):
269
+ *
270
+ * 1. Todos los archivos son tests → `"update-related-tests"`
271
+ * 2. Todos son documentación → `"update-documentation"`
272
+ * 3. Todos son archivos de config → `"update-configuration"`
273
+ * 4. Todos son tipos/interfaces → `"align-shared-types"`
274
+ * 5. Directorio padre más frecuente (excluyendo genéricos) → `"update-<dir>-layer"`
275
+ * 6. Fallback → `"update-related-files-<índice>"`
276
+ *
277
+ * La detección se realiza en un único recorrido que evalúa todas las categorías
278
+ * y acumula el conteo de directorios simultáneamente, en lugar de múltiples
279
+ * pasadas `.every()` separadas.
280
+ *
281
+ * @param blocks - Bloques de la rama cuyas rutas se analizan.
282
+ * @param fallbackIndex - Número de rama usado solo en el fallback genérico.
283
+ */
284
+ function detectBranchDescription(blocks, fallbackIndex) {
285
+ const files = blocks.flatMap((b) => b.files).map((f) => f.toLowerCase());
286
+ if (files.length === 0)
287
+ return `update-related-files-${fallbackIndex}`;
288
+ // Un único recorrido para las 4 categorías y el conteo de directorios,
289
+ // en lugar de cuatro llamadas .every() separadas que re-escaneaban el array.
290
+ const reTest = /(^|\/)(__tests__|tests?|specs?)(\/|$)|(\.spec\.|\.test\.)/;
291
+ // Anclar readme, changelog y license para que no hagan match como substrings.
292
+ // Añadir .adoc y .rst para consistencia con file-stats.ts y commit-planner.ts.
293
+ const reDocs = /(^|\/)(docs?)(\/|$)|(^|\/)readme(\.[^/]*)?$|(^|\/)changelog(\.[^/]*)?$|(^|\/)license(\.[^/]*)?$|\.md$|\.adoc$|\.rst$/;
294
+ const reConfig = /(config|configs|configuration|settings|env)(\/|$|\.)/;
295
+ const reYaml = /\.(ya?ml|toml)$/;
296
+ const reTypes = /(type|types|interface|interfaces|schema|schemas|dto|dtos|model|models)(\/|$|\.)/;
297
+ let allTests = true, allDocs = true, allConfig = true, allTypes = true;
298
+ const dirCounts = new Map();
299
+ for (const f of files) {
300
+ if (allTests && !reTest.test(f))
301
+ allTests = false;
302
+ if (allDocs && !reDocs.test(f))
303
+ allDocs = false;
304
+ if (allConfig && !reConfig.test(f) && !reYaml.test(f))
305
+ allConfig = false;
306
+ if (allTypes && !reTypes.test(f))
307
+ allTypes = false;
308
+ const parts = f.split("/");
309
+ if (parts.length >= 2) {
310
+ const dir = parts[parts.length - 2];
311
+ if (dir)
312
+ dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
313
+ }
314
+ }
315
+ if (allTests)
316
+ return "update-related-tests";
317
+ if (allDocs)
318
+ return "update-documentation";
319
+ if (allConfig)
320
+ return "update-configuration";
321
+ if (allTypes)
322
+ return "align-shared-types";
323
+ // Directorio padre más frecuente, ignorando carpetas genéricas
324
+ const genericDirs = new Set(["src", "lib", "app", ".", ""]);
325
+ let topDir = "", topCount = 0;
326
+ for (const [d, count] of dirCounts) {
327
+ if (!genericDirs.has(d) && count > topCount) {
328
+ topCount = count;
329
+ topDir = d;
330
+ }
331
+ }
332
+ if (topDir)
333
+ return `update-${topDir.replace(/[^a-z0-9-]/g, "-")}-layer`;
334
+ return `update-related-files-${fallbackIndex}`;
335
+ }
336
+ /**
337
+ * Construye el nombre definitivo de una rama del plan.
338
+ *
339
+ * - Rama base existente → devuelve `currentBranch` sin cambios.
340
+ * - Si hay `branchNamingContext` → usa `buildDerivedBranchName` (patrón `feature/<TEAM>-<NUM>-<desc>`).
341
+ * - Si no → no es posible llegar aquí; `parseAndValidateWorkingBranch` ya garantiza el formato.
342
+ */
343
+ function buildBranchPlanName(currentBranch, branchBlocks, index, config, isExistingBaseBranch) {
344
+ if (isExistingBaseBranch)
345
+ return currentBranch;
346
+ const description = detectBranchDescription(branchBlocks, index);
347
+ // branchNamingContext siempre presente: parseAndValidateWorkingBranch lo garantiza al inicio.
348
+ if (!config.branchNamingContext) {
349
+ throw new Error("La rama activa no sigue el formato requerido (feature|bugfix|hotfix/<TEAM>-<NUM>[-<desc>]).");
350
+ }
351
+ return buildDerivedBranchName(config.branchNamingContext, description);
352
+ }
353
+ /**
354
+ * Identifica la rama base existente (la que más score tiene, o más líneas en caso de empate)
355
+ * y asigna los nombres definitivos a todas las ramas del plan.
356
+ *
357
+ * La rama base toma el nombre de `currentBranch` (no se crea, ya existe).
358
+ * Las demás toman nombres derivados con índices a partir del 2.
359
+ *
360
+ * @param plans - Planes sin nombres definitivos (candidatos).
361
+ * @param currentBranch - Nombre de la rama activa en el repositorio.
362
+ * @param config - Configuración para construir los nombres derivados.
363
+ * @returns Plans con `name` e `isExistingBaseBranch` asignados correctamente.
364
+ */
365
+ function markExistingBaseBranch(plans, currentBranch, config) {
366
+ if (!plans.length)
367
+ return plans;
368
+ const ordered = [...plans].sort((a, b) => {
369
+ if (a.score !== b.score)
370
+ return b.score - a.score;
371
+ if (a.lines !== b.lines)
372
+ return b.lines - a.lines;
373
+ return b.files - a.files;
374
+ });
375
+ const basePlan = ordered[0];
376
+ const rewrittenBasePlan = {
377
+ ...basePlan,
378
+ name: buildBranchPlanName(currentBranch, basePlan.blocks, 1, config, true),
379
+ isExistingBaseBranch: true
380
+ };
381
+ const derivedPlans = ordered.slice(1).map((plan, index) => ({
382
+ ...plan,
383
+ name: buildBranchPlanName(currentBranch, plan.blocks, index + 2, config, false),
384
+ isExistingBaseBranch: false
385
+ }));
386
+ // Deduplicar nombres: si dos ramas derivadas reciben el mismo nombre
387
+ // (p.ej. ambas tienen el mismo directorio dominante), se añade sufijo numérico.
388
+ // También es necesario incluir el nombre de la rama base para evitar que
389
+ // buildDerivedBranchName genere un nombre idéntico a currentBranch, lo que
390
+ // causaría que createBranchFromSource lance "La rama destino ya existe".
391
+ const nameOccurrences = new Map();
392
+ nameOccurrences.set(rewrittenBasePlan.name, 1); // reservar nombre de la rama base
393
+ for (const p of derivedPlans) {
394
+ nameOccurrences.set(p.name, (nameOccurrences.get(p.name) ?? 0) + 1);
395
+ }
396
+ const nameCounter = new Map();
397
+ const deduplicatedDerived = derivedPlans.map((plan) => {
398
+ if ((nameOccurrences.get(plan.name) ?? 0) <= 1)
399
+ return plan;
400
+ const n = (nameCounter.get(plan.name) ?? 1);
401
+ nameCounter.set(plan.name, n + 1);
402
+ return { ...plan, name: `${plan.name}-${n}` };
403
+ });
404
+ return [rewrittenBasePlan, ...deduplicatedDerived];
405
+ }
406
+ // ---------------------------------------------------------------------------
407
+ // Public
408
+ // ---------------------------------------------------------------------------
409
+ /**
410
+ * Encuentra el plan óptimo de ramas para un conjunto de bloques.
411
+ *
412
+ * Explora desde `minBranches` hasta `minBranches + config.maxBranchSearchRange`
413
+ * distribuciones posibles y selecciona la que maximiza el score promedio
414
+ * ponderado. El primer elemento del resultado será siempre la rama base
415
+ * existente (`isExistingBaseBranch: true`).
416
+ *
417
+ * @param blocks - Bloques de trabajo a distribuir.
418
+ * @param currentBranch - Nombre de la rama activa en el repositorio.
419
+ * @param config - Configuración activa del proyecto.
420
+ * @param deps - Aristas de dependencia entre archivos.
421
+ * @returns Array de {@link BranchPlan} ordenados: base primero, derivadas después.
422
+ */
423
+ export function findBestPlan(blocks, currentBranch, config, deps = []) {
424
+ const totalLines = blocks.reduce((sum, b) => sum + b.lines, 0);
425
+ const totalFiles = blocks.reduce((sum, b) => sum + b.files.length, 0);
426
+ const largeCount = blocks.filter((b) => !b.divisible).length;
427
+ let minBranches = 1;
428
+ minBranches = Math.max(minBranches, Math.ceil(totalLines / config.idealLinesPerPR));
429
+ minBranches = Math.max(minBranches, Math.ceil(totalFiles / (config.maxFilesPerCommit * config.maxCommitsPerBranchIdeal)));
430
+ minBranches = Math.max(minBranches, largeCount);
431
+ let bestPlans = [];
432
+ for (let branchCount = minBranches; branchCount <= minBranches + config.maxBranchSearchRange; branchCount++) {
433
+ const distributed = distributeBlocks(blocks, branchCount, config, deps);
434
+ let valid = true;
435
+ const plans = [];
436
+ for (let i = 0; i < distributed.length; i++) {
437
+ const branchBlocks = distributed[i];
438
+ if (!branchBlocks.length)
439
+ continue;
440
+ const evalResult = evaluateBranch(branchBlocks, config);
441
+ const commitPlan = buildCommitPlan(branchBlocks, config);
442
+ const finalEval = rescoreWithPlan(evalResult, commitPlan, branchTotals(branchBlocks).lines, config);
443
+ if (finalEval.score < config.targetScore &&
444
+ ![
445
+ "Contiene bloque grande indivisible",
446
+ "PR demasiado grande por total de líneas"
447
+ ].includes(evalResult.reason)) {
448
+ valid = false;
449
+ }
450
+ if (finalEval.commits > config.hardMaxCommitsPerBranch) {
451
+ valid = false;
452
+ }
453
+ plans.push({
454
+ name: `candidate-${i + 1}`,
455
+ ...finalEval,
456
+ blocks: branchBlocks,
457
+ commitPlan,
458
+ isExistingBaseBranch: false
459
+ });
460
+ }
461
+ if (valid) {
462
+ bestPlans = plans;
463
+ // El primer branchCount válido encontrado es siempre el mínimo posible;
464
+ // iteraciones posteriores solo tendrían más ramas (y nunca menor branchCount).
465
+ break;
466
+ }
467
+ }
468
+ if (!bestPlans.length) {
469
+ const fallback = distributeBlocks(blocks, minBranches, config, deps);
470
+ bestPlans = fallback
471
+ .filter((b) => b.length)
472
+ .map((branchBlocks, i) => {
473
+ const evalResult = evaluateBranch(branchBlocks, config);
474
+ const commitPlan = buildCommitPlan(branchBlocks, config);
475
+ const finalEval = rescoreWithPlan(evalResult, commitPlan, branchTotals(branchBlocks).lines, config);
476
+ return {
477
+ name: `candidate-${i + 1}`,
478
+ ...finalEval,
479
+ blocks: branchBlocks,
480
+ commitPlan,
481
+ isExistingBaseBranch: false
482
+ };
483
+ });
484
+ }
485
+ return markExistingBaseBranch(bestPlans, currentBranch, config);
486
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * branch-naming.ts — Parsing y generación de nombres de rama.
3
+ *
4
+ * Este módulo implementa dos responsabilidades:
5
+ *
6
+ * 1. **Validación** (`parseAndValidateWorkingBranch`): verifica que la rama activa
7
+ * siga el patrón `feature|bugfix/<TEAM>-<NUM>-<descripcion>` y extrae sus partes
8
+ * como un {@link BranchNamingContext}.
9
+ *
10
+ * 2. **Generación** (`buildDerivedBranchName`): construye el nombre de una rama
11
+ * derivada nueva a partir del contexto extraído y una descripción en inglés.
12
+ * Trunca el nombre si supera {@link BRANCH_NAME_MAX_LENGTH}.
13
+ *
14
+ * El patrón de rama esperado es:
15
+ * ```
16
+ * feature/ABC-123-add-login-endpoint
17
+ * bugfix/TEAM-456-fix-null-pointer
18
+ * ```
19
+ */
20
+ import { BRANCH_NAME_MAX_LENGTH } from "../shared/constants.js";
21
+ // Patrón de rama: `<tipo>/<TEAMCODE>-<NUM>` o `<tipo>/<TEAMCODE>-<NUM>-<slug>`.
22
+ // La descripción es opcional: `feature/TEAM-123` y `feature/TEAM-123-add-login` son ambos válidos.
23
+ // Solo se aceptan los tipos feature, bugfix y hotfix.
24
+ // La descripción acepta mayúsculas y minúsculas ([a-zA-Z0-9-]) para
25
+ // soportar ramas como feature/FASTY-0001-PRUEBA-IA.
26
+ const BRANCH_PATTERN = /^(feature|bugfix|hotfix)\/([A-Za-z]+)-(\d+)(-[a-zA-Z0-9-]+)?$/;
27
+ /**
28
+ * Valida que el nombre de rama siga el patrón requerido y extrae sus componentes.
29
+ *
30
+ * @param branchName - Nombre completo de la rama activa (ej. `"feature/ABC-123-login"`).
31
+ * @returns Contexto con las partes de la rama parseadas.
32
+ * @throws {Error} Si el nombre no sigue el patrón, con un mensaje descriptivo
33
+ * que incluye el formato esperado y un ejemplo.
34
+ */
35
+ export function parseAndValidateWorkingBranch(branchName) {
36
+ const match = branchName.match(BRANCH_PATTERN);
37
+ if (!match) {
38
+ throw new Error([
39
+ "La rama actual no cumple el formato requerido.",
40
+ "Formato permitido:",
41
+ " <tipo>/TEAMCODE-123",
42
+ " <tipo>/TEAMCODE-123-descripcion-opcional",
43
+ "Tipos válidos:",
44
+ " feature · bugfix · hotfix",
45
+ "Ejemplos:",
46
+ " feature/TEAM-123-add-login-endpoint",
47
+ " bugfix/TEAM-456-null-pointer",
48
+ " hotfix/TEAM-789-critical-fix"
49
+ ].join("\n"));
50
+ }
51
+ const [, branchType, rawTeamCode, storyNumber, descWithDash] = match;
52
+ // La descripción viene con guión inicial (p.ej. "-add-login") o undefined si omitida.
53
+ const originalDescription = descWithDash ? descWithDash.replace(/^-/, "") : "";
54
+ return {
55
+ branchType: branchType,
56
+ teamCode: rawTeamCode.toUpperCase(),
57
+ storyNumber,
58
+ originalDescription
59
+ };
60
+ }
61
+ /**
62
+ * Normaliza un texto libre a un slug solo con caracteres `[a-z0-9-]`.
63
+ *
64
+ * Pasos aplicados:
65
+ * 1. Convierte a minúsculas.
66
+ * 2. Descompone caracteres acentuados (NFD) y elimina los diacríticos.
67
+ * 3. Reemplaza cualquier carácter que no sea letra/dígito con `-`.
68
+ * 4. Elimina guiones al inicio y al final.
69
+ * 5. Colapsa múltiples guiones consecutivos en uno solo.
70
+ *
71
+ * @param value - Texto a normalizar (título, descripción, etc.).
72
+ * @returns Slug en minúsculas con solo `[a-z0-9-]`.
73
+ */
74
+ function normalizeEnglishSlugPart(value) {
75
+ return value
76
+ .toLowerCase()
77
+ .normalize("NFD")
78
+ .replace(/[\u0300-\u036f]/g, "")
79
+ .replace(/[^a-z0-9]+/g, "-")
80
+ .replace(/^-+/, "")
81
+ .replace(/-+$/, "")
82
+ .replace(/--+/g, "-");
83
+ }
84
+ /**
85
+ * Construye el nombre completo de una rama derivada a partir del contexto
86
+ * de nomenclatura y una descripción en inglés en slug.
87
+ *
88
+ * El nombre resultante sigue el patrón:
89
+ * `<branchType>/<TEAMCODE>-<storyNumber>-<description>`
90
+ *
91
+ * Si el nombre supera {@link BRANCH_NAME_MAX_LENGTH} caracteres, la descripción
92
+ * se trunca y se emite un aviso por stderr (no lanza excepción).
93
+ *
94
+ * @param context - Contexto extraído por `parseAndValidateWorkingBranch`.
95
+ * @param description - Descripción en slug inglés (ej. `"update-auth-layer"`),
96
+ * generada típicamente por `detectBranchDescription`.
97
+ * @throws {Error} Si `description` resulta vacía tras la normalización.
98
+ */
99
+ export function buildDerivedBranchName(context, description) {
100
+ const normalizedDescription = normalizeEnglishSlugPart(description);
101
+ if (!normalizedDescription) {
102
+ throw new Error("No se pudo generar una descripcion valida en ingles para la rama.");
103
+ }
104
+ const prefix = `${context.branchType}/${context.teamCode}-${context.storyNumber}-`;
105
+ const full = `${prefix}${normalizedDescription}`;
106
+ if (full.length <= BRANCH_NAME_MAX_LENGTH)
107
+ return full;
108
+ // Truncar la descripción para mantener el nombre dentro del límite.
109
+ const maxDescLength = BRANCH_NAME_MAX_LENGTH - prefix.length;
110
+ const truncated = maxDescLength > 0
111
+ ? normalizedDescription.slice(0, maxDescLength).replace(/-+$/, "") || "changes"
112
+ : "changes";
113
+ const result = maxDescLength > 0
114
+ ? `${prefix}${truncated}`
115
+ : full.slice(0, BRANCH_NAME_MAX_LENGTH).replace(/-+$/, "");
116
+ process.stderr.write(`[branch-naming warn] El nombre de rama supera ${BRANCH_NAME_MAX_LENGTH} caracteres y se truncará.\n` +
117
+ ` Original : ${full}\n` +
118
+ ` Truncado : ${result}\n`);
119
+ return result;
120
+ }