agent-gauntlet 0.1.10 → 0.1.12

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.
Files changed (55) hide show
  1. package/README.md +55 -87
  2. package/package.json +4 -2
  3. package/src/bun-plugins.d.ts +4 -0
  4. package/src/cli-adapters/claude.ts +139 -108
  5. package/src/cli-adapters/codex.ts +141 -117
  6. package/src/cli-adapters/cursor.ts +152 -0
  7. package/src/cli-adapters/gemini.ts +171 -139
  8. package/src/cli-adapters/github-copilot.ts +153 -0
  9. package/src/cli-adapters/index.ts +77 -48
  10. package/src/commands/check.test.ts +24 -20
  11. package/src/commands/check.ts +86 -59
  12. package/src/commands/ci/index.ts +15 -0
  13. package/src/commands/ci/init.ts +96 -0
  14. package/src/commands/ci/list-jobs.ts +78 -0
  15. package/src/commands/detect.test.ts +38 -32
  16. package/src/commands/detect.ts +89 -61
  17. package/src/commands/health.test.ts +67 -53
  18. package/src/commands/health.ts +167 -145
  19. package/src/commands/help.test.ts +37 -37
  20. package/src/commands/help.ts +31 -22
  21. package/src/commands/index.ts +10 -9
  22. package/src/commands/init.test.ts +120 -107
  23. package/src/commands/init.ts +514 -417
  24. package/src/commands/list.test.ts +87 -70
  25. package/src/commands/list.ts +28 -24
  26. package/src/commands/rerun.ts +157 -119
  27. package/src/commands/review.test.ts +26 -20
  28. package/src/commands/review.ts +86 -59
  29. package/src/commands/run.test.ts +22 -20
  30. package/src/commands/run.ts +85 -58
  31. package/src/commands/shared.ts +44 -35
  32. package/src/config/ci-loader.ts +33 -0
  33. package/src/config/ci-schema.ts +52 -0
  34. package/src/config/loader.test.ts +112 -90
  35. package/src/config/loader.ts +132 -123
  36. package/src/config/schema.ts +48 -47
  37. package/src/config/types.ts +28 -13
  38. package/src/config/validator.ts +521 -454
  39. package/src/core/change-detector.ts +122 -104
  40. package/src/core/entry-point.test.ts +60 -62
  41. package/src/core/entry-point.ts +120 -74
  42. package/src/core/job.ts +69 -59
  43. package/src/core/runner.ts +264 -230
  44. package/src/gates/check.ts +78 -69
  45. package/src/gates/result.ts +7 -7
  46. package/src/gates/review.test.ts +277 -138
  47. package/src/gates/review.ts +724 -561
  48. package/src/index.ts +18 -15
  49. package/src/output/console.ts +253 -214
  50. package/src/output/logger.ts +66 -52
  51. package/src/templates/run_gauntlet.template.md +18 -0
  52. package/src/templates/workflow.yml +77 -0
  53. package/src/utils/diff-parser.ts +64 -62
  54. package/src/utils/log-parser.ts +227 -206
  55. package/src/utils/sanitizer.ts +1 -1
