@mks2508/better-logger 5.0.1 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/dist/Logger.d.ts +976 -0
  2. package/dist/Logger.d.ts.map +1 -0
  3. package/dist/ScopedLogger.d.ts +74 -0
  4. package/dist/ScopedLogger.d.ts.map +1 -0
  5. package/dist/chunks/Logger-CKqzXDh3.js +2 -0
  6. package/dist/chunks/Logger-CKqzXDh3.js.map +1 -0
  7. package/dist/chunks/Logger-D_vuKP4l.js +4511 -0
  8. package/dist/chunks/Logger-D_vuKP4l.js.map +1 -0
  9. package/dist/chunks/RemoteLogHandler-CjWpWZGl.js +33 -0
  10. package/dist/chunks/RemoteLogHandler-CjWpWZGl.js.map +1 -0
  11. package/dist/chunks/RemoteLogHandler-ymkQ97xl.js +2 -0
  12. package/dist/chunks/RemoteLogHandler-ymkQ97xl.js.map +1 -0
  13. package/dist/chunks/cli-module-B-njrTWb.js +2 -0
  14. package/dist/chunks/cli-module-B-njrTWb.js.map +1 -0
  15. package/dist/chunks/cli-module-Cx7qotPY.js +390 -0
  16. package/dist/chunks/cli-module-Cx7qotPY.js.map +1 -0
  17. package/dist/chunks/color-converter-CCSQRztd.js +2 -0
  18. package/dist/chunks/color-converter-CCSQRztd.js.map +1 -0
  19. package/dist/chunks/color-converter-_Xdmsy7E.js +462 -0
  20. package/dist/chunks/color-converter-_Xdmsy7E.js.map +1 -0
  21. package/dist/chunks/environment-C1xxvc8l.js +1127 -0
  22. package/dist/chunks/environment-C1xxvc8l.js.map +1 -0
  23. package/dist/chunks/environment-Cprkw3g9.js +4 -0
  24. package/dist/chunks/environment-Cprkw3g9.js.map +1 -0
  25. package/dist/chunks/formatting-DMJxYq9o.js +380 -0
  26. package/dist/chunks/formatting-DMJxYq9o.js.map +1 -0
  27. package/dist/chunks/formatting-DpKxCsXq.js +2 -0
  28. package/dist/chunks/formatting-DpKxCsXq.js.map +1 -0
  29. package/dist/cli/CommandProcessor.d.ts +100 -0
  30. package/dist/cli/CommandProcessor.d.ts.map +1 -0
  31. package/dist/cli/commands/ConfigCommand.d.ts +14 -0
  32. package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
  33. package/dist/cli/commands/ExportCommand.d.ts +48 -0
  34. package/dist/cli/commands/ExportCommand.d.ts.map +1 -0
  35. package/dist/cli/commands/HistoryCommand.d.ts +47 -0
  36. package/dist/cli/commands/HistoryCommand.d.ts.map +1 -0
  37. package/dist/cli/commands/StatusCommand.d.ts +30 -0
  38. package/dist/cli/commands/StatusCommand.d.ts.map +1 -0
  39. package/dist/cli/commands/ThemeCommand.d.ts +30 -0
  40. package/dist/cli/commands/ThemeCommand.d.ts.map +1 -0
  41. package/dist/cli/help.d.ts +12 -0
  42. package/dist/cli/help.d.ts.map +1 -0
  43. package/dist/cli/index.d.ts +15 -0
  44. package/dist/cli/index.d.ts.map +1 -0
  45. package/{src/cli-module.ts → dist/cli-module.d.ts} +2 -8
  46. package/dist/cli-module.d.ts.map +1 -0
  47. package/dist/cli-primitives/box.d.ts +11 -0
  48. package/dist/cli-primitives/box.d.ts.map +1 -0
  49. package/dist/cli-primitives/cli-table.d.ts +11 -0
  50. package/dist/cli-primitives/cli-table.d.ts.map +1 -0
  51. package/dist/cli-primitives/divider.d.ts +11 -0
  52. package/dist/cli-primitives/divider.d.ts.map +1 -0
  53. package/dist/cli-primitives/header.d.ts +12 -0
  54. package/dist/cli-primitives/header.d.ts.map +1 -0
  55. package/{src/cli-primitives/index.ts → dist/cli-primitives/index.d.ts} +1 -1
  56. package/dist/cli-primitives/index.d.ts.map +1 -0
  57. package/dist/cli-primitives/server-fallback.d.ts +25 -0
  58. package/dist/cli-primitives/server-fallback.d.ts.map +1 -0
  59. package/dist/cli-primitives/spinner.d.ts +47 -0
  60. package/dist/cli-primitives/spinner.d.ts.map +1 -0
  61. package/dist/cli-primitives/step.d.ts +11 -0
  62. package/dist/cli-primitives/step.d.ts.map +1 -0
  63. package/dist/cli.cjs +2 -0
  64. package/dist/cli.cjs.map +1 -0
  65. package/dist/cli.js +12 -0
  66. package/dist/cli.js.map +1 -0
  67. package/dist/constants.d.ts +235 -0
  68. package/dist/constants.d.ts.map +1 -0
  69. package/dist/core.cjs +2 -0
  70. package/dist/core.cjs.map +1 -0
  71. package/dist/core.d.ts +127 -0
  72. package/dist/core.d.ts.map +1 -0
  73. package/dist/core.js +291 -0
  74. package/dist/core.js.map +1 -0
  75. package/dist/example.d.ts +18 -0
  76. package/dist/example.d.ts.map +1 -0
  77. package/dist/exports-module.d.ts +196 -0
  78. package/dist/exports-module.d.ts.map +1 -0
  79. package/dist/exports.cjs +2 -0
  80. package/dist/exports.cjs.map +1 -0
  81. package/dist/exports.js +238 -0
  82. package/dist/exports.js.map +1 -0
  83. package/dist/handlers/AnalyticsLogHandler.d.ts +8 -0
  84. package/dist/handlers/AnalyticsLogHandler.d.ts.map +1 -0
  85. package/dist/handlers/ExportLogHandler.d.ts +100 -0
  86. package/dist/handlers/ExportLogHandler.d.ts.map +1 -0
  87. package/dist/handlers/FileLogHandler.d.ts +33 -0
  88. package/dist/handlers/FileLogHandler.d.ts.map +1 -0
  89. package/dist/handlers/RemoteLogHandler.d.ts +11 -0
  90. package/dist/handlers/RemoteLogHandler.d.ts.map +1 -0
  91. package/{src/handlers/index.ts → dist/handlers/index.d.ts} +2 -2
  92. package/dist/handlers/index.d.ts.map +1 -0
  93. package/dist/hooks/HookManager.d.ts +21 -0
  94. package/dist/hooks/HookManager.d.ts.map +1 -0
  95. package/{src/hooks/index.ts → dist/hooks/index.d.ts} +1 -0
  96. package/dist/hooks/index.d.ts.map +1 -0
  97. package/dist/index.cjs +2 -0
  98. package/dist/index.cjs.map +1 -0
  99. package/dist/index.d.ts +161 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +627 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/main.d.ts +2 -0
  104. package/dist/main.d.ts.map +1 -0
  105. package/dist/serializers/SerializerRegistry.d.ts +16 -0
  106. package/dist/serializers/SerializerRegistry.d.ts.map +1 -0
  107. package/{src/serializers/index.ts → dist/serializers/index.d.ts} +1 -0
  108. package/dist/serializers/index.d.ts.map +1 -0
  109. package/dist/styling/LogStyleBuilder.d.ts +146 -0
  110. package/dist/styling/LogStyleBuilder.d.ts.map +1 -0
  111. package/dist/styling/SemanticStyles.d.ts +178 -0
  112. package/dist/styling/SemanticStyles.d.ts.map +1 -0
  113. package/dist/styling/SmartPresets.d.ts +22 -0
  114. package/dist/styling/SmartPresets.d.ts.map +1 -0
  115. package/dist/styling/StyleBuilder.d.ts +140 -0
  116. package/dist/styling/StyleBuilder.d.ts.map +1 -0
  117. package/dist/styling/StyleCache.d.ts +42 -0
  118. package/dist/styling/StyleCache.d.ts.map +1 -0
  119. package/dist/styling/banners.d.ts +42 -0
  120. package/dist/styling/banners.d.ts.map +1 -0
  121. package/dist/styling/index.d.ts +10 -0
  122. package/dist/styling/index.d.ts.map +1 -0
  123. package/dist/styling/themes.d.ts +7 -0
  124. package/dist/styling/themes.d.ts.map +1 -0
  125. package/dist/styling-module.d.ts +178 -0
  126. package/dist/styling-module.d.ts.map +1 -0
  127. package/dist/styling.cjs +2 -0
  128. package/dist/styling.cjs.map +1 -0
  129. package/dist/styling.js +146 -0
  130. package/dist/styling.js.map +1 -0
  131. package/dist/terminal/color-converter.d.ts +73 -0
  132. package/dist/terminal/color-converter.d.ts.map +1 -0
  133. package/dist/terminal/formatter.d.ts +20 -0
  134. package/dist/terminal/formatter.d.ts.map +1 -0
  135. package/dist/terminal/terminal-renderer.d.ts +77 -0
  136. package/dist/terminal/terminal-renderer.d.ts.map +1 -0
  137. package/dist/transports/ConsoleTransport.d.ts +9 -0
  138. package/dist/transports/ConsoleTransport.d.ts.map +1 -0
  139. package/dist/transports/FileTransport.d.ts +15 -0
  140. package/dist/transports/FileTransport.d.ts.map +1 -0
  141. package/dist/transports/HttpTransport.d.ts +16 -0
  142. package/dist/transports/HttpTransport.d.ts.map +1 -0
  143. package/dist/transports/TransportManager.d.ts +14 -0
  144. package/dist/transports/TransportManager.d.ts.map +1 -0
  145. package/{src/transports/index.ts → dist/transports/index.d.ts} +1 -0
  146. package/dist/transports/index.d.ts.map +1 -0
  147. package/{src/types/core.ts → dist/types/core.d.ts} +25 -62
  148. package/dist/types/core.d.ts.map +1 -0
  149. package/{src/types/handlers.ts → dist/types/handlers.d.ts} +5 -15
  150. package/dist/types/handlers.d.ts.map +1 -0
  151. package/{src/types/hooks.ts → dist/types/hooks.d.ts} +3 -12
  152. package/dist/types/hooks.d.ts.map +1 -0
  153. package/dist/types/index.d.ts +11 -0
  154. package/dist/types/index.d.ts.map +1 -0
  155. package/{src/types/serializers.ts → dist/types/serializers.d.ts} +1 -6
  156. package/dist/types/serializers.d.ts.map +1 -0
  157. package/{src/types/transports.ts → dist/types/transports.d.ts} +2 -7
  158. package/dist/types/transports.d.ts.map +1 -0
  159. package/dist/utils/adapter.d.ts +48 -0
  160. package/dist/utils/adapter.d.ts.map +1 -0
  161. package/dist/utils/ansi-colors.d.ts +156 -0
  162. package/dist/utils/ansi-colors.d.ts.map +1 -0
  163. package/dist/utils/environment-detector.d.ts +45 -0
  164. package/dist/utils/environment-detector.d.ts.map +1 -0
  165. package/dist/utils/environment.d.ts +47 -0
  166. package/dist/utils/environment.d.ts.map +1 -0
  167. package/dist/utils/formatting.d.ts +58 -0
  168. package/dist/utils/formatting.d.ts.map +1 -0
  169. package/dist/utils/index.d.ts +8 -0
  170. package/dist/utils/index.d.ts.map +1 -0
  171. package/dist/utils/opentui-detection.d.ts +34 -0
  172. package/dist/utils/opentui-detection.d.ts.map +1 -0
  173. package/dist/utils/output.d.ts +46 -0
  174. package/dist/utils/output.d.ts.map +1 -0
  175. package/dist/utils/stackTrace.d.ts +6 -0
  176. package/dist/utils/stackTrace.d.ts.map +1 -0
  177. package/dist/utils/timestamps.d.ts +20 -0
  178. package/dist/utils/timestamps.d.ts.map +1 -0
  179. package/{src/writers/BufferWriter.ts → dist/writers/BufferWriter.d.ts} +16 -77
  180. package/dist/writers/BufferWriter.d.ts.map +1 -0
  181. package/{src/writers/index.ts → dist/writers/index.d.ts} +1 -1
  182. package/dist/writers/index.d.ts.map +1 -0
  183. package/package.json +5 -1
  184. package/.claude/settings.local.json +0 -48
  185. package/.github/workflows/ci-quality.yml +0 -357
  186. package/.github/workflows/docs-demo.yml +0 -119
  187. package/.github/workflows/releases-core.yml +0 -512
  188. package/.github/workflows/releases-full.yml +0 -582
  189. package/.github/workflows-backup/ci.yml +0 -221
  190. package/.github/workflows-backup/nightly.yml +0 -196
  191. package/.github/workflows-backup/release-optimized.yml +0 -373
  192. package/.github/workflows-backup/release.yml +0 -269
  193. package/.release-notes-0.2.0.md +0 -129
  194. package/.yamllint +0 -28
  195. package/CHANGELOG.json +0 -1120
  196. package/CHANGELOG.md +0 -209
  197. package/CLAUDE.md +0 -217
  198. package/demo.html +0 -847
  199. package/docs/API.md +0 -918
  200. package/docs/CORE.md +0 -264
  201. package/docs/DEVELOPMENT.md +0 -731
  202. package/docs/EXPORTS.md +0 -467
  203. package/docs/PACKAGES.md +0 -244
  204. package/docs/STYLING.md +0 -405
  205. package/docs/_config.yml +0 -36
  206. package/docs/index.md +0 -179
  207. package/examples/README.md +0 -208
  208. package/examples/basic-logging.js +0 -75
  209. package/examples/data-export.js +0 -234
  210. package/examples/package.json +0 -16
  211. package/examples/performance-timing.js +0 -170
  212. package/examples/simplified-api.js +0 -124
  213. package/examples/styling-themes.js +0 -207
  214. package/index.html +0 -358
  215. package/packages/core/package.json +0 -57
  216. package/packages/exports/package.json +0 -41
  217. package/packages/nodejs-opentui/README.md +0 -232
  218. package/packages/nodejs-opentui/package.json +0 -72
  219. package/packages/nodejs-opentui/src/LogRenderer.ts +0 -171
  220. package/packages/nodejs-opentui/src/OpenTUILogHandler.ts +0 -178
  221. package/packages/nodejs-opentui/src/components/LogBadge.tsx +0 -131
  222. package/packages/nodejs-opentui/src/index.ts +0 -133
  223. package/packages/nodejs-opentui/src/types.ts +0 -158
  224. package/packages/nodejs-opentui/tsconfig.json +0 -20
  225. package/packages/styling/package.json +0 -41
  226. package/playground/demo-all.ts +0 -95
  227. package/playground/demo-box.ts +0 -85
  228. package/playground/demo-levels.ts +0 -56
  229. package/playground/demo-real-world.ts +0 -98
  230. package/playground/demo-spinner.ts +0 -77
  231. package/playground/demo-steps.ts +0 -72
  232. package/playground/demo-table.ts +0 -83
  233. package/project-utils/README.md +0 -172
  234. package/project-utils/auto-release-gemini.ts +0 -1193
  235. package/project-utils/auto-release-ui.ts +0 -1329
  236. package/project-utils/commit-generator.ts +0 -1385
  237. package/project-utils/commit-ui.ts +0 -264
  238. package/project-utils/git-utils.ts +0 -199
  239. package/project-utils/github-release-manager.ts +0 -466
  240. package/project-utils/project-config.ts +0 -260
  241. package/project-utils/prompt-templates.js +0 -345
  242. package/project-utils/prompt-templates.ts +0 -422
  243. package/project-utils/version-manager.ts +0 -1078
  244. package/src/Logger.ts +0 -1858
  245. package/src/ScopedLogger.ts +0 -256
  246. package/src/cli/CommandProcessor.ts +0 -248
  247. package/src/cli/commands/ConfigCommand.ts +0 -93
  248. package/src/cli/commands/ExportCommand.ts +0 -276
  249. package/src/cli/commands/HistoryCommand.ts +0 -117
  250. package/src/cli/commands/StatusCommand.ts +0 -112
  251. package/src/cli/commands/ThemeCommand.ts +0 -88
  252. package/src/cli/help.ts +0 -127
  253. package/src/cli/index.ts +0 -71
  254. package/src/cli-primitives/box.ts +0 -86
  255. package/src/cli-primitives/cli-table.ts +0 -62
  256. package/src/cli-primitives/divider.ts +0 -17
  257. package/src/cli-primitives/header.ts +0 -18
  258. package/src/cli-primitives/server-fallback.ts +0 -54
  259. package/src/cli-primitives/spinner.ts +0 -133
  260. package/src/cli-primitives/step.ts +0 -22
  261. package/src/constants.ts +0 -313
  262. package/src/core.ts +0 -397
  263. package/src/example.ts +0 -210
  264. package/src/exports-module.ts +0 -311
  265. package/src/handlers/AnalyticsLogHandler.ts +0 -22
  266. package/src/handlers/ExportLogHandler.ts +0 -610
  267. package/src/handlers/FileLogHandler.ts +0 -169
  268. package/src/handlers/RemoteLogHandler.ts +0 -42
  269. package/src/hooks/HookManager.ts +0 -177
  270. package/src/index.ts +0 -395
  271. package/src/main.ts +0 -196
  272. package/src/serializers/SerializerRegistry.ts +0 -173
  273. package/src/style.css +0 -96
  274. package/src/styling/LogStyleBuilder.ts +0 -355
  275. package/src/styling/SemanticStyles.ts +0 -380
  276. package/src/styling/SmartPresets.ts +0 -288
  277. package/src/styling/StyleBuilder.ts +0 -319
  278. package/src/styling/StyleCache.ts +0 -131
  279. package/src/styling/banners.ts +0 -168
  280. package/src/styling/index.ts +0 -27
  281. package/src/styling/themes.ts +0 -235
  282. package/src/styling-module.ts +0 -244
  283. package/src/terminal/color-converter.ts +0 -315
  284. package/src/terminal/formatter.ts +0 -242
  285. package/src/terminal/terminal-renderer.ts +0 -342
  286. package/src/transports/ConsoleTransport.ts +0 -28
  287. package/src/transports/FileTransport.ts +0 -53
  288. package/src/transports/HttpTransport.ts +0 -56
  289. package/src/transports/TransportManager.ts +0 -130
  290. package/src/types/index.ts +0 -83
  291. package/src/typescript.svg +0 -1
  292. package/src/utils/adapter.ts +0 -291
  293. package/src/utils/ansi-colors.ts +0 -333
  294. package/src/utils/environment-detector.ts +0 -170
  295. package/src/utils/environment.ts +0 -94
  296. package/src/utils/formatting.ts +0 -332
  297. package/src/utils/index.ts +0 -33
  298. package/src/utils/opentui-detection.ts +0 -138
  299. package/src/utils/output.ts +0 -227
  300. package/src/utils/stackTrace.ts +0 -144
  301. package/src/utils/timestamps.ts +0 -80
  302. package/src/vite-env.d.ts +0 -1
  303. package/test-core-browser.html +0 -237
  304. package/test-core-node.js +0 -63
  305. package/tests/test-conflict-resolution.ts +0 -391
  306. package/tests/validate-workflows.sh +0 -131
  307. package/tests/yaml-autofix.sh +0 -146
  308. package/tsconfig.json +0 -50
  309. package/vite.config.ts +0 -256
  310. /package/{public → dist}/vite.svg +0 -0
