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.
- package/LICENSE +52 -0
- package/README.md +168 -0
- package/dist/ai/config-wizard.js +282 -0
- package/dist/ai/enricher.js +290 -0
- package/dist/ai/prompts.js +231 -0
- package/dist/ai/provider.js +265 -0
- package/dist/cli.js +442 -0
- package/dist/config/config.js +315 -0
- package/dist/config/default-config.js +223 -0
- package/dist/core/blocks.js +145 -0
- package/dist/core/commit-planner.js +273 -0
- package/dist/core/dependency.js +284 -0
- package/dist/core/file-stats.js +341 -0
- package/dist/core/history.js +72 -0
- package/dist/core/planner.js +25 -0
- package/dist/core/scoring.js +166 -0
- package/dist/core/strategy.js +486 -0
- package/dist/git/branch-naming.js +120 -0
- package/dist/git/executor.js +378 -0
- package/dist/git/git.js +239 -0
- package/dist/output/report-styles.generated.js +10 -0
- package/dist/output/report.js +726 -0
- package/dist/output/ui.js +417 -0
- package/dist/shared/constants.js +59 -0
- package/dist/shared/types.js +7 -0
- package/dist/shared/utils.js +73 -0
- package/node_modules/@colors/colors/LICENSE +26 -0
- package/node_modules/@colors/colors/README.md +219 -0
- package/node_modules/@colors/colors/examples/normal-usage.js +83 -0
- package/node_modules/@colors/colors/examples/safe-string.js +80 -0
- package/node_modules/@colors/colors/index.d.ts +136 -0
- package/node_modules/@colors/colors/lib/colors.js +211 -0
- package/node_modules/@colors/colors/lib/custom/trap.js +46 -0
- package/node_modules/@colors/colors/lib/custom/zalgo.js +110 -0
- package/node_modules/@colors/colors/lib/extendStringPrototype.js +110 -0
- package/node_modules/@colors/colors/lib/index.js +13 -0
- package/node_modules/@colors/colors/lib/maps/america.js +10 -0
- package/node_modules/@colors/colors/lib/maps/rainbow.js +12 -0
- package/node_modules/@colors/colors/lib/maps/random.js +11 -0
- package/node_modules/@colors/colors/lib/maps/zebra.js +5 -0
- package/node_modules/@colors/colors/lib/styles.js +95 -0
- package/node_modules/@colors/colors/lib/system/has-flag.js +35 -0
- package/node_modules/@colors/colors/lib/system/supports-colors.js +151 -0
- package/node_modules/@colors/colors/package.json +45 -0
- package/node_modules/@colors/colors/safe.d.ts +48 -0
- package/node_modules/@colors/colors/safe.js +10 -0
- package/node_modules/@colors/colors/themes/generic-logging.js +12 -0
- package/node_modules/ansi-align/LICENSE +13 -0
- package/node_modules/ansi-align/README.md +80 -0
- package/node_modules/ansi-align/index.js +61 -0
- package/node_modules/ansi-align/node_modules/ansi-regex/index.d.ts +37 -0
- package/node_modules/ansi-align/node_modules/ansi-regex/index.js +10 -0
- package/node_modules/ansi-align/node_modules/ansi-regex/license +9 -0
- package/node_modules/ansi-align/node_modules/ansi-regex/package.json +55 -0
- package/node_modules/ansi-align/node_modules/ansi-regex/readme.md +78 -0
- package/node_modules/ansi-align/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
- package/node_modules/ansi-align/node_modules/emoji-regex/README.md +73 -0
- package/node_modules/ansi-align/node_modules/emoji-regex/es2015/index.js +6 -0
- package/node_modules/ansi-align/node_modules/emoji-regex/es2015/text.js +6 -0
- package/node_modules/ansi-align/node_modules/emoji-regex/index.d.ts +23 -0
- package/node_modules/ansi-align/node_modules/emoji-regex/index.js +6 -0
- package/node_modules/ansi-align/node_modules/emoji-regex/package.json +50 -0
- package/node_modules/ansi-align/node_modules/emoji-regex/text.js +6 -0
- package/node_modules/ansi-align/node_modules/string-width/index.d.ts +29 -0
- package/node_modules/ansi-align/node_modules/string-width/index.js +47 -0
- package/node_modules/ansi-align/node_modules/string-width/license +9 -0
- package/node_modules/ansi-align/node_modules/string-width/package.json +56 -0
- package/node_modules/ansi-align/node_modules/string-width/readme.md +50 -0
- package/node_modules/ansi-align/node_modules/strip-ansi/index.d.ts +17 -0
- package/node_modules/ansi-align/node_modules/strip-ansi/index.js +4 -0
- package/node_modules/ansi-align/node_modules/strip-ansi/license +9 -0
- package/node_modules/ansi-align/node_modules/strip-ansi/package.json +54 -0
- package/node_modules/ansi-align/node_modules/strip-ansi/readme.md +46 -0
- package/node_modules/ansi-align/package.json +43 -0
- package/node_modules/ansi-regex/index.d.ts +33 -0
- package/node_modules/ansi-regex/index.js +14 -0
- package/node_modules/ansi-regex/license +9 -0
- package/node_modules/ansi-regex/package.json +61 -0
- package/node_modules/ansi-regex/readme.md +66 -0
- package/node_modules/ansi-styles/index.d.ts +236 -0
- package/node_modules/ansi-styles/index.js +223 -0
- package/node_modules/ansi-styles/license +9 -0
- package/node_modules/ansi-styles/package.json +54 -0
- package/node_modules/ansi-styles/readme.md +173 -0
- package/node_modules/boxen/index.d.ts +267 -0
- package/node_modules/boxen/index.js +376 -0
- package/node_modules/boxen/license +9 -0
- package/node_modules/boxen/package.json +69 -0
- package/node_modules/boxen/readme.md +300 -0
- package/node_modules/camelcase/index.d.ts +102 -0
- package/node_modules/camelcase/index.js +110 -0
- package/node_modules/camelcase/license +9 -0
- package/node_modules/camelcase/package.json +47 -0
- package/node_modules/camelcase/readme.md +135 -0
- package/node_modules/chalk/license +9 -0
- package/node_modules/chalk/package.json +83 -0
- package/node_modules/chalk/readme.md +297 -0
- package/node_modules/chalk/source/index.d.ts +325 -0
- package/node_modules/chalk/source/index.js +225 -0
- package/node_modules/chalk/source/utilities.js +33 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/node_modules/cli-boxes/boxes.json +82 -0
- package/node_modules/cli-boxes/index.d.ts +127 -0
- package/node_modules/cli-boxes/index.js +6 -0
- package/node_modules/cli-boxes/license +9 -0
- package/node_modules/cli-boxes/package.json +42 -0
- package/node_modules/cli-boxes/readme.md +115 -0
- package/node_modules/cli-table3/LICENSE +21 -0
- package/node_modules/cli-table3/README.md +236 -0
- package/node_modules/cli-table3/index.d.ts +96 -0
- package/node_modules/cli-table3/index.js +1 -0
- package/node_modules/cli-table3/node_modules/ansi-regex/index.d.ts +37 -0
- package/node_modules/cli-table3/node_modules/ansi-regex/index.js +10 -0
- package/node_modules/cli-table3/node_modules/ansi-regex/license +9 -0
- package/node_modules/cli-table3/node_modules/ansi-regex/package.json +55 -0
- package/node_modules/cli-table3/node_modules/ansi-regex/readme.md +78 -0
- package/node_modules/cli-table3/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
- package/node_modules/cli-table3/node_modules/emoji-regex/README.md +73 -0
- package/node_modules/cli-table3/node_modules/emoji-regex/es2015/index.js +6 -0
- package/node_modules/cli-table3/node_modules/emoji-regex/es2015/text.js +6 -0
- package/node_modules/cli-table3/node_modules/emoji-regex/index.d.ts +23 -0
- package/node_modules/cli-table3/node_modules/emoji-regex/index.js +6 -0
- package/node_modules/cli-table3/node_modules/emoji-regex/package.json +50 -0
- package/node_modules/cli-table3/node_modules/emoji-regex/text.js +6 -0
- package/node_modules/cli-table3/node_modules/string-width/index.d.ts +29 -0
- package/node_modules/cli-table3/node_modules/string-width/index.js +47 -0
- package/node_modules/cli-table3/node_modules/string-width/license +9 -0
- package/node_modules/cli-table3/node_modules/string-width/package.json +56 -0
- package/node_modules/cli-table3/node_modules/string-width/readme.md +50 -0
- package/node_modules/cli-table3/node_modules/strip-ansi/index.d.ts +17 -0
- package/node_modules/cli-table3/node_modules/strip-ansi/index.js +4 -0
- package/node_modules/cli-table3/node_modules/strip-ansi/license +9 -0
- package/node_modules/cli-table3/node_modules/strip-ansi/package.json +54 -0
- package/node_modules/cli-table3/node_modules/strip-ansi/readme.md +46 -0
- package/node_modules/cli-table3/package.json +100 -0
- package/node_modules/cli-table3/src/cell.js +409 -0
- package/node_modules/cli-table3/src/debug.js +28 -0
- package/node_modules/cli-table3/src/layout-manager.js +254 -0
- package/node_modules/cli-table3/src/table.js +106 -0
- package/node_modules/cli-table3/src/utils.js +344 -0
- package/node_modules/commander/LICENSE +22 -0
- package/node_modules/commander/Readme.md +1157 -0
- package/node_modules/commander/esm.mjs +16 -0
- package/node_modules/commander/index.js +24 -0
- package/node_modules/commander/lib/argument.js +149 -0
- package/node_modules/commander/lib/command.js +2509 -0
- package/node_modules/commander/lib/error.js +39 -0
- package/node_modules/commander/lib/help.js +520 -0
- package/node_modules/commander/lib/option.js +330 -0
- package/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/node_modules/commander/package-support.json +16 -0
- package/node_modules/commander/package.json +84 -0
- package/node_modules/commander/typings/esm.d.mts +3 -0
- package/node_modules/commander/typings/index.d.ts +969 -0
- package/node_modules/emoji-regex/LICENSE-MIT.txt +20 -0
- package/node_modules/emoji-regex/README.md +107 -0
- package/node_modules/emoji-regex/index.d.ts +3 -0
- package/node_modules/emoji-regex/index.js +4 -0
- package/node_modules/emoji-regex/index.mjs +4 -0
- package/node_modules/emoji-regex/package.json +45 -0
- package/node_modules/get-east-asian-width/index.d.ts +60 -0
- package/node_modules/get-east-asian-width/index.js +30 -0
- package/node_modules/get-east-asian-width/license +9 -0
- package/node_modules/get-east-asian-width/lookup-data.js +18 -0
- package/node_modules/get-east-asian-width/lookup.js +135 -0
- package/node_modules/get-east-asian-width/package.json +71 -0
- package/node_modules/get-east-asian-width/readme.md +65 -0
- package/node_modules/get-east-asian-width/utilities.js +24 -0
- package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
- package/node_modules/is-fullwidth-code-point/index.js +50 -0
- package/node_modules/is-fullwidth-code-point/license +9 -0
- package/node_modules/is-fullwidth-code-point/package.json +42 -0
- package/node_modules/is-fullwidth-code-point/readme.md +39 -0
- package/node_modules/isbinaryfile/LICENSE.txt +22 -0
- package/node_modules/isbinaryfile/README.md +70 -0
- package/node_modules/isbinaryfile/lib/index.d.ts +3 -0
- package/node_modules/isbinaryfile/lib/index.js +256 -0
- package/node_modules/isbinaryfile/package.json +64 -0
- package/node_modules/string-width/index.d.ts +39 -0
- package/node_modules/string-width/index.js +82 -0
- package/node_modules/string-width/license +9 -0
- package/node_modules/string-width/package.json +64 -0
- package/node_modules/string-width/readme.md +66 -0
- package/node_modules/strip-ansi/index.d.ts +15 -0
- package/node_modules/strip-ansi/index.js +19 -0
- package/node_modules/strip-ansi/license +9 -0
- package/node_modules/strip-ansi/package.json +59 -0
- package/node_modules/strip-ansi/readme.md +37 -0
- package/node_modules/type-fest/index.d.ts +178 -0
- package/node_modules/type-fest/license-cc0 +121 -0
- package/node_modules/type-fest/license-mit +9 -0
- package/node_modules/type-fest/package.json +91 -0
- package/node_modules/type-fest/readme.md +1060 -0
- package/node_modules/type-fest/source/all-union-fields.d.ts +88 -0
- package/node_modules/type-fest/source/and.d.ts +25 -0
- package/node_modules/type-fest/source/array-indices.d.ts +23 -0
- package/node_modules/type-fest/source/array-slice.d.ts +109 -0
- package/node_modules/type-fest/source/array-splice.d.ts +99 -0
- package/node_modules/type-fest/source/array-tail.d.ts +76 -0
- package/node_modules/type-fest/source/array-values.d.ts +22 -0
- package/node_modules/type-fest/source/arrayable.d.ts +29 -0
- package/node_modules/type-fest/source/async-return-type.d.ts +23 -0
- package/node_modules/type-fest/source/asyncify.d.ts +32 -0
- package/node_modules/type-fest/source/basic.d.ts +68 -0
- package/node_modules/type-fest/source/camel-case.d.ts +89 -0
- package/node_modules/type-fest/source/camel-cased-properties-deep.d.ts +97 -0
- package/node_modules/type-fest/source/camel-cased-properties.d.ts +43 -0
- package/node_modules/type-fest/source/conditional-except.d.ts +45 -0
- package/node_modules/type-fest/source/conditional-keys.d.ts +47 -0
- package/node_modules/type-fest/source/conditional-pick-deep.d.ts +118 -0
- package/node_modules/type-fest/source/conditional-pick.d.ts +44 -0
- package/node_modules/type-fest/source/conditional-simplify.d.ts +32 -0
- package/node_modules/type-fest/source/delimiter-case.d.ts +78 -0
- package/node_modules/type-fest/source/delimiter-cased-properties-deep.d.ts +106 -0
- package/node_modules/type-fest/source/delimiter-cased-properties.d.ts +46 -0
- package/node_modules/type-fest/source/distributed-omit.d.ts +89 -0
- package/node_modules/type-fest/source/distributed-pick.d.ts +85 -0
- package/node_modules/type-fest/source/empty-object.d.ts +46 -0
- package/node_modules/type-fest/source/enforce-optional.d.ts +47 -0
- package/node_modules/type-fest/source/entries.d.ts +62 -0
- package/node_modules/type-fest/source/entry.d.ts +65 -0
- package/node_modules/type-fest/source/exact.d.ts +68 -0
- package/node_modules/type-fest/source/except.d.ts +108 -0
- package/node_modules/type-fest/source/find-global-type.d.ts +64 -0
- package/node_modules/type-fest/source/fixed-length-array.d.ts +43 -0
- package/node_modules/type-fest/source/get.d.ts +219 -0
- package/node_modules/type-fest/source/global-this.d.ts +21 -0
- package/node_modules/type-fest/source/greater-than-or-equal.d.ts +22 -0
- package/node_modules/type-fest/source/greater-than.d.ts +56 -0
- package/node_modules/type-fest/source/has-optional-keys.d.ts +21 -0
- package/node_modules/type-fest/source/has-readonly-keys.d.ts +21 -0
- package/node_modules/type-fest/source/has-required-keys.d.ts +59 -0
- package/node_modules/type-fest/source/has-writable-keys.d.ts +21 -0
- package/node_modules/type-fest/source/if-any.d.ts +24 -0
- package/node_modules/type-fest/source/if-empty-object.d.ts +26 -0
- package/node_modules/type-fest/source/if-never.d.ts +24 -0
- package/node_modules/type-fest/source/if-null.d.ts +24 -0
- package/node_modules/type-fest/source/if-unknown.d.ts +24 -0
- package/node_modules/type-fest/source/includes.d.ts +22 -0
- package/node_modules/type-fest/source/int-closed-range.d.ts +35 -0
- package/node_modules/type-fest/source/int-range.d.ts +55 -0
- package/node_modules/type-fest/source/internal/array.d.ts +126 -0
- package/node_modules/type-fest/source/internal/characters.d.ts +67 -0
- package/node_modules/type-fest/source/internal/index.d.ts +8 -0
- package/node_modules/type-fest/source/internal/keys.d.ts +97 -0
- package/node_modules/type-fest/source/internal/numeric.d.ts +118 -0
- package/node_modules/type-fest/source/internal/object.d.ts +236 -0
- package/node_modules/type-fest/source/internal/string.d.ts +210 -0
- package/node_modules/type-fest/source/internal/tuple.d.ts +90 -0
- package/node_modules/type-fest/source/internal/type.d.ts +139 -0
- package/node_modules/type-fest/source/invariant-of.d.ts +76 -0
- package/node_modules/type-fest/source/is-any.d.ts +33 -0
- package/node_modules/type-fest/source/is-equal.d.ts +31 -0
- package/node_modules/type-fest/source/is-float.d.ts +41 -0
- package/node_modules/type-fest/source/is-integer.d.ts +58 -0
- package/node_modules/type-fest/source/is-literal.d.ts +296 -0
- package/node_modules/type-fest/source/is-never.d.ts +42 -0
- package/node_modules/type-fest/source/is-null.d.ts +20 -0
- package/node_modules/type-fest/source/is-tuple.d.ts +89 -0
- package/node_modules/type-fest/source/is-unknown.d.ts +52 -0
- package/node_modules/type-fest/source/iterable-element.d.ts +64 -0
- package/node_modules/type-fest/source/join.d.ts +68 -0
- package/node_modules/type-fest/source/jsonifiable.d.ts +37 -0
- package/node_modules/type-fest/source/jsonify.d.ts +122 -0
- package/node_modules/type-fest/source/kebab-case.d.ts +44 -0
- package/node_modules/type-fest/source/kebab-cased-properties-deep.d.ts +63 -0
- package/node_modules/type-fest/source/kebab-cased-properties.d.ts +40 -0
- package/node_modules/type-fest/source/keys-of-union.d.ts +42 -0
- package/node_modules/type-fest/source/last-array-element.d.ts +38 -0
- package/node_modules/type-fest/source/less-than-or-equal.d.ts +22 -0
- package/node_modules/type-fest/source/less-than.d.ts +26 -0
- package/node_modules/type-fest/source/literal-to-primitive-deep.d.ts +36 -0
- package/node_modules/type-fest/source/literal-to-primitive.d.ts +36 -0
- package/node_modules/type-fest/source/literal-union.d.ts +37 -0
- package/node_modules/type-fest/source/merge-deep.d.ts +486 -0
- package/node_modules/type-fest/source/merge-exclusive.d.ts +41 -0
- package/node_modules/type-fest/source/merge.d.ts +48 -0
- package/node_modules/type-fest/source/multidimensional-array.d.ts +44 -0
- package/node_modules/type-fest/source/multidimensional-readonly-array.d.ts +48 -0
- package/node_modules/type-fest/source/non-empty-object.d.ts +35 -0
- package/node_modules/type-fest/source/non-empty-string.d.ts +28 -0
- package/node_modules/type-fest/source/non-empty-tuple.d.ts +21 -0
- package/node_modules/type-fest/source/numeric.d.ts +222 -0
- package/node_modules/type-fest/source/observable-like.d.ts +63 -0
- package/node_modules/type-fest/source/omit-deep.d.ts +167 -0
- package/node_modules/type-fest/source/omit-index-signature.d.ts +95 -0
- package/node_modules/type-fest/source/opaque.d.ts +1 -0
- package/node_modules/type-fest/source/optional-keys-of.d.ts +39 -0
- package/node_modules/type-fest/source/or.d.ts +25 -0
- package/node_modules/type-fest/source/override-properties.d.ts +36 -0
- package/node_modules/type-fest/source/package-json.d.ts +676 -0
- package/node_modules/type-fest/source/partial-deep.d.ts +151 -0
- package/node_modules/type-fest/source/partial-on-undefined-deep.d.ts +78 -0
- package/node_modules/type-fest/source/pascal-case.d.ts +42 -0
- package/node_modules/type-fest/source/pascal-cased-properties-deep.d.ts +62 -0
- package/node_modules/type-fest/source/pascal-cased-properties.d.ts +36 -0
- package/node_modules/type-fest/source/paths.d.ts +262 -0
- package/node_modules/type-fest/source/pick-deep.d.ts +149 -0
- package/node_modules/type-fest/source/pick-index-signature.d.ts +50 -0
- package/node_modules/type-fest/source/primitive.d.ts +13 -0
- package/node_modules/type-fest/source/promisable.d.ts +25 -0
- package/node_modules/type-fest/source/readonly-deep.d.ts +81 -0
- package/node_modules/type-fest/source/readonly-keys-of.d.ts +30 -0
- package/node_modules/type-fest/source/readonly-tuple.d.ts +41 -0
- package/node_modules/type-fest/source/replace.d.ts +85 -0
- package/node_modules/type-fest/source/require-all-or-none.d.ts +51 -0
- package/node_modules/type-fest/source/require-at-least-one.d.ts +47 -0
- package/node_modules/type-fest/source/require-exactly-one.d.ts +45 -0
- package/node_modules/type-fest/source/require-one-or-none.d.ts +46 -0
- package/node_modules/type-fest/source/required-deep.d.ts +78 -0
- package/node_modules/type-fest/source/required-keys-of.d.ts +30 -0
- package/node_modules/type-fest/source/schema.d.ts +114 -0
- package/node_modules/type-fest/source/screaming-snake-case.d.ts +28 -0
- package/node_modules/type-fest/source/set-field-type.d.ts +65 -0
- package/node_modules/type-fest/source/set-non-nullable-deep.d.ts +83 -0
- package/node_modules/type-fest/source/set-non-nullable.d.ts +39 -0
- package/node_modules/type-fest/source/set-optional.d.ts +38 -0
- package/node_modules/type-fest/source/set-parameter-type.d.ts +117 -0
- package/node_modules/type-fest/source/set-readonly.d.ts +39 -0
- package/node_modules/type-fest/source/set-required-deep.d.ts +68 -0
- package/node_modules/type-fest/source/set-required.d.ts +70 -0
- package/node_modules/type-fest/source/set-return-type.d.ts +29 -0
- package/node_modules/type-fest/source/shared-union-fields-deep.d.ts +178 -0
- package/node_modules/type-fest/source/shared-union-fields.d.ts +76 -0
- package/node_modules/type-fest/source/simplify-deep.d.ts +115 -0
- package/node_modules/type-fest/source/simplify.d.ts +58 -0
- package/node_modules/type-fest/source/single-key-object.d.ts +29 -0
- package/node_modules/type-fest/source/snake-case.d.ts +45 -0
- package/node_modules/type-fest/source/snake-cased-properties-deep.d.ts +63 -0
- package/node_modules/type-fest/source/snake-cased-properties.d.ts +40 -0
- package/node_modules/type-fest/source/split.d.ts +88 -0
- package/node_modules/type-fest/source/spread.d.ts +84 -0
- package/node_modules/type-fest/source/string-key-of.d.ts +25 -0
- package/node_modules/type-fest/source/string-repeat.d.ts +47 -0
- package/node_modules/type-fest/source/string-slice.d.ts +37 -0
- package/node_modules/type-fest/source/stringified.d.ts +23 -0
- package/node_modules/type-fest/source/structured-cloneable.d.ts +92 -0
- package/node_modules/type-fest/source/subtract.d.ts +83 -0
- package/node_modules/type-fest/source/sum.d.ts +78 -0
- package/node_modules/type-fest/source/tagged-union.d.ts +51 -0
- package/node_modules/type-fest/source/tagged.d.ts +256 -0
- package/node_modules/type-fest/source/trim.d.ts +27 -0
- package/node_modules/type-fest/source/tsconfig-json.d.ts +1294 -0
- package/node_modules/type-fest/source/tuple-to-object.d.ts +42 -0
- package/node_modules/type-fest/source/tuple-to-union.d.ts +51 -0
- package/node_modules/type-fest/source/typed-array.d.ts +17 -0
- package/node_modules/type-fest/source/undefined-on-partial-deep.d.ts +80 -0
- package/node_modules/type-fest/source/union-to-intersection.d.ts +61 -0
- package/node_modules/type-fest/source/union-to-tuple.d.ts +56 -0
- package/node_modules/type-fest/source/unknown-array.d.ts +25 -0
- package/node_modules/type-fest/source/unknown-map.d.ts +24 -0
- package/node_modules/type-fest/source/unknown-record.d.ts +31 -0
- package/node_modules/type-fest/source/unknown-set.d.ts +24 -0
- package/node_modules/type-fest/source/value-of.d.ts +42 -0
- package/node_modules/type-fest/source/words.d.ts +118 -0
- package/node_modules/type-fest/source/writable-deep.d.ts +83 -0
- package/node_modules/type-fest/source/writable-keys-of.d.ts +33 -0
- package/node_modules/type-fest/source/writable.d.ts +68 -0
- package/node_modules/widest-line/index.d.ts +12 -0
- package/node_modules/widest-line/index.js +11 -0
- package/node_modules/widest-line/license +9 -0
- package/node_modules/widest-line/package.json +60 -0
- package/node_modules/widest-line/readme.md +26 -0
- package/node_modules/wrap-ansi/index.d.ts +41 -0
- package/node_modules/wrap-ansi/index.js +222 -0
- package/node_modules/wrap-ansi/license +9 -0
- package/node_modules/wrap-ansi/package.json +69 -0
- package/node_modules/wrap-ansi/readme.md +75 -0
- package/package.json +78 -0
- 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
|
+
}
|