@gilbert_oliveira/commit-wizard 2.13.0-canary.2 → 2.13.0-canary.4
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/commit-wizard.js +43 -40
- package/package.json +3 -1
- package/src/commitlint/index.ts +146 -2
- package/src/core/index.ts +17 -6
- package/src/core/openai.ts +142 -11
- package/src/core/smart-split.ts +4 -2
package/dist/commit-wizard.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
`).filter(o=>o.length>0),i=t.length>0?E("git diff --cached",{encoding:"utf-8",stdio:"pipe"}):"";return{hasStaged:t.length>0,stagedFiles:t,diff:i.trim()}}catch(e){throw new Error(`Erro ao obter status do Git: ${e instanceof Error?e.message:"Erro desconhecido"}`)}}function
|
|
4
|
-
`).filter(n=>n.length>0),i=0,o=0;return t.forEach(n=>{let[s,
|
|
5
|
-
... (diff otimizado para focar em mudan\xE7as principais)`;if(
|
|
2
|
+
var Ze=Object.defineProperty;var S=(e,t)=>()=>(e&&(t=e(e=0)),t);var O=(e,t)=>{for(var i in t)Ze(e,i,{get:t[i],enumerable:!0})};import Pt from"path";import{fileURLToPath as It}from"url";var p=S(()=>{"use strict"});import{existsSync as be,readFileSync as ve}from"fs";import{join as Ce}from"path";function Se(e){let t;try{t=Ce(process.cwd(),".commit-wizardrc")}catch{t="/tmp/.commit-wizardrc"}let i=Ce(process.env.HOME||process.env.USERPROFILE||"/tmp",".commit-wizardrc"),o={...Xe};try{if(be(i)){let s=ve(i,"utf-8"),r=JSON.parse(s);o=xe(o,r)}}catch{console.warn("\u26A0\uFE0F Erro ao ler configura\xE7\xE3o global: Erro desconhecido")}let n=e||t;try{if(be(n)){let s=ve(n,"utf-8"),r=JSON.parse(s);o=xe(o,r)}}catch{console.warn("\u26A0\uFE0F Erro ao ler .commit-wizardrc: Erro desconhecido")}return o.openai.apiKey=process.env.OPENAI_API_KEY,process.env.COMMIT_WIZARD_DEBUG,process.env.COMMIT_WIZARD_DRY_RUN==="true"&&(o.dryRun=!0),o}function xe(e,t){return{...e,...t,openai:{...e.openai,...t.openai},smartSplit:{...e.smartSplit,...t.smartSplit},cache:{...e.cache,...t.cache},commitlint:{...e.commitlint,...t.commitlint}}}function we(e){let t=[];return e.openai.apiKey||t.push("OPENAI_API_KEY n\xE3o encontrada nas vari\xE1veis de ambiente"),(e.openai.maxTokens<10||e.openai.maxTokens>4e3)&&t.push("maxTokens deve estar entre 10 e 4000"),(e.openai.temperature<0||e.openai.temperature>2)&&t.push("temperature deve estar entre 0 e 2"),["pt","en","es","fr","de","it","ja","ko","zh"].includes(e.language)||t.push("language deve ser um idioma suportado (pt, en, es, fr, de, it, ja, ko, zh)"),["conventional","simple","detailed"].includes(e.commitStyle)||t.push("commitStyle deve ser conventional, simple ou detailed"),e.smartSplit.minGroupSize<1&&t.push("smartSplit.minGroupSize deve ser pelo menos 1"),(e.smartSplit.maxGroups<1||e.smartSplit.maxGroups>10)&&t.push("smartSplit.maxGroups deve estar entre 1 e 10"),(e.smartSplit.confidenceThreshold<0||e.smartSplit.confidenceThreshold>1)&&t.push("smartSplit.confidenceThreshold deve estar entre 0 e 1"),e.cache.ttl<1&&t.push("cache.ttl deve ser pelo menos 1 minuto"),e.cache.maxSize<1&&t.push("cache.maxSize deve ser pelo menos 1"),t}var Xe,$e=S(()=>{"use strict";p();Xe={openai:{model:"gpt-4o",maxTokens:150,temperature:.7,timeout:3e4,retries:2},language:"pt",commitStyle:"conventional",autoCommit:!1,splitCommits:!1,dryRun:!1,smartSplit:{enabled:!0,minGroupSize:1,maxGroups:5,confidenceThreshold:.7},cache:{enabled:!0,ttl:60,maxSize:100},commitlint:{enabled:!0}}});var j={};O(j,{escapeShellArg:()=>T,executeCommit:()=>M,executeFileCommit:()=>G,getDiffStats:()=>Y,getFileDiff:()=>et,getGitStatus:()=>K,isGitRepository:()=>B});import{execSync as E}from"child_process";function T(e){return`'${e.replace(/'/g,`'"'"'`)}'`}function B(){try{return E("git rev-parse --git-dir",{stdio:"ignore"}),!0}catch{return!1}}function K(){try{let t=E("git diff --cached --name-only",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
|
|
3
|
+
`).filter(o=>o.length>0),i=t.length>0?E("git diff --cached",{encoding:"utf-8",stdio:"pipe"}):"";return{hasStaged:t.length>0,stagedFiles:t,diff:i.trim()}}catch(e){throw new Error(`Erro ao obter status do Git: ${e instanceof Error?e.message:"Erro desconhecido"}`)}}function et(e){try{return E(`git diff --cached -- "${e}"`,{encoding:"utf-8",stdio:"pipe"})}catch(t){throw new Error(`Erro ao obter diff do arquivo ${e}: ${t instanceof Error?t.message:"Erro desconhecido"}`)}}function M(e){try{let t=T(e);return E(`git commit -m ${t}`,{stdio:"pipe"}),{success:!0,hash:E("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:e}}catch(t){return{success:!1,error:t instanceof Error?t.message:"Erro desconhecido ao executar commit"}}}function G(e,t){try{let i=T(t),o=T(e);return E(`git commit ${o} -m ${i}`,{stdio:"pipe"}),{success:!0,hash:E("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:t}}catch(i){return{success:!1,error:i instanceof Error?i.message:"Erro desconhecido ao executar commit do arquivo"}}}function Y(){try{let t=E("git diff --cached --numstat",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
|
|
4
|
+
`).filter(n=>n.length>0),i=0,o=0;return t.forEach(n=>{let[s,r]=n.split(" ");s&&s!=="-"&&(i+=parseInt(s)||0),r&&r!=="-"&&(o+=parseInt(r)||0)}),{added:i,removed:o,files:t.length}}catch{return{added:0,removed:0,files:0}}}var w=S(()=>{"use strict";p()});var ze={};O(ze,{buildPrompt:()=>Ee,detectCommitType:()=>Re,extractCommitTypeFromMessage:()=>je,generateCommitMessage:()=>Fe,generateWithRetry:()=>D,processOpenAIMessage:()=>Me,smartFilterDiff:()=>Ae});function Ae(e,t){if(e.length<=t)return e;let i=[/^diff --git a\/package-lock\.json/m,/^diff --git a\/yarn\.lock/m,/^diff --git a\/pnpm-lock\.yaml/m,/^diff --git a\/composer\.lock/m,/^diff --git a\/Gemfile\.lock/m,/^diff --git a\/Cargo\.lock/m,/^diff --git a\/poetry\.lock/m,/^diff --git a\/go\.sum/m,/^diff --git a\/requirements(?:\.txt)?\.lock/m,/^diff --git a\/.*\.min\.js/m,/^diff --git a\/.*\.min\.css/m,/^diff --git a\/.*\.map/m,/^diff --git a\/dist\//m,/^diff --git a\/build\//m],o=e.split(/(?=^diff --git)/m).filter(a=>a.trim()),n=[],s=[];o.forEach(a=>{i.some(h=>h.test(a))?s.push(a):n.push(a)});let r="",l=t;for(let a of n)if(a.length<=l)r+=a,l-=a.length;else if(l>200){r+=a.substring(0,l-50),l=t-r.length;break}else break;if(!r&&s.length>0&&l>0)for(let a of s)if(a.length<=l)r+=a,l-=a.length;else if(l>200){r+=a.substring(0,l-50),l=t-r.length;break}else break;let m=`
|
|
5
|
+
... (diff otimizado para focar em mudan\xE7as principais)`;if(l>200&&s.length>0){let a=`
|
|
6
6
|
|
|
7
|
-
... (${s.length} arquivo(s) de depend\xEAncias/build omitido(s): ${s.map(h=>{let
|
|
7
|
+
... (${s.length} arquivo(s) de depend\xEAncias/build omitido(s): ${s.map(h=>{let y=h.match(/^diff --git a\/(.*?) b\//);return y?y[1]:""}).filter(Boolean).join(", ")})`,u=l-m.length;a.length<u&&(r+=a)}return r.trim()!==e.trim()?r+m:r}function Ee(e,t,i,o){let n=t.language==="pt"?"portugu\xEAs":"english",s=o!=null?tt(t.commitStyle,t.language,o):it(t.commitStyle,t.language),l=Ae(e,6e3),m=i.length>10?`${i.length} arquivos: ${i.slice(0,5).join(", ")}...`:i.join(", ");return`Gere mensagem de commit em ${n} (${t.commitStyle}).
|
|
8
8
|
|
|
9
|
-
Arquivos: ${
|
|
9
|
+
Arquivos: ${m}
|
|
10
10
|
|
|
11
|
-
${
|
|
11
|
+
${s}
|
|
12
12
|
|
|
13
13
|
Diff:
|
|
14
14
|
\`\`\`
|
|
15
|
-
${
|
|
15
|
+
${l}
|
|
16
16
|
\`\`\`
|
|
17
17
|
|
|
18
|
-
Mensagem:`}function
|
|
19
|
-
|
|
18
|
+
Mensagem:`}function tt(e,t,i){let o=t==="pt",n=[];if(e==="conventional"){n.push(o?"- Use formato: tipo(escopo): descri\xE7\xE3o":"- Use format: type(scope): description");let s=i.typeEnum?.join(", ")??Q;n.push(o?`- Tipos v\xE1lidos: ${s}`:`- Valid types: ${s}`);let r=i.headerMaxLength??72;n.push(o?`- Mantenha a primeira linha com at\xE9 ${r} caracteres`:`- Keep first line under ${r} characters`);let l=i.typeEnum?.[0]??"feat";if(n.push(o?`- Exemplo: "${l}(auth): adicionar valida\xE7\xE3o de email"`:`- Example: "${l}(auth): add email validation"`),i.subjectCase){let{condition:m,cases:c}=i.subjectCase,a=c.join(", ");m==="never"?n.push(o?`- Subject nunca em: ${a}`:`- Subject must never be: ${a}`):m==="always"&&n.push(o?`- Subject deve estar em: ${a}`:`- Subject must be in: ${a}`)}if(i.subjectFullStop){let{condition:m,value:c}=i.subjectFullStop,a=c??".";m==="never"?n.push(o?`- N\xE3o termine o subject com "${a}"`:`- Do not end subject with "${a}"`):m==="always"&&n.push(o?`- Termine o subject com "${a}"`:`- End subject with "${a}"`)}i.bodyLeadingBlank===!0&&n.push(o?"- Adicione uma linha em branco entre o header e o body":"- Add a blank line between header and body"),i.scopeEnum&&i.scopeEnum.length>0&&n.push(o?`- Escopos permitidos: ${i.scopeEnum.join(", ")}`:`- Allowed scopes: ${i.scopeEnum.join(", ")}`)}else if(e==="simple"){let s=i.headerMaxLength??50;o?(n.push("- Use formato simples e direto"),n.push("- Comece com verbo no infinitivo"),n.push('- Exemplo: "corrigir valida\xE7\xE3o de formul\xE1rio"'),n.push(`- M\xE1ximo ${s} caracteres`)):(n.push("- Use simple and direct format"),n.push("- Start with imperative verb"),n.push('- Example: "fix form validation"'),n.push(`- Maximum ${s} characters`))}else{let s=i.headerMaxLength??72;o?(n.push(`- Primeira linha: resumo em at\xE9 ${s} caracteres`),n.push("- Se necess\xE1rio, adicione corpo explicativo"),n.push("- Use presente do indicativo"),n.push("- Seja descritivo mas conciso")):(n.push(`- First line: summary under ${s} characters`),n.push("- Add explanatory body if needed"),n.push("- Use imperative mood"),n.push("- Be descriptive but concise"))}return n.join(`
|
|
19
|
+
`)}function it(e,t){let i={pt:{conventional:`- Use formato: tipo(escopo): descri\xE7\xE3o
|
|
20
|
+
- Tipos v\xE1lidos: ${Q}
|
|
20
21
|
- Exemplo: "feat(auth): adicionar valida\xE7\xE3o de email"
|
|
21
22
|
- Mantenha a primeira linha com at\xE9 50 caracteres`,simple:`- Use formato simples e direto
|
|
22
23
|
- Comece com verbo no infinitivo
|
|
@@ -25,7 +26,7 @@ Mensagem:`}function Ye(e,t){let i={pt:{conventional:`- Use formato: tipo(escopo)
|
|
|
25
26
|
- Se necess\xE1rio, adicione corpo explicativo
|
|
26
27
|
- Use presente do indicativo
|
|
27
28
|
- Seja descritivo mas conciso`},en:{conventional:`- Use format: type(scope): description
|
|
28
|
-
- Valid types:
|
|
29
|
+
- Valid types: ${Q}
|
|
29
30
|
- Example: "feat(auth): add email validation"
|
|
30
31
|
- Keep first line under 50 characters`,simple:`- Use simple and direct format
|
|
31
32
|
- Start with imperative verb
|
|
@@ -33,23 +34,23 @@ Mensagem:`}function Ye(e,t){let i={pt:{conventional:`- Use formato: tipo(escopo)
|
|
|
33
34
|
- Maximum 50 characters`,detailed:`- First line: summary under 50 characters
|
|
34
35
|
- Add explanatory body if needed
|
|
35
36
|
- Use imperative mood
|
|
36
|
-
- Be descriptive but concise`}},o=t==="pt"?"pt":"en";return i[o][e]||i[o].conventional}function
|
|
37
|
+
- Be descriptive but concise`}},o=t==="pt"?"pt":"en";return i[o][e]||i[o].conventional}function je(e){let t={feat:/^(feat|feature)(\([^)]+\))?:/i,fix:/^(fix|bugfix)(\([^)]+\))?:/i,docs:/^(docs|documentation)(\([^)]+\))?:/i,style:/^(style|format)(\([^)]+\))?:/i,refactor:/^(refactor|refactoring)(\([^)]+\))?:/i,test:/^(test|testing)(\([^)]+\))?:/i,chore:/^(chore|maintenance)(\([^)]+\))?:/i,build:/^(build|ci)(\([^)]+\))?:/i,ci:/^(ci|continuous-integration)(\([^)]+\))?:/i};for(let[i,o]of Object.entries(t))if(o.test(e))return i;return null}function Re(e,t){let i=e.toLowerCase(),o=t.join(" ").toLowerCase();return o.includes("test")||o.includes("spec")||i.includes("test(")?"test":o.includes("readme")||o.includes(".md")||o.includes("docs")?"docs":o.includes("package.json")||o.includes("dockerfile")||o.includes(".yml")||o.includes(".yaml")||o.includes("webpack")||o.includes("tsconfig")?"build":o.includes(".css")||o.includes(".scss")||i.includes("style")||i.includes("format")?"style":i.includes("fix")||i.includes("bug")||i.includes("error")||i.includes("issue")?"fix":i.includes("add")||i.includes("new")||i.includes("create")||i.includes("implement")?"feat":i.includes("refactor")||i.includes("restructure")||i.includes("rename")?"refactor":"chore"}function Me(e){if(e.startsWith("```")){let t=e.match(/^```[^\n`]*\n([\s\S]*?)\n\s*```([\s\S]*)$/);if(t){let i=t[1].trim(),o=t[2].trim();e=o?`${i}
|
|
37
38
|
|
|
38
|
-
${o}`:i}else e.match(/^```[\s\S]*```$/)&&(e=e.replace(/^```(?:plaintext|javascript|typescript|python|java|html|css|json|xml|yaml|yml|bash|shell|text)?\s*/,"").replace(/\s*```$/,""))}return e=e.trim(),e}async function
|
|
39
|
-
Mensagem: "${e.message}"`,"\u{1F4AD} Sugest\xE3o de Commit");let t=await
|
|
39
|
+
${o}`:i}else e.match(/^```[\s\S]*```$/)&&(e=e.replace(/^```(?:plaintext|javascript|typescript|python|java|html|css|json|xml|yaml|yml|bash|shell|text)?\s*/,"").replace(/\s*```$/,""))}return e=e.trim(),e}async function Fe(e,t,i,o){try{if(!t.openai.apiKey)return{success:!1,error:"Chave da OpenAI n\xE3o encontrada. Configure OPENAI_API_KEY nas vari\xE1veis de ambiente."};let n=Ee(e,t,i,o),s=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{Authorization:`Bearer ${t.openai.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:t.openai.model,messages:[{role:"user",content:n}],max_tokens:Math.min(t.openai.maxTokens,150),temperature:t.openai.temperature})});if(!s.ok){let a=await s.json().catch(()=>({}));return{success:!1,error:`Erro da OpenAI (${s.status}): ${a.error?.message||"Erro desconhecido"}`}}let l=(await s.json()).choices?.[0]?.message?.content?.trim();if(!l)return{success:!1,error:"OpenAI retornou resposta vazia"};l=Me(l);let m=je(l),c=Re(e,i);return{success:!0,suggestion:{message:l,type:m||c,confidence:.8}}}catch(n){return{success:!1,error:`Erro ao conectar com OpenAI: ${n instanceof Error?n.message:"Erro desconhecido"}`}}}async function D(e,t,i,o=3,n){let s="";for(let r=0;r<o;r++){let l=await Fe(e,t,i,n);if(l.success)return l;s=l.error||"Erro desconhecido",r<o-1&&await new Promise(m=>setTimeout(m,Math.pow(2,r)*1e3))}return{success:!1,error:`Falha ap\xF3s ${o} tentativas. \xDAltimo erro: ${s}`}}var Q,Z=S(()=>{"use strict";p();Q="feat, fix, docs, style, refactor, test, chore, build, ci"});var te={};O(te,{askContinueCommits:()=>ee,confirmCommit:()=>at,copyToClipboard:()=>_,editCommitMessage:()=>U,selectFilesForCommit:()=>X,showCancellation:()=>P,showCommitPreview:()=>N,showCommitResult:()=>v});import{text as ot,select as nt,confirm as q,log as F,note as Pe,cancel as st,isCancel as z}from"@clack/prompts";import rt from"clipboardy";async function N(e){Pe(`Tipo: ${e.type}
|
|
40
|
+
Mensagem: "${e.message}"`,"\u{1F4AD} Sugest\xE3o de Commit");let t=await nt({message:"O que voc\xEA gostaria de fazer?",options:[{value:"commit",label:"\u2705 Fazer commit com esta mensagem",hint:"Executar git commit imediatamente"},{value:"edit",label:"\u270F\uFE0F Editar mensagem",hint:"Modificar a mensagem antes de commitar"},{value:"copy",label:"\u{1F4CB} Copiar para clipboard",hint:"Copiar mensagem e sair sem commitar"},{value:"cancel",label:"\u274C Cancelar",hint:"Sair sem fazer nada"}]});return z(t)?{action:"cancel"}:{action:t}}async function U(e){let t=await ot({message:"Edite a mensagem do commit:",initialValue:e,placeholder:"Digite a mensagem do commit...",validate:o=>{if(!o||o.trim().length===0)return"A mensagem n\xE3o pode estar vazia";if(o.trim().length>72)return"A mensagem est\xE1 muito longa (m\xE1ximo 72 caracteres recomendado)"}});if(z(t))return{action:"cancel"};let i=await q({message:`Confirma a mensagem editada: "${t}"?`});return z(i)||!i?{action:"cancel"}:{action:"commit",message:t}}async function _(e){try{return await rt.write(e),F.success("\u2705 Mensagem copiada para a \xE1rea de transfer\xEAncia!"),!0}catch(t){return F.error(`\u274C Erro ao copiar: ${t instanceof Error?t.message:"Erro desconhecido"}`),!1}}async function at(e){Pe(`"${e}"`,"\u{1F680} Confirmar Commit");let t=await q({message:"Executar o commit agora?"});return z(t)?!1:t}function v(e,t,i){e&&t?(F.success("\u2705 Commit realizado com sucesso!"),F.info(`\u{1F517} Hash: ${t.substring(0,8)}`)):F.error(`\u274C Erro ao realizar commit: ${i||"Erro desconhecido"}`)}async function X(e){F.info("\u{1F4CB} Modo Split: Selecione os arquivos para este commit");let t=[];for(let i of e){let o=await q({message:`Incluir "${i}" neste commit?`});if(z(o))break;o&&t.push(i)}return t}async function ee(e){if(e.length===0)return!1;F.info(`\u{1F4C4} Arquivos restantes: ${e.join(", ")}`);let t=await q({message:"Gerar commit para os arquivos restantes?"});return z(t)?!1:t}function P(){st("Opera\xE7\xE3o cancelada pelo usu\xE1rio")}var L=S(()=>{"use strict";p()});var Ge={};O(Ge,{chooseSplitMode:()=>oe,confirmGroupCommit:()=>lt,showSmartSplitGroups:()=>mt,showSmartSplitProgress:()=>ut});import{select as Ie,confirm as ct,log as ke,note as Oe,isCancel as ie}from"@clack/prompts";async function oe(){let e=await Ie({message:"Como voc\xEA gostaria de organizar os commits?",options:[{value:"smart",label:"\u{1F9E0} Smart Split (Recomendado)",hint:"IA analisa contexto e agrupa automaticamente"},{value:"manual",label:"\u270B Split Manual",hint:"Voc\xEA escolhe arquivos manualmente"},{value:"cancel",label:"\u274C Cancelar",hint:"Voltar ao modo normal"}]});return ie(e)?{action:"cancel"}:e==="manual"?{action:"manual"}:e==="smart"?{action:"proceed"}:{action:"cancel"}}async function mt(e){Oe(`Identificamos ${e.length} grupo(s) l\xF3gico(s) para seus commits:
|
|
40
41
|
|
|
41
42
|
`+e.map((i,o)=>`${o+1}. **${i.name}**
|
|
42
43
|
\u{1F4C4} ${i.files.join(", ")}
|
|
43
44
|
\u{1F4A1} ${i.description}
|
|
44
45
|
\u{1F3AF} Confian\xE7a: ${Math.round(i.confidence*100)}%`).join(`
|
|
45
46
|
|
|
46
|
-
`),"\u{1F9E0} An\xE1lise de Contexto");let t=await
|
|
47
|
+
`),"\u{1F9E0} An\xE1lise de Contexto");let t=await Ie({message:"O que voc\xEA gostaria de fazer?",options:[{value:"proceed",label:"\u2705 Prosseguir com esta organiza\xE7\xE3o",hint:"Usar os grupos como sugeridos pela IA"},{value:"manual",label:"\u270B Fazer split manual",hint:"Escolher arquivos manualmente"},{value:"cancel",label:"\u274C Cancelar",hint:"Voltar ao modo normal"}]});return ie(t)?{action:"cancel"}:t==="proceed"?{action:"proceed",groups:e}:{action:t}}async function lt(e,t){Oe(`**Grupo:** ${e.name}
|
|
47
48
|
**Arquivos:** ${e.files.join(", ")}
|
|
48
|
-
**Mensagem:** "${t}"`,"\u{1F680} Confirmar Commit do Grupo");let i=await
|
|
49
|
-
... (diff truncado)`:t,n=e.length,s=e.reduce((
|
|
49
|
+
**Mensagem:** "${t}"`,"\u{1F680} Confirmar Commit do Grupo");let i=await ct({message:`Fazer commit para "${e.name}"?`});return ie(i)?!1:i}function ut(e,t,i){let o=Math.round(e/t*100),n="\u2588".repeat(Math.floor(o/10))+"\u2591".repeat(10-Math.floor(o/10));ke.info(`\u{1F504} Progresso: [${n}] ${o}% (${e}/${t})`),ke.info(`\u{1F4CB} Processando: ${i}`)}var ne=S(()=>{"use strict";p()});import ft from"crypto";function Te(e){Le=new se(e)}function De(){return Le}function qe(e,t){let i=De();return i?i.get(e,t):{hit:!1}}function Ne(e,t,i){let o=De();o&&o.set(e,t,i)}var se,Le,re=S(()=>{"use strict";p();se=class{cache=new Map;config;constructor(t){this.config=t}generateHash(t,i){let o={files:t.sort(),diff:i.substring(0,1e3),model:this.config.openai.model,temperature:this.config.openai.temperature};return ft.createHash("md5").update(JSON.stringify(o)).digest("hex")}get(t,i){if(!this.config.cache.enabled)return{hit:!1};let o=this.generateHash(t,i),n=this.cache.get(o);if(!n)return{hit:!1};let s=Date.now(),r=this.config.cache.ttl*60*1e3;return s-n.timestamp>r?(this.cache.delete(o),{hit:!1}):{hit:!0,groups:n.groups}}set(t,i,o){if(!this.config.cache.enabled||(this.cache.size>=this.config.cache.maxSize&&this.cleanup(),this.cache.size>=this.config.cache.maxSize))return;let n=this.generateHash(t,i),s={groups:o,timestamp:Date.now(),hash:n};this.cache.set(n,s)}cleanup(){let t=Date.now(),i=this.config.cache.ttl*60*1e3;for(let[o,n]of this.cache.entries())t-n.timestamp>i&&this.cache.delete(o);if(this.cache.size>=this.config.cache.maxSize){let n=Array.from(this.cache.entries()).sort((s,r)=>s[1].timestamp-r[1].timestamp).slice(0,Math.ceil(this.config.cache.maxSize*.5));for(let[s]of n)this.cache.delete(s)}}clear(){this.cache.clear()}getStats(){return{size:this.cache.size,maxSize:this.config.cache.maxSize,enabled:this.config.cache.enabled}}},Le=null});import{log as g}from"@clack/prompts";function pt(e,t){let o=t.length>8e3?t.substring(0,8e3)+`
|
|
50
|
+
... (diff truncado)`:t,n=e.length,s=e.reduce((l,m)=>{let c=m.split(".").pop()||"sem-extensao";return l[c]=(l[c]||0)+1,l},{}),r=Object.entries(s).map(([l,m])=>`${l}: ${m}`).join(", ");return`Analise os arquivos modificados e agrupe em commits l\xF3gicos.
|
|
50
51
|
|
|
51
52
|
ARQUIVOS (${n}): ${e.join(", ")}
|
|
52
|
-
TIPOS: ${
|
|
53
|
+
TIPOS: ${r}
|
|
53
54
|
|
|
54
55
|
DIFF RESUMIDO:
|
|
55
56
|
\`\`\`
|
|
@@ -67,7 +68,7 @@ Agrupe arquivos relacionados. M\xE1ximo 5 grupos. Responda em JSON:
|
|
|
67
68
|
"confidence": 0.8
|
|
68
69
|
}
|
|
69
70
|
]
|
|
70
|
-
}`}function
|
|
71
|
+
}`}function dt(e){let t=e.reduce((o,n)=>{let s=n.split("/").slice(0,-1).join("/")||"root";return o[s]||(o[s]=[]),o[s].push(n),o},{});return`Agrupe estes arquivos em commits l\xF3gicos baseado nos diret\xF3rios:
|
|
71
72
|
|
|
72
73
|
ARQUIVOS POR DIRET\xD3RIO:
|
|
73
74
|
${Object.entries(t).map(([o,n])=>`${o}: ${n.length} arquivo(s)`).join(`
|
|
@@ -86,38 +87,40 @@ Agrupe por funcionalidade relacionada. M\xE1ximo 5 grupos. JSON:
|
|
|
86
87
|
"confidence": 0.7
|
|
87
88
|
}
|
|
88
89
|
]
|
|
89
|
-
}`}async function
|
|
90
|
-
... (diff truncado)`:
|
|
91
|
-
... (conte\xFAdo truncado)`:
|
|
90
|
+
}`}async function gt(e,t,i){try{if(!i.openai.apiKey)return{success:!1,error:"Chave da OpenAI n\xE3o encontrada"};let o=qe(e,t);if(o.hit&&o.groups)return{success:!0,groups:o.groups};let s=t.length>6e3,r=s?dt(e):pt(e,t);s&&console.warn(`\u26A0\uFE0F Diff muito grande (${t.length} chars), usando an\xE1lise baseada em nomes de arquivos`);let l=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{Authorization:`Bearer ${i.openai.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:i.openai.model,messages:[{role:"user",content:r}],max_tokens:800,temperature:.3})});if(!l.ok){let d=await l.json().catch(()=>({}));return{success:!1,error:`Erro da OpenAI (${l.status}): ${d.error?.message||"Erro desconhecido"}`}}let c=(await l.json()).choices?.[0]?.message?.content?.trim();if(!c)return{success:!1,error:"OpenAI retornou resposta vazia"};let a=c.match(/\{[\s\S]*\}/);if(!a)return{success:!1,error:"Resposta da OpenAI n\xE3o cont\xE9m JSON v\xE1lido"};let u=JSON.parse(a[0]);if(!u.groups||!Array.isArray(u.groups))return{success:!1,error:"Formato de resposta inv\xE1lido da OpenAI"};let h=u.groups.flatMap(d=>d.files||[]),y=e.filter(d=>!h.includes(d));y.length>0&&(u.groups[0].files=[...u.groups[0].files||[],...y]);let R=u.groups.map(d=>({id:d.id||`group-${Math.random().toString(36).substr(2,9)}`,name:d.name||"Grupo sem nome",description:d.description||"Sem descri\xE7\xE3o",files:d.files||[],diff:"",confidence:d.confidence||.5}));return Ne(e,t,R),{success:!0,groups:R}}catch(o){return{success:!1,error:`Erro ao analisar contexto: ${o instanceof Error?o.message:"Erro desconhecido"}`}}}async function ht(e){let{getFileDiff:t}=await Promise.resolve().then(()=>(w(),j)),i=e.files.map(s=>{try{let r=t(s),l=4e3;return r.length>l?r.substring(0,l)+`
|
|
91
|
+
... (diff truncado)`:r}catch{return""}}).filter(s=>s.length>0);if(i.length===0&&e.files.length>0){let{execSync:s}=await import("child_process"),r=e.files.filter(m=>{try{return s(`test -f "${m}"`,{stdio:"ignore"}),s(`git status --porcelain -- "${m}"`,{encoding:"utf-8",stdio:"pipe"}).trim().startsWith("??")}catch{return!1}});if(r.length>0)return r.map(m=>{try{let c=s(`cat "${m}"`,{encoding:"utf-8",stdio:"pipe"}),a=2e3,u=c.length>a?c.substring(0,a)+`
|
|
92
|
+
... (conte\xFAdo truncado)`:c;return`diff --git a/${m} b/${m}
|
|
92
93
|
new file mode 100644
|
|
93
94
|
index 0000000..${Math.random().toString(36).substr(2,7)}
|
|
94
95
|
--- /dev/null
|
|
95
|
-
+++ b/${
|
|
96
|
-
@@ -0,0 +1,${
|
|
96
|
+
+++ b/${m}
|
|
97
|
+
@@ -0,0 +1,${u.split(`
|
|
97
98
|
`).length} @@
|
|
98
|
-
${
|
|
99
|
+
${u.split(`
|
|
99
100
|
`).map(h=>`+${h}`).join(`
|
|
100
|
-
`)}`}catch{return""}}).filter(
|
|
101
|
-
`);let
|
|
102
|
-
`).includes(
|
|
103
|
-
... (conte\xFAdo truncado)`:
|
|
101
|
+
`)}`}catch{return""}}).filter(m=>m.length>0).join(`
|
|
102
|
+
`);let l=e.files.filter(m=>{try{return s(`test -f "${m}"`,{stdio:"ignore"}),s("git diff --cached --name-only",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
|
|
103
|
+
`).includes(m)}catch{return!1}});if(l.length>0)return l.map(m=>{try{let c=s(`cat "${m}"`,{encoding:"utf-8",stdio:"pipe"}),a=2e3,u=c.length>a?c.substring(0,a)+`
|
|
104
|
+
... (conte\xFAdo truncado)`:c;return`diff --git a/${m} b/${m}
|
|
104
105
|
index 0000000..${Math.random().toString(36).substr(2,7)} 100644
|
|
105
|
-
--- a/${
|
|
106
|
-
+++ b/${
|
|
107
|
-
@@ -1 +1,${
|
|
106
|
+
--- a/${m}
|
|
107
|
+
+++ b/${m}
|
|
108
|
+
@@ -1 +1,${u.split(`
|
|
108
109
|
`).length} @@
|
|
109
|
-
${
|
|
110
|
+
${u.split(`
|
|
110
111
|
`).map(h=>`+${h}`).join(`
|
|
111
|
-
`)}`}catch{return""}}).filter(
|
|
112
|
+
`)}`}catch{return""}}).filter(m=>m.length>0).join(`
|
|
112
113
|
`)}let o=i.join(`
|
|
113
114
|
`),n=8e3;return o.length>n?o.substring(0,n)+`
|
|
114
|
-
... (diff total truncado)`:o}async function
|
|
115
|
-
\u{1F504} Processando grupo ${
|
|
116
|
-
`),
|
|
115
|
+
... (diff total truncado)`:o}async function ae(e,t,i,o){i.silent||g.info("\u{1F9E0} Modo Smart Split ativado - Agrupando arquivos por contexto"),i.silent||g.info("\u{1F916} Analisando contexto das mudan\xE7as...");let n=await gt(e.stagedFiles,e.diff,t);if(!n.success){i.silent||g.error(`\u274C Erro na an\xE1lise de contexto: ${n.error}`);return}if(!n.groups||n.groups.length===0){i.silent||g.error("\u274C Nenhum grupo foi criado pela an\xE1lise");return}if(i.silent||(g.success(`\u2705 ${n.groups.length} grupo(s) identificado(s):`),n.groups.forEach((s,r)=>{g.info(` ${r+1}. ${s.name} (${s.files.length} arquivo(s))`),g.info(` \u{1F4C4} ${s.files.join(", ")}`)})),!i.yes&&!i.silent){let{showSmartSplitGroups:s}=await Promise.resolve().then(()=>(ne(),Ge)),r=await s(n.groups);if(r.action==="cancel"){i.silent||g.info("\u274C Opera\xE7\xE3o cancelada pelo usu\xE1rio");return}if(r.action==="manual"){let l={...i,split:!0,smartSplit:!1},{main:m}=await Promise.resolve().then(()=>(ce(),_e));await m(l);return}}for(let s=0;s<n.groups.length;s++){let r=n.groups[s];if(!r){i.silent||g.error(`\u274C Grupo ${s+1} \xE9 undefined`);continue}i.silent||g.info(`
|
|
116
|
+
\u{1F504} Processando grupo ${s+1}/${n.groups.length}: ${r.name}`);let l=await ht(r);if(!l){i.silent||(g.warn(`\u26A0\uFE0F Nenhum diff encontrado para o grupo: ${r.name}`),g.info(` \u{1F4C4} Arquivos: ${r.files.join(", ")}`),g.info(" \u{1F4A1} Poss\xEDvel causa: arquivos novos, deletados/recriados, ou sem mudan\xE7as"));continue}i.silent||g.info(`\u{1F916} Gerando commit para: ${r.name}`);let{generateWithRetry:m}=await Promise.resolve().then(()=>(Z(),ze)),c=await m(l,t,r.files,3,o);if(!c.success){i.silent||g.error(`\u274C Erro ao gerar commit para ${r.name}: ${c.error}`);continue}if(!c.suggestion){i.silent||g.error(`\u274C Nenhuma sugest\xE3o gerada para ${r.name}`);continue}if(t.dryRun){i.silent||(g.info(`\u{1F50D} Dry Run - Grupo: ${r.name}`),g.info(`\u{1F4C4} Arquivos: ${r.files.join(", ")}`),g.info(`\u{1F4AD} Mensagem: "${c.suggestion.message}"`));continue}if(i.yes){let{executeFileCommit:a}=await Promise.resolve().then(()=>(w(),j)),u;if(r.files.length===1&&r.files[0])u=a(r.files[0],c.suggestion.message||"");else{let{execSync:h}=await import("child_process"),{escapeShellArg:y}=await Promise.resolve().then(()=>(w(),j));try{let R=r.files.map(b=>y(b)).join(" "),d=y(c.suggestion.message||"");h(`git commit ${R} -m ${d}`,{stdio:"pipe"}),u={success:!0,hash:h("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:c.suggestion.message||""}}catch(R){u={success:!1,error:R instanceof Error?R.message:"Erro desconhecido ao executar commit"}}}v(u.success,u.hash,u.error)}else{let{showCommitPreview:a,editCommitMessage:u,copyToClipboard:h,showCancellation:y}=await Promise.resolve().then(()=>(L(),te));switch((await a(c.suggestion)).action){case"commit":{let{executeFileCommit:d}=await Promise.resolve().then(()=>(w(),j)),$,b=c.suggestion.message||"Atualiza\xE7\xE3o de arquivos";if(r.files.length===1&&r.files[0])$=d(r.files[0],b);else{let{execSync:k}=await import("child_process"),{escapeShellArg:I}=await Promise.resolve().then(()=>(w(),j));try{let A=r.files.map(W=>I(W)).join(" "),H=I(b);k(`git commit ${A} -m ${H}`,{stdio:"pipe"}),$={success:!0,hash:k("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:b}}catch(A){$={success:!1,error:A instanceof Error?A.message:"Erro desconhecido ao executar commit"}}}v($.success,$.hash,$.error);break}case"edit":{let d=await u(c.suggestion.message);if(d.action==="commit"&&d.message){let{executeFileCommit:$}=await Promise.resolve().then(()=>(w(),j)),b;if(r.files.length===1&&r.files[0])b=$(r.files[0],d.message||"");else{let{execSync:k}=await import("child_process"),{escapeShellArg:I}=await Promise.resolve().then(()=>(w(),j));try{let A=r.files.map(W=>I(W)).join(" "),H=I(d.message||"");k(`git commit ${A} -m ${H}`,{stdio:"pipe"}),b={success:!0,hash:k("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:d.message||""}}catch(A){b={success:!1,error:A instanceof Error?A.message:"Erro desconhecido ao executar commit"}}}v(b.success,b.hash,b.error)}break}case"copy":{await h(c.suggestion.message),i.silent||g.info("\u{1F3AF} Mensagem copiada para clipboard");break}case"cancel":{y();return}}}if(s<n.groups.length-1&&!i.yes){let{askContinueCommits:a}=await Promise.resolve().then(()=>(L(),te)),u=n.groups.slice(s+1).filter(y=>y!==void 0).map(y=>y.name);if(!await a(u))break}}i.silent||g.success("\u2705 Smart Split conclu\xEDdo!")}var Ue=S(()=>{"use strict";p();re();L()});import{existsSync as ue,writeFileSync as yt,readFileSync as bt}from"fs";import{join as J,extname as vt,basename as Ct}from"path";import{spawnSync as le}from"child_process";function me(e){let t={},i=e?.rules??{},o=i["type-enum"];Array.isArray(o)&&Array.isArray(o[2])&&(t.typeEnum=o[2]);let n=i["header-max-length"];Array.isArray(n)&&typeof n[2]=="number"&&(t.headerMaxLength=n[2]);let s=i["subject-case"];if(Array.isArray(s)){let[c,a,u]=s;Array.isArray(u)?t.subjectCase={severity:c,condition:a,cases:u}:typeof u=="string"&&(t.subjectCase={severity:c,condition:a,cases:[u]})}let r=i["subject-full-stop"];if(Array.isArray(r)){let[c,a,u]=r;t.subjectFullStop={severity:c,condition:a,value:u??"."}}let l=i["body-leading-blank"];Array.isArray(l)&&(t.bodyLeadingBlank=l[1]==="always");let m=i["scope-enum"];return Array.isArray(m)&&Array.isArray(m[2])&&(t.scopeEnum=m[2]),t}function Je(e){try{let t=vt(e).toLowerCase(),i=Ct(e);if(t===".json"||t===""&&i===".commitlintrc")try{let o=bt(e,"utf-8"),n=JSON.parse(o);return me(n)}catch{return null}if(t===".js"||t===".mjs"||t===".cjs"){let o=["import { pathToFileURL } from 'url';",`const mod = await import(pathToFileURL(${JSON.stringify(e)}).href);`,"const cfg = mod.default ?? mod;","process.stdout.write(JSON.stringify(cfg));"].join(`
|
|
117
|
+
`),n=le(process.execPath,["--input-type=module"],{input:o,encoding:"utf-8",timeout:5e3});if(n.status===0&&n.stdout)try{let l=JSON.parse(n.stdout);return me(l)}catch{}let s=["try {",` const c = require(${JSON.stringify(e)});`," process.stdout.write(JSON.stringify(c.default ?? c));","} catch (e) {"," process.exit(1);","}"].join(`
|
|
118
|
+
`),r=le(process.execPath,["-e",s],{encoding:"utf-8",timeout:5e3});if(r.status===0&&r.stdout)try{let l=JSON.parse(r.stdout);return me(l)}catch{}}return null}catch{return null}}function fe(e){let t=e||process.cwd();for(let i of xt){let o=J(t,i);if(ue(o))return o}return null}function pe(e){let t=e||process.cwd(),i=J(t,"node_modules",".bin","commitlint");return ue(i)}function Ve(e,t){let i=t||process.cwd(),o=J(i,"node_modules",".bin","commitlint"),n=le(o,[],{input:e,encoding:"utf-8",cwd:i});if(n.status===0)return{valid:!0,errors:[],warnings:[]};let r=((n.stdout||"")+(n.stderr||"")).split(`
|
|
119
|
+
`),l=[],m=[];for(let c of r){let a=c.trim();a&&(a.startsWith("\u2716")||a.includes("[error]")?l.push(a):(a.startsWith("\u26A0")||a.includes("[warning]"))&&m.push(a))}if(l.length===0&&n.status!==0){let c=r.map(a=>a.trim()).filter(a=>a.length>0&&!a.startsWith("\u29D7")&&!a.startsWith("\u2714"));l.push(...c)}return{valid:!1,errors:l,warnings:m}}function He(e){let t=e||process.cwd(),i=J(t,"commitlint.config.js");if(ue(i))throw new Error("commitlint.config.js j\xE1 existe neste diret\xF3rio. Remova-o antes de continuar.");yt(i,`export default {
|
|
117
120
|
extends: ['@commitlint/config-conventional'],
|
|
118
121
|
};
|
|
119
|
-
`,"utf-8")}var
|
|
120
|
-
`);if(!
|
|
122
|
+
`,"utf-8")}var xt,de=S(()=>{"use strict";p();xt=["commitlint.config.js","commitlint.config.ts","commitlint.config.mjs","commitlint.config.cjs",".commitlintrc",".commitlintrc.js",".commitlintrc.json",".commitlintrc.yml",".commitlintrc.yaml"]});var _e={};O(_e,{main:()=>he});import{log as f}from"@clack/prompts";function St(e,t){return!t.commitlint.enabled||!pe()?!1:e.validate?!0:fe()!==null}function ge(e,t){let i=Ve(e);return i.valid?(t||f.success("\u2705 Mensagem validada pelo commitlint!"),!0):(f.error("\u274C A mensagem n\xE3o passou na valida\xE7\xE3o do commitlint:"),i.errors.forEach(o=>f.error(` ${o}`)),i.warnings.length>0&&i.warnings.forEach(o=>f.info(` \u26A0\uFE0F ${o}`)),!1)}async function he(e={silent:!1,yes:!1,auto:!1,split:!1,smartSplit:!1,dryRun:!1,help:!1,version:!1,validate:!1,initCommitlint:!1}){e.silent||f.info("\u{1F680} Commit Wizard iniciado!"),B()||(f.error("\u274C N\xE3o foi encontrado um reposit\xF3rio Git neste diret\xF3rio."),e.silent||f.info("\u{1F4A1} Execute o comando em um diret\xF3rio com reposit\xF3rio Git inicializado."),process.exit(1)),e.silent||f.info("\u2699\uFE0F Carregando configura\xE7\xE3o...");let t=Se();Te(t),e.split&&(t.splitCommits=!0),e.dryRun&&(t.dryRun=!0);let i=we(t);i.length>0&&(f.error("\u274C Erros na configura\xE7\xE3o:"),i.forEach(c=>f.error(` \u2022 ${c}`)),process.exit(1)),e.silent||f.success(`\u2705 Configura\xE7\xE3o carregada (modelo: ${t.openai.model}, idioma: ${t.language})`);let o=St(e,t);e.silent||(e.validate&&!pe()?f.info("\u26A0\uFE0F --validate especificado mas commitlint n\xE3o est\xE1 instalado. Valida\xE7\xE3o ignorada."):o&&f.info("\u{1F50D} Valida\xE7\xE3o commitlint ativa"));let n=fe(),s=n?Je(n):null;e.silent||f.info("\u{1F4CB} Verificando arquivos staged...");let r=K();r.hasStaged||(f.warn("\u26A0\uFE0F Nenhum arquivo foi encontrado no stage."),e.silent||f.info("\u{1F4A1} Use `git add <arquivo>` para adicionar arquivos ao stage antes de gerar o commit."),process.exit(0));let l=Y();if(e.silent||(f.success(`\u2705 Encontrados ${r.stagedFiles.length} arquivo(s) staged:`),r.stagedFiles.forEach(c=>f.info(` \u{1F4C4} ${c}`)),f.info(`\u{1F4CA} Estat\xEDsticas: +${l.added} -${l.removed} linhas`)),t.splitCommits||e.smartSplit){if(e.yes)return await ae(r,t,e,s);switch((await oe()).action){case"proceed":return await ae(r,t,e,s);case"manual":return await wt(r,t,e,s);case"cancel":P();return}}e.silent||f.info("\u{1F916} Gerando mensagem de commit com IA...");let m=await D(r.diff,t,r.stagedFiles,3,s);if(m.success||(f.error(`\u274C Erro ao gerar commit: ${m.error}`),process.exit(1)),m.suggestion||(f.error("\u274C Nenhuma sugest\xE3o foi gerada"),process.exit(1)),e.silent||f.success("\u2728 Mensagem de commit gerada!"),t.dryRun){f.info("\u{1F50D} Modo Dry Run - Mensagem gerada:"),f.info(`"${m.suggestion.message}"`),f.info("\u{1F4A1} Execute sem --dry-run para fazer o commit");return}if(e.yes){o&&(ge(m.suggestion.message,e.silent)||process.exit(1));let c=M(m.suggestion.message);v(c.success,c.hash,c.error);return}for(;;)switch((await N(m.suggestion)).action){case"commit":{if(o&&!ge(m.suggestion.message,e.silent))break;let a=M(m.suggestion.message);v(a.success,a.hash,a.error);return}case"edit":{let a=await U(m.suggestion.message);if(a.action==="cancel"){P();return}if(a.action==="commit"&&a.message){if(o&&!ge(a.message,e.silent))break;let u=M(a.message);v(u.success,u.hash,u.error);return}break}case"copy":{await _(m.suggestion.message),e.silent||f.info('\u{1F3AF} Voc\xEA pode usar a mensagem copiada com: git commit -m "mensagem"');return}case"cancel":{P();return}}}async function wt(e,t,i,o){i.silent||f.info("\u{1F504} Modo Split ativado - Commits separados por arquivo");let n=[...e.stagedFiles];for(;n.length>0;){let s=i.yes?[n[0]]:await X(n);if(s.length===0){i.silent||f.info("\u274C Nenhum arquivo selecionado");break}let{getFileDiff:r}=await Promise.resolve().then(()=>(w(),j)),l=s.filter(c=>c!==void 0).map(c=>{try{return r(c)}catch(a){return f.error(`\u274C Erro ao obter diff do arquivo ${c}: ${a instanceof Error?a.message:"Erro desconhecido"}`),""}}).filter(c=>c.length>0).join(`
|
|
123
|
+
`);if(!l){i.silent||f.warn("\u26A0\uFE0F Nenhum diff encontrado para os arquivos selecionados"),n=n.filter(c=>!s.includes(c));continue}i.silent||f.info(`\u{1F916} Gerando commit para: ${s.join(", ")}`);let m=await D(l,t,s.filter(c=>c!==void 0),3,o);if(!m.success){f.error(`\u274C Erro ao gerar commit: ${m.error}`),n=n.filter(c=>!s.includes(c));continue}if(!m.suggestion){f.error("\u274C Nenhuma sugest\xE3o foi gerada"),n=n.filter(c=>!s.includes(c));continue}if(t.dryRun){f.info(`\u{1F50D} Dry Run - Mensagem para ${s.join(", ")}:`),f.info(`"${m.suggestion.message}"`),n=n.filter(c=>!s.includes(c));continue}if(i.yes){let c=s.length===1&&s[0]?await G(s[0],m.suggestion.message):await M(m.suggestion.message);v(c.success,c.hash,c.error)}else{let c=await N(m.suggestion);if(c.action==="commit"){let a=s.length===1&&s[0]?await G(s[0],m.suggestion.message):await M(m.suggestion.message);v(a.success,a.hash,a.error)}else if(c.action==="edit"){let a=await U(m.suggestion.message);if(a.action==="commit"&&a.message){let u=s.length===1&&s[0]?await G(s[0],a.message):await M(a.message);v(u.success,u.hash,u.error)}}else if(c.action==="copy")await _(m.suggestion.message),i.silent||f.info("\u{1F3AF} Mensagem copiada para clipboard");else if(c.action==="cancel"){P();return}}if(n=n.filter(c=>!s.includes(c)),n.length>0&&!i.yes&&!await ee(n))break}i.silent||f.success("\u2705 Modo Split conclu\xEDdo!")}var ce=S(()=>{"use strict";p();$e();w();Z();L();ne();Ue();re();de()});p();ce();import{intro as jt,outro as Rt,log as V}from"@clack/prompts";p();p();import{readFileSync as $t}from"fs";import{join as ye,dirname as At}from"path";import{fileURLToPath as Et}from"url";function We(){try{let e=Et(import.meta.url),t=At(e),i=[ye(t,"..","package.json"),ye(t,"..","..","package.json"),ye(process.cwd(),"package.json")];for(let o of i)try{let n=$t(o,"utf-8"),s=JSON.parse(n);if(s.name==="@gilbert_oliveira/commit-wizard")return s.version}catch{continue}throw new Error("Package.json n\xE3o encontrado")}catch{return console.warn("\u26A0\uFE0F N\xE3o foi poss\xEDvel ler a vers\xE3o do package.json, usando vers\xE3o padr\xE3o"),"0.0.0"}}function Be(e){return{silent:e.includes("--silent")||e.includes("-s"),yes:e.includes("--yes")||e.includes("-y"),auto:e.includes("--auto")||e.includes("-a"),split:e.includes("--split"),smartSplit:e.includes("--smart-split"),dryRun:e.includes("--dry-run")||e.includes("-n"),help:e.includes("--help")||e.includes("-h"),version:e.includes("--version")||e.includes("-v"),validate:e.includes("--validate"),initCommitlint:e.includes("--init-commitlint")}}function Ke(){console.log(`
|
|
121
124
|
\u{1F9D9}\u200D\u2642\uFE0F Commit Wizard - Gerador inteligente de mensagens de commit
|
|
122
125
|
|
|
123
126
|
USAGE:
|
|
@@ -146,4 +149,4 @@ EXAMPLES:
|
|
|
146
149
|
commit-wizard --init-commitlint # Criar configura\xE7\xE3o padr\xE3o do commitlint
|
|
147
150
|
|
|
148
151
|
Para mais informa\xE7\xF5es, visite: https://github.com/user/commit-wizard
|
|
149
|
-
`)}function
|
|
152
|
+
`)}function Ye(){let e=We();console.log(`commit-wizard v${e}`)}de();async function Mt(){try{let e=Be(process.argv.slice(2));if(e.help&&(Ke(),process.exit(0)),e.version&&(Ye(),process.exit(0)),e.initCommitlint){try{He(),V.success("\u2705 commitlint.config.js criado com sucesso!"),V.info("\u{1F4A1} Instale as depend\xEAncias necess\xE1rias: npm install --save-dev @commitlint/cli @commitlint/config-conventional")}catch(t){V.error(`\u274C ${t instanceof Error?t.message:"Erro ao criar configura\xE7\xE3o do commitlint"}`),process.exit(1)}process.exit(0)}e.auto&&(e.silent=!0,e.yes=!0),e.silent||jt("\u{1F9D9}\u200D\u2642\uFE0F Commit Wizard"),await he(e),e.silent||Rt("At\xE9 logo! \u2728")}catch(e){V.error(`Erro: ${e instanceof Error?e.message:"Erro desconhecido"}`),process.exit(1)}}Mt();
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "Commit Wizard",
|
|
4
4
|
"publisher": "gilbert-oliveira",
|
|
5
5
|
"description": "CLI inteligente para gerar mensagens de commit usando OpenAI",
|
|
6
|
-
"version": "2.13.0-canary.
|
|
6
|
+
"version": "2.13.0-canary.4",
|
|
7
7
|
"categories": [
|
|
8
8
|
"Other",
|
|
9
9
|
"SCM Providers"
|
|
@@ -71,6 +71,8 @@
|
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@changesets/cli": "^2.29.5",
|
|
74
|
+
"@commitlint/cli": "^20.4.2",
|
|
75
|
+
"@commitlint/config-conventional": "^20.4.2",
|
|
74
76
|
"@types/jest": "^30.0.0",
|
|
75
77
|
"@types/node": "^24.0.13",
|
|
76
78
|
"@typescript-eslint/eslint-plugin": "^8.36.0",
|
package/src/commitlint/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, writeFileSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
1
|
+
import { existsSync, writeFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, extname, basename } from 'path';
|
|
3
3
|
import { spawnSync } from 'child_process';
|
|
4
4
|
|
|
5
5
|
export interface CommitlintResult {
|
|
@@ -20,6 +20,150 @@ const COMMITLINT_CONFIG_FILES = [
|
|
|
20
20
|
'.commitlintrc.yaml',
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
+
export interface CommitlintRules {
|
|
24
|
+
typeEnum?: string[];
|
|
25
|
+
headerMaxLength?: number;
|
|
26
|
+
subjectCase?: { severity: number; condition: string; cases: string[] };
|
|
27
|
+
subjectFullStop?: { severity: number; condition: string; value: string };
|
|
28
|
+
bodyLeadingBlank?: boolean;
|
|
29
|
+
scopeEnum?: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function extractRulesFromConfig(config: unknown): CommitlintRules {
|
|
33
|
+
const rules: CommitlintRules = {};
|
|
34
|
+
const rawRules =
|
|
35
|
+
(config as { rules?: Record<string, unknown[]> })?.rules ?? {};
|
|
36
|
+
|
|
37
|
+
const typeEnumRule = rawRules['type-enum'];
|
|
38
|
+
if (Array.isArray(typeEnumRule) && Array.isArray(typeEnumRule[2])) {
|
|
39
|
+
rules.typeEnum = typeEnumRule[2] as string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const headerMaxLengthRule = rawRules['header-max-length'];
|
|
43
|
+
if (
|
|
44
|
+
Array.isArray(headerMaxLengthRule) &&
|
|
45
|
+
typeof headerMaxLengthRule[2] === 'number'
|
|
46
|
+
) {
|
|
47
|
+
rules.headerMaxLength = headerMaxLengthRule[2];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const subjectCaseRule = rawRules['subject-case'];
|
|
51
|
+
if (Array.isArray(subjectCaseRule)) {
|
|
52
|
+
const [severity, condition, cases] = subjectCaseRule;
|
|
53
|
+
if (Array.isArray(cases)) {
|
|
54
|
+
rules.subjectCase = {
|
|
55
|
+
severity: severity as number,
|
|
56
|
+
condition: condition as string,
|
|
57
|
+
cases: cases as string[],
|
|
58
|
+
};
|
|
59
|
+
} else if (typeof cases === 'string') {
|
|
60
|
+
rules.subjectCase = {
|
|
61
|
+
severity: severity as number,
|
|
62
|
+
condition: condition as string,
|
|
63
|
+
cases: [cases],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const subjectFullStopRule = rawRules['subject-full-stop'];
|
|
69
|
+
if (Array.isArray(subjectFullStopRule)) {
|
|
70
|
+
const [severity, condition, value] = subjectFullStopRule;
|
|
71
|
+
rules.subjectFullStop = {
|
|
72
|
+
severity: severity as number,
|
|
73
|
+
condition: condition as string,
|
|
74
|
+
value: (value as string) ?? '.',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const bodyLeadingBlankRule = rawRules['body-leading-blank'];
|
|
79
|
+
if (Array.isArray(bodyLeadingBlankRule)) {
|
|
80
|
+
rules.bodyLeadingBlank = bodyLeadingBlankRule[1] === 'always';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const scopeEnumRule = rawRules['scope-enum'];
|
|
84
|
+
if (Array.isArray(scopeEnumRule) && Array.isArray(scopeEnumRule[2])) {
|
|
85
|
+
rules.scopeEnum = scopeEnumRule[2] as string[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return rules;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Reads and parses a commitlint config file, returning the extracted rules.
|
|
93
|
+
* Returns null if the file cannot be parsed or the format is unsupported.
|
|
94
|
+
*/
|
|
95
|
+
export function parseCommitlintRules(configPath: string): CommitlintRules | null {
|
|
96
|
+
try {
|
|
97
|
+
const ext = extname(configPath).toLowerCase();
|
|
98
|
+
const base = basename(configPath);
|
|
99
|
+
|
|
100
|
+
// JSON format: .commitlintrc.json or .commitlintrc (extensionless, try JSON)
|
|
101
|
+
if (ext === '.json' || (ext === '' && base === '.commitlintrc')) {
|
|
102
|
+
try {
|
|
103
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
104
|
+
const config = JSON.parse(content) as unknown;
|
|
105
|
+
return extractRulesFromConfig(config);
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// JS / MJS / CJS: use a Node.js subprocess to evaluate the config
|
|
112
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
113
|
+
// Try ESM dynamic import first (handles export default)
|
|
114
|
+
const esmScript = [
|
|
115
|
+
"import { pathToFileURL } from 'url';",
|
|
116
|
+
`const mod = await import(pathToFileURL(${JSON.stringify(configPath)}).href);`,
|
|
117
|
+
'const cfg = mod.default ?? mod;',
|
|
118
|
+
'process.stdout.write(JSON.stringify(cfg));',
|
|
119
|
+
].join('\n');
|
|
120
|
+
|
|
121
|
+
const esmResult = spawnSync(process.execPath, ['--input-type=module'], {
|
|
122
|
+
input: esmScript,
|
|
123
|
+
encoding: 'utf-8',
|
|
124
|
+
timeout: 5000,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (esmResult.status === 0 && esmResult.stdout) {
|
|
128
|
+
try {
|
|
129
|
+
const config = JSON.parse(esmResult.stdout) as unknown;
|
|
130
|
+
return extractRulesFromConfig(config);
|
|
131
|
+
} catch {
|
|
132
|
+
// JSON parse error – fall through to CJS
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fallback: try CommonJS require
|
|
137
|
+
const cjsScript = [
|
|
138
|
+
'try {',
|
|
139
|
+
` const c = require(${JSON.stringify(configPath)});`,
|
|
140
|
+
' process.stdout.write(JSON.stringify(c.default ?? c));',
|
|
141
|
+
'} catch (e) {',
|
|
142
|
+
' process.exit(1);',
|
|
143
|
+
'}',
|
|
144
|
+
].join('\n');
|
|
145
|
+
const cjsResult = spawnSync(process.execPath, ['-e', cjsScript], {
|
|
146
|
+
encoding: 'utf-8',
|
|
147
|
+
timeout: 5000,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (cjsResult.status === 0 && cjsResult.stdout) {
|
|
151
|
+
try {
|
|
152
|
+
const config = JSON.parse(cjsResult.stdout) as unknown;
|
|
153
|
+
return extractRulesFromConfig(config);
|
|
154
|
+
} catch {
|
|
155
|
+
// JSON parse error
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Unsupported format (e.g. YAML, TypeScript): return null gracefully
|
|
161
|
+
return null;
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
23
167
|
export function findCommitlintConfig(cwd?: string): string | null {
|
|
24
168
|
const dir = cwd || process.cwd();
|
|
25
169
|
for (const file of COMMITLINT_CONFIG_FILES) {
|
package/src/core/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
isCommitlintInstalled,
|
|
27
27
|
findCommitlintConfig,
|
|
28
28
|
validateCommitMessage,
|
|
29
|
+
parseCommitlintRules,
|
|
29
30
|
} from '../commitlint/index';
|
|
30
31
|
|
|
31
32
|
function shouldRunCommitlint(args: CLIArgs, config: Config): boolean {
|
|
@@ -134,6 +135,12 @@ export async function main(
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
// Parse commitlint rules for AI prompt (auto-detected, no validation required)
|
|
139
|
+
const commitlintConfigPath = findCommitlintConfig();
|
|
140
|
+
const commitlintRules = commitlintConfigPath
|
|
141
|
+
? parseCommitlintRules(commitlintConfigPath)
|
|
142
|
+
: null;
|
|
143
|
+
|
|
137
144
|
// Verificar arquivos staged
|
|
138
145
|
if (!args.silent) {
|
|
139
146
|
log.info('📋 Verificando arquivos staged...');
|
|
@@ -165,16 +172,16 @@ export async function main(
|
|
|
165
172
|
if (config.splitCommits || args.smartSplit) {
|
|
166
173
|
if (args.yes) {
|
|
167
174
|
// Modo automático: usar smart split
|
|
168
|
-
return await handleSmartSplitMode(gitStatus, config, args);
|
|
175
|
+
return await handleSmartSplitMode(gitStatus, config, args, commitlintRules);
|
|
169
176
|
} else {
|
|
170
177
|
// Modo interativo: perguntar qual tipo de split
|
|
171
178
|
const splitAction = await chooseSplitMode();
|
|
172
179
|
|
|
173
180
|
switch (splitAction.action) {
|
|
174
181
|
case 'proceed':
|
|
175
|
-
return await handleSmartSplitMode(gitStatus, config, args);
|
|
182
|
+
return await handleSmartSplitMode(gitStatus, config, args, commitlintRules);
|
|
176
183
|
case 'manual':
|
|
177
|
-
return await handleSplitMode(gitStatus, config, args);
|
|
184
|
+
return await handleSplitMode(gitStatus, config, args, commitlintRules);
|
|
178
185
|
case 'cancel':
|
|
179
186
|
showCancellation();
|
|
180
187
|
return;
|
|
@@ -190,7 +197,9 @@ export async function main(
|
|
|
190
197
|
const result = await generateWithRetry(
|
|
191
198
|
gitStatus.diff,
|
|
192
199
|
config,
|
|
193
|
-
gitStatus.stagedFiles
|
|
200
|
+
gitStatus.stagedFiles,
|
|
201
|
+
3,
|
|
202
|
+
commitlintRules
|
|
194
203
|
);
|
|
195
204
|
|
|
196
205
|
if (!result.success) {
|
|
@@ -302,7 +311,7 @@ export async function main(
|
|
|
302
311
|
}
|
|
303
312
|
}
|
|
304
313
|
|
|
305
|
-
async function handleSplitMode(gitStatus: any, config: any, args: CLIArgs) {
|
|
314
|
+
async function handleSplitMode(gitStatus: any, config: any, args: CLIArgs, commitlintRules?: import('../commitlint/index').CommitlintRules | null) {
|
|
306
315
|
if (!args.silent) {
|
|
307
316
|
log.info('🔄 Modo Split ativado - Commits separados por arquivo');
|
|
308
317
|
}
|
|
@@ -358,7 +367,9 @@ async function handleSplitMode(gitStatus: any, config: any, args: CLIArgs) {
|
|
|
358
367
|
const result = await generateWithRetry(
|
|
359
368
|
fileDiffs,
|
|
360
369
|
config,
|
|
361
|
-
selectedFiles.filter((file): file is string => file !== undefined)
|
|
370
|
+
selectedFiles.filter((file): file is string => file !== undefined),
|
|
371
|
+
3,
|
|
372
|
+
commitlintRules
|
|
362
373
|
);
|
|
363
374
|
|
|
364
375
|
if (!result.success) {
|
package/src/core/openai.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Config } from '../config/index';
|
|
2
|
+
import type { CommitlintRules } from '../commitlint/index';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_COMMIT_TYPES = 'feat, fix, docs, style, refactor, test, chore, build, ci';
|
|
2
5
|
|
|
3
6
|
export interface CommitSuggestion {
|
|
4
7
|
message: string;
|
|
@@ -133,13 +136,18 @@ export function smartFilterDiff(diff: string, maxLength: number): string {
|
|
|
133
136
|
export function buildPrompt(
|
|
134
137
|
diff: string,
|
|
135
138
|
config: Config,
|
|
136
|
-
filenames: string[]
|
|
139
|
+
filenames: string[],
|
|
140
|
+
commitlintRules?: CommitlintRules | null
|
|
137
141
|
): string {
|
|
138
142
|
const language = config.language === 'pt' ? 'português' : 'english';
|
|
139
|
-
const styleInstructions =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
const styleInstructions =
|
|
144
|
+
commitlintRules != null
|
|
145
|
+
? getStyleInstructionsFromRules(
|
|
146
|
+
config.commitStyle,
|
|
147
|
+
config.language,
|
|
148
|
+
commitlintRules
|
|
149
|
+
)
|
|
150
|
+
: getStyleInstructions(config.commitStyle, config.language);
|
|
143
151
|
|
|
144
152
|
// Limitar tamanho do diff para economizar tokens usando filtro inteligente
|
|
145
153
|
const maxDiffLength = 6000;
|
|
@@ -167,6 +175,127 @@ Mensagem:`;
|
|
|
167
175
|
return prompt;
|
|
168
176
|
}
|
|
169
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Builds style instructions dynamically from parsed commitlint rules.
|
|
180
|
+
* Falls back to sensible defaults for any rules not explicitly configured.
|
|
181
|
+
*/
|
|
182
|
+
function getStyleInstructionsFromRules(
|
|
183
|
+
style: string,
|
|
184
|
+
language: string,
|
|
185
|
+
rules: CommitlintRules
|
|
186
|
+
): string {
|
|
187
|
+
const isPt = language === 'pt';
|
|
188
|
+
const lines: string[] = [];
|
|
189
|
+
|
|
190
|
+
if (style === 'conventional') {
|
|
191
|
+
lines.push(
|
|
192
|
+
isPt
|
|
193
|
+
? '- Use formato: tipo(escopo): descrição'
|
|
194
|
+
: '- Use format: type(scope): description'
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const types = rules.typeEnum?.join(', ') ?? DEFAULT_COMMIT_TYPES;
|
|
198
|
+
lines.push(
|
|
199
|
+
isPt ? `- Tipos válidos: ${types}` : `- Valid types: ${types}`
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const maxLen = rules.headerMaxLength ?? 72;
|
|
203
|
+
lines.push(
|
|
204
|
+
isPt
|
|
205
|
+
? `- Mantenha a primeira linha com até ${maxLen} caracteres`
|
|
206
|
+
: `- Keep first line under ${maxLen} characters`
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const exampleType = rules.typeEnum?.[0] ?? 'feat';
|
|
210
|
+
lines.push(
|
|
211
|
+
isPt
|
|
212
|
+
? `- Exemplo: "${exampleType}(auth): adicionar validação de email"`
|
|
213
|
+
: `- Example: "${exampleType}(auth): add email validation"`
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (rules.subjectCase) {
|
|
217
|
+
const { condition, cases } = rules.subjectCase;
|
|
218
|
+
const caseList = cases.join(', ');
|
|
219
|
+
if (condition === 'never') {
|
|
220
|
+
lines.push(
|
|
221
|
+
isPt
|
|
222
|
+
? `- Subject nunca em: ${caseList}`
|
|
223
|
+
: `- Subject must never be: ${caseList}`
|
|
224
|
+
);
|
|
225
|
+
} else if (condition === 'always') {
|
|
226
|
+
lines.push(
|
|
227
|
+
isPt
|
|
228
|
+
? `- Subject deve estar em: ${caseList}`
|
|
229
|
+
: `- Subject must be in: ${caseList}`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (rules.subjectFullStop) {
|
|
235
|
+
const { condition, value } = rules.subjectFullStop;
|
|
236
|
+
const char = value ?? '.';
|
|
237
|
+
if (condition === 'never') {
|
|
238
|
+
lines.push(
|
|
239
|
+
isPt
|
|
240
|
+
? `- Não termine o subject com "${char}"`
|
|
241
|
+
: `- Do not end subject with "${char}"`
|
|
242
|
+
);
|
|
243
|
+
} else if (condition === 'always') {
|
|
244
|
+
lines.push(
|
|
245
|
+
isPt
|
|
246
|
+
? `- Termine o subject com "${char}"`
|
|
247
|
+
: `- End subject with "${char}"`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (rules.bodyLeadingBlank === true) {
|
|
253
|
+
lines.push(
|
|
254
|
+
isPt
|
|
255
|
+
? '- Adicione uma linha em branco entre o header e o body'
|
|
256
|
+
: '- Add a blank line between header and body'
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (rules.scopeEnum && rules.scopeEnum.length > 0) {
|
|
261
|
+
lines.push(
|
|
262
|
+
isPt
|
|
263
|
+
? `- Escopos permitidos: ${rules.scopeEnum.join(', ')}`
|
|
264
|
+
: `- Allowed scopes: ${rules.scopeEnum.join(', ')}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
} else if (style === 'simple') {
|
|
268
|
+
const maxLen = rules.headerMaxLength ?? 50;
|
|
269
|
+
if (isPt) {
|
|
270
|
+
lines.push('- Use formato simples e direto');
|
|
271
|
+
lines.push('- Comece com verbo no infinitivo');
|
|
272
|
+
lines.push('- Exemplo: "corrigir validação de formulário"');
|
|
273
|
+
lines.push(`- Máximo ${maxLen} caracteres`);
|
|
274
|
+
} else {
|
|
275
|
+
lines.push('- Use simple and direct format');
|
|
276
|
+
lines.push('- Start with imperative verb');
|
|
277
|
+
lines.push('- Example: "fix form validation"');
|
|
278
|
+
lines.push(`- Maximum ${maxLen} characters`);
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
// detailed
|
|
282
|
+
const maxLen = rules.headerMaxLength ?? 72;
|
|
283
|
+
if (isPt) {
|
|
284
|
+
lines.push(`- Primeira linha: resumo em até ${maxLen} caracteres`);
|
|
285
|
+
lines.push('- Se necessário, adicione corpo explicativo');
|
|
286
|
+
lines.push('- Use presente do indicativo');
|
|
287
|
+
lines.push('- Seja descritivo mas conciso');
|
|
288
|
+
} else {
|
|
289
|
+
lines.push(`- First line: summary under ${maxLen} characters`);
|
|
290
|
+
lines.push('- Add explanatory body if needed');
|
|
291
|
+
lines.push('- Use imperative mood');
|
|
292
|
+
lines.push('- Be descriptive but concise');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return lines.join('\n');
|
|
297
|
+
}
|
|
298
|
+
|
|
170
299
|
/**
|
|
171
300
|
* Obtém instruções específicas baseadas no estilo de commit
|
|
172
301
|
*/
|
|
@@ -174,7 +303,7 @@ function getStyleInstructions(style: string, language: string): string {
|
|
|
174
303
|
const instructions = {
|
|
175
304
|
pt: {
|
|
176
305
|
conventional: `- Use formato: tipo(escopo): descrição
|
|
177
|
-
- Tipos válidos:
|
|
306
|
+
- Tipos válidos: ${DEFAULT_COMMIT_TYPES}
|
|
178
307
|
- Exemplo: "feat(auth): adicionar validação de email"
|
|
179
308
|
- Mantenha a primeira linha com até 50 caracteres`,
|
|
180
309
|
|
|
@@ -190,7 +319,7 @@ function getStyleInstructions(style: string, language: string): string {
|
|
|
190
319
|
},
|
|
191
320
|
en: {
|
|
192
321
|
conventional: `- Use format: type(scope): description
|
|
193
|
-
- Valid types:
|
|
322
|
+
- Valid types: ${DEFAULT_COMMIT_TYPES}
|
|
194
323
|
- Example: "feat(auth): add email validation"
|
|
195
324
|
- Keep first line under 50 characters`,
|
|
196
325
|
|
|
@@ -358,7 +487,8 @@ export function processOpenAIMessage(message: string): string {
|
|
|
358
487
|
export async function generateCommitMessage(
|
|
359
488
|
diff: string,
|
|
360
489
|
config: Config,
|
|
361
|
-
filenames: string[]
|
|
490
|
+
filenames: string[],
|
|
491
|
+
commitlintRules?: CommitlintRules | null
|
|
362
492
|
): Promise<OpenAIResponse> {
|
|
363
493
|
try {
|
|
364
494
|
if (!config.openai.apiKey) {
|
|
@@ -369,7 +499,7 @@ export async function generateCommitMessage(
|
|
|
369
499
|
};
|
|
370
500
|
}
|
|
371
501
|
|
|
372
|
-
const prompt = buildPrompt(diff, config, filenames);
|
|
502
|
+
const prompt = buildPrompt(diff, config, filenames, commitlintRules);
|
|
373
503
|
|
|
374
504
|
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
375
505
|
method: 'POST',
|
|
@@ -438,12 +568,13 @@ export async function generateWithRetry(
|
|
|
438
568
|
diff: string,
|
|
439
569
|
config: Config,
|
|
440
570
|
filenames: string[],
|
|
441
|
-
maxRetries: number = 3
|
|
571
|
+
maxRetries: number = 3,
|
|
572
|
+
commitlintRules?: CommitlintRules | null
|
|
442
573
|
): Promise<OpenAIResponse> {
|
|
443
574
|
let lastError = '';
|
|
444
575
|
|
|
445
576
|
for (let i = 0; i < maxRetries; i++) {
|
|
446
|
-
const result = await generateCommitMessage(diff, config, filenames);
|
|
577
|
+
const result = await generateCommitMessage(diff, config, filenames, commitlintRules);
|
|
447
578
|
|
|
448
579
|
if (result.success) {
|
|
449
580
|
return result;
|
package/src/core/smart-split.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Config } from '../config/index';
|
|
2
2
|
import type { CLIArgs } from '../utils/args';
|
|
3
|
+
import type { CommitlintRules } from '../commitlint/index';
|
|
3
4
|
import { getCachedAnalysis, setCachedAnalysis } from './cache';
|
|
4
5
|
import { showCommitResult } from '../ui/index';
|
|
5
6
|
import { log } from '@clack/prompts';
|
|
@@ -386,7 +387,8 @@ export async function generateGroupDiff(group: FileGroup): Promise<string> {
|
|
|
386
387
|
export async function handleSmartSplitMode(
|
|
387
388
|
gitStatus: any,
|
|
388
389
|
config: Config,
|
|
389
|
-
args: CLIArgs
|
|
390
|
+
args: CLIArgs,
|
|
391
|
+
commitlintRules?: CommitlintRules | null
|
|
390
392
|
): Promise<void> {
|
|
391
393
|
if (!args.silent) {
|
|
392
394
|
log.info('🧠 Modo Smart Split ativado - Agrupando arquivos por contexto');
|
|
@@ -487,7 +489,7 @@ export async function handleSmartSplitMode(
|
|
|
487
489
|
}
|
|
488
490
|
|
|
489
491
|
const { generateWithRetry } = await import('./openai');
|
|
490
|
-
const result = await generateWithRetry(groupDiff, config, group.files);
|
|
492
|
+
const result = await generateWithRetry(groupDiff, config, group.files, 3, commitlintRules);
|
|
491
493
|
|
|
492
494
|
if (!result.success) {
|
|
493
495
|
if (!args.silent) {
|