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,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config.ts — Carga, validación y merge de la configuración del proyecto.
|
|
3
|
+
*
|
|
4
|
+
* ### Flujo de `loadConfig`
|
|
5
|
+
* ```
|
|
6
|
+
* 1. Si no existe el archivo de config → devuelve defaultConfig directamente.
|
|
7
|
+
* 2. Parsea el JSON (o YAML si está disponible js-yaml).
|
|
8
|
+
* 3. Valida tipos básicos de cada campo clave.
|
|
9
|
+
* 4. Valida rangos y valores permitidos.
|
|
10
|
+
* 5. Hace deep-merge(defaultConfig, parsed) → merged.
|
|
11
|
+
* 6. Valida relaciones cruzadas sobre merged.
|
|
12
|
+
* 7. Valida métricas: regla default presente, suma de pesos = 1.0.
|
|
13
|
+
* 8. Devuelve merged.
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ### Por qué las validaciones cruzadas van sobre `merged`
|
|
17
|
+
* Si el usuario solo cambia uno de los dos campos de una relación, el otro sigue
|
|
18
|
+
* en el valor del default. Validar `parsed` nunca vería el campo no sobreescrito.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
21
|
+
import { createRequire } from "node:module";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { defaultConfig } from "./default-config.js";
|
|
24
|
+
import { PROVIDER_REGISTRY } from "../ai/provider.js";
|
|
25
|
+
const _require = createRequire(import.meta.url);
|
|
26
|
+
function isObject(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Combina recursivamente `base` con `override`.
|
|
31
|
+
* Las propiedades de `override` sobreescriben las de `base`.
|
|
32
|
+
* Las propiedades de `base` ausentes en `override` se conservan.
|
|
33
|
+
* Los valores `null` se descartan (conservando el default) con aviso.
|
|
34
|
+
*/
|
|
35
|
+
function deepMerge(base, override) {
|
|
36
|
+
if (!isObject(base) || !isObject(override)) {
|
|
37
|
+
return override ?? base;
|
|
38
|
+
}
|
|
39
|
+
const output = { ...base };
|
|
40
|
+
for (const [key, value] of Object.entries(override)) {
|
|
41
|
+
const baseValue = output[key];
|
|
42
|
+
if (isObject(baseValue) && isObject(value)) {
|
|
43
|
+
output[key] = deepMerge(baseValue, value);
|
|
44
|
+
}
|
|
45
|
+
else if (value !== null) {
|
|
46
|
+
output[key] = value;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
process.stderr.write(`[config warn] El campo "${key}" tiene valor null en la configuración. ` +
|
|
50
|
+
`Se ignorará y se usará el valor por defecto. Para omitirlo correctamente, elimina el campo del JSON.\n`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return output;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resuelve la ruta del archivo de configuración buscando variantes JSON/YAML.
|
|
57
|
+
*/
|
|
58
|
+
function resolveConfigPath(configPath) {
|
|
59
|
+
if (configPath !== "pr-split-advisor.config.json")
|
|
60
|
+
return configPath;
|
|
61
|
+
const base = "pr-split-advisor.config";
|
|
62
|
+
for (const ext of ["json", "yaml", "yml"]) {
|
|
63
|
+
if (existsSync(path.resolve(process.cwd(), `${base}.${ext}`))) {
|
|
64
|
+
return `${base}.${ext}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return configPath;
|
|
68
|
+
}
|
|
69
|
+
function parseConfigFile(filePath, raw) {
|
|
70
|
+
const lower = filePath.toLowerCase();
|
|
71
|
+
if (lower.endsWith(".yaml") || lower.endsWith(".yml")) {
|
|
72
|
+
return parseYamlSimple(raw);
|
|
73
|
+
}
|
|
74
|
+
return JSON.parse(raw);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parser YAML minimalista. Para configs complejas (metrics anidados) se
|
|
78
|
+
* recomienda instalar js-yaml o usar el formato JSON.
|
|
79
|
+
*/
|
|
80
|
+
function parseYamlSimple(raw) {
|
|
81
|
+
try {
|
|
82
|
+
const jsYaml = _require("js-yaml");
|
|
83
|
+
return jsYaml.load(raw);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// js-yaml no disponible: fallback al parser básico
|
|
87
|
+
}
|
|
88
|
+
if (/^\s*-\s+/m.test(raw)) {
|
|
89
|
+
throw new Error('El archivo YAML contiene arrays. Instala js-yaml (npm install js-yaml) o usa formato JSON.');
|
|
90
|
+
}
|
|
91
|
+
const result = {};
|
|
92
|
+
const lines = raw.split("\n");
|
|
93
|
+
const stack = [
|
|
94
|
+
{ indent: -1, obj: result }
|
|
95
|
+
];
|
|
96
|
+
for (const line of lines) {
|
|
97
|
+
const stripped = line.replace(/#.*$/, "").trimEnd();
|
|
98
|
+
if (!stripped.trim())
|
|
99
|
+
continue;
|
|
100
|
+
const indent = stripped.length - stripped.trimStart().length;
|
|
101
|
+
const content = stripped.trim();
|
|
102
|
+
const colonIdx = content.indexOf(":");
|
|
103
|
+
if (colonIdx === -1)
|
|
104
|
+
continue;
|
|
105
|
+
const key = content.slice(0, colonIdx).trim();
|
|
106
|
+
const valueStr = content.slice(colonIdx + 1).trim();
|
|
107
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
108
|
+
stack.pop();
|
|
109
|
+
}
|
|
110
|
+
const currentObj = stack[stack.length - 1].obj;
|
|
111
|
+
if (valueStr === "" || valueStr === "|" || valueStr === ">") {
|
|
112
|
+
const nested = {};
|
|
113
|
+
currentObj[key] = nested;
|
|
114
|
+
stack.push({ indent, obj: nested });
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
currentObj[key] = parseYamlScalar(valueStr);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
function parseYamlScalar(value) {
|
|
123
|
+
if (value === "true")
|
|
124
|
+
return true;
|
|
125
|
+
if (value === "false")
|
|
126
|
+
return false;
|
|
127
|
+
if (value === "null" || value === "~")
|
|
128
|
+
return null;
|
|
129
|
+
if (/^-?\d+(\.\d+)?$/.test(value))
|
|
130
|
+
return Number(value);
|
|
131
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
132
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
133
|
+
return value.slice(1, -1);
|
|
134
|
+
}
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Carga y valida la configuración del proyecto.
|
|
139
|
+
*
|
|
140
|
+
* @param configPath - Ruta al archivo de configuración. Por defecto: `pr-split-advisor.config.json`.
|
|
141
|
+
*/
|
|
142
|
+
export function loadConfig(configPath = "pr-split-advisor.config.json") {
|
|
143
|
+
const resolvedPath = resolveConfigPath(configPath);
|
|
144
|
+
const fullPath = path.resolve(process.cwd(), resolvedPath);
|
|
145
|
+
if (!existsSync(fullPath)) {
|
|
146
|
+
return defaultConfig;
|
|
147
|
+
}
|
|
148
|
+
const raw = readFileSync(fullPath, "utf8");
|
|
149
|
+
let parsed;
|
|
150
|
+
try {
|
|
151
|
+
parsed = parseConfigFile(resolvedPath, raw);
|
|
152
|
+
}
|
|
153
|
+
catch (cause) {
|
|
154
|
+
throw new Error(`Error al parsear ${resolvedPath}: ${cause.message}`);
|
|
155
|
+
}
|
|
156
|
+
// Campos exclusivos de runtime — no deben aparecer en el archivo de config.
|
|
157
|
+
const runtimeOnlyFields = [
|
|
158
|
+
"runtimeExcludedFiles",
|
|
159
|
+
"branchNamingContext",
|
|
160
|
+
];
|
|
161
|
+
for (const field of runtimeOnlyFields) {
|
|
162
|
+
if (field in parsed) {
|
|
163
|
+
throw new Error(`Configuración inválida: "${field}" es un campo de uso interno y no puede ` +
|
|
164
|
+
`definirse en el archivo de configuración. Elimínalo de ${resolvedPath}.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Campos eliminados — rechazar con mensaje de migración
|
|
168
|
+
const removedFields = {
|
|
169
|
+
"runValidation": "Eliminado. Es responsabilidad del desarrollador no hacer push con errores.",
|
|
170
|
+
"validationCommand": "Eliminado. Es responsabilidad del desarrollador no hacer push con errores.",
|
|
171
|
+
"fileGroups": "Eliminado. El distribuidor greedy agrupa archivos automáticamente.",
|
|
172
|
+
"autoCommitMessagePrefix": "Eliminado. El mensaje se introduce manualmente al crear cada commit.",
|
|
173
|
+
"buildCommand": "Eliminado. El build y los tests son responsabilidad del desarrollador.",
|
|
174
|
+
"testCommand": "Eliminado. El build y los tests son responsabilidad del desarrollador.",
|
|
175
|
+
"runPreChecks": "Eliminado. El build y los tests son responsabilidad del desarrollador.",
|
|
176
|
+
};
|
|
177
|
+
for (const [field, reason] of Object.entries(removedFields)) {
|
|
178
|
+
if (field in parsed) {
|
|
179
|
+
throw new Error(`Configuración inválida: "${field}" ya no existe. ${reason} Elimínalo de ${resolvedPath}.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Validaciones de tipos
|
|
183
|
+
const numericFields = [
|
|
184
|
+
"largeFileThreshold", "mediumFileThreshold", "maxFilesPerCommit",
|
|
185
|
+
"maxLinesPerCommitIdeal", "maxCommitsPerBranchIdeal", "idealLinesPerPR",
|
|
186
|
+
"targetScore", "hardMaxCommitsPerBranch"
|
|
187
|
+
];
|
|
188
|
+
for (const field of numericFields) {
|
|
189
|
+
if (field in parsed && typeof parsed[field] !== "number") {
|
|
190
|
+
throw new Error(`Configuración inválida: "${field}" debe ser un número (recibido: ${JSON.stringify(parsed[field])}).`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const positiveFields = [
|
|
194
|
+
"largeFileThreshold", "mediumFileThreshold", "maxFilesPerCommit",
|
|
195
|
+
"maxLinesPerCommitIdeal", "maxCommitsPerBranchIdeal", "idealLinesPerPR",
|
|
196
|
+
"hardMaxCommitsPerBranch"
|
|
197
|
+
];
|
|
198
|
+
for (const field of positiveFields) {
|
|
199
|
+
const val = parsed[field];
|
|
200
|
+
if (val !== undefined && val <= 0) {
|
|
201
|
+
throw new Error(`Configuración inválida: "${field}" debe ser mayor que 0 (recibido: ${val}).`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (parsed.targetScore !== undefined) {
|
|
205
|
+
if (parsed.targetScore <= 0 || parsed.targetScore > 5) {
|
|
206
|
+
throw new Error(`Configuración inválida: "targetScore" debe ser mayor que 0 y no mayor que 5 (recibido: ${parsed.targetScore}).`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (parsed.maxBranchSearchRange !== undefined) {
|
|
210
|
+
if (typeof parsed.maxBranchSearchRange !== "number" ||
|
|
211
|
+
!Number.isInteger(parsed.maxBranchSearchRange) ||
|
|
212
|
+
parsed.maxBranchSearchRange < 1) {
|
|
213
|
+
throw new Error(`Configuración inválida: "maxBranchSearchRange" debe ser un entero mayor o igual a 1 (recibido: ${parsed.maxBranchSearchRange}).`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (parsed.maxFilesPerCommit !== undefined && !Number.isInteger(parsed.maxFilesPerCommit)) {
|
|
217
|
+
throw new Error(`Configuración inválida: "maxFilesPerCommit" debe ser un número entero.`);
|
|
218
|
+
}
|
|
219
|
+
if (parsed.hardMaxCommitsPerBranch !== undefined && !Number.isInteger(parsed.hardMaxCommitsPerBranch)) {
|
|
220
|
+
throw new Error(`Configuración inválida: "hardMaxCommitsPerBranch" debe ser un número entero.`);
|
|
221
|
+
}
|
|
222
|
+
if (parsed.hardMaxCommitsPerBranch !== undefined && parsed.hardMaxCommitsPerBranch < 1) {
|
|
223
|
+
throw new Error(`Configuración inválida: "hardMaxCommitsPerBranch" debe ser >= 1 (recibido: ${parsed.hardMaxCommitsPerBranch}).`);
|
|
224
|
+
}
|
|
225
|
+
const merged = deepMerge(defaultConfig, parsed);
|
|
226
|
+
if (merged.hardMaxCommitsPerBranch <
|
|
227
|
+
merged.maxCommitsPerBranchIdeal) {
|
|
228
|
+
throw new Error(`Configuración inválida: "hardMaxCommitsPerBranch" (${merged.hardMaxCommitsPerBranch}) debe ser mayor o igual que "maxCommitsPerBranchIdeal" (${merged.maxCommitsPerBranchIdeal}).`);
|
|
229
|
+
}
|
|
230
|
+
if (merged.mediumFileThreshold >=
|
|
231
|
+
merged.largeFileThreshold) {
|
|
232
|
+
throw new Error(`Configuración inválida: "mediumFileThreshold" (${merged.mediumFileThreshold}) debe ser menor que "largeFileThreshold" (${merged.largeFileThreshold}).`);
|
|
233
|
+
}
|
|
234
|
+
if ("excludeLockfiles" in parsed && typeof parsed.excludeLockfiles !== "boolean") {
|
|
235
|
+
throw new Error(`Configuración inválida: "excludeLockfiles" debe ser true o false.`);
|
|
236
|
+
}
|
|
237
|
+
if ("exportJson" in parsed && typeof parsed.exportJson !== "boolean") {
|
|
238
|
+
throw new Error(`Configuración inválida: "exportJson" debe ser true o false.`);
|
|
239
|
+
}
|
|
240
|
+
const booleanFields = ["verbose"];
|
|
241
|
+
for (const field of booleanFields) {
|
|
242
|
+
if (field in parsed && typeof parsed[field] !== "boolean") {
|
|
243
|
+
throw new Error(`Configuración inválida: "${field}" debe ser true o false (recibido: ${JSON.stringify(parsed[field])}).`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const stringFields = ["jsonOutputFile", "historyFile"];
|
|
247
|
+
for (const field of stringFields) {
|
|
248
|
+
if (field in parsed && typeof parsed[field] !== "string") {
|
|
249
|
+
throw new Error(`Configuración inválida: "${field}" debe ser un string (recibido: ${JSON.stringify(parsed[field])}).`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if ("baseBranch" in parsed) {
|
|
253
|
+
if (typeof parsed.baseBranch !== "string" || parsed.baseBranch.trim() === "") {
|
|
254
|
+
throw new Error(`Configuración inválida: "baseBranch" no puede ser un string vacío.`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Validar métricas sobre el merge final
|
|
258
|
+
if (isObject(merged.metrics)) {
|
|
259
|
+
const metricsObj = merged.metrics;
|
|
260
|
+
let totalWeight = 0;
|
|
261
|
+
for (const [code, def] of Object.entries(metricsObj)) {
|
|
262
|
+
if (!isObject(def))
|
|
263
|
+
continue;
|
|
264
|
+
const metricDef = def;
|
|
265
|
+
if (Array.isArray(metricDef["scoring"])) {
|
|
266
|
+
const hasDefault = metricDef["scoring"].some((rule) => isObject(rule) && rule["default"] === true);
|
|
267
|
+
if (!hasDefault) {
|
|
268
|
+
throw new Error(`Configuración inválida: la métrica "${code}" debe tener al menos una regla con { default: true }.`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (typeof metricDef["weight"] === "number") {
|
|
272
|
+
totalWeight += metricDef["weight"];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const metricCount = Object.keys(metricsObj).length;
|
|
276
|
+
if (metricCount > 0 && metricCount !== 4) {
|
|
277
|
+
throw new Error(`Configuración inválida: se requieren exactamente 4 métricas (recibido: ${metricCount}).`);
|
|
278
|
+
}
|
|
279
|
+
if (metricCount > 0 && Math.abs(totalWeight - 1.0) > 0.01) {
|
|
280
|
+
throw new Error(`Configuración inválida: la suma de los pesos de las métricas debe ser 1.0 (suma actual: ${totalWeight.toFixed(4)}).`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// ── Validar sección ai (opcional) ─────────────────────────────────────────
|
|
284
|
+
if ("ai" in parsed && isObject(parsed["ai"])) {
|
|
285
|
+
const ai = parsed["ai"];
|
|
286
|
+
if ("provider" in ai && !PROVIDER_REGISTRY.has(ai["provider"])) {
|
|
287
|
+
const knownProviders = [...PROVIDER_REGISTRY.keys()].join(", ");
|
|
288
|
+
throw new Error(`Configuración inválida: "ai.provider" debe ser uno de: ${knownProviders} (recibido: ${JSON.stringify(ai["provider"])}).`);
|
|
289
|
+
}
|
|
290
|
+
if ("model" in ai && (typeof ai["model"] !== "string" || !ai["model"].trim())) {
|
|
291
|
+
throw new Error(`Configuración inválida: "ai.model" debe ser un string no vacío.`);
|
|
292
|
+
}
|
|
293
|
+
if ("timeoutMs" in ai) {
|
|
294
|
+
const t = ai["timeoutMs"];
|
|
295
|
+
if (typeof t !== "number" || t < 1000) {
|
|
296
|
+
throw new Error(`Configuración inválida: "ai.timeoutMs" debe ser un número >= 1000 (recibido: ${JSON.stringify(t)}).`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if ("maxTokens" in ai) {
|
|
300
|
+
const m = ai["maxTokens"];
|
|
301
|
+
if (typeof m !== "number" || m < 64 || m > 32768) {
|
|
302
|
+
throw new Error(`Configuración inválida: "ai.maxTokens" debe ser un número entre 64 y 32768 (recibido: ${JSON.stringify(m)}).`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (isObject(ai["features"])) {
|
|
306
|
+
const f = ai["features"];
|
|
307
|
+
for (const flag of ["commitMessages", "branchDescriptions"]) {
|
|
308
|
+
if (flag in f && typeof f[flag] !== "boolean") {
|
|
309
|
+
throw new Error(`Configuración inválida: "ai.features.${flag}" debe ser true o false (recibido: ${JSON.stringify(f[flag])}).`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return merged;
|
|
315
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* default-config.ts — Valores por defecto de la configuración.
|
|
3
|
+
*
|
|
4
|
+
* `loadConfig` hace un deep-merge de estos valores con lo que el usuario
|
|
5
|
+
* especifica en `pr-split-advisor.config.json`. Cualquier campo omitido
|
|
6
|
+
* toma su valor de aquí.
|
|
7
|
+
*
|
|
8
|
+
* ### Guía de ajuste rápido
|
|
9
|
+
* Si el plan divide demasiado (muchas ramas) o demasiado poco:
|
|
10
|
+
* - `idealLinesPerPR` — cuántas líneas caben en un PR
|
|
11
|
+
* - `largeFileThreshold` — a partir de cuántas líneas un archivo es «grande»
|
|
12
|
+
* - `maxCommitsPerBranchIdeal` — cuántos commits se esperan por rama
|
|
13
|
+
* - `targetScore` — score mínimo aceptable
|
|
14
|
+
*
|
|
15
|
+
* ### Fórmula del score
|
|
16
|
+
* ```
|
|
17
|
+
* score = Σ(points(métrica_i) × weight(métrica_i))
|
|
18
|
+
* ```
|
|
19
|
+
* Un score de 5 indica un PR ideal: pequeño, atómico y fácil de revisar.
|
|
20
|
+
*/
|
|
21
|
+
export const defaultConfig = {
|
|
22
|
+
// ── Git ──────────────────────────────────────────────────────────────────
|
|
23
|
+
baseBranch: "master",
|
|
24
|
+
// ── Archivos ─────────────────────────────────────────────────────────────
|
|
25
|
+
excludeLockfiles: true,
|
|
26
|
+
/**
|
|
27
|
+
* Número de líneas a partir del cual un archivo se considera «grande».
|
|
28
|
+
* El archivo se convierte en un bloque indivisible con 1 commit propio.
|
|
29
|
+
*
|
|
30
|
+
* Guía:
|
|
31
|
+
* - Proyectos nuevos / pequeños : 200–300
|
|
32
|
+
* - Proyectos medianos : 400 (default)
|
|
33
|
+
* - Proyectos legacy / grandes : 600–800
|
|
34
|
+
*/
|
|
35
|
+
largeFileThreshold: 400,
|
|
36
|
+
/**
|
|
37
|
+
* Umbral de tamaño «mediano» (líneas). Los archivos medianos o grandes
|
|
38
|
+
* se aíslan en su propio commit.
|
|
39
|
+
*/
|
|
40
|
+
mediumFileThreshold: 180,
|
|
41
|
+
// ── Commits y ramas ──────────────────────────────────────────────────────
|
|
42
|
+
/**
|
|
43
|
+
* Máximo de archivos por commit.
|
|
44
|
+
* El motor calcula commits = max(ceil(archivos/maxFilesPerCommit), ceil(líneas/maxLinesPerCommitIdeal)).
|
|
45
|
+
*/
|
|
46
|
+
maxFilesPerCommit: 8,
|
|
47
|
+
/** Número ideal de líneas por commit. */
|
|
48
|
+
maxLinesPerCommitIdeal: 120,
|
|
49
|
+
/**
|
|
50
|
+
* Número ideal de commits por rama. Si una rama necesita más, la razón se
|
|
51
|
+
* marca como "Requiere más commits de lo ideal".
|
|
52
|
+
*/
|
|
53
|
+
maxCommitsPerBranchIdeal: 2,
|
|
54
|
+
/**
|
|
55
|
+
* Límite DURO de commits por rama. Si una rama supera este valor el plan
|
|
56
|
+
* completo se descarta y el motor prueba con más ramas.
|
|
57
|
+
* Debe ser siempre mayor que `maxCommitsPerBranchIdeal`.
|
|
58
|
+
*/
|
|
59
|
+
hardMaxCommitsPerBranch: 4,
|
|
60
|
+
/**
|
|
61
|
+
* Total de líneas que debe tener idealmente un PR.
|
|
62
|
+
* Es el parámetro más directo para controlar cuántas ramas genera el plan:
|
|
63
|
+
* `minBranches = ceil(totalLíneas / idealLinesPerPR)`
|
|
64
|
+
*
|
|
65
|
+
* Guía por tamaño de equipo:
|
|
66
|
+
* - Equipo pequeño (1–3 devs) : 80–150
|
|
67
|
+
* - Equipo mediano (4–8 devs) : 150–250
|
|
68
|
+
* - Equipo grande (>8 devs) : 250–400
|
|
69
|
+
*
|
|
70
|
+
* El default agresivo de 99 líneas maximiza la división para proyectos
|
|
71
|
+
* nuevos; auméntalo si el plan genera demasiadas ramas.
|
|
72
|
+
*/
|
|
73
|
+
idealLinesPerPR: 99,
|
|
74
|
+
/**
|
|
75
|
+
* Score mínimo para que el plan sea válido.
|
|
76
|
+
* - < 3.0 : casi cualquier distribución es válida
|
|
77
|
+
* - 4.0 : balance razonable (default)
|
|
78
|
+
* - 5.0 : solo PRs perfectamente pequeños
|
|
79
|
+
*/
|
|
80
|
+
targetScore: 4,
|
|
81
|
+
/**
|
|
82
|
+
* Cuántos valores de `branchCount` explorar por encima del mínimo teórico.
|
|
83
|
+
* El motor se detiene en la primera iteración que produce un plan válido.
|
|
84
|
+
*/
|
|
85
|
+
maxBranchSearchRange: 8,
|
|
86
|
+
// ── Exportación ──────────────────────────────────────────────────────────
|
|
87
|
+
exportJson: true,
|
|
88
|
+
jsonOutputFile: "pr-split-plan.json",
|
|
89
|
+
historyFile: ".pr-split-history.json",
|
|
90
|
+
// ── Runtime (campos internos — no configurar en JSON) ────────────────────
|
|
91
|
+
runtimeExcludedFiles: [],
|
|
92
|
+
branchNamingContext: undefined,
|
|
93
|
+
verbose: false,
|
|
94
|
+
// ── Métricas de scoring ───────────────────────────────────────────────────
|
|
95
|
+
//
|
|
96
|
+
// Orden posicional REQUERIDO (no cambiar el orden de las claves):
|
|
97
|
+
// posición 0 → commitCount (M1.3)
|
|
98
|
+
// posición 1 → filesPerCommit (M1.4)
|
|
99
|
+
// posición 2 → avgLinesPerCommit (M1.5)
|
|
100
|
+
// posición 3 → totalLinesChanged (M3.2)
|
|
101
|
+
//
|
|
102
|
+
metrics: {
|
|
103
|
+
/**
|
|
104
|
+
* M1.3 — Cantidad de commits en el PR.
|
|
105
|
+
* Peso: 0.20 | Contribución máxima: 1.00 punto
|
|
106
|
+
*/
|
|
107
|
+
"M1.3": {
|
|
108
|
+
label: "Cantidad de commits en PR",
|
|
109
|
+
weight: 0.2,
|
|
110
|
+
scoring: [
|
|
111
|
+
{ lte: 2, points: 5 },
|
|
112
|
+
{ lte: 4, points: 4 },
|
|
113
|
+
{ eq: 5, points: 3 },
|
|
114
|
+
{ lte: 7, points: 2 },
|
|
115
|
+
{ default: true, points: 1 }
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
/**
|
|
119
|
+
* M1.4 — Promedio de archivos por commit (excluye chore/style/docs).
|
|
120
|
+
* Peso: 0.25 | Contribución máxima: 1.25 puntos
|
|
121
|
+
*/
|
|
122
|
+
"M1.4": {
|
|
123
|
+
label: "Promedio de archivos por commit en PR",
|
|
124
|
+
weight: 0.25,
|
|
125
|
+
scoring: [
|
|
126
|
+
{ lt: 15, points: 5 },
|
|
127
|
+
{ lt: 20, points: 4 },
|
|
128
|
+
{ lt: 30, points: 3 },
|
|
129
|
+
{ lt: 40, points: 2 },
|
|
130
|
+
{ default: true, points: 1 }
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* M1.5 — Promedio de líneas por commit (excluye chore/style/docs).
|
|
135
|
+
* Peso: 0.25 | Contribución máxima: 1.25 puntos
|
|
136
|
+
*/
|
|
137
|
+
"M1.5": {
|
|
138
|
+
label: "Promedio de líneas por commit en PR",
|
|
139
|
+
weight: 0.25,
|
|
140
|
+
scoring: [
|
|
141
|
+
{ lt: 100, points: 5 },
|
|
142
|
+
{ lt: 150, points: 4 },
|
|
143
|
+
{ lt: 250, points: 3 },
|
|
144
|
+
{ lt: 350, points: 2 },
|
|
145
|
+
{ default: true, points: 1 }
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
/**
|
|
149
|
+
* M3.2 — Total de líneas cambiadas en el PR (incluye todos los commits).
|
|
150
|
+
* Peso: 0.30 | Contribución máxima: 1.50 puntos
|
|
151
|
+
*/
|
|
152
|
+
"M3.2": {
|
|
153
|
+
label: "Líneas modificadas en PR",
|
|
154
|
+
weight: 0.3,
|
|
155
|
+
scoring: [
|
|
156
|
+
{ lte: 50, points: 5 },
|
|
157
|
+
{ lte: 300, points: 4 },
|
|
158
|
+
{ lte: 500, points: 3 },
|
|
159
|
+
{ lte: 1200, points: 2 },
|
|
160
|
+
{ default: true, points: 1 }
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
// ── Reglas de recomendación ───────────────────────────────────────────────
|
|
165
|
+
recommendationRules: [
|
|
166
|
+
{
|
|
167
|
+
when: { eq: 5 },
|
|
168
|
+
message: "Score óptimo — listo para revisión"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
when: { gte: 4, lt: 5 },
|
|
172
|
+
message: "Score aceptable — revisar detalles menores"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
when: { gte: 3, lt: 4 },
|
|
176
|
+
message: "Score moderado — considera dividir más"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
when: { gte: 1, lt: 3 },
|
|
180
|
+
message: "Score bajo — se recomienda refactorizar el plan"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
when: { gte: 0 },
|
|
184
|
+
message: "Sin datos suficientes para recomendar"
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
// ── Capa de IA ───────────────────────────────────────────────────────────────────────────────
|
|
188
|
+
//
|
|
189
|
+
// Configurar vía `pr-split-advisor config` o editando directamente el JSON.
|
|
190
|
+
//
|
|
191
|
+
// Opciones de `provider` soportadas: "groq" | "github" | "copilot"
|
|
192
|
+
// groq → requiere GROQ_API_KEY (console.groq.com, free tier)
|
|
193
|
+
// github → requiere GITHUB_TOKEN (github.com/marketplace/models, free)
|
|
194
|
+
// copilot → sin token — usa automáticamente credenciales de gh CLI
|
|
195
|
+
//
|
|
196
|
+
// API key: preferir `apiKeyEnvVar` (ej: GROQ_API_KEY) antes que literal `apiKey`.
|
|
197
|
+
// Configurar con el asistente: pr-split-advisor config
|
|
198
|
+
//
|
|
199
|
+
ai: {
|
|
200
|
+
enabled: false,
|
|
201
|
+
provider: "groq",
|
|
202
|
+
model: "llama-3.3-70b-versatile",
|
|
203
|
+
apiKey: "",
|
|
204
|
+
apiKeyEnvVar: "GROQ_API_KEY",
|
|
205
|
+
features: {
|
|
206
|
+
commitMessages: true,
|
|
207
|
+
branchDescriptions: true,
|
|
208
|
+
planRebalance: false,
|
|
209
|
+
},
|
|
210
|
+
timeoutMs: 15000,
|
|
211
|
+
maxTokens: 1024,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Campos de `defaultConfig` exclusivamente de uso interno en tiempo de ejecución.
|
|
216
|
+
* No deben aparecer en el archivo `pr-split-advisor.config.json` del proyecto.
|
|
217
|
+
*
|
|
218
|
+
* `postinstall.cjs` importa esta constante para eliminarlos del JSON generado.
|
|
219
|
+
*/
|
|
220
|
+
export const RUNTIME_ONLY_FIELDS = [
|
|
221
|
+
"runtimeExcludedFiles",
|
|
222
|
+
"branchNamingContext",
|
|
223
|
+
];
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* blocks.ts — Agrupación de archivos en bloques de trabajo.
|
|
3
|
+
*
|
|
4
|
+
* Un **bloque** es la unidad mínima de distribución: un conjunto de archivos
|
|
5
|
+
* relacionados que `distributeBlocks` asigna íntegro a una rama.
|
|
6
|
+
*
|
|
7
|
+
* ### Reglas de agrupación
|
|
8
|
+
* 1. **Archivos grandes** (`lines > largeFileThreshold`): bloque propio e indivisible.
|
|
9
|
+
* No pueden compartir commit con otros archivos.
|
|
10
|
+
* 2. **Demás archivos**: se agrupan por la clave `directorio::nombreBase`.
|
|
11
|
+
* `getGroupKey` elimina la extensión y el sufijo `test`/`spec`, por eso
|
|
12
|
+
* `auth.service.ts` y `auth.service.spec.ts` quedan en el mismo bloque.
|
|
13
|
+
* 3. **Archivos de test**: siempre van con su fuente en el mismo bloque e indivisibles.
|
|
14
|
+
* - Si el bloque mezcla test + fuente → `divisible: false` (mismo commit obligatorio).
|
|
15
|
+
* - Si la fuente es un archivo grande (bloque `large::`): los tests se fusionan en
|
|
16
|
+
* ese bloque para garantizar que nunca queden en una rama distinta.
|
|
17
|
+
* Esto evita que SonarQube detecte cobertura negativa en «new code» al analizar
|
|
18
|
+
* un PR sin los tests correspondientes.
|
|
19
|
+
*
|
|
20
|
+
* ### Cálculo de `depScore`
|
|
21
|
+
* Se cuenta el número de aristas de dependencia que tocan cada archivo,
|
|
22
|
+
* usando un mapa de frecuencias construido en O(n+m) (n=archivos, m=aristas).
|
|
23
|
+
* Un bloque con `depScore` alto está muy acoplado con otros bloques; el algoritmo
|
|
24
|
+
* greedy de distribución lo tiene en cuenta para penalizar separarlos.
|
|
25
|
+
*/
|
|
26
|
+
import { basename, dirname } from "node:path";
|
|
27
|
+
/**
|
|
28
|
+
* Genera la clave de agrupación para un archivo.
|
|
29
|
+
*
|
|
30
|
+
* Combina el directorio padre y el nombre base sin extensión ni sufijo
|
|
31
|
+
* `test`/`spec`. Esto asegura que un archivo y su test unitario queden en
|
|
32
|
+
* el mismo bloque, lo que mantiene coherencia en los commits.
|
|
33
|
+
*
|
|
34
|
+
* Ejemplo:
|
|
35
|
+
* `src/auth/auth.service.ts` → `src/auth::auth.service`
|
|
36
|
+
* `src/auth/auth.service.spec.ts` → `src/auth::auth.service`
|
|
37
|
+
*
|
|
38
|
+
* @param file - Ruta relativa del archivo.
|
|
39
|
+
*/
|
|
40
|
+
function getGroupKey(file) {
|
|
41
|
+
const dir = dirname(file);
|
|
42
|
+
const base = basename(file).replace(/\.[^.]+$/, "").replace(/\.?(test|spec)$/, "");
|
|
43
|
+
return `${dir === "." ? "root" : dir}::${base}`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Determina si un archivo es un test/spec basándose en su nombre o ruta.
|
|
47
|
+
*
|
|
48
|
+
* Patrones reconocidos:
|
|
49
|
+
* - Extensión doble: `*.test.ts`, `*.spec.ts`, `*.test.js`, `*.spec.js`, etc.
|
|
50
|
+
* - Directorios convencionales: `__tests__/`, `tests/`, `test/`, `spec/`, `specs/`.
|
|
51
|
+
* - Python: `test_foo.py`, `foo_test.py`.
|
|
52
|
+
*
|
|
53
|
+
* @param file - Ruta relativa del archivo.
|
|
54
|
+
*/
|
|
55
|
+
function isTestFile(file) {
|
|
56
|
+
const lower = file.toLowerCase();
|
|
57
|
+
if (/\.(test|spec)\.[a-z]+$/.test(lower))
|
|
58
|
+
return true;
|
|
59
|
+
if (/(?:^|\/)(__tests__|tests|test|spec|specs)\//.test(lower))
|
|
60
|
+
return true;
|
|
61
|
+
if (/(?:^|\/)test_[^/]+\.py$/.test(lower))
|
|
62
|
+
return true;
|
|
63
|
+
if (/(?:^|\/)[^/]+_test\.py$/.test(lower))
|
|
64
|
+
return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Agrupa los {@link FileStat}s en bloques de trabajo coherentes.
|
|
69
|
+
*
|
|
70
|
+
* - Archivos que superan `config.largeFileThreshold` se convierten en bloques
|
|
71
|
+
* individuales e indivisibles.
|
|
72
|
+
* - El resto se agrupa por directorio + nombre base (sin extensión ni sufijo test/spec).
|
|
73
|
+
* - Los bloques que contienen archivos de test son siempre **indivisibles** para
|
|
74
|
+
* garantizar que test y fuente se commiteen juntos (requerido por SonarQube new coverage).
|
|
75
|
+
* - Si la fuente es grande (bloque `large::`), los tests se fusionan en ese bloque
|
|
76
|
+
* en lugar de quedar en un bloque separado que podría acabar en otra rama.
|
|
77
|
+
* - La propiedad `depScore` de cada bloque refleja cuántas aristas de dependencia
|
|
78
|
+
* tocan alguno de sus archivos (calculado en O(n+m) con mapa de frecuencias).
|
|
79
|
+
*
|
|
80
|
+
* @param fileStats - Lista de estadísticas de archivos cambiados.
|
|
81
|
+
* @param config - Configuración activa del proyecto.
|
|
82
|
+
* @param deps - Aristas de dependencia calculadas por {@link buildDependencyEdges}.
|
|
83
|
+
* @returns Array de bloques ordenados para su distribución en ramas.
|
|
84
|
+
*/
|
|
85
|
+
export function buildBlocks(fileStats, config, deps) {
|
|
86
|
+
const groups = new Map();
|
|
87
|
+
const blocks = [];
|
|
88
|
+
// Mapa groupKey → bloque large para fusionar tests con su fuente grande.
|
|
89
|
+
const largeBlockByGroupKey = new Map();
|
|
90
|
+
for (const fs of fileStats) {
|
|
91
|
+
if (fs.lines > config.largeFileThreshold) {
|
|
92
|
+
const block = {
|
|
93
|
+
id: `large::${fs.path}`,
|
|
94
|
+
priority: fs.priority,
|
|
95
|
+
divisible: false,
|
|
96
|
+
lines: fs.lines,
|
|
97
|
+
files: [fs.path],
|
|
98
|
+
depScore: 0
|
|
99
|
+
};
|
|
100
|
+
blocks.push(block);
|
|
101
|
+
largeBlockByGroupKey.set(getGroupKey(fs.path), block);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const key = getGroupKey(fs.path);
|
|
105
|
+
if (!groups.has(key))
|
|
106
|
+
groups.set(key, []);
|
|
107
|
+
groups.get(key).push(fs);
|
|
108
|
+
}
|
|
109
|
+
for (const [key, files] of groups.entries()) {
|
|
110
|
+
const hasTest = files.some((f) => isTestFile(f.path));
|
|
111
|
+
// Si el grupo contiene solo tests y existe un bloque large con la misma clave,
|
|
112
|
+
// fusionar los tests en ese bloque para que no queden en ramas separadas.
|
|
113
|
+
if (hasTest && files.every((f) => isTestFile(f.path))) {
|
|
114
|
+
const largeBlock = largeBlockByGroupKey.get(key);
|
|
115
|
+
if (largeBlock) {
|
|
116
|
+
for (const f of files) {
|
|
117
|
+
largeBlock.files.push(f.path);
|
|
118
|
+
largeBlock.lines += f.lines;
|
|
119
|
+
largeBlock.priority = Math.min(largeBlock.priority, f.priority);
|
|
120
|
+
}
|
|
121
|
+
continue; // El test queda absorbido en el bloque large; no crear bloque separado.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
blocks.push({
|
|
125
|
+
id: key,
|
|
126
|
+
priority: Math.min(...files.map((f) => f.priority)),
|
|
127
|
+
// Si el bloque contiene al menos un test, marcarlo como indivisible para
|
|
128
|
+
// garantizar que test y fuente se commiteen en el mismo commit.
|
|
129
|
+
divisible: !hasTest,
|
|
130
|
+
lines: files.reduce((sum, f) => sum + f.lines, 0),
|
|
131
|
+
files: files.map((f) => f.path),
|
|
132
|
+
depScore: 0
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// Calcular depScore con un mapa de frecuencias: O(n+m) en lugar de O(n×m).
|
|
136
|
+
const fileEdgeCount = new Map();
|
|
137
|
+
for (const { from, to } of deps) {
|
|
138
|
+
fileEdgeCount.set(from, (fileEdgeCount.get(from) ?? 0) + 1);
|
|
139
|
+
fileEdgeCount.set(to, (fileEdgeCount.get(to) ?? 0) + 1);
|
|
140
|
+
}
|
|
141
|
+
for (const block of blocks) {
|
|
142
|
+
block.depScore = block.files.reduce((sum, file) => sum + (fileEdgeCount.get(file) ?? 0), 0);
|
|
143
|
+
}
|
|
144
|
+
return blocks;
|
|
145
|
+
}
|