@ateriss_/aiv-cli 1.0.0 → 1.0.2
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 +112 -85
- package/dist/index.js +335 -113
- package/package.json +10 -3
- package/src/agents/base.ts +6 -1
- package/src/cli/commands/review.ts +91 -31
- package/src/cli/renderer.ts +61 -0
- package/src/cli/selector.ts +35 -5
- package/src/git/github.ts +49 -1
- package/src/i18n/en.ts +15 -0
- package/src/i18n/es.ts +15 -0
- package/tsup.config.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name":
|
|
3
|
-
"version": "1.0.
|
|
2
|
+
"name": "@ateriss_/aiv-cli",
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "AI-powered PR reviewer CLI — local-first, multi-agent semantic analysis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,14 @@
|
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
14
|
"prepublishOnly": "npm run build"
|
|
15
15
|
},
|
|
16
|
-
"keywords": [
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"pr-review",
|
|
19
|
+
"cli",
|
|
20
|
+
"github",
|
|
21
|
+
"claude",
|
|
22
|
+
"openai"
|
|
23
|
+
],
|
|
17
24
|
"author": "",
|
|
18
25
|
"license": "MIT",
|
|
19
26
|
"dependencies": {
|
package/src/agents/base.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentResult, AgentFinding, PRDiff, AivRules } from '../types';
|
|
2
2
|
import { LLMProvider } from '../providers/base';
|
|
3
|
+
import { getLang } from '../i18n';
|
|
3
4
|
|
|
4
5
|
export interface AgentContext {
|
|
5
6
|
projectContext: string;
|
|
@@ -36,6 +37,10 @@ export abstract class BaseAgent {
|
|
|
36
37
|
.map(f => `### ${f.filename}\n\`\`\`diff\n${f.patch}\n\`\`\``)
|
|
37
38
|
.join('\n\n');
|
|
38
39
|
|
|
40
|
+
const langInstruction = getLang() === 'es'
|
|
41
|
+
? '\n\nIMPORTANT: Respond in Spanish. All string values in the JSON (summary, title, description, suggestion, possibleRegressions) must be written in Spanish.'
|
|
42
|
+
: '';
|
|
43
|
+
|
|
39
44
|
return `## PR: #${ctx.diff.pr.number} — ${ctx.diff.pr.title}
|
|
40
45
|
|
|
41
46
|
**Author:** ${ctx.diff.pr.author}
|
|
@@ -83,7 +88,7 @@ Analyze the above and return a JSON response matching this schema:
|
|
|
83
88
|
"possibleRegressions": ["string"]
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
Return ONLY valid JSON. No markdown fences, no explanation outside the JSON
|
|
91
|
+
Return ONLY valid JSON. No markdown fences, no explanation outside the JSON.${langInstruction}`;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
protected parseResponse(raw: string): AgentResult {
|
|
@@ -2,13 +2,13 @@ import { Command } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { resolveConfig, loadRules, getGithubToken, isInitialized } from '../../config';
|
|
5
|
-
import type { ResolvedConfig } from '../../types';
|
|
5
|
+
import type { ResolvedConfig, ReviewResult } from '../../types';
|
|
6
6
|
import { GithubClient } from '../../git/github';
|
|
7
7
|
import { detectRepoInfo } from '../../git/utils';
|
|
8
8
|
import { Orchestrator } from '../../orchestrator';
|
|
9
9
|
import { ContextManager, refreshContextFiles } from '../../context/manager';
|
|
10
|
-
import { renderReview } from '../renderer';
|
|
11
|
-
import { selectPR, selectPostReviewAction } from '../selector';
|
|
10
|
+
import { renderReview, buildAivComment } from '../renderer';
|
|
11
|
+
import { selectPR, selectPostReviewAction, confirmMerge, selectMergeStrategy } from '../selector';
|
|
12
12
|
import { t } from '../../i18n';
|
|
13
13
|
|
|
14
14
|
// ─── Shared review runner (used by prs.ts and review command) ─────────────────
|
|
@@ -27,15 +27,14 @@ export async function runReview(opts: RunReviewOptions): Promise<void> {
|
|
|
27
27
|
const { prNumber, owner, repo, config, token } = opts;
|
|
28
28
|
const rules = loadRules();
|
|
29
29
|
const activeAgents = opts.agents ?? ['business', 'architecture', 'security'];
|
|
30
|
+
const client = new GithubClient(token);
|
|
30
31
|
|
|
31
32
|
console.log(chalk.bold(t().reviewTitle(prNumber)));
|
|
32
33
|
console.log(chalk.dim(` ${t().reviewAccount(config.github.accountName, config.github.token_env)}\n`));
|
|
33
34
|
|
|
34
35
|
const fetchSpinner = ora(t().reviewFetching(prNumber, `${owner}/${repo}`)).start();
|
|
35
36
|
let prDiff: Awaited<ReturnType<GithubClient['getPRDiff']>>;
|
|
36
|
-
|
|
37
37
|
try {
|
|
38
|
-
const client = new GithubClient(token);
|
|
39
38
|
prDiff = await client.getPRDiff(owner, repo, prNumber);
|
|
40
39
|
fetchSpinner.succeed(t().reviewLoaded(chalk.cyan(prDiff.pr.title), prDiff.files.length));
|
|
41
40
|
} catch (e: any) {
|
|
@@ -43,49 +42,110 @@ export async function runReview(opts: RunReviewOptions): Promise<void> {
|
|
|
43
42
|
return;
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
const result = await resolveResult(client, { owner, repo, prNumber }, config, rules, prDiff, activeAgents, opts.json);
|
|
46
|
+
if (!result) return;
|
|
47
|
+
|
|
48
|
+
if (opts.json) {
|
|
49
|
+
console.log(JSON.stringify(result, null, 2));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
renderReview(result);
|
|
54
|
+
if (!process.stdout.isTTY) return;
|
|
55
|
+
|
|
56
|
+
const action = await selectPostReviewAction(t().postReviewSelectAction);
|
|
57
|
+
if (action === 'skip') return;
|
|
58
|
+
|
|
59
|
+
if (action === 'post_comment') {
|
|
60
|
+
await doPostComment(client, owner, repo, prNumber, result);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const submitted = await doSubmitReview(client, owner, repo, prNumber, action);
|
|
65
|
+
if (!submitted) return;
|
|
66
|
+
|
|
67
|
+
if (action === 'approve') {
|
|
68
|
+
await doApproveFlow(client, owner, repo, prNumber, result);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface RepoRef { owner: string; repo: string; prNumber: number; }
|
|
73
|
+
|
|
74
|
+
async function resolveResult(
|
|
75
|
+
client: GithubClient,
|
|
76
|
+
ref: RepoRef,
|
|
77
|
+
config: ResolvedConfig, rules: ReturnType<typeof loadRules>,
|
|
78
|
+
prDiff: Awaited<ReturnType<GithubClient['getPRDiff']>>,
|
|
79
|
+
activeAgents: string[], json?: boolean,
|
|
80
|
+
): Promise<ReviewResult | null> {
|
|
81
|
+
const { owner, repo, prNumber } = ref;
|
|
82
|
+
if (!json && process.stdout.isTTY) {
|
|
83
|
+
const cached = await client.findAivReview(owner, repo, prNumber);
|
|
84
|
+
if (cached) {
|
|
85
|
+
console.log(chalk.cyan(`\n ${t().reviewCachedFound}`));
|
|
86
|
+
const useCached = await confirmMerge(t().reviewCachedUse);
|
|
87
|
+
if (useCached) {
|
|
88
|
+
console.log(chalk.dim(` ${t().reviewCachedUsing}`));
|
|
89
|
+
return cached;
|
|
90
|
+
}
|
|
91
|
+
console.log(chalk.dim(` ${t().reviewCachedSkipping}`));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
46
95
|
const ctxSpinner = ora(t().reviewLoadingContext).start();
|
|
47
96
|
const context = new ContextManager(process.cwd()).buildReviewContext(prDiff);
|
|
48
97
|
ctxSpinner.succeed(t().reviewContextLoaded);
|
|
49
|
-
|
|
50
98
|
console.log(chalk.dim(t().reviewRunningAgents(activeAgents.join(', '))));
|
|
51
99
|
|
|
52
100
|
try {
|
|
53
|
-
|
|
54
|
-
if (opts.json) {
|
|
55
|
-
console.log(JSON.stringify(result, null, 2));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
renderReview(result);
|
|
101
|
+
return await new Orchestrator(config, rules).run(prDiff, context, activeAgents);
|
|
59
102
|
} catch (e: any) {
|
|
60
103
|
console.log(chalk.red(t().reviewFailed(e.message)));
|
|
61
|
-
return;
|
|
104
|
+
return null;
|
|
62
105
|
}
|
|
106
|
+
}
|
|
63
107
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
108
|
+
async function doPostComment(client: GithubClient, owner: string, repo: string, prNumber: number, result: ReviewResult): Promise<void> {
|
|
109
|
+
const spinner = ora(t().postReviewPostingComment).start();
|
|
110
|
+
try {
|
|
111
|
+
await client.postComment(owner, repo, prNumber, buildAivComment(result));
|
|
112
|
+
spinner.succeed(chalk.green(t().postReviewCommentPosted(prNumber)));
|
|
113
|
+
} catch (e: any) {
|
|
114
|
+
spinner.fail(chalk.red(t().postReviewCommentFailed(e.message)));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
68
117
|
|
|
118
|
+
async function doSubmitReview(client: GithubClient, owner: string, repo: string, prNumber: number, action: 'approve' | 'request_changes'): Promise<boolean> {
|
|
69
119
|
const event = action === 'approve' ? 'APPROVE' : 'REQUEST_CHANGES';
|
|
70
|
-
const
|
|
71
|
-
|
|
120
|
+
const spinner = ora(t().postReviewSubmitting).start();
|
|
72
121
|
try {
|
|
73
|
-
await
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
122
|
+
await client.submitReview(owner, repo, prNumber, event);
|
|
123
|
+
const msg = action === 'approve' ? t().postReviewApproved(prNumber) : t().postReviewChangesRequested(prNumber);
|
|
124
|
+
const color = action === 'approve' ? chalk.green : chalk.yellow;
|
|
125
|
+
spinner.succeed(color(msg));
|
|
126
|
+
return true;
|
|
79
127
|
} catch (e: any) {
|
|
80
|
-
|
|
81
|
-
return;
|
|
128
|
+
spinner.fail(chalk.red(t().postReviewFailed(e.message)));
|
|
129
|
+
return false;
|
|
82
130
|
}
|
|
131
|
+
}
|
|
83
132
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
133
|
+
async function doApproveFlow(client: GithubClient, owner: string, repo: string, prNumber: number, result: ReviewResult): Promise<void> {
|
|
134
|
+
const wantsMerge = await confirmMerge(t().postReviewMergeConfirm);
|
|
135
|
+
if (wantsMerge) {
|
|
136
|
+
await doPostComment(client, owner, repo, prNumber, result);
|
|
137
|
+
const strategy = await selectMergeStrategy(t().postReviewSelectMerge);
|
|
138
|
+
const mergeSpinner = ora(t().postReviewMerging(prNumber)).start();
|
|
139
|
+
try {
|
|
140
|
+
await client.mergePR(owner, repo, prNumber, strategy);
|
|
141
|
+
mergeSpinner.succeed(chalk.green(t().postReviewMerged(prNumber)));
|
|
142
|
+
} catch (e: any) {
|
|
143
|
+
mergeSpinner.fail(chalk.red(t().postReviewMergeFailed(e.message)));
|
|
144
|
+
}
|
|
88
145
|
}
|
|
146
|
+
const refreshSpinner = ora(t().postReviewRefreshing).start();
|
|
147
|
+
await refreshContextFiles(process.cwd());
|
|
148
|
+
refreshSpinner.succeed(chalk.green(t().postReviewRefreshed));
|
|
89
149
|
}
|
|
90
150
|
|
|
91
151
|
// ─── CLI command ──────────────────────────────────────────────────────────────
|
package/src/cli/renderer.ts
CHANGED
|
@@ -2,6 +2,67 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { ReviewResult, AgentFinding } from '../types';
|
|
3
3
|
import { t } from '../i18n';
|
|
4
4
|
|
|
5
|
+
const SEVERITY_EMOJI: Record<string, string> = {
|
|
6
|
+
critical: '🔴', high: '🟠', medium: '🟡', low: '🔵', info: '⚪',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const RISK_EMOJI: Record<string, string> = {
|
|
10
|
+
CRITICAL: '🔴', HIGH: '🟠', MEDIUM: '🟡', LOW: '🟢',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function buildAivComment(result: ReviewResult): string {
|
|
14
|
+
const encoded = Buffer.from(JSON.stringify(result)).toString('base64');
|
|
15
|
+
const header = `<!-- aiv-review:${encoded} -->`;
|
|
16
|
+
|
|
17
|
+
const riskEmoji = RISK_EMOJI[result.riskLabel] ?? '⚪';
|
|
18
|
+
const lines: string[] = [
|
|
19
|
+
header,
|
|
20
|
+
'',
|
|
21
|
+
`## 🤖 aiv Review — PR #${result.prNumber} · ${result.prTitle}`,
|
|
22
|
+
'',
|
|
23
|
+
`**Risk Score:** ${riskEmoji} \`${result.riskScore}/100 ${result.riskLabel}\` · Generated: ${result.generatedAt}`,
|
|
24
|
+
'',
|
|
25
|
+
'---',
|
|
26
|
+
'',
|
|
27
|
+
'### Executive Summary',
|
|
28
|
+
result.executiveSummary,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
if (result.securityIssues.length > 0) {
|
|
32
|
+
lines.push('', '---', '', '### 🔴 Security Issues');
|
|
33
|
+
for (const f of result.securityIssues) lines.push(...findingMd(f));
|
|
34
|
+
}
|
|
35
|
+
if (result.businessRisks.length > 0) {
|
|
36
|
+
lines.push('', '---', '', '### 🟡 Business Risks');
|
|
37
|
+
for (const f of result.businessRisks) lines.push(...findingMd(f));
|
|
38
|
+
}
|
|
39
|
+
if (result.architectureIssues.length > 0) {
|
|
40
|
+
lines.push('', '---', '', '### 🔵 Architecture Issues');
|
|
41
|
+
for (const f of result.architectureIssues) lines.push(...findingMd(f));
|
|
42
|
+
}
|
|
43
|
+
if (result.possibleRegressions.length > 0) {
|
|
44
|
+
lines.push('', '---', '', '### ⚠️ Possible Regressions');
|
|
45
|
+
for (const r of result.possibleRegressions) lines.push(`- ${r}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
lines.push('', '---', '', '<details>', '<summary>Agent Summaries</summary>', '');
|
|
49
|
+
for (const a of result.agents) {
|
|
50
|
+
lines.push(`**${a.agentName.toUpperCase()}** \`[${a.riskScore}/100]\``, a.summary, '');
|
|
51
|
+
}
|
|
52
|
+
lines.push('</details>', '', '---', '*Generated by [aiv](https://www.npmjs.com/package/@ateriss_/aiv-cli)*');
|
|
53
|
+
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function findingMd(f: AgentFinding): string[] {
|
|
58
|
+
const emoji = SEVERITY_EMOJI[f.severity] ?? '⚪';
|
|
59
|
+
const out = [`\n**${emoji} [${f.severity.toUpperCase()}] ${f.title}**`];
|
|
60
|
+
if (f.file) out.push(`\`${f.file}\``);
|
|
61
|
+
out.push(f.description);
|
|
62
|
+
if (f.suggestion) out.push(`> 💡 ${f.suggestion}`);
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
5
66
|
export function renderReview(result: ReviewResult): void {
|
|
6
67
|
const tr = t();
|
|
7
68
|
const riskColor = riskChalk(result.riskLabel);
|
package/src/cli/selector.ts
CHANGED
|
@@ -42,7 +42,8 @@ export async function selectPR(prs: PullRequest[], message: string): Promise<Pul
|
|
|
42
42
|
return selected === CANCEL ? null : (prs.find(pr => pr.number === selected) ?? null);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export type PostReviewAction = 'approve' | 'request_changes' | 'skip';
|
|
45
|
+
export type PostReviewAction = 'approve' | 'request_changes' | 'post_comment' | 'skip';
|
|
46
|
+
export type MergeStrategy = 'merge' | 'squash' | 'rebase';
|
|
46
47
|
|
|
47
48
|
export async function selectPostReviewAction(message: string): Promise<PostReviewAction> {
|
|
48
49
|
const inquirer = await getInquirer();
|
|
@@ -52,18 +53,47 @@ export async function selectPostReviewAction(message: string): Promise<PostRevie
|
|
|
52
53
|
name: 'action',
|
|
53
54
|
message,
|
|
54
55
|
choices: [
|
|
55
|
-
{ name: chalk.green('✔ Approve PR'),
|
|
56
|
-
{ name: chalk.yellow('⚑ Request Changes'),
|
|
56
|
+
{ name: chalk.green('✔ Approve PR'), value: 'approve', short: 'Approve' },
|
|
57
|
+
{ name: chalk.yellow('⚑ Request Changes'), value: 'request_changes', short: 'Request Changes' },
|
|
58
|
+
{ name: chalk.cyan('💬 Post as PR comment'), value: 'post_comment', short: 'Post Comment' },
|
|
57
59
|
new inquirer.Separator('─'.repeat(42)),
|
|
58
|
-
{ name: chalk.dim('↩ Skip'),
|
|
60
|
+
{ name: chalk.dim('↩ Skip'), value: 'skip', short: 'Skip' },
|
|
59
61
|
],
|
|
60
|
-
pageSize:
|
|
62
|
+
pageSize: 5,
|
|
61
63
|
loop: false,
|
|
62
64
|
}]);
|
|
63
65
|
|
|
64
66
|
return answers['action'] as PostReviewAction;
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
export async function confirmMerge(message: string): Promise<boolean> {
|
|
70
|
+
const inquirer = await getInquirer();
|
|
71
|
+
const answers = await inquirer.prompt([{
|
|
72
|
+
type: 'confirm',
|
|
73
|
+
name: 'ok',
|
|
74
|
+
message,
|
|
75
|
+
default: false,
|
|
76
|
+
}]);
|
|
77
|
+
return Boolean(answers['ok']);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function selectMergeStrategy(message: string): Promise<MergeStrategy> {
|
|
81
|
+
const inquirer = await getInquirer();
|
|
82
|
+
const answers = await inquirer.prompt([{
|
|
83
|
+
type: 'list',
|
|
84
|
+
name: 'strategy',
|
|
85
|
+
message,
|
|
86
|
+
choices: [
|
|
87
|
+
{ name: chalk.cyan('Squash and merge'), value: 'squash', short: 'Squash' },
|
|
88
|
+
{ name: 'Merge commit', value: 'merge', short: 'Merge' },
|
|
89
|
+
{ name: chalk.dim('Rebase and merge'), value: 'rebase', short: 'Rebase' },
|
|
90
|
+
],
|
|
91
|
+
pageSize: 3,
|
|
92
|
+
loop: false,
|
|
93
|
+
}]);
|
|
94
|
+
return answers['strategy'] as MergeStrategy;
|
|
95
|
+
}
|
|
96
|
+
|
|
67
97
|
export async function confirmReview(pr: PullRequest, label: string): Promise<boolean> {
|
|
68
98
|
const inquirer = await getInquirer();
|
|
69
99
|
|
package/src/git/github.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { PullRequest, PRDiff, PRFile } from '../types';
|
|
1
|
+
import { PullRequest, PRDiff, PRFile, ReviewResult } from '../types';
|
|
2
|
+
|
|
3
|
+
const AIV_COMMENT_TAG = '<!-- aiv-review:';
|
|
2
4
|
|
|
3
5
|
const GITHUB_API = 'https://api.github.com';
|
|
4
6
|
|
|
@@ -59,6 +61,52 @@ export class GithubClient {
|
|
|
59
61
|
return { pr, files, rawDiff };
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
async findAivReview(owner: string, repo: string, prNumber: number): Promise<ReviewResult | null> {
|
|
65
|
+
const url = `${GITHUB_API}/repos/${owner}/${repo}/issues/${prNumber}/comments?per_page=100`;
|
|
66
|
+
const res = await this.fetch(url);
|
|
67
|
+
if (!res.ok) return null;
|
|
68
|
+
|
|
69
|
+
const comments = await res.json() as any[];
|
|
70
|
+
for (const c of comments) {
|
|
71
|
+
const body: string = c.body ?? '';
|
|
72
|
+
if (!body.startsWith(AIV_COMMENT_TAG)) continue;
|
|
73
|
+
const end = body.indexOf(' -->');
|
|
74
|
+
if (end === -1) continue;
|
|
75
|
+
try {
|
|
76
|
+
const encoded = body.slice(AIV_COMMENT_TAG.length, end).trim();
|
|
77
|
+
return JSON.parse(Buffer.from(encoded, 'base64').toString('utf8')) as ReviewResult;
|
|
78
|
+
} catch { continue; }
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async postComment(owner: string, repo: string, prNumber: number, body: string): Promise<void> {
|
|
84
|
+
const url = `${GITHUB_API}/repos/${owner}/${repo}/issues/${prNumber}/comments`;
|
|
85
|
+
const { default: fetch } = await import('node-fetch');
|
|
86
|
+
const res = await fetch(url, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { ...this.headers, 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify({ body }),
|
|
90
|
+
}) as unknown as Response;
|
|
91
|
+
if (!res.ok) await this.throwError(res, `post comment on PR #${prNumber}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async mergePR(
|
|
95
|
+
owner: string,
|
|
96
|
+
repo: string,
|
|
97
|
+
prNumber: number,
|
|
98
|
+
mergeMethod: 'merge' | 'squash' | 'rebase' = 'squash',
|
|
99
|
+
): Promise<void> {
|
|
100
|
+
const url = `${GITHUB_API}/repos/${owner}/${repo}/pulls/${prNumber}/merge`;
|
|
101
|
+
const { default: fetch } = await import('node-fetch');
|
|
102
|
+
const res = await fetch(url, {
|
|
103
|
+
method: 'PUT',
|
|
104
|
+
headers: { ...this.headers, 'Content-Type': 'application/json' },
|
|
105
|
+
body: JSON.stringify({ merge_method: mergeMethod }),
|
|
106
|
+
}) as unknown as Response;
|
|
107
|
+
if (!res.ok) await this.throwError(res, `merge PR #${prNumber}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
62
110
|
async submitReview(
|
|
63
111
|
owner: string,
|
|
64
112
|
repo: string,
|
package/src/i18n/en.ts
CHANGED
|
@@ -175,6 +175,21 @@ export const en = {
|
|
|
175
175
|
postReviewFailed: (msg: string) => `Failed to submit review: ${msg}`,
|
|
176
176
|
postReviewRefreshing: 'Refreshing project context...',
|
|
177
177
|
postReviewRefreshed: 'Context updated.',
|
|
178
|
+
postReviewMergeConfirm: 'Merge this PR now?',
|
|
179
|
+
postReviewSelectMerge: 'Select merge strategy:',
|
|
180
|
+
postReviewMerging: (n: number) => `Merging PR #${n}...`,
|
|
181
|
+
postReviewMerged: (n: number) => ` PR #${n} merged.`,
|
|
182
|
+
postReviewMergeFailed: (msg: string) => `Failed to merge: ${msg}`,
|
|
183
|
+
postReviewMergeStrategyMerge: 'Merge commit',
|
|
184
|
+
postReviewMergeStrategySquash: 'Squash and merge',
|
|
185
|
+
postReviewMergeStrategyRebase: 'Rebase and merge',
|
|
186
|
+
postReviewPostingComment: 'Posting review as PR comment...',
|
|
187
|
+
postReviewCommentPosted: (n: number) => ` Review posted as comment on PR #${n}.`,
|
|
188
|
+
postReviewCommentFailed: (msg: string) => `Failed to post comment: ${msg}`,
|
|
189
|
+
reviewCachedFound: 'Found a previous aiv analysis on this PR.',
|
|
190
|
+
reviewCachedUse: 'Use cached analysis?',
|
|
191
|
+
reviewCachedUsing: 'Using cached analysis.',
|
|
192
|
+
reviewCachedSkipping: 'Running fresh analysis...',
|
|
178
193
|
|
|
179
194
|
// ── context generate ───────────────────────────────────────────────────────
|
|
180
195
|
contextGenerateTitle: '\n Generating context and rules with AI...\n',
|
package/src/i18n/es.ts
CHANGED
|
@@ -177,6 +177,21 @@ export const es: TranslationKeys = {
|
|
|
177
177
|
postReviewFailed: (msg: string) => `Error al enviar la revisión: ${msg}`,
|
|
178
178
|
postReviewRefreshing: 'Actualizando contexto del proyecto...',
|
|
179
179
|
postReviewRefreshed: 'Contexto actualizado.',
|
|
180
|
+
postReviewMergeConfirm: '¿Hacer merge de este PR ahora?',
|
|
181
|
+
postReviewSelectMerge: 'Selecciona la estrategia de merge:',
|
|
182
|
+
postReviewMerging: (n: number) => `Haciendo merge del PR #${n}...`,
|
|
183
|
+
postReviewMerged: (n: number) => ` PR #${n} mergeado.`,
|
|
184
|
+
postReviewMergeFailed: (msg: string) => `Error al hacer merge: ${msg}`,
|
|
185
|
+
postReviewMergeStrategyMerge: 'Merge commit',
|
|
186
|
+
postReviewMergeStrategySquash: 'Squash and merge',
|
|
187
|
+
postReviewMergeStrategyRebase: 'Rebase and merge',
|
|
188
|
+
postReviewPostingComment: 'Publicando revisión como comentario del PR...',
|
|
189
|
+
postReviewCommentPosted: (n: number) => ` Revisión publicada como comentario en el PR #${n}.`,
|
|
190
|
+
postReviewCommentFailed: (msg: string) => `Error al publicar comentario: ${msg}`,
|
|
191
|
+
reviewCachedFound: 'Se encontró un análisis previo de aiv en este PR.',
|
|
192
|
+
reviewCachedUse: '¿Usar el análisis en caché?',
|
|
193
|
+
reviewCachedUsing: 'Usando análisis en caché.',
|
|
194
|
+
reviewCachedSkipping: 'Ejecutando análisis nuevo...',
|
|
180
195
|
|
|
181
196
|
// ── context generate ───────────────────────────────────────────────────────
|
|
182
197
|
contextGenerateTitle: '\n Generando contexto y reglas con IA...\n',
|