@mks2508/better-logger 0.0.2-alpha.2 → 0.8.1-alpha.1

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 (231) hide show
  1. package/.github/workflows/ci-quality.yml +356 -0
  2. package/.github/workflows/docs-demo.yml +123 -0
  3. package/.github/workflows/releases-core.yml +448 -0
  4. package/.github/workflows/releases-full.yml +404 -0
  5. package/.github/{workflows → workflows-backup}/ci.yml +12 -110
  6. package/.github/workflows-backup/nightly.yml +196 -0
  7. package/.github/workflows-backup/release-optimized.yml +373 -0
  8. package/.release-notes-0.2.0.md +129 -0
  9. package/CHANGELOG.json +658 -0
  10. package/README.md +110 -512
  11. package/demo.html +9 -2
  12. package/dist/Logger.d.ts +176 -0
  13. package/dist/Logger.d.ts.map +1 -0
  14. package/dist/Logger.js +498 -0
  15. package/dist/chunks/{Logger-BrFKFZcD.js → Logger-CbCUS5pQ.js} +987 -5
  16. package/dist/chunks/Logger-CbCUS5pQ.js.map +1 -0
  17. package/dist/chunks/Logger-PCfgYKTP.js +2 -0
  18. package/dist/chunks/Logger-PCfgYKTP.js.map +1 -0
  19. package/dist/chunks/RemoteLogHandler-CjWpWZGl.js +33 -0
  20. package/dist/chunks/RemoteLogHandler-CjWpWZGl.js.map +1 -0
  21. package/dist/chunks/RemoteLogHandler-ymkQ97xl.js +2 -0
  22. package/dist/chunks/RemoteLogHandler-ymkQ97xl.js.map +1 -0
  23. package/dist/chunks/StyleBuilder-BoJTmskd.js +361 -0
  24. package/dist/chunks/StyleBuilder-BoJTmskd.js.map +1 -0
  25. package/dist/chunks/StyleBuilder-LPLzsoHY.js +4 -0
  26. package/dist/chunks/StyleBuilder-LPLzsoHY.js.map +1 -0
  27. package/dist/cli/CommandProcessor.d.ts +40 -0
  28. package/dist/cli/CommandProcessor.d.ts.map +1 -0
  29. package/dist/cli/CommandProcessor.js +57 -0
  30. package/dist/cli/commands/ConfigCommand.d.ts +17 -0
  31. package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
  32. package/dist/cli/commands/ConfigCommand.js +88 -0
  33. package/dist/cli/commands/ExportCommand.d.ts +51 -0
  34. package/dist/cli/commands/ExportCommand.d.ts.map +1 -0
  35. package/dist/cli/commands/ExportCommand.js +243 -0
  36. package/dist/cli/commands/StatusCommand.d.ts +33 -0
  37. package/dist/cli/commands/StatusCommand.d.ts.map +1 -0
  38. package/dist/cli/commands/StatusCommand.js +93 -0
  39. package/dist/cli/commands/ThemeCommand.d.ts +33 -0
  40. package/dist/cli/commands/ThemeCommand.d.ts.map +1 -0
  41. package/dist/cli/commands/ThemeCommand.js +79 -0
  42. package/dist/cli/help.d.ts +15 -0
  43. package/dist/cli/help.d.ts.map +1 -0
  44. package/dist/cli/help.js +118 -0
  45. package/dist/cli/index.d.ts +15 -0
  46. package/dist/cli/index.d.ts.map +1 -0
  47. package/dist/cli/index.js +40 -0
  48. package/dist/constants.d.ts +91 -0
  49. package/dist/constants.d.ts.map +1 -0
  50. package/dist/constants.js +80 -0
  51. package/dist/core.cjs +1 -1
  52. package/dist/core.cjs.map +1 -1
  53. package/dist/core.d.ts +130 -0
  54. package/dist/core.d.ts.map +1 -0
  55. package/dist/core.js +258 -242
  56. package/dist/core.js.map +1 -1
  57. package/dist/example.d.ts +18 -0
  58. package/dist/example.d.ts.map +1 -0
  59. package/dist/example.js +154 -0
  60. package/dist/exports-module.d.ts +203 -0
  61. package/dist/exports-module.d.ts.map +1 -0
  62. package/dist/exports-module.js +260 -0
  63. package/dist/exports.cjs +1 -1
  64. package/dist/exports.cjs.map +1 -1
  65. package/dist/exports.js +2 -2
  66. package/dist/handlers/AnalyticsLogHandler.d.ts +11 -0
  67. package/dist/handlers/AnalyticsLogHandler.d.ts.map +1 -0
  68. package/dist/handlers/AnalyticsLogHandler.js +19 -0
  69. package/dist/handlers/ExportLogHandler.d.ts +103 -0
  70. package/dist/handlers/ExportLogHandler.d.ts.map +1 -0
  71. package/dist/handlers/ExportLogHandler.js +516 -0
  72. package/dist/handlers/FileLogHandler.d.ts +13 -0
  73. package/dist/handlers/FileLogHandler.d.ts.map +1 -0
  74. package/dist/handlers/FileLogHandler.js +24 -0
  75. package/dist/handlers/RemoteLogHandler.d.ts +14 -0
  76. package/dist/handlers/RemoteLogHandler.d.ts.map +1 -0
  77. package/dist/handlers/RemoteLogHandler.js +37 -0
  78. package/dist/handlers/index.d.ts +8 -0
  79. package/dist/handlers/index.d.ts.map +1 -0
  80. package/dist/handlers/index.js +7 -0
  81. package/dist/index.cjs +1 -1
  82. package/dist/index.cjs.map +1 -1
  83. package/dist/index.d.ts +86 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +89 -103
  86. package/dist/main.d.ts +2 -0
  87. package/dist/main.d.ts.map +1 -0
  88. package/dist/main.js +139 -0
  89. package/dist/styling/StyleBuilder.d.ts +132 -0
  90. package/dist/styling/StyleBuilder.d.ts.map +1 -0
  91. package/dist/styling/StyleBuilder.js +268 -0
  92. package/dist/styling/banners.d.ts +45 -0
  93. package/dist/styling/banners.d.ts.map +1 -0
  94. package/dist/styling/banners.js +155 -0
  95. package/dist/styling/index.d.ts +7 -0
  96. package/dist/styling/index.d.ts.map +1 -0
  97. package/dist/styling/index.js +6 -0
  98. package/dist/styling/themes.d.ts +10 -0
  99. package/dist/styling/themes.d.ts.map +1 -0
  100. package/dist/styling/themes.js +231 -0
  101. package/dist/styling-module.d.ts +185 -0
  102. package/dist/styling-module.d.ts.map +1 -0
  103. package/dist/styling-module.js +199 -0
  104. package/dist/styling.cjs +1 -1
  105. package/dist/styling.cjs.map +1 -1
  106. package/dist/styling.js +4 -4
  107. package/dist/types/Logger.d.ts +172 -0
  108. package/dist/types/Logger.d.ts.map +1 -0
  109. package/dist/types/cli/CommandProcessor.d.ts +37 -0
  110. package/dist/types/cli/CommandProcessor.d.ts.map +1 -0
  111. package/dist/types/cli/commands/ConfigCommand.d.ts +14 -0
  112. package/dist/types/cli/commands/ConfigCommand.d.ts.map +1 -0
  113. package/dist/types/cli/commands/ExportCommand.d.ts +48 -0
  114. package/dist/types/cli/commands/ExportCommand.d.ts.map +1 -0
  115. package/dist/types/cli/commands/StatusCommand.d.ts +30 -0
  116. package/dist/types/cli/commands/StatusCommand.d.ts.map +1 -0
  117. package/dist/types/cli/commands/ThemeCommand.d.ts +30 -0
  118. package/dist/types/cli/commands/ThemeCommand.d.ts.map +1 -0
  119. package/dist/types/cli/help.d.ts +12 -0
  120. package/dist/types/cli/help.d.ts.map +1 -0
  121. package/dist/types/cli/index.d.ts +15 -0
  122. package/dist/types/cli/index.d.ts.map +1 -0
  123. package/dist/types/constants.d.ts +88 -0
  124. package/dist/types/constants.d.ts.map +1 -0
  125. package/dist/types/core.d.ts +71 -211
  126. package/dist/types/core.d.ts.map +1 -0
  127. package/dist/types/core.js +13 -0
  128. package/dist/types/example.d.ts +18 -0
  129. package/dist/types/example.d.ts.map +1 -0
  130. package/dist/types/exports-module.d.ts +196 -0
  131. package/dist/types/exports-module.d.ts.map +1 -0
  132. package/dist/types/handlers/AnalyticsLogHandler.d.ts +8 -0
  133. package/dist/types/handlers/AnalyticsLogHandler.d.ts.map +1 -0
  134. package/dist/types/handlers/ExportLogHandler.d.ts +100 -0
  135. package/dist/types/handlers/ExportLogHandler.d.ts.map +1 -0
  136. package/dist/types/handlers/FileLogHandler.d.ts +10 -0
  137. package/dist/types/handlers/FileLogHandler.d.ts.map +1 -0
  138. package/dist/types/handlers/RemoteLogHandler.d.ts +11 -0
  139. package/dist/types/handlers/RemoteLogHandler.d.ts.map +1 -0
  140. package/dist/types/handlers/index.d.ts +8 -0
  141. package/dist/types/handlers/index.d.ts.map +1 -0
  142. package/dist/types/handlers.d.ts +88 -0
  143. package/dist/types/handlers.d.ts.map +1 -0
  144. package/dist/types/handlers.js +4 -0
  145. package/dist/types/index.d.ts +7 -675
  146. package/dist/types/index.d.ts.map +1 -0
  147. package/dist/types/index.js +4 -0
  148. package/dist/types/main.d.ts +2 -0
  149. package/dist/types/main.d.ts.map +1 -0
  150. package/dist/types/styling/StyleBuilder.d.ts +132 -0
  151. package/dist/types/styling/StyleBuilder.d.ts.map +1 -0
  152. package/dist/types/styling/banners.d.ts +42 -0
  153. package/dist/types/styling/banners.d.ts.map +1 -0
  154. package/dist/types/styling/index.d.ts +7 -0
  155. package/dist/types/styling/index.d.ts.map +1 -0
  156. package/dist/types/styling/themes.d.ts +7 -0
  157. package/dist/types/styling/themes.d.ts.map +1 -0
  158. package/dist/types/styling-module.d.ts +178 -0
  159. package/dist/types/styling-module.d.ts.map +1 -0
  160. package/dist/types/types/core.d.ts +71 -0
  161. package/dist/types/types/core.d.ts.map +1 -0
  162. package/dist/types/types/handlers.d.ts +85 -0
  163. package/dist/types/types/handlers.d.ts.map +1 -0
  164. package/dist/types/types/index.d.ts +7 -0
  165. package/dist/types/types/index.d.ts.map +1 -0
  166. package/dist/types/utils/index.d.ts +7 -0
  167. package/dist/types/utils/index.d.ts.map +1 -0
  168. package/dist/types/utils/output.d.ts +29 -0
  169. package/dist/types/utils/output.d.ts.map +1 -0
  170. package/dist/types/utils/stackTrace.d.ts +6 -0
  171. package/dist/types/utils/stackTrace.d.ts.map +1 -0
  172. package/dist/types/utils/timestamps.d.ts +20 -0
  173. package/dist/types/utils/timestamps.d.ts.map +1 -0
  174. package/dist/utils/index.d.ts +7 -0
  175. package/dist/utils/index.d.ts.map +1 -0
  176. package/dist/utils/index.js +6 -0
  177. package/dist/utils/output.d.ts +32 -0
  178. package/dist/utils/output.d.ts.map +1 -0
  179. package/dist/utils/output.js +97 -0
  180. package/dist/utils/stackTrace.d.ts +9 -0
  181. package/dist/utils/stackTrace.d.ts.map +1 -0
  182. package/dist/utils/stackTrace.js +81 -0
  183. package/dist/utils/timestamps.d.ts +20 -0
  184. package/dist/utils/timestamps.d.ts.map +1 -0
  185. package/dist/utils/timestamps.js +71 -0
  186. package/docs/API.md +607 -0
  187. package/docs/DEVELOPMENT.md +731 -0
  188. package/docs/_config.yml +36 -0
  189. package/docs/index.md +179 -0
  190. package/examples/README.md +208 -0
  191. package/examples/basic-logging.js +75 -0
  192. package/examples/data-export.js +234 -0
  193. package/examples/package.json +16 -0
  194. package/examples/performance-timing.js +170 -0
  195. package/examples/styling-themes.js +207 -0
  196. package/package.json +24 -21
  197. package/packages/core/package.json +35 -0
  198. package/packages/exports/package.json +41 -0
  199. package/packages/styling/package.json +41 -0
  200. package/project-utils/README.md +172 -0
  201. package/project-utils/auto-release-gemini.ts +1193 -0
  202. package/project-utils/commit-generator.ts +1000 -0
  203. package/project-utils/commit-ui.ts +246 -0
  204. package/project-utils/git-utils.ts +199 -0
  205. package/project-utils/github-release-manager.ts +466 -0
  206. package/project-utils/prompt-templates.js +345 -0
  207. package/project-utils/prompt-templates.ts +422 -0
  208. package/project-utils/version-manager.ts +1078 -0
  209. package/src/Logger.ts +2 -1
  210. package/src/handlers/ExportLogHandler.ts +141 -0
  211. package/vite.config.ts +152 -50
  212. package/.claude/settings.local.json +0 -22
  213. package/.npmrc.bak +0 -1
  214. package/dist/chunks/Logger-BQhMKy_T.js +0 -2
  215. package/dist/chunks/Logger-BQhMKy_T.js.map +0 -1
  216. package/dist/chunks/Logger-BrFKFZcD.js.map +0 -1
  217. package/dist/chunks/core-2opW4Pi3.js +0 -194
  218. package/dist/chunks/core-2opW4Pi3.js.map +0 -1
  219. package/dist/chunks/core-DyugwSYZ.js +0 -4
  220. package/dist/chunks/core-DyugwSYZ.js.map +0 -1
  221. package/dist/chunks/exports-BNP3R7dp.js +0 -421
  222. package/dist/chunks/exports-BNP3R7dp.js.map +0 -1
  223. package/dist/chunks/exports-U1xLBXrY.js +0 -2
  224. package/dist/chunks/exports-U1xLBXrY.js.map +0 -1
  225. package/dist/chunks/styling-DhUDzwlE.js +0 -654
  226. package/dist/chunks/styling-DhUDzwlE.js.map +0 -1
  227. package/dist/chunks/styling-tmRDI28D.js +0 -2
  228. package/dist/chunks/styling-tmRDI28D.js.map +0 -1
  229. package/dist/types/exports.d.ts +0 -600
  230. package/dist/types/styling.d.ts +0 -751
  231. /package/.github/{workflows → workflows-backup}/release.yml +0 -0
