@ateriss_/aiv-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,318 @@
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:"0.1.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
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
+ Initialized! Next steps:
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.
6
+ `,prsFetching:e=>`Fetching PRs for ${e}...`,prsNoneFound:`
7
+ No open pull requests found.
8
+ `,prsColPR:"PR",prsColTitle:"Title",prsColAuthor:"Author",prsColBranch:"Branch",prsColChanges:"Changes",prsColCreated:"Created",prsFooter:e=>` Showing ${e} open PR(s). Run aiv review <pr-number> to analyze.
9
+ `,prsMissingToken:e=>`Missing env var: ${e}. Set your GitHub token.`,prsFailed:e=>`Failed: ${e}`,reviewTitle:e=>`
10
+ aiv review \u2014 PR #${e}
11
+ `,reviewFetching:(e,t)=>`Fetching PR #${e} from ${t}...`,reviewLoaded:(e,t)=>`PR loaded: ${e} (${t} files)`,reviewFetchFailed:e=>`Failed to fetch PR: ${e}`,reviewLoadingContext:"Loading project context...",reviewContextLoaded:"Context loaded",reviewRunningAgents:e=>`
12
+ Running agents: ${e}
13
+ `,reviewFailed:e=>`
14
+ Review failed: ${e}
15
+ `,reviewAccount:(e,t)=>`Account: ${e} (${t})`,contextRefreshTitle:`
16
+ Refreshing context...
17
+ `,contextScanningTree:"Scanning project structure...",contextTreeUpdated:"tree.json updated",contextTreeFailed:e=>`tree.json failed: ${e}`,contextRebuildingCtx:"Rebuilding context.md...",contextCtxUpdated:"context.md updated",contextCtxFailed:e=>`context.md failed: ${e}`,contextEditHint:e=>`
18
+ Edit ${e} to add custom context.
19
+ `,contextNoFile:"No context.md found. Run `aiv context refresh`.",configGlobalTitle:" ~/.aiv/config.yml (global)",configNotCreated:" (not created yet)",configRepoConfigTitle:" .aiv/config.yml (repo)",configRulesTitle:" .aiv/rules.yml",configProviderSet:e=>` Default provider set to: ${e}`,configRepoSet:(e,t)=>` Repo set to: ${e}/${t}`,configNoRules:" No rules.yml found.",configLangSet:e=>` Language set to: ${e}`,configInvalidLang:"Invalid language. Choose: en, es",configTokenEnvHint:e=>` Token env var: ${e}`,configUsernameHint:e=>` Username: ${e}`,configRepoAccountHint:e=>` Repo account: ${e}`,configSavedToRepo:" Saved to .aiv/config.yml",accountsTitle:`
20
+ GitHub Accounts
21
+ `,accountsNone:" No accounts configured. Add one with: aiv config add-account <name>",accountsColName:"Name",accountsColUser:"Username",accountsColEnvVar:"Token Env Var",accountsColToken:"Token",accountsColDesc:"Description",accountsColDefault:"Default",accountsTokenFound:"found",accountsTokenMissing:"missing",accountsDefaultMark:"\u2714 default",accountsAdded:e=>` Account "${e}" added to global config.`,accountsRemoved:e=>` Account "${e}" removed.`,accountsDefaultSet:e=>` Default account set to: ${e}`,accountsRepoSet:e=>` This repo will use account: ${e}`,accountsNotFound:e=>` Account "${e}" not found.`,accountsAlreadyExists:e=>` Account "${e}" already exists. Use --force to overwrite.`,accountsGlobalHint:`
22
+ Global config: ~/.aiv/config.yml`,accountsRepoHint:` Repo config: .aiv/config.yml
23
+ `,orchestratorRunning:e=>` Running ${e} agent...`,orchestratorDone:(e,t,n)=>` ${e} \u2014 ${t} finding(s) [score: ${n}]`,orchestratorAgentFailed:(e,t)=>` ${e} failed: ${t}`,orchestratorFailedMsg:e=>`Agent failed: ${e}`,orchestratorFailedUnexpected:"Agent failed unexpectedly.",orchestratorOverallRisk:(e,t,n,o)=>`Overall risk: ${e} (${t}/100). ${n} finding(s) across ${o} agent(s).`,orchestratorCriticalFound:e=>`${e} high/critical issue(s) require attention.`,orchestratorNoCritical:"No critical issues detected.",renderRiskScore:"Risk Score:",renderGenerated:"Generated:",renderExecutiveSummary:" Executive Summary",renderSecurityIssues:" Security Issues",renderBusinessRisks:" Business Risks",renderArchitectureIssues:" Architecture Issues",renderRegressions:" Possible Regressions",renderAgentSummaries:" Agent Summaries",renderReviewTitle:(e,t)=>` Review: PR #${e} \u2014 ${t}`,renderSuggestion:"Suggestion:",severityCritical:"CRITICAL",severityHigh:"HIGH",severityMedium:"MEDIUM",severityLow:"LOW",severityInfo:"INFO",errorMissingToken:(e,t)=>`Missing env var: ${e} (account: ${t})`,errorAccountNotFound:e=>`Account "${e}" not found.`,errorAccountNotFoundGlobal:e=>`Account "${e}" not found in global config. Add it first with: aiv config add-account ${e}`,customProviderAdded:e=>` Provider "${e}" added to global config.`,customProviderRemoved:e=>` Provider "${e}" removed.`,customProviderNotFound:e=>` Provider "${e}" not found in custom_providers.`,customProviderAlreadyExists:e=>` Provider "${e}" already exists. Use --force to overwrite.`,customProviderBaseUrlRequired:" --base-url is required for custom (non-built-in) providers.",customProviderTitle:`
24
+ Custom Providers (OpenAI-compatible)
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
+ 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:`
28
+ Generating context and rules with AI...
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
+ aiv \u2014 Available Agents
31
+ `,agentsColAgent:"Agent",agentsColDesc:"Description",agentsColFocus:"Focus Areas",agentsFooter:"Run specific agents with: aiv review <pr> --agent business security",agentBusinessDesc:"Analyzes business logic, domain rules, and functional correctness",agentBusinessFocus:"Business logic, domain invariants, rule violations, functional regressions",agentArchDesc:"Reviews structural patterns, module coupling, and design decisions",agentArchFocus:"Layer violations, coupling, SRP, dependency direction, abstraction quality",agentSecDesc:"Detects security vulnerabilities and data exposure risks",agentSecFocus:"Auth bypass, injection, data leakage, OWASP Top 10, sensitive data handling"};var je={notInitialized:"No inicializado. Ejecuta `aiv init` primero.",invalidProvider:"Proveedor inv\xE1lido. Elige: claude, openai, mock",invalidPrNumber:"N\xFAmero de PR inv\xE1lido.",repoNotDetected:"No se pudo detectar el repositorio. Usa --owner y --repo o config\xFAralo en .aiv/config.yml",initAlreadyDone:"aiv ya est\xE1 inicializado. Usa --force para reinicializar.",initTitle:` aiv \u2014 Revisor de PRs con IA
32
+ `,initWritingGlobalConfig:"Escribiendo config global (~/.aiv/config.yml)...",initGlobalConfigCreated:"Config global creada (~/.aiv/config.yml)",initGlobalConfigExists:"La config global ya existe (~/.aiv/config.yml)",initWritingConfig:"Escribiendo config del repo (.aiv/config.yml)...",initConfigCreated:"Config del repo creada (.aiv/config.yml)",initRulesCreated:"rules.yml creado",initScanningTree:"Escaneando estructura del proyecto...",initTreeCreated:"tree.json creado",initTreeSkipped:"tree.json omitido (no se pudo escanear)",initBuildingContext:"Construyendo contexto del proyecto...",initContextCreated:"context.md creado",initContextSkipped:"context.md omitido (no se pudo analizar)",initGitignoreUpdated:".gitignore actualizado",initSuccessTitle:`
33
+ \xA1Inicializado! Pr\xF3ximos pasos:
34
+ `,initStep1:e=>` Configura tu API key: export ${e}=tu-clave`,initStep2:e=>` Configura GitHub token: export ${e}=tu-token`,initStep3:" Listar PRs: aiv prs",initStep4:" Revisar un PR: aiv review <numero-pr>",initEditContext:e=>` Edita ${e} para agregar contexto de negocio.`,initEditRules:e=>` Edita ${e} para definir tus reglas.`,initGlobalHint:" Config global: ~/.aiv/config.yml",initAddAccountHint:" Agregar cuentas: aiv config add-account <nombre> --token-env <VAR>",selectorSelectPR:"Selecciona un PR para revisar:",selectorConfirmReview:"Revisar",selectorCancelled:` Cancelado.
35
+ `,prsFetching:e=>`Obteniendo PRs de ${e}...`,prsNoneFound:`
36
+ No se encontraron pull requests abiertos.
37
+ `,prsColPR:"PR",prsColTitle:"T\xEDtulo",prsColAuthor:"Autor",prsColBranch:"Rama",prsColChanges:"Cambios",prsColCreated:"Creado",prsFooter:e=>` Mostrando ${e} PR(s) abierto(s). Ejecuta aiv review <numero-pr> para analizar.
38
+ `,prsMissingToken:e=>`Variable de entorno faltante: ${e}. Configura tu GitHub token.`,prsFailed:e=>`Error: ${e}`,reviewTitle:e=>`
39
+ aiv review \u2014 PR #${e}
40
+ `,reviewFetching:(e,t)=>`Obteniendo PR #${e} de ${t}...`,reviewLoaded:(e,t)=>`PR cargado: ${e} (${t} archivos)`,reviewFetchFailed:e=>`Error al obtener el PR: ${e}`,reviewLoadingContext:"Cargando contexto del proyecto...",reviewContextLoaded:"Contexto cargado",reviewRunningAgents:e=>`
41
+ Ejecutando agentes: ${e}
42
+ `,reviewFailed:e=>`
43
+ Revisi\xF3n fallida: ${e}
44
+ `,reviewAccount:(e,t)=>`Cuenta: ${e} (${t})`,contextRefreshTitle:`
45
+ Actualizando contexto...
46
+ `,contextScanningTree:"Escaneando estructura del proyecto...",contextTreeUpdated:"tree.json actualizado",contextTreeFailed:e=>`tree.json fall\xF3: ${e}`,contextRebuildingCtx:"Reconstruyendo context.md...",contextCtxUpdated:"context.md actualizado",contextCtxFailed:e=>`context.md fall\xF3: ${e}`,contextEditHint:e=>`
47
+ Edita ${e} para agregar contexto personalizado.
48
+ `,contextNoFile:"No se encontr\xF3 context.md. Ejecuta `aiv context refresh`.",configGlobalTitle:" ~/.aiv/config.yml (global)",configNotCreated:" (a\xFAn no creado)",configRepoConfigTitle:" .aiv/config.yml (repo)",configRulesTitle:" .aiv/rules.yml",configProviderSet:e=>` Proveedor predeterminado: ${e}`,configRepoSet:(e,t)=>` Repositorio configurado: ${e}/${t}`,configNoRules:" No se encontr\xF3 rules.yml.",configLangSet:e=>` Idioma configurado: ${e}`,configInvalidLang:"Idioma inv\xE1lido. Elige: en, es",configTokenEnvHint:e=>` Variable de token: ${e}`,configUsernameHint:e=>` Usuario: ${e}`,configRepoAccountHint:e=>` Cuenta del repo: ${e}`,configSavedToRepo:" Guardado en .aiv/config.yml",accountsTitle:`
49
+ Cuentas de GitHub
50
+ `,accountsNone:" Sin cuentas configuradas. Agrega una con: aiv config add-account <nombre>",accountsColName:"Nombre",accountsColUser:"Usuario",accountsColEnvVar:"Variable Token",accountsColToken:"Token",accountsColDesc:"Descripci\xF3n",accountsColDefault:"Default",accountsTokenFound:"encontrado",accountsTokenMissing:"no encontrado",accountsDefaultMark:"\u2714 default",accountsAdded:e=>` Cuenta "${e}" agregada a la config global.`,accountsRemoved:e=>` Cuenta "${e}" eliminada.`,accountsDefaultSet:e=>` Cuenta predeterminada: ${e}`,accountsRepoSet:e=>` Este repo usar\xE1 la cuenta: ${e}`,accountsNotFound:e=>` Cuenta "${e}" no encontrada.`,accountsAlreadyExists:e=>` La cuenta "${e}" ya existe. Usa --force para sobreescribir.`,accountsGlobalHint:`
51
+ Config global: ~/.aiv/config.yml`,accountsRepoHint:` Config repo: .aiv/config.yml
52
+ `,orchestratorRunning:e=>` Ejecutando agente ${e}...`,orchestratorDone:(e,t,n)=>` ${e} \u2014 ${t} hallazgo(s) [puntuaci\xF3n: ${n}]`,orchestratorAgentFailed:(e,t)=>` ${e} fall\xF3: ${t}`,orchestratorFailedMsg:e=>`Agente fall\xF3: ${e}`,orchestratorFailedUnexpected:"El agente fall\xF3 inesperadamente.",orchestratorOverallRisk:(e,t,n,o)=>`Riesgo general: ${e} (${t}/100). ${n} hallazgo(s) en ${o} agente(s).`,orchestratorCriticalFound:e=>`${e} problema(s) de nivel alto/cr\xEDtico requieren atenci\xF3n.`,orchestratorNoCritical:"No se detectaron problemas cr\xEDticos.",renderRiskScore:"Riesgo:",renderGenerated:"Generado:",renderExecutiveSummary:" Resumen Ejecutivo",renderSecurityIssues:" Problemas de Seguridad",renderBusinessRisks:" Riesgos de Negocio",renderArchitectureIssues:" Problemas Arquitect\xF3nicos",renderRegressions:" Posibles Regresiones",renderAgentSummaries:" Res\xFAmenes por Agente",renderReviewTitle:(e,t)=>` Revisi\xF3n: PR #${e} \u2014 ${t}`,renderSuggestion:"Sugerencia:",severityCritical:"CR\xCDTICO",severityHigh:"ALTO",severityMedium:"MEDIO",severityLow:"BAJO",severityInfo:"INFO",errorMissingToken:(e,t)=>`Variable de entorno faltante: ${e} (cuenta: ${t})`,errorAccountNotFound:e=>`Cuenta "${e}" no encontrada.`,errorAccountNotFoundGlobal:e=>`Cuenta "${e}" no encontrada en la config global. Agr\xE9gala primero con: aiv config add-account ${e}`,customProviderAdded:e=>` Provider "${e}" agregado a la config global.`,customProviderRemoved:e=>` Provider "${e}" eliminado.`,customProviderNotFound:e=>` Provider "${e}" no encontrado en custom_providers.`,customProviderAlreadyExists:e=>` Provider "${e}" ya existe. Usa --force para sobreescribir.`,customProviderBaseUrlRequired:" --base-url es requerido para providers personalizados.",customProviderTitle:`
53
+ Providers Personalizados (compatibles con OpenAI)
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
+ 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:`
57
+ Generando contexto y reglas con IA...
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
+ 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=`
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
+ \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
+ \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
+ \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
+ \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}
69
+
70
+ ## Architecture
71
+ ${an(e,n,o)}
72
+
73
+ ## Modules
74
+ ${i.map(l=>`- ${l}`).join(`
75
+ `)||"- (not detected)"}
76
+
77
+ ## Technologies
78
+ ${o.map(l=>`- ${l}`).join(`
79
+ `)||"- (not detected)"}
80
+
81
+ ## Critical Dependencies
82
+ ${a.map(l=>`- ${l}`).join(`
83
+ `)||"- (not detected)"}
84
+
85
+ ## Sensitive Zones
86
+ ${s.map(l=>`- ${l}`).join(`
87
+ `)||"- (none detected)"}
88
+
89
+ ## Business Rules
90
+ (Edit this section to document key business rules for the AI reviewers)
91
+
92
+ ## System Summary
93
+ ${t} \u2014 ${o.slice(0,3).join(", ")} project.
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+`
96
+ `,"utf8");return}E.readFileSync(t,"utf8").includes(".aiv")||E.appendFileSync(t,`
97
+ # aiv local context
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)}
103
+
104
+ ## Architecture
105
+ (Describe your system architecture here)
106
+
107
+ ## Modules
108
+ (List main modules and their responsibilities)
109
+
110
+ ## Technologies
111
+ (List key technologies and frameworks)
112
+
113
+ ## Critical Dependencies
114
+ (List dependencies that are critical to the system)
115
+
116
+ ## Sensitive Zones
117
+ (List sensitive areas: auth, payments, data access, etc.)
118
+
119
+ ## Business Rules
120
+ (Describe key business rules the AI should be aware of)
121
+
122
+ ## System Summary
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}
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(`
128
+ `),i=t.diff.files.filter(s=>s.patch).map(s=>`### ${s.filename}
129
+ \`\`\`diff
130
+ ${s.patch}
131
+ \`\`\``).join(`
132
+
133
+ `);return`## PR: #${t.diff.pr.number} \u2014 ${t.diff.pr.title}
134
+
135
+ **Author:** ${t.diff.pr.author}
136
+ **Branch:** ${t.diff.pr.branch} \u2192 ${t.diff.pr.base}
137
+ **Description:** ${t.diff.pr.description??"No description provided."}
138
+
139
+ ---
140
+
141
+ ## Project Context
142
+ ${t.projectContext}
143
+
144
+ ---
145
+
146
+ ## Business Rules (from .aiv/rules.yml)
147
+ \`\`\`json
148
+ ${n}
149
+ \`\`\`
150
+
151
+ ---
152
+
153
+ ## Files Changed (${t.diff.files.length} files)
154
+ ${o}
155
+
156
+ ---
157
+
158
+ ## Diff
159
+ ${i}
160
+
161
+ ---
162
+
163
+ Analyze the above and return a JSON response matching this schema:
164
+ {
165
+ "summary": "string \u2014 concise summary of your findings",
166
+ "findings": [
167
+ {
168
+ "severity": "critical|high|medium|low|info",
169
+ "category": "string",
170
+ "title": "string",
171
+ "description": "string",
172
+ "file": "string (optional)",
173
+ "suggestion": "string (optional)"
174
+ }
175
+ ],
176
+ "riskScore": 0-100,
177
+ "possibleRegressions": ["string"]
178
+ }
179
+
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.
181
+
182
+ Your job is to analyze Pull Request changes STRICTLY from a business and domain perspective.
183
+
184
+ Focus on:
185
+ - Business logic correctness: does the code behave as the domain requires?
186
+ - Domain rule violations: are any business invariants broken?
187
+ - Functional regressions: could this break existing behavior users depend on?
188
+ - Side effects on other business flows (billing, notifications, state machines, etc.)
189
+ - Missing required steps (auditing, logging, approvals, notifications)
190
+ - Incorrect calculations, status transitions, or conditional logic
191
+ - Data integrity concerns (missing validations, incorrect defaults)
192
+
193
+ You are NOT a linter, NOT a security scanner. You analyze MEANING, not syntax.
194
+
195
+ If the rules.yml specifies required_calls or required_checks for a module, verify they are present.
196
+
197
+ Be concrete. Reference specific lines or functions when relevant.
198
+ Assign a riskScore from 0 (no risk) to 100 (critical business risk).
199
+
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.
201
+
202
+ Your job is to analyze Pull Request changes from an architectural and structural perspective.
203
+
204
+ Focus on:
205
+ - Layer violations (e.g., business logic leaking into controllers, DB logic in service layer)
206
+ - Module coupling: are new dependencies being introduced unnecessarily?
207
+ - Single Responsibility: are files/classes/functions doing too many things?
208
+ - Dependency direction: does data flow in the correct direction through the system?
209
+ - Abstraction quality: are new abstractions well-named, well-scoped, and necessary?
210
+ - Scalability concerns: will this break under load or as the system grows?
211
+ - Consistency with existing patterns in the codebase
212
+ - Over-engineering or under-engineering
213
+ - Fragile design decisions that will require future rework
214
+
215
+ You are NOT checking syntax, linting, or security.
216
+
217
+ Use the project context to understand the existing architecture. Flag deviations.
218
+ Assign a riskScore from 0 (clean) to 100 (structural problem that needs blocking).
219
+
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.
221
+
222
+ Your job is to analyze Pull Request changes for security vulnerabilities and risks.
223
+
224
+ Focus on:
225
+ - Authentication and authorization flaws (missing checks, privilege escalation)
226
+ - Injection vulnerabilities (SQL, NoSQL, command, LDAP, template injection)
227
+ - Insecure direct object references (IDOR)
228
+ - Sensitive data exposure (logging secrets, returning PII, insecure storage)
229
+ - Cryptographic issues (weak algorithms, hardcoded secrets, insecure RNG)
230
+ - Input validation gaps (missing sanitization, unsafe deserialization)
231
+ - Race conditions or TOCTOU vulnerabilities
232
+ - Broken access control in API endpoints
233
+ - SSRF, path traversal, open redirects
234
+ - Dependency security (new packages with known issues)
235
+
236
+ Mark findings involving sensitive_modules from the rules with higher severity.
237
+
238
+ Be precise: name the vulnerability class (OWASP), the file, and the specific line or pattern.
239
+ Assign a riskScore from 0 (no security issues) to 100 (active vulnerability, block immediately).
240
+
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
+ ## Relevant Project Tree
244
+ \`\`\`json
245
+ ${s}
246
+ \`\`\``:""].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(`
248
+ `+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
+ ${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(`
251
+ `+g.default.bold.red(t.renderSecurityIssues)),console.log(g.default.dim(" "+"\u2500".repeat(56))),Ne(e.securityIssues)),e.businessRisks.length>0&&(console.log(`
252
+ `+g.default.bold.yellow(t.renderBusinessRisks)),console.log(g.default.dim(" "+"\u2500".repeat(56))),Ne(e.businessRisks)),e.architectureIssues.length>0&&(console.log(`
253
+ `+g.default.bold.blue(t.renderArchitectureIssues)),console.log(g.default.dim(" "+"\u2500".repeat(56))),Ne(e.architectureIssues)),e.possibleRegressions.length>0&&(console.log(`
254
+ `+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("\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
+ `).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=`
265
+ Project name: ${n}
266
+
267
+ package.json summary:
268
+ ${s}
269
+
270
+ Project file tree (tree.json):
271
+ ${i}
272
+
273
+ Sample source files:
274
+ ${a}
275
+
276
+ Generate context.md and rules.yml suitable for AI PR reviewers for this project.
277
+ Return ONLY valid JSON with exactly these two fields:
278
+ {
279
+ "context": "<full context.md content as a string>",
280
+ "rules": "<full rules.yml content as a string>"
281
+ }`,p=await t.complete([{role:"user",content:l}],`You are an expert software architect.
282
+ Given a project's structure, dependencies, and sample code, produce:
283
+
284
+ 1. context.md \u2014 Markdown file with these sections:
285
+ ## Architecture \u2014 overall pattern (monorepo, monolith, microservices, layers, etc.)
286
+ ## Modules \u2014 key modules or directories and their responsibilities
287
+ ## Technologies \u2014 main frameworks and libraries in use
288
+ ## Critical Dependencies \u2014 security-sensitive or infrastructure packages
289
+ ## Sensitive Zones \u2014 paths that require extra scrutiny (auth, payments, migrations, etc.)
290
+ ## Business Rules \u2014 inferred invariants the codebase enforces (edit this section after generation)
291
+ ## System Summary \u2014 one paragraph description
292
+
293
+ 2. rules.yml \u2014 YAML file with:
294
+ sensitive_modules: [list of folder/module names that are sensitive]
295
+ business_rules:
296
+ <module>:
297
+ required_calls: [functions that must always be called in this module]
298
+ required_checks: [validations that must be present]
299
+ forbidden_patterns: [patterns that must never appear]
300
+
301
+ 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: []
303
+ 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??"?"}
305
+ 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
+
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
+ `+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
+ 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(`
316
+ `+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);
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@ateriss_/aiv-cli",
3
+ "version": "0.1.0",
4
+ "description": "AI-powered PR reviewer CLI — local-first, multi-agent semantic analysis",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "aiv": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsup",
11
+ "typecheck": "tsc",
12
+ "dev": "ts-node src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": ["ai", "pr-review", "cli", "github", "claude", "openai"],
17
+ "author": "",
18
+ "license": "MIT",
19
+ "dependencies": {
20
+ "@anthropic-ai/sdk": "^0.37.0",
21
+ "chalk": "^5.3.0",
22
+ "commander": "^12.1.0",
23
+ "inquirer": "^9.3.7",
24
+ "js-yaml": "^4.1.0",
25
+ "node-fetch": "^3.3.2",
26
+ "ora": "^8.1.1",
27
+ "openai": "^4.77.0",
28
+ "table": "^6.8.2"
29
+ },
30
+ "devDependencies": {
31
+ "@types/inquirer": "^9.0.7",
32
+ "@types/js-yaml": "^4.0.9",
33
+ "@types/node": "^22.10.0",
34
+ "ts-node": "^10.9.2",
35
+ "tsup": "^8.3.5",
36
+ "typescript": "^5.7.2"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ }
41
+ }
@@ -0,0 +1,32 @@
1
+ import { BaseAgent } from './base';
2
+ import { LLMProvider } from '../providers/base';
3
+
4
+ export class ArchitectureReviewer extends BaseAgent {
5
+ readonly agentName = 'architecture';
6
+
7
+ readonly systemPrompt = `You are a principal software architect performing a code review.
8
+
9
+ Your job is to analyze Pull Request changes from an architectural and structural perspective.
10
+
11
+ Focus on:
12
+ - Layer violations (e.g., business logic leaking into controllers, DB logic in service layer)
13
+ - Module coupling: are new dependencies being introduced unnecessarily?
14
+ - Single Responsibility: are files/classes/functions doing too many things?
15
+ - Dependency direction: does data flow in the correct direction through the system?
16
+ - Abstraction quality: are new abstractions well-named, well-scoped, and necessary?
17
+ - Scalability concerns: will this break under load or as the system grows?
18
+ - Consistency with existing patterns in the codebase
19
+ - Over-engineering or under-engineering
20
+ - Fragile design decisions that will require future rework
21
+
22
+ You are NOT checking syntax, linting, or security.
23
+
24
+ Use the project context to understand the existing architecture. Flag deviations.
25
+ Assign a riskScore from 0 (clean) to 100 (structural problem that needs blocking).
26
+
27
+ Return only valid JSON as specified.`;
28
+
29
+ constructor(provider: LLMProvider) {
30
+ super(provider);
31
+ }
32
+ }