@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.
- package/.github/workflows/ci-quality.yml +356 -0
- package/.github/workflows/docs-demo.yml +123 -0
- package/.github/workflows/releases-core.yml +448 -0
- package/.github/workflows/releases-full.yml +404 -0
- package/.github/{workflows → workflows-backup}/ci.yml +12 -110
- package/.github/workflows-backup/nightly.yml +196 -0
- package/.github/workflows-backup/release-optimized.yml +373 -0
- package/.release-notes-0.2.0.md +129 -0
- package/CHANGELOG.json +658 -0
- package/README.md +110 -512
- package/demo.html +9 -2
- package/dist/Logger.d.ts +176 -0
- package/dist/Logger.d.ts.map +1 -0
- package/dist/Logger.js +498 -0
- package/dist/chunks/{Logger-BrFKFZcD.js → Logger-CbCUS5pQ.js} +987 -5
- package/dist/chunks/Logger-CbCUS5pQ.js.map +1 -0
- package/dist/chunks/Logger-PCfgYKTP.js +2 -0
- package/dist/chunks/Logger-PCfgYKTP.js.map +1 -0
- package/dist/chunks/RemoteLogHandler-CjWpWZGl.js +33 -0
- package/dist/chunks/RemoteLogHandler-CjWpWZGl.js.map +1 -0
- package/dist/chunks/RemoteLogHandler-ymkQ97xl.js +2 -0
- package/dist/chunks/RemoteLogHandler-ymkQ97xl.js.map +1 -0
- package/dist/chunks/StyleBuilder-BoJTmskd.js +361 -0
- package/dist/chunks/StyleBuilder-BoJTmskd.js.map +1 -0
- package/dist/chunks/StyleBuilder-LPLzsoHY.js +4 -0
- package/dist/chunks/StyleBuilder-LPLzsoHY.js.map +1 -0
- package/dist/cli/CommandProcessor.d.ts +40 -0
- package/dist/cli/CommandProcessor.d.ts.map +1 -0
- package/dist/cli/CommandProcessor.js +57 -0
- package/dist/cli/commands/ConfigCommand.d.ts +17 -0
- package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
- package/dist/cli/commands/ConfigCommand.js +88 -0
- package/dist/cli/commands/ExportCommand.d.ts +51 -0
- package/dist/cli/commands/ExportCommand.d.ts.map +1 -0
- package/dist/cli/commands/ExportCommand.js +243 -0
- package/dist/cli/commands/StatusCommand.d.ts +33 -0
- package/dist/cli/commands/StatusCommand.d.ts.map +1 -0
- package/dist/cli/commands/StatusCommand.js +93 -0
- package/dist/cli/commands/ThemeCommand.d.ts +33 -0
- package/dist/cli/commands/ThemeCommand.d.ts.map +1 -0
- package/dist/cli/commands/ThemeCommand.js +79 -0
- package/dist/cli/help.d.ts +15 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +118 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +40 -0
- package/dist/constants.d.ts +91 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +80 -0
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.ts +130 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +258 -242
- package/dist/core.js.map +1 -1
- package/dist/example.d.ts +18 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +154 -0
- package/dist/exports-module.d.ts +203 -0
- package/dist/exports-module.d.ts.map +1 -0
- package/dist/exports-module.js +260 -0
- package/dist/exports.cjs +1 -1
- package/dist/exports.cjs.map +1 -1
- package/dist/exports.js +2 -2
- package/dist/handlers/AnalyticsLogHandler.d.ts +11 -0
- package/dist/handlers/AnalyticsLogHandler.d.ts.map +1 -0
- package/dist/handlers/AnalyticsLogHandler.js +19 -0
- package/dist/handlers/ExportLogHandler.d.ts +103 -0
- package/dist/handlers/ExportLogHandler.d.ts.map +1 -0
- package/dist/handlers/ExportLogHandler.js +516 -0
- package/dist/handlers/FileLogHandler.d.ts +13 -0
- package/dist/handlers/FileLogHandler.d.ts.map +1 -0
- package/dist/handlers/FileLogHandler.js +24 -0
- package/dist/handlers/RemoteLogHandler.d.ts +14 -0
- package/dist/handlers/RemoteLogHandler.d.ts.map +1 -0
- package/dist/handlers/RemoteLogHandler.js +37 -0
- package/dist/handlers/index.d.ts +8 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +7 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +86 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +89 -103
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +139 -0
- package/dist/styling/StyleBuilder.d.ts +132 -0
- package/dist/styling/StyleBuilder.d.ts.map +1 -0
- package/dist/styling/StyleBuilder.js +268 -0
- package/dist/styling/banners.d.ts +45 -0
- package/dist/styling/banners.d.ts.map +1 -0
- package/dist/styling/banners.js +155 -0
- package/dist/styling/index.d.ts +7 -0
- package/dist/styling/index.d.ts.map +1 -0
- package/dist/styling/index.js +6 -0
- package/dist/styling/themes.d.ts +10 -0
- package/dist/styling/themes.d.ts.map +1 -0
- package/dist/styling/themes.js +231 -0
- package/dist/styling-module.d.ts +185 -0
- package/dist/styling-module.d.ts.map +1 -0
- package/dist/styling-module.js +199 -0
- package/dist/styling.cjs +1 -1
- package/dist/styling.cjs.map +1 -1
- package/dist/styling.js +4 -4
- package/dist/types/Logger.d.ts +172 -0
- package/dist/types/Logger.d.ts.map +1 -0
- package/dist/types/cli/CommandProcessor.d.ts +37 -0
- package/dist/types/cli/CommandProcessor.d.ts.map +1 -0
- package/dist/types/cli/commands/ConfigCommand.d.ts +14 -0
- package/dist/types/cli/commands/ConfigCommand.d.ts.map +1 -0
- package/dist/types/cli/commands/ExportCommand.d.ts +48 -0
- package/dist/types/cli/commands/ExportCommand.d.ts.map +1 -0
- package/dist/types/cli/commands/StatusCommand.d.ts +30 -0
- package/dist/types/cli/commands/StatusCommand.d.ts.map +1 -0
- package/dist/types/cli/commands/ThemeCommand.d.ts +30 -0
- package/dist/types/cli/commands/ThemeCommand.d.ts.map +1 -0
- package/dist/types/cli/help.d.ts +12 -0
- package/dist/types/cli/help.d.ts.map +1 -0
- package/dist/types/cli/index.d.ts +15 -0
- package/dist/types/cli/index.d.ts.map +1 -0
- package/dist/types/constants.d.ts +88 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/core.d.ts +71 -211
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +13 -0
- package/dist/types/example.d.ts +18 -0
- package/dist/types/example.d.ts.map +1 -0
- package/dist/types/exports-module.d.ts +196 -0
- package/dist/types/exports-module.d.ts.map +1 -0
- package/dist/types/handlers/AnalyticsLogHandler.d.ts +8 -0
- package/dist/types/handlers/AnalyticsLogHandler.d.ts.map +1 -0
- package/dist/types/handlers/ExportLogHandler.d.ts +100 -0
- package/dist/types/handlers/ExportLogHandler.d.ts.map +1 -0
- package/dist/types/handlers/FileLogHandler.d.ts +10 -0
- package/dist/types/handlers/FileLogHandler.d.ts.map +1 -0
- package/dist/types/handlers/RemoteLogHandler.d.ts +11 -0
- package/dist/types/handlers/RemoteLogHandler.d.ts.map +1 -0
- package/dist/types/handlers/index.d.ts +8 -0
- package/dist/types/handlers/index.d.ts.map +1 -0
- package/dist/types/handlers.d.ts +88 -0
- package/dist/types/handlers.d.ts.map +1 -0
- package/dist/types/handlers.js +4 -0
- package/dist/types/index.d.ts +7 -675
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/main.d.ts.map +1 -0
- package/dist/types/styling/StyleBuilder.d.ts +132 -0
- package/dist/types/styling/StyleBuilder.d.ts.map +1 -0
- package/dist/types/styling/banners.d.ts +42 -0
- package/dist/types/styling/banners.d.ts.map +1 -0
- package/dist/types/styling/index.d.ts +7 -0
- package/dist/types/styling/index.d.ts.map +1 -0
- package/dist/types/styling/themes.d.ts +7 -0
- package/dist/types/styling/themes.d.ts.map +1 -0
- package/dist/types/styling-module.d.ts +178 -0
- package/dist/types/styling-module.d.ts.map +1 -0
- package/dist/types/types/core.d.ts +71 -0
- package/dist/types/types/core.d.ts.map +1 -0
- package/dist/types/types/handlers.d.ts +85 -0
- package/dist/types/types/handlers.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +7 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +7 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/dist/types/utils/output.d.ts +29 -0
- package/dist/types/utils/output.d.ts.map +1 -0
- package/dist/types/utils/stackTrace.d.ts +6 -0
- package/dist/types/utils/stackTrace.d.ts.map +1 -0
- package/dist/types/utils/timestamps.d.ts +20 -0
- package/dist/types/utils/timestamps.d.ts.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/output.d.ts +32 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +97 -0
- package/dist/utils/stackTrace.d.ts +9 -0
- package/dist/utils/stackTrace.d.ts.map +1 -0
- package/dist/utils/stackTrace.js +81 -0
- package/dist/utils/timestamps.d.ts +20 -0
- package/dist/utils/timestamps.d.ts.map +1 -0
- package/dist/utils/timestamps.js +71 -0
- package/docs/API.md +607 -0
- package/docs/DEVELOPMENT.md +731 -0
- package/docs/_config.yml +36 -0
- package/docs/index.md +179 -0
- package/examples/README.md +208 -0
- package/examples/basic-logging.js +75 -0
- package/examples/data-export.js +234 -0
- package/examples/package.json +16 -0
- package/examples/performance-timing.js +170 -0
- package/examples/styling-themes.js +207 -0
- package/package.json +24 -21
- package/packages/core/package.json +35 -0
- package/packages/exports/package.json +41 -0
- package/packages/styling/package.json +41 -0
- package/project-utils/README.md +172 -0
- package/project-utils/auto-release-gemini.ts +1193 -0
- package/project-utils/commit-generator.ts +1000 -0
- package/project-utils/commit-ui.ts +246 -0
- package/project-utils/git-utils.ts +199 -0
- package/project-utils/github-release-manager.ts +466 -0
- package/project-utils/prompt-templates.js +345 -0
- package/project-utils/prompt-templates.ts +422 -0
- package/project-utils/version-manager.ts +1078 -0
- package/src/Logger.ts +2 -1
- package/src/handlers/ExportLogHandler.ts +141 -0
- package/vite.config.ts +152 -50
- package/.claude/settings.local.json +0 -22
- package/.npmrc.bak +0 -1
- package/dist/chunks/Logger-BQhMKy_T.js +0 -2
- package/dist/chunks/Logger-BQhMKy_T.js.map +0 -1
- package/dist/chunks/Logger-BrFKFZcD.js.map +0 -1
- package/dist/chunks/core-2opW4Pi3.js +0 -194
- package/dist/chunks/core-2opW4Pi3.js.map +0 -1
- package/dist/chunks/core-DyugwSYZ.js +0 -4
- package/dist/chunks/core-DyugwSYZ.js.map +0 -1
- package/dist/chunks/exports-BNP3R7dp.js +0 -421
- package/dist/chunks/exports-BNP3R7dp.js.map +0 -1
- package/dist/chunks/exports-U1xLBXrY.js +0 -2
- package/dist/chunks/exports-U1xLBXrY.js.map +0 -1
- package/dist/chunks/styling-DhUDzwlE.js +0 -654
- package/dist/chunks/styling-DhUDzwlE.js.map +0 -1
- package/dist/chunks/styling-tmRDI28D.js +0 -2
- package/dist/chunks/styling-tmRDI28D.js.map +0 -1
- package/dist/types/exports.d.ts +0 -600
- package/dist/types/styling.d.ts +0 -751
- /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
|
+
}
|