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