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,378 @@
1
+ /**
2
+ * executor.ts — Aplicación del plan de ramas y commits al repositorio git.
3
+ *
4
+ * ### Modelo: stacked branches
5
+ * Cada rama se crea desde la punta (HEAD) de la rama anterior, heredando
6
+ * todos sus commits y añadiendo los propios. El flujo es:
7
+ *
8
+ * ```
9
+ * 1. sourceBranch → añade commits del existingBasePlan
10
+ * 2. derived-1 (from tip de sourceBranch) → añade commits propios
11
+ * 3. derived-2 (from tip de derived-1) → hereda todo + añade commits propios
12
+ * 4. volver a sourceBranch y limpiar staged
13
+ * ```
14
+ *
15
+ * ### Rollback
16
+ * Si cualquier paso falla:
17
+ * 1. `git checkout sourceBranch`
18
+ * 2. `git reset --hard <sha-original>` — deshace commits parciales en sourceBranch
19
+ * 3. `git clean -fd` — limpia working tree (incluyendo archivos untracked del plan)
20
+ * 4. `git branch -D` de cada rama derivada ya creada
21
+ */
22
+ import { addFiles, branchExists, commit as gitCommit, detectRemote, getCurrentBranch, pushBranch, q, run, shSafe } from "./git.js";
23
+ import { ui } from "../output/ui.js";
24
+ // Centinela que indica que la rama de respaldo no pudo crearse en esta ejecución.
25
+ const BACKUP_FAILED = null;
26
+ /**
27
+ * Elimina una rama local de forma segura, capturando el error si falla
28
+ * (ej. la rama ya fue eliminada manualmente).
29
+ *
30
+ * @param branchName - Nombre de la rama a eliminar.
31
+ */
32
+ function deleteBranch(branchName) {
33
+ try {
34
+ run(`git branch -D ${q(branchName)}`);
35
+ }
36
+ catch {
37
+ ui.warn(`No se pudo eliminar la rama ${branchName} durante el rollback.`);
38
+ }
39
+ }
40
+ /**
41
+ * Deshace todas las ramas creadas durante un `applyPlan` fallido.
42
+ *
43
+ * Pasos del rollback:
44
+ * 1. Vuelve a `sourceBranch` con `git checkout`.
45
+ * 2. `git reset --hard <originalHeadSha>` — deshace cualquier commit parcial
46
+ * que PASO 1 hubiera hecho en sourceBranch antes del fallo.
47
+ * 3. Limpia el working tree y el index (`git clean -fd` incluye archivos untracked).
48
+ * 4. Elimina con `git branch -D` cada rama derivada ya creada.
49
+ *
50
+ * @param createdBranches - Nombres de ramas derivadas ya creadas.
51
+ * @param sourceBranch - Rama origen a la que volver.
52
+ * @param originalHeadSha - SHA del HEAD de sourceBranch antes de empezar.
53
+ */
54
+ function rollback(createdBranches, sourceBranch, originalHeadSha) {
55
+ ui.section("ROLLBACK TOTAL", "#");
56
+ ui.warn(`Algo fallo. Restaurando el repositorio a la rama origen '${sourceBranch}'...`);
57
+ try {
58
+ run(`git checkout ${q(sourceBranch)}`);
59
+ run(`git reset --hard ${q(originalHeadSha)}`);
60
+ run("git restore --staged .");
61
+ run("git clean -fd");
62
+ ui.ok(`Repositorio restaurado correctamente a '${sourceBranch}' (SHA: ${originalHeadSha.slice(0, 7)}).`);
63
+ }
64
+ catch {
65
+ ui.warn("No se pudo restaurar el working tree completamente. Revisalo manualmente.");
66
+ }
67
+ if (createdBranches.length) {
68
+ ui.warn(`Eliminando ${createdBranches.length} rama(s) creadas durante el proceso...`);
69
+ for (const branch of createdBranches) {
70
+ deleteBranch(branch);
71
+ ui.muted(` Rama eliminada: ${branch}`);
72
+ }
73
+ }
74
+ ui.ok("Rollback completado. El repositorio queda exactamente como al inicio.");
75
+ }
76
+ /**
77
+ * Crea una nueva rama local desde la posición actual del HEAD.
78
+ * En el modelo apilado cada rama se crea desde la punta de la anterior,
79
+ * por lo que no es necesario hacer checkout antes de llamar a esta función.
80
+ *
81
+ * @throws {Error} Si la rama ya existe localmente.
82
+ */
83
+ function createBranchFromSource(targetBranch) {
84
+ if (branchExists(targetBranch)) {
85
+ throw new Error(`La rama destino ya existe: ${targetBranch}. Elíminala o usa otro nombre antes de ejecutar apply.`);
86
+ }
87
+ run(`git checkout -b ${q(targetBranch)}`);
88
+ }
89
+ /**
90
+ * Crea una rama de respaldo con un commit que captura todo el working tree
91
+ * (archivos modificados + no rastreados) tal como estaba antes del apply.
92
+ *
93
+ * ### Estrategia: git plumbing sin checkout
94
+ * Usamos `git write-tree` / `git commit-tree` para crear el objeto commit
95
+ * directamente en el object store, sin cambiar HEAD ni la rama activa.
96
+ * Así el working tree queda intacto para el apply que viene después.
97
+ *
98
+ * Pasos:
99
+ * 1. `git add -A` → stagea toda la copia de trabajo
100
+ * 2. `git write-tree` → crea un objeto tree desde el index → hash
101
+ * 3. `git commit-tree` → crea el commit con ese tree → hash
102
+ * 4. `git branch <name> <sha>`→ apunta la rama de respaldo al nuevo commit
103
+ * 5. `git restore --staged .` → devuelve el index a HEAD (working tree sin cambios)
104
+ *
105
+ * La rama NO se incluye en `createdBranches`, por lo que el rollback
106
+ * automático nunca la elimina — es exactamente el punto de restauración.
107
+ *
108
+ * Formato del nombre: `<sourceBranch>-backup-<YYYY-MM-DDTHH-mm-ss>`
109
+ *
110
+ * @param sourceBranch - Nombre de la rama origen.
111
+ * @param originalHeadSha - SHA del HEAD antes del apply.
112
+ * @returns El nombre de la rama de respaldo creada, o `null` si no pudo crearse.
113
+ */
114
+ async function createBackupBranch(sourceBranch, originalHeadSha) {
115
+ // El nombre mantiene el prefijo de tipo (feature/, fix/, etc.) y añade
116
+ // "-backup-<timestamp>" al final para agruparse junto a la rama origen.
117
+ const iso = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
118
+ const backupName = `${sourceBranch}-backup-${iso}`;
119
+ ui.section("RAMA DE RESPALDO", "#");
120
+ try {
121
+ // Paso 1: stagear toda la copia de trabajo (tracked + untracked)
122
+ run("git add -A");
123
+ // Paso 2: construir el objeto tree desde el index actual
124
+ const treeHash = shSafe("git write-tree").trim();
125
+ if (!treeHash)
126
+ throw new Error("git write-tree no devolvió un hash válido.");
127
+ // Paso 3: crear el objeto commit (sin modificar HEAD ni la rama activa)
128
+ const isoMsg = new Date().toISOString();
129
+ const commitMsg = `chore(backup): snapshot of working tree before apply (${isoMsg})`;
130
+ const backupCommitHash = shSafe(`git commit-tree ${q(treeHash)} -p ${q(originalHeadSha)} -m ${q(commitMsg)}`).trim();
131
+ if (!backupCommitHash)
132
+ throw new Error("git commit-tree no devolvió un hash válido.");
133
+ // Paso 4: apuntar la nueva rama al commit de respaldo
134
+ run(`git branch ${q(backupName)} ${q(backupCommitHash)}`);
135
+ // Paso 5: destagear todo → index vuelve a HEAD, working tree sin cambios
136
+ run("git restore --staged .");
137
+ ui.ok(`Rama de respaldo creada: '${backupName}'`);
138
+ ui.muted(` Commit de respaldo: ${backupCommitHash.slice(0, 7)} — snapshot completo del working tree`);
139
+ }
140
+ catch (err) {
141
+ // Asegurarse de limpiar el index si algo falló a mitad
142
+ try {
143
+ run("git restore --staged .");
144
+ }
145
+ catch { /* ignorar */ }
146
+ ui.warn(`No se pudo crear la rama de respaldo '${backupName}': ${err.message}`);
147
+ ui.warn("Continuando sin respaldo.");
148
+ return BACKUP_FAILED;
149
+ }
150
+ // Intentar publicar en remoto (no bloqueante)
151
+ const remote = detectRemote(sourceBranch);
152
+ try {
153
+ run(`git push ${q(remote)} ${q(backupName)}`);
154
+ ui.ok(`Respaldo publicado en '${remote}'.`);
155
+ }
156
+ catch {
157
+ ui.warn(`No se pudo publicar el respaldo en '${remote}'. Queda disponible localmente.`);
158
+ }
159
+ ui.info("Para recuperar archivos o commits desde el respaldo:");
160
+ ui.muted(` git checkout ${q(backupName)} -- <archivo> → restaurar un archivo concreto`);
161
+ ui.muted(` git diff ${q(backupName)} → ver qué cambió`);
162
+ ui.muted(` git cherry-pick <sha> → traer un commit específico`);
163
+ return backupName;
164
+ }
165
+ /**
166
+ * Construye el mensaje del commit para una posición específica del plan.
167
+ *
168
+ * Usa `commit.suggestedMessage` si está disponible (lo estará siempre en el
169
+ * flujo normal). El fallback es código defensivo para planes construidos
170
+ * externamente sin pasar por `buildCommitPlan`.
171
+ *
172
+ * @param plan - Plan de rama que contiene el array de commits.
173
+ * @param commitIndex - Índice base-0 dentro de `plan.commitPlan`.
174
+ */
175
+ function buildCommitMessage(plan, commitIndex) {
176
+ const commit = plan.commitPlan[commitIndex];
177
+ if (commit?.suggestedMessage) {
178
+ return commit.suggestedMessage;
179
+ }
180
+ return `chore(split): ${plan.name} commit ${commitIndex + 1}`;
181
+ }
182
+ // ---------------------------------------------------------------------------
183
+ // apply
184
+ // ---------------------------------------------------------------------------
185
+ /**
186
+ * Aplica el plan de ramas al repositorio git.
187
+ *
188
+ * Siempre muestra prompts para el nombre de cada rama y el mensaje de cada
189
+ * commit. Presionar Enter acepta el valor sugerido.
190
+ *
191
+ * ### Flujo
192
+ * 1. Commits del existingBasePlan directamente en sourceBranch.
193
+ * 2. Ramas derivadas en cascada (stacked): cada una hereda la anterior.
194
+ * 3. Volver a sourceBranch y limpiar el index.
195
+ * 4. Preguntar si publicar en remoto.
196
+ *
197
+ * Si cualquier paso falla: rollback automático (`git reset --hard <sha>`).
198
+ *
199
+ * @param plans - Lista de planes devuelta por `findBestPlan`.
200
+ * @param config - Configuración activa (usado para mostrar contexto de nomenclatura).
201
+ */
202
+ export async function applyPlan(plans, config) {
203
+ const sourceBranch = getCurrentBranch();
204
+ const existingBasePlan = plans.find((plan) => plan.isExistingBaseBranch);
205
+ const derivedPlans = plans.filter((plan) => !plan.isExistingBaseBranch);
206
+ ui.section("APLICANDO PLAN", "#");
207
+ ui.kv("Rama origen", sourceBranch);
208
+ if (config.branchNamingContext) {
209
+ ui.kv("Tipo de rama", config.branchNamingContext.branchType);
210
+ ui.kv("Código de equipo", config.branchNamingContext.teamCode);
211
+ ui.kv("Número de historia", config.branchNamingContext.storyNumber);
212
+ }
213
+ if (existingBasePlan) {
214
+ ui.info(`La rama base existente se usara como PR inicial: ${existingBasePlan.name}`);
215
+ }
216
+ // Si no hay ramas derivadas que crear: la rama actual ya es suficientemente buena
217
+ if (derivedPlans.length === 0) {
218
+ ui.section("ANALISIS DE CALIDAD", "#");
219
+ if (existingBasePlan) {
220
+ const score = existingBasePlan.score?.toFixed(2) ?? "—";
221
+ const recommendation = existingBasePlan.recommendation ?? "";
222
+ ui.ok(`La rama '${sourceBranch}' tiene buena calidad y no necesita dividirse.`);
223
+ ui.kv("Puntaje obtenido", `${score} / 5.00`);
224
+ if (recommendation) {
225
+ ui.info(recommendation);
226
+ }
227
+ }
228
+ else {
229
+ ui.ok(`La rama '${sourceBranch}' ya es suficiente para un solo PR.`);
230
+ }
231
+ return;
232
+ }
233
+ ui.info(`Se crearán ${derivedPlans.length} ramas derivadas desde '${sourceBranch}'.`);
234
+ ui.info("Personaliza el nombre de cada rama y el mensaje de cada commit.");
235
+ ui.muted(" Presiona Enter para aceptar el valor sugerido.");
236
+ const createdBranches = [];
237
+ // Conjunto de nombres ya reservados en esta sesión (incluye la rama origen
238
+ // y las ramas derivadas confirmadas). Permite detectar duplicados ANTES de
239
+ // llamar a createBranchFromSource y evitar el rollback por colisión.
240
+ const reservedNames = new Set([sourceBranch]);
241
+ // Guardar SHA antes de tocar nada — usado en rollback para deshacer
242
+ // cualquier commit parcial que se haya hecho en sourceBranch (PASO 1).
243
+ // Si el comando falla (repo vacío u otro estado inválido) lanzamos un error
244
+ // inmediato: continuar sin el SHA haría que el rollback NO pueda deshacer
245
+ // los commits ya hechos en sourceBranch (git reset --hard HEAD no retrocede).
246
+ const rawSha = shSafe("git rev-parse HEAD").trim();
247
+ if (!rawSha) {
248
+ throw new Error("No se pudo obtener el SHA del HEAD actual. " +
249
+ "Asegúrate de que el repositorio tenga al menos un commit antes de ejecutar apply.");
250
+ }
251
+ const originalHeadSha = rawSha;
252
+ // Crear rama de respaldo ANTES del bloque try para que el rollback nunca la elimine.
253
+ // Es el punto de recuperación si el plan cambia de alcance o algo falla inesperadamente.
254
+ // Puede ser null si la creación falló (ya se imprimió un aviso).
255
+ const backupBranchName = await createBackupBranch(sourceBranch, originalHeadSha);
256
+ try {
257
+ // ── PASO 1: Commitear los archivos de la rama base original ────────────────
258
+ // Los archivos están en el working tree como cambios unstaged.
259
+ // Los commitamos directamente en sourceBranch.
260
+ if (existingBasePlan && existingBasePlan.commitPlan.length > 0) {
261
+ ui.section(`RAMA BASE: ${sourceBranch}`, "#");
262
+ ui.info("Aplicando commits de la rama base original...");
263
+ for (let i = 0; i < existingBasePlan.commitPlan.length; i++) {
264
+ const commit = existingBasePlan.commitPlan[i];
265
+ let commitMessage = buildCommitMessage(existingBasePlan, i);
266
+ commitMessage = await ui.prompt(` Commit ${commit.index} — mensaje`, commitMessage);
267
+ ui.subsection(`Commit ${commit.index}`);
268
+ ui.muted(`Mensaje: ${commitMessage}`);
269
+ ui.fileList(commit.files, 100);
270
+ addFiles(commit.files);
271
+ // Bug fix 4: si git add fue no-op (archivos ya commiteados por aheadCount > 0)
272
+ // no hay nada staged y git commit fallaría. Omitir el commit y avisar.
273
+ const staged = shSafe("git diff --cached --name-only").trim();
274
+ if (!staged) {
275
+ ui.warn(` Commit ${commit.index} omitido: ningún archivo del commit tiene cambios en el working tree (puede que ya estén commiteados).`);
276
+ continue;
277
+ }
278
+ gitCommit(commitMessage);
279
+ }
280
+ ui.ok(`Rama base '${sourceBranch}' lista.`);
281
+ }
282
+ // ── PASO 2: Crear ramas derivadas en cascada (stacked branches) ───────────
283
+ for (const plan of derivedPlans) {
284
+ const totalAssigned = plan.commitPlan.flatMap(c => c.files).length;
285
+ // Bug fix 3: mostrar el prompt ANTES de ui.section para que el header
286
+ // refleje el nombre FINAL elegido por el usuario, no el sugerido.
287
+ ui.muted(`\n Rama sugerida: ${plan.name} (${totalAssigned} archivos asignados)`);
288
+ let targetBranch = await ui.prompt("Nombre de la rama", plan.name);
289
+ // Validar unicidad: re-pedir si el nombre ya está reservado en esta sesión
290
+ // o ya existe como rama local. Evita el crash y rollback por colisión.
291
+ while (reservedNames.has(targetBranch) || branchExists(targetBranch)) {
292
+ if (reservedNames.has(targetBranch)) {
293
+ ui.warn(`El nombre '${targetBranch}' ya fue usado para otra rama en este apply. Elige un nombre diferente.`);
294
+ }
295
+ else {
296
+ ui.warn(`La rama local '${targetBranch}' ya existe. Elíminala primero o elige otro nombre.`);
297
+ }
298
+ targetBranch = await ui.prompt("Nombre de la rama", `${targetBranch}-2`);
299
+ }
300
+ reservedNames.add(targetBranch);
301
+ ui.section(`RAMA: ${targetBranch}`, "#");
302
+ // Crear la rama desde el HEAD actual (punta de la rama anterior).
303
+ // No hacemos checkout ni reset: los archivos del working tree permanecen.
304
+ createBranchFromSource(targetBranch);
305
+ createdBranches.push(targetBranch);
306
+ for (let i = 0; i < plan.commitPlan.length; i++) {
307
+ const commit = plan.commitPlan[i];
308
+ let commitMessage = buildCommitMessage(plan, i);
309
+ commitMessage = await ui.prompt(` Commit ${commit.index} — mensaje`, commitMessage);
310
+ ui.subsection(`Commit ${commit.index}`);
311
+ ui.muted(`Mensaje: ${commitMessage}`);
312
+ ui.fileList(commit.files, 100);
313
+ addFiles(commit.files);
314
+ // Bug fix 4: misma guardia que en PASO 1 — evita git commit vacío.
315
+ const staged = shSafe("git diff --cached --name-only").trim();
316
+ if (!staged) {
317
+ ui.warn(` Commit ${commit.index} omitido: ningún archivo tiene cambios en el working tree.`);
318
+ continue;
319
+ }
320
+ gitCommit(commitMessage);
321
+ }
322
+ ui.ok(`Rama lista: ${targetBranch}`);
323
+ }
324
+ // ── PASO 3: Volver a la rama origen y liberar el index ──────────────────
325
+ // NO se llama git clean -fd aquí (éxito): evita borrar archivos del usuario
326
+ // que no forman parte del plan y que git checkout no toca.
327
+ // Solo se limpia el index por si algo quedó staged sin commitear.
328
+ run(`git checkout ${q(sourceBranch)}`);
329
+ run("git restore --staged .");
330
+ // ── PASO 4: Preguntar si publicar en remoto ────────────────────────────
331
+ const allCreated = [
332
+ ...(existingBasePlan ? [sourceBranch] : []),
333
+ ...createdBranches,
334
+ ];
335
+ if (allCreated.length) {
336
+ const remote = detectRemote(sourceBranch);
337
+ ui.info(`Ramas listas para PR:`);
338
+ for (const branch of allCreated) {
339
+ ui.muted(` • ${branch}`);
340
+ }
341
+ const publish = await ui.confirm(`¿Deseas publicar las ${allCreated.length} rama(s) en '${remote}'?`);
342
+ if (publish) {
343
+ ui.section("PUBLICANDO RAMAS EN REMOTO", "#");
344
+ for (const branch of allCreated) {
345
+ ui.muted(` → ${branch}`);
346
+ try {
347
+ pushBranch(branch, remote);
348
+ ui.ok(`${branch} publicada correctamente.`);
349
+ }
350
+ catch (pushErr) {
351
+ ui.warn(`No se pudo publicar ${branch}: ${pushErr.message}`);
352
+ }
353
+ }
354
+ }
355
+ else {
356
+ ui.warn("Ramas no publicadas. Puedes publicarlas después con: git push <remoto> <rama>");
357
+ }
358
+ }
359
+ ui.ok("Apply finalizado.");
360
+ }
361
+ catch (err) {
362
+ ui.error(`Error durante apply: ${err.message}`);
363
+ rollback(createdBranches, sourceBranch, originalHeadSha);
364
+ // Bug fix 2: solo mostrar instrucciones de recuperación si el backup
365
+ // fue realmente creado en esta ejecución (no null).
366
+ if (backupBranchName) {
367
+ ui.info(`Rama de respaldo disponible: '${backupBranchName}'`);
368
+ ui.muted(` Para inspeccionar el estado anterior al apply:`);
369
+ ui.muted(` git checkout ${q(backupBranchName)}`);
370
+ ui.muted(` Para recuperar un archivo específico:`);
371
+ ui.muted(` git checkout ${q(backupBranchName)} -- <archivo>`);
372
+ }
373
+ else {
374
+ ui.warn("No hay rama de respaldo disponible. Verifica el estado del repositorio manualmente.");
375
+ }
376
+ throw err;
377
+ }
378
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * git.ts — Capa de abstracción sobre los comandos git de bajo nivel.
3
+ *
4
+ * Este módulo es el único punto del proyecto que ejecuta procesos git directamente.
5
+ * Todos los demás módulos (executor.ts, file-stats.ts, dependency.ts, etc.)
6
+ * deben pasar por aquí, lo que facilita:
7
+ * - el testeo (solo hay un lugar donde mockear `execSync`)
8
+ * - el manejo uniforme de errores
9
+ *
10
+ * ### Funciones exportadas
11
+ * - `q` — escapa un valor para usarlo dentro de comillas en shell
12
+ * - `shSafe` — ejecuta un comando y devuelve `""` si falla (no lanza)
13
+ * - `run` — ejecuta un comando con `stdio: inherit`
14
+ * - `requireGitRepo` — falla si el directorio actual no es un repositorio git
15
+ * - `requireCleanIndex` — falla si hay cambios staged sin commitear
16
+ * - `getCurrentBranch` — devuelve el nombre de la rama activa
17
+ * - `branchExists` — comprueba si una rama local existe
18
+ * - `addFiles` — hace `git add` de una lista de archivos
19
+ * - `commit` — hace `git commit` con un mensaje dado
20
+ */
21
+ import { execSync } from "node:child_process";
22
+ /**
23
+ * Escapa un valor para usarlo de forma segura dentro de comillas dobles en
24
+ * una cadena de comando shell. Escapa: `"`, `\`, `$`, `` ` ``.
25
+ *
26
+ * **¿Por qué no usar `shell: true` directamente?** Porque nombres de rama o
27
+ * rutas con espacios, dólares o comillas rompergían el comando sin este escaping.
28
+ *
29
+ * @param value - Cadena a escapar (nombre de rama, ruta de archivo, mensaje de commit…).
30
+ * @returns La cadena envuelta en comillas dobles con caracteres especiales escapados.
31
+ */
32
+ export function q(value) {
33
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
34
+ }
35
+ /** Ejecuta un comando shell y devuelve su salida (sin trailing newline). Lanza excepción si el proceso retorna código ≠ 0. */
36
+ function sh(cmd) {
37
+ return execSync(cmd, {
38
+ encoding: "utf8",
39
+ stdio: ["pipe", "pipe", "ignore"]
40
+ }).trim();
41
+ }
42
+ /**
43
+ * Como {@link sh} pero devuelve `""` en lugar de lanzar excepción si el comando falla.
44
+ *
45
+ * Usar en consultas git que pueden fallar legítimamente (ej. el repositorio
46
+ * no existe todavía, el archivo no está en el HEAD, etc.). El parámetro `verbose`
47
+ * permite escribir el error a stderr para depuración.
48
+ *
49
+ * @param cmd - Comando shell a ejecutar.
50
+ * @param options - `verbose: true` escribe el error a stderr en lugar de silenciarlo.
51
+ */
52
+ export function shSafe(cmd, { verbose = false } = {}) {
53
+ try {
54
+ return sh(cmd);
55
+ }
56
+ catch (err) {
57
+ if (verbose) {
58
+ process.stderr.write(`[git warn] command failed: ${cmd}\n${err.message}\n`);
59
+ }
60
+ return "";
61
+ }
62
+ }
63
+ /**
64
+ * Ejecuta un comando shell con `stdio: "inherit"` para que la salida del proceso
65
+ * hijo sea visible directamente en la terminal.
66
+ *
67
+ * A diferencia de `sh`, esta función se usa para comandos que modifican estado
68
+ * (checkout, commit, restore, etc.) donde la visibilidad de la salida es importante.
69
+ *
70
+ * @param cmd - Comando shell a ejecutar.
71
+ */
72
+ export function run(cmd) {
73
+ execSync(cmd, { stdio: "inherit" });
74
+ }
75
+ /**
76
+ * Verifica que el directorio actual sea un repositorio git válido.
77
+ * Lanza un `Error` con mensaje en español si no lo es.
78
+ * Debe llamarse al comienzo de la ejecución del CLI para evitar errores
79
+ * crípticos más adelante.
80
+ */
81
+ export function requireGitRepo() {
82
+ const res = shSafe("git rev-parse --is-inside-work-tree");
83
+ if (res !== "true") {
84
+ throw new Error("Este directorio no es un repositorio git.");
85
+ }
86
+ }
87
+ /**
88
+ * Verifica que el índice git esté limpio (sin cambios staged).
89
+ *
90
+ * La herramienta trabaja con el working tree, no con el índice. Si hay cambios
91
+ * staged al iniciar, el flujo de `git restore --source` y `git add` puede
92
+ * sobrescribirlos silenciosamente. Esta guardia lo previene.
93
+ *
94
+ * Lanza un `Error` si hay cambios staged pendientes.
95
+ */
96
+ export function requireCleanIndex() {
97
+ try {
98
+ execSync("git diff --cached --quiet");
99
+ }
100
+ catch {
101
+ throw new Error("Tienes cambios staged en el index. Limpia el index antes de ejecutar.");
102
+ }
103
+ }
104
+ /**
105
+ * Devuelve el nombre de la rama activa en el repositorio.
106
+ * Equivale a `git rev-parse --abbrev-ref HEAD`.
107
+ *
108
+ * @throws {Error} Si el comando falla (repositorio vacío, HEAD detached, etc.).
109
+ */
110
+ export function getCurrentBranch() {
111
+ return sh("git rev-parse --abbrev-ref HEAD");
112
+ }
113
+ /**
114
+ * Comprueba si una rama local con el nombre dado existe en el repositorio.
115
+ *
116
+ * Usa `git show-ref` en lugar de `git branch --list` para ser más rápido
117
+ * (no necesita listar todas las ramas, solo verificar la ref específica).
118
+ *
119
+ * @param name - Nombre de la rama a verificar (sin `refs/heads/`).
120
+ * @returns `true` si la rama existe localmente, `false` en caso contrario.
121
+ */
122
+ export function branchExists(name) {
123
+ try {
124
+ execSync(`git show-ref --verify --quiet ${q(`refs/heads/${name}`)}`);
125
+ return true;
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ }
131
+ /**
132
+ * Añade los archivos indicados al índice con `git add`.
133
+ *
134
+ * Si la lista está vacía, no ejecuta ningún comando (guarda un proceso git).
135
+ * Cada ruta se escapa con {@link q} para soportar nombres con espacios o
136
+ * caracteres especiales.
137
+ *
138
+ * @param files - Rutas relativas de los archivos a añadir al índice.
139
+ */
140
+ export function addFiles(files) {
141
+ if (!files.length)
142
+ return;
143
+ const quoted = files.map((f) => q(f)).join(" ");
144
+ run(`git add -- ${quoted}`);
145
+ }
146
+ /**
147
+ * Crea un commit con el mensaje dado.
148
+ *
149
+ * El mensaje se escapa con {@link q} para soportar comillas, saltos de línea
150
+ * y caracteres especiales en el cuerpo del commit.
151
+ *
152
+ * @param message - Mensaje del commit (idealmente en formato Conventional Commits).
153
+ */
154
+ export function commit(message) {
155
+ run(`git commit -m ${q(message)}`);
156
+ }
157
+ // ─── v2.0.11 — Funciones de remoto ──────────────────────────────────────────────
158
+ /**
159
+ * Hace `git fetch <remote> <branch> --quiet`, actualizando la ref de tracking
160
+ * remota (`refs/remotes/<remote>/<branch>`) sin emitir salida normal.
161
+ *
162
+ * Puede lanzar una excepción si no hay red o si el remoto no existe;
163
+ * el llamador debe capturarla si quiere un comportamiento degradado.
164
+ *
165
+ * @param remote - Nombre del remoto (ej. `"origin"`).
166
+ * @param branch - Nombre de la rama a actualizar.
167
+ */
168
+ export function fetchQuiet(remote, branch) {
169
+ run(`git fetch ${q(remote)} ${q(branch)} --quiet`);
170
+ }
171
+ /**
172
+ * Devuelve `true` si la ref de tracking `refs/remotes/<remote>/<branch>`
173
+ * existe en el repo local (indica que la rama existe en el remoto y fue
174
+ * descargada en algún momento).
175
+ *
176
+ * @param branch - Nombre de la rama remota.
177
+ * @param remote - Nombre del remoto (ej. `"origin"`).
178
+ */
179
+ export function remoteTrackingExists(branch, remote) {
180
+ return shSafe(`git rev-parse --verify ${q(`refs/remotes/${remote}/${branch}`)}`).length > 0;
181
+ }
182
+ /**
183
+ * Devuelve cuántos commits tiene la rama remota que la local NO tiene.
184
+ * Un valor > 0 indica que la rama local está desactualizada (detrás del remoto).
185
+ *
186
+ * @param branch - Nombre de la rama local/remota.
187
+ * @param remote - Nombre del remoto (ej. `"origin"`).
188
+ * @returns Número de commits pendientes de `git pull`, o 0 si está al día.
189
+ */
190
+ export function localBehindCount(branch, remote) {
191
+ const count = shSafe(`git rev-list ${q(branch)}..${q(`${remote}/${branch}`)} --count`);
192
+ return parseInt(count, 10) || 0;
193
+ }
194
+ /**
195
+ * Detecta el nombre del remoto configurado para la rama indicada.
196
+ *
197
+ * Orden de resolución:
198
+ * 1. Upstream tracking de la rama: `git rev-parse --abbrev-ref --symbolic-full-name <branch>@{u}`
199
+ * devuelve p.ej. `origin/feature/ABC-123` → toma la primera parte antes de `/`.
200
+ * 2. Primer remoto listado por `git remote`.
201
+ * 3. Fallback: `"origin"`.
202
+ *
203
+ * @param branch - Nombre de la rama cuyo upstream se consulta.
204
+ * @returns Nombre del remoto detectado.
205
+ */
206
+ export function detectRemote(branch) {
207
+ const upstream = shSafe(`git rev-parse --abbrev-ref --symbolic-full-name ${q(`${branch}@{u}`)}`);
208
+ if (upstream) {
209
+ const remote = upstream.split("/")[0].trim();
210
+ if (remote && remote !== upstream)
211
+ return remote;
212
+ }
213
+ const firstRemote = shSafe("git remote").split("\n")[0].trim();
214
+ if (firstRemote)
215
+ return firstRemote;
216
+ return "origin";
217
+ }
218
+ /**
219
+ * Devuelve cuántos commits tiene la rama actual (HEAD) que `baseBranch` NO tiene.
220
+ * Un valor > 0 indica que la rama ya tiene commits propios respecto a la base.
221
+ *
222
+ * @param baseBranch - Rama base del PR (ej. `"master"`, `"main"`).
223
+ * @returns Número de commits adelantados en HEAD, o 0 si está al mismo nivel.
224
+ */
225
+ export function localAheadCount(baseBranch) {
226
+ const count = shSafe(`git rev-list ${q(baseBranch)}..HEAD --count`);
227
+ return parseInt(count, 10) || 0;
228
+ }
229
+ /**
230
+ * Hace push de una rama local al remoto indicado.
231
+ *
232
+ * @param branch - Nombre de la rama local a publicar.
233
+ * @param remote - Nombre del remoto destino (ej. `"origin"`).
234
+ */
235
+ export function pushBranch(branch, remote) {
236
+ // --set-upstream configura el tracking para que `git push` sin argumentos
237
+ // funcione correctamente en usos posteriores de la rama.
238
+ run(`git push --set-upstream ${q(remote)} ${q(branch)}`);
239
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * ⚠️ ARCHIVO GENERADO AUTOMÁTICAMENTE — NO EDITAR MANUALMENTE.
3
+ *
4
+ * Generado por: scripts/compile-styles.cjs
5
+ * Fuente SCSS: src/output/report.scss
6
+ *
7
+ * Para aplicar cambios editá `src/output/report.scss` y ejecutá:
8
+ * npm run build:styles
9
+ */
10
+ export const reportStyles = `@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap";:root{--bg: #f0f4f8;--surface: #ffffff;--surface-soft: #f7f9fc;--border: #dde3ed;--text: #1e293b;--muted: #64748b;--green: #15803d;--green-soft: #dcfce7;--green-border: #86efac;--yellow: #a16207;--yellow-soft: #fef3c7;--yellow-border: #fde68a;--orange: #c2410c;--orange-soft: #ffedd5;--orange-border: #fdba74;--red: #b91c1c;--red-soft: #fee2e2;--red-border: #fca5a5;--blue: #1d4ed8;--blue-soft: #dbeafe;--blue-border: #93c5fd;--purple: #6d28d9;--purple-soft: #ede9fe;--shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.07), 0 1px 2px rgba(15, 23, 42, 0.05);--shadow: 0 4px 16px rgba(15, 23, 42, 0.08), 0 1px 3px rgba(15, 23, 42, 0.05);--shadow-lg: 0 10px 40px rgba(15, 23, 42, 0.12), 0 2px 8px rgba(15, 23, 42, 0.06);--radius: 14px;--radius-sm: 8px}*,*::before,*::after{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;padding:0;background:var(--bg);color:var(--text);font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;font-size:15px;line-height:1.6}.container{max-width:1320px;margin:0 auto;padding:32px 24px 80px}.hero{background:linear-gradient(135deg, #0f172a 0%, #1e3a6e 50%, #1d4ed8 100%);color:#fff;border-radius:24px;padding:36px 36px 28px;box-shadow:var(--shadow-lg);margin-bottom:28px;position:relative;overflow:hidden}.hero::before{content:"";position:absolute;inset:0;background:url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");pointer-events:none}.hero-top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;flex-wrap:wrap}.hero-title-area h1{margin:0 0 6px;font-size:34px;font-weight:800;letter-spacing:-0.5px;line-height:1.1}.hero-title-area p{margin:0;color:hsla(0,0%,100%,.75);font-size:14px;max-width:500px}.hero-timestamp{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);border-radius:10px;padding:8px 14px;font-size:12px;color:hsla(0,0%,100%,.85);white-space:nowrap}.hero-grid{margin-top:24px;display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:12px}.hero-item{background:hsla(0,0%,100%,.11);border:1px solid hsla(0,0%,100%,.16);border-radius:14px;padding:14px 18px;backdrop-filter:blur(4px);transition:background .2s}.hero-item:hover{background:hsla(0,0%,100%,.17)}.hero-item .label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;opacity:.7;margin-bottom:5px}.hero-item .value{font-size:17px;font-weight:700;word-break:break-word}.disclaimer-banner{display:flex;align-items:flex-start;gap:16px;margin:24px 0 0;padding:20px 24px;background:#fefce8;border:2px solid #f59e0b;border-radius:12px;box-shadow:0 2px 8px rgba(245,158,11,.18)}.disclaimer-banner .disclaimer-icon{font-size:32px;line-height:1;flex-shrink:0;margin-top:2px}.disclaimer-banner .disclaimer-body{flex:1;color:#78350f}.disclaimer-banner .disclaimer-body strong:first-child{display:block;font-size:15px;font-weight:800;letter-spacing:.5px;text-transform:uppercase;color:#92400e;margin-bottom:8px}.disclaimer-banner .disclaimer-body p{margin:0 0 8px;font-size:14px;line-height:1.6}.disclaimer-banner .disclaimer-body p:last-child{margin-bottom:0}.disclaimer-banner .disclaimer-body ul{margin:6px 0 10px 18px;padding:0;font-size:14px;line-height:1.7}.disclaimer-banner .disclaimer-body .disclaimer-footer{font-size:12px;font-style:italic;color:#a16207;margin-top:8px}.section{margin-top:36px}.section-heading{display:flex;align-items:center;gap:10px;margin:0 0 16px;font-size:20px;font-weight:700;color:#0f172a}.section-heading::after{content:"";flex:1;height:1px;background:var(--border)}.summary-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(190px, 1fr));gap:12px}.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:4px;transition:box-shadow .2s,transform .2s}.stat-card:hover{box-shadow:var(--shadow);transform:translateY(-1px)}.stat-card .label{font-size:12px;color:var(--muted);font-weight:500;text-transform:uppercase;letter-spacing:.04em}.stat-card .value{font-size:26px;font-weight:800;line-height:1.2}.stat-card .sub{font-size:12px;color:var(--muted)}.stat-card.blue .value{color:var(--blue)}.stat-card.green .value{color:var(--green)}.stat-card.yellow .value{color:var(--yellow)}.stat-card.red .value{color:var(--red)}.stat-card.purple .value{color:var(--purple)}.card{background:var(--surface);border:1px solid var(--border);border-radius:18px;padding:22px;box-shadow:var(--shadow);margin-bottom:20px}.badges{display:flex;flex-wrap:wrap;gap:8px}.badge{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;border:1px solid rgba(0,0,0,0)}.badge.green{background:var(--green-soft);color:var(--green);border-color:var(--green-border)}.badge.yellow{background:var(--yellow-soft);color:var(--yellow);border-color:var(--yellow-border)}.badge.orange{background:var(--orange-soft);color:var(--orange);border-color:var(--orange-border)}.badge.red{background:var(--red-soft);color:var(--red);border-color:var(--red-border)}.badge.blue{background:var(--blue-soft);color:var(--blue);border-color:var(--blue-border)}.badge.purple{background:var(--purple-soft);color:var(--purple);border-color:#c4b5fd}table{width:100%;border-collapse:collapse;background:var(--surface);border:1px solid var(--border);border-radius:14px;overflow:hidden;box-shadow:var(--shadow-sm);margin:10px 0 20px}table thead th{background:#f1f5fd;color:#374151;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;text-align:left;padding:11px 14px;border-bottom:1px solid var(--border)}table tbody td{padding:10px 14px;border-bottom:1px solid #e9eff7;vertical-align:middle;font-size:13px}table tbody tr:last-child td{border-bottom:none}table tbody tr:nth-child(even){background:#fafbff}table tbody tr:hover{background:#f0f6ff}.num-cell{text-align:right;font-variant-numeric:tabular-nums;font-weight:600}code{background:#eef2ff;color:#312e81;padding:2px 7px;border-radius:6px;font-size:12px;font-family:"SF Mono","Fira Code","Cascadia Code",monospace;word-break:break-word}.muted{color:var(--muted);font-size:13px}.toc-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px;box-shadow:var(--shadow-sm);margin-bottom:20px}.toc-title{font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px}.toc-list{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.toc-link{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:600;text-decoration:none;border:1px solid rgba(0,0,0,0);transition:opacity .15s}.toc-link:hover{opacity:.8;text-decoration:none}.toc-ok{background:var(--green-soft);color:var(--green);border-color:var(--green-border)}.toc-warn{background:var(--yellow-soft);color:var(--yellow);border-color:var(--yellow-border)}.toc-bad{background:var(--red-soft);color:var(--red);border-color:var(--red-border)}.toc-score{background:rgba(0,0,0,.08);border-radius:999px;padding:1px 7px;font-size:11px;font-weight:800}.plan-card{border-radius:20px;overflow:hidden;box-shadow:var(--shadow);border:1px solid var(--border);margin-bottom:28px}.plan-card-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;padding:22px 26px}.plan-card-body{background:var(--surface);padding:22px 26px}.plan-header-green{background:linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);border-bottom:1px solid #86efac}.plan-header-yellow{background:linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);border-bottom:1px solid #fde68a}.plan-header-red{background:linear-gradient(135deg, #fff1f2 0%, #fee2e2 100%);border-bottom:1px solid #fca5a5}.plan-header-left{display:flex;align-items:center;gap:14px}.plan-branch-icon{font-size:28px;width:48px;height:48px;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.06);border-radius:14px;flex-shrink:0}.plan-branch-name{font-size:17px;font-weight:800;color:#0f172a;word-break:break-all;display:flex;align-items:center;gap:8px;flex-wrap:wrap}.plan-branch-sub{font-size:13px;color:var(--muted);margin-top:2px}.existing-badge{display:inline-block;background:#dbeafe;color:#1e40af;border:1px solid #93c5fd;padding:1px 8px;border-radius:999px;font-size:11px;font-weight:700;margin-right:4px}.plan-header-right{display:flex;flex-direction:column;align-items:center;gap:4px}.gauge-svg{display:block}.plan-score-label{font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.score-label-green{color:var(--green)}.score-label-yellow{color:var(--yellow)}.score-label-red{color:var(--red)}.kpi-row{display:flex;flex-wrap:wrap;gap:12px;margin:16px 0 24px}.kpi{background:var(--surface-soft);border:1px solid var(--border);border-radius:12px;padding:10px 16px;min-width:90px;text-align:center}.kpi-v{font-size:22px;font-weight:800;color:#0f172a;line-height:1.1}.kpi-k{font-size:11px;color:var(--muted);margin-top:3px;text-transform:uppercase;letter-spacing:.04em}.recommendation-box{display:flex;align-items:flex-start;gap:10px;background:#f0f9ff;border:1px solid #bae6fd;border-radius:12px;padding:12px 16px;margin-bottom:16px;font-size:14px;color:#0369a1}.exclusion-note{display:flex;align-items:flex-start;gap:8px;background:#fffbeb;border:1px solid #fde68a;border-radius:10px;padding:10px 14px;margin-bottom:16px;font-size:13px;color:#92400e}.rec-icon{font-size:16px;flex-shrink:0}.section-title{font-size:14px;font-weight:700;color:#0f172a;margin:28px 0 10px;display:flex;align-items:center;gap:6px}.metrics-table thead th{background:#f8faff}.metric-code{background:#e0e7ff;color:#3730a3;font-weight:700}.metric-label{font-size:13px}.metric-value{font-weight:600;color:#374151}.metric-weight{font-weight:600;color:var(--muted)}.metric-contrib{font-weight:700;color:#0f172a;font-variant-numeric:tabular-nums}.pts-bar-wrap{height:6px;background:#e2e8f0;border-radius:999px;overflow:hidden;width:80px;display:inline-block;vertical-align:middle;margin-right:6px}.pts-bar{height:100%;border-radius:999px;transition:width .4s ease}.pts-good{color:var(--green);font-weight:700}.pts-warn{color:var(--yellow);font-weight:700}.pts-bad{color:var(--red);font-weight:700}.metrics-total-row td{background:#f8faff;font-size:13px}.metric-total-value{font-size:16px;font-weight:800;color:#0f172a}.ct-badge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.ct-added{background:#dcfce7;color:#15803d;border:1px solid #86efac}.ct-untracked{background:#ede9fe;color:#6d28d9;border:1px solid #c4b5fd}.ct-modified{background:#dbeafe;color:#1d4ed8;border:1px solid #93c5fd}.ct-deleted{background:#fee2e2;color:#b91c1c;border:1px solid #fca5a5}.ct-renamed{background:#fef3c7;color:#a16207;border:1px solid #fde68a}.ct-default{background:#f1f5f9;color:#475569}.ct-feat{background:#dbeafe;color:#1d4ed8}.ct-fix{background:#fee2e2;color:#b91c1c}.ct-chore{background:#f1f5f9;color:#475569}.ct-docs{background:#fef3c7;color:#a16207}.ct-test{background:#ede9fe;color:#6d28d9}.ct-refactor{background:#f0fdfa;color:#0f766e}.ct-style{background:#fdf4ff;color:#9333ea}.ct-perf{background:#fff7ed;color:#c2410c}.file-path code{max-width:380px;display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.diff-bar{display:inline-flex;height:8px;border-radius:4px;overflow:hidden;background:#e2e8f0;vertical-align:middle;min-width:4px}.diff-add{background:#4ade80;height:100%}.diff-del{background:#f87171;height:100%}.diff-labels{font-size:11px;margin-left:6px;font-variant-numeric:tabular-nums}.add-lbl{color:var(--green);font-weight:700}.del-lbl{color:var(--red);font-weight:700;margin-left:3px}.prio-dot{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:999px;font-size:12px;font-weight:800}.prio-1{background:#fee2e2;color:#b91c1c}.prio-2{background:#ffedd5;color:#c2410c}.prio-3{background:#fef3c7;color:#a16207}.prio-4{background:#dbeafe;color:#1d4ed8}.prio-5{background:#f1f5f9;color:#475569}.divisible-yes{color:var(--green);font-size:13px;font-weight:700}.divisible-no{color:var(--red);font-size:13px;font-weight:700}.block-id{background:#f1f5f9;color:#334155;font-size:11px}.block-files code{font-size:10px;margin:1px}.timeline{position:relative;padding-left:28px}.timeline::before{content:"";position:absolute;left:9px;top:16px;bottom:16px;width:2px;background:#e2e8f0;border-radius:999px}.timeline-item{position:relative;margin-bottom:20px}.timeline-dot{position:absolute;left:-24px;top:14px;width:12px;height:12px;background:#3b82f6;border:2px solid #fff;border-radius:999px;box-shadow:0 0 0 2px #bfdbfe}.timeline-body{background:var(--surface-soft);border:1px solid var(--border);border-radius:12px;padding:12px 16px}.timeline-header{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:6px}.commit-index{background:#e2e8f0;color:#475569;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:800}.commit-type-badge{padding:3px 10px;border-radius:999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.commit-msg{font-size:13px;color:#0f172a;background:#f8fafc;border:1px solid #e2e8f0;padding:2px 8px;border-radius:6px;word-break:break-word}.timeline-meta{display:flex;flex-wrap:wrap;gap:12px;font-size:12px;color:var(--muted);margin-bottom:8px}.timeline-meta span{display:flex;align-items:center;gap:3px}.commit-files-list{margin:0;padding-left:16px;list-style:disc}.commit-files-list li{margin:2px 0;font-size:12px}.commit-files-list code{font-size:11px;padding:1px 5px}.dep-arrow{color:var(--muted);font-size:16px;vertical-align:middle}.footer{margin-top:48px;border-top:1px solid var(--border);padding-top:20px;text-align:center;font-size:12px;color:var(--muted)}.copy-btn{background:none;border:1px solid rgba(0,0,0,.12);border-radius:6px;padding:1px 6px;cursor:pointer;font-size:11px;color:#64748b;line-height:1.4;transition:background .15s,color .15s;flex-shrink:0;vertical-align:middle}.copy-btn:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8}.commit-type-pills{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.commit-type-pill{display:inline-block;padding:1px 8px;border-radius:999px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;opacity:.85}.toc-bar-wrap{width:50px;height:4px;background:rgba(0,0,0,.1);border-radius:999px;overflow:hidden;flex-shrink:0}.toc-bar{height:100%;border-radius:999px;transition:width .4s ease}.toc-bar-ok{background:var(--green)}.toc-bar-warn{background:var(--yellow)}.toc-bar-bad{background:var(--red)}.toc-commits{font-size:11px;opacity:.65;font-weight:500;white-space:nowrap}.back-to-top{text-align:right;padding-top:12px;border-top:1px solid var(--border);margin-top:20px}.back-link{font-size:12px;color:var(--muted);text-decoration:none;font-weight:600}.back-link:hover{color:#3b82f6;text-decoration:underline}.git-commands-block{background:#0f172a;border-radius:14px;padding:18px 20px;overflow-x:auto;margin-top:8px;box-shadow:inset 0 1px 4px rgba(0,0,0,.3)}.git-commands-block pre{margin:0;font-family:"JetBrains Mono","Fira Mono",ui-monospace,monospace;font-size:12px;line-height:1.8;color:#e2e8f0;white-space:pre}.git-commands-block .gc-comment{color:#4b6988;font-style:italic}.git-commands-block .gc-cmd{color:#7dd3fc;font-weight:700}.git-commands-block .gc-branch{color:#86efac}.git-commands-block .gc-file{color:#fca5a5}.git-commands-block .gc-msg{color:#fde68a}.ct-ci{background:#ecfdf5;color:#059669}.ct-revert{background:#fdf4ff;color:#c026d3}@media(max-width: 960px){.hero{padding:24px 20px}.hero-title-area h1{font-size:26px}.plan-card-header{padding:18px}.plan-card-body{padding:18px}.file-path code{max-width:200px}}@media(max-width: 640px){.container{padding:16px 12px 60px}.kpi-row{gap:8px}.kpi{padding:8px 12px;min-width:72px}.kpi-v{font-size:18px}}@media print{body{background:#fff;font-size:12px}.container{max-width:100%;padding:0}.hero{border-radius:0;box-shadow:none}.plan-card,.card,.stat-card,table{box-shadow:none;break-inside:avoid}.section{break-before:auto}a{color:inherit;text-decoration:none}}`;