agent-gauntlet 0.1.11 → 0.2.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
@@ -1,122 +1,90 @@
1
1
  # Agent Gauntlet
2
2
 
3
- Agent Gauntlet is a configurable “quality gate” runner for AI-assisted development workflows.
3
+ > Don't just review the agent's code put it through the gauntlet.
4
4
 
5
- You define:
6
- - **Entry points** (paths in your repo)
7
- - **Check gates** (shell commands: tests, linters, typecheck, etc.)
8
- - **Review gates** (AI CLI tools run on diffs, with regex-based pass/fail)
5
+ Agent Gauntlet is a configurable “feedback loop” runner for AI-assisted development workflows.
9
6
 
10
- Then `agent-gauntlet` detects which parts of the repo changed and runs the relevant gates.
7
+ You configure which paths in your repo should trigger which validations — shell commands like tests and linters, plus AI-powered code reviews. When files change, Gauntlet automatically runs the relevant validations and reports results.
11
8
 
12
- ### AI CLI Integration
9
+ For AI reviews, it uses the CLI tool of your choice: Gemini, Codex, Claude Code, GitHub Copilot, or Cursor.
13
10
 
14
- Agent Gauntlet is designed to be "tool-agnostic" by leveraging the AI CLI tools you already have installed (such as `gemini`, `codex`, or `claude`). Instead of managing its own API keys or subscriptions, it invokes these CLIs directly. This allows you to:
15
- - **Leverage existing subscriptions**: Use the tools you are already paying for.
16
- - **Dynamic Context**: Agents are invoked in a non-interactive, read-only mode where they can use their own file-reading and search tools to pull additional context from your repository as needed.
17
- - **Security**: By using standard CLI tools with strict flags (like `--sandbox` or `--allowed-tools`), Agent Gauntlet ensures that agents can read your code to review it without being able to modify your files or escape the repository scope.
11
+ ## Features
18
12
 
19
- ### Requirements
13
+ - **Agent validation loop**: Keep your coding agent on track with automated feedback loops. Detect problems — deterministically and/or non-deterministically — and let your agent fix and Gauntlet verify.
14
+ - **Multi-agent collaboration**: Enable one AI agent to automatically request code reviews from another. For example, if Claude made changes, Gauntlet can request a review from Codex or Gemini — spreading token usage across your subscriptions instead of burning through one.
15
+ - **Leverage existing subscriptions**: Agent Gauntlet is *free* and tool-agnostic, leveraging the AI CLI tools you already have installed.
16
+ - **Easy CI setup**: Define your checks once, run them locally and in GitHub.
20
17
 
21
- - **Bun** (Required runtime, v1.0.0+)
22
- - **git** (change detection and diffs)
23
- - For review gates: one or more supported AI CLIs installed (`gemini`, `codex`, `claude`, `github-copilot`, `cursor`). For the full list of tools and how they are used, see [CLI Invocation Details](docs/cli-invocation-details.md)
18
+ ## Usage Patterns
24
19
 
25
- ### Installation
20
+ Agent Gauntlet supports three primary usage patterns, each suited for different development workflows:
21
+ 1. Run CLI: `agent-gauntlet run`
22
+ 2. Run agent command: `/gauntlet`
23
+ 3. Automatically run after agent completes task
26
24
 
27
- You can install `agent-gauntlet` globally using `npm` or `bun` (Bun must be installed on the system in both cases):
25
+ The use cases below illustrate when each of these patterns may be used.
28
26
 
29
- **Using Bun (Recommended):**
30
- ```bash
31
- bun add -g agent-gauntlet
32
- ```
27
+ ### 1. Planning Mode
33
28
 
34
- **Using npm:**
35
- ```bash
36
- npm install -g agent-gauntlet
37
- ```
29
+ **Use case:** Generate and review high-level implementation plans before coding.
38
30
 
39
- ### Quick start
31
+ **Problem Gauntlet solves:** Catch architectural issues and requirement misunderstandings before coding to avoid costly rework.
40
32
 
41
- - **Initialize configuration**
33
+ **Workflow:**
42
34
 
