@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/README.md +954 -0
- package/dist/index.js +318 -0
- package/package.json +41 -0
- package/src/agents/architecture.ts +32 -0
- package/src/agents/base.ts +125 -0
- package/src/agents/business.ts +32 -0
- package/src/agents/index.ts +4 -0
- package/src/agents/security.ts +33 -0
- package/src/cache/index.ts +47 -0
- package/src/cli/banner.ts +32 -0
- package/src/cli/commands/agents.ts +49 -0
- package/src/cli/commands/config.ts +426 -0
- package/src/cli/commands/context.ts +131 -0
- package/src/cli/commands/init.ts +117 -0
- package/src/cli/commands/prs.ts +118 -0
- package/src/cli/commands/review.ts +171 -0
- package/src/cli/renderer.ts +102 -0
- package/src/cli/selector.ts +78 -0
- package/src/config/index.ts +241 -0
- package/src/context/builder.ts +199 -0
- package/src/context/generator.ts +138 -0
- package/src/context/manager.ts +83 -0
- package/src/context/tree.ts +90 -0
- package/src/git/github.ts +112 -0
- package/src/git/utils.ts +51 -0
- package/src/i18n/en.ts +203 -0
- package/src/i18n/es.ts +203 -0
- package/src/i18n/index.ts +57 -0
- package/src/index.ts +29 -0
- package/src/orchestrator/index.ts +110 -0
- package/src/providers/base.ts +16 -0
- package/src/providers/claude.ts +36 -0
- package/src/providers/factory.ts +84 -0
- package/src/providers/fallback.ts +47 -0
- package/src/providers/gemini.ts +58 -0
- package/src/providers/mock.ts +27 -0
- package/src/providers/openai.ts +41 -0
- package/src/types.ts +175 -0
- package/tsconfig.json +17 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { TreeNode } from '../types';
|
|
4
|
+
|
|
5
|
+
const IGNORED = new Set([
|
|
6
|
+
'node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage',
|
|
7
|
+
'.aiv', '.cache', 'vendor', '__pycache__', '.venv', 'venv', '.DS_Store',
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
const MODULE_TYPE_MAP: Array<[RegExp, TreeNode['module_type']]> = [
|
|
11
|
+
[/auth|login|session|jwt|oauth|passport/i, 'auth'],
|
|
12
|
+
[/sql|database|db|migration|seed|schema|prisma|typeorm|sequelize|knex/i, 'sql'],
|
|
13
|
+
[/component|view|page|template|ui|frontend|client|web/i, 'frontend'],
|
|
14
|
+
[/service|controller|handler|route|api|endpoint|backend|server/i, 'backend'],
|
|
15
|
+
[/infra|terraform|k8s|kubernetes|helm|docker|deploy|ci|cd/i, 'infra'],
|
|
16
|
+
[/config|setting|env|constant/i, 'config'],
|
|
17
|
+
[/test|spec|__test__|__mock__|fixture/i, 'test'],
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const SENSITIVITY_MAP: Array<[RegExp, TreeNode['sensitivity']]> = [
|
|
21
|
+
[/auth|login|password|jwt|oauth|session|token|secret|crypto|encrypt/i, 'high'],
|
|
22
|
+
[/payment|billing|payroll|salary|invoice|stripe|financial/i, 'high'],
|
|
23
|
+
[/admin|permission|role|acl|rbac|sudo/i, 'high'],
|
|
24
|
+
[/migration|seed|database|schema/i, 'medium'],
|
|
25
|
+
[/service|controller|api|route/i, 'medium'],
|
|
26
|
+
[/test|spec|mock|fixture|config/i, 'low'],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export async function buildTree(cwd: string, maxDepth: number = 4): Promise<TreeNode> {
|
|
30
|
+
return scanDir(cwd, cwd, maxDepth);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function scanDir(root: string, dir: string, maxDepth: number, depth: number = 0): TreeNode {
|
|
34
|
+
const name = path.basename(dir);
|
|
35
|
+
const rel = path.relative(root, dir) || '.';
|
|
36
|
+
|
|
37
|
+
const node: TreeNode = {
|
|
38
|
+
path: rel,
|
|
39
|
+
type: 'directory',
|
|
40
|
+
module_type: detectModuleType(name),
|
|
41
|
+
sensitivity: detectSensitivity(name),
|
|
42
|
+
children: [],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (depth >= maxDepth) return node;
|
|
46
|
+
|
|
47
|
+
let entries: fs.Dirent[];
|
|
48
|
+
try {
|
|
49
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
50
|
+
} catch {
|
|
51
|
+
return node;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (IGNORED.has(entry.name)) continue;
|
|
56
|
+
|
|
57
|
+
const fullPath = path.join(dir, entry.name);
|
|
58
|
+
if (entry.isDirectory()) {
|
|
59
|
+
node.children!.push(scanDir(root, fullPath, maxDepth, depth + 1));
|
|
60
|
+
} else if (depth < maxDepth) {
|
|
61
|
+
node.children!.push({
|
|
62
|
+
path: path.relative(root, fullPath),
|
|
63
|
+
type: 'file',
|
|
64
|
+
module_type: detectModuleType(entry.name),
|
|
65
|
+
sensitivity: detectSensitivity(entry.name),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Limit children to keep tree.json manageable
|
|
71
|
+
if (node.children!.length > 50) {
|
|
72
|
+
node.children = node.children!.slice(0, 50);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return node;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function detectModuleType(name: string): TreeNode['module_type'] {
|
|
79
|
+
for (const [pattern, type] of MODULE_TYPE_MAP) {
|
|
80
|
+
if (pattern.test(name)) return type;
|
|
81
|
+
}
|
|
82
|
+
return 'other';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function detectSensitivity(name: string): TreeNode['sensitivity'] {
|
|
86
|
+
for (const [pattern, level] of SENSITIVITY_MAP) {
|
|
87
|
+
if (pattern.test(name)) return level;
|
|
88
|
+
}
|
|
89
|
+
return 'low';
|
|
90
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { PullRequest, PRDiff, PRFile } from '../types';
|
|
2
|
+
|
|
3
|
+
const GITHUB_API = 'https://api.github.com';
|
|
4
|
+
|
|
5
|
+
export class GithubClient {
|
|
6
|
+
private headers: Record<string, string>;
|
|
7
|
+
|
|
8
|
+
constructor(token: string) {
|
|
9
|
+
this.headers = {
|
|
10
|
+
Authorization: `Bearer ${token}`,
|
|
11
|
+
Accept: 'application/vnd.github.v3+json',
|
|
12
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
13
|
+
'User-Agent': 'aiv-cli/0.1.0',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async listPRs(owner: string, repo: string, limit: number = 20): Promise<PullRequest[]> {
|
|
18
|
+
const url = `${GITHUB_API}/repos/${owner}/${repo}/pulls?state=open&per_page=${Math.min(limit, 100)}&sort=updated&direction=desc`;
|
|
19
|
+
const res = await this.fetch(url);
|
|
20
|
+
if (!res.ok) await this.throwError(res, 'list PRs');
|
|
21
|
+
|
|
22
|
+
const data = await res.json() as any[];
|
|
23
|
+
return data.map(pr => this.mapPR(pr));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getPR(owner: string, repo: string, prNumber: number): Promise<PullRequest> {
|
|
27
|
+
const url = `${GITHUB_API}/repos/${owner}/${repo}/pulls/${prNumber}`;
|
|
28
|
+
const res = await this.fetch(url);
|
|
29
|
+
if (!res.ok) await this.throwError(res, `get PR #${prNumber}`);
|
|
30
|
+
return this.mapPR(await res.json() as any);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getPRFiles(owner: string, repo: string, prNumber: number): Promise<PRFile[]> {
|
|
34
|
+
const url = `${GITHUB_API}/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100`;
|
|
35
|
+
const res = await this.fetch(url);
|
|
36
|
+
if (!res.ok) await this.throwError(res, 'get PR files');
|
|
37
|
+
|
|
38
|
+
const data = await res.json() as any[];
|
|
39
|
+
return data.map(f => ({
|
|
40
|
+
filename: f.filename,
|
|
41
|
+
status: f.status,
|
|
42
|
+
additions: f.additions,
|
|
43
|
+
deletions: f.deletions,
|
|
44
|
+
patch: f.patch,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getPRDiff(owner: string, repo: string, prNumber: number): Promise<PRDiff> {
|
|
49
|
+
const [pr, files] = await Promise.all([
|
|
50
|
+
this.getPR(owner, repo, prNumber),
|
|
51
|
+
this.getPRFiles(owner, repo, prNumber),
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const rawDiff = files
|
|
55
|
+
.filter(f => f.patch)
|
|
56
|
+
.map(f => `diff --git a/${f.filename} b/${f.filename}\n${f.patch}`)
|
|
57
|
+
.join('\n');
|
|
58
|
+
|
|
59
|
+
return { pr, files, rawDiff };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async submitReview(
|
|
63
|
+
owner: string,
|
|
64
|
+
repo: string,
|
|
65
|
+
prNumber: number,
|
|
66
|
+
event: 'APPROVE' | 'REQUEST_CHANGES',
|
|
67
|
+
body: string = '',
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
const url = `${GITHUB_API}/repos/${owner}/${repo}/pulls/${prNumber}/reviews`;
|
|
70
|
+
const { default: fetch } = await import('node-fetch');
|
|
71
|
+
const res = await fetch(url, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: { ...this.headers, 'Content-Type': 'application/json' },
|
|
74
|
+
body: JSON.stringify({ event, body }),
|
|
75
|
+
}) as unknown as Response;
|
|
76
|
+
if (!res.ok) await this.throwError(res, `submit review on PR #${prNumber}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private mapPR(pr: any): PullRequest {
|
|
80
|
+
return {
|
|
81
|
+
id: pr.id,
|
|
82
|
+
number: pr.number,
|
|
83
|
+
title: pr.title,
|
|
84
|
+
author: pr.user?.login ?? 'unknown',
|
|
85
|
+
branch: pr.head?.ref ?? '',
|
|
86
|
+
base: pr.base?.ref ?? '',
|
|
87
|
+
url: pr.html_url,
|
|
88
|
+
state: pr.state,
|
|
89
|
+
createdAt: pr.created_at,
|
|
90
|
+
updatedAt: pr.updated_at,
|
|
91
|
+
additions: pr.additions ?? 0,
|
|
92
|
+
deletions: pr.deletions ?? 0,
|
|
93
|
+
changedFiles: pr.changed_files ?? 0,
|
|
94
|
+
labels: (pr.labels ?? []).map((l: any) => l.name),
|
|
95
|
+
description: pr.body ?? undefined,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private async fetch(url: string): Promise<Response> {
|
|
100
|
+
const { default: fetch } = await import('node-fetch');
|
|
101
|
+
return fetch(url, { headers: this.headers }) as unknown as Response;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async throwError(res: Response, action: string): Promise<never> {
|
|
105
|
+
let msg = `GitHub API error (${res.status}) while trying to ${action}`;
|
|
106
|
+
try {
|
|
107
|
+
const body = await res.json() as any;
|
|
108
|
+
if (body.message) msg += `: ${body.message}`;
|
|
109
|
+
} catch {}
|
|
110
|
+
throw new Error(msg);
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/git/utils.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
export interface RepoInfo {
|
|
6
|
+
owner: string;
|
|
7
|
+
repo: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function detectRepoInfo(cwd: string): RepoInfo | null {
|
|
11
|
+
try {
|
|
12
|
+
const remoteUrl = execSync('git remote get-url origin', { cwd, stdio: 'pipe' }).toString().trim();
|
|
13
|
+
return parseGithubUrl(remoteUrl);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function parseGithubUrl(url: string): RepoInfo | null {
|
|
20
|
+
const sshMatch = /github\.com[:/]([^/]+)\/([^/.]+)/.exec(url);
|
|
21
|
+
if (sshMatch) return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
22
|
+
|
|
23
|
+
const httpsMatch = /github\.com\/([^/]+)\/([^/.]+)/.exec(url);
|
|
24
|
+
if (httpsMatch) return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function appendGitignore(cwd: string): void {
|
|
30
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
31
|
+
const entry = '.aiv/';
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
34
|
+
fs.writeFileSync(gitignorePath, entry + '\n', 'utf8');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
39
|
+
if (!content.includes('.aiv')) {
|
|
40
|
+
fs.appendFileSync(gitignorePath, `\n# aiv local context\n${entry}\n`, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isGitRepo(cwd: string): boolean {
|
|
45
|
+
try {
|
|
46
|
+
execSync('git rev-parse --git-dir', { cwd, stdio: 'pipe' });
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/i18n/en.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export const en = {
|
|
2
|
+
// ── General ────────────────────────────────────────────────────────────────
|
|
3
|
+
notInitialized: 'Not initialized. Run `aiv init` first.',
|
|
4
|
+
invalidProvider: 'Invalid provider. Choose: claude, openai, mock',
|
|
5
|
+
invalidPrNumber: 'Invalid PR number.',
|
|
6
|
+
repoNotDetected: 'Could not detect GitHub owner/repo. Use --owner and --repo flags or configure in .aiv/config.yml',
|
|
7
|
+
|
|
8
|
+
// ── init ───────────────────────────────────────────────────────────────────
|
|
9
|
+
initAlreadyDone: 'aiv is already initialized. Use --force to reinitialize.',
|
|
10
|
+
initTitle: ' aiv — AI PR Reviewer\n',
|
|
11
|
+
initWritingGlobalConfig: 'Writing global config (~/.aiv/config.yml)...',
|
|
12
|
+
initGlobalConfigCreated: 'Global config created (~/.aiv/config.yml)',
|
|
13
|
+
initGlobalConfigExists: 'Global config already exists (~/.aiv/config.yml)',
|
|
14
|
+
initWritingConfig: 'Writing repo config (.aiv/config.yml)...',
|
|
15
|
+
initConfigCreated: 'Repo config created (.aiv/config.yml)',
|
|
16
|
+
initRulesCreated: 'rules.yml created',
|
|
17
|
+
initScanningTree: 'Scanning project structure...',
|
|
18
|
+
initTreeCreated: 'tree.json created',
|
|
19
|
+
initTreeSkipped: 'tree.json skipped (could not scan)',
|
|
20
|
+
initBuildingContext: 'Building project context...',
|
|
21
|
+
initContextCreated: 'context.md created',
|
|
22
|
+
initContextSkipped: 'context.md skipped (could not analyze)',
|
|
23
|
+
initGitignoreUpdated: '.gitignore updated',
|
|
24
|
+
initSuccessTitle: '\n Initialized! Next steps:\n',
|
|
25
|
+
initStep1: (envVar: string) => ` Set your API key: export ${envVar}=your-key`,
|
|
26
|
+
initStep2: (envVar: string) => ` Set GitHub token: export ${envVar}=your-token`,
|
|
27
|
+
initStep3: ' List PRs: aiv prs',
|
|
28
|
+
initStep4: ' Review a PR: aiv review <pr-number>',
|
|
29
|
+
initEditContext: (path: string) => ` Edit ${path} to add business context.`,
|
|
30
|
+
initEditRules: (path: string) => ` Edit ${path} to define your rules.`,
|
|
31
|
+
initGlobalHint: ' Global config: ~/.aiv/config.yml',
|
|
32
|
+
initAddAccountHint: ' Add accounts: aiv config add-account <name> --token-env <VAR>',
|
|
33
|
+
|
|
34
|
+
// ── selector ───────────────────────────────────────────────────────────────
|
|
35
|
+
selectorSelectPR: 'Select a PR to review:',
|
|
36
|
+
selectorConfirmReview: 'Review',
|
|
37
|
+
selectorCancelled: ' Cancelled.\n',
|
|
38
|
+
|
|
39
|
+
// ── prs ────────────────────────────────────────────────────────────────────
|
|
40
|
+
prsFetching: (repo: string) => `Fetching PRs for ${repo}...`,
|
|
41
|
+
prsNoneFound: '\n No open pull requests found.\n',
|
|
42
|
+
prsColPR: 'PR',
|
|
43
|
+
prsColTitle: 'Title',
|
|
44
|
+
prsColAuthor: 'Author',
|
|
45
|
+
prsColBranch: 'Branch',
|
|
46
|
+
prsColChanges: 'Changes',
|
|
47
|
+
prsColCreated: 'Created',
|
|
48
|
+
prsFooter: (count: number) => ` Showing ${count} open PR(s). Run aiv review <pr-number> to analyze.\n`,
|
|
49
|
+
prsMissingToken: (envVar: string) => `Missing env var: ${envVar}. Set your GitHub token.`,
|
|
50
|
+
prsFailed: (msg: string) => `Failed: ${msg}`,
|
|
51
|
+
|
|
52
|
+
// ── review ─────────────────────────────────────────────────────────────────
|
|
53
|
+
reviewTitle: (n: number) => `\n aiv review — PR #${n}\n`,
|
|
54
|
+
reviewFetching: (n: number, repo: string) => `Fetching PR #${n} from ${repo}...`,
|
|
55
|
+
reviewLoaded: (title: string, files: number) => `PR loaded: ${title} (${files} files)`,
|
|
56
|
+
reviewFetchFailed: (msg: string) => `Failed to fetch PR: ${msg}`,
|
|
57
|
+
reviewLoadingContext: 'Loading project context...',
|
|
58
|
+
reviewContextLoaded: 'Context loaded',
|
|
59
|
+
reviewRunningAgents: (agents: string) => `\n Running agents: ${agents}\n`,
|
|
60
|
+
reviewFailed: (msg: string) => `\n Review failed: ${msg}\n`,
|
|
61
|
+
reviewAccount: (name: string, envVar: string) => `Account: ${name} (${envVar})`,
|
|
62
|
+
|
|
63
|
+
// ── context ────────────────────────────────────────────────────────────────
|
|
64
|
+
contextRefreshTitle: '\n Refreshing context...\n',
|
|
65
|
+
contextScanningTree: 'Scanning project structure...',
|
|
66
|
+
contextTreeUpdated: 'tree.json updated',
|
|
67
|
+
contextTreeFailed: (msg: string) => `tree.json failed: ${msg}`,
|
|
68
|
+
contextRebuildingCtx: 'Rebuilding context.md...',
|
|
69
|
+
contextCtxUpdated: 'context.md updated',
|
|
70
|
+
contextCtxFailed: (msg: string) => `context.md failed: ${msg}`,
|
|
71
|
+
contextEditHint: (path: string) => `\n Edit ${path} to add custom context.\n`,
|
|
72
|
+
contextNoFile: 'No context.md found. Run `aiv context refresh`.',
|
|
73
|
+
|
|
74
|
+
// ── config ─────────────────────────────────────────────────────────────────
|
|
75
|
+
configGlobalTitle: ' ~/.aiv/config.yml (global)',
|
|
76
|
+
configNotCreated: ' (not created yet)',
|
|
77
|
+
configRepoConfigTitle: ' .aiv/config.yml (repo)',
|
|
78
|
+
configRulesTitle: ' .aiv/rules.yml',
|
|
79
|
+
configProviderSet: (p: string) => ` Default provider set to: ${p}`,
|
|
80
|
+
configRepoSet: (o: string, r: string) => ` Repo set to: ${o}/${r}`,
|
|
81
|
+
configNoRules: ' No rules.yml found.',
|
|
82
|
+
configLangSet: (lang: string) => ` Language set to: ${lang}`,
|
|
83
|
+
configInvalidLang: 'Invalid language. Choose: en, es',
|
|
84
|
+
configTokenEnvHint: (v: string) => ` Token env var: ${v}`,
|
|
85
|
+
configUsernameHint: (u: string) => ` Username: ${u}`,
|
|
86
|
+
configRepoAccountHint: (a: string) => ` Repo account: ${a}`,
|
|
87
|
+
configSavedToRepo: ' Saved to .aiv/config.yml',
|
|
88
|
+
|
|
89
|
+
// ── accounts ───────────────────────────────────────────────────────────────
|
|
90
|
+
accountsTitle: '\n GitHub Accounts\n',
|
|
91
|
+
accountsNone: ' No accounts configured. Add one with: aiv config add-account <name>',
|
|
92
|
+
accountsColName: 'Name',
|
|
93
|
+
accountsColUser: 'Username',
|
|
94
|
+
accountsColEnvVar: 'Token Env Var',
|
|
95
|
+
accountsColToken: 'Token',
|
|
96
|
+
accountsColDesc: 'Description',
|
|
97
|
+
accountsColDefault: 'Default',
|
|
98
|
+
accountsTokenFound: 'found',
|
|
99
|
+
accountsTokenMissing: 'missing',
|
|
100
|
+
accountsDefaultMark: '✔ default',
|
|
101
|
+
accountsAdded: (name: string) => ` Account "${name}" added to global config.`,
|
|
102
|
+
accountsRemoved: (name: string) => ` Account "${name}" removed.`,
|
|
103
|
+
accountsDefaultSet: (name: string) => ` Default account set to: ${name}`,
|
|
104
|
+
accountsRepoSet: (name: string) => ` This repo will use account: ${name}`,
|
|
105
|
+
accountsNotFound: (name: string) => ` Account "${name}" not found.`,
|
|
106
|
+
accountsAlreadyExists: (name: string) => ` Account "${name}" already exists. Use --force to overwrite.`,
|
|
107
|
+
accountsGlobalHint: '\n Global config: ~/.aiv/config.yml',
|
|
108
|
+
accountsRepoHint: ' Repo config: .aiv/config.yml\n',
|
|
109
|
+
|
|
110
|
+
// ── orchestrator ───────────────────────────────────────────────────────────
|
|
111
|
+
orchestratorRunning: (agent: string) => ` Running ${agent} agent...`,
|
|
112
|
+
orchestratorDone: (agent: string, count: number, score: number) => ` ${agent} — ${count} finding(s) [score: ${score}]`,
|
|
113
|
+
orchestratorAgentFailed: (agent: string, msg: string) => ` ${agent} failed: ${msg}`,
|
|
114
|
+
orchestratorFailedMsg: (msg: string) => `Agent failed: ${msg}`,
|
|
115
|
+
orchestratorFailedUnexpected: 'Agent failed unexpectedly.',
|
|
116
|
+
orchestratorOverallRisk: (label: string, score: number, total: number, agents: number) =>
|
|
117
|
+
`Overall risk: ${label} (${score}/100). ${total} finding(s) across ${agents} agent(s).`,
|
|
118
|
+
orchestratorCriticalFound: (count: number) => `${count} high/critical issue(s) require attention.`,
|
|
119
|
+
orchestratorNoCritical: 'No critical issues detected.',
|
|
120
|
+
|
|
121
|
+
// ── renderer ───────────────────────────────────────────────────────────────
|
|
122
|
+
renderRiskScore: 'Risk Score:',
|
|
123
|
+
renderGenerated: 'Generated:',
|
|
124
|
+
renderExecutiveSummary: ' Executive Summary',
|
|
125
|
+
renderSecurityIssues: ' Security Issues',
|
|
126
|
+
renderBusinessRisks: ' Business Risks',
|
|
127
|
+
renderArchitectureIssues: ' Architecture Issues',
|
|
128
|
+
renderRegressions: ' Possible Regressions',
|
|
129
|
+
renderAgentSummaries: ' Agent Summaries',
|
|
130
|
+
renderReviewTitle: (n: number, title: string) => ` Review: PR #${n} — ${title}`,
|
|
131
|
+
renderSuggestion: 'Suggestion:',
|
|
132
|
+
severityCritical: 'CRITICAL',
|
|
133
|
+
severityHigh: 'HIGH',
|
|
134
|
+
severityMedium: 'MEDIUM',
|
|
135
|
+
severityLow: 'LOW',
|
|
136
|
+
severityInfo: 'INFO',
|
|
137
|
+
|
|
138
|
+
// ── errors from config layer ────────────────────────────────────────────────
|
|
139
|
+
errorMissingToken: (envVar: string, account: string) => `Missing env var: ${envVar} (account: ${account})`,
|
|
140
|
+
errorAccountNotFound: (name: string) => `Account "${name}" not found.`,
|
|
141
|
+
errorAccountNotFoundGlobal: (name: string) =>
|
|
142
|
+
`Account "${name}" not found in global config. Add it first with: aiv config add-account ${name}`,
|
|
143
|
+
|
|
144
|
+
// ── custom providers ──────────────────────────────────────────────────────
|
|
145
|
+
customProviderAdded: (name: string) => ` Provider "${name}" added to global config.`,
|
|
146
|
+
customProviderRemoved: (name: string) => ` Provider "${name}" removed.`,
|
|
147
|
+
customProviderNotFound: (name: string) => ` Provider "${name}" not found in custom_providers.`,
|
|
148
|
+
customProviderAlreadyExists: (name: string) => ` Provider "${name}" already exists. Use --force to overwrite.`,
|
|
149
|
+
customProviderBaseUrlRequired: ' --base-url is required for custom (non-built-in) providers.',
|
|
150
|
+
customProviderTitle: '\n Custom Providers (OpenAI-compatible)\n',
|
|
151
|
+
customProviderNone: ' No custom providers configured. Add one with: aiv config add-provider <name> --base-url <url> --api-key-env <VAR> --model <model>',
|
|
152
|
+
customProviderColName: 'Name',
|
|
153
|
+
customProviderColUrl: 'Base URL',
|
|
154
|
+
customProviderColModel: 'Default Model',
|
|
155
|
+
customProviderColEnvVar: 'Token Env Var',
|
|
156
|
+
customProviderColToken: 'Token',
|
|
157
|
+
builtinProviderSet: (name: string, model: string, envVar: string) => ` ${name}: model=${model}, key_env=${envVar}`,
|
|
158
|
+
|
|
159
|
+
// ── provider routing ───────────────────────────────────────────────────────
|
|
160
|
+
providerFallback: (from: string, to: string) => `Provider "${from}" quota/rate limit — switching to "${to}"`,
|
|
161
|
+
providerAllFailed: 'All providers in the fallback chain failed. Check your API keys and quotas.',
|
|
162
|
+
configAgentProviderSet: (agent: string, spec: string) => ` Agent "${agent}" will use: ${spec}`,
|
|
163
|
+
configFallbackSet: (chain: string) => ` Fallback chain set: ${chain}`,
|
|
164
|
+
configFallbackCleared: ' Fallback chain cleared.',
|
|
165
|
+
configAgentProviderShow: '\n Agent provider assignments\n',
|
|
166
|
+
configAgentProviderNone: ' No per-agent providers configured. All agents use the default provider.',
|
|
167
|
+
configInvalidAgentName: (name: string) => ` Unknown agent: "${name}". Valid: business, architecture, security, context`,
|
|
168
|
+
configInvalidProviderSpec: (spec: string) => ` Invalid spec: "${spec}". Use format: provider or provider/model (e.g. claude/claude-haiku-4-5)`,
|
|
169
|
+
|
|
170
|
+
// ── post-review action ─────────────────────────────────────────────────────
|
|
171
|
+
postReviewSelectAction: 'What would you like to do with this PR?',
|
|
172
|
+
postReviewSubmitting: 'Submitting review to GitHub...',
|
|
173
|
+
postReviewApproved: (n: number) => ` PR #${n} approved on GitHub.`,
|
|
174
|
+
postReviewChangesRequested: (n: number) => ` Changes requested on PR #${n}.`,
|
|
175
|
+
postReviewFailed: (msg: string) => `Failed to submit review: ${msg}`,
|
|
176
|
+
postReviewRefreshing: 'Refreshing project context...',
|
|
177
|
+
postReviewRefreshed: 'Context updated.',
|
|
178
|
+
|
|
179
|
+
// ── context generate ───────────────────────────────────────────────────────
|
|
180
|
+
contextGenerateTitle: '\n Generating context and rules with AI...\n',
|
|
181
|
+
contextGenerating: 'Analyzing project with AI...',
|
|
182
|
+
contextGenerateDone: 'Done. Edit the files if needed.',
|
|
183
|
+
contextGenerateFailed: (msg: string) => `Generation failed: ${msg}`,
|
|
184
|
+
contextGenerateConfirmOverwrite: (file: string) => `${file} already exists. Overwrite?`,
|
|
185
|
+
contextGenerateSkipped: (file: string) => ` ${file} skipped (existing file kept).`,
|
|
186
|
+
contextGenerateWritten: (file: string) => ` ${file} written.`,
|
|
187
|
+
contextGenerateProviderError: (envVar: string) => `Missing AI key: ${envVar}. Check your provider config.`,
|
|
188
|
+
|
|
189
|
+
// ── agents command ─────────────────────────────────────────────────────────
|
|
190
|
+
agentsTitle: '\n aiv — Available Agents\n',
|
|
191
|
+
agentsColAgent: 'Agent',
|
|
192
|
+
agentsColDesc: 'Description',
|
|
193
|
+
agentsColFocus: 'Focus Areas',
|
|
194
|
+
agentsFooter: 'Run specific agents with: aiv review <pr> --agent business security',
|
|
195
|
+
agentBusinessDesc: 'Analyzes business logic, domain rules, and functional correctness',
|
|
196
|
+
agentBusinessFocus: 'Business logic, domain invariants, rule violations, functional regressions',
|
|
197
|
+
agentArchDesc: 'Reviews structural patterns, module coupling, and design decisions',
|
|
198
|
+
agentArchFocus: 'Layer violations, coupling, SRP, dependency direction, abstraction quality',
|
|
199
|
+
agentSecDesc: 'Detects security vulnerabilities and data exposure risks',
|
|
200
|
+
agentSecFocus: 'Auth bypass, injection, data leakage, OWASP Top 10, sensitive data handling',
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export type TranslationKeys = typeof en;
|