agent-gauntlet 0.1.9 → 0.1.11

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 (48) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -2
  3. package/src/cli-adapters/claude.ts +139 -108
  4. package/src/cli-adapters/codex.ts +141 -117
  5. package/src/cli-adapters/cursor.ts +152 -0
  6. package/src/cli-adapters/gemini.ts +171 -139
  7. package/src/cli-adapters/github-copilot.ts +153 -0
  8. package/src/cli-adapters/index.ts +77 -48
  9. package/src/commands/check.test.ts +24 -20
  10. package/src/commands/check.ts +65 -59
  11. package/src/commands/detect.test.ts +38 -32
  12. package/src/commands/detect.ts +74 -61
  13. package/src/commands/health.test.ts +67 -53
  14. package/src/commands/health.ts +167 -145
  15. package/src/commands/help.test.ts +37 -37
  16. package/src/commands/help.ts +30 -22
  17. package/src/commands/index.ts +9 -9
  18. package/src/commands/init.test.ts +118 -107
  19. package/src/commands/init.ts +515 -417
  20. package/src/commands/list.test.ts +87 -70
  21. package/src/commands/list.ts +28 -24
  22. package/src/commands/rerun.ts +142 -119
  23. package/src/commands/review.test.ts +26 -20
  24. package/src/commands/review.ts +65 -59
  25. package/src/commands/run.test.ts +22 -20
  26. package/src/commands/run.ts +64 -58
  27. package/src/commands/shared.ts +44 -35
  28. package/src/config/loader.test.ts +112 -90
  29. package/src/config/loader.ts +132 -123
  30. package/src/config/schema.ts +49 -47
  31. package/src/config/types.ts +15 -13
  32. package/src/config/validator.ts +521 -454
  33. package/src/core/change-detector.ts +122 -104
  34. package/src/core/entry-point.test.ts +60 -62
  35. package/src/core/entry-point.ts +76 -67
  36. package/src/core/job.ts +69 -59
  37. package/src/core/runner.ts +261 -221
  38. package/src/gates/check.ts +78 -69
  39. package/src/gates/result.ts +7 -6
  40. package/src/gates/review.test.ts +188 -0
  41. package/src/gates/review.ts +717 -506
  42. package/src/index.ts +16 -15
  43. package/src/output/console.ts +253 -198
  44. package/src/output/logger.ts +65 -51
  45. package/src/templates/run_gauntlet.template.md +18 -0
  46. package/src/utils/diff-parser.ts +64 -62
  47. package/src/utils/log-parser.ts +227 -206
  48. package/src/utils/sanitizer.ts +1 -1
@@ -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,65 @@
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_in_ci: z.boolean().default(true),
15
+ run_locally: z.boolean().default(true),
16
+ timeout: z.number().optional(),
17
+ fail_fast: z.boolean().optional(),
18
+ })
19
+ .refine(
20
+ (data) => {
21
+ // fail_fast can only be used when parallel is false
22
+ if (data.fail_fast === true && data.parallel === true) {
23
+ return false;
24
+ }
25
+ return true;
26
+ },
27
+ {
28
+ message: "fail_fast can only be used when parallel is false",
29
+ },
30
+ );
29
31
 
30
32
  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(),
33
+ name: z.string().min(1),
34
+ prompt: z.string().min(1), // Path relative to .gauntlet/reviews/
35
+ cli_preference: z.array(z.string().min(1)).optional(),
36
+ num_reviews: z.number().default(1),
37
+ parallel: z.boolean().default(true),
38
+ run_in_ci: z.boolean().default(true),
39
+ run_locally: z.boolean().default(true),
40
+ timeout: z.number().optional(),
39
41
  });
40
42
 
41
43
  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(),
44
+ model: z.string().optional(),
45
+ cli_preference: z.array(z.string().min(1)).optional(),
46
+ num_reviews: z.number().default(1),
47
+ parallel: z.boolean().default(true),
48
+ run_in_ci: z.boolean().default(true),
49
+ run_locally: z.boolean().default(true),
50
+ timeout: z.number().optional(),
49
51
  });
50
52
 
51
53
  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(),
54
+ path: z.string().min(1),
55
+ checks: z.array(z.string().min(1)).optional(),
56
+ reviews: z.array(z.string().min(1)).optional(),
55
57
  });
56
58
 
57
59
  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),
60
+ base_branch: z.string().min(1).default("origin/main"),
61
+ log_dir: z.string().min(1).default(".gauntlet_logs"),
62
+ allow_parallel: z.boolean().default(true),
63
+ cli: cliConfigSchema,
64
+ entry_points: z.array(entryPointSchema).min(1),
63
65
  });
@@ -1,23 +1,25 @@
1
- import { z } from 'zod';
2
- import {
3
- checkGateSchema,
4
- reviewGateSchema,
5
- reviewPromptFrontmatterSchema,
6
- entryPointSchema,
7
- gauntletConfigSchema,
8
- cliConfigSchema
9
- } from './schema.js';
1
+ import type { z } from "zod";
2
+ import type {
3
+ checkGateSchema,
4
+ cliConfigSchema,
5
+ entryPointSchema,
6
+ gauntletConfigSchema,
7
+ reviewGateSchema,
8
+ reviewPromptFrontmatterSchema,
9
+ } from "./schema.js";
10
10
 
11
11
  export type CheckGateConfig = z.infer<typeof checkGateSchema>;
12
12
  export type ReviewGateConfig = z.infer<typeof reviewGateSchema>;
13
- export type ReviewPromptFrontmatter = z.infer<typeof reviewPromptFrontmatterSchema>;
13
+ export type ReviewPromptFrontmatter = z.infer<
14
+ typeof reviewPromptFrontmatterSchema
15
+ >;
14
16
  export type EntryPointConfig = z.infer<typeof entryPointSchema>;
15
17
  export type GauntletConfig = z.infer<typeof gauntletConfigSchema>;
16
18
  export type CLIConfig = z.infer<typeof cliConfigSchema>;
17
19
 
18
20
  // Combined type for the fully loaded configuration
19
21
  export interface LoadedConfig {
20
- project: GauntletConfig;
21
- checks: Record<string, CheckGateConfig>;
22
- reviews: Record<string, ReviewGateConfig & ReviewPromptFrontmatter>; // Merged with frontmatter
22
+ project: GauntletConfig;
23
+ checks: Record<string, CheckGateConfig>;
24
+ reviews: Record<string, ReviewGateConfig & ReviewPromptFrontmatter>; // Merged with frontmatter
23
25
  }