@@ -1,23 +1,25 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
2
- import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { loadConfig } from './loader.js';
5
-
6
- const TEST_DIR = path.join(process.cwd(), 'test-env-' + Date.now());
7
- const GAUNTLET_DIR = path.join(TEST_DIR, '.gauntlet');
8
- const CHECKS_DIR = path.join(GAUNTLET_DIR, 'checks');
9
- const REVIEWS_DIR = path.join(GAUNTLET_DIR, 'reviews');
10
-
11
- describe('Config Loader', () => {
12
- beforeAll(async () => {
13
- // Setup directory structure
14
- await fs.mkdir(TEST_DIR);
15
- await fs.mkdir(GAUNTLET_DIR);
16
- await fs.mkdir(CHECKS_DIR);
17
- await fs.mkdir(REVIEWS_DIR);
18
-
19
- // Write config.yml
20
- await fs.writeFile(path.join(GAUNTLET_DIR, 'config.yml'), `
1
+ import { afterAll, beforeAll, describe, expect, it } from "bun:test";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { loadConfig } from "./loader.js";
5
+
6
+ const TEST_DIR = path.join(process.cwd(), `test-env-${Date.now()}`);
7
+ const GAUNTLET_DIR = path.join(TEST_DIR, ".gauntlet");
8
+ const CHECKS_DIR = path.join(GAUNTLET_DIR, "checks");
9
+ const REVIEWS_DIR = path.join(GAUNTLET_DIR, "reviews");
10
+
11
+ describe("Config Loader", () => {
12
+ beforeAll(async () => {
13
+ // Setup directory structure
14
+ await fs.mkdir(TEST_DIR);
15
+ await fs.mkdir(GAUNTLET_DIR);
16
+ await fs.mkdir(CHECKS_DIR);
17
+ await fs.mkdir(REVIEWS_DIR);
18
+
19
+ // Write config.yml
20
+ await fs.writeFile(
21
+ path.join(GAUNTLET_DIR, "config.yml"),
22
+ `
21
23
  base_branch: origin/dev
22
24
  log_dir: test_logs
23
25
  cli:
@@ -31,99 +33,119 @@ entry_points:
31
33
  - lint
32
34
  reviews:
33
35
  - security
34
- `);
36
+ `,
37
+ );
35
38
 
36
- // Write a check definition
37
- await fs.writeFile(path.join(CHECKS_DIR, 'lint.yml'), `
39
+ // Write a check definition
40
+ await fs.writeFile(
41
+ path.join(CHECKS_DIR, "lint.yml"),
42
+ `
38
43
  name: lint
39
44
  command: npm run lint
40
45
  working_directory: .
41
- `);
46
+ `,
47
+ );
42
48
 
43
- // Write a review definition
44
- await fs.writeFile(path.join(REVIEWS_DIR, 'security.md'), `---
49
+ // Write a review definition
50
+ await fs.writeFile(
51
+ path.join(REVIEWS_DIR, "security.md"),
52
+ `---
45
53
  cli_preference:
46
54
  - gemini
47
55
  ---
48
56
 
49
57
  # Security Review
50
58
  Check for vulnerabilities.
51
- `);
59
+ `,
60
+ );
52
61
 
53
- // Write a review definition without preference
54
- await fs.writeFile(path.join(REVIEWS_DIR, 'style.md'), `---
62
+ // Write a review definition without preference
63
+ await fs.writeFile(
64
+ path.join(REVIEWS_DIR, "style.md"),
65
+ `---
55
66
  num_reviews: 1
56
67
  ---
57
68
 
58
69
  # Style Review
59
70
  Check style.
60
- `);
61
- });
62
-
63
- afterAll(async () => {
64
- // Cleanup
65
- await fs.rm(TEST_DIR, { recursive: true, force: true });
66
- });
67
-
68
- it('should load project configuration correctly', async () => {
69
- const config = await loadConfig(TEST_DIR);
70
-
71
- expect(config.project.base_branch).toBe('origin/dev');
72
- expect(config.project.log_dir).toBe('test_logs');
73
- expect(config.project.entry_points).toHaveLength(1);
74
- expect(config.project.entry_points[0].path).toBe('src/');
75
- });
76
-
77
- it('should load check gates correctly', async () => {
78
- const config = await loadConfig(TEST_DIR);
79
-
80
- expect(Object.keys(config.checks)).toContain('lint');
81
- expect(config.checks['lint'].command).toBe('npm run lint');
82
- });
83
-
84
- it('should load review gates correctly', async () => {
85
- const config = await loadConfig(TEST_DIR);
86
-
87
- expect(Object.keys(config.reviews)).toContain('security');
88
- expect(config.reviews['security'].name).toBe('security');
89
- expect(config.reviews['security'].cli_preference).toEqual(['gemini']);
90
- expect(config.reviews['security'].promptContent).toContain('Check for vulnerabilities.');
91
- });
92
-
93
- it('should merge default cli preference', async () => {
94
- const config = await loadConfig(TEST_DIR);
95
-
96
- expect(Object.keys(config.reviews)).toContain('style');
97
- expect(config.reviews['style'].cli_preference).toEqual(['claude', 'gemini']);
98
- });
99
-
100
- it('should reject check gate with fail_fast when parallel is true', async () => {
101
- await fs.writeFile(path.join(CHECKS_DIR, 'invalid.yml'), `
71
+ `,
72
+ );
73
+ });
74
+
75
+ afterAll(async () => {
76
+ // Cleanup
77
+ await fs.rm(TEST_DIR, { recursive: true, force: true });
78
+ });
79
+
80
+ it("should load project configuration correctly", async () => {
81
+ const config = await loadConfig(TEST_DIR);
82
+
83
+ expect(config.project.base_branch).toBe("origin/dev");
84
+ expect(config.project.log_dir).toBe("test_logs");
85
+ expect(config.project.entry_points).toHaveLength(1);
86
+ expect(config.project.entry_points[0].path).toBe("src/");
87
+ });
88
+
89
+ it("should load check gates correctly", async () => {
90
+ const config = await loadConfig(TEST_DIR);
91
+
92
+ expect(Object.keys(config.checks)).toContain("lint");
93
+ expect(config.checks.lint.command).toBe("npm run lint");
94
+ });
95
+
96
+ it("should load review gates correctly", async () => {
97
+ const config = await loadConfig(TEST_DIR);
98
+
99
+ expect(Object.keys(config.reviews)).toContain("security");
100
+ expect(config.reviews.security.name).toBe("security");
101
+ expect(config.reviews.security.cli_preference).toEqual(["gemini"]);
102
+ expect(config.reviews.security.promptContent).toContain(
103
+ "Check for vulnerabilities.",
104
+ );
105
+ });
106
+
107
+ it("should merge default cli preference", async () => {
108
+ const config = await loadConfig(TEST_DIR);
109
+
110
+ expect(Object.keys(config.reviews)).toContain("style");
111
+ expect(config.reviews.style.cli_preference).toEqual(["claude", "gemini"]);
112
+ });
113
+
114
+ it("should reject check gate with fail_fast when parallel is true", async () => {
115
+ await fs.writeFile(
116
+ path.join(CHECKS_DIR, "invalid.yml"),
117
+ `
102
118
  name: invalid
103
119
  command: echo test
104
120
  parallel: true
105
121
  fail_fast: true
106
- `);
107
-
108
- await expect(loadConfig(TEST_DIR)).rejects.toThrow(/fail_fast can only be used when parallel is false/);
109
- });
110
-
111
- it('should accept check gate with fail_fast when parallel is false', async () => {
112
- // Clean up the invalid file first
113
- try {
114
- await fs.unlink(path.join(CHECKS_DIR, 'invalid.yml'));
115
- } catch {}
116
-
117
- await fs.writeFile(path.join(CHECKS_DIR, 'valid.yml'), `
122
+ `,
123
+ );
124
+
125
+ await expect(loadConfig(TEST_DIR)).rejects.toThrow(
126
+ /fail_fast can only be used when parallel is false/,
127
+ );
128
+ });
129
+
130
+ it("should accept check gate with fail_fast when parallel is false", async () => {
131
+ // Clean up the invalid file first
132
+ try {
133
+ await fs.unlink(path.join(CHECKS_DIR, "invalid.yml"));
134
+ } catch {}
135
+
136
+ await fs.writeFile(
137
+ path.join(CHECKS_DIR, "valid.yml"),
138
+ `
118
139
  name: valid
119
140
  command: echo test
120
141
  parallel: false
121
142
  fail_fast: true
122
- `);
123
-
124
- const config = await loadConfig(TEST_DIR);
125
- expect(config.checks['valid']).toBeDefined();
126
- expect(config.checks['valid'].fail_fast).toBe(true);
127
- expect(config.checks['valid'].parallel).toBe(false);
128
- });
143
+ `,
144
+ );
145
+
146
+ const config = await loadConfig(TEST_DIR);
147
+ expect(config.checks.valid).toBeDefined();
148
+ expect(config.checks.valid.fail_fast).toBe(true);
149
+ expect(config.checks.valid.parallel).toBe(false);
150
+ });
129
151
  });