@@ -0,0 +1,1000 @@
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
+ * Ejecuta push de todos los commits
664
+ */
665
+ private async pushCommits(): Promise<void> {
666
+ if (this.noPush) {
667
+ this.log('⏭️ Push deshabilitado por --no-push');
668
+ return;
669
+ }
670
+
671
+ this.log('\n📤 Pushing commits to remote...');
672
+
673
+ try {
674
+ // Detectar rama actual para push
675
+ const currentBranch = await this.gitCommand(['branch', '--show-current']);
676
+ await this.gitCommand(['push', 'origin', currentBranch]);
677
+ this.log('✅ Push completado exitosamente');
678
+ } catch (error) {
679
+ console.error('❌ Error en push:', error);
680
+ this.log('💡 Los commits están en tu repositorio local');
681
+ }
682
+ }
683
+
684
+ /**
685
+ * Valida que auto-approve es seguro de ejecutar
686
+ */
687
+ private async validateAutoApprove(): Promise<boolean> {
688
+ try {
689
+ // Verificar que estamos en la rama correcta (master o main)
690
+ const currentBranch = await this.gitCommand(['branch', '--show-current']);
691
+ const validBranches = ['master', 'main'];
692
+ if (!validBranches.includes(currentBranch)) {
693
+ console.warn(`⚠️ No estás en una rama principal válida (actual: ${currentBranch}, válidas: ${validBranches.join(', ')})`);
694
+ return false;
695
+ }
696
+
697
+ // Verificar que el repositorio está limpio (sin conflictos)
698
+ const statusOutput = await this.gitCommand(['status', '--porcelain']);
699
+ const conflicts = statusOutput.split('\n').filter(line => line.startsWith('UU'));
700
+ if (conflicts.length > 0) {
701
+ console.error('❌ Hay conflictos de merge sin resolver');
702
+ return false;
703
+ }
704
+
705
+ return true;
706
+ } catch (error) {
707
+ console.error('❌ Error validando repositorio:', error);
708
+ return false;
709
+ }
710
+ }
711
+
712
+ /**
713
+ * Ejecuta commits desde un archivo de propuesta existente
714
+ */
715
+ private async executeFromProposalFile(proposalPath: string): Promise<void> {
716
+ this.log(`📂 Cargando propuesta desde: ${proposalPath}`);
717
+
718
+ try {
719
+ const proposalContent = readFileSync(proposalPath, 'utf-8');
720
+
721
+ // Obtener archivos disponibles
722
+ const analysis = await this.generateAnalysisContext();
723
+ const proposals = this.parseCommitProposals(proposalContent, analysis.files);
724
+
725
+ if (proposals.length === 0) {
726
+ console.error('❌ No se encontraron commits válidos en el archivo de propuesta');
727
+ return;
728
+ }
729
+
730
+ this.log(`📦 Encontrados ${proposals.length} commits en la propuesta:`);
731
+ proposals.forEach((p, i) => {
732
+ this.log(` ${i + 1}. ${p.title}`);
733
+ });
734
+
735
+ if (!this.autoApprove) {
736
+ console.log('\n💡 Usa --auto-approve para ejecutar estos commits automáticamente');
737
+ return;
738
+ }
739
+
740
+ // Obtener archivos actuales para el contexto
741
+ const files = await this.getRepositoryStatus();
742
+
743
+ let successfulCommits = 0;
744
+
745
+ for (let i = 0; i < proposals.length; i++) {
746
+ const proposal = proposals[i];
747
+ const success = await this.executeCommit(proposal, files);
748
+ if (success) {
749
+ successfulCommits++;
750
+ } else {
751
+ console.error(`❌ Falló commit ${i + 1}: ${proposal.title}`);
752
+ }
753
+ }
754
+
755
+ this.log(`\n📊 Resultados: ${successfulCommits}/${proposals.length} commits exitosos`);
756
+
757
+ if (successfulCommits > 0) {
758
+ await this.pushCommits();
759
+ }
760
+
761
+ this.log('\n✅ Ejecución desde archivo completada');
762
+
763
+ } catch (error) {
764
+ console.error('❌ Error leyendo archivo de propuesta:', error);
765
+ throw error;
766
+ }
767
+ }
768
+
769
+ /**
770
+ * Ejecuta el generador completo
771
+ */
772
+ async generate(): Promise<void> {
773
+ // Si se especifica un archivo de propuesta existente, usarlo directamente
774
+ if (this.proposalFile && existsSync(this.proposalFile)) {
775
+ return this.executeFromProposalFile(this.proposalFile);
776
+ }
777
+
778
+ this.log(`🚀 Iniciando generador de commits...${this.autoApprove ? ' (AUTO-APPROVE MODE)' : ''}\n`);
779
+
780
+ const args = process.argv.slice(2);
781
+ const isExhaustive = args.includes('-exhaustive');
782
+
783
+ // Parsear parámetros de contexto mejorados
784
+ let extraContext = '';
785
+ let workType = '';
786
+ let contextDescription = '';
787
+ let affectedComponents = '';
788
+ let performanceImpact = '';
789
+ let breakingChanges = '';
790
+
791
+ const extraIndex = args.indexOf('--extra');
792
+ if (extraIndex > -1 && args[extraIndex + 1]) {
793
+ extraContext = args[extraIndex + 1];
794
+ this.log(`💬 Contexto extra proporcionado por el usuario.`);
795
+ } else if (extraIndex > -1) {
796
+ console.warn('⚠️ El parámetro --extra requiere un valor de texto después.');
797
+ }
798
+
799
+ const contextIndex = args.indexOf('--context');
800
+ if (contextIndex > -1 && args[contextIndex + 1]) {
801
+ contextDescription = args[contextIndex + 1];
802
+ this.log(`📋 Contexto del trabajo: ${contextDescription}`);
803
+ }
804
+
805
+ const workTypeIndex = args.indexOf('--work-type');
806
+ if (workTypeIndex > -1 && args[workTypeIndex + 1]) {
807
+ workType = args[workTypeIndex + 1];
808
+ this.log(`🏷️ Tipo de trabajo: ${workType}`);
809
+ }
810
+
811
+ const componentsIndex = args.indexOf('--affected-components');
812
+ if (componentsIndex > -1 && args[componentsIndex + 1]) {
813
+ affectedComponents = args[componentsIndex + 1];
814
+ this.log(`🎯 Componentes afectados: ${affectedComponents}`);
815
+ }
816
+
817
+ const perfIndex = args.indexOf('--performance-impact');
818
+ if (perfIndex > -1 && args[perfIndex + 1]) {
819
+ performanceImpact = args[perfIndex + 1];
820
+ this.log(`⚡ Impacto en rendimiento: ${performanceImpact}`);
821
+ }
822
+
823
+ const breakingIndex = args.indexOf('--breaking-changes');
824
+ if (breakingIndex > -1 && args[breakingIndex + 1]) {
825
+ breakingChanges = args[breakingIndex + 1];
826
+ this.log(`⚠️ Cambios que rompen compatibilidad: ${breakingChanges}`);
827
+ }
828
+
829
+ try {
830
+ // Verificar que estamos en un repositorio git
831
+ await this.gitCommand(['status']);
832
+
833
+ // Generar análisis completo
834
+ const analysis = await this.generateAnalysisContext();
835
+
836
+ if (analysis.files.length === 0) {
837
+ this.log('✅ No hay cambios para procesar');
838
+ return;
839
+ }
840
+
841
+ const fileCount = analysis.files.length;
842
+ const exhaustiveMode = isExhaustive || fileCount > 50;
843
+
844
+ this.log(`📋 Encontrados ${fileCount} archivos modificados`);
845
+ this.log(`📊 Estadísticas: +${analysis.stats.total_additions} -${analysis.stats.total_deletions} líneas`);
846
+ if (exhaustiveMode) {
847
+ this.log('⚡️ Activado modo de análisis exhaustivo.');
848
+ }
849
+
850
+ // Preparar contexto completo mejorado
851
+ const enhancedContext = this.buildEnhancedContext(
852
+ extraContext,
853
+ contextDescription,
854
+ workType,
855
+ affectedComponents,
856
+ performanceImpact,
857
+ breakingChanges
858
+ );
859
+
860
+ // Analizar con Gemini
861
+ const commitProposal = await this.analyzeWithGemini(analysis, exhaustiveMode, enhancedContext);
862
+
863
+ // Guardar propuesta
864
+ const proposalPath = this.saveCommitProposal(commitProposal);
865
+
866
+ if (this.autoApprove) {
867
+ // Validar que es seguro ejecutar auto-approve
868
+ const isValid = await this.validateAutoApprove();
869
+ if (!isValid) {
870
+ console.error('❌ Auto-approve cancelado por validaciones de seguridad');
871
+ return;
872
+ }
873
+
874
+ // Parsear y ejecutar commits
875
+ this.log('\n🤖 Ejecutando commits automáticamente...');
876
+ const proposals = this.parseCommitProposals(commitProposal, analysis.files);
877
+
878
+ if (proposals.length === 0) {
879
+ console.warn('⚠️ No se encontraron commits válidos para ejecutar');
880
+ console.log('📋 Revisa la propuesta manualmente:');
881
+ console.log(commitProposal);
882
+ return;
883
+ }
884
+
885
+ // En modo quiet, mostrar progreso compacto
886
+ if (this.quiet) {
887
+ console.log(`🚀 Ejecutando ${proposals.length} commits propuestos...`);
888
+ } else {
889
+ this.log(`📦 Encontrados ${proposals.length} commits para ejecutar:`);
890
+ proposals.forEach((p, i) => {
891
+ this.log(` ${i + 1}. ${p.title}`);
892
+ });
893
+ }
894
+
895
+ let successfulCommits = 0;
896
+
897
+ // Ejecutar cada commit secuencialmente
898
+ for (let i = 0; i < proposals.length; i++) {
899
+ const proposal = proposals[i];
900
+ const success = await this.executeCommit(proposal, analysis.files);
901
+ if (success) {
902
+ successfulCommits++;
903
+ // Mostrar éxito incluso en modo quiet
904
+ const status = this.quiet ? `✅ Commit ${i + 1}/${proposals.length}: "${proposal.title}"`
905
+ : `✅ Commit ${i + 1} exitoso`;
906
+ console.log(status);
907
+ } else {
908
+ // Mostrar errores siempre (incluso en quiet)
909
+ console.error(`❌ Commit ${i + 1}/${proposals.length}: "${proposal.title}" - No hay cambios staged`);
910
+ // Continuar con los siguientes commits
911
+ }
912
+ }
913
+
914
+ // Mostrar resultados finales (siempre visible)
915
+ console.log(`\n📊 Resultado: ${successfulCommits}/${proposals.length} commits exitosos`);
916
+
917
+ if (successfulCommits > 0) {
918
+ await this.pushCommits();
919
+ }
920
+
921
+ this.log('\n✅ Auto-approve completado');
922
+
923
+ } else {
924
+ // Modo normal - solo mostrar propuesta
925
+ this.log('\n✅ Análisis completado');
926
+ console.log(`📄 Propuesta guardada en: ${proposalPath}`);
927
+ if (!this.quiet) {
928
+ console.log('\n📋 Propuesta de commits:');
929
+ console.log('─'.repeat(60));
930
+ console.log(commitProposal);
931
+ console.log('─'.repeat(60));
932
+ console.log('\n💡 Usa --auto-approve para ejecutar automáticamente los commits');
933
+ console.log('💡 O usa --proposal-file <ruta> para reutilizar esta propuesta');
934
+ }
935
+ }
936
+
937
+ } catch (error) {
938
+ console.error('❌ Error en el generador:', error);
939
+ process.exit(1);
940
+ }
941
+ }
942
+ }
943
+
944
+ // Ejecutar el generador si se llama directamente
945
+ if (import.meta.main) {
946
+ const args = process.argv.slice(2);
947
+
948
+ if (args.includes('--help') || args.includes('-h')) {
949
+ console.log(`
950
+ 🚀 Generador Automático de Commits con Gemini CLI
951
+
952
+ Analiza cambios del repositorio y genera commits coherentes siguiendo los patrones del proyecto.
953
+
954
+ Uso:
955
+ node project-utils/commit-generator.ts [opciones]
956
+
957
+ Opciones principales:
958
+ --auto-approve Ejecutar automáticamente los commits propuestos y hacer push
959
+ --proposal-file <ruta> Usar propuesta existente (ej: project-utils/.temp/commit-proposal-*.md)
960
+ --quiet Ejecución silenciosa (solo errores y resultados finales)
961
+ --output-dir <directorio> Directorio personalizado para archivos temporales
962
+
963
+ Opciones de configuración:
964
+ --no-push Con --auto-approve, no hacer push (solo commits locales)
965
+ --extra <texto> Contexto adicional para mejorar el análisis
966
+ --context <descripción> Descripción del trabajo actual
967
+ --work-type <tipo> Tipo: feature|fix|refactor|docs|test
968
+ --affected-components <lista> Componentes afectados (ej: "core,styling,exports")
969
+ --performance-impact <tipo> Impacto: none|minor|major
970
+ --breaking-changes <si|no> Si introduce cambios incompatibles
971
+ --exhaustive Análisis exhaustivo para proyectos complejos
972
+ --help, -h Mostrar esta ayuda
973
+
974
+ Ejemplos:
975
+ node project-utils/commit-generator.ts # Generar propuesta básica
976
+ node project-utils/commit-generator.ts --auto-approve # Ejecutar automáticamente
977
+ node project-utils/commit-generator.ts --quiet --auto-approve # Ejecución silenciosa
978
+ node project-utils/commit-generator.ts --proposal-file project-utils/.temp/commit-proposal-20240101-120000.md --auto-approve
979
+ node project-utils/commit-generator.ts --context "logger functionality" --work-type feature
980
+ node project-utils/commit-generator.ts --work-type fix --affected-components "core,exports"
981
+
982
+ Modo Auto-Approve:
983
+ - Valida estado del repositorio (rama main/master, sin conflictos)
984
+ - Parsea commits propuestos por Gemini AI
985
+ - Ejecuta cada commit secuencialmente con archivos apropiados
986
+ - Hace push automático a origin/current-branch (excepto con --no-push)
987
+ - Manejo de errores y rollback en caso de fallos
988
+
989
+ Seguridad:
990
+ - Solo funciona en rama main o master
991
+ - Validación de conflictos antes de ejecutar
992
+ - Commits atómicos con manejo de errores individual
993
+ - Logs completos de todas las operaciones
994
+ `);
995
+ process.exit(0);
996
+ }
997
+
998
+ const generator = new CommitGenerator();
999
+ await generator.generate();
1000
+ }