@gilbert_oliveira/commit-wizard 1.0.26 → 1.2.0

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ts-node
1
+ #!/usr/bin/env node
2
2
  import chalk from 'chalk';
3
3
  import inquirer from 'inquirer';
4
4
  import os from 'os';
@@ -6,241 +6,472 @@ import path from 'path';
6
6
  import fs from 'fs';
7
7
  import { execSync } from 'child_process';
8
8
  import ora from 'ora';
9
- import { encode, decode } from 'gpt-tokenizer';
10
- const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
11
- if (!OPENAI_API_KEY) {
12
- console.log(chalk.redBright('\n🚨 Erro: A variável de ambiente ') +
13
- chalk.yellow('OPENAI_API_KEY') +
14
- chalk.redBright(' não está definida.\n'));
15
- console.log(chalk.white(' Defina sua chave com: ') +
16
- chalk.cyan('export OPENAI_API_KEY="sua-chave"') +
17
- chalk.white(' ou configure no seu ') +
18
- chalk.cyan('.bashrc') +
19
- chalk.white(' ou ') +
20
- chalk.cyan('.zshrc\n'));
21
- process.exit(1);
9
+ import { loadConfig, saveConfig, createConfigExample } from './config.js';
10
+ import { AIService } from './ai-service.js';
11
+ import { GitUtils } from './git-utils.js';
12
+ import { DiffProcessor } from './diff-processor.js';
13
+ import { CommitSplitter } from './commit-splitter.js';
14
+ // 🛑 Tratamento de interrupção (Ctrl+C)
15
+ process.on('SIGINT', () => {
16
+ console.log(chalk.yellow('\n\n👋 Processo interrompido pelo usuário. Até mais!'));
17
+ process.exit(0);
18
+ });
19
+ process.on('SIGTERM', () => {
20
+ console.log(chalk.yellow('\n\n🛑 Processo terminado. Até mais!'));
21
+ process.exit(0);
22
+ });
23
+ /**
24
+ * Exibe informações sobre o sistema e configuração
25
+ */
26
+ function displaySystemInfo(config, gitUtils) {
27
+ console.log(chalk.cyan.bold('\n📊 Informações do Sistema:'));
28
+ console.log(chalk.white(`• Modelo AI: ${config.model}`));
29
+ console.log(chalk.white(`• Linguagem: ${config.language}`));
30
+ console.log(chalk.white(`• Temperatura: ${config.temperature}`));
31
+ console.log(chalk.white(`• Tokens máximos: ${config.maxTokens}`));
32
+ console.log(chalk.white(`• Auto commit: ${config.autoCommit ? '✅' : '❌'}`));
33
+ console.log(chalk.white(`• Emojis: ${config.includeEmoji ? '✅' : '❌'}`));
34
+ if (gitUtils.isGitRepository()) {
35
+ const branches = gitUtils.getBranches();
36
+ console.log(chalk.white(`• Branch atual: ${branches.current}`));
37
+ console.log(chalk.white(`• Alterações pendentes: ${gitUtils.hasUncommittedChanges() ? '⚠️' : '✅'}`));
38
+ }
22
39
  }
23
40
  /**
24
- * Realiza a chamada à API do OpenAI.
25
- * @param prompt Texto que será enviado como mensagem do usuário.
26
- * @param mode Define o contexto: 'commit' para gerar mensagem de commit ou outro valor para resumo.
27
- * @returns Resposta da API (string com a mensagem ou o resumo).
41
+ * Menu interativo para configuração
28
42
  */
29
- export async function callOpenAI(prompt, mode = 'commit') {
30
- const url = 'https://api.openai.com/v1/chat/completions';
31
- // Escolhe o prompt inicial de acordo com o modo.
32
- const systemPrompt = mode === 'commit'
33
- ? "Você é um assistente que gera mensagens de commit seguindo a convenção do Conventional Commits."
34
- : "Você é um assistente que resume alterações de código de forma breve, usando linguagem imperativa em português.";
35
- const body = {
36
- model: "gpt-4-turbo",
37
- messages: [
38
- { role: "system", content: systemPrompt },
39
- { role: "user", content: prompt }
40
- ],
41
- temperature: 0.2
42
- };
43
- const response = await fetch(url, {
44
- method: "POST",
45
- headers: {
46
- "Content-Type": "application/json",
47
- "Authorization": `Bearer ${OPENAI_API_KEY}`
43
+ async function configurationMenu() {
44
+ const currentConfig = loadConfig();
45
+ console.log(chalk.blue.bold('\n⚙️ Menu de Configuração'));
46
+ const { action } = await inquirer.prompt([
47
+ {
48
+ type: 'list',
49
+ name: 'action',
50
+ message: 'O que você gostaria de configurar?',
51
+ choices: [
52
+ { name: '🤖 Modelo de IA', value: 'model' },
53
+ { name: '🌍 Idioma', value: 'language' },
54
+ { name: '🎛️ Temperatura', value: 'temperature' },
55
+ { name: '📊 Tokens máximos', value: 'maxTokens' },
56
+ { name: '😀 Incluir emojis', value: 'includeEmoji' },
57
+ { name: '🚀 Auto commit', value: 'autoCommit' },
58
+ { name: '🚫 Padrões de exclusão', value: 'excludePatterns' },
59
+ { name: '📄 Criar arquivo exemplo', value: 'createExample' },
60
+ { name: '💾 Salvar configuração global', value: 'saveGlobal' },
61
+ { name: '🔙 Voltar', value: 'back' },
62
+ ],
48
63
  },
49
- body: JSON.stringify(body)
50
- });
51
- if (!response.ok) {
52
- throw new Error(`Erro na API OpenAI: ${response.statusText}`);
53
- }
54
- const data = await response.json();
55
- // Retorna a resposta do primeiro "choice".
56
- return data.choices[0].message.content.trim();
64
+ ]);
65
+ if (action === 'back')
66
+ return;
67
+ if (action === 'createExample') {
68
+ createConfigExample();
69
+ return;
70
+ }
71
+ const newConfig = {};
72
+ switch (action) {
73
+ case 'model': {
74
+ const { model } = await inquirer.prompt([
75
+ {
76
+ type: 'list',
77
+ name: 'model',
78
+ message: 'Escolha o modelo de IA:',
79
+ choices: [
80
+ { name: 'GPT-4o (Mais recente)', value: 'gpt-4o' },
81
+ { name: 'GPT-4o Mini (Mais rápido e barato)', value: 'gpt-4o-mini' },
82
+ { name: 'GPT-4 Turbo', value: 'gpt-4-turbo' },
83
+ { name: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' },
84
+ ],
85
+ default: currentConfig.model,
86
+ },
87
+ ]);
88
+ newConfig.model = model;
89
+ break;
90
+ }
91
+ case 'language': {
92
+ const { language } = await inquirer.prompt([
93
+ {
94
+ type: 'list',
95
+ name: 'language',
96
+ message: 'Escolha o idioma:',
97
+ choices: [
98
+ { name: 'Português', value: 'pt' },
99
+ { name: 'English', value: 'en' },
100
+ ],
101
+ default: currentConfig.language,
102
+ },
103
+ ]);
104
+ newConfig.language = language;
105
+ break;
106
+ }
107
+ case 'temperature': {
108
+ const { temperature } = await inquirer.prompt([
109
+ {
110
+ type: 'input',
111
+ name: 'temperature',
112
+ message: 'Digite a temperatura (0.0 - 1.0):',
113
+ default: currentConfig.temperature.toString(),
114
+ validate: (input) => {
115
+ const num = parseFloat(input);
116
+ return !isNaN(num) && num >= 0 && num <= 1 ? true : 'Digite um número entre 0.0 e 1.0';
117
+ },
118
+ },
119
+ ]);
120
+ newConfig.temperature = parseFloat(temperature);
121
+ break;
122
+ }
123
+ case 'maxTokens': {
124
+ const { maxTokens } = await inquirer.prompt([
125
+ {
126
+ type: 'input',
127
+ name: 'maxTokens',
128
+ message: 'Digite o número máximo de tokens:',
129
+ default: currentConfig.maxTokens.toString(),
130
+ validate: (input) => {
131
+ const num = parseInt(input);
132
+ return !isNaN(num) && num > 0 ? true : 'Digite um número positivo';
133
+ },
134
+ },
135
+ ]);
136
+ newConfig.maxTokens = parseInt(maxTokens);
137
+ break;
138
+ }
139
+ case 'includeEmoji': {
140
+ const { includeEmoji } = await inquirer.prompt([
141
+ {
142
+ type: 'confirm',
143
+ name: 'includeEmoji',
144
+ message: 'Incluir emojis nas mensagens de commit?',
145
+ default: currentConfig.includeEmoji,
146
+ },
147
+ ]);
148
+ newConfig.includeEmoji = includeEmoji;
149
+ break;
150
+ }
151
+ case 'autoCommit': {
152
+ const { autoCommit } = await inquirer.prompt([
153
+ {
154
+ type: 'confirm',
155
+ name: 'autoCommit',
156
+ message: 'Fazer commit automaticamente sem confirmação?',
157
+ default: currentConfig.autoCommit,
158
+ },
159
+ ]);
160
+ newConfig.autoCommit = autoCommit;
161
+ break;
162
+ }
163
+ case 'excludePatterns': {
164
+ const { excludePatterns } = await inquirer.prompt([
165
+ {
166
+ type: 'input',
167
+ name: 'excludePatterns',
168
+ message: 'Padrões de exclusão (separados por vírgula):',
169
+ default: currentConfig.excludePatterns.join(', '),
170
+ },
171
+ ]);
172
+ newConfig.excludePatterns = excludePatterns.split(',').map((p) => p.trim());
173
+ break;
174
+ }
175
+ case 'saveGlobal':
176
+ saveConfig(currentConfig, true);
177
+ return;
178
+ }
179
+ if (Object.keys(newConfig).length > 0) {
180
+ saveConfig(newConfig);
181
+ console.log(chalk.green('✅ Configuração atualizada com sucesso!'));
182
+ }
57
183
  }
58
184
  /**
59
- * Divide o diff em chunks menores com base na contagem de tokens.
60
- * Utiliza o gpt-tokenizer para garantir que cada chunk não exceda o limite de tokens.
61
- * @param diff O diff completo em formato de string.
62
- * @param maxTokens Quantidade máxima de tokens permitida para cada chunk (padrão: 1000 tokens).
63
- * @returns Array de strings, cada uma representando um chunk.
185
+ * Função principal do commit wizard
64
186
  */
65
- export function chunkDiff(diff, maxTokens = 1000) {
66
- // Codifica o diff para obter o array de tokens.
67
- const tokens = encode(diff);
68
- // Se o diff couber em um único chunk, retorna-o diretamente.
69
- if (tokens.length <= maxTokens) {
70
- return [diff];
71
- }
72
- const chunks = [];
73
- // Percorre os tokens de forma que cada chunk contenha no máximo maxTokens tokens.
74
- for (let i = 0; i < tokens.length; i += maxTokens) {
75
- const chunkTokens = tokens.slice(i, i + maxTokens);
76
- const chunkText = decode(chunkTokens);
77
- chunks.push(chunkText);
78
- }
79
- return chunks;
80
- }
81
- // Pré-prompt para a geração da mensagem de commit conforme as convenções
82
- const COMMIT_PROMPT = `
83
- Por favor, escreva a mensagem de commit para este diff usando a convenção de Conventional Commits: https://www.conventionalcommits.org/en/v1.0.0/.
84
- A mensagem deve começar com um tipo de commit, como:
85
- feat: para novas funcionalidades
86
- fix: para correções de bugs
87
- chore: para alterações que não afetam a funcionalidade
88
- docs: para mudanças na documentação
89
- style: para alterações no estilo do código (como formatação)
90
- refactor: para alterações no código que não alteram a funcionalidade
91
- perf: para melhorias de desempenho
92
- test: para alterações nos testes
93
- ci: para mudanças no pipeline de integração contínua
94
-
95
- Exemplo:
96
- feat(auth): adicionar suporte ao login com Google
97
-
98
- Caso o commit seja uma alteração significativa (breaking change), inclua um título com \`!\` após o tipo de commit e adicione a explicação em \`BREAKING CHANGE\`:
99
- feat!(auth): reestruturar fluxo de login
100
- BREAKING CHANGE: A API de login foi alterada e não é compatível com versões anteriores.
101
-
102
- Gere também uma descrição mais detalhada do commit, se necessário.
103
-
104
- Use sempre linguagem imperativa e primeira pessoa do singular, como:
105
- - "adiciona recurso"
106
- - "corrige bug"
107
- - "remove arquivo"
108
-
109
- Lembre-se: os textos fora do Conventional Commit devem ser em português.
110
- `;
111
- async function main() {
112
- // Verifica se o diretório é um repositório git.
113
- try {
114
- execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
187
+ async function runCommitWizard() {
188
+ const config = loadConfig();
189
+ if (!config.apiKey) {
190
+ console.log(chalk.redBright('\n🚨 Erro: A variável de ambiente ') +
191
+ chalk.yellow('OPENAI_API_KEY') +
192
+ chalk.redBright(' não está definida.\n'));
193
+ console.log(chalk.white('→ Defina sua chave com: ') +
194
+ chalk.cyan('export OPENAI_API_KEY="sua-chave"') +
195
+ chalk.white(' ou configure no seu ') +
196
+ chalk.cyan('.bashrc') +
197
+ chalk.white(' ou ') +
198
+ chalk.cyan('.zshrc\n'));
199
+ process.exit(1);
115
200
  }
116
- catch {
201
+ const gitUtils = new GitUtils(config.excludePatterns);
202
+ const aiService = new AIService(config);
203
+ const diffProcessor = new DiffProcessor(aiService, config.maxTokens);
204
+ // Verifica se é um repositório Git
205
+ if (!gitUtils.isGitRepository()) {
117
206
  console.error(chalk.red('❌ Este diretório não é um repositório git.'));
118
207
  process.exit(1);
119
208
  }
120
- // Verifica se alterações staged, desconsiderando arquivos .lock
121
- let stagedFiles;
209
+ // Obtém status dos arquivos staged
210
+ const gitStatus = gitUtils.getStagedStatus();
211
+ if (!gitStatus.hasStagedFiles) {
212
+ console.log(chalk.yellow('⚠️ Não há alterações staged para o commit.'));
213
+ console.log(chalk.cyan('💡 Dica: Use ') +
214
+ chalk.white('git add <arquivos>') +
215
+ chalk.cyan(' para adicionar arquivos ao stage.'));
216
+ process.exit(0);
217
+ }
218
+ // Analisa complexidade do diff
219
+ const complexity = diffProcessor.analyzeDiffComplexity(gitStatus.diff);
220
+ const stats = diffProcessor.extractDiffStats(gitStatus.diff);
221
+ const hasBreakingChanges = diffProcessor.detectBreakingChanges(gitStatus.diff);
222
+ console.log(chalk.cyan.bold('\n📈 Análise do Diff:'));
223
+ console.log(chalk.white(`• Arquivos alterados: ${stats.files.length}`));
224
+ console.log(chalk.white(`• Linhas adicionadas: ${chalk.green(`+${stats.additions}`)}`));
225
+ console.log(chalk.white(`• Linhas removidas: ${chalk.red(`-${stats.deletions}`)}`));
226
+ console.log(chalk.white(`• Tokens estimados: ${complexity.tokenCount}`));
227
+ console.log(chalk.white(`• Complexidade: ${complexity.complexity === 'simple' ? '🟢 Simples' : complexity.complexity === 'moderate' ? '🟡 Moderada' : '🔴 Complexa'}`));
228
+ if (hasBreakingChanges) {
229
+ console.log(chalk.yellow('⚠️ Possíveis breaking changes detectadas!'));
230
+ }
231
+ // Processa o diff
232
+ console.log(chalk.cyan('\n🔍 Iniciando análise...'));
122
233
  try {
123
- stagedFiles = execSync('git diff --cached --name-only -- . ":(exclude)*.lock*"', { encoding: 'utf8' }).toString().trim();
124
- if (!stagedFiles) {
125
- console.log(chalk.yellow('⚠️ Não alterações staged para o commit.'));
126
- process.exit(0);
234
+ const processedDiff = await diffProcessor.processLargeDiff(gitStatus.diff);
235
+ // Gera mensagem de commit
236
+ const generateSpinner = ora('🤖 Gerando mensagem de commit com IA...').start();
237
+ const response = await aiService.generateCommitMessage(processedDiff);
238
+ generateSpinner.succeed('✨ Mensagem de commit gerada com sucesso.');
239
+ if (response.usage) {
240
+ console.log(chalk.dim(`\n💰 Tokens utilizados: ${response.usage.totalTokens} (prompt: ${response.usage.promptTokens}, resposta: ${response.usage.completionTokens})`));
241
+ }
242
+ console.log(chalk.greenBright('\n✨ Mensagem de commit gerada:'));
243
+ console.log(chalk.yellowBright(response.content));
244
+ // Se auto commit estiver ativado, faz o commit diretamente
245
+ if (config.autoCommit) {
246
+ console.log(chalk.blue('\n🚀 Auto commit ativado, realizando commit...'));
247
+ gitUtils.commit(response.content);
248
+ console.log(chalk.green.bold('✅ Commit realizado com sucesso.'));
249
+ return;
250
+ }
251
+ // Menu de ações
252
+ const { action } = await inquirer.prompt([
253
+ {
254
+ type: 'list',
255
+ name: 'action',
256
+ message: chalk.blue.bold('O que deseja fazer com a mensagem de commit?'),
257
+ choices: [
258
+ { name: '📌 Confirmar e commitar', value: 'confirm' },
259
+ { name: '📝 Editar a mensagem antes de commitar', value: 'edit' },
260
+ { name: '🔄 Regenerar mensagem', value: 'regenerate' },
261
+ { name: '📋 Copiar para clipboard', value: 'copy' },
262
+ { name: '🚫 Cancelar o commit', value: 'cancel' },
263
+ ],
264
+ },
265
+ ]);
266
+ if (action === 'regenerate') {
267
+ // Recursão para regenerar
268
+ return runCommitWizard();
269
+ }
270
+ if (action === 'copy') {
271
+ try {
272
+ execSync(`echo "${response.content}" | pbcopy`, { stdio: 'ignore' });
273
+ console.log(chalk.green('📋 Mensagem copiada para o clipboard!'));
274
+ }
275
+ catch {
276
+ console.log(chalk.yellow('⚠️ Não foi possível copiar para o clipboard. Mensagem:'));
277
+ console.log(response.content);
278
+ }
279
+ return;
280
+ }
281
+ if (action === 'cancel') {
282
+ console.log(chalk.yellow('🚫 Commit cancelado pelo usuário.'));
283
+ return;
284
+ }
285
+ let finalMessage = response.content;
286
+ if (action === 'edit') {
287
+ const tempFilePath = path.join(os.tmpdir(), 'COMMIT_EDITMSG');
288
+ fs.writeFileSync(tempFilePath, response.content);
289
+ console.log(chalk.cyan('📝 Abrindo editor para edição da mensagem...'));
290
+ const editor = process.env.EDITOR || 'nano';
291
+ try {
292
+ execSync(`${editor} ${tempFilePath}`, { stdio: 'inherit' });
293
+ finalMessage = fs.readFileSync(tempFilePath, 'utf8').trim();
294
+ fs.unlinkSync(tempFilePath);
295
+ if (!finalMessage) {
296
+ console.error(chalk.red('❌ Nenhuma mensagem inserida, commit cancelado.'));
297
+ return;
298
+ }
299
+ }
300
+ catch (error) {
301
+ console.error(chalk.red('❌ Erro ao abrir o editor:'), error);
302
+ fs.unlinkSync(tempFilePath);
303
+ return;
304
+ }
305
+ }
306
+ // Realiza o commit
307
+ const additionalArgs = process.argv
308
+ .slice(2)
309
+ .filter(arg => !['--config', '--info'].includes(arg));
310
+ gitUtils.commit(finalMessage, additionalArgs);
311
+ console.log(chalk.green.bold('✅ Commit realizado com sucesso.'));
312
+ // Mostra informações do commit criado
313
+ try {
314
+ const commitInfo = gitUtils.getLastCommitInfo();
315
+ console.log(chalk.dim(`\n📝 Commit ${commitInfo.hash.substring(0, 7)}: ${commitInfo.message}`));
316
+ }
317
+ catch {
318
+ // Ignora erros ao obter info do commit
127
319
  }
128
320
  }
129
321
  catch (error) {
130
- console.error(chalk.red('❌ Erro ao verificar alterações staged:'), error);
322
+ console.error(chalk.red('❌ Erro durante o commit:'), error);
131
323
  process.exit(1);
132
324
  }
133
- // Obtém o diff completo das alterações staged, ignorando arquivos .lock*
134
- let diff;
135
- try {
136
- diff = execSync('git diff --cached -- . ":(exclude)*.lock*"', { encoding: 'utf8' });
325
+ }
326
+ /**
327
+ * Função para multi-commit inteligente
328
+ */
329
+ async function runMultiCommit() {
330
+ const config = loadConfig();
331
+ if (!config.apiKey) {
332
+ console.log(chalk.redBright('\n🚨 Erro: A variável de ambiente ') +
333
+ chalk.yellow('OPENAI_API_KEY') +
334
+ chalk.redBright(' não está definida.\n'));
335
+ process.exit(1);
137
336
  }
138
- catch (error) {
139
- console.error(chalk.red('❌ Erro ao obter o diff:'), error);
337
+ const gitUtils = new GitUtils(config.excludePatterns);
338
+ const aiService = new AIService(config);
339
+ const commitSplitter = new CommitSplitter(gitUtils, config);
340
+ // Verifica se é um repositório Git
341
+ if (!gitUtils.isGitRepository()) {
342
+ console.error(chalk.red('❌ Este diretório não é um repositório git.'));
140
343
  process.exit(1);
141
344
  }
142
- // Divide o diff em chunks com base no número máximo de tokens.
143
- const MAX_TOKENS = 1000;
144
- const chunks = chunkDiff(diff, MAX_TOKENS);
145
- let inputForCommit;
146
- if (chunks.length === 1) {
147
- inputForCommit = chunks[0];
148
- }
149
- else {
150
- // Se houver vários chunks, gera um resumo para todos eles utilizando um único spinner.
151
- const partialSummaries = [];
152
- const chunkSummaryPrefix = "A partir do diff abaixo, extraia um resumo breve das alterações (use linguagem imperativa e em português):";
153
- // Inicia um spinner único para todo o processo
154
- const spinnerSummary = ora("Gerando resumo do commit.").start();
155
- try {
156
- for (const chunk of chunks) {
157
- const prompt = `${chunkSummaryPrefix}\n\n${chunk}`;
158
- const summary = await callOpenAI(prompt, 'resumo');
159
- partialSummaries.push(summary);
345
+ // Obtém status dos arquivos staged
346
+ const gitStatus = gitUtils.getStagedStatus();
347
+ if (!gitStatus.hasStagedFiles) {
348
+ console.log(chalk.yellow('⚠️ Não há alterações staged para o commit.'));
349
+ console.log(chalk.cyan('💡 Dica: Use ') +
350
+ chalk.white('git add <arquivos>') +
351
+ chalk.cyan(' para adicionar arquivos ao stage.'));
352
+ process.exit(0);
353
+ }
354
+ console.log(chalk.blue.bold('\n🎯 Multi-Commit Inteligente'));
355
+ console.log(chalk.gray('Dividindo alterações em commits organizados por contexto...\n'));
356
+ try {
357
+ // Analisa e divide o diff
358
+ const splitResult = await commitSplitter.analyzeAndSplit();
359
+ if (splitResult.groups.length <= 1) {
360
+ console.log(chalk.yellow('📝 Apenas um contexto detectado. Fazendo commit normal...'));
361
+ return runCommitWizard();
362
+ }
363
+ // Exibe preview dos grupos propostos
364
+ console.log(chalk.cyan.bold(`\n📋 ${splitResult.groups.length} commits propostos:`));
365
+ for (let i = 0; i < splitResult.groups.length; i++) {
366
+ const group = splitResult.groups[i];
367
+ console.log(chalk.white(`\n${i + 1}. ${group.emoji} ${group.type}: ${group.description}`));
368
+ console.log(chalk.gray(` Arquivos (${group.files.length}): ${group.files.join(', ')}`));
369
+ }
370
+ // Confirma se quer prosseguir
371
+ const { proceed } = await inquirer.prompt([
372
+ {
373
+ type: 'confirm',
374
+ name: 'proceed',
375
+ message: 'Deseja prosseguir com estes commits?',
376
+ default: true,
377
+ },
378
+ ]);
379
+ if (!proceed) {
380
+ console.log(chalk.yellow('🚫 Multi-commit cancelado pelo usuário.'));
381
+ return;
382
+ }
383
+ // Executa commits sequencialmente
384
+ let successCount = 0;
385
+ const totalCommits = splitResult.groups.length;
386
+ for (let i = 0; i < splitResult.groups.length; i++) {
387
+ const group = splitResult.groups[i];
388
+ console.log(chalk.blue(`\n[${i + 1}/${totalCommits}] Processando: ${group.emoji} ${group.type}`));
389
+ try {
390
+ // Remove todos os arquivos do stage
391
+ execSync('git reset HEAD .');
392
+ // Adiciona apenas os arquivos deste grupo
393
+ for (const file of group.files) {
394
+ execSync(`git add "${file}"`);
395
+ }
396
+ // Gera mensagem de commit específica para este grupo
397
+ const response = await aiService.generateCommitMessage(group.diff);
398
+ let commitMessage = response.content;
399
+ // Adiciona emoji se configurado
400
+ if (config.includeEmoji && !commitMessage.startsWith(group.emoji)) {
401
+ commitMessage = `${group.emoji} ${commitMessage}`;
402
+ }
403
+ // Realiza o commit
404
+ gitUtils.commit(commitMessage);
405
+ console.log(chalk.green(`✅ Commit ${i + 1}: ${commitMessage.split('\n')[0]}`));
406
+ successCount++;
407
+ }
408
+ catch (error) {
409
+ console.error(chalk.red(`❌ Erro no commit ${i + 1}:`), error);
410
+ const { continueOnError } = await inquirer.prompt([
411
+ {
412
+ type: 'confirm',
413
+ name: 'continueOnError',
414
+ message: 'Deseja continuar com os próximos commits?',
415
+ default: false,
416
+ },
417
+ ]);
418
+ if (!continueOnError) {
419
+ break;
420
+ }
160
421
  }
161
- spinnerSummary.succeed("Resumo do commit gerado.");
162
422
  }
163
- catch (error) {
164
- spinnerSummary.fail("Erro ao gerar resumo do commit.");
165
- console.error(chalk.red('❌ Erro ao gerar resumo para o commit:'), error);
166
- process.exit(1);
423
+ // Resultado final
424
+ console.log(chalk.cyan.bold(`\n🎉 Multi-commit concluído!`));
425
+ console.log(chalk.white(`• Commits realizados: ${successCount}/${totalCommits}`));
426
+ if (successCount > 0) {
427
+ console.log(chalk.green('✨ Histórico organizado com sucesso!'));
167
428
  }
168
- inputForCommit = partialSummaries.join("\n\n");
169
- }
170
- // Gera a mensagem de commit com o pré-prompt e o diff (ou seus resumos).
171
- const finalPrompt = `${COMMIT_PROMPT}\n\nDiff:\n\n${inputForCommit}`;
172
- const spinnerCommit = ora('Gerando mensagem de commit com base no diff...').start();
173
- let generatedMessage;
174
- try {
175
- generatedMessage = await callOpenAI(finalPrompt, 'commit');
176
- // Remove os delimitadores de bloco de código (```)
177
- generatedMessage = generatedMessage.replace(/```/g, '').trim();
178
- spinnerCommit.succeed('Mensagem de commit gerada com sucesso.');
179
429
  }
180
430
  catch (error) {
181
- spinnerCommit.fail('Erro ao gerar a mensagem de commit.');
182
- console.error(chalk.red('❌ Erro ao gerar a mensagem de commit:'), error);
431
+ console.error(chalk.red('Erro durante o multi-commit:'), error);
183
432
  process.exit(1);
184
433
  }
185
- console.log(chalk.greenBright('\n✨ Mensagem de commit gerada automaticamente:'));
186
- console.log(chalk.yellowBright(generatedMessage));
187
- // Pergunta ao usuário se deseja confirmar, editar ou cancelar o commit.
188
- const promptModule = inquirer.createPromptModule();
189
- const { action } = await promptModule([
190
- {
191
- type: 'list',
192
- name: 'action',
193
- message: chalk.blue.bold('O que deseja fazer com a mensagem de commit gerada?'),
194
- choices: [
195
- { name: '📌 Confirmar e commitar', value: 'confirm' },
196
- { name: '📝 Editar a mensagem antes de commitar', value: 'edit' },
197
- { name: '🚫 Cancelar o commit', value: 'cancel' },
198
- ],
199
- },
200
- ]);
201
- // Cria um arquivo temporário para armazenar a mensagem (para edição se necessário).
202
- const tempFilePath = path.join(os.tmpdir(), 'COMMIT_EDITMSG');
203
- fs.writeFileSync(tempFilePath, generatedMessage);
204
- if (action === 'edit') {
205
- console.log(chalk.cyan('📝 Abrindo editor para edição da mensagem...'));
206
- const editor = process.env.EDITOR || 'nano';
207
- try {
208
- execSync(`${editor} ${tempFilePath}`, { stdio: 'inherit' });
209
- }
210
- catch (error) {
211
- console.error(chalk.red('❌ Erro ao abrir o editor:'), error);
212
- process.exit(1);
213
- }
214
- }
215
- else if (action === 'cancel') {
216
- console.log(chalk.yellow('🚫 Commit cancelado pelo usuário.'));
217
- fs.unlinkSync(tempFilePath);
218
- process.exit(0);
219
- }
220
- // Lê a mensagem final (após eventual edição).
221
- const finalMessage = fs.readFileSync(tempFilePath, 'utf8').trim();
222
- if (!finalMessage) {
223
- console.error(chalk.red('❌ Nenhuma mensagem inserida, commit cancelado.'));
224
- fs.unlinkSync(tempFilePath);
225
- process.exit(1);
434
+ }
435
+ /**
436
+ * Função principal
437
+ */
438
+ async function main() {
439
+ const args = process.argv.slice(2);
440
+ // Verifica argumentos de linha de comando
441
+ if (args.includes('--help') || args.includes('-h')) {
442
+ console.log(chalk.blue.bold('\n🧙‍♂️ Commit Wizard - Gerador de commits com IA\n'));
443
+ console.log(chalk.white('Uso: commit-wizard [opções]\n'));
444
+ console.log(chalk.white('Opções:'));
445
+ console.log(chalk.white(' --config, -c Abrir menu de configuração'));
446
+ console.log(chalk.white(' --info, -i Mostrar informações do sistema'));
447
+ console.log(chalk.white(' --split, -s Dividir em múltiplos commits por contexto'));
448
+ console.log(chalk.white(' --help, -h Mostrar esta ajuda'));
449
+ console.log(chalk.white('\nExemplos:'));
450
+ console.log(chalk.cyan(' commit-wizard # Gerar commit normal'));
451
+ console.log(chalk.cyan(' commit-wizard --split # Multi-commit por contexto'));
452
+ console.log(chalk.cyan(' commit-wizard --config # Configurar o wizard'));
453
+ console.log(chalk.cyan(' commit-wizard --info # Ver informações do sistema'));
454
+ return;
226
455
  }
227
- // Captura quaisquer argumentos adicionais passados para o comando.
228
- const gitArgs = process.argv.slice(2).join(' ');
229
- console.log(chalk.blue('🔍 Argumentos adicionais para o commit:'), gitArgs);
230
- // Realiza o commit com a mensagem final.
231
- try {
232
- execSync(`git commit -F ${tempFilePath} ${gitArgs}`, { stdio: 'inherit' });
233
- console.log(chalk.green.bold('✅ Commit realizado com sucesso.'));
456
+ if (args.includes('--config') || args.includes('-c')) {
457
+ await configurationMenu();
458
+ return;
234
459
  }
235
- catch (error) {
236
- console.error(chalk.red('❌ Erro ao realizar o commit:'), error);
460
+ if (args.includes('--info') || args.includes('-i')) {
461
+ const config = loadConfig();
462
+ const gitUtils = new GitUtils(config.excludePatterns);
463
+ displaySystemInfo(config, gitUtils);
464
+ return;
237
465
  }
238
- finally {
239
- fs.unlinkSync(tempFilePath);
466
+ if (args.includes('--split') || args.includes('-s')) {
467
+ await runMultiCommit();
468
+ return;
240
469
  }
470
+ // Executa o wizard principal
471
+ await runCommitWizard();
241
472
  }
242
- main().catch((err) => {
243
- console.error(chalk.red('❌ Erro durante o commit:'), err);
473
+ main().catch(err => {
474
+ console.error(chalk.red('❌ Erro durante execução:'), err);
244
475
  process.exit(1);
245
476
  });
246
477
  //# sourceMappingURL=index.js.map