@ateriss_/aiv-cli 0.1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +329 -324
- package/dist/index.js +50 -48
- package/package.json +1 -1
- package/src/agents/base.ts +6 -1
- package/src/cli/commands/review.ts +13 -1
- package/src/cli/selector.ts +29 -0
- package/src/git/github.ts +16 -0
- package/src/i18n/en.ts +8 -0
- package/src/i18n/es.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var
|
|
2
|
+
"use strict";var Kt=Object.create;var Ee=Object.defineProperty;var Wt=Object.getOwnPropertyDescriptor;var Yt=Object.getOwnPropertyNames;var Qt=Object.getPrototypeOf,Zt=Object.prototype.hasOwnProperty;var Xt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var en=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Yt(t))!Zt.call(e,i)&&i!==n&&Ee(e,i,{get:()=>t[i],enumerable:!(o=Wt(t,i))||o.enumerable});return e};var u=(e,t,n)=>(n=e!=null?Kt(Qt(e)):{},en(t||!e||!e.__esModule?Ee(n,"default",{value:e,enumerable:!0}):n,e));var He=Xt((Wn,nn)=>{nn.exports={name:"@ateriss_/aiv-cli",version:"1.0.1",description:"AI-powered PR reviewer CLI \u2014 local-first, multi-agent semantic analysis",main:"dist/index.js",bin:{aiv:"dist/index.js"},scripts:{build:"tsup",typecheck:"tsc",dev:"ts-node src/index.ts",start:"node dist/index.js",prepublishOnly:"npm run build"},keywords:["ai","pr-review","cli","github","claude","openai"],author:"",license:"MIT",dependencies:{"@anthropic-ai/sdk":"^0.37.0",chalk:"^5.3.0",commander:"^12.1.0",inquirer:"^9.3.7","js-yaml":"^4.1.0","node-fetch":"^3.3.2",ora:"^8.1.1",openai:"^4.77.0",table:"^6.8.2"},devDependencies:{"@types/inquirer":"^9.0.7","@types/js-yaml":"^4.0.9","@types/node":"^22.10.0","ts-node":"^10.9.2",tsup:"^8.3.5",typescript:"^5.7.2"},engines:{node:">=18.0.0"}}});var Ht=require("commander");var ce=u(require("fs")),Ge=u(require("path")),De=u(require("os"));var $e={notInitialized:"Not initialized. Run `aiv init` first.",invalidProvider:"Invalid provider. Choose: claude, openai, mock",invalidPrNumber:"Invalid PR number.",repoNotDetected:"Could not detect GitHub owner/repo. Use --owner and --repo flags or configure in .aiv/config.yml",initAlreadyDone:"aiv is already initialized. Use --force to reinitialize.",initTitle:` aiv \u2014 AI PR Reviewer
|
|
3
3
|
`,initWritingGlobalConfig:"Writing global config (~/.aiv/config.yml)...",initGlobalConfigCreated:"Global config created (~/.aiv/config.yml)",initGlobalConfigExists:"Global config already exists (~/.aiv/config.yml)",initWritingConfig:"Writing repo config (.aiv/config.yml)...",initConfigCreated:"Repo config created (.aiv/config.yml)",initRulesCreated:"rules.yml created",initScanningTree:"Scanning project structure...",initTreeCreated:"tree.json created",initTreeSkipped:"tree.json skipped (could not scan)",initBuildingContext:"Building project context...",initContextCreated:"context.md created",initContextSkipped:"context.md skipped (could not analyze)",initGitignoreUpdated:".gitignore updated",initSuccessTitle:`
|
|
4
4
|
Initialized! Next steps:
|
|
5
5
|
`,initStep1:e=>` Set your API key: export ${e}=your-key`,initStep2:e=>` Set GitHub token: export ${e}=your-token`,initStep3:" List PRs: aiv prs",initStep4:" Review a PR: aiv review <pr-number>",initEditContext:e=>` Edit ${e} to add business context.`,initEditRules:e=>` Edit ${e} to define your rules.`,initGlobalHint:" Global config: ~/.aiv/config.yml",initAddAccountHint:" Add accounts: aiv config add-account <name> --token-env <VAR>",selectorSelectPR:"Select a PR to review:",selectorConfirmReview:"Review",selectorCancelled:` Cancelled.
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
Custom Providers (OpenAI-compatible)
|
|
25
25
|
`,customProviderNone:" No custom providers configured. Add one with: aiv config add-provider <name> --base-url <url> --api-key-env <VAR> --model <model>",customProviderColName:"Name",customProviderColUrl:"Base URL",customProviderColModel:"Default Model",customProviderColEnvVar:"Token Env Var",customProviderColToken:"Token",builtinProviderSet:(e,t,n)=>` ${e}: model=${t}, key_env=${n}`,providerFallback:(e,t)=>`Provider "${e}" quota/rate limit \u2014 switching to "${t}"`,providerAllFailed:"All providers in the fallback chain failed. Check your API keys and quotas.",configAgentProviderSet:(e,t)=>` Agent "${e}" will use: ${t}`,configFallbackSet:e=>` Fallback chain set: ${e}`,configFallbackCleared:" Fallback chain cleared.",configAgentProviderShow:`
|
|
26
26
|
Agent provider assignments
|
|
27
|
-
`,configAgentProviderNone:" No per-agent providers configured. All agents use the default provider.",configInvalidAgentName:e=>` Unknown agent: "${e}". Valid: business, architecture, security, context`,configInvalidProviderSpec:e=>` Invalid spec: "${e}". Use format: provider or provider/model (e.g. claude/claude-haiku-4-5)`,postReviewSelectAction:"What would you like to do with this PR?",postReviewSubmitting:"Submitting review to GitHub...",postReviewApproved:e=>` PR #${e} approved on GitHub.`,postReviewChangesRequested:e=>` Changes requested on PR #${e}.`,postReviewFailed:e=>`Failed to submit review: ${e}`,postReviewRefreshing:"Refreshing project context...",postReviewRefreshed:"Context updated.",contextGenerateTitle:`
|
|
27
|
+
`,configAgentProviderNone:" No per-agent providers configured. All agents use the default provider.",configInvalidAgentName:e=>` Unknown agent: "${e}". Valid: business, architecture, security, context`,configInvalidProviderSpec:e=>` Invalid spec: "${e}". Use format: provider or provider/model (e.g. claude/claude-haiku-4-5)`,postReviewSelectAction:"What would you like to do with this PR?",postReviewSubmitting:"Submitting review to GitHub...",postReviewApproved:e=>` PR #${e} approved on GitHub.`,postReviewChangesRequested:e=>` Changes requested on PR #${e}.`,postReviewFailed:e=>`Failed to submit review: ${e}`,postReviewRefreshing:"Refreshing project context...",postReviewRefreshed:"Context updated.",postReviewMergeConfirm:"Merge this PR now?",postReviewSelectMerge:"Select merge strategy:",postReviewMerging:e=>`Merging PR #${e}...`,postReviewMerged:e=>` PR #${e} merged.`,postReviewMergeFailed:e=>`Failed to merge: ${e}`,postReviewMergeStrategyMerge:"Merge commit",postReviewMergeStrategySquash:"Squash and merge",postReviewMergeStrategyRebase:"Rebase and merge",contextGenerateTitle:`
|
|
28
28
|
Generating context and rules with AI...
|
|
29
29
|
`,contextGenerating:"Analyzing project with AI...",contextGenerateDone:"Done. Edit the files if needed.",contextGenerateFailed:e=>`Generation failed: ${e}`,contextGenerateConfirmOverwrite:e=>`${e} already exists. Overwrite?`,contextGenerateSkipped:e=>` ${e} skipped (existing file kept).`,contextGenerateWritten:e=>` ${e} written.`,contextGenerateProviderError:e=>`Missing AI key: ${e}. Check your provider config.`,agentsTitle:`
|
|
30
30
|
aiv \u2014 Available Agents
|
|
@@ -53,22 +53,22 @@
|
|
|
53
53
|
Providers Personalizados (compatibles con OpenAI)
|
|
54
54
|
`,customProviderNone:" Sin providers personalizados. Agrega uno con: aiv config add-provider <nombre> --base-url <url> --api-key-env <VAR> --model <modelo>",customProviderColName:"Nombre",customProviderColUrl:"Base URL",customProviderColModel:"Modelo",customProviderColEnvVar:"Var Token",customProviderColToken:"Token",builtinProviderSet:(e,t,n)=>` ${e}: model=${t}, key_env=${n}`,providerFallback:(e,t)=>`Provider "${e}" excedi\xF3 su cuota/l\xEDmite \u2014 cambiando a "${t}"`,providerAllFailed:"Todos los providers en la cadena de fallback fallaron. Revisa tus API keys y cuotas.",configAgentProviderSet:(e,t)=>` El agente "${e}" usar\xE1: ${t}`,configFallbackSet:e=>` Cadena de fallback configurada: ${e}`,configFallbackCleared:" Cadena de fallback eliminada.",configAgentProviderShow:`
|
|
55
55
|
Providers por agente
|
|
56
|
-
`,configAgentProviderNone:" Sin providers por agente. Todos usan el provider predeterminado.",configInvalidAgentName:e=>` Agente desconocido: "${e}". V\xE1lidos: business, architecture, security, context`,configInvalidProviderSpec:e=>` Spec inv\xE1lido: "${e}". Formato: provider o provider/modelo (ej. claude/claude-haiku-4-5)`,postReviewSelectAction:"\xBFQu\xE9 deseas hacer con este PR?",postReviewSubmitting:"Enviando revisi\xF3n a GitHub...",postReviewApproved:e=>` PR #${e} aprobado en GitHub.`,postReviewChangesRequested:e=>` Se solicitaron cambios en el PR #${e}.`,postReviewFailed:e=>`Error al enviar la revisi\xF3n: ${e}`,postReviewRefreshing:"Actualizando contexto del proyecto...",postReviewRefreshed:"Contexto actualizado.",contextGenerateTitle:`
|
|
56
|
+
`,configAgentProviderNone:" Sin providers por agente. Todos usan el provider predeterminado.",configInvalidAgentName:e=>` Agente desconocido: "${e}". V\xE1lidos: business, architecture, security, context`,configInvalidProviderSpec:e=>` Spec inv\xE1lido: "${e}". Formato: provider o provider/modelo (ej. claude/claude-haiku-4-5)`,postReviewSelectAction:"\xBFQu\xE9 deseas hacer con este PR?",postReviewSubmitting:"Enviando revisi\xF3n a GitHub...",postReviewApproved:e=>` PR #${e} aprobado en GitHub.`,postReviewChangesRequested:e=>` Se solicitaron cambios en el PR #${e}.`,postReviewFailed:e=>`Error al enviar la revisi\xF3n: ${e}`,postReviewRefreshing:"Actualizando contexto del proyecto...",postReviewRefreshed:"Contexto actualizado.",postReviewMergeConfirm:"\xBFHacer merge de este PR ahora?",postReviewSelectMerge:"Selecciona la estrategia de merge:",postReviewMerging:e=>`Haciendo merge del PR #${e}...`,postReviewMerged:e=>` PR #${e} mergeado.`,postReviewMergeFailed:e=>`Error al hacer merge: ${e}`,postReviewMergeStrategyMerge:"Merge commit",postReviewMergeStrategySquash:"Squash and merge",postReviewMergeStrategyRebase:"Rebase and merge",contextGenerateTitle:`
|
|
57
57
|
Generando contexto y reglas con IA...
|
|
58
58
|
`,contextGenerating:"Analizando proyecto con IA...",contextGenerateDone:"Listo. Edita los archivos si es necesario.",contextGenerateFailed:e=>`Generaci\xF3n fallida: ${e}`,contextGenerateConfirmOverwrite:e=>`${e} ya existe. \xBFSobreescribir?`,contextGenerateSkipped:e=>` ${e} omitido (se conserv\xF3 el existente).`,contextGenerateWritten:e=>` ${e} escrito.`,contextGenerateProviderError:e=>`Clave de IA faltante: ${e}. Revisa tu config de proveedor.`,agentsTitle:`
|
|
59
59
|
aiv \u2014 Agentes Disponibles
|
|
60
|
-
`,agentsColAgent:"Agente",agentsColDesc:"Descripci\xF3n",agentsColFocus:"\xC1reas de Enfoque",agentsFooter:"Ejecuta agentes espec\xEDficos: aiv review <pr> --agent business security",agentBusinessDesc:"Analiza l\xF3gica de negocio, reglas de dominio y correcci\xF3n funcional",agentBusinessFocus:"L\xF3gica de negocio, invariantes de dominio, violaciones de reglas, regresiones funcionales",agentArchDesc:"Revisa patrones estructurales, acoplamiento de m\xF3dulos y decisiones de dise\xF1o",agentArchFocus:"Violaciones de capas, acoplamiento, SRP, direcci\xF3n de dependencias, calidad de abstracciones",agentSecDesc:"Detecta vulnerabilidades de seguridad y riesgos de exposici\xF3n de datos",agentSecFocus:"Bypass de autenticaci\xF3n, inyecci\xF3n, filtraci\xF3n de datos, OWASP Top 10, datos sensibles"};var
|
|
60
|
+
`,agentsColAgent:"Agente",agentsColDesc:"Descripci\xF3n",agentsColFocus:"\xC1reas de Enfoque",agentsFooter:"Ejecuta agentes espec\xEDficos: aiv review <pr> --agent business security",agentBusinessDesc:"Analiza l\xF3gica de negocio, reglas de dominio y correcci\xF3n funcional",agentBusinessFocus:"L\xF3gica de negocio, invariantes de dominio, violaciones de reglas, regresiones funcionales",agentArchDesc:"Revisa patrones estructurales, acoplamiento de m\xF3dulos y decisiones de dise\xF1o",agentArchFocus:"Violaciones de capas, acoplamiento, SRP, direcci\xF3n de dependencias, calidad de abstracciones",agentSecDesc:"Detecta vulnerabilidades de seguridad y riesgos de exposici\xF3n de datos",agentSecFocus:"Bypass de autenticaci\xF3n, inyecci\xF3n, filtraci\xF3n de datos, OWASP Top 10, datos sensibles"};var tn={en:$e,es:je},V="en";function Oe(){let e=process.env.AIV_LANG;if(e&&ae(e)){V=e;return}try{let n=Ge.join(De.homedir(),".aiv","config.yml");if(ce.existsSync(n)){let o=ce.readFileSync(n,"utf8"),i=/^\s*lang:\s*['"]?(\w+)['"]?\s*$/m.exec(o);if(i&&ae(i[1])){V=i[1];return}}}catch{}(process.env.LANG??process.env.LANGUAGE??"").startsWith("es")&&(V="es")}function Ue(e){V=e}function qe(){return V}function r(){return tn[V]??$e}function ae(e){return e==="en"||e==="es"}var Be=["en","es"];var Q=u(require("chalk"));function on(){try{return He().version}catch{return"0.1.0"}}var rn=`
|
|
61
61
|
\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
62
62
|
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
63
63
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
64
64
|
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
65
65
|
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
66
|
-
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D`;function
|
|
67
|
-
AIV V${e} BETA`)),console.log(Q.default.cyan(
|
|
68
|
-
`))}var
|
|
66
|
+
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D`;function ze(){if(!process.stdout.isTTY||process.argv.includes("--json"))return;let e=on();console.log(Q.default.cyan(`
|
|
67
|
+
AIV V${e} BETA`)),console.log(Q.default.cyan(rn)),console.log(Q.default.dim(" by Ateriss")),console.log(Q.default.white.bold(` AI Pull Request Reviewer
|
|
68
|
+
`))}var pt=require("commander"),j=u(require("fs")),mt=u(require("path")),h=u(require("chalk")),ne=u(require("ora"));var R=u(require("fs")),F=u(require("path")),Je=u(require("os")),I=u(require("js-yaml"));function Ve(){return F.join(Je.homedir(),".aiv")}function Z(){return F.join(Ve(),"config.yml")}function K(e=process.cwd()){return F.join(e,".aiv")}function X(e){return F.join(K(e),"config.yml")}function W(e){return F.join(K(e),"rules.yml")}function N(e){return F.join(K(e),"context.md")}function q(e){return F.join(K(e),"tree.json")}function b(e){return R.existsSync(X(e))}function Ke(){return R.existsSync(Z())}function v(){let e=Z();if(!R.existsSync(e))return{...$};let t=I.load(R.readFileSync(e,"utf8"));return t.github??=$.github,t.github.accounts??={},t}function C(e){let t=Ve();R.existsSync(t)||R.mkdirSync(t,{recursive:!0}),R.writeFileSync(Z(),I.dump(e,{lineWidth:120}),"utf8")}function ee(e){let t=X(e);return R.existsSync(t)?I.load(R.readFileSync(t,"utf8")):{}}function te(e,t){R.writeFileSync(X(t),I.dump(e,{lineWidth:120}),"utf8")}function We(e){let t=W(e);return R.existsSync(t)?I.load(R.readFileSync(t,"utf8")):{}}function Ye(e,t){R.writeFileSync(W(t),I.dump(e,{lineWidth:120}),"utf8")}function Y(e){let t=v(),n=ee(e),o=n.github?.account??t.github.default_account??"default",i=t.github.accounts[o]??t.github.accounts[t.github.default_account]??Object.values(t.github.accounts)[0]??{token_env:"GITHUB_TOKEN"},a=t.review??$.review,s=t.providers??$.providers;return{lang:t.lang??"en",providers:{default:s.default??"claude",fallback:s.fallback??[],agents:s.agents??{}},claude:t.claude??$.claude,openai:t.openai??$.openai,gemini:t.gemini??$.gemini,custom_providers:t.custom_providers??{},review:{max_tokens:n.review?.max_tokens??a.max_tokens,max_findings:n.review?.max_findings??a.max_findings},github:{accountName:o,token_env:i.token_env,username:i.username,owner:n.github?.owner,repo:n.github?.repo}}}function le(e){let t=process.env[e.github.token_env];if(!t)throw new Error(r().errorMissingToken(e.github.token_env,e.github.accountName));return t}function Qe(e,t){let n=v();n.github.accounts[e]=t,Object.keys(n.github.accounts).length===1&&(n.github.default_account=e),C(n)}function Ze(e){let t=v();if(!(e in t.github.accounts))throw new Error(r().errorAccountNotFound(e));delete t.github.accounts[e],t.github.default_account===e&&(t.github.default_account=Object.keys(t.github.accounts)[0]??""),C(t)}function Xe(e){let t=v();if(!(e in t.github.accounts))throw new Error(r().errorAccountNotFound(e));t.github.default_account=e,C(t)}function et(e,t){let n=v();if(!(e in n.github.accounts))throw new Error(r().errorAccountNotFoundGlobal(e));let o=ee(t);o.github??={},o.github.account=e,te(o,t)}function Le(){let e=v();return Object.entries(e.github.accounts).map(([t,n])=>({name:t,account:n,isDefault:t===e.github.default_account,hasToken:!!process.env[n.token_env]}))}var $={lang:"en",providers:{default:"claude"},claude:{model:"claude-sonnet-4-6",api_key_env:"CLAUDE_API_KEY"},openai:{model:"gpt-4.1",api_key_env:"OPENAI_API_KEY"},gemini:{model:"gemini-2.0-flash",api_key_env:"GEMINI_API_KEY"},custom_providers:{},review:{max_tokens:2e4,max_findings:20},github:{default_account:"default",accounts:{default:{token_env:"GITHUB_TOKEN",description:"Default GitHub account"}}}};function tt(e,t){let n=v();n.custom_providers??={},n.custom_providers[e]=t,C(n)}function nt(e){let t=v();if(!t.custom_providers?.[e])throw new Error(r().customProviderNotFound(e));delete t.custom_providers[e],C(t)}function ot(){let e=v();return Object.entries(e.custom_providers??{}).map(([t,n])=>({name:t,cfg:n,hasToken:!!process.env[n.api_key_env]}))}var it={github:{},context:{provider:"local"}},rt={sensitive_modules:["auth","payroll","payments","billing"],business_rules:{payroll:{required_calls:["auditLog","validateTransaction"]},auth:{required_checks:["permissionValidation"]}}};var x=u(require("fs")),L=u(require("path")),sn=new Set(["node_modules",".git","dist","build",".next",".nuxt","coverage",".aiv",".cache","vendor","__pycache__",".venv","venv"]),an=[["package.json","Node.js"],["tsconfig.json","TypeScript"],["requirements.txt","Python"],["Pipfile","Python (Pipenv)"],["pyproject.toml","Python (Poetry)"],["pom.xml","Java (Maven)"],["build.gradle","Java/Kotlin (Gradle)"],["Cargo.toml","Rust"],["go.mod","Go"],["composer.json","PHP (Composer)"],["Gemfile","Ruby"],["docker-compose.yml","Docker Compose"],["Dockerfile","Docker"],["terraform.tf","Terraform"],[".terraform","Terraform"],["kubernetes","Kubernetes"],["k8s","Kubernetes"],["prisma","Prisma ORM"],["drizzle.config","Drizzle ORM"],["sequelize","Sequelize ORM"],["typeorm","TypeORM"],["mongoose","MongoDB (Mongoose)"],["next.config","Next.js"],["nuxt.config","Nuxt.js"],["vite.config","Vite"],["webpack.config","Webpack"],["jest.config","Jest"],["vitest.config","Vitest"]],cn=["auth","login","password","token","session","jwt","oauth","payment","billing","invoice","stripe","paypal","payroll","salary","compensation","admin","permission","role","acl","rbac","crypto","encrypt","decrypt","hash","secret","migration","seed","database","schema"];async function de(e){let t=L.basename(e),n=x.readdirSync(e),o=dn(e,n),i=un(e),a=gn(e),s=pn(e);return`# Project Context: ${t}
|
|
69
69
|
|
|
70
70
|
## Architecture
|
|
71
|
-
${
|
|
71
|
+
${mn(e,n,o)}
|
|
72
72
|
|
|
73
73
|
## Modules
|
|
74
74
|
${i.map(l=>`- ${l}`).join(`
|
|
@@ -79,11 +79,11 @@ ${o.map(l=>`- ${l}`).join(`
|
|
|
79
79
|
`)||"- (not detected)"}
|
|
80
80
|
|
|
81
81
|
## Critical Dependencies
|
|
82
|
-
${
|
|
82
|
+
${s.map(l=>`- ${l}`).join(`
|
|
83
83
|
`)||"- (not detected)"}
|
|
84
84
|
|
|
85
85
|
## Sensitive Zones
|
|
86
|
-
${
|
|
86
|
+
${a.map(l=>`- ${l}`).join(`
|
|
87
87
|
`)||"- (none detected)"}
|
|
88
88
|
|
|
89
89
|
## Business Rules
|
|
@@ -92,14 +92,14 @@ ${s.map(l=>`- ${l}`).join(`
|
|
|
92
92
|
## System Summary
|
|
93
93
|
${t} \u2014 ${o.slice(0,3).join(", ")} project.
|
|
94
94
|
Auto-generated context. Edit ${e}/.aiv/context.md to add business-specific knowledge.
|
|
95
|
-
`}var
|
|
95
|
+
`}var ln=[[["react"],"React"],[["vue"],"Vue.js"],[["@angular/core"],"Angular"],[["express"],"Express.js"],[["fastify"],"Fastify"],[["nestjs","@nestjs/core"],"NestJS"],[["graphql"],"GraphQL"],[["apollo-server","@apollo/server"],"Apollo Server"],[["knex"],"Knex.js"]];function dn(e,t){let n=[];for(let[i,a]of an)t.some(s=>s.toLowerCase().startsWith(i.toLowerCase()))&&n.push(a);let o=L.join(e,"package.json");if(x.existsSync(o))try{let i=JSON.parse(x.readFileSync(o,"utf8")),a={...i.dependencies,...i.devDependencies};for(let[s,l]of ln)s.some(d=>d in a)&&n.push(l)}catch{}return[...new Set(n)]}function un(e){let t=["src","app","lib","packages","modules"],n=[];for(let o of t){let i=L.join(e,o);x.existsSync(i)&&x.statSync(i).isDirectory()&&x.readdirSync(i).filter(s=>{try{return x.statSync(L.join(i,s)).isDirectory()}catch{return!1}}).forEach(s=>n.push(`${o}/${s}`))}return n.slice(0,20)}function gn(e){let t=[];function n(o,i=0){if(i>3)return;let a;try{a=x.readdirSync(o)}catch{return}for(let s of a){if(sn.has(s))continue;let l=s.toLowerCase();for(let p of cn)if(l.includes(p)){let A=L.relative(e,L.join(o,s));t.push(A);break}let d=L.join(o,s);try{x.statSync(d).isDirectory()&&n(d,i+1)}catch{}}}return n(e),[...new Set(t)].slice(0,15)}function pn(e){let t=L.join(e,"package.json");if(!x.existsSync(t))return[];try{let o={...JSON.parse(x.readFileSync(t,"utf8")).dependencies};return Object.keys(o).filter(a=>{let s=a.toLowerCase();return s.includes("auth")||s.includes("jwt")||s.includes("passport")||s.includes("crypto")||s.includes("stripe")||s.includes("paypal")||s.includes("prisma")||s.includes("typeorm")||s.includes("sequelize")||s.includes("mongoose")||s.includes("knex")||s.includes("pg")||s.includes("mysql")||s.includes("redis")||s.includes("aws-sdk")||s.includes("@aws")||s.includes("firebase")}).slice(0,15)}catch{return[]}}function mn(e,t,n){let o=t.includes("src"),i=t.includes("packages"),a=t.includes("apps"),s=t.includes("modules");return i&&a?"Monorepo (apps/ + packages/ structure)":s?"Modular monolith (modules/ structure)":o?"Standard source layout (src/)":n.includes("NestJS")?"NestJS application (controllers/services/modules)":n.includes("Next.js")?"Next.js application (pages/ or app/ router)":"Standard project layout"}var ct=u(require("fs")),B=u(require("path")),fn=new Set(["node_modules",".git","dist","build",".next",".nuxt","coverage",".aiv",".cache","vendor","__pycache__",".venv","venv",".DS_Store"]),vn=[[/auth|login|session|jwt|oauth|passport/i,"auth"],[/sql|database|db|migration|seed|schema|prisma|typeorm|sequelize|knex/i,"sql"],[/component|view|page|template|ui|frontend|client|web/i,"frontend"],[/service|controller|handler|route|api|endpoint|backend|server/i,"backend"],[/infra|terraform|k8s|kubernetes|helm|docker|deploy|ci|cd/i,"infra"],[/config|setting|env|constant/i,"config"],[/test|spec|__test__|__mock__|fixture/i,"test"]],hn=[[/auth|login|password|jwt|oauth|session|token|secret|crypto|encrypt/i,"high"],[/payment|billing|payroll|salary|invoice|stripe|financial/i,"high"],[/admin|permission|role|acl|rbac|sudo/i,"high"],[/migration|seed|database|schema/i,"medium"],[/service|controller|api|route/i,"medium"],[/test|spec|mock|fixture|config/i,"low"]];async function ue(e,t=4){return lt(e,e,t)}function lt(e,t,n,o=0){let i=B.basename(t),s={path:B.relative(e,t)||".",type:"directory",module_type:st(i),sensitivity:at(i),children:[]};if(o>=n)return s;let l;try{l=ct.readdirSync(t,{withFileTypes:!0})}catch{return s}for(let d of l){if(fn.has(d.name))continue;let p=B.join(t,d.name);d.isDirectory()?s.children.push(lt(e,p,n,o+1)):o<n&&s.children.push({path:B.relative(e,p),type:"file",module_type:st(d.name),sensitivity:at(d.name)})}return s.children.length>50&&(s.children=s.children.slice(0,50)),s}function st(e){for(let[t,n]of vn)if(t.test(e))return n;return"other"}function at(e){for(let[t,n]of hn)if(t.test(e))return n;return"low"}var E=u(require("fs")),dt=u(require("path")),ut=require("child_process");function ge(e){try{let t=(0,ut.execSync)("git remote get-url origin",{cwd:e,stdio:"pipe"}).toString().trim();return yn(t)}catch{return null}}function yn(e){let t=/github\.com[:/]([^/]+)\/([^/.]+)/.exec(e);if(t)return{owner:t[1],repo:t[2]};let n=/github\.com\/([^/]+)\/([^/.]+)/.exec(e);return n?{owner:n[1],repo:n[2]}:null}function gt(e){let t=dt.join(e,".gitignore"),n=".aiv/";if(!E.existsSync(t)){E.writeFileSync(t,n+`
|
|
96
96
|
`,"utf8");return}E.readFileSync(t,"utf8").includes(".aiv")||E.appendFileSync(t,`
|
|
97
97
|
# aiv local context
|
|
98
98
|
${n}
|
|
99
|
-
`,"utf8")}function
|
|
100
|
-
`),console.log(` ${r().initEditContext(
|
|
101
|
-
`),console.log(
|
|
102
|
-
`))})}function
|
|
99
|
+
`,"utf8")}function ft(){return new pt.Command("init").alias("i").description("Initialize aiv in the current repository").option("--force","Reinitialize even if already initialized").action(async e=>{let t=process.cwd();if(b(t)&&!e.force){console.log(h.default.yellow(r().initAlreadyDone));return}if(console.log(h.default.bold(r().initTitle)),Ke())console.log(h.default.dim(` \u21B3 ${r().initGlobalConfigExists}`));else{let d=(0,ne.default)(r().initWritingGlobalConfig).start();C($),d.succeed(h.default.green(r().initGlobalConfigCreated))}let n=K(t);j.existsSync(n)||j.mkdirSync(n,{recursive:!0});let o=(0,ne.default)(r().initWritingConfig).start();te(it,t),o.succeed(h.default.green(r().initConfigCreated)),Ye(rt,t),console.log(h.default.green(` \u2714 ${r().initRulesCreated}`));let i=(0,ne.default)(r().initScanningTree).start();try{let d=await ue(t);j.writeFileSync(q(t),JSON.stringify(d,null,2),"utf8"),i.succeed(h.default.green(r().initTreeCreated))}catch{i.warn(r().initTreeSkipped)}let a=(0,ne.default)(r().initBuildingContext).start();try{let d=await de(t);j.writeFileSync(N(t),d,"utf8"),a.succeed(h.default.green(r().initContextCreated))}catch{a.warn(r().initContextSkipped),j.writeFileSync(N(t),bn(t),"utf8")}gt(t),console.log(h.default.green(` \u2714 ${r().initGitignoreUpdated}`));let s=$.claude?.api_key_env??"CLAUDE_API_KEY",l=$.github.accounts.default?.token_env??"GITHUB_TOKEN";console.log(h.default.bold.green(r().initSuccessTitle)),console.log(` ${h.default.cyan("1.")} ${r().initStep1(s)}`),console.log(` ${h.default.cyan("2.")} ${r().initStep2(l)}`),console.log(` ${h.default.cyan("3.")} ${r().initStep3}`),console.log(` ${h.default.cyan("4.")} ${r().initStep4}
|
|
100
|
+
`),console.log(` ${r().initEditContext(h.default.cyan(".aiv/context.md"))}`),console.log(` ${r().initEditRules(h.default.cyan(".aiv/rules.yml"))}
|
|
101
|
+
`),console.log(h.default.dim(r().initGlobalHint)),console.log(h.default.dim(r().initAddAccountHint+`
|
|
102
|
+
`))})}function bn(e){return`# Project Context: ${mt.basename(e)}
|
|
103
103
|
|
|
104
104
|
## Architecture
|
|
105
105
|
(Describe your system architecture here)
|
|
@@ -121,16 +121,18 @@ ${n}
|
|
|
121
121
|
|
|
122
122
|
## System Summary
|
|
123
123
|
(High-level summary of what this system does)
|
|
124
|
-
`}var
|
|
124
|
+
`}var Tt=require("commander"),m=u(require("chalk")),Nt=u(require("ora")),Mt=require("table");var oe="https://api.github.com",M=class{headers;constructor(t){this.headers={Authorization:`Bearer ${t}`,Accept:"application/vnd.github.v3+json","X-GitHub-Api-Version":"2022-11-28","User-Agent":"aiv-cli/0.1.0"}}async listPRs(t,n,o=20){let i=`${oe}/repos/${t}/${n}/pulls?state=open&per_page=${Math.min(o,100)}&sort=updated&direction=desc`,a=await this.fetch(i);return a.ok||await this.throwError(a,"list PRs"),(await a.json()).map(l=>this.mapPR(l))}async getPR(t,n,o){let i=`${oe}/repos/${t}/${n}/pulls/${o}`,a=await this.fetch(i);return a.ok||await this.throwError(a,`get PR #${o}`),this.mapPR(await a.json())}async getPRFiles(t,n,o){let i=`${oe}/repos/${t}/${n}/pulls/${o}/files?per_page=100`,a=await this.fetch(i);return a.ok||await this.throwError(a,"get PR files"),(await a.json()).map(l=>({filename:l.filename,status:l.status,additions:l.additions,deletions:l.deletions,patch:l.patch}))}async getPRDiff(t,n,o){let[i,a]=await Promise.all([this.getPR(t,n,o),this.getPRFiles(t,n,o)]),s=a.filter(l=>l.patch).map(l=>`diff --git a/${l.filename} b/${l.filename}
|
|
125
125
|
${l.patch}`).join(`
|
|
126
|
-
`);return{pr:i,files:
|
|
127
|
-
\u26A1 ${
|
|
126
|
+
`);return{pr:i,files:a,rawDiff:s}}async mergePR(t,n,o,i="squash"){let a=`${oe}/repos/${t}/${n}/pulls/${o}/merge`,{default:s}=await import("node-fetch"),l=await s(a,{method:"PUT",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({merge_method:i})});l.ok||await this.throwError(l,`merge PR #${o}`)}async submitReview(t,n,o,i,a=""){let s=`${oe}/repos/${t}/${n}/pulls/${o}/reviews`,{default:l}=await import("node-fetch"),d=await l(s,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({event:i,body:a})});d.ok||await this.throwError(d,`submit review on PR #${o}`)}mapPR(t){return{id:t.id,number:t.number,title:t.title,author:t.user?.login??"unknown",branch:t.head?.ref??"",base:t.base?.ref??"",url:t.html_url,state:t.state,createdAt:t.created_at,updatedAt:t.updated_at,additions:t.additions??0,deletions:t.deletions??0,changedFiles:t.changed_files??0,labels:(t.labels??[]).map(n=>n.name),description:t.body??void 0}}async fetch(t){let{default:n}=await import("node-fetch");return n(t,{headers:this.headers})}async throwError(t,n){let o=`GitHub API error (${t.status}) while trying to ${n}`;try{let i=await t.json();i.message&&(o+=`: ${i.message}`)}catch{}throw new Error(o)}};var S=u(require("chalk"));async function ie(){return(await import("inquirer")).default}var vt=-1;function wn(e){let t=S.default.cyan(`#${e.number}`),n=e.title.length>52?e.title.slice(0,51)+"\u2026":e.title,o=S.default.dim(`${e.author} \xB7 ${e.branch}`),i=S.default.green(`+${e.additions}`)+S.default.dim("/")+S.default.red(`-${e.deletions}`);return`${t} ${n} ${o} ${i}`}async function pe(e,t){let n=await ie(),o=[...e.map(s=>({name:wn(s),value:s.number,short:`#${s.number}`})),new n.Separator("\u2500".repeat(62)),{name:S.default.dim("\u21A9 Cancel"),value:vt,short:"Cancel"}],a=(await n.prompt([{type:"list",name:"prNumber",message:t,choices:o,pageSize:14,loop:!1}])).prNumber;return a===vt?null:e.find(s=>s.number===a)??null}async function ht(e){let t=await ie();return(await t.prompt([{type:"list",name:"action",message:e,choices:[{name:S.default.green("\u2714 Approve PR"),value:"approve",short:"Approve"},{name:S.default.yellow("\u2691 Request Changes"),value:"request_changes",short:"Request Changes"},new t.Separator("\u2500".repeat(42)),{name:S.default.dim("\u21A9 Skip"),value:"skip",short:"Skip"}],pageSize:4,loop:!1}])).action}async function yt(e){return!!(await(await ie()).prompt([{type:"confirm",name:"ok",message:e,default:!1}])).ok}async function bt(e){return(await(await ie()).prompt([{type:"list",name:"strategy",message:e,choices:[{name:S.default.cyan("Squash and merge"),value:"squash",short:"Squash"},{name:"Merge commit",value:"merge",short:"Merge"},{name:S.default.dim("Rebase and merge"),value:"rebase",short:"Rebase"}],pageSize:3,loop:!1}])).strategy}async function wt(e,t){return!!(await(await ie()).prompt([{type:"confirm",name:"ok",message:`${t} ${S.default.cyan(`#${e.number}`)} \u2014 ${e.title}?`,default:!0}])).ok}var $t=require("commander"),f=u(require("chalk")),H=u(require("ora"));var se=u(require("chalk")),kt=u(require("ora"));var Ct=u(require("chalk"));var Rt=u(require("@anthropic-ai/sdk")),me=class{name="claude";model;client;constructor(t,n="claude-sonnet-4-6"){this.model=n,this.client=new Rt.default({apiKey:t})}async complete(t,n,o=4096){let i=await this.client.messages.create({model:this.model,max_tokens:o,system:n,messages:t.map(s=>({role:s.role==="system"?"user":s.role,content:s.content}))});return{content:i.content.filter(s=>s.type==="text").map(s=>s.text).join(""),inputTokens:i.usage.input_tokens,outputTokens:i.usage.output_tokens}}};var xt=u(require("openai")),re=class{name;model;client;constructor(t,n="gpt-4.1",o,i="openai"){this.name=i,this.model=n,this.client=new xt.default({apiKey:t,...o?{baseURL:o}:{}})}async complete(t,n,o=4096){let i=[];n&&i.push({role:"system",content:n}),i.push(...t.map(s=>({role:s.role,content:s.content})));let a=await this.client.chat.completions.create({model:this.model,max_tokens:o,messages:i});return{content:a.choices[0]?.message?.content??"",inputTokens:a.usage?.prompt_tokens,outputTokens:a.usage?.completion_tokens}}};var Rn="https://generativelanguage.googleapis.com/v1beta/models",fe=class{name="gemini";model;apiKey;constructor(t,n="gemini-2.0-flash"){this.apiKey=t,this.model=n}async complete(t,n,o=4096){let a={contents:t.filter(k=>k.role!=="system").map(k=>({role:k.role==="assistant"?"model":"user",parts:[{text:k.content}]})),generationConfig:{maxOutputTokens:o}};n&&(a.systemInstruction={parts:[{text:n}]});let s=`${Rn}/${this.model}:generateContent?key=${this.apiKey}`,{default:l}=await import("node-fetch"),d=await l(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)});if(!d.ok){let k=await d.json(),y=d.status,Pe=k?.error?.message??"unknown error";throw Object.assign(new Error(`Gemini API error (${y}): ${Pe}`),{status:y})}let p=await d.json();return{content:(p?.candidates?.[0]?.content?.parts??[]).map(k=>k.text??"").join(""),inputTokens:p?.usageMetadata?.promptTokenCount,outputTokens:p?.usageMetadata?.candidatesTokenCount}}};var ve=class{name="mock";model="mock-model";async complete(t,n){return{content:JSON.stringify({summary:"Mock review: This is a test response from the mock provider.",findings:[{severity:"low",category:"test",title:"Mock Finding",description:"This is a placeholder finding from the mock provider.",suggestion:"Replace mock provider with a real one."}],riskScore:10,possibleRegressions:["Mock regression note"]}),inputTokens:100,outputTokens:100}}};var he=class{constructor(t,n){this.chain=t;this.onFallback=n;if(t.length===0)throw new Error("FallbackProvider requires at least one provider")}chain;onFallback;index=0;get name(){return this.chain[this.index].name}get model(){return this.chain[this.index].model}async complete(t,n,o){for(let i=this.index;i<this.chain.length;i++)try{let a=await this.chain[i].complete(t,n,o);return this.index=i,a}catch(a){if(!xn(a)||i+1>=this.chain.length)throw a;this.onFallback?.(this.chain[i].name,this.chain[i+1].name),this.index=i+1}throw new Error("All providers in fallback chain exhausted")}};function xn(e){let t=e?.status??e?.statusCode??e?.httpStatus;if(t===429||t===529)return!0;let n=e instanceof Error?e.message.toLowerCase():String(e).toLowerCase();return n.includes("rate_limit")||n.includes("rate limit")||n.includes("quota")||n.includes("overloaded")||n.includes("too many requests")||n.includes("insufficient_quota")}function ye(e,t){let n=e.providers.agents[t],o=n?Cn(e,n):Te(e,e.providers.default),i=e.providers.fallback;if(i.length===0)return o;let a=[o];for(let s of i){let l=kn(e,s);l&&!Sn(l,o)&&a.push(l)}return a.length===1?o:new he(a,(s,l)=>{console.log(Ct.default.yellow(`
|
|
127
|
+
\u26A1 ${s} quota/rate limit \u2014 switching to ${l}`))})}function Cn(e,t){let n=t.indexOf("/"),o=n===-1?t:t.slice(0,n),i=n===-1?void 0:t.slice(n+1);return Te(e,o,i)}function Te(e,t,n){if(t==="claude"){let i=process.env[e.claude.api_key_env];if(!i)throw new Error(`Missing env var: ${e.claude.api_key_env}`);return new me(i,n??e.claude.model)}if(t==="openai"){let i=process.env[e.openai.api_key_env];if(!i)throw new Error(`Missing env var: ${e.openai.api_key_env}`);return new re(i,n??e.openai.model)}if(t==="gemini"){let i=process.env[e.gemini.api_key_env];if(!i)throw new Error(`Missing env var: ${e.gemini.api_key_env}`);return new fe(i,n??e.gemini.model)}if(t==="mock")return new ve;let o=e.custom_providers[t];if(o){let i=o.api_key_env?process.env[o.api_key_env]??"":"";return new re(i,n??o.model,o.base_url,t)}throw new Error(`Unknown provider: "${t}". Add it with: aiv config add-provider ${t} --base-url <url> --api-key-env <VAR> --model <model>`)}function kn(e,t){try{return Te(e,t)}catch{return null}}function Sn(e,t){return e.name===t.name&&e.model===t.model}var G=class{constructor(t){this.provider=t}provider;async run(t){let n=this.buildUserMessage(t),o=await this.provider.complete([{role:"user",content:n}],this.systemPrompt,4096);return this.parseResponse(o.content)}buildUserMessage(t){let n=JSON.stringify(t.rules,null,2),o=t.diff.files.map(s=>`${s.status.toUpperCase()} ${s.filename} (+${s.additions}/-${s.deletions})`).join(`
|
|
128
128
|
`),i=t.diff.files.filter(s=>s.patch).map(s=>`### ${s.filename}
|
|
129
129
|
\`\`\`diff
|
|
130
130
|
${s.patch}
|
|
131
131
|
\`\`\``).join(`
|
|
132
132
|
|
|
133
|
-
`)
|
|
133
|
+
`),a=qe()==="es"?`
|
|
134
|
+
|
|
135
|
+
IMPORTANT: Respond in Spanish. All string values in the JSON (summary, title, description, suggestion, possibleRegressions) must be written in Spanish.`:"";return`## PR: #${t.diff.pr.number} \u2014 ${t.diff.pr.title}
|
|
134
136
|
|
|
135
137
|
**Author:** ${t.diff.pr.author}
|
|
136
138
|
**Branch:** ${t.diff.pr.branch} \u2192 ${t.diff.pr.base}
|
|
@@ -177,7 +179,7 @@ Analyze the above and return a JSON response matching this schema:
|
|
|
177
179
|
"possibleRegressions": ["string"]
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
Return ONLY valid JSON. No markdown fences, no explanation outside the JSON
|
|
182
|
+
Return ONLY valid JSON. No markdown fences, no explanation outside the JSON.${a}`}parseResponse(t){let n;try{let i=t.replace(/```json\n?/g,"").replace(/```\n?/g,"").trim();n=JSON.parse(i)}catch{return{agentName:this.agentName,findings:[{severity:"info",category:"parse-error",title:"Could not parse agent response",description:t.slice(0,500)}],summary:"Agent returned unparseable response.",riskScore:0}}let o=(n.findings??[]).map(i=>({severity:i.severity??"info",category:i.category??"general",title:i.title??"Untitled",description:i.description??"",file:i.file,suggestion:i.suggestion}));return{agentName:this.agentName,findings:o,summary:n.summary??"",riskScore:Math.max(0,Math.min(100,parseInt(n.riskScore??"0")))}}};var be=class extends G{agentName="business";systemPrompt=`You are a senior business analyst and domain expert performing a code review.
|
|
181
183
|
|
|
182
184
|
Your job is to analyze Pull Request changes STRICTLY from a business and domain perspective.
|
|
183
185
|
|
|
@@ -197,7 +199,7 @@ If the rules.yml specifies required_calls or required_checks for a module, verif
|
|
|
197
199
|
Be concrete. Reference specific lines or functions when relevant.
|
|
198
200
|
Assign a riskScore from 0 (no risk) to 100 (critical business risk).
|
|
199
201
|
|
|
200
|
-
Return only valid JSON as specified.`;constructor(t){super(t)}};var
|
|
202
|
+
Return only valid JSON as specified.`;constructor(t){super(t)}};var we=class extends G{agentName="architecture";systemPrompt=`You are a principal software architect performing a code review.
|
|
201
203
|
|
|
202
204
|
Your job is to analyze Pull Request changes from an architectural and structural perspective.
|
|
203
205
|
|
|
@@ -217,7 +219,7 @@ You are NOT checking syntax, linting, or security.
|
|
|
217
219
|
Use the project context to understand the existing architecture. Flag deviations.
|
|
218
220
|
Assign a riskScore from 0 (clean) to 100 (structural problem that needs blocking).
|
|
219
221
|
|
|
220
|
-
Return only valid JSON as specified.`;constructor(t){super(t)}};var Re=class extends
|
|
222
|
+
Return only valid JSON as specified.`;constructor(t){super(t)}};var Re=class extends G{agentName="security";systemPrompt=`You are a senior application security engineer performing a code review.
|
|
221
223
|
|
|
222
224
|
Your job is to analyze Pull Request changes for security vulnerabilities and risks.
|
|
223
225
|
|
|
@@ -238,40 +240,40 @@ Mark findings involving sensitive_modules from the rules with higher severity.
|
|
|
238
240
|
Be precise: name the vulnerability class (OWASP), the file, and the specific line or pattern.
|
|
239
241
|
Assign a riskScore from 0 (no security issues) to 100 (active vulnerability, block immediately).
|
|
240
242
|
|
|
241
|
-
Return only valid JSON as specified.`;constructor(t){super(t)}};var
|
|
242
|
-
`)}function
|
|
243
|
+
Return only valid JSON as specified.`;constructor(t){super(t)}};var xe=class{constructor(t,n){this.config=t;this.rules=n}config;rules;async run(t,n,o){let i=Pn(o,this.config),a={diff:t,projectContext:n,rules:this.rules},s=await An(i,a),l=$n(s.map(p=>p.riskScore)),d=s.flatMap(p=>p.possibleRegressions??[]);return{prNumber:t.pr.number,prTitle:t.pr.title,executiveSummary:Ln(s,l),riskScore:l,riskLabel:St(l),agents:s,businessRisks:s.find(p=>p.agentName==="business")?.findings??[],architectureIssues:s.find(p=>p.agentName==="architecture")?.findings??[],securityIssues:s.find(p=>p.agentName==="security")?.findings??[],possibleRegressions:d,generatedAt:new Date().toISOString()}}};function Pn(e,t){let n={business:o=>new be(o),architecture:o=>new we(o),security:o=>new Re(o)};return e.filter(o=>o in n).map(o=>n[o](ye(t,o)))}async function An(e,t){return(await Promise.allSettled(e.map(async o=>{let i=(0,kt.default)(r().orchestratorRunning(se.default.cyan(o.agentName))).start();try{let a=await o.run(t);return i.succeed(r().orchestratorDone(se.default.cyan(o.agentName),a.findings.length,a.riskScore)),a}catch(a){return i.fail(se.default.red(r().orchestratorAgentFailed(se.default.cyan(o.agentName),a.message))),{agentName:o.agentName,findings:[],summary:r().orchestratorFailedMsg(a.message),riskScore:0}}}))).map(o=>o.status==="fulfilled"?o.value:{agentName:"unknown",findings:[],summary:r().orchestratorFailedUnexpected,riskScore:0})}function $n(e){if(e.length===0)return 0;let t=Math.max(...e),n=e.reduce((o,i)=>o+i,0)/e.length;return Math.round(t*.6+n*.4)}function Ln(e,t){let n=St(t),o=e.reduce((s,l)=>s+l.findings.length,0),i=e.flatMap(s=>s.findings).filter(s=>s.severity==="critical"||s.severity==="high").length,a=i>0?r().orchestratorCriticalFound(i):r().orchestratorNoCritical;return[r().orchestratorOverallRisk(n,t,o,e.length),a,"",...e.map(s=>`[${s.agentName.toUpperCase()}] ${s.summary}`)].join(`
|
|
244
|
+
`)}function St(e){return e>=80?"CRITICAL":e>=60?"HIGH":e>=30?"MEDIUM":"LOW"}var _=u(require("fs"));async function ke(e){let t=!1,n=!1;try{let o=await ue(e);_.writeFileSync(q(e),JSON.stringify(o,null,2),"utf8"),t=!0}catch{}try{let o=await de(e);_.writeFileSync(N(e),o,"utf8"),n=!0}catch{}return{treeOk:t,contextOk:n}}var Ce=class{cwd;constructor(t=process.cwd()){this.cwd=t}readContext(){if(!b(this.cwd))return"";let t=N(this.cwd);return _.existsSync(t)?_.readFileSync(t,"utf8"):""}readTree(){let t=q(this.cwd);if(!_.existsSync(t))return null;try{return JSON.parse(_.readFileSync(t,"utf8"))}catch{return null}}buildReviewContext(t){let n=this.readContext(),o=this.readTree(),i=new Set(t.files.map(s=>s.filename)),a=o?Tn(o,i):"";return[n,a?`
|
|
243
245
|
## Relevant Project Tree
|
|
244
246
|
\`\`\`json
|
|
245
|
-
${
|
|
247
|
+
${a}
|
|
246
248
|
\`\`\``:""].filter(Boolean).join(`
|
|
247
|
-
`)}};function
|
|
249
|
+
`)}};function Tn(e,t){let o=(e.children??[]).filter(i=>{let a=i.path??"";return Array.from(t).some(s=>s.startsWith(a))||i.sensitivity==="high"}).map(i=>({path:i.path,type:i.type,module_type:i.module_type,sensitivity:i.sensitivity}));return o.length===0?"":JSON.stringify(o,null,2)}var g=u(require("chalk"));function At(e){let t=r(),o=Pt(e.riskLabel)(`${e.riskScore}/100 [${e.riskLabel}]`);console.log(`
|
|
248
250
|
`+g.default.bold("\u2501".repeat(60))),console.log(g.default.bold(t.renderReviewTitle(e.prNumber,e.prTitle))),console.log(g.default.bold("\u2501".repeat(60))),console.log(`
|
|
249
251
|
${g.default.bold(t.renderRiskScore)} ${o}`),console.log(` ${g.default.bold(t.renderGenerated)} ${g.default.dim(e.generatedAt)}
|
|
250
|
-
`),console.log(g.default.bold(t.renderExecutiveSummary)),console.log(g.default.dim(" "+"\u2500".repeat(56))),console.log(
|
|
252
|
+
`),console.log(g.default.bold(t.renderExecutiveSummary)),console.log(g.default.dim(" "+"\u2500".repeat(56))),console.log(Me(e.executiveSummary,2)),e.securityIssues.length>0&&(console.log(`
|
|
251
253
|
`+g.default.bold.red(t.renderSecurityIssues)),console.log(g.default.dim(" "+"\u2500".repeat(56))),Ne(e.securityIssues)),e.businessRisks.length>0&&(console.log(`
|
|
252
254
|
`+g.default.bold.yellow(t.renderBusinessRisks)),console.log(g.default.dim(" "+"\u2500".repeat(56))),Ne(e.businessRisks)),e.architectureIssues.length>0&&(console.log(`
|
|
253
255
|
`+g.default.bold.blue(t.renderArchitectureIssues)),console.log(g.default.dim(" "+"\u2500".repeat(56))),Ne(e.architectureIssues)),e.possibleRegressions.length>0&&(console.log(`
|
|
254
256
|
`+g.default.bold.magenta(t.renderRegressions)),console.log(g.default.dim(" "+"\u2500".repeat(56))),e.possibleRegressions.forEach(i=>{console.log(` ${g.default.magenta("\u25C6")} ${i}`)})),console.log(`
|
|
255
|
-
`+g.default.bold(t.renderAgentSummaries)),console.log(g.default.dim(" "+"\u2500".repeat(56))),e.agents.forEach(i=>{let
|
|
256
|
-
${g.default.bold(i.agentName.toUpperCase())} ${
|
|
257
|
+
`+g.default.bold(t.renderAgentSummaries)),console.log(g.default.dim(" "+"\u2500".repeat(56))),e.agents.forEach(i=>{let s=Pt(Mn(i.riskScore))(`[${i.riskScore}/100]`);console.log(`
|
|
258
|
+
${g.default.bold(i.agentName.toUpperCase())} ${s}`),console.log(Me(i.summary,2))}),console.log(`
|
|
257
259
|
`+g.default.bold("\u2501".repeat(60))+`
|
|
258
|
-
`)}function Ne(e){let t=r();e.forEach(n=>{let o=
|
|
259
|
-
${o} ${g.default.bold(n.title)}${i}`),console.log(
|
|
260
|
+
`)}function Ne(e){let t=r();e.forEach(n=>{let o=Nn(n.severity),i=n.file?g.default.dim(` \u2192 ${n.file}`):"";console.log(`
|
|
261
|
+
${o} ${g.default.bold(n.title)}${i}`),console.log(Me(n.description,2)),n.suggestion&&console.log(` ${g.default.dim(t.renderSuggestion)} ${n.suggestion}`)})}function Nn(e){let t=r();switch(e){case"critical":return g.default.bgRed.white(` ${t.severityCritical} `);case"high":return g.default.red(`[${t.severityHigh}]`);case"medium":return g.default.yellow(`[${t.severityMedium}]`);case"low":return g.default.blue(`[${t.severityLow}]`);default:return g.default.dim(`[${t.severityInfo}]`)}}function Pt(e){switch(e){case"CRITICAL":return g.default.bgRed.white;case"HIGH":return g.default.red;case"MEDIUM":return g.default.yellow;default:return g.default.green}}function Mn(e){return e>=80?"CRITICAL":e>=60?"HIGH":e>=30?"MEDIUM":"LOW"}function Me(e,t){let n=" ".repeat(t);return e.split(`
|
|
260
262
|
`).map(o=>n+o).join(`
|
|
261
|
-
`)}async function
|
|
262
|
-
`));let d=(0,
|
|
263
|
-
`+(0
|
|
264
|
-
`)),e.select===!1||!process.stdout.isTTY)return;let
|
|
263
|
+
`)}async function _e(e){let{prNumber:t,owner:n,repo:o,config:i,token:a}=e,s=We(),l=e.agents??["business","architecture","security"];console.log(f.default.bold(r().reviewTitle(t))),console.log(f.default.dim(` ${r().reviewAccount(i.github.accountName,i.github.token_env)}
|
|
264
|
+
`));let d=(0,H.default)(r().reviewFetching(t,`${n}/${o}`)).start(),p;try{p=await new M(a).getPRDiff(n,o,t),d.succeed(r().reviewLoaded(f.default.cyan(p.pr.title),p.files.length))}catch(T){d.fail(f.default.red(r().reviewFetchFailed(T.message)));return}let A=(0,H.default)(r().reviewLoadingContext).start(),k=new Ce(process.cwd()).buildReviewContext(p);A.succeed(r().reviewContextLoaded),console.log(f.default.dim(r().reviewRunningAgents(l.join(", "))));try{let T=await new xe(i,s).run(p,k,l);if(e.json){console.log(JSON.stringify(T,null,2));return}At(T)}catch(T){console.log(f.default.red(r().reviewFailed(T.message)));return}if(!process.stdout.isTTY)return;let y=await ht(r().postReviewSelectAction);if(y==="skip")return;let Pe=y==="approve"?"APPROVE":"REQUEST_CHANGES",Ae=(0,H.default)(r().postReviewSubmitting).start();try{await new M(a).submitReview(n,o,t,Pe),y==="approve"?Ae.succeed(f.default.green(r().postReviewApproved(t))):Ae.succeed(f.default.yellow(r().postReviewChangesRequested(t)))}catch(T){Ae.fail(f.default.red(r().postReviewFailed(T.message)));return}if(y==="approve"){if(await yt(r().postReviewMergeConfirm)){let Jt=await bt(r().postReviewSelectMerge),Ie=(0,H.default)(r().postReviewMerging(t)).start();try{await new M(a).mergePR(n,o,t,Jt),Ie.succeed(f.default.green(r().postReviewMerged(t)))}catch(Vt){Ie.fail(f.default.red(r().postReviewMergeFailed(Vt.message)))}}let zt=(0,H.default)(r().postReviewRefreshing).start();await ke(process.cwd()),zt.succeed(f.default.green(r().postReviewRefreshed))}}function Lt(){return new $t.Command("review").alias("r").description("Run AI review on a pull request").argument("[pr-number]","PR number (omit to pick interactively)").option("--owner <owner>","GitHub owner").option("--repo <repo>","GitHub repo").option("--agent <agents...>","Run specific agents only (business, architecture, security)").option("--json","Output raw JSON result").action(async(e,t)=>{if(!b()){console.log(f.default.red(r().notInitialized));return}let n=Y(),o;try{o=le(n)}catch{console.log(f.default.red(r().prsMissingToken(n.github.token_env)));return}let i=ge(process.cwd()),a=t.owner??n.github.owner??i?.owner,s=t.repo??n.github.repo??i?.repo;if(!a||!s){console.log(f.default.red(r().repoNotDetected));return}let l;if(e===void 0){let d=(0,H.default)(r().prsFetching(f.default.cyan(`${a}/${s}`))).start(),p;try{p=await new M(o).listPRs(a,s,30),d.stop()}catch(k){d.fail(f.default.red(r().prsFailed(k.message)));return}if(p.length===0){console.log(f.default.yellow(r().prsNoneFound));return}let A=await pe(p,r().selectorSelectPR);if(!A){console.log(f.default.dim(r().selectorCancelled));return}l=A.number}else if(l=Number.parseInt(e),Number.isNaN(l)){console.log(f.default.red(r().invalidPrNumber));return}await _e({prNumber:l,owner:a,repo:s,config:n,token:o,agents:t.agent,json:t.json})})}function _t(){return new Tt.Command("prs").alias("p").description("List open pull requests and optionally launch a review").option("--limit <n>","Max number of PRs to show","20").option("--owner <owner>","GitHub owner (overrides auto-detect)").option("--repo <repo>","GitHub repo (overrides auto-detect)").option("--no-select","Skip interactive selector, just list PRs").action(async e=>{if(!b()){console.log(m.default.red(r().notInitialized));return}let t=Y(),n;try{n=le(t)}catch{console.log(m.default.red(r().prsMissingToken(t.github.token_env)));return}let o=ge(process.cwd()),i=e.owner??t.github.owner??o?.owner,a=e.repo??t.github.repo??o?.repo;if(!i||!a){console.log(m.default.red(r().repoNotDetected));return}let s=(0,Nt.default)(r().prsFetching(m.default.cyan(`${i}/${a}`))).start(),l;try{l=await new M(n).listPRs(i,a,Number.parseInt(e.limit)),s.stop()}catch(y){s.fail(m.default.red(r().prsFailed(y.message)));return}if(l.length===0){console.log(m.default.yellow(r().prsNoneFound));return}let d=l.map(y=>[m.default.cyan(`#${y.number}`),_n(y.title,50),m.default.dim(y.author),m.default.dim(y.branch),m.default.green(`+${y.additions}`)+m.default.dim("/")+m.default.red(`-${y.deletions}`),m.default.dim(Fn(y.createdAt))]),p=[m.default.bold(r().prsColPR),m.default.bold(r().prsColTitle),m.default.bold(r().prsColAuthor),m.default.bold(r().prsColBranch),m.default.bold(r().prsColChanges),m.default.bold(r().prsColCreated)];if(console.log(`
|
|
265
|
+
`+(0,Mt.table)([p,...d],{border:{topBody:"\u2500",topJoin:"\u252C",topLeft:"\u256D",topRight:"\u256E",bottomBody:"\u2500",bottomJoin:"\u2534",bottomLeft:"\u2570",bottomRight:"\u256F",bodyLeft:"\u2502",bodyRight:"\u2502",bodyJoin:"\u2502",joinBody:"\u2500",joinLeft:"\u251C",joinRight:"\u2524",joinJoin:"\u253C"},columnDefault:{paddingLeft:1,paddingRight:1}})),console.log(m.default.dim(` ${r().prsFooter(l.length).trim()}`)),console.log(m.default.dim(` ${r().reviewAccount(t.github.accountName,t.github.token_env)}
|
|
266
|
+
`)),e.select===!1||!process.stdout.isTTY)return;let A=await pe(l,r().selectorSelectPR);if(!A){console.log(m.default.dim(r().selectorCancelled));return}if(!await wt(A,r().selectorConfirmReview)){console.log(m.default.dim(r().selectorCancelled));return}await _e({prNumber:A.number,owner:i,repo:a,config:t,token:n})})}function _n(e,t){return e.length>t?e.slice(0,t-1)+"\u2026":e}function Fn(e){return new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"2-digit"})}var jt=require("commander"),z=u(require("fs")),w=u(require("chalk")),Se=u(require("ora"));var P=u(require("fs")),D=u(require("path"));var In=new Set(["node_modules",".git","dist","build",".aiv",".next","coverage"]),En=/\.(ts|js|tsx|jsx|py|go|java|rb|php|cs|rs)$/;async function Ft(e,t){let n=D.basename(e),o=q(e),i=P.existsSync(o)?P.readFileSync(o,"utf8").slice(0,6e3):"(tree not available \u2014 run aiv context refresh first)",a=Gn(e),s=Dn(e),l=`
|
|
265
267
|
Project name: ${n}
|
|
266
268
|
|
|
267
269
|
package.json summary:
|
|
268
|
-
${
|
|
270
|
+
${a}
|
|
269
271
|
|
|
270
272
|
Project file tree (tree.json):
|
|
271
273
|
${i}
|
|
272
274
|
|
|
273
275
|
Sample source files:
|
|
274
|
-
${
|
|
276
|
+
${s}
|
|
275
277
|
|
|
276
278
|
Generate context.md and rules.yml suitable for AI PR reviewers for this project.
|
|
277
279
|
Return ONLY valid JSON with exactly these two fields:
|
|
@@ -299,20 +301,20 @@ Given a project's structure, dependencies, and sample code, produce:
|
|
|
299
301
|
forbidden_patterns: [patterns that must never appear]
|
|
300
302
|
|
|
301
303
|
Be specific. Infer real module names from the file tree. Do not invent generic placeholders.
|
|
302
|
-
Return ONLY the JSON object \u2014 no explanation, no markdown fences.`,4e3);return
|
|
304
|
+
Return ONLY the JSON object \u2014 no explanation, no markdown fences.`,4e3);return jn(p.content)}function jn(e){let t=/\{[\s\S]*\}/.exec(e.trim());if(!t)throw new Error("AI returned no JSON");try{let n=JSON.parse(t[0]);if(!n.context||!n.rules)throw new Error("missing fields");return{context:n.context,rules:n.rules}}catch{return{context:e,rules:`sensitive_modules: []
|
|
303
305
|
business_rules: {}
|
|
304
|
-
`}}}function
|
|
306
|
+
`}}}function Gn(e){let t=D.join(e,"package.json");if(!P.existsSync(t))return"(not found)";try{let n=JSON.parse(P.readFileSync(t,"utf8")),o=Object.keys({...n.dependencies,...n.devDependencies}).slice(0,30);return`name: ${n.name??"?"}
|
|
305
307
|
scripts: ${Object.keys(n.scripts??{}).join(", ")}
|
|
306
|
-
dependencies: ${o.join(", ")}`}catch{return"(unreadable)"}}function
|
|
307
|
-
${
|
|
308
|
+
dependencies: ${o.join(", ")}`}catch{return"(unreadable)"}}function Dn(e){let t=["src","app","lib","packages","modules"].map(o=>D.join(e,o)).filter(o=>{try{return P.statSync(o).isDirectory()}catch{return!1}}),n=[];for(let o of t.slice(0,2))if(It(o,n),n.length>=12)break;return n.slice(0,12).map(o=>{let i=D.relative(e,o),a=P.readFileSync(o,"utf8").slice(0,400);return`--- ${i} ---
|
|
309
|
+
${a}`}).join(`
|
|
308
310
|
|
|
309
|
-
`).slice(0,7e3)}function
|
|
310
|
-
`+
|
|
311
|
+
`).slice(0,7e3)}function It(e,t,n=0){if(n>3||t.length>=12)return;let o;try{o=P.readdirSync(e)}catch{return}for(let i of o){if(In.has(i))continue;let a=D.join(e,i);try{let s=P.statSync(a);s.isFile()&&En.test(i)?t.push(a):s.isDirectory()&&It(a,t,n+1)}catch{}if(t.length>=12)break}}function Gt(){let e=new jt.Command("context").alias("ctx").description("Manage local project context");return e.command("refresh").description("Rebuild project context and tree from current codebase").action(async()=>{if(!b()){console.log(w.default.red(r().notInitialized));return}console.log(w.default.bold(r().contextRefreshTitle));let t=(0,Se.default)(r().contextScanningTree).start(),n=(0,Se.default)(r().contextRebuildingCtx),{treeOk:o,contextOk:i}=await ke(process.cwd());o?t.succeed(w.default.green(r().contextTreeUpdated)):t.fail(r().contextTreeFailed("scan failed")),n.start(),i?n.succeed(w.default.green(r().contextCtxUpdated)):n.fail(r().contextCtxFailed("analysis failed")),console.log(w.default.dim(r().contextEditHint(w.default.cyan(".aiv/context.md"))))}),e.command("show").description("Show current context.md contents").action(()=>{if(!b()){console.log(w.default.red(r().notInitialized));return}let t=N();if(!z.existsSync(t)){console.log(w.default.yellow(r().contextNoFile));return}console.log(`
|
|
312
|
+
`+z.readFileSync(t,"utf8"))}),e.command("generate").description("Use AI to generate context.md and rules.yml from the current codebase").option("--context-only","Generate only context.md").option("--rules-only","Generate only rules.yml").option("--force","Overwrite existing files without asking").action(async t=>{if(!b()){console.log(w.default.red(r().notInitialized));return}let n=Y(),o;try{o=ye(n,"context")}catch(d){console.log(w.default.red(r().contextGenerateProviderError(d.message)));return}console.log(w.default.bold(r().contextGenerateTitle));let i=(0,Se.default)(r().contextGenerating).start(),a;try{a=await Ft(process.cwd(),o),i.succeed(w.default.green(r().contextGenerateDone))}catch(d){i.fail(w.default.red(r().contextGenerateFailed(d.message)));return}let s=!t.rulesOnly,l=!t.contextOnly;s&&await Et(N(),a.context,".aiv/context.md",t.force),l&&await Et(W(),a.rules,".aiv/rules.yml",t.force),console.log(w.default.dim(r().contextEditHint(w.default.cyan(".aiv/context.md"))))}),e}async function Et(e,t,n,o){if(z.existsSync(e)&&!o){let{default:i}=await import("inquirer"),{ok:a}=await i.prompt([{type:"confirm",name:"ok",message:r().contextGenerateConfirmOverwrite(n),default:!0}]);if(!a){console.log(w.default.dim(r().contextGenerateSkipped(n)));return}}z.writeFileSync(e,t,"utf8"),console.log(w.default.green(r().contextGenerateWritten(n)))}var Dt=require("commander"),J=u(require("fs")),c=u(require("chalk")),Fe=require("table");function Ot(){let e=new Dt.Command("config").alias("cf").description("View or update aiv configuration");return e.command("show").description("Show global and repo config").action(()=>{let t=r();console.log(`
|
|
311
313
|
`+c.default.bold(t.configGlobalTitle)+`
|
|
312
|
-
`);let n=Z();console.log(
|
|
313
|
-
`),console.log(
|
|
314
|
+
`);let n=Z();console.log(J.existsSync(n)?J.readFileSync(n,"utf8"):c.default.dim(t.configNotCreated)),b()&&(console.log(c.default.bold(t.configRepoConfigTitle)+`
|
|
315
|
+
`),console.log(J.readFileSync(X(),"utf8")))}),e.command("set-provider <provider>").description("Set default AI provider (claude | openai | gemini | mock | <custom-name>)").action(t=>{let n=new Set(["claude","openai","gemini","mock"]),o=v();if(!(n.has(t)||t in(o.custom_providers??{}))){console.log(c.default.red(r().invalidProvider));return}o.providers.default=t,C(o),console.log(c.default.green(r().configProviderSet(t)))}),e.command("add-provider <name>").description("Register an AI provider. Built-in: gemini. Custom: any OpenAI-compatible endpoint.").option("--base-url <url>","Base URL for OpenAI-compatible providers (required for custom)").option("--api-key-env <var>","Env var holding the API key").option("--model <model>","Default model for this provider").option("--force","Overwrite if provider already exists").action((t,n)=>{new Set(["claude","openai","gemini","mock"]).has(t)?On(t,n):Un(t,n)}),e.command("remove-provider <name>").description("Remove a custom provider from global config").action(t=>{try{nt(t),console.log(c.default.green(r().customProviderRemoved(t)))}catch{console.log(c.default.red(r().customProviderNotFound(t)))}}),e.command("list-providers").description("List all configured providers (built-in and custom)").action(()=>{let t=r(),n=v(),o=ot();console.log(c.default.bold(`
|
|
314
316
|
Built-in Providers
|
|
315
|
-
`));for(let i of["claude","openai","gemini"]){let
|
|
317
|
+
`));for(let i of["claude","openai","gemini"]){let a=n[i],s=n.providers.default===i?c.default.green(" \u2714 default"):"";a&&console.log(` ${c.default.cyan(i.padEnd(8))} model=${c.default.dim(a.model)} key_env=${c.default.dim(a.api_key_env)}${s}`)}if(console.log(c.default.bold(t.customProviderTitle)),o.length===0)console.log(c.default.dim(t.customProviderNone));else{let i=o.map(({name:s,cfg:l,hasToken:d})=>[n.providers.default===s?c.default.green(s):s,c.default.dim(l.base_url),c.default.dim(l.model),c.default.dim(l.api_key_env),d?c.default.green("found"):c.default.red("missing")]),a=[c.default.bold(t.customProviderColName),c.default.bold(t.customProviderColUrl),c.default.bold(t.customProviderColModel),c.default.bold(t.customProviderColEnvVar),c.default.bold(t.customProviderColToken)];console.log((0,Fe.table)([a,...i],{border:{topBody:"\u2500",topJoin:"\u252C",topLeft:"\u256D",topRight:"\u256E",bottomBody:"\u2500",bottomJoin:"\u2534",bottomLeft:"\u2570",bottomRight:"\u256F",bodyLeft:"\u2502",bodyRight:"\u2502",bodyJoin:"\u2502",joinBody:"\u2500",joinLeft:"\u251C",joinRight:"\u2524",joinJoin:"\u253C"},columnDefault:{paddingLeft:1,paddingRight:1}}))}console.log()}),e.command("set-repo <owner> <repo>").description("Set GitHub owner and repo in repo config").action((t,n)=>{if(!b()){console.log(c.default.red(r().notInitialized));return}let o=ee();o.github??={},o.github.owner=t,o.github.repo=n,te(o),console.log(c.default.green(r().configRepoSet(t,n)))}),e.command("set-lang <lang>").description(`Set display language (${Be.join(" | ")})`).action(t=>{if(!ae(t)){console.log(c.default.red(r().configInvalidLang));return}let n=v();n.lang=t,C(n),Ue(t),console.log(c.default.green(r().configLangSet(t)))}),e.command("rules").description("Show repo rules.yml").action(()=>{if(!b()){console.log(c.default.red(r().notInitialized));return}let t=r();console.log(`
|
|
316
318
|
`+c.default.bold(t.configRulesTitle)+`
|
|
317
|
-
`);let n=
|
|
318
|
-
`))})}
|
|
319
|
+
`);let n=W();console.log(J.existsSync(n)?J.readFileSync(n,"utf8"):c.default.yellow(t.configNoRules))}),e.command("accounts").description("List GitHub accounts in global config").action(()=>{let t=r(),n=Le();if(console.log(c.default.bold(t.accountsTitle)),n.length===0){console.log(c.default.yellow(t.accountsNone));return}let o=n.map(({name:a,account:s,isDefault:l,hasToken:d})=>[l?c.default.green(a):a,l?c.default.green(t.accountsDefaultMark):"",s.username?c.default.dim(s.username):c.default.dim("\u2014"),c.default.dim(s.token_env),d?c.default.green(t.accountsTokenFound):c.default.red(t.accountsTokenMissing),c.default.dim(s.description??"")]),i=[c.default.bold(t.accountsColName),c.default.bold(t.accountsColDefault),c.default.bold(t.accountsColUser),c.default.bold(t.accountsColEnvVar),c.default.bold(t.accountsColToken),c.default.bold(t.accountsColDesc)];if(console.log((0,Fe.table)([i,...o],{border:{topBody:"\u2500",topJoin:"\u252C",topLeft:"\u256D",topRight:"\u256E",bottomBody:"\u2500",bottomJoin:"\u2534",bottomLeft:"\u2570",bottomRight:"\u256F",bodyLeft:"\u2502",bodyRight:"\u2502",bodyJoin:"\u2502",joinBody:"\u2500",joinLeft:"\u251C",joinRight:"\u2524",joinJoin:"\u253C"},columnDefault:{paddingLeft:1,paddingRight:1}})),console.log(c.default.dim(t.accountsGlobalHint)),b()){let a=ee().github?.account;a&&console.log(c.default.dim(t.configRepoAccountHint(a)))}console.log(c.default.dim(t.accountsRepoHint))}),e.command("add-account <name>").description("Add a GitHub account to global config").requiredOption("--token-env <envVar>","Environment variable holding the GitHub token").option("--username <username>","GitHub username (optional, for display)").option("--description <desc>","Short description (optional)").option("--force","Overwrite if account already exists").action((t,n)=>{if(Le().find(a=>a.name===t)&&!n.force){console.log(c.default.yellow(r().accountsAlreadyExists(t)));return}let i={token_env:n.tokenEnv,username:n.username,description:n.description};Qe(t,i),console.log(c.default.green(r().accountsAdded(t))),console.log(c.default.dim(r().configTokenEnvHint(n.tokenEnv))),n.username&&console.log(c.default.dim(r().configUsernameHint(n.username)))}),e.command("remove-account <name>").description("Remove a GitHub account from global config").action(t=>{try{Ze(t),console.log(c.default.green(r().accountsRemoved(t)))}catch{console.log(c.default.red(r().accountsNotFound(t)))}}),e.command("default-account <name>").description("Set the global default GitHub account").action(t=>{try{Xe(t),console.log(c.default.green(r().accountsDefaultSet(t)))}catch{console.log(c.default.red(r().accountsNotFound(t)))}}),e.command("use-account <name>").description("Use a specific GitHub account for this repo (overrides global default)").action(t=>{if(!b()){console.log(c.default.red(r().notInitialized));return}try{et(t),console.log(c.default.green(r().accountsRepoSet(t))),console.log(c.default.dim(r().configSavedToRepo))}catch(n){console.log(c.default.red(n.message))}}),e.command("set-agent-provider <agent> [spec]").description("Assign a provider+model to a specific agent (e.g. business claude/claude-haiku-4-5)").option("--clear","Remove the override for this agent").action((t,n,o)=>{if(!new Set(["business","architecture","security","context"]).has(t)){console.log(c.default.red(r().configInvalidAgentName(t)));return}let a=v();if(a.providers.agents??={},o.clear){delete a.providers.agents[t],C(a),console.log(c.default.green(r().configAgentProviderSet(t,"(default)")));return}if(!n){console.log(c.default.red(r().configInvalidProviderSpec("")));return}let s=new Set(["claude","openai","gemini","mock"]),l=n.split("/")[0],d=Object.keys(v().custom_providers??{});if(!s.has(l)&&!d.includes(l)){console.log(c.default.red(r().configInvalidProviderSpec(n)));return}a.providers.agents[t]=n,C(a),console.log(c.default.green(r().configAgentProviderSet(t,n)))}),e.command("set-fallback [providers...]").description("Set ordered fallback provider chain (e.g. openai claude). Pass no args to clear.").action(t=>{let n=v(),o=new Set(["claude","openai","gemini","mock",...Object.keys(n.custom_providers??{})]),i=t.filter(s=>!o.has(s));if(i.length>0){console.log(c.default.red(r().configInvalidProviderSpec(i.join(", "))));return}let a=n;a.providers.fallback=t,C(a),t.length===0?console.log(c.default.green(r().configFallbackCleared)):console.log(c.default.green(r().configFallbackSet(t.join(" \u2192 "))))}),e.command("show-agents").description("Show per-agent provider assignments and fallback chain").action(()=>{let t=v(),n=r(),o=t.providers.agents??{},i=t.providers.fallback??[];if(console.log(c.default.bold(n.configAgentProviderShow)),Object.keys(o).length===0)console.log(c.default.dim(n.configAgentProviderNone));else for(let[a,s]of Object.entries(o))console.log(` ${c.default.cyan(a.padEnd(14))} ${s}`);console.log(),console.log(` ${"default".padEnd(14)} ${c.default.dim(t.providers.default)}`),i.length>0&&console.log(` ${"fallback".padEnd(14)} ${c.default.dim(i.join(" \u2192 "))}`),console.log()}),e.action(()=>e.help()),e}function On(e,t){if(!t.model&&!t.apiKeyEnv){console.log(c.default.yellow(" Provide --model, --api-key-env, or both to update a built-in provider."));return}let n=v(),o=n[e]??{};t.model&&(o.model=t.model),t.apiKeyEnv&&(o.api_key_env=t.apiKeyEnv),n[e]=o,C(n),console.log(c.default.green(r().builtinProviderSet(e,o.model,o.api_key_env)))}function Un(e,t){if(!t.baseUrl){console.log(c.default.red(r().customProviderBaseUrlRequired));return}if(v().custom_providers?.[e]&&!t.force){console.log(c.default.yellow(r().customProviderAlreadyExists(e)));return}tt(e,{base_url:t.baseUrl,api_key_env:t.apiKeyEnv??`${e.toUpperCase()}_API_KEY`,model:t.model??"unknown"}),console.log(c.default.green(r().customProviderAdded(e)))}var Ut=require("commander"),O=u(require("chalk")),qt=require("table");function Bt(){return new Ut.Command("agents").alias("a").description("List available AI review agents").action(()=>{let e=r(),t=[{name:"business",desc:e.agentBusinessDesc,focus:e.agentBusinessFocus},{name:"architecture",desc:e.agentArchDesc,focus:e.agentArchFocus},{name:"security",desc:e.agentSecDesc,focus:e.agentSecFocus}];console.log(O.default.bold(e.agentsTitle));let n=t.map(i=>[O.default.cyan(i.name),i.desc,O.default.dim(i.focus)]),o=[O.default.bold(e.agentsColAgent),O.default.bold(e.agentsColDesc),O.default.bold(e.agentsColFocus)];console.log((0,qt.table)([o,...n],{border:{topBody:"\u2500",topJoin:"\u252C",topLeft:"\u256D",topRight:"\u256E",bottomBody:"\u2500",bottomJoin:"\u2534",bottomLeft:"\u2570",bottomRight:"\u256F",bodyLeft:"\u2502",bodyRight:"\u2502",bodyJoin:"\u2502",joinBody:"\u2500",joinLeft:"\u251C",joinRight:"\u2524",joinJoin:"\u253C"},columns:[{width:14},{width:44,wrapWord:!0},{width:52,wrapWord:!0}],columnDefault:{paddingLeft:1,paddingRight:1}})),console.log(O.default.dim(` ${e.agentsFooter}
|
|
320
|
+
`))})}Oe();ze();var U=new Ht.Command;U.name("aiv").description("AI-powered PR reviewer \u2014 local-first, multi-agent semantic analysis").version("0.1.0");U.addCommand(ft());U.addCommand(_t());U.addCommand(Lt());U.addCommand(Gt());U.addCommand(Ot());U.addCommand(Bt());U.parse(process.argv);
|
package/package.json
CHANGED
package/src/agents/base.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentResult, AgentFinding, PRDiff, AivRules } from '../types';
|
|
2
2
|
import { LLMProvider } from '../providers/base';
|
|
3
|
+
import { getLang } from '../i18n';
|
|
3
4
|
|
|
4
5
|
export interface AgentContext {
|
|
5
6
|
projectContext: string;
|
|
@@ -36,6 +37,10 @@ export abstract class BaseAgent {
|
|
|
36
37
|
.map(f => `### ${f.filename}\n\`\`\`diff\n${f.patch}\n\`\`\``)
|
|
37
38
|
.join('\n\n');
|
|
38
39
|
|
|
40
|
+
const langInstruction = getLang() === 'es'
|
|
41
|
+
? '\n\nIMPORTANT: Respond in Spanish. All string values in the JSON (summary, title, description, suggestion, possibleRegressions) must be written in Spanish.'
|
|
42
|
+
: '';
|
|
43
|
+
|
|
39
44
|
return `## PR: #${ctx.diff.pr.number} — ${ctx.diff.pr.title}
|
|
40
45
|
|
|
41
46
|
**Author:** ${ctx.diff.pr.author}
|
|
@@ -83,7 +88,7 @@ Analyze the above and return a JSON response matching this schema:
|
|
|
83
88
|
"possibleRegressions": ["string"]
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
Return ONLY valid JSON. No markdown fences, no explanation outside the JSON
|
|
91
|
+
Return ONLY valid JSON. No markdown fences, no explanation outside the JSON.${langInstruction}`;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
protected parseResponse(raw: string): AgentResult {
|
|
@@ -8,7 +8,7 @@ import { detectRepoInfo } from '../../git/utils';
|
|
|
8
8
|
import { Orchestrator } from '../../orchestrator';
|
|
9
9
|
import { ContextManager, refreshContextFiles } from '../../context/manager';
|
|
10
10
|
import { renderReview } from '../renderer';
|
|
11
|
-
import { selectPR, selectPostReviewAction } from '../selector';
|
|
11
|
+
import { selectPR, selectPostReviewAction, confirmMerge, selectMergeStrategy } from '../selector';
|
|
12
12
|
import { t } from '../../i18n';
|
|
13
13
|
|
|
14
14
|
// ─── Shared review runner (used by prs.ts and review command) ─────────────────
|
|
@@ -82,6 +82,18 @@ export async function runReview(opts: RunReviewOptions): Promise<void> {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
if (action === 'approve') {
|
|
85
|
+
const wantsMerge = await confirmMerge(t().postReviewMergeConfirm);
|
|
86
|
+
if (wantsMerge) {
|
|
87
|
+
const strategy = await selectMergeStrategy(t().postReviewSelectMerge);
|
|
88
|
+
const mergeSpinner = ora(t().postReviewMerging(prNumber)).start();
|
|
89
|
+
try {
|
|
90
|
+
await new GithubClient(token).mergePR(owner, repo, prNumber, strategy);
|
|
91
|
+
mergeSpinner.succeed(chalk.green(t().postReviewMerged(prNumber)));
|
|
92
|
+
} catch (e: any) {
|
|
93
|
+
mergeSpinner.fail(chalk.red(t().postReviewMergeFailed(e.message)));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
const refreshSpinner = ora(t().postReviewRefreshing).start();
|
|
86
98
|
await refreshContextFiles(process.cwd());
|
|
87
99
|
refreshSpinner.succeed(chalk.green(t().postReviewRefreshed));
|