43
- ```bash
44
- agent-gauntlet init
45
- ```
35
+ 1. Create a plan document in your project directory
36
+ 2. Run `agent-gauntlet run` from the terminal
37
+ 3. Gauntlet detects the new or modified plan and invokes configured AI CLIs to review it
38
+ 4. *(Optional)* Ask your assistant to refine the plan based on review feedback
46
39
 
47
- - **Run gates**
40
+ **Note:** Review configuration and prompts are fully customizable. Example prompt: *"Review this plan for completeness and potential issues."*
48
41
 
49
- ```bash
50
- agent-gauntlet
51
- ```
42
+ ### 2. AI-Assisted Development
52
43
 
53
- ### Development
44
+ **Use case:** Pair with an AI coding assistant to implement features with continuous quality checks.
54
45
 
55
- - **Install dependencies**
46
+ **Problem Gauntlet solves:** Catch AI-introduced bugs and quality issues through automated checks and multi-LLM review.
56
47
 
57
- ```bash
58
- bun install
59
- ```
48
+ **Workflow:**
60
49
 
61
- - **Build the CLI binary**
50
+ 1. Collaborate with your assistant to implement code changes
51
+ 2. Run `/gauntlet` from chat
52
+ 3. Gauntlet detects changed files and runs configured checks (linter, tests, type checking, etc.)
53
+ 4. Simultaneously, Gauntlet invokes AI CLIs for code review
54
+ 5. Assistant reviews results, fixes identified issues, and runs `agent-gauntlet rerun`
55
+ 6. Gauntlet verifies fixes and checks for new issues
56
+ 7. Process repeats automatically (up to 3 reruns) until all gates pass
62
57
 
63
- ```bash
64
- bun run build
65
- ```
58
+ ### 3. Agentic Implementation
66
59
 
67
- ### Basic usage
60
+ **Use case:** Delegate well-defined tasks to a coding agent for autonomous implementation.
68
61
 
69
- - **Run gates for detected changes**
62
+ **Problem Gauntlet solves:** Enable autonomous agent development with built-in quality gates, eliminating the validation gap when humans aren't in the loop.
70
63
 
71
- ```bash
72
- agent-gauntlet run
73
- ```
64
+ **Workflow:**
74
65
 
