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,726 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* report.ts — Generación de reportes HTML profesionales y exportación JSON del plan.
|
|
3
|
+
*
|
|
4
|
+
* Expone tres funciones públicas:
|
|
5
|
+
*
|
|
6
|
+
* - **`renderHtmlReport`**: genera el HTML completo del reporte como string.
|
|
7
|
+
* Se puede usar en tests o para previsualizar sin escribir en disco.
|
|
8
|
+
*
|
|
9
|
+
* - **`writeHtmlReport`**: llama a `renderHtmlReport` y escribe el resultado
|
|
10
|
+
* en el archivo indicado.
|
|
11
|
+
*
|
|
12
|
+
* - **`exportJson`**: serializa el plan de ramas a un archivo JSON estructurado
|
|
13
|
+
* para consumo por otras herramientas (CI/CD, dashboards, etc.).
|
|
14
|
+
*
|
|
15
|
+
* ### Estructura del HTML
|
|
16
|
+
* El reporte sigue una arquitectura de componentes dentro de funciones:
|
|
17
|
+
* - `buildReportStyles()` — el bloque `<style>` completo (CSS custom properties + layout)
|
|
18
|
+
* - `buildScoreGaugeSvg(score)` — SVG de anillo circular para el score de una rama
|
|
19
|
+
* - `buildMetricsBreakdownTable(plan, config)` — tabla detallada de métricas con aporte ponderado
|
|
20
|
+
* - `buildFilesBarsRow(f, maxLines)` — fila de archivo con barras visuales de adiciones/eliminaciones
|
|
21
|
+
* - `buildCommitTimelineHtml(plan)` — línea de tiempo visual de commits de una rama
|
|
22
|
+
* - `buildPlanCardHtml(plan, config)` — el `<article>` de cada rama del plan
|
|
23
|
+
* - `buildTocHtml(plans)` — tabla de contenidos anclada con navegación
|
|
24
|
+
* - `renderHtmlReport(input)` — el documento HTML completo ensamblado
|
|
25
|
+
*
|
|
26
|
+
* ### Seguridad
|
|
27
|
+
* Todos los valores de usuario (nombres de rama, rutas de archivos, mensajes de commit)
|
|
28
|
+
* pasan por `esc()` antes de insertarse en el HTML para prevenir XSS.
|
|
29
|
+
*/
|
|
30
|
+
import { writeFileSync } from "node:fs";
|
|
31
|
+
import { APP_REPORT_TITLE } from "../shared/constants.js";
|
|
32
|
+
import { changeTypeLabel } from "../shared/utils.js";
|
|
33
|
+
import { reportStyles } from "./report-styles.generated.js";
|
|
34
|
+
/**
|
|
35
|
+
* Escapa caracteres HTML especiales (`&`, `<`, `>`) para prevenir XSS.
|
|
36
|
+
* Todos los valores de cadena provenientes del usuario o del análisis
|
|
37
|
+
* deben pasar por esta función antes de insertarse en el HTML.
|
|
38
|
+
*
|
|
39
|
+
* @param value - Texto a escapar.
|
|
40
|
+
*/
|
|
41
|
+
function esc(value) {
|
|
42
|
+
return value
|
|
43
|
+
.replaceAll("&", "&")
|
|
44
|
+
.replaceAll("<", "<")
|
|
45
|
+
.replaceAll(">", ">")
|
|
46
|
+
.replaceAll('"', """)
|
|
47
|
+
.replaceAll("'", "'");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Devuelve la etiqueta de estado de un plan según su score.
|
|
51
|
+
*
|
|
52
|
+
* - ÓPTIMO : score ≥ targetScore
|
|
53
|
+
* - ACEPTABLE: score ≥ warnThreshold y score < targetScore
|
|
54
|
+
* - RIESGO : score < warnThreshold
|
|
55
|
+
*
|
|
56
|
+
* `warnThreshold` se calcula dinámicamente:
|
|
57
|
+
* - targetScore > 4 → warnThreshold = 4.0
|
|
58
|
+
* - targetScore ≤ 4 → warnThreshold = max(0, targetScore − 1)
|
|
59
|
+
*
|
|
60
|
+
* Con el default (targetScore = 4): ACEPTABLE = [3.0, 4.0), RIESGO = < 3.0.
|
|
61
|
+
*/
|
|
62
|
+
function scoreLabel(score, targetScore) {
|
|
63
|
+
if (score >= targetScore)
|
|
64
|
+
return "ÓPTIMO";
|
|
65
|
+
const warnThreshold = targetScore > 4 ? 4 : Math.max(0, targetScore - 1);
|
|
66
|
+
if (score >= warnThreshold)
|
|
67
|
+
return "ACEPTABLE";
|
|
68
|
+
return "RIESGO";
|
|
69
|
+
}
|
|
70
|
+
/** Devuelve `"SIN ALERTAS"` si la razón es `"OK"`, `"CON ALERTAS"` en caso contrario. */
|
|
71
|
+
function alertLabel(reason) {
|
|
72
|
+
return reason === "OK" ? "SIN ALERTAS" : "CON ALERTAS";
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Genera un anillo SVG circular que representa visualmente el score de 0 a 5.
|
|
76
|
+
*
|
|
77
|
+
* @param score - Score a representar (0–5).
|
|
78
|
+
* @param targetScore - Score objetivo usado para determinar el color del anillo.
|
|
79
|
+
*/
|
|
80
|
+
function buildScoreGaugeSvg(score, targetScore) {
|
|
81
|
+
const r = 36;
|
|
82
|
+
const circ = 2 * Math.PI * r;
|
|
83
|
+
const pct = Math.min(score / 5, 1);
|
|
84
|
+
const dash = (pct * circ).toFixed(1);
|
|
85
|
+
const gap = (circ - parseFloat(dash)).toFixed(1);
|
|
86
|
+
const warnThreshold = targetScore > 4 ? 4 : Math.max(0, targetScore - 1);
|
|
87
|
+
const color = score >= targetScore ? "#16a34a" : score >= warnThreshold ? "#d97706" : "#dc2626";
|
|
88
|
+
return `
|
|
89
|
+
<svg width="100" height="100" viewBox="0 0 100 100" class="gauge-svg" aria-label="Score ${score.toFixed(2)} de 5">
|
|
90
|
+
<circle cx="50" cy="50" r="${r}" fill="none" stroke="#e2e8f0" stroke-width="10"/>
|
|
91
|
+
<circle cx="50" cy="50" r="${r}" fill="none" stroke="${color}" stroke-width="10"
|
|
92
|
+
stroke-dasharray="${dash} ${gap}"
|
|
93
|
+
stroke-linecap="round" transform="rotate(-90 50 50)" style="transition:stroke-dasharray 0.6s ease"/>
|
|
94
|
+
<text x="50" y="46" text-anchor="middle" font-size="14" font-weight="800" fill="${color}">${score.toFixed(2)}</text>
|
|
95
|
+
<text x="50" y="60" text-anchor="middle" font-size="9" fill="#64748b">/ 5.00</text>
|
|
96
|
+
</svg>`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Genera la tabla de desglose detallado de métricas para una rama.
|
|
100
|
+
* Muestra: código, nombre, valor medido, puntos, peso y aporte ponderado.
|
|
101
|
+
*
|
|
102
|
+
* @param plan - Plan de rama con métricas y datos calculados.
|
|
103
|
+
* @param config - Configuración activa con definiciones de métricas.
|
|
104
|
+
*/
|
|
105
|
+
function buildMetricsBreakdownTable(plan, config) {
|
|
106
|
+
// Valores brutos posicionales (mismo orden que config.metrics).
|
|
107
|
+
// Convención: 0=commits, 1=arch/commit, 2=líneas/commit, 3=total líneas.
|
|
108
|
+
const displayValues = [
|
|
109
|
+
`${plan.commits} commits`,
|
|
110
|
+
`${plan.filesPerCommit} arch/commit`,
|
|
111
|
+
`${plan.linesPerCommit} líneas/commit`,
|
|
112
|
+
`${plan.lines} líneas`
|
|
113
|
+
];
|
|
114
|
+
const metricRows = Object.entries(config.metrics).map(([code, def], i) => {
|
|
115
|
+
const pts = plan.metrics[code] ?? 0;
|
|
116
|
+
const w = def.weight;
|
|
117
|
+
const contribution = (pts * w).toFixed(3);
|
|
118
|
+
const ptsPct = Math.round((pts / 5) * 100);
|
|
119
|
+
const displayValue = displayValues[i] ?? "—";
|
|
120
|
+
const barColor = pts >= 4 ? "#16a34a" : pts >= 3 ? "#d97706" : "#dc2626";
|
|
121
|
+
const ptsClass = pts >= 4 ? "pts-good" : pts >= 3 ? "pts-warn" : "pts-bad";
|
|
122
|
+
return `<tr>
|
|
123
|
+
<td><code class="metric-code">${esc(code)}</code></td>
|
|
124
|
+
<td class="metric-label">${esc(def.label)}</td>
|
|
125
|
+
<td class="metric-value">${esc(displayValue)}</td>
|
|
126
|
+
<td>
|
|
127
|
+
<div class="pts-bar-wrap">
|
|
128
|
+
<div class="pts-bar" style="width:${ptsPct}%;background:${barColor}"></div>
|
|
129
|
+
</div>
|
|
130
|
+
<span class="${ptsClass}">${pts} / 5</span>
|
|
131
|
+
</td>
|
|
132
|
+
<td class="metric-weight">${(w * 100).toFixed(0)}%</td>
|
|
133
|
+
<td class="metric-contrib">${contribution}</td>
|
|
134
|
+
</tr>`;
|
|
135
|
+
});
|
|
136
|
+
return `<table class="metrics-table">
|
|
137
|
+
<thead>
|
|
138
|
+
<tr>
|
|
139
|
+
<th>Cód.</th>
|
|
140
|
+
<th>Métrica</th>
|
|
141
|
+
<th>Valor medido</th>
|
|
142
|
+
<th>Puntos</th>
|
|
143
|
+
<th>Peso</th>
|
|
144
|
+
<th>Aporte</th>
|
|
145
|
+
</tr>
|
|
146
|
+
</thead>
|
|
147
|
+
<tbody>
|
|
148
|
+
${metricRows.join("")}
|
|
149
|
+
<tr class="metrics-total-row">
|
|
150
|
+
<td colspan="5" style="text-align:right;font-weight:700;">Score total</td>
|
|
151
|
+
<td class="metric-total-value">${plan.score.toFixed(3)}</td>
|
|
152
|
+
</tr>
|
|
153
|
+
</tbody>
|
|
154
|
+
</table>`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Genera una fila `<tr>` para la tabla de archivos con mini-barras visuales
|
|
158
|
+
* proporcionales al máximo de líneas del conjunto.
|
|
159
|
+
*
|
|
160
|
+
* @param f - Estadísticas del archivo.
|
|
161
|
+
* @param maxLines - Valor máximo de líneas en el conjunto (para normalizar barras).
|
|
162
|
+
*/
|
|
163
|
+
function buildFilesBarsRow(f, maxLines) {
|
|
164
|
+
const typeStyles = {
|
|
165
|
+
A: "ct-added",
|
|
166
|
+
U: "ct-untracked",
|
|
167
|
+
M: "ct-modified",
|
|
168
|
+
D: "ct-deleted",
|
|
169
|
+
R: "ct-renamed"
|
|
170
|
+
};
|
|
171
|
+
const ctClass = typeStyles[f.changeType] ?? "ct-default";
|
|
172
|
+
const totalBar = maxLines > 0 ? Math.round((f.lines / maxLines) * 80) : 0;
|
|
173
|
+
const addPct = f.lines > 0 ? Math.round((f.additions / f.lines) * totalBar) : 0;
|
|
174
|
+
const delPct = totalBar - addPct;
|
|
175
|
+
return `<tr>
|
|
176
|
+
<td><span class="ct-badge ${ctClass}">${esc(changeTypeLabel(f.changeType))}</span></td>
|
|
177
|
+
<td class="file-path"><code>${esc(f.path)}</code></td>
|
|
178
|
+
<td class="num-cell">${f.lines}</td>
|
|
179
|
+
<td>
|
|
180
|
+
<div class="diff-bar">
|
|
181
|
+
<div class="diff-add" style="width:${addPct}px" title="+${f.additions}"></div>
|
|
182
|
+
<div class="diff-del" style="width:${delPct}px" title="-${f.deletions}"></div>
|
|
183
|
+
</div>
|
|
184
|
+
<span class="diff-labels"><span class="add-lbl">+${f.additions}</span> <span class="del-lbl">-${f.deletions}</span></span>
|
|
185
|
+
</td>
|
|
186
|
+
<td class="num-cell prio-${f.priority}">${f.priority}</td>
|
|
187
|
+
</tr>`;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Genera la línea de tiempo visual de commits de un plan de rama.
|
|
191
|
+
*
|
|
192
|
+
* @param plan - Plan de rama con el array `commitPlan`.
|
|
193
|
+
*/
|
|
194
|
+
function buildCommitTimelineHtml(plan) {
|
|
195
|
+
if (plan.commitPlan.length === 0) {
|
|
196
|
+
return `<p class="muted">Sin commits en este plan.</p>`;
|
|
197
|
+
}
|
|
198
|
+
const items = plan.commitPlan.map((c) => {
|
|
199
|
+
const typeClass = `ct-${esc(c.suggestedType)}`;
|
|
200
|
+
const fileList = c.files
|
|
201
|
+
.map((fp) => `<li><code>${esc(fp)}</code></li>`)
|
|
202
|
+
.join("");
|
|
203
|
+
return `<div class="timeline-item">
|
|
204
|
+
<div class="timeline-dot"></div>
|
|
205
|
+
<div class="timeline-body">
|
|
206
|
+
<div class="timeline-header">
|
|
207
|
+
<span class="commit-index">#${c.index}</span>
|
|
208
|
+
<span class="commit-type-badge ${typeClass}">${esc(c.suggestedType)}</span>
|
|
209
|
+
<code class="commit-msg">${esc(c.suggestedMessage)}</code>
|
|
210
|
+
<button class="copy-btn" data-copy="${esc(c.suggestedMessage)}" onclick="handleCopy(this)" title="Copiar mensaje de commit">📋</button>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="timeline-meta">
|
|
213
|
+
<span>${c.files.length} archivos</span>
|
|
214
|
+
<span>${c.totalLines} líneas</span>
|
|
215
|
+
<span>scope: <strong>${esc(c.suggestedScope)}</strong></span>
|
|
216
|
+
</div>
|
|
217
|
+
<ul class="commit-files-list">${fileList}</ul>
|
|
218
|
+
</div>
|
|
219
|
+
</div>`;
|
|
220
|
+
});
|
|
221
|
+
return `<div class="timeline">${items.join("")}</div>`;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Genera el bloque de comandos git para implementar un plan de rama.
|
|
225
|
+
*
|
|
226
|
+
* @param plan - Plan de rama con nombre, commits y archivos.
|
|
227
|
+
*/
|
|
228
|
+
function buildGitCommandsHtml(plan) {
|
|
229
|
+
const lines = [];
|
|
230
|
+
if (!plan.isExistingBaseBranch) {
|
|
231
|
+
lines.push(`<span class="gc-comment"># Crear la rama</span>`, `<span class="gc-cmd">git</span> checkout -b <span class="gc-branch">${esc(plan.name)}</span>`, "");
|
|
232
|
+
}
|
|
233
|
+
for (let i = 0; i < plan.commitPlan.length; i++) {
|
|
234
|
+
const c = plan.commitPlan[i];
|
|
235
|
+
lines.push(`<span class="gc-comment"># Commit ${c.index} – ${esc(c.suggestedType)}</span>`);
|
|
236
|
+
if (c.files.length > 0) {
|
|
237
|
+
const fileList = c.files.map((f) => `<span class="gc-file">${esc(f)}</span>`).join(" ");
|
|
238
|
+
lines.push(`<span class="gc-cmd">git</span> add ${fileList}`);
|
|
239
|
+
}
|
|
240
|
+
lines.push(`<span class="gc-cmd">git</span> commit -m <span class="gc-msg">"${esc(c.suggestedMessage)}"</span>`);
|
|
241
|
+
if (i < plan.commitPlan.length - 1)
|
|
242
|
+
lines.push("");
|
|
243
|
+
}
|
|
244
|
+
return `<div class="git-commands-block"><pre>${lines.join("\n")}</pre></div>`;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Genera el HTML del `<article>` para una rama del plan.
|
|
248
|
+
* Incluye anillo de score, desglose de métricas, bloques y línea de tiempo de commits.
|
|
249
|
+
*
|
|
250
|
+
* @param plan - Datos del plan de rama a renderizar.
|
|
251
|
+
* @param config - Configuración activa (necesaria para score objetivo y métricas).
|
|
252
|
+
* @param index - Índice posicional (0-based) para el anchor de navegación.
|
|
253
|
+
*/
|
|
254
|
+
function buildPlanCardHtml(plan, config, index) {
|
|
255
|
+
const warnThreshold = config.targetScore > 4 ? 4 : Math.max(0, config.targetScore - 1);
|
|
256
|
+
const scoreClass = plan.score >= config.targetScore ? "green" : plan.score >= warnThreshold ? "yellow" : "red";
|
|
257
|
+
const alertClass = plan.reason === "OK" ? "green" : "yellow";
|
|
258
|
+
const label = scoreLabel(plan.score, config.targetScore);
|
|
259
|
+
// Resumen de tipos de commit para las pills del header
|
|
260
|
+
const typeCounts = {};
|
|
261
|
+
for (const c of plan.commitPlan) {
|
|
262
|
+
typeCounts[c.suggestedType] = (typeCounts[c.suggestedType] ?? 0) + 1;
|
|
263
|
+
}
|
|
264
|
+
const typePills = Object.entries(typeCounts)
|
|
265
|
+
.sort((a, b) => b[1] - a[1])
|
|
266
|
+
.map(([t, n]) => `<span class="commit-type-pill ct-${esc(t)}">${esc(t)} ×${n}</span>`)
|
|
267
|
+
.join(" ");
|
|
268
|
+
return `
|
|
269
|
+
<article class="plan-card" id="branch-${index}">
|
|
270
|
+
<div class="plan-card-header plan-header-${scoreClass}">
|
|
271
|
+
<div class="plan-header-left">
|
|
272
|
+
<div class="plan-branch-icon">⎇</div>
|
|
273
|
+
<div>
|
|
274
|
+
<div class="plan-branch-name">
|
|
275
|
+
${esc(plan.name)}
|
|
276
|
+
<button class="copy-btn" data-copy="${esc(plan.name)}" onclick="handleCopy(this)" title="Copiar nombre de rama">📋</button>
|
|
277
|
+
</div>
|
|
278
|
+
<div class="plan-branch-sub">
|
|
279
|
+
${plan.isExistingBaseBranch ? '<span class="existing-badge">rama base actual</span>' : ""}
|
|
280
|
+
${plan.files} archivos · ${plan.lines} líneas · ${plan.commits} commits
|
|
281
|
+
</div>
|
|
282
|
+
${typePills ? `<div class="commit-type-pills">${typePills}</div>` : ""}
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="plan-header-right">
|
|
286
|
+
${buildScoreGaugeSvg(plan.score, config.targetScore)}
|
|
287
|
+
<div class="plan-score-label score-label-${scoreClass}">${label}</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<div class="plan-card-body">
|
|
292
|
+
<div class="badges" style="margin-bottom:16px">
|
|
293
|
+
<span class="badge ${scoreClass}">${esc(label)}</span>
|
|
294
|
+
<span class="badge ${alertClass}">${esc(alertLabel(plan.reason))}</span>
|
|
295
|
+
${plan.reason !== "OK" ? `<span class="badge orange">${esc(plan.reason)}</span>` : ""}
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
${plan.recommendation ? `<div class="recommendation-box">
|
|
299
|
+
<span class="rec-icon">💡</span>
|
|
300
|
+
<span>${esc(plan.recommendation)}</span>
|
|
301
|
+
</div>` : ""}
|
|
302
|
+
|
|
303
|
+
<div class="kpi-row">
|
|
304
|
+
<div class="kpi"><div class="kpi-v">${plan.files}</div><div class="kpi-k">Archivos</div></div>
|
|
305
|
+
<div class="kpi"><div class="kpi-v">${plan.lines}</div><div class="kpi-k">Líneas totales</div></div>
|
|
306
|
+
<div class="kpi"><div class="kpi-v">${plan.commits}</div><div class="kpi-k">Commits sugeridos</div></div>
|
|
307
|
+
<div class="kpi"><div class="kpi-v">${plan.filesPerCommit}</div><div class="kpi-k">Arch / commit</div></div>
|
|
308
|
+
<div class="kpi"><div class="kpi-v">${plan.linesPerCommit}</div><div class="kpi-k">Líneas / commit</div></div>
|
|
309
|
+
<div class="kpi"><div class="kpi-v">${plan.blocks.length}</div><div class="kpi-k">Bloques</div></div>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
${plan.excludedCommitTypes?.length
|
|
313
|
+
? `<div class="exclusion-note">⚠ Los promedios <strong>arch/commit (M1.4)</strong> y <strong>líneas/commit (M1.5)</strong> excluyen commits de tipo: <strong>${esc(plan.excludedCommitTypes.join(", "))}</strong>. Las métricas M1.3 (cantidad de commits) y M3.2 (líneas totales) consideran todos los commits.</div>`
|
|
314
|
+
: ""}
|
|
315
|
+
|
|
316
|
+
<h4 class="section-title">📊 Desglose de métricas</h4>
|
|
317
|
+
${buildMetricsBreakdownTable(plan, config)}
|
|
318
|
+
|
|
319
|
+
<h4 class="section-title">🧱 Bloques de trabajo</h4>
|
|
320
|
+
<table>
|
|
321
|
+
<thead>
|
|
322
|
+
<tr>
|
|
323
|
+
<th>Bloque</th>
|
|
324
|
+
<th>Prioridad</th>
|
|
325
|
+
<th>Divisible</th>
|
|
326
|
+
<th>Líneas</th>
|
|
327
|
+
<th>Archivos</th>
|
|
328
|
+
<th>Dep. Score</th>
|
|
329
|
+
<th>Archivos del bloque</th>
|
|
330
|
+
</tr>
|
|
331
|
+
</thead>
|
|
332
|
+
<tbody>
|
|
333
|
+
${plan.blocks.map((block) => `
|
|
334
|
+
<tr>
|
|
335
|
+
<td><code class="block-id">${esc(block.id)}</code></td>
|
|
336
|
+
<td><span class="prio-dot prio-${block.priority}">${block.priority}</span></td>
|
|
337
|
+
<td>${block.divisible
|
|
338
|
+
? '<span class="divisible-yes">✓ Sí</span>'
|
|
339
|
+
: '<span class="divisible-no">✗ No</span>'}</td>
|
|
340
|
+
<td class="num-cell">${block.lines}</td>
|
|
341
|
+
<td class="num-cell">${block.files.length}</td>
|
|
342
|
+
<td class="num-cell">${block.depScore}</td>
|
|
343
|
+
<td class="block-files">${block.files.map((fp) => `<code>${esc(fp)}</code>`).join(" ")}</td>
|
|
344
|
+
</tr>`).join("")}
|
|
345
|
+
</tbody>
|
|
346
|
+
</table>
|
|
347
|
+
|
|
348
|
+
<h4 class="section-title">🕐 Plan de commits</h4>
|
|
349
|
+
${buildCommitTimelineHtml(plan)}
|
|
350
|
+
|
|
351
|
+
<h4 class="section-title">⚙️ Cómo implementar</h4>
|
|
352
|
+
${buildGitCommandsHtml(plan)}
|
|
353
|
+
|
|
354
|
+
<div class="back-to-top">
|
|
355
|
+
<a href="#" class="back-link">↑ Volver al inicio</a>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</article>`;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Genera la tabla de contenidos de navegación anclada para las ramas del plan.
|
|
362
|
+
*
|
|
363
|
+
* @param plans - Lista de planes de rama.
|
|
364
|
+
* @param targetScore - Score objetivo para color del indicador.
|
|
365
|
+
*/
|
|
366
|
+
function buildTocHtml(plans, targetScore) {
|
|
367
|
+
if (plans.length === 0)
|
|
368
|
+
return "";
|
|
369
|
+
const items = plans.map((p, i) => {
|
|
370
|
+
const warnThreshold = targetScore > 4 ? 4 : Math.max(0, targetScore - 1);
|
|
371
|
+
const cls = p.score >= targetScore ? "toc-ok" : p.score >= warnThreshold ? "toc-warn" : "toc-bad";
|
|
372
|
+
const barKey = cls.replace("toc-", "");
|
|
373
|
+
const barPct = Math.round((p.score / 5) * 100);
|
|
374
|
+
return `<li>
|
|
375
|
+
<a href="#branch-${i}" class="toc-link ${cls}">
|
|
376
|
+
<span class="toc-score">${p.score.toFixed(2)}</span>
|
|
377
|
+
<div class="toc-bar-wrap"><div class="toc-bar toc-bar-${barKey}" style="width:${barPct}%"></div></div>
|
|
378
|
+
<span class="toc-name">${esc(p.name)}</span>
|
|
379
|
+
<span class="toc-commits">· ${p.commitPlan.length} commits</span>
|
|
380
|
+
</a>
|
|
381
|
+
</li>`;
|
|
382
|
+
});
|
|
383
|
+
return `<div class="toc-box">
|
|
384
|
+
<div class="toc-title">🗂 Ramas del plan</div>
|
|
385
|
+
<ul class="toc-list">${items.join("")}</ul>
|
|
386
|
+
</div>`;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Devuelve el CSS compilado del reporte HTML (sin etiqueta `<style>`).
|
|
390
|
+
* El CSS es generado a partir de `src/output/report.scss` en el paso `build:styles`.
|
|
391
|
+
* El llamador lo inserta dentro de `<style>...</style>` en `renderHtmlReport`.
|
|
392
|
+
*/
|
|
393
|
+
function buildReportStyles() {
|
|
394
|
+
return reportStyles;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Genera el reporte HTML completo como string.
|
|
398
|
+
*
|
|
399
|
+
* @param input - Todos los datos del análisis necesarios para el reporte.
|
|
400
|
+
* @returns Documento HTML completo (UTF-8, lang="es").
|
|
401
|
+
*/
|
|
402
|
+
export function renderHtmlReport(input) {
|
|
403
|
+
const { currentBranch, baseBranch, config, fileStats, deps, blocks, plans } = input;
|
|
404
|
+
const totalFiles = fileStats.length;
|
|
405
|
+
const totalLines = fileStats.reduce((sum, f) => sum + f.lines, 0);
|
|
406
|
+
const totalAdditions = fileStats.reduce((sum, f) => sum + f.additions, 0);
|
|
407
|
+
const totalDeletions = fileStats.reduce((sum, f) => sum + f.deletions, 0);
|
|
408
|
+
const createdFiles = fileStats.filter((f) => f.changeType === "A" || f.changeType === "U").length;
|
|
409
|
+
const updatedFiles = fileStats.filter((f) => f.changeType === "M").length;
|
|
410
|
+
const deletedFiles = fileStats.filter((f) => f.changeType === "D").length;
|
|
411
|
+
const renamedFiles = fileStats.filter((f) => f.changeType === "R").length;
|
|
412
|
+
const largeFiles = fileStats.filter((f) => f.lines > config.largeFileThreshold).length;
|
|
413
|
+
const avgScore = plans.length > 0
|
|
414
|
+
? plans.reduce((sum, p) => sum + p.score, 0) / plans.length
|
|
415
|
+
: 0;
|
|
416
|
+
const warnThreshold = config.targetScore > 4 ? 4 : Math.max(0, config.targetScore - 1);
|
|
417
|
+
const optimalPlans = plans.filter((p) => p.score >= config.targetScore).length;
|
|
418
|
+
const acceptablePlans = plans.filter((p) => p.score >= warnThreshold && p.score < config.targetScore).length;
|
|
419
|
+
const riskPlans = plans.filter((p) => p.score < warnThreshold).length;
|
|
420
|
+
const maxLinesForBar = fileStats.length > 0 ? Math.max(...fileStats.map((f) => f.lines)) : 1;
|
|
421
|
+
const timestamp = new Date().toLocaleString("es-PE", {
|
|
422
|
+
year: "numeric", month: "long", day: "numeric",
|
|
423
|
+
hour: "2-digit", minute: "2-digit"
|
|
424
|
+
});
|
|
425
|
+
return `<!doctype html>
|
|
426
|
+
<html lang="es">
|
|
427
|
+
<head>
|
|
428
|
+
<meta charset="utf-8" />
|
|
429
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
430
|
+
<title>${APP_REPORT_TITLE}</title>
|
|
431
|
+
<style>${buildReportStyles()}</style>
|
|
432
|
+
</head>
|
|
433
|
+
<body>
|
|
434
|
+
<div class="container">
|
|
435
|
+
|
|
436
|
+
<!-- ══ HERO ══════════════════════════════════════════════════════════ -->
|
|
437
|
+
<header class="hero">
|
|
438
|
+
<div class="hero-top">
|
|
439
|
+
<div class="hero-title-area">
|
|
440
|
+
<h1>🔍 Pull Request Split Advisor</h1>
|
|
441
|
+
<p>Análisis de cambios, bloques, dependencias heurísticas y plan sugerido de ramas y commits.</p>
|
|
442
|
+
</div>
|
|
443
|
+
<div class="hero-timestamp">📅 ${timestamp}</div>
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
<div class="hero-grid">
|
|
447
|
+
<div class="hero-item">
|
|
448
|
+
<div class="label">Rama analizada</div>
|
|
449
|
+
<div class="value">⎇ ${esc(currentBranch)}</div>
|
|
450
|
+
</div>
|
|
451
|
+
<div class="hero-item">
|
|
452
|
+
<div class="label">Rama base</div>
|
|
453
|
+
<div class="value">⎇ ${esc(baseBranch)}</div>
|
|
454
|
+
</div>
|
|
455
|
+
<div class="hero-item">
|
|
456
|
+
<div class="label">Score objetivo</div>
|
|
457
|
+
<div class="value">${config.targetScore} / 5.00</div>
|
|
458
|
+
</div>
|
|
459
|
+
<div class="hero-item">
|
|
460
|
+
<div class="label">Score promedio del plan</div>
|
|
461
|
+
<div class="value">${avgScore.toFixed(2)}</div>
|
|
462
|
+
</div>
|
|
463
|
+
<div class="hero-item">
|
|
464
|
+
<div class="label">Ramas ÓPTIMO</div>
|
|
465
|
+
<div class="value" style="color:#4ade80">${optimalPlans}</div>
|
|
466
|
+
</div>
|
|
467
|
+
<div class="hero-item">
|
|
468
|
+
<div class="label">Ramas ACEPTABLE</div>
|
|
469
|
+
<div class="value" style="color:#fbbf24">${acceptablePlans}</div>
|
|
470
|
+
</div>
|
|
471
|
+
<div class="hero-item">
|
|
472
|
+
<div class="label">Ramas RIESGO</div>
|
|
473
|
+
<div class="value" style="color:#f87171">${riskPlans}</div>
|
|
474
|
+
</div>
|
|
475
|
+
<div class="hero-item">
|
|
476
|
+
<div class="label">Archivos modificados</div>
|
|
477
|
+
<div class="value">${totalFiles}</div>
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
</header>
|
|
481
|
+
<!-- ══ AVISO DE RESPONSABILIDAD ═══════════════════════════════ -->
|
|
482
|
+
<section class="disclaimer-banner" role="alert" aria-label="Aviso de responsabilidad">
|
|
483
|
+
<div class="disclaimer-icon">⚠️</div>
|
|
484
|
+
<div class="disclaimer-body">
|
|
485
|
+
<strong>AVISO DE RESPONSABILIDAD</strong>
|
|
486
|
+
<p>
|
|
487
|
+
Esta herramienta genera un plan de distribución de cambios.
|
|
488
|
+
<strong>No ejecuta compilaciones ni suites de pruebas.</strong>
|
|
489
|
+
La verificación de la integridad del código es
|
|
490
|
+
<strong>responsabilidad exclusiva del equipo de desarrollo</strong>.
|
|
491
|
+
</p>
|
|
492
|
+
<ul>
|
|
493
|
+
<li>Confirme que el proyecto <strong>compile sin errores</strong> antes de implementar el plan.</li>
|
|
494
|
+
<li>Confirme que <strong>todas las pruebas finalicen correctamente</strong> antes de crear los PRs.</li>
|
|
495
|
+
</ul>
|
|
496
|
+
<p class="disclaimer-footer">
|
|
497
|
+
Pull Request Split Advisor no asume responsabilidad por ramas o PRs generados a partir de código defectuoso.
|
|
498
|
+
</p>
|
|
499
|
+
</div>
|
|
500
|
+
</section>
|
|
501
|
+
<!-- ══ RESUMEN DE CAMBIOS ══════════════════════════════════════════ -->
|
|
502
|
+
<section class="section">
|
|
503
|
+
<h2 class="section-heading">📁 Resumen de cambios</h2>
|
|
504
|
+
<div class="summary-grid">
|
|
505
|
+
<div class="stat-card blue">
|
|
506
|
+
<div class="label">Total de archivos</div>
|
|
507
|
+
<div class="value">${totalFiles}</div>
|
|
508
|
+
<div class="sub">${totalLines} líneas totales</div>
|
|
509
|
+
</div>
|
|
510
|
+
<div class="stat-card green">
|
|
511
|
+
<div class="label">Líneas agregadas</div>
|
|
512
|
+
<div class="value">+${totalAdditions}</div>
|
|
513
|
+
</div>
|
|
514
|
+
<div class="stat-card red">
|
|
515
|
+
<div class="label">Líneas eliminadas</div>
|
|
516
|
+
<div class="value">-${totalDeletions}</div>
|
|
517
|
+
</div>
|
|
518
|
+
<div class="stat-card green">
|
|
519
|
+
<div class="label">Archivos nuevos (A + U)</div>
|
|
520
|
+
<div class="value">${createdFiles}</div>
|
|
521
|
+
</div>
|
|
522
|
+
<div class="stat-card blue">
|
|
523
|
+
<div class="label">Archivos modificados</div>
|
|
524
|
+
<div class="value">${updatedFiles}</div>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="stat-card red">
|
|
527
|
+
<div class="label">Archivos eliminados</div>
|
|
528
|
+
<div class="value">${deletedFiles}</div>
|
|
529
|
+
</div>
|
|
530
|
+
<div class="stat-card yellow">
|
|
531
|
+
<div class="label">Archivos renombrados</div>
|
|
532
|
+
<div class="value">${renamedFiles}</div>
|
|
533
|
+
</div>
|
|
534
|
+
<div class="stat-card yellow">
|
|
535
|
+
<div class="label">Archivos grandes (> ${config.largeFileThreshold} líneas)</div>
|
|
536
|
+
<div class="value">${largeFiles}</div>
|
|
537
|
+
</div>
|
|
538
|
+
<div class="stat-card purple">
|
|
539
|
+
<div class="label">Ramas sugeridas</div>
|
|
540
|
+
<div class="value">${plans.length}</div>
|
|
541
|
+
</div>
|
|
542
|
+
<div class="stat-card blue">
|
|
543
|
+
<div class="label">Ideal líneas por PR</div>
|
|
544
|
+
<div class="value">${config.idealLinesPerPR}</div>
|
|
545
|
+
</div>
|
|
546
|
+
<div class="stat-card blue">
|
|
547
|
+
<div class="label">Máx líneas / commit</div>
|
|
548
|
+
<div class="value">${config.maxLinesPerCommitIdeal}</div>
|
|
549
|
+
</div>
|
|
550
|
+
<div class="stat-card blue">
|
|
551
|
+
<div class="label">Máx commits / rama</div>
|
|
552
|
+
<div class="value">${config.maxCommitsPerBranchIdeal}</div>
|
|
553
|
+
</div>
|
|
554
|
+
${(() => {
|
|
555
|
+
const pct = config.testCoveragePercent ?? 0;
|
|
556
|
+
const cls = pct >= 20 ? "green" : pct > 0 ? "yellow" : "red";
|
|
557
|
+
return `<div class="stat-card ${cls}">
|
|
558
|
+
<div class="label">Cobertura de tests</div>
|
|
559
|
+
<div class="value">${pct}%</div>
|
|
560
|
+
<div class="sub">${pct >= 20 ? "Buena cobertura" : pct > 0 ? "Cobertura parcial" : "Sin tests detectados"}</div>
|
|
561
|
+
</div>`;
|
|
562
|
+
})()}
|
|
563
|
+
</div>
|
|
564
|
+
</section>
|
|
565
|
+
|
|
566
|
+
<!-- ══ NAVEGACIÓN DEL PLAN ════════════════════════════════════════ -->
|
|
567
|
+
${plans.length > 0 ? `<section class="section">
|
|
568
|
+
<h2 class="section-heading">🗺 Navegación del plan</h2>
|
|
569
|
+
${buildTocHtml(plans, config.targetScore)}
|
|
570
|
+
</section>` : ""}
|
|
571
|
+
|
|
572
|
+
<!-- ══ DETALLE DE ARCHIVOS ════════════════════════════════════════ -->
|
|
573
|
+
<section class="section">
|
|
574
|
+
<h2 class="section-heading">📄 Detalle de archivos</h2>
|
|
575
|
+
<table>
|
|
576
|
+
<thead>
|
|
577
|
+
<tr>
|
|
578
|
+
<th>Tipo</th>
|
|
579
|
+
<th>Archivo</th>
|
|
580
|
+
<th>Líneas</th>
|
|
581
|
+
<th>Cambios (+/-)</th>
|
|
582
|
+
<th>Prioridad</th>
|
|
583
|
+
</tr>
|
|
584
|
+
</thead>
|
|
585
|
+
<tbody>
|
|
586
|
+
${fileStats.map((f) => buildFilesBarsRow(f, maxLinesForBar)).join("")}
|
|
587
|
+
</tbody>
|
|
588
|
+
</table>
|
|
589
|
+
</section>
|
|
590
|
+
|
|
591
|
+
<!-- ══ DEPENDENCIAS HEURÍSTICAS ══════════════════════════════════ -->
|
|
592
|
+
<section class="section">
|
|
593
|
+
<h2 class="section-heading">🔗 Dependencias heurísticas</h2>
|
|
594
|
+
${deps.length
|
|
595
|
+
? `<table>
|
|
596
|
+
<thead>
|
|
597
|
+
<tr>
|
|
598
|
+
<th>Archivo origen</th>
|
|
599
|
+
<th style="width:40px"></th>
|
|
600
|
+
<th>Archivo dependencia</th>
|
|
601
|
+
</tr>
|
|
602
|
+
</thead>
|
|
603
|
+
<tbody>
|
|
604
|
+
${deps.map((d) => `<tr>
|
|
605
|
+
<td><code>${esc(d.from)}</code></td>
|
|
606
|
+
<td class="dep-arrow" style="text-align:center">→</td>
|
|
607
|
+
<td><code>${esc(d.to)}</code></td>
|
|
608
|
+
</tr>`).join("")}
|
|
609
|
+
</tbody>
|
|
610
|
+
</table>`
|
|
611
|
+
: `<div class="card"><p class="muted" style="margin:0">No se detectaron dependencias heurísticas entre los archivos modificados.</p></div>`}
|
|
612
|
+
</section>
|
|
613
|
+
|
|
614
|
+
<!-- ══ BLOQUES DETECTADOS ════════════════════════════════════════ -->
|
|
615
|
+
<section class="section">
|
|
616
|
+
<h2 class="section-heading">🧱 Bloques detectados</h2>
|
|
617
|
+
<table>
|
|
618
|
+
<thead>
|
|
619
|
+
<tr>
|
|
620
|
+
<th>Bloque</th>
|
|
621
|
+
<th>Prioridad</th>
|
|
622
|
+
<th>Divisible</th>
|
|
623
|
+
<th>Líneas</th>
|
|
624
|
+
<th>Archivos</th>
|
|
625
|
+
<th>Dep. Score</th>
|
|
626
|
+
</tr>
|
|
627
|
+
</thead>
|
|
628
|
+
<tbody>
|
|
629
|
+
${blocks.map((b) => `<tr>
|
|
630
|
+
<td><code class="block-id">${esc(b.id)}</code></td>
|
|
631
|
+
<td><span class="prio-dot prio-${b.priority}">${b.priority}</span></td>
|
|
632
|
+
<td>${b.divisible
|
|
633
|
+
? '<span class="divisible-yes">✓ Sí</span>'
|
|
634
|
+
: '<span class="divisible-no">✗ No</span>'}</td>
|
|
635
|
+
<td class="num-cell">${b.lines}</td>
|
|
636
|
+
<td class="num-cell">${b.files.length}</td>
|
|
637
|
+
<td class="num-cell">${b.depScore}</td>
|
|
638
|
+
</tr>`).join("")}
|
|
639
|
+
</tbody>
|
|
640
|
+
</table>
|
|
641
|
+
</section>
|
|
642
|
+
|
|
643
|
+
<!-- ══ PLAN RECOMENDADO ══════════════════════════════════════════ -->
|
|
644
|
+
<section class="section">
|
|
645
|
+
<h2 class="section-heading">🚀 Plan recomendado</h2>
|
|
646
|
+
|
|
647
|
+
<div class="card" style="margin-bottom:24px">
|
|
648
|
+
<div class="summary-grid">
|
|
649
|
+
<div class="stat-card purple">
|
|
650
|
+
<div class="label">Ramas sugeridas</div>
|
|
651
|
+
<div class="value">${plans.length}</div>
|
|
652
|
+
</div>
|
|
653
|
+
<div class="stat-card blue">
|
|
654
|
+
<div class="label">Score promedio</div>
|
|
655
|
+
<div class="value">${avgScore.toFixed(2)}</div>
|
|
656
|
+
</div>
|
|
657
|
+
<div class="stat-card green">
|
|
658
|
+
<div class="label">Ramas ÓPTIMO (≥ ${config.targetScore})</div>
|
|
659
|
+
<div class="value">${optimalPlans}</div>
|
|
660
|
+
</div>
|
|
661
|
+
<div class="stat-card yellow">
|
|
662
|
+
<div class="label">Ramas ACEPTABLE (≥ ${warnThreshold.toFixed(1)})</div>
|
|
663
|
+
<div class="value">${acceptablePlans}</div>
|
|
664
|
+
</div>
|
|
665
|
+
<div class="stat-card red">
|
|
666
|
+
<div class="label">Ramas RIESGO (< ${warnThreshold.toFixed(1)})</div>
|
|
667
|
+
<div class="value">${riskPlans}</div>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="stat-card yellow">
|
|
670
|
+
<div class="label">Ramas indivisibles</div>
|
|
671
|
+
<div class="value">${plans.filter((p) => p.reason.includes("indivisible")).length}</div>
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
|
|
676
|
+
${plans.map((plan, i) => buildPlanCardHtml(plan, config, i)).join("")}
|
|
677
|
+
</section>
|
|
678
|
+
|
|
679
|
+
<!-- ══ FOOTER ════════════════════════════════════════════════════ -->
|
|
680
|
+
<footer class="footer">
|
|
681
|
+
<p>Generado por <strong>${APP_REPORT_TITLE}</strong> · ${timestamp}</p>
|
|
682
|
+
<p>Score objetivo: <strong>${config.targetScore}</strong> · Ramas analizadas: <strong>${plans.length}</strong> · Archivos: <strong>${totalFiles}</strong></p>
|
|
683
|
+
</footer>
|
|
684
|
+
|
|
685
|
+
</div>
|
|
686
|
+
<script>
|
|
687
|
+
function handleCopy(btn) {
|
|
688
|
+
var text = btn.getAttribute('data-copy') || '';
|
|
689
|
+
if (!navigator.clipboard) return;
|
|
690
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
691
|
+
var orig = btn.textContent;
|
|
692
|
+
btn.textContent = '✓';
|
|
693
|
+
setTimeout(function() { btn.textContent = orig; }, 1500);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
</script>
|
|
697
|
+
</body>
|
|
698
|
+
</html>`;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Genera el reporte HTML y lo escribe en disco.
|
|
702
|
+
*
|
|
703
|
+
* @param filePath - Ruta de destino del archivo HTML.
|
|
704
|
+
* @param input - Datos del análisis.
|
|
705
|
+
*/
|
|
706
|
+
export function writeHtmlReport(filePath, input) {
|
|
707
|
+
writeFileSync(filePath, renderHtmlReport(input), "utf8");
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Genera el JSON serializado del plan.
|
|
711
|
+
* @param plans - Lista de planes de rama.
|
|
712
|
+
* @param currentBranch - Nombre de la rama activa.
|
|
713
|
+
* @param baseBranch - Nombre de la rama base.
|
|
714
|
+
* @param config - Configuración activa (se incluye `targetScore` en el JSON).
|
|
715
|
+
*/
|
|
716
|
+
export function exportJson(plans, currentBranch, baseBranch, config) {
|
|
717
|
+
if (!config.exportJson)
|
|
718
|
+
return;
|
|
719
|
+
const payload = {
|
|
720
|
+
currentBranch,
|
|
721
|
+
baseBranch,
|
|
722
|
+
targetScore: config.targetScore,
|
|
723
|
+
branches: plans
|
|
724
|
+
};
|
|
725
|
+
writeFileSync(config.jsonOutputFile, JSON.stringify(payload, null, 2), "utf8");
|
|
726
|
+
}
|