@ateriss_/aiv-cli 1.0.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 CHANGED
@@ -3,12 +3,14 @@
3
3
  A local-first, multi-agent CLI for reviewing GitHub pull requests using any supported AI provider. Runs three specialized agents in parallel (Business, Architecture, Security) and produces a structured risk report with findings, suggestions, and an executive summary.
4
4
 
5
5
  ```
6
- █████╗ ██╗██╗ ██╗
7
- ██╔══██╗██║██║ ██║
8
- ███████║██║██║ ██║
9
- ██╔══██║██║╚██╗ ██╔╝
10
- ██║ ██║██║ ╚████╔╝
11
- ╚═╝ ╚═╝╚═╝ ╚═══╝ by Ateriss
6
+ █████╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗██╗ ██╗██╗███████╗██╗ ██╗███████╗██████╗
7
+ ██╔══██╗██║ ██╔══██╗██╔══██╗ ██╔══██╗██╔════╝██║ ██║██║██╔════╝██║ ██║██╔════╝██╔══██╗
8
+ ███████║██║ ██████╔╝██████╔╝ ██████╔╝█████╗ ██║ ██║██║█████╗ ██║ █╗ ██║█████╗ ██████╔╝
9
+ ██╔══██║██║ ██╔═══╝ ██╔══██╗ ██╔══██╗██╔══╝ ╚██╗ ██╔╝██║██╔══╝ ██║███╗██║██╔══╝ ██╔══██╗
10
+ ██║ ██║██║ ██║ ██║ ██║ ██║ ██║███████╗ ╚████╔╝ ██║███████╗╚███╔███╔╝███████╗██║ ██║
11
+ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═══╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝`;
12
+
13
+ by Ateriss
12
14
  ```
13
15
 
14
16
  ---
@@ -45,7 +47,7 @@ A local-first, multi-agent CLI for reviewing GitHub pull requests using any supp
45
47
  ## Requirements
46
48
 
47
49
  - Node.js 18 or higher
48
- - A GitHub personal access token with `repo` scope
50
+ - A GitHub personal access token with `repo` scope (or `public_repo` for public repos)
49
51
  - An API key for at least one supported AI provider (see [AI Providers](#ai-providers))
50
52
 
51
53
  ---
@@ -53,17 +55,7 @@ A local-first, multi-agent CLI for reviewing GitHub pull requests using any supp
53
55
  ## Installation
54
56
 
55
57
  ```bash
56
- npm install -g aiv
57
- ```
58
-
59
- Or clone and build locally:
60
-
61
- ```bash
62
- git clone https://github.com/your-org/aiv-cli
63
- cd aiv-cli
64
- npm install
65
- npm run build
66
- npm link
58
+ npm install -g @ateriss_/aiv-cli
67
59
  ```
68
60
 
69
61
  ---
@@ -84,11 +76,16 @@ This creates:
84
76
  - `.aiv/context.md` — auto-generated project context
85
77
  - `.aiv/tree.json` — project file structure snapshot
86
78
 
87
- **2. Set your API key and GitHub token:**
79
+ **2. Set your API key and GitHub token as persistent environment variables:**
88
80
 
89
81
  ```bash
82
+ # macOS / Linux (add to ~/.bashrc or ~/.zshrc)
90
83
  export CLAUDE_API_KEY=sk-ant-...
91
84
  export GITHUB_TOKEN=ghp_...
85
+
86
+ # Windows (PowerShell — persists across sessions)
87
+ [System.Environment]::SetEnvironmentVariable("CLAUDE_API_KEY", "sk-ant-...", "User")
88
+ [System.Environment]::SetEnvironmentVariable("GITHUB_TOKEN", "ghp_...", "User")
92
89
  ```
93
90
 
94
91
  **3. (Optional) Let AI generate your context and rules:**
@@ -109,7 +106,7 @@ aiv prs
109
106
  aiv review 42
110
107
  ```
111
108
 
112
- After the report, aiv asks if you want to **approve or request changes** directly on GitHub.
109
+ After the report, aiv asks if you want to **approve**, **request changes**, or **merge** directly on GitHub.
113
110
 
114
111
  ---
115
112
 
@@ -210,7 +207,7 @@ aiv review 42 --agent business architecture
210
207
  aiv review 42 --json # raw JSON, no interactive prompt
211
208
  ```
212
209
 
213
- After the report prints, aiv asks whether to **approve**, **request changes**, or **skip** — and submits the decision directly to GitHub. If you approve, `context.md` and `tree.json` are refreshed automatically.
210
+ After the report prints, aiv asks whether to **approve**, **request changes**, or **skip** — and submits the decision directly to GitHub. If you approve, you can optionally merge the PR right away and choose the merge strategy. `context.md` and `tree.json` are always refreshed after an approval.
214
211
 
215
212
  ---
216
213
 
@@ -230,7 +227,7 @@ aiv ctx generate --rules-only
230
227
  aiv ctx generate --force # overwrite without asking
231
228
  ```
232
229
 
233
- `generate` uses the configured AI provider (or `providers.agents.context` if set) to analyze the project structure and produce a `context.md` and `rules.yml` tailored to your stack. Run it after `init` or whenever a new AI agent type is added — edit the output only where needed.
230
+ `generate` uses the configured AI provider (or `providers.agents.context` if set) to analyze the project structure and produce a `context.md` and `rules.yml` tailored to your stack. Run it after `init` or whenever the project changes significantly — edit the output only where needed.
234
231
 
235
232
  ---
236
233
 
@@ -682,17 +679,27 @@ After every review in an interactive terminal, aiv asks:
682
679
  ↩ Skip