75
- - **Run only one gate name** (runs it across all applicable entry points)
66
+ 1. Configure your agent to automatically run `/gauntlet` after completing implementation:
67
+ - **Rules files:** Add to `.cursorrules`, `AGENT.md`, or similar
68
+ - **Custom commands:** Create a `/my-dev-workflow` that includes gauntlet
69
+ - **Git hooks:** Use pre-commit hooks to trigger gauntlet
70
+ - **Agent hooks:** Leverage platform features (e.g., Claude's Stop event)
71
+ 2. Assign the task to your agent and step away
72
+ 3. When you return: the task is complete, reviewed by a different LLM, all issues fixed, and CI checks passing
76
73
 
77
- ```bash
78
- agent-gauntlet run --gate lint
79
- ```
74
+ **Benefit:** Fully autonomous quality assurance without manual intervention.
80
75
 
81
- - **List configured gates and entry points**
76
+ ## Quick Start
82
77
 
83
- ```bash
84
- agent-gauntlet list
85
- ```
78
+ 1. **Install**: `bun add -g agent-gauntlet`
79
+ 2. **Initialize**: `agent-gauntlet init`
80
+ 3. **Run**: `agent-gauntlet run`
86
81
 
87
- - **Check which AI CLIs are installed**
82
+ For basic usage and configuration guide, see the [Quick Start Guide](docs/quick-start.md).
88
83
 
89
- ```bash
90
- agent-gauntlet health
91
- ```
92
-
93
- ### Agent loop rules
94
-
95
- The `.gauntlet/run_gauntlet.md` file defines how AI agents should interact with the gauntlet. By default, agents will terminate after 4 runs (1 initial + 3 fix attempts). You can increase this limit by manually editing the termination conditions in that file.
96
-
97
- ### Configuration layout
98
-
99
- Agent Gauntlet loads configuration from your repository:
100
-
101
- ```text
102
- .gauntlet/
103
- config.yml
104
- checks/
105
- *.yml
106
- reviews/
107
- *.md
108
- ```
109
-
110
- - **Project config**: `.gauntlet/config.yml`
111
- - **Check definitions**: `.gauntlet/checks/*.yml`
112
- - **Review definitions**: `.gauntlet/reviews/*.md` (filename is the review gate name)
113
-
114
- ### Logs
115
-
116
- Each job writes a log file under `log_dir` (default: `.gauntlet_logs/`). Filenames are derived from the job id (sanitized).
117
-
118
- ### Documentation
84
+ ## Documentation
119
85
 
86
+ - [Quick Start Guide](docs/quick-start.md) — installation, basic usage, and config layout
120
87
  - [User Guide](docs/user-guide.md) — full usage details
121
88
  - [Configuration Reference](docs/config-reference.md) — all configuration fields + defaults
122
89
  - [CLI Invocation Details](docs/cli-invocation-details.md) — how we securely invoke AI CLIs
90
+ - [Development Guide](docs/development.md) — how to build and develop this project
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-gauntlet",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "description": "A CLI tool for testing AI coding agents",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Paul Caplan",
@@ -0,0 +1,4 @@
1
+ declare module "*.yml" {
2
+ const content: string;
3
+ export default content;
4
+ }
@@ -139,7 +139,7 @@ export class GitHubCopilotAdapter implements CLIAdapter {
139
139
  // because copilot requires stdin input. The tmpFile path is system-controlled
140
140
  // (os.tmpdir() + Date.now() + process.pid), not user-supplied, eliminating injection risk.
141
141
  // Double quotes handle paths with spaces. This pattern matches claude.ts:131.
142
- const cmd = `cat "${tmpFile}" | copilot --allow-tool shell(cat) --allow-tool shell(grep) --allow-tool shell(ls) --allow-tool shell(find) --allow-tool shell(head) --allow-tool shell(tail)`;
142
+ const cmd = `cat "${tmpFile}" | copilot --allow-tool "shell(cat)" --allow-tool "shell(grep)" --allow-tool "shell(ls)" --allow-tool "shell(find)" --allow-tool "shell(head)" --allow-tool "shell(tail)"`;
143
143
  const { stdout } = await execAsync(cmd, {
144
144
  timeout: opts.timeoutMs,
145
145
  maxBuffer: MAX_BUFFER_BYTES,
@@ -13,6 +13,10 @@ export function registerCheckCommand(program: Command): void {
13
13
  program
14
14
  .command("check")
15
15
  .description("Run only applicable checks for detected changes")
16
+ .option(
17
+ "-b, --base-branch <branch>",
18
+ "Override base branch for change detection",
19
+ )
16
20
  .option("-g, --gate <name>", "Run specific check gate only")
17
21
  .option("-c, --commit <sha>", "Use diff for a specific commit")
18
22
  .option(
@@ -26,7 +30,17 @@ export function registerCheckCommand(program: Command): void {
26
30
  // Rotate logs before starting
27
31
  await rotateLogs(config.project.log_dir);
28
32
 
29
- const changeDetector = new ChangeDetector(config.project.base_branch, {
33
+ // Determine effective base branch
34
+ // Priority: CLI override > CI env var > config
35
+ const effectiveBaseBranch =
36
+ options.baseBranch ||
37
+ (process.env.GITHUB_BASE_REF &&
38
+ (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true")
39
+ ? process.env.GITHUB_BASE_REF
40
+ : null) ||
41
+ config.project.base_branch;
42
+
43
+ const changeDetector = new ChangeDetector(effectiveBaseBranch, {
30
44
  commit: options.commit,
31
45
  uncommitted: options.uncommitted,
32
46
  });
@@ -65,7 +79,14 @@ export function registerCheckCommand(program: Command): void {
65
79
 
66
80
  const logger = new Logger(config.project.log_dir);
67
81
  const reporter = new ConsoleReporter();
68
- const runner = new Runner(config, logger, reporter);
82
+ const runner = new Runner(
83
+ config,
84
+ logger,
85
+ reporter,
86
+ undefined,
87
+ undefined,
88
+ effectiveBaseBranch,
89
+ );
69
90
 
70
91
  const success = await runner.run(jobs);
71
92
  process.exit(success ? 0 : 1);
@@ -0,0 +1,15 @@
1
+ import type { Command } from "commander";
2
+ import { initCI } from "./init.js";
3
+ import { listJobs } from "./list-jobs.js";
4
+
5
+ export function registerCICommand(program: Command): void {
6
+ const ci = program.command("ci").description("Manage CI integration");
7
+
8
+ ci.command("init")
9
+ .description("Initialize CI workflow and configuration")
10
+ .action(initCI);
11
+
12
+ ci.command("list-jobs")
13
+ .description("List CI jobs (used by workflow)")
14
+ .action(listJobs);
15
+ }
@@ -0,0 +1,96 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import chalk from "chalk";
4
+ import YAML from "yaml";
5
+ import { loadCIConfig } from "../../config/ci-loader.js";
6
+ import type { CIConfig } from "../../config/types.js";
7
+ import workflowTemplate from "../../templates/workflow.yml" with {
8
+ type: "text",
9
+ };
10
+
11
+ export async function initCI(): Promise<void> {
12
+ const workflowDir = path.join(process.cwd(), ".github", "workflows");
13
+ const workflowPath = path.join(workflowDir, "gauntlet.yml");
14
+ const gauntletDir = path.join(process.cwd(), ".gauntlet");
15
+ const ciConfigPath = path.join(gauntletDir, "ci.yml");
16
+
17
+ // 1. Ensure .gauntlet/ci.yml exists
18
+ if (!(await fileExists(ciConfigPath))) {
19
+ console.log(chalk.yellow("Creating starter .gauntlet/ci.yml..."));
20
+ await fs.mkdir(gauntletDir, { recursive: true });
21
+ const starterContent = `# CI Configuration for Agent Gauntlet
22
+ # Define runtimes, services, and which checks to run in CI.
23
+
24
+ runtimes:
25
+ # ruby:
26
+ # version: "3.3"
27
+ # bundler_cache: true
28
+
29
+ services:
30
+ # postgres:
31
+ # image: postgres:16
32
+ # ports: ["5432:5432"]
33
+
34
+ setup:
35
+ # - name: Global Setup
36
+ # run: echo "Setting up..."
37
+
38
+ checks:
39
+ # - name: linter
40
+ # requires_runtimes: [ruby]
41
+ `;
42
+ await fs.writeFile(ciConfigPath, starterContent);
43
+ } else {
44
+ console.log(chalk.dim("Found existing .gauntlet/ci.yml"));
45
+ }
46
+
47
+ // 2. Load CI config to get services
48
+ let ciConfig: CIConfig | undefined;
49
+ try {
50
+ ciConfig = await loadCIConfig();
51
+ } catch (_e) {
52
+ console.warn(
53
+ chalk.yellow(
54
+ "Could not load CI config to inject services. Workflow will have no services defined.",
55
+ ),
56
+ );
57
+ }
58
+
59
+ // 3. Generate workflow file
60
+ console.log(chalk.dim(`Generating ${workflowPath}...`));
61
+ await fs.mkdir(workflowDir, { recursive: true });
62
+
63
+ let templateContent = workflowTemplate;
64
+
65
+ // Inject services
66
+ if (ciConfig?.services && Object.keys(ciConfig.services).length > 0) {
67
+ const servicesYaml = YAML.stringify({ services: ciConfig.services });
68
+ // Indent services
69
+ const indentedServices = servicesYaml
70
+ .split("\n")
71
+ .map((line) => (line.trim() ? ` ${line}` : line))
72
+ .join("\n");
73
+
74
+ templateContent = templateContent.replace(
75
+ " # Services will be injected here by agent-gauntlet",
76
+ indentedServices,
77
+ );
78
+ } else {
79
+ templateContent = templateContent.replace(
80
+ " # Services will be injected here by agent-gauntlet\n",
81
+ "",
82
+ );
83
+ }
84
+
85
+ await fs.writeFile(workflowPath, templateContent);
86
+ console.log(chalk.green("Successfully generated GitHub Actions workflow!"));
87
+ }
88
+
89
+ async function fileExists(path: string): Promise<boolean> {
90
+ try {
91
+ const stat = await fs.stat(path);
92
+ return stat.isFile();
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
@@ -0,0 +1,89 @@
1
+ import { loadCIConfig } from "../../config/ci-loader.js";
2
+ import { loadConfig } from "../../config/loader.js";
3
+ import type { CISetupStep } from "../../config/types.js";
4
+ import { EntryPointExpander } from "../../core/entry-point.js";
5
+
6
+ export async function listJobs(): Promise<void> {
7
+ try {
8
+ const config = await loadConfig();
9
+ const ciConfig = await loadCIConfig();
10
+ const expander = new EntryPointExpander();
11
+ const expandedEntryPoints = await expander.expandAll(
12
+ config.project.entry_points,
13
+ );
14
+
15
+ const matrixJobs = [];
16
+ const seenJobs = new Set<string>();
17
+
18
+ const globalSetup = formatSetup(ciConfig.setup || undefined);
19
+
20
+ if (ciConfig.checks) {
21
+ for (const ep of expandedEntryPoints) {
22
+ // Get checks enabled for this entry point
23
+ const allowedChecks = new Set(ep.config.checks || []);
24
+
25
+ for (const check of ciConfig.checks) {
26
+ if (allowedChecks.has(check.name)) {
27
+ // Check definition from .gauntlet/checks/*.yml
28
+ const checkDef = config.checks[check.name];
29
+ if (!checkDef) {
30
+ console.warn(
31
+ `Warning: Check '${check.name}' found in CI config but not defined in checks/*.yml`,
32
+ );
33
+ continue;
34
+ }
35
+
36
+ const workingDirectory = checkDef.working_directory || ep.path;
37
+ // Include entry point in key to ensure each entry point/check pair is distinct
38
+ const jobKey = `${ep.path}:${check.name}:${workingDirectory}`;
39
+
40
+ // Skip if we've already created a job for this exact entry point/check combination
41
+ if (seenJobs.has(jobKey)) {
42
+ continue;
43
+ }
44
+ seenJobs.add(jobKey);
45
+
46
+ const id = `${check.name}-${ep.path.replace(/\//g, "-")}`;
47
+
48
+ matrixJobs.push({
49
+ id,
50
+ name: check.name,
51
+ entry_point: ep.path,
52
+ working_directory: workingDirectory,
53
+ command: checkDef.command,
54
+ runtimes: check.requires_runtimes || [],
55
+ services: check.requires_services || [],
56
+ setup: formatSetup(check.setup || undefined),
57
+ global_setup: globalSetup,
58
+ });
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ const output = {
65
+ matrix: matrixJobs,
66
+ services: ciConfig.services || {},
67
+ runtimes: ciConfig.runtimes || {},
68
+ };
69
+
70
+ console.log(JSON.stringify(output));
71
+ } catch (e) {
72
+ console.error("Error generating CI jobs:", e);
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ const formatSetup = (steps: CISetupStep[] | null | undefined): string => {
78
+ if (!steps || steps.length === 0) return "";
79
+ return steps
80
+ .map((s) => {
81
+ const cmd = s.working_directory
82
+ ? `(cd "${s.working_directory}" && ${s.run})`
83
+ : s.run;
84
+ return `echo "::group::${s.name}"
85
+ ${cmd}
86
+ echo "::endgroup::"`;
87
+ })
88
+ .join("\n");
89
+ };
@@ -11,6 +11,10 @@ export function registerDetectCommand(program: Command): void {
11
11
  .description(
12
12
  "Show what gates would run for detected changes (without executing them)",
13
13
  )
14
+ .option(
15
+ "-b, --base-branch <branch>",
16
+ "Override base branch for change detection",
17
+ )
14
18
  .option("-c, --commit <sha>", "Use diff for a specific commit")
15
19
  .option(
16
20
  "-u, --uncommitted",
@@ -19,7 +23,18 @@ export function registerDetectCommand(program: Command): void {
19
23
  .action(async (options) => {
20
24
  try {
21
25
  const config = await loadConfig();
22
- const changeDetector = new ChangeDetector(config.project.base_branch, {
26
+
27
+ // Determine effective base branch
28
+ // Priority: CLI override > CI env var > config
29
+ const effectiveBaseBranch =
30
+ options.baseBranch ||
31
+ (process.env.GITHUB_BASE_REF &&
32
+ (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true")
33
+ ? process.env.GITHUB_BASE_REF
34
+ : null) ||
35
+ config.project.base_branch;
36
+
37
+ const changeDetector = new ChangeDetector(effectiveBaseBranch, {
23
38
  commit: options.commit,
24
39
  uncommitted: options.uncommitted,
25
40
  });
@@ -33,7 +33,7 @@ describe("Health Command", () => {
33
33
  path.join(GAUNTLET_DIR, "config.yml"),
34
34
  `
35
35
  base_branch: origin/main
36
- log_dir: .gauntlet_logs
36
+ log_dir: gauntlet_logs
37
37
  cli:
38
38
  default_preference:
39
39
  - gemini
@@ -24,6 +24,7 @@ export function registerHelpCommand(program: Command): void {
24
24
  console.log(" list List configured gates");
25
25
  console.log(" health Check CLI tool availability");
26
26
  console.log(" init Initialize .gauntlet configuration");
27
+ console.log(" ci CI integration commands (init, list-jobs)");
27
28
  console.log(" help Show this help message\n");
28
29
  console.log(
29
30
  "For more information, see: https://github.com/your-repo/agent-gauntlet",
@@ -1,4 +1,5 @@
1
1
  export { registerCheckCommand } from "./check.js";
2
+ export { registerCICommand } from "./ci/index.js";
2
3
  export { registerDetectCommand } from "./detect.js";
3
4
  export { registerHealthCommand } from "./health.js";
4
5
  export { registerHelpCommand } from "./help.js";
@@ -40,6 +40,8 @@ mock.module("../cli-adapters/index.js", () => ({
40
40
  getAllAdapters: () => mockAdapters,
41
41
  getProjectCommandAdapters: () => mockAdapters,
42
42
  getUserCommandAdapters: () => [],
43
+ getAdapter: (name: string) => mockAdapters.find((a) => a.name === name),
44
+ getValidCLITools: () => mockAdapters.map((a) => a.name),
43
45
  }));
44
46
 
45
47
  // Import after mocking
@@ -16,7 +16,7 @@ allowed-tools: Bash
16
16
  Execute the autonomous verification suite.
17
17
 
18
18
  1. Run \`agent-gauntlet run\`.
19
- 2. If it fails, read the log files in \`.gauntlet_logs/\` to understand exactly what went wrong.
19
+ 2. If it fails, read the log files in \`gauntlet_logs/\` to understand exactly what went wrong.
20
20
  3. Fix any code or logic errors found by the tools or AI reviewers, prioritizing higher-priority violations (critical > high > medium > low).
21
21
  4. If you disagree with AI reviewer feedback, briefly explain your reasoning in the code comments rather than ignoring it silently.
22
22
  5. Do NOT commit your changes yet—keep them uncommitted so the rerun command can review them.
@@ -336,7 +336,7 @@ function generateConfigYml(config: InitConfig): string {
336
336
  - code-quality`;
337
337
 
338
338
  return `base_branch: origin/main
339
- log_dir: .gauntlet_logs
339
+ log_dir: gauntlet_logs
340
340
 
341
341
  # Run gates in parallel when possible (default: true)
342
342
  # allow_parallel: true
@@ -37,7 +37,7 @@ describe("List Command", () => {
37
37
  path.join(GAUNTLET_DIR, "config.yml"),
38
38
  `
39
39
  base_branch: origin/main
40
- log_dir: .gauntlet_logs
40
+ log_dir: gauntlet_logs
41
41
  cli:
42
42
  default_preference:
43
43
  - gemini
@@ -19,6 +19,10 @@ export function registerRerunCommand(program: Command): void {
19
19
  .description(
20
20
  "Rerun gates (checks & reviews) with previous failures as context (defaults to uncommitted changes)",
21
21
  )
22
+ .option(
23
+ "-b, --base-branch <branch>",
24
+ "Override base branch for change detection",
25
+ )
22
26
  .option("-g, --gate <name>", "Run specific gate only")
23
27
  .option(
24
28
  "-c, --commit <sha>",
@@ -71,6 +75,16 @@ export function registerRerunCommand(program: Command): void {
71
75
  // Rotate logs before starting the new run
72
76
  await rotateLogs(config.project.log_dir);
73
77
 
78
+ // Determine effective base branch
79
+ // Priority: CLI override > CI env var > config
80
+ const effectiveBaseBranch =
81
+ options.baseBranch ||
82
+ (process.env.GITHUB_BASE_REF &&
83
+ (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true")
84
+ ? process.env.GITHUB_BASE_REF
85
+ : null) ||
86
+ config.project.base_branch;
87
+
74
88
  // Detect changes (default to uncommitted unless --commit is specified)
75
89
  // Note: Rerun defaults to uncommitted changes for faster iteration loops,
76
90
  // unlike 'run' which defaults to base_branch comparison.
@@ -80,7 +94,7 @@ export function registerRerunCommand(program: Command): void {
80
94
  };
81
95
 
82
96
  const changeDetector = new ChangeDetector(
83
- config.project.base_branch,
97
+ effectiveBaseBranch,
84
98
  changeOptions,
85
99
  );
86
100
  const expander = new EntryPointExpander();
@@ -132,6 +146,7 @@ export function registerRerunCommand(program: Command): void {
132
146
  reporter,
133
147
  failuresMap, // Pass previous failures map
134
148
  changeOptions, // Pass change detection options
149
+ effectiveBaseBranch, // Pass effective base branch
135
150
  );
136
151
 
137
152
  const success = await runner.run(jobs);
@@ -13,6 +13,10 @@ export function registerReviewCommand(program: Command): void {
13
13
  program
14
14
  .command("review")
15
15
  .description("Run only applicable reviews for detected changes")
16
+ .option(
17
+ "-b, --base-branch <branch>",
18
+ "Override base branch for change detection",
19
+ )
16
20
  .option("-g, --gate <name>", "Run specific review gate only")
17
21
  .option("-c, --commit <sha>", "Use diff for a specific commit")
18
22
  .option(
@@ -26,7 +30,17 @@ export function registerReviewCommand(program: Command): void {
26
30
  // Rotate logs before starting
27
31
  await rotateLogs(config.project.log_dir);
28
32
 
29
- const changeDetector = new ChangeDetector(config.project.base_branch, {
33
+ // Determine effective base branch
34
+ // Priority: CLI override > CI env var > config
35
+ const effectiveBaseBranch =
36
+ options.baseBranch ||
37
+ (process.env.GITHUB_BASE_REF &&
38
+ (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true")
39
+ ? process.env.GITHUB_BASE_REF
40
+ : null) ||
41
+ config.project.base_branch;
42
+
43
+ const changeDetector = new ChangeDetector(effectiveBaseBranch, {
30
44
  commit: options.commit,
31
45
  uncommitted: options.uncommitted,
32
46
  });
@@ -65,7 +79,14 @@ export function registerReviewCommand(program: Command): void {
65
79
 
66
80
  const logger = new Logger(config.project.log_dir);
67
81
  const reporter = new ConsoleReporter();
68
- const runner = new Runner(config, logger, reporter);
82
+ const runner = new Runner(
83
+ config,
84
+ logger,
85
+ reporter,
86
+ undefined,
87
+ undefined,
88
+ effectiveBaseBranch,
89
+ );
69
90
 
70
91
  const success = await runner.run(jobs);
71
92
  process.exit(success ? 0 : 1);