@gilbert_oliveira/commit-wizard 2.12.2-canary.1 → 2.12.3-canary.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commit-wizard.js +85 -65
- package/package.json +4 -2
- package/src/bin/commit-wizard.ts +12 -11
- package/src/cache/analysis.ts +210 -0
- package/src/commands/commit.ts +410 -0
- package/src/commands/init.ts +17 -0
- package/src/commands/version.ts +5 -0
- package/src/commitlint/index.ts +253 -0
- package/src/config/index.ts +25 -0
- package/src/core/cache.ts +13 -209
- package/src/core/index.ts +3 -385
- package/src/core/openai.ts +18 -465
- package/src/core/smart-split.ts +7 -722
- package/src/git/diff-filter.ts +118 -0
- package/src/pipeline/enforce.ts +152 -0
- package/src/pipeline/generate.ts +116 -0
- package/src/pipeline/split.ts +737 -0
- package/src/pipeline/types.ts +26 -0
- package/src/prompt/builder.ts +61 -0
- package/src/prompt/system.ts +69 -0
- package/src/prompt/templates.ts +172 -0
- package/src/prompt/types.ts +4 -0
- package/src/providers/factory.ts +18 -0
- package/src/providers/openai.ts +367 -0
- package/src/providers/types.ts +11 -0
- package/src/types/clack.d.ts +49 -4
- package/src/ui/format.ts +33 -0
- package/src/ui/index.ts +9 -5
- package/src/ui/smart-split.ts +1 -1
- package/src/ui/spinner.ts +23 -0
- package/src/ui/theme.ts +17 -0
- package/src/utils/args.ts +16 -8
- package/src/utils/hash.ts +9 -0
- package/src/utils/version.ts +11 -6
package/src/core/index.ts
CHANGED
|
@@ -1,385 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
isGitRepository,
|
|
5
|
-
getGitStatus,
|
|
6
|
-
getDiffStats,
|
|
7
|
-
executeCommit,
|
|
8
|
-
executeFileCommit,
|
|
9
|
-
} from '../git/index';
|
|
10
|
-
import { generateWithRetry } from './openai';
|
|
11
|
-
import {
|
|
12
|
-
showCommitPreview,
|
|
13
|
-
editCommitMessage,
|
|
14
|
-
copyToClipboard,
|
|
15
|
-
showCommitResult,
|
|
16
|
-
showCancellation,
|
|
17
|
-
selectFilesForCommit,
|
|
18
|
-
askContinueCommits,
|
|
19
|
-
} from '../ui/index';
|
|
20
|
-
import { chooseSplitMode } from '../ui/smart-split';
|
|
21
|
-
import { handleSmartSplitMode } from './smart-split';
|
|
22
|
-
import { initializeCache } from './cache';
|
|
23
|
-
import type { CLIArgs } from '../utils/args';
|
|
24
|
-
import type { Config } from '../config/index';
|
|
25
|
-
|
|
26
|
-
export async function main(
|
|
27
|
-
args: CLIArgs = {
|
|
28
|
-
silent: false,
|
|
29
|
-
yes: false,
|
|
30
|
-
auto: false,
|
|
31
|
-
split: false,
|
|
32
|
-
smartSplit: false,
|
|
33
|
-
dryRun: false,
|
|
34
|
-
help: false,
|
|
35
|
-
version: false,
|
|
36
|
-
}
|
|
37
|
-
) {
|
|
38
|
-
if (!args.silent) {
|
|
39
|
-
log.info('🚀 Commit Wizard iniciado!');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Verificar se estamos em um repositório Git
|
|
43
|
-
if (!isGitRepository()) {
|
|
44
|
-
log.error('❌ Não foi encontrado um repositório Git neste diretório.');
|
|
45
|
-
if (!args.silent) {
|
|
46
|
-
log.info(
|
|
47
|
-
'💡 Execute o comando em um diretório com repositório Git inicializado.'
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Carregar e validar configuração
|
|
54
|
-
if (!args.silent) {
|
|
55
|
-
log.info('⚙️ Carregando configuração...');
|
|
56
|
-
}
|
|
57
|
-
const config = loadConfig();
|
|
58
|
-
|
|
59
|
-
// Inicializar cache global
|
|
60
|
-
initializeCache(config);
|
|
61
|
-
|
|
62
|
-
// Sobrescrever configuração com argumentos CLI
|
|
63
|
-
if (args.split) {
|
|
64
|
-
config.splitCommits = true;
|
|
65
|
-
}
|
|
66
|
-
if (args.dryRun) {
|
|
67
|
-
config.dryRun = true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const configErrors = validateConfig(config);
|
|
71
|
-
|
|
72
|
-
if (configErrors.length > 0) {
|
|
73
|
-
log.error('❌ Erros na configuração:');
|
|
74
|
-
configErrors.forEach((error) => log.error(` • ${error}`));
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (!args.silent) {
|
|
79
|
-
log.success(
|
|
80
|
-
`✅ Configuração carregada (modelo: ${config.openai.model}, idioma: ${config.language})`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Verificar arquivos staged
|
|
85
|
-
if (!args.silent) {
|
|
86
|
-
log.info('📋 Verificando arquivos staged...');
|
|
87
|
-
}
|
|
88
|
-
const gitStatus = getGitStatus();
|
|
89
|
-
|
|
90
|
-
if (!gitStatus.hasStaged) {
|
|
91
|
-
log.warn('⚠️ Nenhum arquivo foi encontrado no stage.');
|
|
92
|
-
if (!args.silent) {
|
|
93
|
-
log.info(
|
|
94
|
-
'💡 Use `git add <arquivo>` para adicionar arquivos ao stage antes de gerar o commit.'
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
process.exit(0);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const diffStats = getDiffStats();
|
|
101
|
-
if (!args.silent) {
|
|
102
|
-
log.success(
|
|
103
|
-
`✅ Encontrados ${gitStatus.stagedFiles.length} arquivo(s) staged:`
|
|
104
|
-
);
|
|
105
|
-
gitStatus.stagedFiles.forEach((file) => log.info(` 📄 ${file}`));
|
|
106
|
-
log.info(
|
|
107
|
-
`📊 Estatísticas: +${diffStats.added} -${diffStats.removed} linhas`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Modo Split: escolher entre smart split e split manual
|
|
112
|
-
if (config.splitCommits || args.smartSplit) {
|
|
113
|
-
if (args.yes) {
|
|
114
|
-
// Modo automático: usar smart split
|
|
115
|
-
return await handleSmartSplitMode(gitStatus, config, args);
|
|
116
|
-
} else {
|
|
117
|
-
// Modo interativo: perguntar qual tipo de split
|
|
118
|
-
const splitAction = await chooseSplitMode();
|
|
119
|
-
|
|
120
|
-
switch (splitAction.action) {
|
|
121
|
-
case 'proceed':
|
|
122
|
-
return await handleSmartSplitMode(gitStatus, config, args);
|
|
123
|
-
case 'manual':
|
|
124
|
-
return await handleSplitMode(gitStatus, config, args);
|
|
125
|
-
case 'cancel':
|
|
126
|
-
showCancellation();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Gerar mensagem de commit com OpenAI
|
|
133
|
-
if (!args.silent) {
|
|
134
|
-
log.info('🤖 Gerando mensagem de commit com IA...');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const result = await generateWithRetry(
|
|
138
|
-
gitStatus.diff,
|
|
139
|
-
config,
|
|
140
|
-
gitStatus.stagedFiles
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
if (!result.success) {
|
|
144
|
-
log.error(`❌ Erro ao gerar commit: ${result.error}`);
|
|
145
|
-
process.exit(1);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!result.suggestion) {
|
|
149
|
-
log.error('❌ Nenhuma sugestão foi gerada');
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (!args.silent) {
|
|
154
|
-
log.success('✨ Mensagem de commit gerada!');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Modo Dry Run: apenas mostrar mensagem
|
|
158
|
-
if (config.dryRun) {
|
|
159
|
-
log.info('🔍 Modo Dry Run - Mensagem gerada:');
|
|
160
|
-
log.info(`"${result.suggestion.message}"`);
|
|
161
|
-
log.info('💡 Execute sem --dry-run para fazer o commit');
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Modo automático: commit direto
|
|
166
|
-
if (args.yes) {
|
|
167
|
-
const commitResult = executeCommit(result.suggestion.message);
|
|
168
|
-
showCommitResult(
|
|
169
|
-
commitResult.success,
|
|
170
|
-
commitResult.hash,
|
|
171
|
-
commitResult.error
|
|
172
|
-
);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Interface interativa
|
|
177
|
-
while (true) {
|
|
178
|
-
const uiAction = await showCommitPreview(result.suggestion);
|
|
179
|
-
|
|
180
|
-
switch (uiAction.action) {
|
|
181
|
-
case 'commit': {
|
|
182
|
-
// Commit direto com mensagem gerada
|
|
183
|
-
const commitResult = executeCommit(result.suggestion.message);
|
|
184
|
-
showCommitResult(
|
|
185
|
-
commitResult.success,
|
|
186
|
-
commitResult.hash,
|
|
187
|
-
commitResult.error
|
|
188
|
-
);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
case 'edit': {
|
|
192
|
-
// Editar mensagem
|
|
193
|
-
const editAction = await editCommitMessage(result.suggestion.message);
|
|
194
|
-
if (editAction.action === 'cancel') {
|
|
195
|
-
showCancellation();
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
if (editAction.action === 'commit' && editAction.message) {
|
|
199
|
-
const editCommitResult = executeCommit(editAction.message);
|
|
200
|
-
showCommitResult(
|
|
201
|
-
editCommitResult.success,
|
|
202
|
-
editCommitResult.hash,
|
|
203
|
-
editCommitResult.error
|
|
204
|
-
);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
case 'copy': {
|
|
210
|
-
// Copiar para clipboard
|
|
211
|
-
await copyToClipboard(result.suggestion.message);
|
|
212
|
-
if (!args.silent) {
|
|
213
|
-
log.info(
|
|
214
|
-
'🎯 Você pode usar a mensagem copiada com: git commit -m "mensagem"'
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
case 'cancel': {
|
|
220
|
-
// Cancelar operação
|
|
221
|
-
showCancellation();
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async function handleSplitMode(gitStatus: any, config: any, args: CLIArgs) {
|
|
229
|
-
if (!args.silent) {
|
|
230
|
-
log.info('🔄 Modo Split ativado - Commits separados por arquivo');
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
let remainingFiles = [
|
|
234
|
-
...(gitStatus as { stagedFiles: string[] }).stagedFiles,
|
|
235
|
-
];
|
|
236
|
-
|
|
237
|
-
while (remainingFiles.length > 0) {
|
|
238
|
-
// Selecionar arquivos para este commit
|
|
239
|
-
const selectedFiles = args.yes
|
|
240
|
-
? [remainingFiles[0]] // Modo automático: um arquivo por vez
|
|
241
|
-
: await selectFilesForCommit(remainingFiles);
|
|
242
|
-
|
|
243
|
-
if (selectedFiles.length === 0) {
|
|
244
|
-
if (!args.silent) {
|
|
245
|
-
log.info('❌ Nenhum arquivo selecionado');
|
|
246
|
-
}
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Gerar diff apenas dos arquivos selecionados
|
|
251
|
-
const { getFileDiff } = await import('../git/index');
|
|
252
|
-
const fileDiffs = selectedFiles
|
|
253
|
-
.filter((file): file is string => file !== undefined)
|
|
254
|
-
.map((file) => {
|
|
255
|
-
try {
|
|
256
|
-
return getFileDiff(file);
|
|
257
|
-
} catch (error) {
|
|
258
|
-
log.error(
|
|
259
|
-
`❌ Erro ao obter diff do arquivo ${file}: ${error instanceof Error ? error.message : 'Erro desconhecido'}`
|
|
260
|
-
);
|
|
261
|
-
return '';
|
|
262
|
-
}
|
|
263
|
-
})
|
|
264
|
-
.filter((diff) => diff.length > 0)
|
|
265
|
-
.join('\n');
|
|
266
|
-
|
|
267
|
-
if (!fileDiffs) {
|
|
268
|
-
if (!args.silent) {
|
|
269
|
-
log.warn('⚠️ Nenhum diff encontrado para os arquivos selecionados');
|
|
270
|
-
}
|
|
271
|
-
remainingFiles = remainingFiles.filter(
|
|
272
|
-
(file) => !selectedFiles.includes(file)
|
|
273
|
-
);
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (!args.silent) {
|
|
278
|
-
log.info(`🤖 Gerando commit para: ${selectedFiles.join(', ')}`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const result = await generateWithRetry(
|
|
282
|
-
fileDiffs,
|
|
283
|
-
config,
|
|
284
|
-
selectedFiles.filter((file): file is string => file !== undefined)
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
if (!result.success) {
|
|
288
|
-
log.error(`❌ Erro ao gerar commit: ${result.error}`);
|
|
289
|
-
remainingFiles = remainingFiles.filter(
|
|
290
|
-
(file) => !selectedFiles.includes(file)
|
|
291
|
-
);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (!result.suggestion) {
|
|
296
|
-
log.error('❌ Nenhuma sugestão foi gerada');
|
|
297
|
-
remainingFiles = remainingFiles.filter(
|
|
298
|
-
(file) => !selectedFiles.includes(file)
|
|
299
|
-
);
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Modo Dry Run: apenas mostrar mensagem
|
|
304
|
-
if ((config as Config).dryRun) {
|
|
305
|
-
log.info(`🔍 Dry Run - Mensagem para ${selectedFiles.join(', ')}:`);
|
|
306
|
-
log.info(`"${result.suggestion.message}"`);
|
|
307
|
-
remainingFiles = remainingFiles.filter(
|
|
308
|
-
(file) => !selectedFiles.includes(file)
|
|
309
|
-
);
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Modo automático: commit direto
|
|
314
|
-
if (args.yes) {
|
|
315
|
-
// Para múltiplos arquivos, usar commit normal
|
|
316
|
-
// Para arquivo único, usar executeFileCommit
|
|
317
|
-
const commitResult =
|
|
318
|
-
selectedFiles.length === 1 && selectedFiles[0]
|
|
319
|
-
? await executeFileCommit(selectedFiles[0], result.suggestion.message)
|
|
320
|
-
: await executeCommit(result.suggestion.message);
|
|
321
|
-
|
|
322
|
-
showCommitResult(
|
|
323
|
-
commitResult.success,
|
|
324
|
-
commitResult.hash,
|
|
325
|
-
commitResult.error
|
|
326
|
-
);
|
|
327
|
-
} else {
|
|
328
|
-
// Interface interativa para este commit
|
|
329
|
-
const uiAction = await showCommitPreview(result.suggestion);
|
|
330
|
-
|
|
331
|
-
if (uiAction.action === 'commit') {
|
|
332
|
-
const commitResult =
|
|
333
|
-
selectedFiles.length === 1 && selectedFiles[0]
|
|
334
|
-
? await executeFileCommit(
|
|
335
|
-
selectedFiles[0],
|
|
336
|
-
result.suggestion.message
|
|
337
|
-
)
|
|
338
|
-
: await executeCommit(result.suggestion.message);
|
|
339
|
-
showCommitResult(
|
|
340
|
-
commitResult.success,
|
|
341
|
-
commitResult.hash,
|
|
342
|
-
commitResult.error
|
|
343
|
-
);
|
|
344
|
-
} else if (uiAction.action === 'edit') {
|
|
345
|
-
const editAction = await editCommitMessage(result.suggestion.message);
|
|
346
|
-
if (editAction.action === 'commit' && editAction.message) {
|
|
347
|
-
const commitResult =
|
|
348
|
-
selectedFiles.length === 1 && selectedFiles[0]
|
|
349
|
-
? await executeFileCommit(selectedFiles[0], editAction.message)
|
|
350
|
-
: await executeCommit(editAction.message);
|
|
351
|
-
showCommitResult(
|
|
352
|
-
commitResult.success,
|
|
353
|
-
commitResult.hash,
|
|
354
|
-
commitResult.error
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
} else if (uiAction.action === 'copy') {
|
|
358
|
-
await copyToClipboard(result.suggestion.message);
|
|
359
|
-
if (!args.silent) {
|
|
360
|
-
log.info('🎯 Mensagem copiada para clipboard');
|
|
361
|
-
}
|
|
362
|
-
} else if (uiAction.action === 'cancel') {
|
|
363
|
-
showCancellation();
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Remover arquivos processados
|
|
369
|
-
remainingFiles = remainingFiles.filter(
|
|
370
|
-
(file) => !selectedFiles.includes(file)
|
|
371
|
-
);
|
|
372
|
-
|
|
373
|
-
// Perguntar se quer continuar (exceto em modo automático)
|
|
374
|
-
if (remainingFiles.length > 0 && !args.yes) {
|
|
375
|
-
const continueCommits = await askContinueCommits(remainingFiles);
|
|
376
|
-
if (!continueCommits) {
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (!args.silent) {
|
|
383
|
-
log.success('✅ Modo Split concluído!');
|
|
384
|
-
}
|
|
385
|
-
}
|
|
1
|
+
// Re-exports para compatibilidade retroativa.
|
|
2
|
+
// A lógica foi movida para src/commands/commit.ts na task 7.
|
|
3
|
+
export { runCommitCommand, main } from '../commands/commit';
|