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 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 Run AI review for staged changes');
19
- log(' pre-commit Run lock-aware pre-commit flow');
20
- log(' install Install .githooks/pre-commit and set core.hooksPath');
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
@@ -1,2 +1,3 @@
1
1
  export declare function git(args: string[], cwd?: string): string;
2
+ export declare function getDiffBetweenRefs(base: string, head: string, cwd?: string): string;
2
3
  export declare function getGitPath(name: string): string;
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
- 'Staged diff:',
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 (!canReach('https://api.anthropic.com')) {
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 (!canReach('https://api.openai.com/v1/models') && !canReach('https://chatgpt.com')) {
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 (!canReach('https://api.github.com')) {
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.2",
4
- "description": "Review your git diff with local Codex CLI or Copilot CLI — run manually in one command or automatically as a git hook",
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",