683
680
  ```
684
681
 
685
- - **Approve** — submits an `APPROVE` review to GitHub, then auto-refreshes `context.md` and `tree.json`
686
- - **Request Changes** — submits a `REQUEST_CHANGES` review to GitHub
687
- - **Skip** — does nothing, exits cleanly
682
+ - **Approve** — submits an `APPROVE` review to GitHub. aiv then asks if you want to merge:
688
683
 
689
- The context refresh on approval ensures agents have current information for future reviews of the same repo.
684
+ ```
685
+ ? Merge this PR now? (y/N)
686
+
687
+ ? Select merge strategy:
688
+ ❯ Squash and merge
689
+ Merge commit
690
+ Rebase and merge
691
+ ```
692
+
693
+ After approving (with or without merge), `context.md` and `tree.json` are refreshed automatically.
694
+
695
+ - **Request Changes** — submits a `REQUEST_CHANGES` review to GitHub.
696
+ - **Skip** — does nothing, exits cleanly.
690
697
 
691
698
  ---
692
699
 
693
700
  ## Multi-language
694
701
 
695
- aiv supports English and Spanish. All output — errors, spinners, table headers, severity labels — follows the configured language.
702
+ aiv supports English and Spanish. All output — errors, spinners, table headers, severity labels, and AI agent responses — follows the configured language.
696
703
 
697
704
  ```bash
698
705
  aiv config set-lang es
@@ -728,7 +735,12 @@ export KIMI_API_KEY=sk-...
728
735
  aiv config add-provider kimi --api-key-env KIMI_API_KEY ...
