git-ai-review 2.0.2 → 2.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 +17 -4
- package/dist/cli.js +20 -4
- package/dist/git.d.ts +1 -0
- package/dist/git.js +3 -0
- package/dist/review.d.ts +3 -0
- package/dist/review.js +19 -8
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ This package is licensed under **MPL-2.0**.
|
|
|
36
36
|
|
|
37
37
|
## What it does
|
|
38
38
|
|
|
39
|
-
`git-ai-review` uses your locally installed AI CLIs to review staged changes before they are committed. You can run it on demand with a single command (`npx git-ai-review review`) or install it as a git hook so every commit is reviewed automatically.
|
|
39
|
+
`git-ai-review` uses your locally installed AI CLIs to review staged changes before they are committed. You can run it on demand with a single command (`npx git-ai-review review`), review the diff between two branches (`npx git-ai-review diff main feature-branch`), or install it as a git hook so every commit is reviewed automatically.
|
|
40
40
|
|
|
41
41
|
- Uses your local **Codex CLI** as the primary reviewer.
|
|
42
42
|
- Falls back to **Copilot CLI**, then **Claude CLI** when the previous reviewer is unavailable.
|
|
@@ -55,9 +55,11 @@ This package is licensed under **MPL-2.0**.
|
|
|
55
55
|
At least one reviewer CLI must be installed and authenticated on each developer machine.
|
|
56
56
|
Recommended: install multiple for resilient fallback behavior.
|
|
57
57
|
|
|
58
|
-
- Codex CLI (`codex`) with active login (`codex login`) — primary reviewer
|
|
59
|
-
- Copilot CLI (`copilot`) with active login (`copilot auth`)
|
|
60
|
-
- Claude CLI (`claude`)
|
|
58
|
+
- Codex CLI (`codex`) with active login (`codex login`) or `OPENAI_API_KEY` env var — primary reviewer
|
|
59
|
+
- Copilot CLI (`copilot`) with active login (`copilot auth`) or `GITHUB_TOKEN` env var
|
|
60
|
+
- Claude CLI (`claude`) with `ANTHROPIC_API_KEY` env var or OAuth login
|
|
61
|
+
|
|
62
|
+
When an API token env var is set, the network preflight check for that reviewer is skipped — this is useful in CI environments or when interactive OAuth login is not available.
|
|
61
63
|
|
|
62
64
|
Default fallback chain: Codex → Copilot → Claude. If all are unavailable, review fails.
|
|
63
65
|
|
|
@@ -89,6 +91,10 @@ npx git-ai-review review --verbose
|
|
|
89
91
|
npx git-ai-review review --codex
|
|
90
92
|
npx git-ai-review review --copilot
|
|
91
93
|
npx git-ai-review review --claude
|
|
94
|
+
npx git-ai-review diff main
|
|
95
|
+
npx git-ai-review diff main feature-branch
|
|
96
|
+
npx git-ai-review diff main feature-branch --verbose
|
|
97
|
+
npx git-ai-review diff main feature-branch --codex
|
|
92
98
|
npx git-ai-review pre-commit
|
|
93
99
|
npx git-ai-review pre-commit --verbose
|
|
94
100
|
npx git-ai-review install
|
|
@@ -102,6 +108,9 @@ npm run git-ai-review -- review --verbose
|
|
|
102
108
|
npm run git-ai-review -- review --codex
|
|
103
109
|
npm run git-ai-review -- review --copilot
|
|
104
110
|
npm run git-ai-review -- review --claude
|
|
111
|
+
npm run git-ai-review -- diff main
|
|
112
|
+
npm run git-ai-review -- diff main feature-branch
|
|
113
|
+
npm run git-ai-review -- diff main feature-branch --verbose
|
|
105
114
|
npm run git-ai-review -- pre-commit
|
|
106
115
|
npm run git-ai-review -- pre-commit --verbose
|
|
107
116
|
npm run git-ai-review -- install
|
|
@@ -114,6 +123,7 @@ npm run git-ai-review -- install
|
|
|
114
123
|
- `review --codex`: force Codex reviewer only (skip Copilot/Claude fallback).
|
|
115
124
|
- `review --copilot`: force Copilot reviewer only (skip Codex/Claude).
|
|
116
125
|
- `review --claude`: force Claude reviewer only (skip Codex/Copilot).
|
|
126
|
+
- `diff <base> [head]`: run AI review for the diff between two branches/refs. Uses three-dot diff (`base...head`) to show changes introduced on `head` since it diverged from `base`. If `head` is omitted, defaults to `HEAD`. Supports the same `--verbose` and reviewer flags as `review`.
|
|
117
127
|
- `pre-commit`: run lock-aware pre-commit flow (recommended for hooks).
|
|
118
128
|
- `pre-commit --verbose`: same as pre-commit, but with detailed prompt/raw model logs.
|
|
119
129
|
- `install`: install hook script and set `core.hooksPath`.
|
|
@@ -138,6 +148,9 @@ In each target repository:
|
|
|
138
148
|
|
|
139
149
|
## Environment variables
|
|
140
150
|
|
|
151
|
+
- `ANTHROPIC_API_KEY`: Anthropic API token for Claude — skips network preflight when set.
|
|
152
|
+
- `OPENAI_API_KEY`: OpenAI API token for Codex — skips network preflight when set.
|
|
153
|
+
- `GITHUB_TOKEN`: GitHub token for Copilot — skips network preflight when set.
|
|
141
154
|
- `CODEX_BIN`: custom Codex executable path/name.
|
|
142
155
|
- `COPILOT_BIN`: custom Copilot executable path/name.
|
|
143
156
|
- `CLAUDE_BIN`: custom Claude executable path/name.
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* SPDX-License-Identifier: MPL-2.0
|
|
2
2
|
* Copyright (c) 2026 ai-review contributors
|
|
3
3
|
*/
|
|
4
|
-
import { runReview } from './review.js';
|
|
4
|
+
import { runReview, buildPrompt } from './review.js';
|
|
5
5
|
import { runPreCommit } from './precommit.js';
|
|
6
6
|
import { installPreCommitHook } from './install.js';
|
|
7
|
+
import { getDiffBetweenRefs } from './git.js';
|
|
7
8
|
const DEFAULT_CLI_DEPS = {
|
|
8
9
|
runReview,
|
|
9
10
|
runPreCommit,
|
|
@@ -15,9 +16,10 @@ function printHelp(log) {
|
|
|
15
16
|
log('git-ai-review <command> [options]');
|
|
16
17
|
log('');
|
|
17
18
|
log('Commands:');
|
|
18
|
-
log(' review
|
|
19
|
-
log('
|
|
20
|
-
log('
|
|
19
|
+
log(' review Run AI review for staged changes');
|
|
20
|
+
log(' diff <base> [head] Run AI review for diff between branches');
|
|
21
|
+
log(' pre-commit Run lock-aware pre-commit flow');
|
|
22
|
+
log(' install Install .githooks/pre-commit and set core.hooksPath');
|
|
21
23
|
log('');
|
|
22
24
|
log('Options:');
|
|
23
25
|
log(' --codex Force Codex reviewer only (skip Copilot/Claude fallback)');
|
|
@@ -55,6 +57,20 @@ export async function runCli(argv = process.argv.slice(2), deps = DEFAULT_CLI_DE
|
|
|
55
57
|
const result = await deps.runReview(cwd, { verbose, reviewer });
|
|
56
58
|
return result.pass ? 0 : 1;
|
|
57
59
|
}
|
|
60
|
+
if (command === 'diff') {
|
|
61
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
62
|
+
const base = positional[0];
|
|
63
|
+
const head = positional[1] || 'HEAD';
|
|
64
|
+
if (!base) {
|
|
65
|
+
deps.log('Error: diff command requires a base branch. Usage: git-ai-review diff <base> [head]');
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
const result = await deps.runReview(cwd, { verbose, reviewer }, {
|
|
69
|
+
getStagedDiff: () => getDiffBetweenRefs(base, head, cwd),
|
|
70
|
+
buildPrompt: (diff, promptCwd) => buildPrompt(diff, promptCwd, `Branch diff (${base}...${head})`),
|
|
71
|
+
});
|
|
72
|
+
return result.pass ? 0 : 1;
|
|
73
|
+
}
|
|
58
74
|
if (command === 'pre-commit') {
|
|
59
75
|
return await deps.runPreCommit(cwd, { verbose, reviewer });
|
|
60
76
|
}
|
package/dist/git.d.ts
CHANGED
package/dist/git.js
CHANGED
|
@@ -5,6 +5,9 @@ import { execFileSync } from 'node:child_process';
|
|
|
5
5
|
export function git(args, cwd = process.cwd()) {
|
|
6
6
|
return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim();
|
|
7
7
|
}
|
|
8
|
+
export function getDiffBetweenRefs(base, head, cwd = process.cwd()) {
|
|
9
|
+
return git(['diff', '--no-color', `${base}...${head}`, '--'], cwd);
|
|
10
|
+
}
|
|
8
11
|
export function getGitPath(name) {
|
|
9
12
|
try {
|
|
10
13
|
return git(['rev-parse', '--git-path', name]);
|
package/dist/review.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface VerboseRunnerOutput {
|
|
|
20
20
|
}
|
|
21
21
|
export declare function logVerboseRunnerOutput(output: VerboseRunnerOutput, log?: (message: string) => void): void;
|
|
22
22
|
export declare function needsShellForBinary(binary: string, osPlatform?: NodeJS.Platform): boolean;
|
|
23
|
+
export declare function hasApiToken(envVarName: string, env?: NodeJS.ProcessEnv): boolean;
|
|
24
|
+
export declare function checkPreflight(name: string, tokenEnvVar: string, urls: string[], verbose: boolean, reach?: (url: string) => boolean): boolean;
|
|
23
25
|
export declare function resolveCommandFromPath(candidate: string): string | null;
|
|
24
26
|
export declare function resolveCodexMacAppBinary(osPlatform?: NodeJS.Platform, pathExists?: (path: string) => boolean): string | null;
|
|
25
27
|
export interface ResolveBinaryOptions {
|
|
@@ -28,6 +30,7 @@ export interface ResolveBinaryOptions {
|
|
|
28
30
|
resolveCodexFallback?: () => string | null;
|
|
29
31
|
}
|
|
30
32
|
export declare function resolveBinary(envName: string, candidates: string[], options?: ResolveBinaryOptions): string | null;
|
|
33
|
+
export declare function buildPrompt(diff: string, cwd?: string, diffLabel?: string): string;
|
|
31
34
|
export declare function parseSubagentOutput(raw: string): ParsedReview;
|
|
32
35
|
export declare function buildSpawnOptions(cwd: string, env: Record<string, string>, osPlatform?: NodeJS.Platform): {
|
|
33
36
|
cwd: string;
|
package/dist/review.js
CHANGED
|
@@ -36,6 +36,20 @@ function canReach(url) {
|
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
export function hasApiToken(envVarName, env = process.env) {
|
|
40
|
+
return Boolean(env[envVarName]?.trim());
|
|
41
|
+
}
|
|
42
|
+
export function checkPreflight(name, tokenEnvVar, urls, verbose, reach = canReach) {
|
|
43
|
+
if (hasApiToken(tokenEnvVar)) {
|
|
44
|
+
if (verbose)
|
|
45
|
+
console.log(`${name}: using API token (${tokenEnvVar}) — skipping network preflight.`);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (urls.some(reach))
|
|
49
|
+
return true;
|
|
50
|
+
console.error(`${name}: network preflight failed — skipping.`);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
39
53
|
export function resolveCommandFromPath(candidate) {
|
|
40
54
|
if (!/^[a-zA-Z0-9_./-]+$/.test(candidate)) {
|
|
41
55
|
return null;
|
|
@@ -96,7 +110,7 @@ export function resolveBinary(envName, candidates, options = {}) {
|
|
|
96
110
|
function getStagedDiff() {
|
|
97
111
|
return git(['diff', '--cached', '--no-color']);
|
|
98
112
|
}
|
|
99
|
-
function buildPrompt(diff, cwd = process.cwd()) {
|
|
113
|
+
export function buildPrompt(diff, cwd = process.cwd(), diffLabel = 'Staged diff') {
|
|
100
114
|
const context = buildAgentsContext(cwd);
|
|
101
115
|
return [
|
|
102
116
|
...resolvePromptHeaderLines(),
|
|
@@ -114,7 +128,7 @@ function buildPrompt(diff, cwd = process.cwd()) {
|
|
|
114
128
|
'Respond with ONLY a JSON object matching this schema (no markdown, no backticks, no explanation):',
|
|
115
129
|
JSON.stringify(REVIEW_SCHEMA, null, 2),
|
|
116
130
|
'',
|
|
117
|
-
|
|
131
|
+
`${diffLabel}:`,
|
|
118
132
|
diff,
|
|
119
133
|
].join('\n');
|
|
120
134
|
}
|
|
@@ -208,8 +222,7 @@ async function runClaude(prompt, verbose) {
|
|
|
208
222
|
console.error('Claude: binary not found in PATH (or CLAUDE_BIN not set) — skipping.');
|
|
209
223
|
return { available: false };
|
|
210
224
|
}
|
|
211
|
-
if (!
|
|
212
|
-
console.error('Claude: network preflight failed (api.anthropic.com) — skipping.');
|
|
225
|
+
if (!checkPreflight('Claude', 'ANTHROPIC_API_KEY', ['https://api.anthropic.com'], verbose)) {
|
|
213
226
|
return { available: false };
|
|
214
227
|
}
|
|
215
228
|
try {
|
|
@@ -267,8 +280,7 @@ function runCodex(prompt, verbose) {
|
|
|
267
280
|
console.error('Codex: binary not found in PATH (or CODEX_BIN not set) — skipping.');
|
|
268
281
|
return { available: false };
|
|
269
282
|
}
|
|
270
|
-
if (!
|
|
271
|
-
console.error('Codex: network preflight failed — skipping.');
|
|
283
|
+
if (!checkPreflight('Codex', 'OPENAI_API_KEY', ['https://api.openai.com/v1/models', 'https://chatgpt.com'], verbose)) {
|
|
272
284
|
return { available: false };
|
|
273
285
|
}
|
|
274
286
|
const tempDir = mkdtempSync(join(tmpdir(), 'ai-review-codex-'));
|
|
@@ -326,8 +338,7 @@ function runCopilot(prompt, verbose) {
|
|
|
326
338
|
console.error('Copilot: binary not found in PATH (or COPILOT_BIN not set) — skipping.');
|
|
327
339
|
return { available: false };
|
|
328
340
|
}
|
|
329
|
-
if (!
|
|
330
|
-
console.error('Copilot: network preflight failed (api.github.com) — skipping.');
|
|
341
|
+
if (!checkPreflight('Copilot', 'GITHUB_TOKEN', ['https://api.github.com'], verbose)) {
|
|
331
342
|
return { available: false };
|
|
332
343
|
}
|
|
333
344
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-ai-review",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "Review your git diff with local Codex CLI or
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Review your git diff with local Codex CLI, Copilot CLI, or Claude CLI — run manually in one command or automatically as a git hook",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
7
7
|
"bin": {
|
|
@@ -28,10 +28,19 @@
|
|
|
28
28
|
"pre-commit",
|
|
29
29
|
"ai",
|
|
30
30
|
"codex",
|
|
31
|
-
"copilot"
|
|
31
|
+
"copilot",
|
|
32
|
+
"claude"
|
|
32
33
|
],
|
|
33
34
|
"author": "Sergei Poletaev <spylogsster@gmail.com>",
|
|
34
35
|
"license": "MPL-2.0",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/spylogsster/git-ai-review.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/spylogsster/git-ai-review#readme",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/spylogsster/git-ai-review/issues"
|
|
43
|
+
},
|
|
35
44
|
"devDependencies": {
|
|
36
45
|
"@types/node": "^22.13.1",
|
|
37
46
|
"tsx": "^4.20.6",
|