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.
- package/README.md +55 -87
- package/package.json +4 -2
- package/src/bun-plugins.d.ts +4 -0
- package/src/cli-adapters/claude.ts +139 -108
- package/src/cli-adapters/codex.ts +141 -117
- package/src/cli-adapters/cursor.ts +152 -0
- package/src/cli-adapters/gemini.ts +171 -139
- package/src/cli-adapters/github-copilot.ts +153 -0
- package/src/cli-adapters/index.ts +77 -48
- package/src/commands/check.test.ts +24 -20
- package/src/commands/check.ts +86 -59
- package/src/commands/ci/index.ts +15 -0
- package/src/commands/ci/init.ts +96 -0
- package/src/commands/ci/list-jobs.ts +78 -0
- package/src/commands/detect.test.ts +38 -32
- package/src/commands/detect.ts +89 -61
- package/src/commands/health.test.ts +67 -53
- package/src/commands/health.ts +167 -145
- package/src/commands/help.test.ts +37 -37
- package/src/commands/help.ts +31 -22
- package/src/commands/index.ts +10 -9
- package/src/commands/init.test.ts +120 -107
- package/src/commands/init.ts +514 -417
- package/src/commands/list.test.ts +87 -70
- package/src/commands/list.ts +28 -24
- package/src/commands/rerun.ts +157 -119
- package/src/commands/review.test.ts +26 -20
- package/src/commands/review.ts +86 -59
- package/src/commands/run.test.ts +22 -20
- package/src/commands/run.ts +85 -58
- package/src/commands/shared.ts +44 -35
- package/src/config/ci-loader.ts +33 -0
- package/src/config/ci-schema.ts +52 -0
- package/src/config/loader.test.ts +112 -90
- package/src/config/loader.ts +132 -123
- package/src/config/schema.ts +48 -47
- package/src/config/types.ts +28 -13
- package/src/config/validator.ts +521 -454
- package/src/core/change-detector.ts +122 -104
- package/src/core/entry-point.test.ts +60 -62
- package/src/core/entry-point.ts +120 -74
- package/src/core/job.ts +69 -59
- package/src/core/runner.ts +264 -230
- package/src/gates/check.ts +78 -69
- package/src/gates/result.ts +7 -7
- package/src/gates/review.test.ts +277 -138
- package/src/gates/review.ts +724 -561
- package/src/index.ts +18 -15
- package/src/output/console.ts +253 -214
- package/src/output/logger.ts +66 -52
- package/src/templates/run_gauntlet.template.md +18 -0
- package/src/templates/workflow.yml +77 -0
- package/src/utils/diff-parser.ts +64 -62
- package/src/utils/log-parser.ts +227 -206
- package/src/utils/sanitizer.ts +1 -1
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import { loadConfig } from
|
|
5
|
-
|
|
6
|
-
const TEST_DIR = path.join(process.cwd(),
|
|
7
|
-
const GAUNTLET_DIR = path.join(TEST_DIR,
|
|
8
|
-
const CHECKS_DIR = path.join(GAUNTLET_DIR,
|
|
9
|
-
const REVIEWS_DIR = path.join(GAUNTLET_DIR,
|
|
10
|
-
|
|
11
|
-
describe(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
});
|
package/src/config/loader.ts
CHANGED
|
@@ -1,130 +1,139 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
const GAUNTLET_DIR =
|
|
13
|
-
const CONFIG_FILE =
|
|
14
|
-
const CHECKS_DIR =
|
|
15
|
-
const REVIEWS_DIR =
|
|
16
|
-
|
|
17
|
-
export async function loadConfig(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
try {
|
|
134
|
+
const stat = await fs.stat(path);
|
|
135
|
+
return stat.isDirectory();
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
130
139
|
}
|
package/src/config/schema.ts
CHANGED
|
@@ -1,63 +1,64 @@
|
|
|
1
|
-
import { z } from
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
export const cliConfigSchema = z.object({
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
});
|