@mks2508/better-logger 5.0.0 → 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 (162) hide show
  1. package/dist/chunks/{Logger-Cbi1f10o.js → Logger-CKqzXDh3.js} +2 -2
  2. package/dist/chunks/{Logger-Cbi1f10o.js.map → Logger-CKqzXDh3.js.map} +1 -1
  3. package/dist/chunks/{Logger-BZ7B7OXE.js → Logger-D_vuKP4l.js} +2 -2
  4. package/dist/chunks/{Logger-BZ7B7OXE.js.map → Logger-D_vuKP4l.js.map} +1 -1
  5. package/dist/chunks/cli-module-B-njrTWb.js +2 -0
  6. package/dist/chunks/cli-module-B-njrTWb.js.map +1 -0
  7. package/dist/chunks/{cli-module-ClIJmaT8.js → cli-module-Cx7qotPY.js} +16 -9
  8. package/dist/chunks/cli-module-Cx7qotPY.js.map +1 -0
  9. package/dist/cli.cjs +1 -1
  10. package/dist/cli.js +1 -1
  11. package/dist/exports.cjs +1 -1
  12. package/dist/exports.js +1 -1
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.js +3 -3
  15. package/dist/styling.cjs +1 -1
  16. package/dist/styling.js +2 -2
  17. package/dist/terminal/formatter.d.ts.map +1 -1
  18. package/package.json +5 -1
  19. package/.claude/settings.local.json +0 -48
  20. package/.github/workflows/ci-quality.yml +0 -357
  21. package/.github/workflows/docs-demo.yml +0 -119
  22. package/.github/workflows/releases-core.yml +0 -512
  23. package/.github/workflows/releases-full.yml +0 -582
  24. package/.github/workflows-backup/ci.yml +0 -221
  25. package/.github/workflows-backup/nightly.yml +0 -196
  26. package/.github/workflows-backup/release-optimized.yml +0 -373
  27. package/.github/workflows-backup/release.yml +0 -269
  28. package/.release-notes-0.2.0.md +0 -129
  29. package/.yamllint +0 -28
  30. package/CHANGELOG.json +0 -1076
  31. package/CHANGELOG.md +0 -209
  32. package/CLAUDE.md +0 -217
  33. package/bun.lock +0 -333
  34. package/demo.html +0 -847
  35. package/dist/chunks/cli-module-CadqgZ1Z.js +0 -2
  36. package/dist/chunks/cli-module-CadqgZ1Z.js.map +0 -1
  37. package/dist/chunks/cli-module-ClIJmaT8.js.map +0 -1
  38. package/docs/API.md +0 -918
  39. package/docs/CORE.md +0 -264
  40. package/docs/DEVELOPMENT.md +0 -731
  41. package/docs/EXPORTS.md +0 -467
  42. package/docs/PACKAGES.md +0 -244
  43. package/docs/STYLING.md +0 -405
  44. package/docs/_config.yml +0 -36
  45. package/docs/index.md +0 -179
  46. package/examples/README.md +0 -208
  47. package/examples/basic-logging.js +0 -75
  48. package/examples/data-export.js +0 -234
  49. package/examples/package.json +0 -16
  50. package/examples/performance-timing.js +0 -170
  51. package/examples/simplified-api.js +0 -124
  52. package/examples/styling-themes.js +0 -207
  53. package/index.html +0 -358
  54. package/packages/core/package.json +0 -57
  55. package/packages/exports/package.json +0 -41
  56. package/packages/nodejs-opentui/README.md +0 -232
  57. package/packages/nodejs-opentui/package.json +0 -72
  58. package/packages/nodejs-opentui/src/LogRenderer.ts +0 -171
  59. package/packages/nodejs-opentui/src/OpenTUILogHandler.ts +0 -178
  60. package/packages/nodejs-opentui/src/components/LogBadge.tsx +0 -131
  61. package/packages/nodejs-opentui/src/index.ts +0 -133
  62. package/packages/nodejs-opentui/src/types.ts +0 -158
  63. package/packages/nodejs-opentui/tsconfig.json +0 -20
  64. package/packages/styling/package.json +0 -41
  65. package/playground/demo-all.ts +0 -95
  66. package/playground/demo-box.ts +0 -85
  67. package/playground/demo-levels.ts +0 -56
  68. package/playground/demo-real-world.ts +0 -98
  69. package/playground/demo-spinner.ts +0 -77
  70. package/playground/demo-steps.ts +0 -72
  71. package/playground/demo-table.ts +0 -83
  72. package/project-utils/README.md +0 -172
  73. package/project-utils/auto-release-gemini.ts +0 -1193
  74. package/project-utils/auto-release-ui.ts +0 -1329
  75. package/project-utils/commit-generator.ts +0 -1385
  76. package/project-utils/commit-ui.ts +0 -264
  77. package/project-utils/git-utils.ts +0 -199
  78. package/project-utils/github-release-manager.ts +0 -466
  79. package/project-utils/project-config.ts +0 -260
  80. package/project-utils/prompt-templates.js +0 -345
  81. package/project-utils/prompt-templates.ts +0 -422
  82. package/project-utils/version-manager.ts +0 -1078
  83. package/public/vite.svg +0 -1
  84. package/src/Logger.ts +0 -1858
  85. package/src/ScopedLogger.ts +0 -256
  86. package/src/cli/CommandProcessor.ts +0 -248
  87. package/src/cli/commands/ConfigCommand.ts +0 -93
  88. package/src/cli/commands/ExportCommand.ts +0 -276
  89. package/src/cli/commands/HistoryCommand.ts +0 -117
  90. package/src/cli/commands/StatusCommand.ts +0 -112
  91. package/src/cli/commands/ThemeCommand.ts +0 -88
  92. package/src/cli/help.ts +0 -127
  93. package/src/cli/index.ts +0 -71
  94. package/src/cli-module.ts +0 -21
  95. package/src/cli-primitives/box.ts +0 -86
  96. package/src/cli-primitives/cli-table.ts +0 -62
  97. package/src/cli-primitives/divider.ts +0 -17
  98. package/src/cli-primitives/header.ts +0 -18
  99. package/src/cli-primitives/index.ts +0 -13
  100. package/src/cli-primitives/server-fallback.ts +0 -54
  101. package/src/cli-primitives/spinner.ts +0 -133
  102. package/src/cli-primitives/step.ts +0 -22
  103. package/src/constants.ts +0 -313
  104. package/src/core.ts +0 -397
  105. package/src/example.ts +0 -210
  106. package/src/exports-module.ts +0 -311
  107. package/src/handlers/AnalyticsLogHandler.ts +0 -22
  108. package/src/handlers/ExportLogHandler.ts +0 -610
  109. package/src/handlers/FileLogHandler.ts +0 -169
  110. package/src/handlers/RemoteLogHandler.ts +0 -42
  111. package/src/handlers/index.ts +0 -8
  112. package/src/hooks/HookManager.ts +0 -177
  113. package/src/hooks/index.ts +0 -1
  114. package/src/index.ts +0 -395
  115. package/src/main.ts +0 -196
  116. package/src/serializers/SerializerRegistry.ts +0 -173
  117. package/src/serializers/index.ts +0 -1
  118. package/src/style.css +0 -96
  119. package/src/styling/LogStyleBuilder.ts +0 -355
  120. package/src/styling/SemanticStyles.ts +0 -380
  121. package/src/styling/SmartPresets.ts +0 -288
  122. package/src/styling/StyleBuilder.ts +0 -319
  123. package/src/styling/StyleCache.ts +0 -131
  124. package/src/styling/banners.ts +0 -168
  125. package/src/styling/index.ts +0 -27
  126. package/src/styling/themes.ts +0 -235
  127. package/src/styling-module.ts +0 -244
  128. package/src/terminal/color-converter.ts +0 -315
  129. package/src/terminal/formatter.ts +0 -236
  130. package/src/terminal/terminal-renderer.ts +0 -342
  131. package/src/transports/ConsoleTransport.ts +0 -28
  132. package/src/transports/FileTransport.ts +0 -53
  133. package/src/transports/HttpTransport.ts +0 -56
  134. package/src/transports/TransportManager.ts +0 -130
  135. package/src/transports/index.ts +0 -4
  136. package/src/types/core.ts +0 -417
  137. package/src/types/handlers.ts +0 -95
  138. package/src/types/hooks.ts +0 -45
  139. package/src/types/index.ts +0 -83
  140. package/src/types/serializers.ts +0 -30
  141. package/src/types/transports.ts +0 -52
  142. package/src/typescript.svg +0 -1
  143. package/src/utils/adapter.ts +0 -291
  144. package/src/utils/ansi-colors.ts +0 -333
  145. package/src/utils/environment-detector.ts +0 -170
  146. package/src/utils/environment.ts +0 -94
  147. package/src/utils/formatting.ts +0 -332
  148. package/src/utils/index.ts +0 -33
  149. package/src/utils/opentui-detection.ts +0 -138
  150. package/src/utils/output.ts +0 -227
  151. package/src/utils/stackTrace.ts +0 -144
  152. package/src/utils/timestamps.ts +0 -80
  153. package/src/vite-env.d.ts +0 -1
  154. package/src/writers/BufferWriter.ts +0 -157
  155. package/src/writers/index.ts +0 -6
  156. package/test-core-browser.html +0 -237
  157. package/test-core-node.js +0 -63
  158. package/tests/test-conflict-resolution.ts +0 -391
  159. package/tests/validate-workflows.sh +0 -131
  160. package/tests/yaml-autofix.sh +0 -146
  161. package/tsconfig.json +0 -50
  162. package/vite.config.ts +0 -256
@@ -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
- }