729
736
  ```
730
737
 
731
- GitHub tokens require `repo` scope to read pull requests and diffs. To submit reviews (approve/request changes), the token also needs `pull_requests:write` scope.
738
+ ### GitHub token permissions
739
+
740
+ | Scope | Required for |
741
+ |-------|-------------|
742
+ | `repo` (private repos) or `public_repo` | Reading PRs and diffs |
743
+ | `pull_requests: write` (fine-grained PAT) | Submitting reviews and merging |
732
744
 
733
745
  ---
734
746
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var Ut=Object.create;var Ee=Object.defineProperty;var Bt=Object.getOwnPropertyDescriptor;var Ht=Object.getOwnPropertyNames;var qt=Object.getPrototypeOf,zt=Object.prototype.hasOwnProperty;var Jt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Vt=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Ht(t))!zt.call(e,i)&&i!==n&&Ee(e,i,{get:()=>t[i],enumerable:!(o=Bt(t,i))||o.enumerable});return e};var u=(e,t,n)=>(n=e!=null?Ut(qt(e)):{},Vt(t||!e||!e.__esModule?Ee(n,"default",{value:e,enumerable:!0}):n,e));var Be=Jt((Bn,Wt)=>{Wt.exports={name:"@ateriss_/aiv-cli",version:"1.0.0",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 Ot=require("commander");var se=u(require("fs")),Ge=u(require("path")),Me=u(require("os"));var Ae={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
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 Kt={en:Ae,es:je},Y="en";function De(){let e=process.env.AIV_LANG;if(e&&re(e)){Y=e;return}try{let n=Ge.join(Me.homedir(),".aiv","config.yml");if(se.existsSync(n)){let o=se.readFileSync(n,"utf8"),i=/^\s*lang:\s*['"]?(\w+)['"]?\s*$/m.exec(o);if(i&&re(i[1])){Y=i[1];return}}}catch{}(process.env.LANG??process.env.LANGUAGE??"").startsWith("es")&&(Y="es")}function Oe(e){Y=e}function r(){return Kt[Y]??Ae}function re(e){return e==="en"||e==="es"}var Ue=["en","es"];var Q=u(require("chalk"));function Yt(){try{return Be().version}catch{return"0.1.0"}}var Qt=`
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 He(){if(!process.stdout.isTTY||process.argv.includes("--json"))return;let e=Yt();console.log(Q.default.cyan(`
67
- AIV V${e} BETA`)),console.log(Q.default.cyan(Qt)),console.log(Q.default.dim(" by Ateriss")),console.log(Q.default.white.bold(` AI Pull Request Reviewer
68
- `))}var ut=require("commander"),j=u(require("fs")),gt=u(require("path")),v=u(require("chalk")),ne=u(require("ora"));var w=u(require("fs")),I=u(require("path")),qe=u(require("os")),F=u(require("js-yaml"));function ze(){return I.join(qe.homedir(),".aiv")}function Z(){return I.join(ze(),"config.yml")}function J(e=process.cwd()){return I.join(e,".aiv")}function X(e){return I.join(J(e),"config.yml")}function V(e){return I.join(J(e),"rules.yml")}function N(e){return I.join(J(e),"context.md")}function B(e){return I.join(J(e),"tree.json")}function b(e){return w.existsSync(X(e))}function Je(){return w.existsSync(Z())}function f(){let e=Z();if(!w.existsSync(e))return{...$};let t=F.load(w.readFileSync(e,"utf8"));return t.github??=$.github,t.github.accounts??={},t}function C(e){let t=ze();w.existsSync(t)||w.mkdirSync(t,{recursive:!0}),w.writeFileSync(Z(),F.dump(e,{lineWidth:120}),"utf8")}function ee(e){let t=X(e);return w.existsSync(t)?F.load(w.readFileSync(t,"utf8")):{}}function te(e,t){w.writeFileSync(X(t),F.dump(e,{lineWidth:120}),"utf8")}function Ve(e){let t=V(e);return w.existsSync(t)?F.load(w.readFileSync(t,"utf8")):{}}function Ke(e,t){w.writeFileSync(V(t),F.dump(e,{lineWidth:120}),"utf8")}function K(e){let t=f(),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"},s=t.review??$.review,a=t.providers??$.providers;return{lang:t.lang??"en",providers:{default:a.default??"claude",fallback:a.fallback??[],agents:a.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??s.max_tokens,max_findings:n.review?.max_findings??s.max_findings},github:{accountName:o,token_env:i.token_env,username:i.username,owner:n.github?.owner,repo:n.github?.repo}}}function ae(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 We(e,t){let n=f();n.github.accounts[e]=t,Object.keys(n.github.accounts).length===1&&(n.github.default_account=e),C(n)}function Ye(e){let t=f();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 Qe(e){let t=f();if(!(e in t.github.accounts))throw new Error(r().errorAccountNotFound(e));t.github.default_account=e,C(t)}function Ze(e,t){let n=f();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 $e(){let e=f();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 Xe(e,t){let n=f();n.custom_providers??={},n.custom_providers[e]=t,C(n)}function et(e){let t=f();if(!t.custom_providers?.[e])throw new Error(r().customProviderNotFound(e));delete t.custom_providers[e],C(t)}function tt(){let e=f();return Object.entries(e.custom_providers??{}).map(([t,n])=>({name:t,cfg:n,hasToken:!!process.env[n.api_key_env]}))}var nt={github:{},context:{provider:"local"}},ot={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")),Zt=new Set(["node_modules",".git","dist","build",".next",".nuxt","coverage",".aiv",".cache","vendor","__pycache__",".venv","venv"]),Xt=[["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"]],en=["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 ce(e){let t=L.basename(e),n=x.readdirSync(e),o=nn(e,n),i=on(e),s=rn(e),a=sn(e);return`# Project Context: ${t}
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
- ${an(e,n,o)}
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
- ${a.map(l=>`- ${l}`).join(`
82
+ ${s.map(l=>`- ${l}`).join(`
83
83
  `)||"- (not detected)"}
84
84
 
85
85
  ## Sensitive Zones
86
- ${s.map(l=>`- ${l}`).join(`
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 tn=[[["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 nn(e,t){let n=[];for(let[i,s]of Xt)t.some(a=>a.toLowerCase().startsWith(i.toLowerCase()))&&n.push(s);let o=L.join(e,"package.json");if(x.existsSync(o))try{let i=JSON.parse(x.readFileSync(o,"utf8")),s={...i.dependencies,...i.devDependencies};for(let[a,l]of tn)a.some(d=>d in s)&&n.push(l)}catch{}return[...new Set(n)]}function on(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(a=>{try{return x.statSync(L.join(i,a)).isDirectory()}catch{return!1}}).forEach(a=>n.push(`${o}/${a}`))}return n.slice(0,20)}function rn(e){let t=[];function n(o,i=0){if(i>3)return;let s;try{s=x.readdirSync(o)}catch{return}for(let a of s){if(Zt.has(a))continue;let l=a.toLowerCase();for(let p of en)if(l.includes(p)){let P=L.relative(e,L.join(o,a));t.push(P);break}let d=L.join(o,a);try{x.statSync(d).isDirectory()&&n(d,i+1)}catch{}}}return n(e),[...new Set(t)].slice(0,15)}function sn(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(s=>{let a=s.toLowerCase();return a.includes("auth")||a.includes("jwt")||a.includes("passport")||a.includes("crypto")||a.includes("stripe")||a.includes("paypal")||a.includes("prisma")||a.includes("typeorm")||a.includes("sequelize")||a.includes("mongoose")||a.includes("knex")||a.includes("pg")||a.includes("mysql")||a.includes("redis")||a.includes("aws-sdk")||a.includes("@aws")||a.includes("firebase")}).slice(0,15)}catch{return[]}}function an(e,t,n){let o=t.includes("src"),i=t.includes("packages"),s=t.includes("apps"),a=t.includes("modules");return i&&s?"Monorepo (apps/ + packages/ structure)":a?"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 st=u(require("fs")),H=u(require("path")),cn=new Set(["node_modules",".git","dist","build",".next",".nuxt","coverage",".aiv",".cache","vendor","__pycache__",".venv","venv",".DS_Store"]),ln=[[/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"]],dn=[[/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 le(e,t=4){return at(e,e,t)}function at(e,t,n,o=0){let i=H.basename(t),a={path:H.relative(e,t)||".",type:"directory",module_type:it(i),sensitivity:rt(i),children:[]};if(o>=n)return a;let l;try{l=st.readdirSync(t,{withFileTypes:!0})}catch{return a}for(let d of l){if(cn.has(d.name))continue;let p=H.join(t,d.name);d.isDirectory()?a.children.push(at(e,p,n,o+1)):o<n&&a.children.push({path:H.relative(e,p),type:"file",module_type:it(d.name),sensitivity:rt(d.name)})}return a.children.length>50&&(a.children=a.children.slice(0,50)),a}function it(e){for(let[t,n]of ln)if(t.test(e))return n;return"other"}function rt(e){for(let[t,n]of dn)if(t.test(e))return n;return"low"}var E=u(require("fs")),ct=u(require("path")),lt=require("child_process");function de(e){try{let t=(0,lt.execSync)("git remote get-url origin",{cwd:e,stdio:"pipe"}).toString().trim();return un(t)}catch{return null}}function un(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 dt(e){let t=ct.join(e,".gitignore"),n=".aiv/";if(!E.existsSync(t)){E.writeFileSync(t,n+`
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 pt(){return new ut.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(v.default.yellow(r().initAlreadyDone));return}if(console.log(v.default.bold(r().initTitle)),Je())console.log(v.default.dim(` \u21B3 ${r().initGlobalConfigExists}`));else{let d=(0,ne.default)(r().initWritingGlobalConfig).start();C($),d.succeed(v.default.green(r().initGlobalConfigCreated))}let n=J(t);j.existsSync(n)||j.mkdirSync(n,{recursive:!0});let o=(0,ne.default)(r().initWritingConfig).start();te(nt,t),o.succeed(v.default.green(r().initConfigCreated)),Ke(ot,t),console.log(v.default.green(` \u2714 ${r().initRulesCreated}`));let i=(0,ne.default)(r().initScanningTree).start();try{let d=await le(t);j.writeFileSync(B(t),JSON.stringify(d,null,2),"utf8"),i.succeed(v.default.green(r().initTreeCreated))}catch{i.warn(r().initTreeSkipped)}let s=(0,ne.default)(r().initBuildingContext).start();try{let d=await ce(t);j.writeFileSync(N(t),d,"utf8"),s.succeed(v.default.green(r().initContextCreated))}catch{s.warn(r().initContextSkipped),j.writeFileSync(N(t),gn(t),"utf8")}dt(t),console.log(v.default.green(` \u2714 ${r().initGitignoreUpdated}`));let a=$.claude?.api_key_env??"CLAUDE_API_KEY",l=$.github.accounts.default?.token_env??"GITHUB_TOKEN";console.log(v.default.bold.green(r().initSuccessTitle)),console.log(` ${v.default.cyan("1.")} ${r().initStep1(a)}`),console.log(` ${v.default.cyan("2.")} ${r().initStep2(l)}`),console.log(` ${v.default.cyan("3.")} ${r().initStep3}`),console.log(` ${v.default.cyan("4.")} ${r().initStep4}
100
- `),console.log(` ${r().initEditContext(v.default.cyan(".aiv/context.md"))}`),console.log(` ${r().initEditRules(v.default.cyan(".aiv/rules.yml"))}
101
- `),console.log(v.default.dim(r().initGlobalHint)),console.log(v.default.dim(r().initAddAccountHint+`
102
- `))})}function gn(e){return`# Project Context: ${gt.basename(e)}
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 Pt=require("commander"),m=u(require("chalk")),At=u(require("ora")),$t=require("table");var ue="https://api.github.com",G=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=`${ue}/repos/${t}/${n}/pulls?state=open&per_page=${Math.min(o,100)}&sort=updated&direction=desc`,s=await this.fetch(i);return s.ok||await this.throwError(s,"list PRs"),(await s.json()).map(l=>this.mapPR(l))}async getPR(t,n,o){let i=`${ue}/repos/${t}/${n}/pulls/${o}`,s=await this.fetch(i);return s.ok||await this.throwError(s,`get PR #${o}`),this.mapPR(await s.json())}async getPRFiles(t,n,o){let i=`${ue}/repos/${t}/${n}/pulls/${o}/files?per_page=100`,s=await this.fetch(i);return s.ok||await this.throwError(s,"get PR files"),(await s.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,s]=await Promise.all([this.getPR(t,n,o),this.getPRFiles(t,n,o)]),a=s.filter(l=>l.patch).map(l=>`diff --git a/${l.filename} b/${l.filename}
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:s,rawDiff:a}}async submitReview(t,n,o,i,s=""){let a=`${ue}/repos/${t}/${n}/pulls/${o}/reviews`,{default:l}=await import("node-fetch"),d=await l(a,{method:"POST",headers:{...this.headers,"Content-Type":"application/json"},body:JSON.stringify({event:i,body:s})});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 T=u(require("chalk"));async function Le(){return(await import("inquirer")).default}var mt=-1;function pn(e){let t=T.default.cyan(`#${e.number}`),n=e.title.length>52?e.title.slice(0,51)+"\u2026":e.title,o=T.default.dim(`${e.author} \xB7 ${e.branch}`),i=T.default.green(`+${e.additions}`)+T.default.dim("/")+T.default.red(`-${e.deletions}`);return`${t} ${n} ${o} ${i}`}async function ge(e,t){let n=await Le(),o=[...e.map(a=>({name:pn(a),value:a.number,short:`#${a.number}`})),new n.Separator("\u2500".repeat(62)),{name:T.default.dim("\u21A9 Cancel"),value:mt,short:"Cancel"}],s=(await n.prompt([{type:"list",name:"prNumber",message:t,choices:o,pageSize:14,loop:!1}])).prNumber;return s===mt?null:e.find(a=>a.number===s)??null}async function ft(e){let t=await Le();return(await t.prompt([{type:"list",name:"action",message:e,choices:[{name:T.default.green("\u2714 Approve PR"),value:"approve",short:"Approve"},{name:T.default.yellow("\u2691 Request Changes"),value:"request_changes",short:"Request Changes"},new t.Separator("\u2500".repeat(42)),{name:T.default.dim("\u21A9 Skip"),value:"skip",short:"Skip"}],pageSize:4,loop:!1}])).action}async function vt(e,t){return!!(await(await Le()).prompt([{type:"confirm",name:"ok",message:`${t} ${T.default.cyan(`#${e.number}`)} \u2014 ${e.title}?`,default:!0}])).ok}var kt=require("commander"),h=u(require("chalk")),W=u(require("ora"));var ie=u(require("chalk")),Rt=u(require("ora"));var bt=u(require("chalk"));var ht=u(require("@anthropic-ai/sdk")),pe=class{name="claude";model;client;constructor(t,n="claude-sonnet-4-6"){this.model=n,this.client=new ht.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(a=>({role:a.role==="system"?"user":a.role,content:a.content}))});return{content:i.content.filter(a=>a.type==="text").map(a=>a.text).join(""),inputTokens:i.usage.input_tokens,outputTokens:i.usage.output_tokens}}};var yt=u(require("openai")),oe=class{name;model;client;constructor(t,n="gpt-4.1",o,i="openai"){this.name=i,this.model=n,this.client=new yt.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(a=>({role:a.role,content:a.content})));let s=await this.client.chat.completions.create({model:this.model,max_tokens:o,messages:i});return{content:s.choices[0]?.message?.content??"",inputTokens:s.usage?.prompt_tokens,outputTokens:s.usage?.completion_tokens}}};var mn="https://generativelanguage.googleapis.com/v1beta/models",me=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 s={contents:t.filter(k=>k.role!=="system").map(k=>({role:k.role==="assistant"?"model":"user",parts:[{text:k.content}]})),generationConfig:{maxOutputTokens:o}};n&&(s.systemInstruction={parts:[{text:n}]});let a=`${mn}/${this.model}:generateContent?key=${this.apiKey}`,{default:l}=await import("node-fetch"),d=await l(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!d.ok){let k=await d.json(),y=d.status,Se=k?.error?.message??"unknown error";throw Object.assign(new Error(`Gemini API error (${y}): ${Se}`),{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 fe=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 ve=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 s=await this.chain[i].complete(t,n,o);return this.index=i,s}catch(s){if(!fn(s)||i+1>=this.chain.length)throw s;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 fn(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 he(e,t){let n=e.providers.agents[t],o=n?vn(e,n):Te(e,e.providers.default),i=e.providers.fallback;if(i.length===0)return o;let s=[o];for(let a of i){let l=hn(e,a);l&&!yn(l,o)&&s.push(l)}return s.length===1?o:new ve(s,(a,l)=>{console.log(bt.default.yellow(`
127
- \u26A1 ${a} quota/rate limit \u2014 switching to ${l}`))})}function vn(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 pe(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 oe(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 me(i,n??e.gemini.model)}if(t==="mock")return new fe;let o=e.custom_providers[t];if(o){let i=o.api_key_env?process.env[o.api_key_env]??"":"";return new oe(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 hn(e,t){try{return Te(e,t)}catch{return null}}function yn(e,t){return e.name===t.name&&e.model===t.model}var M=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(`
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
- `);return`## PR: #${t.diff.pr.number} \u2014 ${t.diff.pr.title}
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.`}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 ye=class extends M{agentName="business";systemPrompt=`You are a senior business analyst and domain expert performing a code review.
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 be=class extends M{agentName="architecture";systemPrompt=`You are a principal software architect performing a code review.
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 M{agentName="security";systemPrompt=`You are a senior application security engineer performing a code review.
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 we=class{constructor(t,n){this.config=t;this.rules=n}config;rules;async run(t,n,o){let i=bn(o,this.config),s={diff:t,projectContext:n,rules:this.rules},a=await Rn(i,s),l=wn(a.map(p=>p.riskScore)),d=a.flatMap(p=>p.possibleRegressions??[]);return{prNumber:t.pr.number,prTitle:t.pr.title,executiveSummary:xn(a,l),riskScore:l,riskLabel:wt(l),agents:a,businessRisks:a.find(p=>p.agentName==="business")?.findings??[],architectureIssues:a.find(p=>p.agentName==="architecture")?.findings??[],securityIssues:a.find(p=>p.agentName==="security")?.findings??[],possibleRegressions:d,generatedAt:new Date().toISOString()}}};function bn(e,t){let n={business:o=>new ye(o),architecture:o=>new be(o),security:o=>new Re(o)};return e.filter(o=>o in n).map(o=>n[o](he(t,o)))}async function Rn(e,t){return(await Promise.allSettled(e.map(async o=>{let i=(0,Rt.default)(r().orchestratorRunning(ie.default.cyan(o.agentName))).start();try{let s=await o.run(t);return i.succeed(r().orchestratorDone(ie.default.cyan(o.agentName),s.findings.length,s.riskScore)),s}catch(s){return i.fail(ie.default.red(r().orchestratorAgentFailed(ie.default.cyan(o.agentName),s.message))),{agentName:o.agentName,findings:[],summary:r().orchestratorFailedMsg(s.message),riskScore:0}}}))).map(o=>o.status==="fulfilled"?o.value:{agentName:"unknown",findings:[],summary:r().orchestratorFailedUnexpected,riskScore:0})}function wn(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 xn(e,t){let n=wt(t),o=e.reduce((a,l)=>a+l.findings.length,0),i=e.flatMap(a=>a.findings).filter(a=>a.severity==="critical"||a.severity==="high").length,s=i>0?r().orchestratorCriticalFound(i):r().orchestratorNoCritical;return[r().orchestratorOverallRisk(n,t,o,e.length),s,"",...e.map(a=>`[${a.agentName.toUpperCase()}] ${a.summary}`)].join(`
242
- `)}function wt(e){return e>=80?"CRITICAL":e>=60?"HIGH":e>=30?"MEDIUM":"LOW"}var _=u(require("fs"));async function Ce(e){let t=!1,n=!1;try{let o=await le(e);_.writeFileSync(B(e),JSON.stringify(o,null,2),"utf8"),t=!0}catch{}try{let o=await ce(e);_.writeFileSync(N(e),o,"utf8"),n=!0}catch{}return{treeOk:t,contextOk:n}}var xe=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=B(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(a=>a.filename)),s=o?Cn(o,i):"";return[n,s?`
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
- ${s}
247
+ ${a}
246
248
  \`\`\``:""].filter(Boolean).join(`
247
- `)}};function Cn(e,t){let o=(e.children??[]).filter(i=>{let s=i.path??"";return Array.from(t).some(a=>a.startsWith(s))||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 Ct(e){let t=r(),o=xt(e.riskLabel)(`${e.riskScore}/100 [${e.riskLabel}]`);console.log(`
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(_e(e.executiveSummary,2)),e.securityIssues.length>0&&(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 a=xt(Sn(i.riskScore))(`[${i.riskScore}/100]`);console.log(`
256
- ${g.default.bold(i.agentName.toUpperCase())} ${a}`),console.log(_e(i.summary,2))}),console.log(`
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=kn(n.severity),i=n.file?g.default.dim(` \u2192 ${n.file}`):"";console.log(`
259
- ${o} ${g.default.bold(n.title)}${i}`),console.log(_e(n.description,2)),n.suggestion&&console.log(` ${g.default.dim(t.renderSuggestion)} ${n.suggestion}`)})}function kn(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 xt(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 Sn(e){return e>=80?"CRITICAL":e>=60?"HIGH":e>=30?"MEDIUM":"LOW"}function _e(e,t){let n=" ".repeat(t);return e.split(`
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 Ie(e){let{prNumber:t,owner:n,repo:o,config:i,token:s}=e,a=Ve(),l=e.agents??["business","architecture","security"];console.log(h.default.bold(r().reviewTitle(t))),console.log(h.default.dim(` ${r().reviewAccount(i.github.accountName,i.github.token_env)}
262
- `));let d=(0,W.default)(r().reviewFetching(t,`${n}/${o}`)).start(),p;try{p=await new G(s).getPRDiff(n,o,t),d.succeed(r().reviewLoaded(h.default.cyan(p.pr.title),p.files.length))}catch(A){d.fail(h.default.red(r().reviewFetchFailed(A.message)));return}let P=(0,W.default)(r().reviewLoadingContext).start(),k=new xe(process.cwd()).buildReviewContext(p);P.succeed(r().reviewContextLoaded),console.log(h.default.dim(r().reviewRunningAgents(l.join(", "))));try{let A=await new we(i,a).run(p,k,l);if(e.json){console.log(JSON.stringify(A,null,2));return}Ct(A)}catch(A){console.log(h.default.red(r().reviewFailed(A.message)));return}if(!process.stdout.isTTY)return;let y=await ft(r().postReviewSelectAction);if(y==="skip")return;let Se=y==="approve"?"APPROVE":"REQUEST_CHANGES",Pe=(0,W.default)(r().postReviewSubmitting).start();try{await new G(s).submitReview(n,o,t,Se),y==="approve"?Pe.succeed(h.default.green(r().postReviewApproved(t))):Pe.succeed(h.default.yellow(r().postReviewChangesRequested(t)))}catch(A){Pe.fail(h.default.red(r().postReviewFailed(A.message)));return}if(y==="approve"){let A=(0,W.default)(r().postReviewRefreshing).start();await Ce(process.cwd()),A.succeed(h.default.green(r().postReviewRefreshed))}}function St(){return new kt.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(h.default.red(r().notInitialized));return}let n=K(),o;try{o=ae(n)}catch{console.log(h.default.red(r().prsMissingToken(n.github.token_env)));return}let i=de(process.cwd()),s=t.owner??n.github.owner??i?.owner,a=t.repo??n.github.repo??i?.repo;if(!s||!a){console.log(h.default.red(r().repoNotDetected));return}let l;if(e===void 0){let d=(0,W.default)(r().prsFetching(h.default.cyan(`${s}/${a}`))).start(),p;try{p=await new G(o).listPRs(s,a,30),d.stop()}catch(k){d.fail(h.default.red(r().prsFailed(k.message)));return}if(p.length===0){console.log(h.default.yellow(r().prsNoneFound));return}let P=await ge(p,r().selectorSelectPR);if(!P){console.log(h.default.dim(r().selectorCancelled));return}l=P.number}else if(l=Number.parseInt(e),Number.isNaN(l)){console.log(h.default.red(r().invalidPrNumber));return}await Ie({prNumber:l,owner:s,repo:a,config:n,token:o,agents:t.agent,json:t.json})})}function Lt(){return new Pt.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=K(),n;try{n=ae(t)}catch{console.log(m.default.red(r().prsMissingToken(t.github.token_env)));return}let o=de(process.cwd()),i=e.owner??t.github.owner??o?.owner,s=e.repo??t.github.repo??o?.repo;if(!i||!s){console.log(m.default.red(r().repoNotDetected));return}let a=(0,At.default)(r().prsFetching(m.default.cyan(`${i}/${s}`))).start(),l;try{l=await new G(n).listPRs(i,s,Number.parseInt(e.limit)),a.stop()}catch(y){a.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}`),Pn(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(An(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(`
263
- `+(0,$t.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)}
264
- `)),e.select===!1||!process.stdout.isTTY)return;let P=await ge(l,r().selectorSelectPR);if(!P){console.log(m.default.dim(r().selectorCancelled));return}if(!await vt(P,r().selectorConfirmReview)){console.log(m.default.dim(r().selectorCancelled));return}await Ie({prNumber:P.number,owner:i,repo:s,config:t,token:n})})}function Pn(e,t){return e.length>t?e.slice(0,t-1)+"\u2026":e}function An(e){return new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"2-digit"})}var It=require("commander"),q=u(require("fs")),R=u(require("chalk")),ke=u(require("ora"));var S=u(require("fs")),D=u(require("path"));var $n=new Set(["node_modules",".git","dist","build",".aiv",".next","coverage"]),Ln=/\.(ts|js|tsx|jsx|py|go|java|rb|php|cs|rs)$/;async function Tt(e,t){let n=D.basename(e),o=B(e),i=S.existsSync(o)?S.readFileSync(o,"utf8").slice(0,6e3):"(tree not available \u2014 run aiv context refresh first)",s=Nn(e),a=_n(e),l=`
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
- ${s}
270
+ ${a}
269
271
 
270
272
  Project file tree (tree.json):
271
273
  ${i}
272
274
 
273
275
  Sample source files:
274
- ${a}
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 Tn(p.content)}function Tn(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: []
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 Nn(e){let t=D.join(e,"package.json");if(!S.existsSync(t))return"(not found)";try{let n=JSON.parse(S.readFileSync(t,"utf8")),o=Object.keys({...n.dependencies,...n.devDependencies}).slice(0,30);return`name: ${n.name??"?"}
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 _n(e){let t=["src","app","lib","packages","modules"].map(o=>D.join(e,o)).filter(o=>{try{return S.statSync(o).isDirectory()}catch{return!1}}),n=[];for(let o of t.slice(0,2))if(Nt(o,n),n.length>=12)break;return n.slice(0,12).map(o=>{let i=D.relative(e,o),s=S.readFileSync(o,"utf8").slice(0,400);return`--- ${i} ---
307
- ${s}`}).join(`
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 Nt(e,t,n=0){if(n>3||t.length>=12)return;let o;try{o=S.readdirSync(e)}catch{return}for(let i of o){if($n.has(i))continue;let s=D.join(e,i);try{let a=S.statSync(s);a.isFile()&&Ln.test(i)?t.push(s):a.isDirectory()&&Nt(s,t,n+1)}catch{}if(t.length>=12)break}}function Ft(){let e=new It.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(R.default.red(r().notInitialized));return}console.log(R.default.bold(r().contextRefreshTitle));let t=(0,ke.default)(r().contextScanningTree).start(),n=(0,ke.default)(r().contextRebuildingCtx),{treeOk:o,contextOk:i}=await Ce(process.cwd());o?t.succeed(R.default.green(r().contextTreeUpdated)):t.fail(r().contextTreeFailed("scan failed")),n.start(),i?n.succeed(R.default.green(r().contextCtxUpdated)):n.fail(r().contextCtxFailed("analysis failed")),console.log(R.default.dim(r().contextEditHint(R.default.cyan(".aiv/context.md"))))}),e.command("show").description("Show current context.md contents").action(()=>{if(!b()){console.log(R.default.red(r().notInitialized));return}let t=N();if(!q.existsSync(t)){console.log(R.default.yellow(r().contextNoFile));return}console.log(`
310
- `+q.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(R.default.red(r().notInitialized));return}let n=K(),o;try{o=he(n,"context")}catch(d){console.log(R.default.red(r().contextGenerateProviderError(d.message)));return}console.log(R.default.bold(r().contextGenerateTitle));let i=(0,ke.default)(r().contextGenerating).start(),s;try{s=await Tt(process.cwd(),o),i.succeed(R.default.green(r().contextGenerateDone))}catch(d){i.fail(R.default.red(r().contextGenerateFailed(d.message)));return}let a=!t.rulesOnly,l=!t.contextOnly;a&&await _t(N(),s.context,".aiv/context.md",t.force),l&&await _t(V(),s.rules,".aiv/rules.yml",t.force),console.log(R.default.dim(r().contextEditHint(R.default.cyan(".aiv/context.md"))))}),e}async function _t(e,t,n,o){if(q.existsSync(e)&&!o){let{default:i}=await import("inquirer"),{ok:s}=await i.prompt([{type:"confirm",name:"ok",message:r().contextGenerateConfirmOverwrite(n),default:!0}]);if(!s){console.log(R.default.dim(r().contextGenerateSkipped(n)));return}}q.writeFileSync(e,t,"utf8"),console.log(R.default.green(r().contextGenerateWritten(n)))}var Et=require("commander"),z=u(require("fs")),c=u(require("chalk")),Fe=require("table");function jt(){let e=new Et.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
+ `).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(z.existsSync(n)?z.readFileSync(n,"utf8"):c.default.dim(t.configNotCreated)),b()&&(console.log(c.default.bold(t.configRepoConfigTitle)+`
313
- `),console.log(z.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=f();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)?In(t,n):Fn(t,n)}),e.command("remove-provider <name>").description("Remove a custom provider from global config").action(t=>{try{et(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=f(),o=tt();console.log(c.default.bold(`
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 s=n[i],a=n.providers.default===i?c.default.green(" \u2714 default"):"";s&&console.log(` ${c.default.cyan(i.padEnd(8))} model=${c.default.dim(s.model)} key_env=${c.default.dim(s.api_key_env)}${a}`)}if(console.log(c.default.bold(t.customProviderTitle)),o.length===0)console.log(c.default.dim(t.customProviderNone));else{let i=o.map(({name:a,cfg:l,hasToken:d})=>[n.providers.default===a?c.default.green(a):a,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")]),s=[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)([s,...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 (${Ue.join(" | ")})`).action(t=>{if(!re(t)){console.log(c.default.red(r().configInvalidLang));return}let n=f();n.lang=t,C(n),Oe(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(`
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=V();console.log(z.existsSync(n)?z.readFileSync(n,"utf8"):c.default.yellow(t.configNoRules))}),e.command("accounts").description("List GitHub accounts in global config").action(()=>{let t=r(),n=$e();if(console.log(c.default.bold(t.accountsTitle)),n.length===0){console.log(c.default.yellow(t.accountsNone));return}let o=n.map(({name:s,account:a,isDefault:l,hasToken:d})=>[l?c.default.green(s):s,l?c.default.green(t.accountsDefaultMark):"",a.username?c.default.dim(a.username):c.default.dim("\u2014"),c.default.dim(a.token_env),d?c.default.green(t.accountsTokenFound):c.default.red(t.accountsTokenMissing),c.default.dim(a.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 s=ee().github?.account;s&&console.log(c.default.dim(t.configRepoAccountHint(s)))}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($e().find(s=>s.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};We(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{Ye(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{Qe(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{Ze(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 s=f();if(s.providers.agents??={},o.clear){delete s.providers.agents[t],C(s),console.log(c.default.green(r().configAgentProviderSet(t,"(default)")));return}if(!n){console.log(c.default.red(r().configInvalidProviderSpec("")));return}let a=new Set(["claude","openai","gemini","mock"]),l=n.split("/")[0],d=Object.keys(f().custom_providers??{});if(!a.has(l)&&!d.includes(l)){console.log(c.default.red(r().configInvalidProviderSpec(n)));return}s.providers.agents[t]=n,C(s),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=f(),o=new Set(["claude","openai","gemini","mock",...Object.keys(n.custom_providers??{})]),i=t.filter(a=>!o.has(a));if(i.length>0){console.log(c.default.red(r().configInvalidProviderSpec(i.join(", "))));return}let s=n;s.providers.fallback=t,C(s),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=f(),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[s,a]of Object.entries(o))console.log(` ${c.default.cyan(s.padEnd(14))} ${a}`);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 In(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=f(),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 Fn(e,t){if(!t.baseUrl){console.log(c.default.red(r().customProviderBaseUrlRequired));return}if(f().custom_providers?.[e]&&!t.force){console.log(c.default.yellow(r().customProviderAlreadyExists(e)));return}Xe(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 Gt=require("commander"),O=u(require("chalk")),Mt=require("table");function Dt(){return new Gt.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,Mt.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}
318
- `))})}De();He();var U=new Ot.Command;U.name("aiv").description("AI-powered PR reviewer \u2014 local-first, multi-agent semantic analysis").version("0.1.0");U.addCommand(pt());U.addCommand(Lt());U.addCommand(St());U.addCommand(Ft());U.addCommand(jt());U.addCommand(Dt());U.parse(process.argv);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateriss_/aiv-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "AI-powered PR reviewer CLI — local-first, multi-agent semantic analysis",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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));
@@ -43,6 +43,7 @@ export async function selectPR(prs: PullRequest[], message: string): Promise<Pul
43
43
  }
44
44
 
45
45
  export type PostReviewAction = 'approve' | 'request_changes' | 'skip';
46
+ export type MergeStrategy = 'merge' | 'squash' | 'rebase';
46
47
 
47
48
  export async function selectPostReviewAction(message: string): Promise<PostReviewAction> {
48
49
  const inquirer = await getInquirer();
@@ -64,6 +65,34 @@ export async function selectPostReviewAction(message: string): Promise<PostRevie
64
65
  return answers['action'] as PostReviewAction;
65
66
  }
66
67
 
68
+ export async function confirmMerge(message: string): Promise<boolean> {
69
+ const inquirer = await getInquirer();
70
+ const answers = await inquirer.prompt([{
71
+ type: 'confirm',
72
+ name: 'ok',
73
+ message,
74
+ default: false,
75
+ }]);
76
+ return Boolean(answers['ok']);
77
+ }
78
+
79
+ export async function selectMergeStrategy(message: string): Promise<MergeStrategy> {
80
+ const inquirer = await getInquirer();
81
+ const answers = await inquirer.prompt([{
82
+ type: 'list',
83
+ name: 'strategy',
84
+ message,
85
+ choices: [
86
+ { name: chalk.cyan('Squash and merge'), value: 'squash', short: 'Squash' },
87
+ { name: 'Merge commit', value: 'merge', short: 'Merge' },
88
+ { name: chalk.dim('Rebase and merge'), value: 'rebase', short: 'Rebase' },
89
+ ],
90
+ pageSize: 3,
91
+ loop: false,
92
+ }]);
93
+ return answers['strategy'] as MergeStrategy;
94
+ }
95
+
67
96
  export async function confirmReview(pr: PullRequest, label: string): Promise<boolean> {
68
97
  const inquirer = await getInquirer();
69
98
 
package/src/git/github.ts CHANGED
@@ -59,6 +59,22 @@ export class GithubClient {
59
59
  return { pr, files, rawDiff };
60
60
  }
61
61
 
62
+ async mergePR(
63
+ owner: string,
64
+ repo: string,
65
+ prNumber: number,
66
+ mergeMethod: 'merge' | 'squash' | 'rebase' = 'squash',
67
+ ): Promise<void> {
68
+ const url = `${GITHUB_API}/repos/${owner}/${repo}/pulls/${prNumber}/merge`;
69
+ const { default: fetch } = await import('node-fetch');
70
+ const res = await fetch(url, {
71
+ method: 'PUT',
72
+ headers: { ...this.headers, 'Content-Type': 'application/json' },
73
+ body: JSON.stringify({ merge_method: mergeMethod }),
74
+ }) as unknown as Response;
75
+ if (!res.ok) await this.throwError(res, `merge PR #${prNumber}`);
76
+ }
77
+
62
78
  async submitReview(
63
79
  owner: string,
64
80
  repo: string,
package/src/i18n/en.ts CHANGED
@@ -175,6 +175,14 @@ export const en = {
175
175
  postReviewFailed: (msg: string) => `Failed to submit review: ${msg}`,
176
176
  postReviewRefreshing: 'Refreshing project context...',
177
177
  postReviewRefreshed: 'Context updated.',
178
+ postReviewMergeConfirm: 'Merge this PR now?',
179
+ postReviewSelectMerge: 'Select merge strategy:',
180
+ postReviewMerging: (n: number) => `Merging PR #${n}...`,
181
+ postReviewMerged: (n: number) => ` PR #${n} merged.`,
182
+ postReviewMergeFailed: (msg: string) => `Failed to merge: ${msg}`,
183
+ postReviewMergeStrategyMerge: 'Merge commit',
184
+ postReviewMergeStrategySquash: 'Squash and merge',
185
+ postReviewMergeStrategyRebase: 'Rebase and merge',
178
186
 
179
187
  // ── context generate ───────────────────────────────────────────────────────
180
188
  contextGenerateTitle: '\n Generating context and rules with AI...\n',
package/src/i18n/es.ts CHANGED
@@ -177,6 +177,14 @@ export const es: TranslationKeys = {
177
177
  postReviewFailed: (msg: string) => `Error al enviar la revisión: ${msg}`,
178
178
  postReviewRefreshing: 'Actualizando contexto del proyecto...',
179
179
  postReviewRefreshed: 'Contexto actualizado.',
180
+ postReviewMergeConfirm: '¿Hacer merge de este PR ahora?',
181
+ postReviewSelectMerge: 'Selecciona la estrategia de merge:',
182
+ postReviewMerging: (n: number) => `Haciendo merge del PR #${n}...`,
183
+ postReviewMerged: (n: number) => ` PR #${n} mergeado.`,
184
+ postReviewMergeFailed: (msg: string) => `Error al hacer merge: ${msg}`,
185
+ postReviewMergeStrategyMerge: 'Merge commit',
186
+ postReviewMergeStrategySquash: 'Squash and merge',
187
+ postReviewMergeStrategyRebase: 'Rebase and merge',
180
188
 
181
189
  // ── context generate ───────────────────────────────────────────────────────
182
190
  contextGenerateTitle: '\n Generando contexto y reglas con IA...\n',