@@ -1,1385 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Generador Automático de Commits con Gemini CLI
5
- * Analiza todos los cambios del repositorio y genera commits coherentes
6
- * siguiendo los patrones establecidos para el proyecto Better Logger
7
- */
8
-
9
- import { spawn } from 'child_process';
10
- import { readFileSync, writeFileSync, existsSync } from 'fs';
11
- import { createCommitPrompt, GeminiResponseParser, BETTER_LOGGER_PROJECT_CONFIG, type GeminiPromptConfig } from './prompt-templates';
12
- import { join } from 'path';
13
-
14
- interface FileChange {
15
- path: string;
16
- status: 'modified' | 'added' | 'deleted' | 'renamed' | 'untracked';
17
- diff?: string;
18
- lines_added?: number;
19
- lines_removed?: number;
20
- is_binary?: boolean;
21
- }
22
-
23
- interface GitStats {
24
- total_files: number;
25
- total_additions: number;
26
- total_deletions: number;
27
- files_by_extension: Record<string, number>;
28
- directories_affected: string[];
29
- }
30
-
31
- interface CommitAnalysis {
32
- files: FileChange[];
33
- stats: GitStats;
34
- project_context: {
35
- name: string;
36
- description: string;
37
- tech_stack: string[];
38
- target_platform: string;
39
- };
40
- commit_patterns: string;
41
- }
42
-
43
- interface CommitProposal {
44
- title: string;
45
- description: string;
46
- technical: string;
47
- changelog: string;
48
- files?: string[];
49
- }
50
-
51
- class CommitGenerator {
52
- private projectRoot: string;
53
- private tempDir: string;
54
- private autoApprove: boolean;
55
- private noPush: boolean;
56
- private quiet: boolean;
57
- private proposalFile?: string;
58
- private outputDir?: string;
59
-
60
- constructor() {
61
- this.projectRoot = process.cwd();
62
- this.tempDir = this.getArgValue('--output-dir') || join(this.projectRoot, 'project-utils/.temp');
63
- this.autoApprove = process.argv.includes('--auto-approve');
64
- this.noPush = process.argv.includes('--no-push');
65
- this.quiet = process.argv.includes('--quiet');
66
- this.proposalFile = this.getArgValue('--proposal-file');
67
- this.outputDir = this.getArgValue('--output-dir');
68
- this.ensureTempDir();
69
- }
70
-
71
- private getArgValue(argName: string): string | undefined {
72
- const args = process.argv;
73
- const argIndex = args.indexOf(argName);
74
- if (argIndex > -1 && args[argIndex + 1] && !args[argIndex + 1].startsWith('--')) {
75
- return args[argIndex + 1];
76
- }
77
- return undefined;
78
- }
79
-
80
- private ensureTempDir(): void {
81
- if (!existsSync(this.tempDir)) {
82
- const { spawnSync } = require('child_process');
83
- spawnSync('mkdir', ['-p', this.tempDir], { stdio: 'ignore' });
84
- }
85
- }
86
-
87
- private log(message: string): void {
88
- if (!this.quiet) {
89
- console.log(message);
90
- }
91
- }
92
-
93
- private logInfo(message: string): void {
94
- if (!this.quiet) {
95
- console.log(message);
96
- }
97
- }
98
-
99
- /**
100
- * Ejecuta un comando git y devuelve el resultado
101
- */
102
- private async gitCommand(args: string[]): Promise<string> {
103
- const { spawnSync } = await import('child_process');
104
- const result = spawnSync('git', args, {
105
- cwd: this.projectRoot,
106
- encoding: 'utf-8',
107
- stdio: ['pipe', 'pipe', 'pipe']
108
- });
109
-
110
- if (result.status !== 0) {
111
- const error = result.stderr || 'Git command failed';
112
- throw new Error(`Git error: ${error}`);
113
- }
114
-
115
- return (result.stdout || '').trim();
116
- }
117
-
118
- /**
119
- * Agrega todos los cambios al staging area
120
- */
121
- private async stageAllChanges(): Promise<void> {
122
- this.log('📦 Agregando todos los cambios al staging area...');
123
- await this.gitCommand(['add', '-A']);
124
- }
125
-
126
- /**
127
- * Obtiene el estado actual del repositorio
128
- */
129
- private async getRepositoryStatus(): Promise<FileChange[]> {
130
- this.log('🔍 Analizando estado del repositorio...');
131
-
132
- const statusOutput = await this.gitCommand(['status', '--porcelain']);
133
- const files: FileChange[] = [];
134
-
135
- for (const line of statusOutput.split('\n').filter(l => l.trim())) {
136
- const status = line.substring(0, 2);
137
- const filePath = line.substring(3);
138
-
139
- let fileStatus: FileChange['status'];
140
- if (status.includes('A')) fileStatus = 'added';
141
- else if (status.includes('M')) fileStatus = 'modified';
142
- else if (status.includes('D')) fileStatus = 'deleted';
143
- else if (status.includes('R')) fileStatus = 'renamed';
144
- else fileStatus = 'untracked';
145
-
146
- files.push({
147
- path: filePath,
148
- status: fileStatus,
149
- });
150
- }
151
-
152
- return files;
153
- }
154
-
155
- /**
156
- * Obtiene el diff de un archivo específico
157
- */
158
- private async getFileDiff(filePath: string, isStaged: boolean = true): Promise<string> {
159
- try {
160
- const diffArgs = isStaged
161
- ? ['diff', '--cached', '--', filePath]
162
- : ['diff', '--', filePath];
163
-
164
- return await this.gitCommand(diffArgs);
165
- } catch (error) {
166
- // Si es un archivo nuevo o binario, devolver información básica
167
- try {
168
- const showArgs = ['show', `HEAD:${filePath}`];
169
- await this.gitCommand(showArgs);
170
- return `New file: ${filePath}`;
171
- } catch {
172
- return `Binary or new file: ${filePath}`;
173
- }
174
- }
175
- }
176
-
177
- /**
178
- * Obtiene estadísticas del repositorio
179
- */
180
- private async getGitStats(): Promise<GitStats> {
181
- this.log('📊 Calculando estadísticas de cambios...');
182
-
183
- try {
184
- const diffStat = await this.gitCommand(['diff', '--cached', '--stat']);
185
- const lines = diffStat.split('\n').filter(l => l.trim());
186
-
187
- let totalFiles = 0;
188
- let totalAdditions = 0;
189
- let totalDeletions = 0;
190
- const filesByExtension: Record<string, number> = {};
191
- const directoriesAffected = new Set<string>();
192
-
193
- for (const line of lines) {
194
- if (line.includes('|')) {
195
- totalFiles++;
196
- const filePath = line.split('|')[0].trim();
197
-
198
- // Extraer extensión
199
- const ext = filePath.split('.').pop() || 'no-ext';
200
- filesByExtension[ext] = (filesByExtension[ext] || 0) + 1;
201
-
202
- // Extraer directorio
203
- const dir = filePath.split('/')[0];
204
- directoriesAffected.add(dir);
205
-
206
- // Extraer adiciones y eliminaciones
207
- const stats = line.split('|')[1];
208
- const plusCount = (stats.match(/\+/g) || []).length;
209
- const minusCount = (stats.match(/\-/g) || []).length;
210
- totalAdditions += plusCount;
211
- totalDeletions += minusCount;
212
- }
213
- }
214
-
215
- return {
216
- total_files: totalFiles,
217
- total_additions: totalAdditions,
218
- total_deletions: totalDeletions,
219
- files_by_extension: filesByExtension,
220
- directories_affected: Array.from(directoriesAffected),
221
- };
222
- } catch (error) {
223
- return {
224
- total_files: 0,
225
- total_additions: 0,
226
- total_deletions: 0,
227
- files_by_extension: {},
228
- directories_affected: [],
229
- };
230
- }
231
- }
232
-
233
- /**
234
- * Genera el contexto completo para Gemini CLI
235
- */
236
- private async generateAnalysisContext(): Promise<CommitAnalysis> {
237
- this.log('🧠 Generando contexto de análisis...');
238
-
239
- await this.stageAllChanges();
240
-
241
- const files = await this.getRepositoryStatus();
242
- const stats = await this.getGitStats();
243
-
244
- // Obtener diffs para cada archivo
245
- for (const file of files) {
246
- if (file.status !== 'deleted') {
247
- try {
248
- file.diff = await this.getFileDiff(file.path);
249
-
250
- // Calcular líneas agregadas/eliminadas del diff
251
- if (file.diff) {
252
- file.lines_added = (file.diff.match(/^\+[^+]/gm) || []).length;
253
- file.lines_removed = (file.diff.match(/^-[^-]/gm) || []).length;
254
- file.is_binary = file.diff.includes('Binary files differ');
255
- }
256
- } catch (error) {
257
- file.diff = `Error getting diff: ${error}`;
258
- }
259
- }
260
- }
261
-
262
- // Cargar patrones de commit
263
- const patternsPath = join(this.projectRoot, 'commit-templates/commit-patterns.md');
264
- const commitPatterns = existsSync(patternsPath)
265
- ? readFileSync(patternsPath, 'utf-8')
266
- : 'No commit patterns found';
267
-
268
- return {
269
- files,
270
- stats,
271
- project_context: {
272
- name: 'OpenTUI',
273
- description: 'Modern Terminal User Interface Framework',
274
- tech_stack: ['TypeScript', 'Node.js', 'Terminal UI', 'CLI'],
275
- target_platform: 'Cross-platform (macOS, Linux, Windows)',
276
- },
277
- commit_patterns: commitPatterns,
278
- };
279
- }
280
-
281
- private createStandardPrompt(analysis: CommitAnalysis, extraContext: string = ''): string {
282
- const config: GeminiPromptConfig = {
283
- projectContext: {
284
- name: BETTER_LOGGER_PROJECT_CONFIG.name,
285
- description: BETTER_LOGGER_PROJECT_CONFIG.description,
286
- version: BETTER_LOGGER_PROJECT_CONFIG.version,
287
- techStack: [...BETTER_LOGGER_PROJECT_CONFIG.techStack],
288
- targetPlatform: BETTER_LOGGER_PROJECT_CONFIG.targetPlatform,
289
- },
290
- analysisType: 'commit',
291
- specificContext: extraContext,
292
- data: {
293
- stats: analysis.stats,
294
- files: analysis.files.map(file => ({
295
- path: file.path,
296
- status: file.status,
297
- lines_added: file.lines_added,
298
- lines_removed: file.lines_removed,
299
- is_binary: file.is_binary,
300
- diff_preview: file.diff?.substring(0, 1500) || 'No diff available'
301
- })),
302
- patterns: analysis.commit_patterns
303
- }
304
- };
305
-
306
- return createCommitPrompt(config);
307
- }
308
-
309
- private createExhaustivePrompt(analysis: CommitAnalysis, extraContext: string = ''): string {
310
- const config: GeminiPromptConfig = {
311
- projectContext: {
312
- name: BETTER_LOGGER_PROJECT_CONFIG.name,
313
- description: BETTER_LOGGER_PROJECT_CONFIG.description,
314
- version: BETTER_LOGGER_PROJECT_CONFIG.version,
315
- techStack: [...BETTER_LOGGER_PROJECT_CONFIG.techStack],
316
- targetPlatform: BETTER_LOGGER_PROJECT_CONFIG.targetPlatform,
317
- },
318
- analysisType: 'commit',
319
- specificContext: `MODO EXHAUSTIVO: Análisis profundo requerido.\n${extraContext}`,
320
- data: {
321
- mode: 'exhaustive',
322
- stats: analysis.stats,
323
- files: analysis.files.map(file => ({
324
- path: file.path,
325
- status: file.status,
326
- lines_added: file.lines_added,
327
- lines_removed: file.lines_removed,
328
- is_binary: file.is_binary,
329
- diff_preview: file.diff?.substring(0, 2000) || 'No diff available'
330
- })),
331
- patterns: analysis.commit_patterns
332
- }
333
- };
334
-
335
- return createCommitPrompt(config);
336
- }
337
-
338
- /**
339
- * Construye contexto mejorado con parámetros adicionales
340
- */
341
- private buildEnhancedContext(
342
- extraContext: string,
343
- contextDescription: string,
344
- workType: string,
345
- affectedComponents: string,
346
- performanceImpact: string,
347
- breakingChanges: string
348
- ): string {
349
- let enhancedContext = extraContext;
350
-
351
- const contextParts = [];
352
-
353
- if (contextDescription) {
354
- contextParts.push(`**Descripción del trabajo**: ${contextDescription}`);
355
- }
356
-
357
- if (workType) {
358
- const workTypeDescriptions = {
359
- 'feature': 'Nueva funcionalidad o capacidad',
360
- 'bugfix': 'Corrección de error o fallo',
361
- 'refactor': 'Mejora del código sin cambios de funcionalidad',
362
- 'docs': 'Actualización de documentación',
363
- 'performance': 'Optimización de rendimiento',
364
- 'ui': 'Cambios en interfaz de usuario',
365
- 'api': 'Modificaciones en API o endpoints',
366
- 'security': 'Mejoras de seguridad',
367
- 'test': 'Adición o modificación de tests'
368
- };
369
- contextParts.push(`**Tipo de trabajo**: ${workType} - ${workTypeDescriptions[workType] || workType}`);
370
- }
371
-
372
- if (affectedComponents) {
373
- contextParts.push(`**Componentes afectados**: ${affectedComponents}`);
374
- }
375
-
376
- if (performanceImpact) {
377
- const performanceDescriptions = {
378
- 'mejora': 'Este cambio mejora el rendimiento del sistema',
379
- 'neutro': 'Este cambio no afecta significativamente el rendimiento',
380
- 'regresion': 'Este cambio puede impactar negativamente el rendimiento (justificado por otros beneficios)'
381
- };
382
- contextParts.push(`**Impacto en rendimiento**: ${performanceImpact} - ${performanceDescriptions[performanceImpact] || performanceImpact}`);
383
- }
384
-
385
- if (breakingChanges) {
386
- const breakingDescription = breakingChanges.toLowerCase() === 'si'
387
- ? 'Este cambio introduce cambios que rompen compatibilidad hacia atrás'
388
- : 'Este cambio mantiene compatibilidad hacia atrás';
389
- contextParts.push(`**Cambios incompatibles**: ${breakingChanges} - ${breakingDescription}`);
390
- }
391
-
392
- if (contextParts.length > 0) {
393
- const contextSection = contextParts.join('\n');
394
- enhancedContext = enhancedContext
395
- ? `${enhancedContext}\n\n## Contexto Estructurado\n\n${contextSection}`
396
- : `## Contexto Estructurado\n\n${contextSection}`;
397
- }
398
-
399
- return enhancedContext;
400
- }
401
-
402
- /**
403
- * Invoca Gemini CLI con el contexto de análisis
404
- */
405
- private async analyzeWithGemini(analysis: CommitAnalysis, exhaustive: boolean = false, extraContext: string = ''): Promise<string> {
406
- this.log(`🤖 Analizando cambios con Gemini CLI... ${exhaustive ? '(Modo Exhaustivo)' : ''}`);
407
-
408
- const prompt = exhaustive
409
- ? this.createExhaustivePrompt(analysis, extraContext)
410
- : this.createStandardPrompt(analysis, extraContext);
411
-
412
- // Guardar el contexto en un archivo temporal
413
- const contextPath = join(this.tempDir, 'analysis-context.json');
414
- writeFileSync(contextPath, JSON.stringify(analysis, null, 2));
415
-
416
- // Guardar el prompt en un archivo temporal
417
- const promptPath = join(this.tempDir, 'gemini-prompt.txt');
418
- writeFileSync(promptPath, prompt);
419
-
420
- try {
421
- // Ejecutar Gemini CLI
422
- const geminiResult = Bun.spawnSync(['gemini'], {
423
- cwd: this.projectRoot,
424
- stdin: Buffer.from(prompt) as any,
425
- stdout: 'pipe',
426
- stderr: 'pipe',
427
- });
428
-
429
- if (geminiResult.exitCode !== 0) {
430
- const error = geminiResult.stderr?.toString() || 'Gemini CLI failed';
431
- throw new Error(`Gemini CLI error: ${error}`);
432
- }
433
-
434
- const response = geminiResult.stdout?.toString() || '';
435
-
436
- // Guardar la respuesta
437
- const responsePath = join(this.tempDir, 'gemini-response.md');
438
- writeFileSync(responsePath, response);
439
-
440
- return response;
441
- } catch (error) {
442
- console.error('❌ Error ejecutando Gemini CLI:', error);
443
- console.error('💡 Verifica que Gemini CLI esté instalado y configurado');
444
- console.log('📝 Contexto guardado en:', contextPath);
445
- console.log('📝 Prompt guardado en:', promptPath);
446
- throw error;
447
- }
448
- }
449
-
450
- /**
451
- * Guarda la propuesta de commits
452
- */
453
- private saveCommitProposal(analysis: string): string {
454
- const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
455
- const proposalPath = join(this.tempDir, `commit-proposal-${timestamp}.md`);
456
-
457
- writeFileSync(proposalPath, analysis);
458
- return proposalPath;
459
- }
460
-
461
- /**
462
- * Parsea propuestas de commit de la respuesta de Gemini
463
- */
464
- private parseCommitProposals(aiResponse: string, allFiles: FileChange[]): CommitProposal[] {
465
- // Usar el parser estandarizado
466
- const parsedProposals = GeminiResponseParser.parseCommitProposals(aiResponse);
467
-
468
- // Si hay múltiples commits, dividir archivos inteligentemente
469
- if (parsedProposals.length > 1) {
470
- return this.distributeFilesAcrossCommits(parsedProposals, allFiles);
471
- }
472
-
473
- // Commit único usa todos los archivos
474
- return parsedProposals.map(proposal => ({
475
- title: proposal.title,
476
- description: proposal.description,
477
- technical: proposal.technical,
478
- changelog: proposal.changelog,
479
- files: allFiles.map(f => f.path).filter(path =>
480
- !path.includes('.temp/') &&
481
- !path.startsWith('.release-notes-') &&
482
- !path.includes(' -> ')
483
- )
484
- }));
485
- }
486
-
487
- /**
488
- * Distribuye archivos entre múltiples commits basado en patrones
489
- */
490
- private distributeFilesAcrossCommits(
491
- parsedProposals: any[],
492
- allFiles: FileChange[]
493
- ): CommitProposal[] {
494
- const availableFiles = allFiles
495
- .map(f => f.path)
496
- .filter(path =>
497
- !path.includes('.temp/') &&
498
- !path.startsWith('.release-notes-') &&
499
- !path.includes(' -> ')
500
- );
501
-
502
- const fileDistribution = new Map<string, string[]>();
503
- const usedFiles = new Set<string>();
504
-
505
- // Patterns para clasificar archivos por tipo de commit
506
- const patterns = {
507
- ci: ['.github/workflows/', 'project-utils/', '.yml'],
508
- architecture: ['package.json', 'packages/', 'vite.config.ts', 'tsconfig.json'],
509
- core: ['src/core', 'src/Logger.ts', 'src/constants.ts'],
510
- styling: ['src/styling', 'src/style'],
511
- exports: ['src/exports', 'src/handlers/'],
512
- docs: ['README.md', 'docs/', '.md'],
513
- config: ['vite.config', 'tsconfig', '.json']
514
- };
515
-
516
- // Asignar archivos a commits basado en contenido/título
517
- parsedProposals.forEach((proposal, index) => {
518
- const commitFiles: string[] = [];
519
- const title = proposal.title.toLowerCase();
520
- const description = (proposal.description || '').toLowerCase();
521
-
522
- // Detectar tipo de commit por título/descripción
523
- let commitType = 'misc';
524
- if (title.includes('ci') || title.includes('workflow') || title.includes('bun')) {
525
- commitType = 'ci';
526
- } else if (title.includes('arquitectura') || title.includes('monorepo') || title.includes('modular')) {
527
- commitType = 'architecture';
528
- } else if (title.includes('core') || title.includes('logger')) {
529
- commitType = 'core';
530
- } else if (title.includes('styling') || title.includes('style')) {
531
- commitType = 'styling';
532
- } else if (title.includes('export') || title.includes('handler')) {
533
- commitType = 'exports';
534
- }
535
-
536
- // Asignar archivos que coincidan con el patrón
537
- if (patterns[commitType]) {
538
- availableFiles.forEach(file => {
539
- if (!usedFiles.has(file)) {
540
- const matchesPattern = patterns[commitType].some(pattern =>
541
- file.includes(pattern)
542
- );
543
- if (matchesPattern) {
544
- commitFiles.push(file);
545
- usedFiles.add(file);
546
- }
547
- }
548
- });
549
- }
550
-
551
- fileDistribution.set(`commit_${index}`, commitFiles);
552
- });
553
-
554
- // Asignar archivos restantes al primer commit (fallback)
555
- const remainingFiles = availableFiles.filter(file => !usedFiles.has(file));
556
- if (remainingFiles.length > 0) {
557
- const firstCommitFiles = fileDistribution.get('commit_0') || [];
558
- firstCommitFiles.push(...remainingFiles);
559
- fileDistribution.set('commit_0', firstCommitFiles);
560
- }
561
-
562
- // Crear propuestas finales
563
- return parsedProposals.map((proposal, index) => {
564
- const commitFiles = fileDistribution.get(`commit_${index}`) || [];
565
-
566
- // Log de asignación (solo en modo debug)
567
- if (!this.quiet) {
568
- console.log(`📋 Commit ${index + 1}: "${proposal.title}" → ${commitFiles.length} archivos`);
569
- commitFiles.forEach(file => console.log(` - ${file}`));
570
- }
571
-
572
- return {
573
- title: proposal.title,
574
- description: proposal.description,
575
- technical: proposal.technical,
576
- changelog: proposal.changelog,
577
- files: commitFiles
578
- };
579
- });
580
- }
581
-
582
- /**
583
- * Ejecuta un commit individual
584
- */
585
- private async executeCommit(proposal: CommitProposal, allFiles: FileChange[]): Promise<boolean> {
586
- this.log(`\n🔨 Ejecutando commit: ${proposal.title}`);
587
-
588
- try {
589
- // Si no hay archivos específicos, usar todos los archivos disponibles (excluyendo temp files)
590
- const targetFiles = proposal.files && proposal.files.length > 0
591
- ? proposal.files
592
- : allFiles
593
- .map(f => f.path)
594
- .filter(path => !path.includes('.temp/') && !path.startsWith('.release-notes-'))
595
- .filter(path => !path.includes(' -> ')); // Filtrar sintaxis de rename "file.yml -> backup/file.yml"
596
-
597
- // Validación pre-commit: verificar que hay archivos para procesar
598
- if (targetFiles.length === 0) {
599
- console.warn(` ⚠️ No hay archivos asignados a este commit`);
600
- return false;
601
- }
602
-
603
- this.log(` 📋 Procesando ${targetFiles.length} archivos asignados`);
604
-
605
- // Agregar archivos específicos al staging area
606
- let stagedFiles = 0;
607
- for (const file of targetFiles) {
608
- try {
609
- await this.gitCommand(['add', file]);
610
- stagedFiles++;
611
- this.log(` ✓ Agregado: ${file}`);
612
- } catch (error) {
613
- console.warn(` ⚠️ No se pudo agregar ${file}:`, error);
614
- }
615
- }
616
-
617
- // Validación: verificar que se staged al menos un archivo
618
- if (stagedFiles === 0) {
619
- console.warn(` ⚠️ No se pudo agregar ningún archivo al staging area`);
620
- return false;
621
- }
622
-
623
- // Verificar que hay algo para commitear
624
- try {
625
- const statusResult = await this.gitCommand(['diff', '--cached', '--name-only']);
626
- if (!statusResult.trim()) {
627
- console.warn(` ⚠️ No hay cambios staged para este commit (staged files: ${stagedFiles})`);
628
- return false;
629
- }
630
-
631
- const stagedFilesList = statusResult.trim().split('\n');
632
- this.log(` 📦 ${stagedFilesList.length} archivos listos para commit`);
633
-
634
- } catch (error) {
635
- // Fallback si diff --cached no funciona
636
- this.log(` 🔍 Verificando staging area...`);
637
- }
638
-
639
- // Crear mensaje de commit
640
- let commitMessage = proposal.title;
641
- if (proposal.description) {
642
- commitMessage += `\n\n${proposal.description}`;
643
- }
644
- if (proposal.technical) {
645
- commitMessage += `\n\n<technical>\n${proposal.technical}\n</technical>`;
646
- }
647
- if (proposal.changelog) {
648
- commitMessage += `\n\n<changelog>\n${proposal.changelog}\n</changelog>`;
649
- }
650
-
651
- // Ejecutar commit
652
- await this.gitCommand(['commit', '-m', commitMessage]);
653
- this.log(` ✅ Commit exitoso`);
654
- return true;
655
-
656
- } catch (error) {
657
- console.error(` ❌ Error en commit:`, error);
658
- return false;
659
- }
660
- }
661
-
662
- /**
663
- * Detecta si estamos ejecutando en un contexto de release
664
- * - GITHUB_ACTIONS: ejecutando desde workflow
665
- * - RELEASE_WORKFLOW: variable específica de release workflow
666
- * - Análisis de archivos modificados relacionados con release
667
- */
668
- private isReleaseWorkflowContext(): boolean {
669
- // Contexto explícito de GitHub Actions release
670
- if (process.env.GITHUB_ACTIONS === 'true' && process.env.RELEASE_WORKFLOW === 'true') {
671
- return true;
672
- }
673
-
674
- // Detección por archivos modificados relacionados con release
675
- return this.hasReleaseRelatedFiles();
676
- }
677
-
678
- /**
679
- * Verifica si los archivos modificados están relacionados con releases
680
- */
681
- private hasReleaseRelatedFiles(): boolean {
682
- try {
683
- // Obtener archivos staged (ya llamado en generateAnalysisContext)
684
- const stagedFiles = this.getAllStagedFiles();
685
-
686
- const releasePatterns = [
687
- 'package.json', // Cambios de versión
688
- 'packages/*/package.json', // Sub-packages
689
- 'CHANGELOG.json', // Cambios en changelog
690
- '.release-notes-', // Archivos de release notes
691
- 'project-utils/.temp/' // Archivos temporales de release
692
- ];
693
-
694
- return stagedFiles.some(file =>
695
- releasePatterns.some(pattern => {
696
- if (pattern.includes('*')) {
697
- // Patrón con wildcard
698
- const regex = new RegExp(pattern.replace('*', '.*'));
699
- return regex.test(file);
700
- } else {
701
- // Patrón exacto
702
- return file.includes(pattern);
703
- }
704
- })
705
- );
706
- } catch (error) {
707
- // Si no podemos verificar, asumir contexto normal
708
- return false;
709
- }
710
- }
711
-
712
- /**
713
- * Detecta el tipo específico de conflictos de git
714
- */
715
- private async analyzeConflictType(): Promise<{
716
- hasConflicts: boolean;
717
- conflictType: 'none' | 'merge' | 'rebase' | 'tmp-files' | 'manual';
718
- conflictedFiles: string[];
719
- canAutoResolve: boolean;
720
- requiresManualIntervention: boolean;
721
- }> {
722
- try {
723
- // Verificar si hay archivos con conflictos de merge
724
- const conflictedFiles = await this.getConflictedFiles();
725
-
726
- if (conflictedFiles.length === 0) {
727
- return {
728
- hasConflicts: false,
729
- conflictType: 'none',
730
- conflictedFiles: [],
731
- canAutoResolve: true,
732
- requiresManualIntervention: false
733
- };
734
- }
735
-
736
- // Analizar tipo de conflictos
737
- const tmpFiles = conflictedFiles.filter(file =>
738
- file.includes('.temp') ||
739
- file.includes('tmp') ||
740
- file.includes('.cache') ||
741
- file.startsWith('project-utils/.temp/')
742
- );
743
-
744
- const criticalFiles = conflictedFiles.filter(file =>
745
- file.includes('src/') ||
746
- file.includes('package.json') ||
747
- file.includes('tsconfig.json') ||
748
- file.includes('.github/workflows/')
749
- );
750
-
751
- // Determinar si es auto-resoluble
752
- const canAutoResolve = tmpFiles.length > 0 && criticalFiles.length === 0;
753
-
754
- let conflictType: 'merge' | 'rebase' | 'tmp-files' | 'manual' = 'manual';
755
-
756
- if (tmpFiles.length === conflictedFiles.length) {
757
- conflictType = 'tmp-files';
758
- } else if (await this.isInRebaseState()) {
759
- conflictType = 'rebase';
760
- } else {
761
- conflictType = 'merge';
762
- }
763
-
764
- return {
765
- hasConflicts: true,
766
- conflictType,
767
- conflictedFiles,
768
- canAutoResolve,
769
- requiresManualIntervention: criticalFiles.length > 0
770
- };
771
- } catch (error) {
772
- this.log(`❌ Error analizando conflictos: ${error}`);
773
- return {
774
- hasConflicts: false,
775
- conflictType: 'none',
776
- conflictedFiles: [],
777
- canAutoResolve: false,
778
- requiresManualIntervention: true
779
- };
780
- }
781
- }
782
-
783
- /**
784
- * Obtiene archivos con conflictos de merge/rebase
785
- */
786
- private async getConflictedFiles(): Promise<string[]> {
787
- try {
788
- const { spawnSync } = require('child_process');
789
- const result = spawnSync('git', ['diff', '--name-only', '--diff-filter=U'], {
790
- cwd: this.projectRoot,
791
- encoding: 'utf-8',
792
- stdio: ['pipe', 'pipe', 'pipe']
793
- });
794
-
795
- if (result.status !== 0) {
796
- return [];
797
- }
798
-
799
- return (result.stdout || '').trim().split('\n').filter(line => line.length > 0);
800
- } catch (error) {
801
- return [];
802
- }
803
- }
804
-
805
- /**
806
- * Verifica si estamos en estado de rebase
807
- */
808
- private async isInRebaseState(): Promise<boolean> {
809
- try {
810
- const { existsSync } = require('fs');
811
- const rebaseMergeDir = join(this.projectRoot, '.git', 'rebase-merge');
812
- const rebaseApplyDir = join(this.projectRoot, '.git', 'rebase-apply');
813
- return existsSync(rebaseMergeDir) || existsSync(rebaseApplyDir);
814
- } catch {
815
- return false;
816
- }
817
- }
818
-
819
- /**
820
- * Resuelve conflictos automáticamente cuando es seguro
821
- */
822
- private async autoResolveConflicts(conflictAnalysis: any): Promise<boolean> {
823
- if (!conflictAnalysis.canAutoResolve) {
824
- return false;
825
- }
826
-
827
- this.log('🔧 Intentando resolución automática de conflictos...');
828
-
829
- try {
830
- // Para archivos temporales, usar la versión local (nuestra)
831
- for (const file of conflictAnalysis.conflictedFiles) {
832
- if (file.includes('.temp') || file.includes('tmp') || file.includes('.cache')) {
833
- await this.gitCommand(['add', file]);
834
- this.log(`✅ Auto-resuelto conflicto en archivo temporal: ${file}`);
835
- }
836
- }
837
-
838
- return true;
839
- } catch (error) {
840
- this.log(`❌ Error en resolución automática: ${error}`);
841
- return false;
842
- }
843
- }
844
-
845
- /**
846
- * Crea backup de cambios importantes antes de operaciones peligrosas
847
- */
848
- private async createSafetyBackup(): Promise<string> {
849
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
850
- const backupBranch = `backup-before-conflict-resolution-${timestamp}`;
851
-
852
- try {
853
- // Crear rama de backup
854
- await this.gitCommand(['checkout', '-b', backupBranch]);
855
-
856
- // Volver a la rama original
857
- const currentBranch = await this.getCurrentBranch();
858
- await this.gitCommand(['checkout', currentBranch]);
859
-
860
- this.log(`💾 Backup creado en rama: ${backupBranch}`);
861
- return backupBranch;
862
- } catch (error) {
863
- this.log(`⚠️ No se pudo crear backup: ${error}`);
864
- throw error;
865
- }
866
- }
867
-
868
- /**
869
- * Obtiene la rama actual
870
- */
871
- private async getCurrentBranch(): Promise<string> {
872
- try {
873
- const result = await this.gitCommand(['rev-parse', '--abbrev-ref', 'HEAD']);
874
- return result.trim();
875
- } catch {
876
- return 'main';
877
- }
878
- }
879
-
880
- /**
881
- * Obtiene todos los archivos en staging area
882
- */
883
- private getAllStagedFiles(): string[] {
884
- try {
885
- const { spawnSync } = require('child_process');
886
- const result = spawnSync('git', ['diff', '--cached', '--name-only'], {
887
- cwd: this.projectRoot,
888
- encoding: 'utf-8',
889
- stdio: ['pipe', 'pipe', 'pipe']
890
- });
891
-
892
- if (result.status !== 0) {
893
- return [];
894
- }
895
-
896
- return (result.stdout || '').trim().split('\n').filter(Boolean);
897
- } catch (error) {
898
- return [];
899
- }
900
- }
901
-
902
- /**
903
- * Maneja commits en contexto de release con estrategia anti-conflictos
904
- */
905
- private async handleReleaseWorkflowCommit(): Promise<void> {
906
- this.log('🔄 Contexto de release detectado - aplicando estrategia anti-conflictos...');
907
-
908
- try {
909
- const currentBranch = await this.gitCommand(['branch', '--show-current']);
910
-
911
- // Estrategia: Pull-Rebase-Push con reintentos (MAX_RETRIES=3)
912
- let retryCount = 0;
913
- const maxRetries = 3;
914
- const MAX_RETRIES = 3;
915
-
916
- while (retryCount < maxRetries) {
917
- try {
918
- // Paso 1: Fetch último estado del remote (git fetch origin)
919
- this.log(`🔄 Intento ${retryCount + 1}/${maxRetries}: Sincronizando con remote...`);
920
- await this.gitCommand(['fetch', 'origin', currentBranch]);
921
-
922
- // Paso 2: Intentar rebase automático (git rebase)
923
- try {
924
- await this.gitCommand(['rebase', `origin/${currentBranch}`]);
925
- this.log('✅ Rebase completado exitosamente');
926
- } catch (rebaseError) {
927
- this.log('⚠️ Conflictos detectados durante rebase - analizando...');
928
-
929
- // Analizar tipo de conflictos
930
- const conflictAnalysis = await this.analyzeConflictType();
931
-
932
- if (conflictAnalysis.hasConflicts) {
933
- this.log(`📊 Análisis de conflictos:`);
934
- this.log(` Tipo: ${conflictAnalysis.conflictType}`);
935
- this.log(` Archivos: ${conflictAnalysis.conflictedFiles.join(', ')}`);
936
- this.log(` Auto-resoluble: ${conflictAnalysis.canAutoResolve ? 'Sí' : 'No'}`);
937
- this.log(` Intervención manual: ${conflictAnalysis.requiresManualIntervention ? 'Requerida' : 'No necesaria'}`);
938
- }
939
-
940
- // Crear backup antes de intentar resolución
941
- let backupBranch: string | null = null;
942
- if (conflictAnalysis.requiresManualIntervention) {
943
- try {
944
- backupBranch = await this.createSafetyBackup();
945
- } catch (backupError) {
946
- this.log(`⚠️ No se pudo crear backup: ${backupError}`);
947
- }
948
- }
949
-
950
- // Intentar resolución automática
951
- if (conflictAnalysis.canAutoResolve) {
952
- const resolved = await this.autoResolveConflicts(conflictAnalysis);
953
- if (resolved) {
954
- this.log('✅ Conflictos resueltos automáticamente, continuando rebase...');
955
- await this.gitCommand(['rebase', '--continue']);
956
- return; // Continuar con el flujo normal
957
- }
958
- }
959
-
960
- // Si no se pudo resolver automáticamente, aplicar estrategia conservadora
961
- this.log('⚠️ Aplicando estrategia conservadora (reset hard)');
962
- await this.gitCommand(['rebase', '--abort']);
963
- await this.gitCommand(['reset', '--hard', `origin/${currentBranch}`]);
964
-
965
- // Re-aplicar nuestros commits si es necesario
966
- const hasLocalCommits = await this.hasUnpushedCommits();
967
- if (hasLocalCommits) {
968
- this.log('🔄 Re-aplicando cambios locales...');
969
- const errorMsg = `Conflictos no resolubles automáticamente - requiere intervención manual.${backupBranch ? ` Backup disponible en rama: ${backupBranch}` : ''}`;
970
- throw new Error(errorMsg);
971
- }
972
- }
973
-
974
- // Paso 3: Push con fuerza controlled
975
- await this.gitCommand(['push', 'origin', currentBranch]);
976
- this.log('✅ Push en contexto release completado');
977
- return;
978
-
979
- } catch (pushError) {
980
- retryCount++;
981
- if (retryCount >= maxRetries) {
982
- throw pushError;
983
- }
984
-
985
- this.log(`⚠️ Push falló (intento ${retryCount}), reintentando en 2s...`);
986
- await new Promise(resolve => setTimeout(resolve, 2000));
987
- }
988
- }
989
-
990
- } catch (error) {
991
- console.error('❌ Error en manejo de contexto release:', error);
992
- throw error;
993
- }
994
- }
995
-
996
- /**
997
- * Verifica si hay commits locales no pusheados
998
- */
999
- private async hasUnpushedCommits(): Promise<boolean> {
1000
- try {
1001
- const currentBranch = await this.gitCommand(['branch', '--show-current']);
1002
- const unpushedOutput = await this.gitCommand(['log', `origin/${currentBranch}..HEAD`, '--oneline']);
1003
- return unpushedOutput.trim().length > 0;
1004
- } catch (error) {
1005
- // Si no podemos verificar, asumir que sí hay commits locales
1006
- return true;
1007
- }
1008
- }
1009
-
1010
- /**
1011
- * Ejecuta push de todos los commits
1012
- */
1013
- private async pushCommits(): Promise<void> {
1014
- if (this.noPush) {
1015
- this.log('⏭️ Push deshabilitado por --no-push');
1016
- return;
1017
- }
1018
-
1019
- this.log('\n📤 Pushing commits to remote...');
1020
-
1021
- // Detectar si estamos en contexto de release y usar estrategia especializada
1022
- if (this.isReleaseWorkflowContext()) {
1023
- return this.handleReleaseWorkflowCommit();
1024
- }
1025
-
1026
- // Push estándar para contexto de desarrollo
1027
- try {
1028
- const currentBranch = await this.gitCommand(['branch', '--show-current']);
1029
- await this.gitCommand(['push', 'origin', currentBranch]);
1030
- this.log('✅ Push completado exitosamente');
1031
- } catch (error) {
1032
- console.error('❌ Error en push:', error);
1033
- this.log('💡 Los commits están en tu repositorio local');
1034
- throw error;
1035
- }
1036
- }
1037
-
1038
- /**
1039
- * Valida que auto-approve es seguro de ejecutar
1040
- */
1041
- private async validateAutoApprove(): Promise<boolean> {
1042
- try {
1043
- // Verificar que estamos en la rama correcta (master o main)
1044
- const currentBranch = await this.gitCommand(['branch', '--show-current']);
1045
- const validBranches = ['master', 'main'];
1046
- if (!validBranches.includes(currentBranch)) {
1047
- console.warn(`⚠️ No estás en una rama principal válida (actual: ${currentBranch}, válidas: ${validBranches.join(', ')})`);
1048
- return false;
1049
- }
1050
-
1051
- // Verificar que el repositorio está limpio (sin conflictos)
1052
- const statusOutput = await this.gitCommand(['status', '--porcelain']);
1053
- const conflicts = statusOutput.split('\n').filter(line => line.startsWith('UU'));
1054
- if (conflicts.length > 0) {
1055
- console.error('❌ Hay conflictos de merge sin resolver');
1056
- return false;
1057
- }
1058
-
1059
- return true;
1060
- } catch (error) {
1061
- console.error('❌ Error validando repositorio:', error);
1062
- return false;
1063
- }
1064
- }
1065
-
1066
- /**
1067
- * Ejecuta commits desde un archivo de propuesta existente
1068
- */
1069
- private async executeFromProposalFile(proposalPath: string): Promise<void> {
1070
- this.log(`📂 Cargando propuesta desde: ${proposalPath}`);
1071
-
1072
- try {
1073
- const proposalContent = readFileSync(proposalPath, 'utf-8');
1074
-
1075
- // Obtener archivos disponibles
1076
- const analysis = await this.generateAnalysisContext();
1077
- const proposals = this.parseCommitProposals(proposalContent, analysis.files);
1078
-
1079
- if (proposals.length === 0) {
1080
- console.error('❌ No se encontraron commits válidos en el archivo de propuesta');
1081
- return;
1082
- }
1083
-
1084
- this.log(`📦 Encontrados ${proposals.length} commits en la propuesta:`);
1085
- proposals.forEach((p, i) => {
1086
- this.log(` ${i + 1}. ${p.title}`);
1087
- });
1088
-
1089
- if (!this.autoApprove) {
1090
- console.log('\n💡 Usa --auto-approve para ejecutar estos commits automáticamente');
1091
- return;
1092
- }
1093
-
1094
- // Obtener archivos actuales para el contexto
1095
- const files = await this.getRepositoryStatus();
1096
-
1097
- let successfulCommits = 0;
1098
-
1099
- for (let i = 0; i < proposals.length; i++) {
1100
- const proposal = proposals[i];
1101
- const success = await this.executeCommit(proposal, files);
1102
- if (success) {
1103
- successfulCommits++;
1104
- } else {
1105
- console.error(`❌ Falló commit ${i + 1}: ${proposal.title}`);
1106
- }
1107
- }
1108
-
1109
- this.log(`\n📊 Resultados: ${successfulCommits}/${proposals.length} commits exitosos`);
1110
-
1111
- if (successfulCommits > 0) {
1112
- await this.pushCommits();
1113
- }
1114
-
1115
- this.log('\n✅ Ejecución desde archivo completada');
1116
-
1117
- } catch (error) {
1118
- console.error('❌ Error leyendo archivo de propuesta:', error);
1119
- throw error;
1120
- }
1121
- }
1122
-
1123
- /**
1124
- * Ejecuta typecheck para validar tipos antes del commit
1125
- */
1126
- private async runTypeCheck(): Promise<void> {
1127
- this.log('🔍 Ejecutando typecheck...');
1128
-
1129
- try {
1130
- const { spawnSync } = await import('child_process');
1131
- const result = spawnSync('npm', ['run', 'type-check'], {
1132
- cwd: this.projectRoot,
1133
- encoding: 'utf-8',
1134
- stdio: ['pipe', 'pipe', 'pipe']
1135
- });
1136
-
1137
- if (result.status !== 0) {
1138
- const error = result.stderr || result.stdout || 'Typecheck failed';
1139
- this.log(`❌ Typecheck falló:`);
1140
- this.log(error);
1141
- throw new Error(`Typecheck failed: ${error}`);
1142
- }
1143
-
1144
- this.log('✅ Typecheck completado correctamente');
1145
- } catch (error) {
1146
- this.log(`❌ Error ejecutando typecheck: ${error}`);
1147
- throw error;
1148
- }
1149
- }
1150
-
1151
- /**
1152
- * Ejecuta el generador completo
1153
- */
1154
- async generate(): Promise<void> {
1155
- // Si se especifica un archivo de propuesta existente, usarlo directamente
1156
- if (this.proposalFile && existsSync(this.proposalFile)) {
1157
- return this.executeFromProposalFile(this.proposalFile);
1158
- }
1159
-
1160
- this.log(`🚀 Iniciando generador de commits...${this.autoApprove ? ' (AUTO-APPROVE MODE)' : ''}\n`);
1161
-
1162
- // Ejecutar typecheck antes de continuar
1163
- await this.runTypeCheck();
1164
-
1165
- const args = process.argv.slice(2);
1166
- const isExhaustive = args.includes('-exhaustive');
1167
-
1168
- // Parsear parámetros de contexto mejorados
1169
- let extraContext = '';
1170
- let workType = '';
1171
- let contextDescription = '';
1172
- let affectedComponents = '';
1173
- let performanceImpact = '';
1174
- let breakingChanges = '';
1175
-
1176
- const extraIndex = args.indexOf('--extra');
1177
- if (extraIndex > -1 && args[extraIndex + 1]) {
1178
- extraContext = args[extraIndex + 1];
1179
- this.log(`💬 Contexto extra proporcionado por el usuario.`);
1180
- } else if (extraIndex > -1) {
1181
- console.warn('⚠️ El parámetro --extra requiere un valor de texto después.');
1182
- }
1183
-
1184
- const contextIndex = args.indexOf('--context');
1185
- if (contextIndex > -1 && args[contextIndex + 1]) {
1186
- contextDescription = args[contextIndex + 1];
1187
- this.log(`📋 Contexto del trabajo: ${contextDescription}`);
1188
- }
1189
-
1190
- const workTypeIndex = args.indexOf('--work-type');
1191
- if (workTypeIndex > -1 && args[workTypeIndex + 1]) {
1192
- workType = args[workTypeIndex + 1];
1193
- this.log(`🏷️ Tipo de trabajo: ${workType}`);
1194
- }
1195
-
1196
- const componentsIndex = args.indexOf('--affected-components');
1197
- if (componentsIndex > -1 && args[componentsIndex + 1]) {
1198
- affectedComponents = args[componentsIndex + 1];
1199
- this.log(`🎯 Componentes afectados: ${affectedComponents}`);
1200
- }
1201
-
1202
- const perfIndex = args.indexOf('--performance-impact');
1203
- if (perfIndex > -1 && args[perfIndex + 1]) {
1204
- performanceImpact = args[perfIndex + 1];
1205
- this.log(`⚡ Impacto en rendimiento: ${performanceImpact}`);
1206
- }
1207
-
1208
- const breakingIndex = args.indexOf('--breaking-changes');
1209
- if (breakingIndex > -1 && args[breakingIndex + 1]) {
1210
- breakingChanges = args[breakingIndex + 1];
1211
- this.log(`⚠️ Cambios que rompen compatibilidad: ${breakingChanges}`);
1212
- }
1213
-
1214
- try {
1215
- // Verificar que estamos en un repositorio git
1216
- await this.gitCommand(['status']);
1217
-
1218
- // Generar análisis completo
1219
- const analysis = await this.generateAnalysisContext();
1220
-
1221
- if (analysis.files.length === 0) {
1222
- this.log('✅ No hay cambios para procesar');
1223
- return;
1224
- }
1225
-
1226
- const fileCount = analysis.files.length;
1227
- const exhaustiveMode = isExhaustive || fileCount > 50;
1228
-
1229
- this.log(`📋 Encontrados ${fileCount} archivos modificados`);
1230
- this.log(`📊 Estadísticas: +${analysis.stats.total_additions} -${analysis.stats.total_deletions} líneas`);
1231
- if (exhaustiveMode) {
1232
- this.log('⚡️ Activado modo de análisis exhaustivo.');
1233
- }
1234
-
1235
- // Preparar contexto completo mejorado
1236
- const enhancedContext = this.buildEnhancedContext(
1237
- extraContext,
1238
- contextDescription,
1239
- workType,
1240
- affectedComponents,
1241
- performanceImpact,
1242
- breakingChanges
1243
- );
1244
-
1245
- // Analizar con Gemini
1246
- const commitProposal = await this.analyzeWithGemini(analysis, exhaustiveMode, enhancedContext);
1247
-
1248
- // Guardar propuesta
1249
- const proposalPath = this.saveCommitProposal(commitProposal);
1250
-
1251
- if (this.autoApprove) {
1252
- // Validar que es seguro ejecutar auto-approve
1253
- const isValid = await this.validateAutoApprove();
1254
- if (!isValid) {
1255
- console.error('❌ Auto-approve cancelado por validaciones de seguridad');
1256
- return;
1257
- }
1258
-
1259
- // Parsear y ejecutar commits
1260
- this.log('\n🤖 Ejecutando commits automáticamente...');
1261
- const proposals = this.parseCommitProposals(commitProposal, analysis.files);
1262
-
1263
- if (proposals.length === 0) {
1264
- console.warn('⚠️ No se encontraron commits válidos para ejecutar');
1265
- console.log('📋 Revisa la propuesta manualmente:');
1266
- console.log(commitProposal);
1267
- return;
1268
- }
1269
-
1270
- // En modo quiet, mostrar progreso compacto
1271
- if (this.quiet) {
1272
- console.log(`🚀 Ejecutando ${proposals.length} commits propuestos...`);
1273
- } else {
1274
- this.log(`📦 Encontrados ${proposals.length} commits para ejecutar:`);
1275
- proposals.forEach((p, i) => {
1276
- this.log(` ${i + 1}. ${p.title}`);
1277
- });
1278
- }
1279
-
1280
- let successfulCommits = 0;
1281
-
1282
- // Ejecutar cada commit secuencialmente
1283
- for (let i = 0; i < proposals.length; i++) {
1284
- const proposal = proposals[i];
1285
- const success = await this.executeCommit(proposal, analysis.files);
1286
- if (success) {
1287
- successfulCommits++;
1288
- // Mostrar éxito incluso en modo quiet
1289
- const status = this.quiet ? `✅ Commit ${i + 1}/${proposals.length}: "${proposal.title}"`
1290
- : `✅ Commit ${i + 1} exitoso`;
1291
- console.log(status);
1292
- } else {
1293
- // Mostrar errores siempre (incluso en quiet)
1294
- console.error(`❌ Commit ${i + 1}/${proposals.length}: "${proposal.title}" - No hay cambios staged`);
1295
- // Continuar con los siguientes commits
1296
- }
1297
- }
1298
-
1299
- // Mostrar resultados finales (siempre visible)
1300
- console.log(`\n📊 Resultado: ${successfulCommits}/${proposals.length} commits exitosos`);
1301
-
1302
- if (successfulCommits > 0) {
1303
- await this.pushCommits();
1304
- }
1305
-
1306
- this.log('\n✅ Auto-approve completado');
1307
-
1308
- } else {
1309
- // Modo normal - solo mostrar propuesta
1310
- this.log('\n✅ Análisis completado');
1311
- console.log(`📄 Propuesta guardada en: ${proposalPath}`);
1312
- if (!this.quiet) {
1313
- console.log('\n📋 Propuesta de commits:');
1314
- console.log('─'.repeat(60));
1315
- console.log(commitProposal);
1316
- console.log('─'.repeat(60));
1317
- console.log('\n💡 Usa --auto-approve para ejecutar automáticamente los commits');
1318
- console.log('💡 O usa --proposal-file <ruta> para reutilizar esta propuesta');
1319
- }
1320
- }
1321
-
1322
- } catch (error) {
1323
- console.error('❌ Error en el generador:', error);
1324
- process.exit(1);
1325
- }
1326
- }
1327
- }
1328
-
1329
- // Ejecutar el generador si se llama directamente
1330
- if (import.meta.main) {
1331
- const args = process.argv.slice(2);
1332
-
1333
- if (args.includes('--help') || args.includes('-h')) {
1334
- console.log(`
1335
- 🚀 Generador Automático de Commits con Gemini CLI
1336
-
1337
- Analiza cambios del repositorio y genera commits coherentes siguiendo los patrones del proyecto.
1338
-
1339
- Uso:
1340
- node project-utils/commit-generator.ts [opciones]
1341
-
1342
- Opciones principales:
1343
- --auto-approve Ejecutar automáticamente los commits propuestos y hacer push
1344
- --proposal-file <ruta> Usar propuesta existente (ej: project-utils/.temp/commit-proposal-*.md)
1345
- --quiet Ejecución silenciosa (solo errores y resultados finales)
1346
- --output-dir <directorio> Directorio personalizado para archivos temporales
1347
-
1348
- Opciones de configuración:
1349
- --no-push Con --auto-approve, no hacer push (solo commits locales)
1350
- --extra <texto> Contexto adicional para mejorar el análisis
1351
- --context <descripción> Descripción del trabajo actual
1352
- --work-type <tipo> Tipo: feature|fix|refactor|docs|test
1353
- --affected-components <lista> Componentes afectados (ej: "core,styling,exports")
1354
- --performance-impact <tipo> Impacto: none|minor|major
1355
- --breaking-changes <si|no> Si introduce cambios incompatibles
1356
- --exhaustive Análisis exhaustivo para proyectos complejos
1357
- --help, -h Mostrar esta ayuda
1358
-
1359
- Ejemplos:
1360
- node project-utils/commit-generator.ts # Generar propuesta básica
1361
- node project-utils/commit-generator.ts --auto-approve # Ejecutar automáticamente
1362
- node project-utils/commit-generator.ts --quiet --auto-approve # Ejecución silenciosa
1363
- node project-utils/commit-generator.ts --proposal-file project-utils/.temp/commit-proposal-20240101-120000.md --auto-approve
1364
- node project-utils/commit-generator.ts --context "logger functionality" --work-type feature
1365
- node project-utils/commit-generator.ts --work-type fix --affected-components "core,exports"
1366
-
1367
- Modo Auto-Approve:
1368
- - Valida estado del repositorio (rama main/master, sin conflictos)
1369
- - Parsea commits propuestos por Gemini AI
1370
- - Ejecuta cada commit secuencialmente con archivos apropiados
1371
- - Hace push automático a origin/current-branch (excepto con --no-push)
1372
- - Manejo de errores y rollback en caso de fallos
1373
-
1374
- Seguridad:
1375
- - Solo funciona en rama main o master
1376
- - Validación de conflictos antes de ejecutar
1377
- - Commits atómicos con manejo de errores individual
1378
- - Logs completos de todas las operaciones
1379
- `);
1380
- process.exit(0);
1381
- }
1382
-
1383
- const generator = new CommitGenerator();
1384
- await generator.generate();
1385
- }