@@ -1,130 +1,139 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import YAML from 'yaml';
4
- import matter from 'gray-matter';
5
- import {
6
- gauntletConfigSchema,
7
- checkGateSchema,
8
- reviewPromptFrontmatterSchema
9
- } from './schema.js';
10
- import { LoadedConfig, CheckGateConfig } from './types.js';
11
-
12
- const GAUNTLET_DIR = '.gauntlet';
13
- const CONFIG_FILE = 'config.yml';
14
- const CHECKS_DIR = 'checks';
15
- const REVIEWS_DIR = 'reviews';
16
-
17
- export async function loadConfig(rootDir: string = process.cwd()): Promise<LoadedConfig> {
18
- const gauntletPath = path.join(rootDir, GAUNTLET_DIR);
19
- const configPath = path.join(gauntletPath, CONFIG_FILE);
20
-
21
- // 1. Load project config
22
- if (!(await fileExists(configPath))) {
23
- throw new Error(`Configuration file not found at ${configPath}`);
24
- }
25
-
26
- const configContent = await fs.readFile(configPath, 'utf-8');
27
- const projectConfigRaw = YAML.parse(configContent);
28
- const projectConfig = gauntletConfigSchema.parse(projectConfigRaw);
29
-
30
- // 2. Load checks
31
- const checksPath = path.join(gauntletPath, CHECKS_DIR);
32
- const checks: Record<string, CheckGateConfig> = {};
33
-
34
- if (await dirExists(checksPath)) {
35
- const checkFiles = await fs.readdir(checksPath);
36
- for (const file of checkFiles) {
37
- if (file.endsWith('.yml') || file.endsWith('.yaml')) {
38
- const filePath = path.join(checksPath, file);
39
- const content = await fs.readFile(filePath, 'utf-8');
40
- const raw = YAML.parse(content);
41
- // Ensure name matches filename if not provided or just use filename as key
42
- const parsed = checkGateSchema.parse(raw);
43
- checks[parsed.name] = parsed;
44
- }
45
- }
46
- }
47
-
48
- // 3. Load reviews (prompts + frontmatter)
49
- const reviewsPath = path.join(gauntletPath, REVIEWS_DIR);
50
- const reviews: Record<string, any> = {};
51
-
52
- if (await dirExists(reviewsPath)) {
53
- const reviewFiles = await fs.readdir(reviewsPath);
54
- for (const file of reviewFiles) {
55
- if (file.endsWith('.md')) {
56
- const filePath = path.join(reviewsPath, file);
57
- const content = await fs.readFile(filePath, 'utf-8');
58
- const { data: frontmatter, content: promptBody } = matter(content);
59
-
60
- const parsedFrontmatter = reviewPromptFrontmatterSchema.parse(frontmatter);
61
- const name = path.basename(file, '.md');
62
-
63
- reviews[name] = {
64
- name,
65
- prompt: file, // Store filename relative to reviews dir
66
- promptContent: promptBody, // Store the actual prompt content for easy access
67
- ...parsedFrontmatter
68
- };
69
-
70
- // Merge default CLI preference if not specified
71
- if (!reviews[name].cli_preference) {
72
- reviews[name].cli_preference = projectConfig.cli.default_preference;
73
- } else {
74
- // Validate that specified preferences are allowed by project config
75
- const allowedTools = new Set(projectConfig.cli.default_preference);
76
- for (const tool of reviews[name].cli_preference) {
77
- if (!allowedTools.has(tool)) {
78
- throw new Error(`Review "${name}" uses CLI tool "${tool}" which is not in the project-level allowed list (cli.default_preference).`);
79
- }
80
- }
81
- }
82
- }
83
- }
84
- }
85
-
86
- // 4. Validate entry point references
87
- const checkNames = new Set(Object.keys(checks));
88
- const reviewNames = new Set(Object.keys(reviews));
89
-
90
- for (const entryPoint of projectConfig.entry_points) {
91
- if (entryPoint.checks) {
92
- for (const checkName of entryPoint.checks) {
93
- if (!checkNames.has(checkName)) {
94
- throw new Error(`Entry point "${entryPoint.path}" references non-existent check gate: "${checkName}"`);
95
- }
96
- }
97
- }
98
- if (entryPoint.reviews) {
99
- for (const reviewName of entryPoint.reviews) {
100
- if (!reviewNames.has(reviewName)) {
101
- throw new Error(`Entry point "${entryPoint.path}" references non-existent review gate: "${reviewName}"`);
102
- }
103
- }
104
- }
105
- }
106
-
107
- return {
108
- project: projectConfig,
109
- checks,
110
- reviews,
111
- };
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import matter from "gray-matter";
4
+ import YAML from "yaml";
5
+ import {
6
+ checkGateSchema,
7
+ gauntletConfigSchema,
8
+ reviewPromptFrontmatterSchema,
9
+ } from "./schema.js";
10
+ import type { CheckGateConfig, LoadedConfig } from "./types.js";
11
+
12
+ const GAUNTLET_DIR = ".gauntlet";
13
+ const CONFIG_FILE = "config.yml";
14
+ const CHECKS_DIR = "checks";
15
+ const REVIEWS_DIR = "reviews";
16
+
17
+ export async function loadConfig(
18
+ rootDir: string = process.cwd(),
19
+ ): Promise<LoadedConfig> {
20
+ const gauntletPath = path.join(rootDir, GAUNTLET_DIR);
21
+ const configPath = path.join(gauntletPath, CONFIG_FILE);
22
+
23
+ // 1. Load project config
24
+ if (!(await fileExists(configPath))) {
25
+ throw new Error(`Configuration file not found at ${configPath}`);
26
+ }
27
+
28
+ const configContent = await fs.readFile(configPath, "utf-8");
29
+ const projectConfigRaw = YAML.parse(configContent);
30
+ const projectConfig = gauntletConfigSchema.parse(projectConfigRaw);
31
+
32
+ // 2. Load checks
33
+ const checksPath = path.join(gauntletPath, CHECKS_DIR);
34
+ const checks: Record<string, CheckGateConfig> = {};
35
+
36
+ if (await dirExists(checksPath)) {
37
+ const checkFiles = await fs.readdir(checksPath);
38
+ for (const file of checkFiles) {
39
+ if (file.endsWith(".yml") || file.endsWith(".yaml")) {
40
+ const filePath = path.join(checksPath, file);
41
+ const content = await fs.readFile(filePath, "utf-8");
42
+ const raw = YAML.parse(content);
43
+ // Ensure name matches filename if not provided or just use filename as key
44
+ const parsed = checkGateSchema.parse(raw);
45
+ checks[parsed.name] = parsed;
46
+ }
47
+ }
48
+ }
49
+
50
+ // 3. Load reviews (prompts + frontmatter)
51
+ const reviewsPath = path.join(gauntletPath, REVIEWS_DIR);
52
+ const reviews: LoadedConfig["reviews"] = {};
53
+
54
+ if (await dirExists(reviewsPath)) {
55
+ const reviewFiles = await fs.readdir(reviewsPath);
56
+ for (const file of reviewFiles) {
57
+ if (file.endsWith(".md")) {
58
+ const filePath = path.join(reviewsPath, file);
59
+ const content = await fs.readFile(filePath, "utf-8");
60
+ const { data: frontmatter, content: promptBody } = matter(content);
61
+
62
+ const parsedFrontmatter =
63
+ reviewPromptFrontmatterSchema.parse(frontmatter);
64
+ const name = path.basename(file, ".md");
65
+
66
+ reviews[name] = {
67
+ name,
68
+ prompt: file, // Store filename relative to reviews dir
69
+ promptContent: promptBody, // Store the actual prompt content for easy access
70
+ ...parsedFrontmatter,
71
+ };
72
+
73
+ // Merge default CLI preference if not specified
74
+ if (!reviews[name].cli_preference) {
75
+ reviews[name].cli_preference = projectConfig.cli.default_preference;
76
+ } else {
77
+ // Validate that specified preferences are allowed by project config
78
+ const allowedTools = new Set(projectConfig.cli.default_preference);
79
+ for (const tool of reviews[name].cli_preference) {
80
+ if (!allowedTools.has(tool)) {
81
+ throw new Error(
82
+ `Review "${name}" uses CLI tool "${tool}" which is not in the project-level allowed list (cli.default_preference).`,
83
+ );
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ // 4. Validate entry point references
92
+ const checkNames = new Set(Object.keys(checks));
93
+ const reviewNames = new Set(Object.keys(reviews));
94
+
95
+ for (const entryPoint of projectConfig.entry_points) {
96
+ if (entryPoint.checks) {
97
+ for (const checkName of entryPoint.checks) {
98
+ if (!checkNames.has(checkName)) {
99
+ throw new Error(
100
+ `Entry point "${entryPoint.path}" references non-existent check gate: "${checkName}"`,
101
+ );
102
+ }
103
+ }
104
+ }
105
+ if (entryPoint.reviews) {
106
+ for (const reviewName of entryPoint.reviews) {
107
+ if (!reviewNames.has(reviewName)) {
108
+ throw new Error(
109
+ `Entry point "${entryPoint.path}" references non-existent review gate: "${reviewName}"`,
110
+ );
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ return {
117
+ project: projectConfig,
118
+ checks,
119
+ reviews,
120
+ };
112
121
  }
113
122
 
114
123
  async function fileExists(path: string): Promise<boolean> {
115
- try {
116
- const stat = await fs.stat(path);
117
- return stat.isFile();
118
- } catch {
119
- return false;
120
- }
124
+ try {
125
+ const stat = await fs.stat(path);
126
+ return stat.isFile();
127
+ } catch {
128
+ return false;
129
+ }
121
130
  }
122
131
 
123
132
  async function dirExists(path: string): Promise<boolean> {
124
- try {
125
- const stat = await fs.stat(path);
126
- return stat.isDirectory();
127
- } catch {
128
- return false;
129
- }
133
+ try {
134
+ const stat = await fs.stat(path);
135
+ return stat.isDirectory();
136
+ } catch {
137
+ return false;
138
+ }
130
139
  }
@@ -1,63 +1,64 @@
1
- import { z } from 'zod';
1
+ import { z } from "zod";
2
2
 
3
3
  export const cliConfigSchema = z.object({
4
- default_preference: z.array(z.string().min(1)).min(1),
5
- check_usage_limit: z.boolean().default(false),
4
+ default_preference: z.array(z.string().min(1)).min(1),
5
+ check_usage_limit: z.boolean().default(false),
6
6
  });
7
7
 
8
- export const checkGateSchema = z.object({
9
- name: z.string().min(1),
10
- command: z.string().min(1),
11
- working_directory: z.string().optional(),
12
- parallel: z.boolean().default(false),
13
- run_in_ci: z.boolean().default(true),
14
- run_locally: z.boolean().default(true),
15
- timeout: z.number().optional(),
16
- fail_fast: z.boolean().optional(),
17
- }).refine(
18
- (data) => {
19
- // fail_fast can only be used when parallel is false
20
- if (data.fail_fast === true && data.parallel === true) {
21
- return false;
22
- }
23
- return true;
24
- },
25
- {
26
- message: "fail_fast can only be used when parallel is false",
27
- }
28
- );
8
+ export const checkGateSchema = z
9
+ .object({
10
+ name: z.string().min(1),
11
+ command: z.string().min(1),
12
+ working_directory: z.string().optional(),
13
+ parallel: z.boolean().default(false),
14
+ run_locally: z.boolean().default(true),
15
+ timeout: z.number().optional(),
16
+ fail_fast: z.boolean().optional(),
17
+ })
18
+ .refine(
19
+ (data) => {
20
+ // fail_fast can only be used when parallel is false
21
+ if (data.fail_fast === true && data.parallel === true) {
22
+ return false;
23
+ }
24
+ return true;
25
+ },
26
+ {
27
+ message: "fail_fast can only be used when parallel is false",
28
+ },
29
+ );
29
30
 
30
31
  export const reviewGateSchema = z.object({
31
- name: z.string().min(1),
32
- prompt: z.string().min(1), // Path relative to .gauntlet/reviews/
33
- cli_preference: z.array(z.string().min(1)).optional(),
34
- num_reviews: z.number().default(1),
35
- parallel: z.boolean().default(true),
36
- run_in_ci: z.boolean().default(true),
37
- run_locally: z.boolean().default(true),
38
- timeout: z.number().optional(),
32
+ name: z.string().min(1),
33
+ prompt: z.string().min(1), // Path relative to .gauntlet/reviews/
34
+ cli_preference: z.array(z.string().min(1)).optional(),
35
+ num_reviews: z.number().default(1),
36
+ parallel: z.boolean().default(true),
37
+ run_in_ci: z.boolean().default(true),
38
+ run_locally: z.boolean().default(true),
39
+ timeout: z.number().optional(),
39
40
  });
40
41
 
41
42
  export const reviewPromptFrontmatterSchema = z.object({
42
- model: z.string().optional(),
43
- cli_preference: z.array(z.string().min(1)).optional(),
44
- num_reviews: z.number().default(1),
45
- parallel: z.boolean().default(true),
46
- run_in_ci: z.boolean().default(true),
47
- run_locally: z.boolean().default(true),
48
- timeout: z.number().optional(),
43
+ model: z.string().optional(),
44
+ cli_preference: z.array(z.string().min(1)).optional(),
45
+ num_reviews: z.number().default(1),
46
+ parallel: z.boolean().default(true),
47
+ run_in_ci: z.boolean().default(true),
48
+ run_locally: z.boolean().default(true),
49
+ timeout: z.number().optional(),
49
50
  });
50
51
 
51
52
  export const entryPointSchema = z.object({
52
- path: z.string().min(1),
53
- checks: z.array(z.string().min(1)).optional(),
54
- reviews: z.array(z.string().min(1)).optional(),
53
+ path: z.string().min(1),
54
+ checks: z.array(z.string().min(1)).optional(),
55
+ reviews: z.array(z.string().min(1)).optional(),
55
56
  });
56
57
 
57
58
  export const gauntletConfigSchema = z.object({
58
- base_branch: z.string().min(1).default('origin/main'),
59
- log_dir: z.string().min(1).default('.gauntlet_logs'),
60
- allow_parallel: z.boolean().default(true),
61
- cli: cliConfigSchema,
62
- entry_points: z.array(entryPointSchema).min(1),
59
+ base_branch: z.string().min(1).default("origin/main"),
60
+ log_dir: z.string().min(1).default(".gauntlet_logs"),
61
+ allow_parallel: z.boolean().default(true),
62
+ cli: cliConfigSchema,
63
+ entry_points: z.array(entryPointSchema).min(1),
63
64
  });