agent-gauntlet 0.14.0 → 0.15.1

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 (22) hide show
  1. package/dist/index.js +902 -565
  2. package/dist/index.js.map +16 -13
  3. package/dist/scripts/status.js +10 -2
  4. package/dist/scripts/status.js.map +3 -3
  5. package/package.json +2 -1
  6. package/skills/gauntlet-check/SKILL.md +22 -0
  7. package/skills/gauntlet-run/SKILL.md +67 -0
  8. package/skills/gauntlet-run/extract-prompt.md +121 -0
  9. package/skills/gauntlet-run/update-prompt.md +113 -0
  10. package/{dist/skill-templates/status.md → skills/gauntlet-status/SKILL.md} +1 -1
  11. /package/{dist/skill-templates/fix-pr.md → skills/gauntlet-fix-pr/SKILL.md} +0 -0
  12. /package/{dist/skill-templates/help-skill.md → skills/gauntlet-help/SKILL.md} +0 -0
  13. /package/{dist/skill-templates/help-ref-adapter-troubleshooting.md → skills/gauntlet-help/references/adapter-troubleshooting.md} +0 -0
  14. /package/{dist/skill-templates/help-ref-ci-pr-troubleshooting.md → skills/gauntlet-help/references/ci-pr-troubleshooting.md} +0 -0
  15. /package/{dist/skill-templates/help-ref-config-troubleshooting.md → skills/gauntlet-help/references/config-troubleshooting.md} +0 -0
  16. /package/{dist/skill-templates/help-ref-gate-troubleshooting.md → skills/gauntlet-help/references/gate-troubleshooting.md} +0 -0
  17. /package/{dist/skill-templates/help-ref-lock-troubleshooting.md → skills/gauntlet-help/references/lock-troubleshooting.md} +0 -0
  18. /package/{dist/skill-templates/help-ref-stop-hook-troubleshooting.md → skills/gauntlet-help/references/stop-hook-troubleshooting.md} +0 -0
  19. /package/{dist/skill-templates/push-pr.md → skills/gauntlet-push-pr/SKILL.md} +0 -0
  20. /package/{dist/skill-templates/setup-skill.md → skills/gauntlet-setup/SKILL.md} +0 -0
  21. /package/{dist/skill-templates → skills/gauntlet-setup/references}/check-catalog.md +0 -0
  22. /package/{dist/skill-templates/setup-ref-project-structure.md → skills/gauntlet-setup/references/project-structure.md} +0 -0
package/dist/index.js CHANGED
@@ -2,12 +2,292 @@
2
2
  import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
+ // src/scripts/status.ts
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ function parseKeyValue(text) {
9
+ const result = {};
10
+ for (const match of text.matchAll(/(\w+)=(\S+)/g)) {
11
+ const key = match[1];
12
+ const value = match[2];
13
+ if (key && value)
14
+ result[key] = value;
15
+ }
16
+ return result;
17
+ }
18
+ function parseTimestamp(line) {
19
+ const m = line.match(/^\[([^\]]+)\]/);
20
+ return m?.[1] ?? "";
21
+ }
22
+ function parseEventType(line) {
23
+ const m = line.match(/^\[[^\]]+\]\s+(\S+)/);
24
+ return m?.[1] ?? "";
25
+ }
26
+ function parseEventBody(line) {
27
+ const m = line.match(/^\[[^\]]+\]\s+\S+\s*(.*)/);
28
+ return m?.[1] ?? "";
29
+ }
30
+ function parseDebugLog(content, sessionStartTime) {
31
+ const lines = content.split(`
32
+ `).filter((l) => l.trim());
33
+ const sessions = [];
34
+ let current = null;
35
+ for (const line of lines) {
36
+ const event = parseEventType(line);
37
+ const body = parseEventBody(line);
38
+ const ts = parseTimestamp(line);
39
+ switch (event) {
40
+ case "RUN_START": {
41
+ if (sessionStartTime && new Date(ts) < sessionStartTime) {
42
+ current = null;
43
+ break;
44
+ }
45
+ const kv = parseKeyValue(body);
46
+ current = {
47
+ start: {
48
+ timestamp: ts,
49
+ mode: kv.mode ?? "unknown",
50
+ baseRef: kv.base_ref,
51
+ filesChanged: Number(kv.files_changed ?? kv.changes ?? 0),
52
+ linesAdded: Number(kv.lines_added ?? 0),
53
+ linesRemoved: Number(kv.lines_removed ?? 0),
54
+ gates: Number(kv.gates ?? 0)
55
+ },
56
+ gates: []
57
+ };
58
+ sessions.push(current);
59
+ break;
60
+ }
61
+ case "GATE_RESULT": {
62
+ if (!current)
63
+ break;
64
+ const gateIdMatch = body.match(/^(\S+)/);
65
+ const kv = parseKeyValue(body);
66
+ current.gates.push({
67
+ timestamp: ts,
68
+ gateId: gateIdMatch?.[1] ?? "unknown",
69
+ cli: kv.cli,
70
+ status: kv.status ?? "unknown",
71
+ duration: kv.duration ?? "?",
72
+ violations: kv.violations !== undefined ? Number(kv.violations) : undefined
73
+ });
74
+ break;
75
+ }
76
+ case "RUN_END": {
77
+ if (!current)
78
+ break;
79
+ const kv = parseKeyValue(body);
80
+ current.end = {
81
+ timestamp: ts,
82
+ status: kv.status ?? "unknown",
83
+ fixed: Number(kv.fixed ?? 0),
84
+ skipped: Number(kv.skipped ?? 0),
85
+ failed: Number(kv.failed ?? 0),
86
+ iterations: Number(kv.iterations ?? 0),
87
+ duration: kv.duration ?? "?"
88
+ };
89
+ break;
90
+ }
91
+ case "STOP_HOOK": {
92
+ if (!current)
93
+ break;
94
+ const kv = parseKeyValue(body);
95
+ current.stopHook = {
96
+ timestamp: ts,
97
+ decision: kv.decision ?? "unknown",
98
+ reason: kv.reason ?? "unknown"
99
+ };
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ return sessions;
105
+ }
106
+ function getSessionStartTime(logDir) {
107
+ const entries = fs.readdirSync(logDir).filter((f) => !f.startsWith(".") && f !== "previous");
108
+ let earliest;
109
+ for (const entry of entries) {
110
+ const mtime = fs.statSync(path.join(logDir, entry)).mtimeMs;
111
+ if (earliest === undefined || mtime < earliest) {
112
+ earliest = mtime;
113
+ }
114
+ }
115
+ return earliest !== undefined ? new Date(earliest) : undefined;
116
+ }
117
+ function formatFileInventory(logDir) {
118
+ const lines = [];
119
+ const entries = fs.readdirSync(logDir).filter((f) => !f.startsWith(".") && f !== "previous");
120
+ if (entries.length === 0)
121
+ return lines;
122
+ const checks = [];
123
+ const reviews = [];
124
+ const other = [];
125
+ for (const entry of entries.sort()) {
126
+ const fullPath = path.join(logDir, entry);
127
+ const stat = fs.statSync(fullPath);
128
+ const sizeKB = (stat.size / 1024).toFixed(1);
129
+ const line = `- ${fullPath} (${sizeKB} KB)`;
130
+ if (entry.startsWith("review_")) {
131
+ reviews.push(line);
132
+ } else if (entry.startsWith("check_")) {
133
+ checks.push(line);
134
+ } else {
135
+ other.push(line);
136
+ }
137
+ }
138
+ lines.push("### Log Files");
139
+ lines.push("");
140
+ if (checks.length > 0) {
141
+ lines.push("**Check logs:**");
142
+ lines.push(...checks);
143
+ }
144
+ if (reviews.length > 0) {
145
+ lines.push("**Review logs/JSON:**");
146
+ lines.push(...reviews);
147
+ }
148
+ if (other.length > 0) {
149
+ lines.push("**Other:**");
150
+ lines.push(...other);
151
+ }
152
+ lines.push("");
153
+ return lines;
154
+ }
155
+ function formatStatusLine(end) {
156
+ return end.status === "pass" ? "PASSED" : end.status === "fail" ? "FAILED" : end.status.toUpperCase();
157
+ }
158
+ function formatAllRuns(sessions) {
159
+ const lines = [];
160
+ lines.push("### All Runs in Session");
161
+ lines.push("");
162
+ for (let i = 0;i < sessions.length; i++) {
163
+ const s = sessions[i];
164
+ if (!s)
165
+ continue;
166
+ const status = s.end ? s.end.status : "in-progress";
167
+ const duration = s.end ? s.end.duration : "?";
168
+ lines.push(`${i + 1}. [${s.start.timestamp}] mode=${s.start.mode} status=${status} duration=${duration}`);
169
+ }
170
+ lines.push("");
171
+ return lines;
172
+ }
173
+ function formatSession(sessions, logDir) {
174
+ if (sessions.length === 0) {
175
+ return "No gauntlet runs found in logs.";
176
+ }
177
+ const lastComplete = [...sessions].reverse().find((s) => s.end);
178
+ const session = lastComplete ?? sessions[sessions.length - 1];
179
+ if (!session)
180
+ return "No gauntlet runs found in logs.";
181
+ const lines = [];
182
+ lines.push("## Gauntlet Session Summary");
183
+ lines.push("");
184
+ if (session.end) {
185
+ lines.push(`**Status:** ${formatStatusLine(session.end)}`);
186
+ lines.push(`**Iterations:** ${session.end.iterations}`);
187
+ lines.push(`**Duration:** ${session.end.duration}`);
188
+ lines.push(`**Fixed:** ${session.end.fixed} | **Skipped:** ${session.end.skipped} | **Failed:** ${session.end.failed}`);
189
+ } else {
190
+ lines.push("**Status:** In Progress (no RUN_END found)");
191
+ }
192
+ lines.push("");
193
+ lines.push("### Diff Stats");
194
+ lines.push(`- Mode: ${session.start.mode}`);
195
+ if (session.start.baseRef) {
196
+ lines.push(`- Base ref: ${session.start.baseRef}`);
197
+ }
198
+ lines.push(`- Files changed: ${session.start.filesChanged}`);
199
+ lines.push(`- Lines: +${session.start.linesAdded} / -${session.start.linesRemoved}`);
200
+ lines.push(`- Gates: ${session.start.gates}`);
201
+ lines.push("");
202
+ lines.push("### Gate Results");
203
+ lines.push("");
204
+ lines.push("| Gate | CLI | Status | Duration | Violations |");
205
+ lines.push("|------|-----|--------|----------|------------|");
206
+ for (const gate of session.gates) {
207
+ const violations = gate.violations !== undefined ? String(gate.violations) : "-";
208
+ const statusIcon = gate.status === "pass" ? "pass" : "FAIL";
209
+ lines.push(`| ${gate.gateId} | ${gate.cli ?? "-"} | ${statusIcon} | ${gate.duration} | ${violations} |`);
210
+ }
211
+ lines.push("");
212
+ if (session.stopHook) {
213
+ lines.push("### Stop Hook");
214
+ lines.push(`- Decision: ${session.stopHook.decision}`);
215
+ lines.push(`- Reason: ${session.stopHook.reason}`);
216
+ lines.push("");
217
+ }
218
+ lines.push(...formatFileInventory(logDir));
219
+ if (sessions.length > 1) {
220
+ lines.push(...formatAllRuns(sessions));
221
+ }
222
+ return lines.join(`
223
+ `);
224
+ }
225
+ function getLogDir(cwd) {
226
+ const configPath = path.join(cwd, ".gauntlet", "config.yml");
227
+ try {
228
+ const content = fs.readFileSync(configPath, "utf-8");
229
+ const match = content.match(/^log_dir:\s*(.+)$/m);
230
+ if (match?.[1])
231
+ return match[1].trim();
232
+ } catch {}
233
+ return "gauntlet_logs";
234
+ }
235
+ function resolveLogPaths(activeDir) {
236
+ const previousDir = path.join(activeDir, "previous");
237
+ const debugLogPath = path.join(activeDir, ".debug.log");
238
+ const activeHasLogs = fs.existsSync(activeDir) && fs.readdirSync(activeDir).some((f) => !f.startsWith(".") && f !== "previous");
239
+ if (activeHasLogs) {
240
+ return { logDir: activeDir, debugLogPath };
241
+ }
242
+ if (!fs.existsSync(previousDir)) {
243
+ console.log("No gauntlet_logs directory found.");
244
+ return null;
245
+ }
246
+ const logDir = resolvePreviousLogDir(previousDir);
247
+ if (!logDir)
248
+ return null;
249
+ return { logDir, debugLogPath };
250
+ }
251
+ function resolvePreviousLogDir(previousDir) {
252
+ const prevEntries = fs.readdirSync(previousDir);
253
+ const hasDirectFiles = prevEntries.some((f) => f.endsWith(".log") || f.endsWith(".json"));
254
+ if (hasDirectFiles)
255
+ return previousDir;
256
+ const prevDirs = prevEntries.map((d) => path.join(previousDir, d)).filter((d) => fs.statSync(d).isDirectory()).sort().reverse();
257
+ if (prevDirs.length === 0) {
258
+ console.log("No gauntlet logs found.");
259
+ return null;
260
+ }
261
+ return prevDirs[0];
262
+ }
263
+ function main() {
264
+ const cwd = process.cwd();
265
+ const logDirName = getLogDir(cwd);
266
+ const activeDir = path.join(cwd, logDirName);
267
+ const paths = resolveLogPaths(activeDir);
268
+ if (!paths) {
269
+ process.exit(0);
270
+ }
271
+ let sessions = [];
272
+ if (fs.existsSync(paths.debugLogPath)) {
273
+ const debugContent = fs.readFileSync(paths.debugLogPath, "utf-8");
274
+ const sessionStart = getSessionStartTime(paths.logDir);
275
+ sessions = parseDebugLog(debugContent, sessionStart);
276
+ }
277
+ const output = formatSession(sessions, paths.logDir);
278
+ console.log(output);
279
+ }
280
+ var isDirectRun = (import.meta.url === `file://${process.argv[1]}` || typeof Bun !== "undefined" && import.meta.url === `file://${Bun.main}`) && (process.argv[1]?.endsWith("status.ts") || process.argv[1]?.endsWith("status.js"));
281
+ if (isDirectRun) {
282
+ main();
283
+ }
284
+
5
285
  // src/index.ts
6
286
  import { Command } from "commander";
7
287
  // package.json
8
288
  var package_default = {
9
289
  name: "agent-gauntlet",
10
- version: "0.14.0",
290
+ version: "0.15.1",
11
291
  description: "A CLI tool for testing AI coding agents",
12
292
  license: "MIT",
13
293
  author: "Paul Caplan",
@@ -28,6 +308,7 @@ var package_default = {
28
308
  ],
29
309
  files: [
30
310
  "dist",
311
+ "skills",
31
312
  "README.md",
32
313
  "LICENSE"
33
314
  ],
@@ -85,12 +366,12 @@ var package_default = {
85
366
  import chalk3 from "chalk";
86
367
 
87
368
  // src/config/global.ts
88
- import fs from "node:fs/promises";
369
+ import fs2 from "node:fs/promises";
89
370
  import os from "node:os";
90
- import path from "node:path";
371
+ import path2 from "node:path";
91
372
  import YAML from "yaml";
92
373
  import { z } from "zod";
93
- var GLOBAL_CONFIG_PATH = path.join(os.homedir(), ".config", "agent-gauntlet", "config.yml");
374
+ var GLOBAL_CONFIG_PATH = path2.join(os.homedir(), ".config", "agent-gauntlet", "config.yml");
94
375
  var debugLogConfigSchema = z.object({
95
376
  enabled: z.boolean().default(false),
96
377
  max_size_mb: z.number().default(10)
@@ -123,7 +404,7 @@ var DEFAULT_GLOBAL_CONFIG = {
123
404
  };
124
405
  async function loadGlobalConfig() {
125
406
  try {
126
- const content = await fs.readFile(GLOBAL_CONFIG_PATH, "utf-8");
407
+ const content = await fs2.readFile(GLOBAL_CONFIG_PATH, "utf-8");
127
408
  const raw = YAML.parse(content);
128
409
  return globalConfigSchema.parse(raw);
129
410
  } catch (error) {
@@ -136,30 +417,59 @@ async function loadGlobalConfig() {
136
417
  }
137
418
 
138
419
  // src/config/loader.ts
139
- import fs2 from "node:fs/promises";
140
- import path2 from "node:path";
420
+ import fs3 from "node:fs/promises";
421
+ import path3 from "node:path";
141
422
  import matter from "gray-matter";
142
423
  import YAML2 from "yaml";
143
424
 
144
425
  // src/built-in-reviews/code-quality.md
145
426
  var code_quality_default = `# Code Quality Review
146
427
 
147
- You are a senior software engineer performing a code review. Your primary goal is to identify **real problems** that could cause bugs, security vulnerabilities, or performance issues in production. Do not report style, formatting, naming conventions, or maintainability suggestions unless you see something egregious.
428
+ You are a senior software engineer performing a code review. Your goal is to identify **real problems** that could cause bugs, security vulnerabilities, performance issues, or silent failures in production.
429
+
430
+ ## Review Strategy
431
+
432
+ Use a multi-lens approach covering three analysis areas. For each lens, first check whether the corresponding pr-review-toolkit agent is available. If it is, dispatch that agent against the diff. If it is not available, perform the analysis inline using the framework below.
433
+
434
+ ### Lens 1: Code Quality, Bugs & Security
148
435
 
149
- ## Focus Areas (in priority order)
436
+ **Agent:** \`code-reviewer\` (from pr-review-toolkit)
150
437
 
151
- 1. **Bugs** Logic errors, null/undefined issues, race conditions, unhandled edge cases, resource leaks
152
- 2. **Security** — Injection vulnerabilities, auth/authz flaws, sensitive data exposure, input validation gaps
153
- 3. **Performance** — Algorithmic complexity issues, N+1 queries, blocking operations, memory problems
154
- 4. **Maintainability** — Unclear code, missing error handling, duplication
438
+ If the \`code-reviewer\` agent is unavailable, review inline for:
439
+ - **Logic errors** — off-by-one, null/undefined, race conditions, unhandled edge cases
440
+ - **Security flaws** — injection, auth/authz gaps, sensitive data exposure, input validation
441
+ - **Performance** — algorithmic complexity, N+1 queries, blocking operations, memory leaks
442
+ - **Resource leaks** — unclosed handles, missing cleanup in error paths
443
+
444
+ ### Lens 2: Silent Failures & Error Handling
445
+
446
+ **Agent:** \`silent-failure-hunter\` (from pr-review-toolkit)
447
+
448
+ If the \`silent-failure-hunter\` agent is unavailable, review inline for:
449
+ - **Swallowed errors** — empty catch blocks, catch-and-return-default, ignored promise rejections
450
+ - **Missing logging** — error paths with no observability, failures that disappear silently
451
+ - **Inadequate error handling** — overly broad catch, lost error context, fallbacks that hide bugs
452
+
453
+ ### Lens 3: Type Design
454
+
455
+ **Agent:** \`type-design-analyzer\` (from pr-review-toolkit)
456
+
457
+ If the \`type-design-analyzer\` agent is unavailable, review inline for:
458
+ - **Type invariants** — types that permit invalid states, missing constraints
459
+ - **Encapsulation** — exposed internals, mutable shared state, leaky abstractions
460
+ - **Enforcement** — runtime validation gaps at system boundaries
461
+
462
+ ## Merging Results
463
+
464
+ After completing all three lenses (whether via agents or inline analysis), merge the findings. Deduplicate any overlapping issues. For each violation, report it exactly once under whichever lens found it first.
155
465
 
156
466
  ## Do NOT Report
157
467
 
158
468
  - Style, formatting, or naming preferences
159
469
  - Missing documentation, comments, or type annotations
160
- - Suggestions for "better" abstractions or patterns that aren't broken
161
- - Hypothetical issues that require unlikely preconditions
162
- - Issues in code that wasn't changed in this diff
470
+ - Suggestions for "better" abstractions that aren't broken
471
+ - Hypothetical issues requiring unlikely preconditions
472
+ - Issues in code not changed in this diff
163
473
 
164
474
  ## Guidelines
165
475
 
@@ -189,7 +499,8 @@ function loadBuiltInReview(name) {
189
499
  import { z as z2 } from "zod";
190
500
  var adapterConfigSchema = z2.object({
191
501
  allow_tool_use: z2.boolean().default(true),
192
- thinking_budget: z2.enum(["off", "low", "medium", "high"]).optional()
502
+ thinking_budget: z2.enum(["off", "low", "medium", "high"]).optional(),
503
+ model: z2.string().optional()
193
504
  });
194
505
  var cliConfigSchema = z2.object({
195
506
  default_preference: z2.array(z2.string().min(1)).min(1),
@@ -326,24 +637,24 @@ var CONFIG_FILE = "config.yml";
326
637
  var CHECKS_DIR = "checks";
327
638
  var REVIEWS_DIR = "reviews";
328
639
  async function loadConfig(rootDir = process.cwd()) {
329
- const gauntletPath = path2.join(rootDir, GAUNTLET_DIR);
330
- const configPath = path2.join(gauntletPath, CONFIG_FILE);
640
+ const gauntletPath = path3.join(rootDir, GAUNTLET_DIR);
641
+ const configPath = path3.join(gauntletPath, CONFIG_FILE);
331
642
  if (!await fileExists(configPath)) {
332
643
  throw new Error(`Configuration file not found at ${configPath}`);
333
644
  }
334
- const configContent = await fs2.readFile(configPath, "utf-8");
645
+ const configContent = await fs3.readFile(configPath, "utf-8");
335
646
  const projectConfigRaw = YAML2.parse(configContent);
336
647
  const projectConfig = gauntletConfigSchema.parse(projectConfigRaw);
337
- const checksPath = path2.join(gauntletPath, CHECKS_DIR);
648
+ const checksPath = path3.join(gauntletPath, CHECKS_DIR);
338
649
  const checks = {};
339
650
  if (await dirExists(checksPath)) {
340
- const checkFiles = await fs2.readdir(checksPath);
651
+ const checkFiles = await fs3.readdir(checksPath);
341
652
  for (const file of checkFiles) {
342
653
  if (file.endsWith(".yml") || file.endsWith(".yaml")) {
343
- const filePath = path2.join(checksPath, file);
344
- const content = await fs2.readFile(filePath, "utf-8");
654
+ const filePath = path3.join(checksPath, file);
655
+ const content = await fs3.readFile(filePath, "utf-8");
345
656
  const raw = YAML2.parse(content);
346
- const name = path2.basename(file, path2.extname(file));
657
+ const name = path3.basename(file, path3.extname(file));
347
658
  const parsed = checkGateSchema.parse(raw);
348
659
  const fixFile = parsed.fix_instructions_file || parsed.fix_instructions;
349
660
  const loadedCheck = {
@@ -360,14 +671,14 @@ async function loadConfig(rootDir = process.cwd()) {
360
671
  }
361
672
  }
362
673
  }
363
- const reviewsPath = path2.join(gauntletPath, REVIEWS_DIR);
674
+ const reviewsPath = path3.join(gauntletPath, REVIEWS_DIR);
364
675
  const reviews = {};
365
676
  if (await dirExists(reviewsPath)) {
366
- const reviewFiles = await fs2.readdir(reviewsPath);
677
+ const reviewFiles = await fs3.readdir(reviewsPath);
367
678
  const reviewNameSources = new Map;
368
679
  for (const file of reviewFiles) {
369
680
  if (file.endsWith(".md") || file.endsWith(".yml") || file.endsWith(".yaml")) {
370
- const name = path2.basename(file, path2.extname(file));
681
+ const name = path3.basename(file, path3.extname(file));
371
682
  if (isBuiltInReview(name)) {
372
683
  throw new Error(`Review file "${file}" uses the reserved "built-in:" prefix. Rename the file to avoid conflicts with built-in reviews.`);
373
684
  }
@@ -383,11 +694,11 @@ async function loadConfig(rootDir = process.cwd()) {
383
694
  }
384
695
  for (const file of reviewFiles) {
385
696
  if (file.endsWith(".md")) {
386
- const filePath = path2.join(reviewsPath, file);
387
- const content = await fs2.readFile(filePath, "utf-8");
697
+ const filePath = path3.join(reviewsPath, file);
698
+ const content = await fs3.readFile(filePath, "utf-8");
388
699
  const { data: frontmatter, content: promptBody } = matter(content);
389
700
  const parsedFrontmatter = reviewPromptFrontmatterSchema.parse(frontmatter);
390
- const name = path2.basename(file, ".md");
701
+ const name = path3.basename(file, ".md");
391
702
  const review = {
392
703
  name,
393
704
  prompt: file,
@@ -409,11 +720,11 @@ async function loadConfig(rootDir = process.cwd()) {
409
720
  }
410
721
  reviews[name] = review;
411
722
  } else if (file.endsWith(".yml") || file.endsWith(".yaml")) {
412
- const filePath = path2.join(reviewsPath, file);
413
- const content = await fs2.readFile(filePath, "utf-8");
723
+ const filePath = path3.join(reviewsPath, file);
724
+ const content = await fs3.readFile(filePath, "utf-8");
414
725
  const raw = YAML2.parse(content);
415
726
  const parsed = reviewYamlSchema.parse(raw);
416
- const name = path2.basename(file, path2.extname(file));
727
+ const name = path3.basename(file, path3.extname(file));
417
728
  const review = {
418
729
  name,
419
730
  prompt: file,
@@ -476,33 +787,33 @@ async function loadConfig(rootDir = process.cwd()) {
476
787
  }
477
788
  async function loadPromptFile(filePath, gauntletPath, source) {
478
789
  let resolvedPath;
479
- if (path2.isAbsolute(filePath)) {
790
+ if (path3.isAbsolute(filePath)) {
480
791
  console.warn(`Warning: ${source} uses absolute path "${filePath}". Prefer relative paths for portability.`);
481
792
  resolvedPath = filePath;
482
793
  } else {
483
- resolvedPath = path2.resolve(gauntletPath, filePath);
794
+ resolvedPath = path3.resolve(gauntletPath, filePath);
484
795
  }
485
- const normalizedGauntletPath = path2.resolve(gauntletPath);
486
- const relativeToDotGauntlet = path2.relative(normalizedGauntletPath, resolvedPath);
487
- if (relativeToDotGauntlet.startsWith("..") || path2.isAbsolute(relativeToDotGauntlet)) {
796
+ const normalizedGauntletPath = path3.resolve(gauntletPath);
797
+ const relativeToDotGauntlet = path3.relative(normalizedGauntletPath, resolvedPath);
798
+ if (relativeToDotGauntlet.startsWith("..") || path3.isAbsolute(relativeToDotGauntlet)) {
488
799
  console.warn(`Warning: ${source} references file outside .gauntlet/ directory: "${filePath}" (resolves to ${resolvedPath}). Review .gauntlet/ config changes carefully in PRs.`);
489
800
  }
490
801
  if (!await fileExists(resolvedPath)) {
491
802
  throw new Error(`File not found: ${resolvedPath} (referenced by ${source})`);
492
803
  }
493
- return fs2.readFile(resolvedPath, "utf-8");
804
+ return fs3.readFile(resolvedPath, "utf-8");
494
805
  }
495
- async function fileExists(path3) {
806
+ async function fileExists(path4) {
496
807
  try {
497
- const stat = await fs2.stat(path3);
808
+ const stat = await fs3.stat(path4);
498
809
  return stat.isFile();
499
810
  } catch {
500
811
  return false;
501
812
  }
502
813
  }
503
- async function dirExists(path3) {
814
+ async function dirExists(path4) {
504
815
  try {
505
- const stat = await fs2.stat(path3);
816
+ const stat = await fs3.stat(path4);
506
817
  return stat.isDirectory();
507
818
  } catch {
508
819
  return false;
@@ -601,8 +912,8 @@ class ChangeDetector {
601
912
  }
602
913
 
603
914
  // src/core/entry-point.ts
604
- import fs3 from "node:fs/promises";
605
- import path3 from "node:path";
915
+ import fs4 from "node:fs/promises";
916
+ import path4 from "node:path";
606
917
  import picomatch from "picomatch";
607
918
 
608
919
  class EntryPointExpander {
@@ -695,15 +1006,15 @@ class EntryPointExpander {
695
1006
  const relPath = file.slice(parentDir.length + 1);
696
1007
  const subDirName = relPath.split("/")[0];
697
1008
  if (subDirName) {
698
- affectedSubDirs.add(path3.join(parentDir, subDirName));
1009
+ affectedSubDirs.add(path4.join(parentDir, subDirName));
699
1010
  }
700
1011
  }
701
1012
  return Array.from(affectedSubDirs);
702
1013
  }
703
1014
  async listSubDirectories(parentDir) {
704
1015
  try {
705
- const dirents = await fs3.readdir(parentDir, { withFileTypes: true });
706
- return dirents.filter((d) => d.isDirectory()).map((d) => path3.join(parentDir, d.name));
1016
+ const dirents = await fs4.readdir(parentDir, { withFileTypes: true });
1017
+ return dirents.filter((d) => d.isDirectory()).map((d) => path4.join(parentDir, d.name));
707
1018
  } catch {
708
1019
  return [];
709
1020
  }
@@ -905,25 +1216,25 @@ ${config.fixInstructionsContent}
905
1216
 
906
1217
  // src/gates/review.ts
907
1218
  import { exec as exec8 } from "node:child_process";
908
- import fs16 from "node:fs/promises";
909
- import path15 from "node:path";
1219
+ import fs17 from "node:fs/promises";
1220
+ import path16 from "node:path";
910
1221
  import { promisify as promisify9 } from "node:util";
911
1222
 
912
1223
  // src/cli-adapters/index.ts
913
1224
  import { spawn as spawn2 } from "node:child_process";
914
- import fs15 from "node:fs/promises";
1225
+ import fs16 from "node:fs/promises";
915
1226
 
916
1227
  // src/cli-adapters/claude.ts
917
1228
  import { exec as exec3 } from "node:child_process";
918
- import fs10 from "node:fs/promises";
1229
+ import fs11 from "node:fs/promises";
919
1230
  import os2 from "node:os";
920
- import path10 from "node:path";
1231
+ import path11 from "node:path";
921
1232
  import { promisify as promisify4 } from "node:util";
922
1233
 
923
1234
  // src/commands/stop-hook.ts
924
1235
  import fsSync from "node:fs";
925
- import fs9 from "node:fs/promises";
926
- import path9 from "node:path";
1236
+ import fs10 from "node:fs/promises";
1237
+ import path10 from "node:path";
927
1238
 
928
1239
  // src/hooks/adapters/claude-stop-hook.ts
929
1240
  class ClaudeStopHookAdapter {
@@ -1017,8 +1328,8 @@ class CursorStopHookAdapter {
1017
1328
 
1018
1329
  // src/hooks/stop-hook-handler.ts
1019
1330
  import { execFile } from "node:child_process";
1020
- import fs8 from "node:fs/promises";
1021
- import path8 from "node:path";
1331
+ import fs9 from "node:fs/promises";
1332
+ import path9 from "node:path";
1022
1333
  import { promisify as promisify3 } from "node:util";
1023
1334
  import YAML3 from "yaml";
1024
1335
 
@@ -1077,9 +1388,9 @@ function resolveStopHookConfig(projectConfig, globalConfig) {
1077
1388
  }
1078
1389
 
1079
1390
  // src/output/app-logger.ts
1080
- import fs4 from "node:fs";
1391
+ import fs5 from "node:fs";
1081
1392
  import fsPromises from "node:fs/promises";
1082
- import path4 from "node:path";
1393
+ import path5 from "node:path";
1083
1394
  import {
1084
1395
  configure,
1085
1396
  getLogger
@@ -1137,8 +1448,8 @@ function safeStringify2(value) {
1137
1448
  }
1138
1449
  }
1139
1450
  function createDebugLogSink(logDir) {
1140
- const debugLogPath = path4.join(logDir, ".debug.log");
1141
- debugLogFd = fs4.openSync(debugLogPath, fs4.constants.O_WRONLY | fs4.constants.O_CREAT | fs4.constants.O_APPEND);
1451
+ const debugLogPath = path5.join(logDir, ".debug.log");
1452
+ debugLogFd = fs5.openSync(debugLogPath, fs5.constants.O_WRONLY | fs5.constants.O_CREAT | fs5.constants.O_APPEND);
1142
1453
  return (record) => {
1143
1454
  if (debugLogFd === null)
1144
1455
  return;
@@ -1149,7 +1460,7 @@ function createDebugLogSink(logDir) {
1149
1460
  const line = `[${timestamp}] ${level} [${category}] ${message}
1150
1461
  `;
1151
1462
  try {
1152
- fs4.writeSync(debugLogFd, line);
1463
+ fs5.writeSync(debugLogFd, line);
1153
1464
  } catch {}
1154
1465
  };
1155
1466
  }
@@ -1192,7 +1503,7 @@ async function initLogger(config) {
1192
1503
  } catch (error) {
1193
1504
  if (debugLogFd !== null) {
1194
1505
  try {
1195
- fs4.closeSync(debugLogFd);
1506
+ fs5.closeSync(debugLogFd);
1196
1507
  } catch {}
1197
1508
  debugLogFd = null;
1198
1509
  }
@@ -1202,7 +1513,7 @@ async function initLogger(config) {
1202
1513
  async function resetLogger() {
1203
1514
  if (debugLogFd !== null) {
1204
1515
  try {
1205
- fs4.closeSync(debugLogFd);
1516
+ fs5.closeSync(debugLogFd);
1206
1517
  } catch {}
1207
1518
  debugLogFd = null;
1208
1519
  }
@@ -1227,17 +1538,17 @@ function isLoggerConfigured() {
1227
1538
  }
1228
1539
 
1229
1540
  // src/hooks/stop-hook-state.ts
1230
- import fs7 from "node:fs/promises";
1231
- import path7 from "node:path";
1541
+ import fs8 from "node:fs/promises";
1542
+ import path8 from "node:path";
1232
1543
 
1233
1544
  // src/utils/execution-state.ts
1234
1545
  import { spawn } from "node:child_process";
1235
- import fs6 from "node:fs/promises";
1236
- import path6 from "node:path";
1546
+ import fs7 from "node:fs/promises";
1547
+ import path7 from "node:path";
1237
1548
 
1238
1549
  // src/utils/debug-log.ts
1239
- import fs5 from "node:fs/promises";
1240
- import path5 from "node:path";
1550
+ import fs6 from "node:fs/promises";
1551
+ import path6 from "node:path";
1241
1552
  var DEBUG_LOG_FILENAME = ".debug.log";
1242
1553
  var DEBUG_LOG_BACKUP_FILENAME = ".debug.log.1";
1243
1554
  function getDebugLogFilename() {
@@ -1254,8 +1565,8 @@ class DebugLogger {
1254
1565
  enabled;
1255
1566
  runStartTime;
1256
1567
  constructor(logDir, config) {
1257
- this.logPath = path5.join(logDir, DEBUG_LOG_FILENAME);
1258
- this.backupPath = path5.join(logDir, DEBUG_LOG_BACKUP_FILENAME);
1568
+ this.logPath = path6.join(logDir, DEBUG_LOG_FILENAME);
1569
+ this.backupPath = path6.join(logDir, DEBUG_LOG_BACKUP_FILENAME);
1259
1570
  this.maxSizeBytes = config.maxSizeMb * 1024 * 1024;
1260
1571
  this.enabled = config.enabled;
1261
1572
  }
@@ -1358,18 +1669,18 @@ class DebugLogger {
1358
1669
  `;
1359
1670
  try {
1360
1671
  await this.rotateIfNeeded();
1361
- await fs5.mkdir(path5.dirname(this.logPath), { recursive: true });
1362
- await fs5.appendFile(this.logPath, entry, "utf-8");
1672
+ await fs6.mkdir(path6.dirname(this.logPath), { recursive: true });
1673
+ await fs6.appendFile(this.logPath, entry, "utf-8");
1363
1674
  } catch {}
1364
1675
  }
1365
1676
  async rotateIfNeeded() {
1366
1677
  try {
1367
- const stat = await fs5.stat(this.logPath);
1678
+ const stat = await fs6.stat(this.logPath);
1368
1679
  if (stat.size >= this.maxSizeBytes) {
1369
1680
  try {
1370
- await fs5.rm(this.backupPath, { force: true });
1681
+ await fs6.rm(this.backupPath, { force: true });
1371
1682
  } catch {}
1372
- await fs5.rename(this.logPath, this.backupPath);
1683
+ await fs6.rename(this.logPath, this.backupPath);
1373
1684
  }
1374
1685
  } catch {}
1375
1686
  }
@@ -1427,8 +1738,8 @@ function isValidStateData(data) {
1427
1738
  }
1428
1739
  async function readExecutionState(logDir) {
1429
1740
  try {
1430
- const statePath = path6.join(logDir, EXECUTION_STATE_FILENAME);
1431
- const content = await fs6.readFile(statePath, "utf-8");
1741
+ const statePath = path7.join(logDir, EXECUTION_STATE_FILENAME);
1742
+ const content = await fs7.readFile(statePath, "utf-8");
1432
1743
  const data = JSON.parse(content);
1433
1744
  if (!isValidStateData(data))
1434
1745
  return null;
@@ -1483,7 +1794,7 @@ async function createWorkingTreeRef() {
1483
1794
  });
1484
1795
  }
1485
1796
  async function writeExecutionState(logDir) {
1486
- const statePath = path6.join(logDir, EXECUTION_STATE_FILENAME);
1797
+ const statePath = path7.join(logDir, EXECUTION_STATE_FILENAME);
1487
1798
  const [branch, commit, workingTreeRef, rawState] = await Promise.all([
1488
1799
  getCurrentBranch(),
1489
1800
  getCurrentCommit(),
@@ -1515,11 +1826,11 @@ async function writeExecutionState(logDir) {
1515
1826
  changes.working_tree_ref = workingTreeRef;
1516
1827
  }
1517
1828
  await getDebugLogger()?.logStateWrite(changes);
1518
- await fs6.mkdir(logDir, { recursive: true });
1519
- await fs6.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
1829
+ await fs7.mkdir(logDir, { recursive: true });
1830
+ await fs7.writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
1520
1831
  try {
1521
- const sessionRefPath = path6.join(logDir, SESSION_REF_FILENAME);
1522
- await fs6.rm(sessionRefPath, { force: true });
1832
+ const sessionRefPath = path7.join(logDir, SESSION_REF_FILENAME);
1833
+ await fs7.rm(sessionRefPath, { force: true });
1523
1834
  } catch {}
1524
1835
  }
1525
1836
  async function getCurrentBranch() {
@@ -1650,13 +1961,13 @@ function isAdapterCoolingDown(entry) {
1650
1961
  return Date.now() - markedAt < COOLDOWN_MS;
1651
1962
  }
1652
1963
  async function getUnhealthyAdapters(logDir) {
1653
- const statePath = path6.join(logDir, EXECUTION_STATE_FILENAME);
1964
+ const statePath = path7.join(logDir, EXECUTION_STATE_FILENAME);
1654
1965
  const rawState = await readRawState(statePath);
1655
1966
  return extractUnhealthyAdapters(rawState) ?? {};
1656
1967
  }
1657
1968
  async function readRawState(statePath) {
1658
1969
  try {
1659
- const content = await fs6.readFile(statePath, "utf-8");
1970
+ const content = await fs7.readFile(statePath, "utf-8");
1660
1971
  return JSON.parse(content);
1661
1972
  } catch {
1662
1973
  return null;
@@ -1664,7 +1975,7 @@ async function readRawState(statePath) {
1664
1975
  }
1665
1976
  async function markAdapterUnhealthy(logDir, adapterName, reason) {
1666
1977
  await getDebugLogger()?.logAdapterHealthChange(adapterName, false, reason);
1667
- const statePath = path6.join(logDir, EXECUTION_STATE_FILENAME);
1978
+ const statePath = path7.join(logDir, EXECUTION_STATE_FILENAME);
1668
1979
  const rawData = await readRawState(statePath) ?? {};
1669
1980
  const adapters = rawData.unhealthy_adapters ?? {};
1670
1981
  adapters[adapterName] = {
@@ -1672,12 +1983,12 @@ async function markAdapterUnhealthy(logDir, adapterName, reason) {
1672
1983
  reason
1673
1984
  };
1674
1985
  rawData.unhealthy_adapters = adapters;
1675
- await fs6.mkdir(logDir, { recursive: true });
1676
- await fs6.writeFile(statePath, JSON.stringify(rawData, null, 2), "utf-8");
1986
+ await fs7.mkdir(logDir, { recursive: true });
1987
+ await fs7.writeFile(statePath, JSON.stringify(rawData, null, 2), "utf-8");
1677
1988
  }
1678
1989
  async function markAdapterHealthy(logDir, adapterName) {
1679
1990
  await getDebugLogger()?.logAdapterHealthChange(adapterName, true);
1680
- const statePath = path6.join(logDir, EXECUTION_STATE_FILENAME);
1991
+ const statePath = path7.join(logDir, EXECUTION_STATE_FILENAME);
1681
1992
  const rawData = await readRawState(statePath);
1682
1993
  if (!rawData)
1683
1994
  return;
@@ -1690,13 +2001,13 @@ async function markAdapterHealthy(logDir, adapterName) {
1690
2001
  } else {
1691
2002
  rawData.unhealthy_adapters = adapters;
1692
2003
  }
1693
- await fs6.writeFile(statePath, JSON.stringify(rawData, null, 2), "utf-8");
2004
+ await fs7.writeFile(statePath, JSON.stringify(rawData, null, 2), "utf-8");
1694
2005
  }
1695
2006
  async function deleteExecutionState(logDir) {
1696
2007
  try {
1697
2008
  await getDebugLogger()?.logStateDelete();
1698
- const statePath = path6.join(logDir, EXECUTION_STATE_FILENAME);
1699
- await fs6.rm(statePath, { force: true });
2009
+ const statePath = path7.join(logDir, EXECUTION_STATE_FILENAME);
2010
+ await fs7.rm(statePath, { force: true });
1700
2011
  } catch {}
1701
2012
  }
1702
2013
 
@@ -1708,14 +2019,14 @@ var BLOCK_TIMESTAMPS_LOCK = ".block-timestamps.lock";
1708
2019
  var LOCK_TIMEOUT_MS = 2000;
1709
2020
  var LOCK_RETRY_MS = 50;
1710
2021
  async function acquireTimestampLock(logDir) {
1711
- const lockPath = path7.join(logDir, BLOCK_TIMESTAMPS_LOCK);
2022
+ const lockPath = path8.join(logDir, BLOCK_TIMESTAMPS_LOCK);
1712
2023
  const deadline = Date.now() + LOCK_TIMEOUT_MS;
1713
2024
  while (Date.now() < deadline) {
1714
2025
  try {
1715
- const handle = await fs7.open(lockPath, "wx");
2026
+ const handle = await fs8.open(lockPath, "wx");
1716
2027
  await handle.close();
1717
2028
  return async () => {
1718
- await fs7.rm(lockPath, { force: true }).catch(() => {});
2029
+ await fs8.rm(lockPath, { force: true }).catch(() => {});
1719
2030
  };
1720
2031
  } catch (err) {
1721
2032
  const code = err.code;
@@ -1728,7 +2039,7 @@ async function acquireTimestampLock(logDir) {
1728
2039
  }
1729
2040
  async function hasFailedRunLogs(logDir) {
1730
2041
  try {
1731
- const entries = await fs7.readdir(logDir);
2042
+ const entries = await fs8.readdir(logDir);
1732
2043
  return entries.some((f) => (f.endsWith(".log") || f.endsWith(".json")) && f !== "previous" && !f.startsWith("console.") && !f.startsWith("."));
1733
2044
  } catch {
1734
2045
  return false;
@@ -1777,8 +2088,8 @@ async function getLastRunStatus(logDir) {
1777
2088
  }
1778
2089
  async function readBlockTimestamps(logDir) {
1779
2090
  try {
1780
- const filePath = path7.join(logDir, BLOCK_TIMESTAMPS_FILE);
1781
- const content = await fs7.readFile(filePath, "utf-8");
2091
+ const filePath = path8.join(logDir, BLOCK_TIMESTAMPS_FILE);
2092
+ const content = await fs8.readFile(filePath, "utf-8");
1782
2093
  const parsed = JSON.parse(content);
1783
2094
  if (!Array.isArray(parsed))
1784
2095
  return [];
@@ -1794,8 +2105,8 @@ async function recordBlockTimestamp(logDir) {
1794
2105
  const existing = await readBlockTimestamps(logDir);
1795
2106
  const recent = existing.filter((ts) => now - ts < LOOP_WINDOW_MS);
1796
2107
  recent.push(now);
1797
- const filePath = path7.join(logDir, BLOCK_TIMESTAMPS_FILE);
1798
- await fs7.writeFile(filePath, JSON.stringify(recent), "utf-8");
2108
+ const filePath = path8.join(logDir, BLOCK_TIMESTAMPS_FILE);
2109
+ await fs8.writeFile(filePath, JSON.stringify(recent), "utf-8");
1799
2110
  return recent;
1800
2111
  } finally {
1801
2112
  await release();
@@ -1803,8 +2114,8 @@ async function recordBlockTimestamp(logDir) {
1803
2114
  }
1804
2115
  async function resetBlockTimestamps(logDir) {
1805
2116
  try {
1806
- const filePath = path7.join(logDir, BLOCK_TIMESTAMPS_FILE);
1807
- await fs7.rm(filePath, { force: true });
2117
+ const filePath = path8.join(logDir, BLOCK_TIMESTAMPS_FILE);
2118
+ await fs8.rm(filePath, { force: true });
1808
2119
  } catch {}
1809
2120
  }
1810
2121
 
@@ -1820,8 +2131,8 @@ var SKILL_INSTRUCTIONS = {
1820
2131
  };
1821
2132
  async function readProjectConfig(projectCwd) {
1822
2133
  try {
1823
- const configPath = path8.join(projectCwd, ".gauntlet", "config.yml");
1824
- const content = await fs8.readFile(configPath, "utf-8");
2134
+ const configPath = path9.join(projectCwd, ".gauntlet", "config.yml");
2135
+ const content = await fs9.readFile(configPath, "utf-8");
1825
2136
  return YAML3.parse(content);
1826
2137
  } catch {
1827
2138
  return;
@@ -1858,7 +2169,7 @@ function getStatusMessage(status, context) {
1858
2169
  }
1859
2170
  return STATUS_MESSAGES[status] ?? `Unknown status: ${status}`;
1860
2171
  }
1861
- async function getLogDir(projectCwd) {
2172
+ async function getLogDir2(projectCwd) {
1862
2173
  const config = await readProjectConfig(projectCwd);
1863
2174
  return config?.log_dir || DEFAULT_LOG_DIR;
1864
2175
  }
@@ -1868,8 +2179,8 @@ async function getDebugLogConfig(projectCwd) {
1868
2179
  }
1869
2180
  async function getResolvedStopHookConfig(projectCwd) {
1870
2181
  try {
1871
- const configPath = path8.join(projectCwd, ".gauntlet", "config.yml");
1872
- const content = await fs8.readFile(configPath, "utf-8");
2182
+ const configPath = path9.join(projectCwd, ".gauntlet", "config.yml");
2183
+ const content = await fs9.readFile(configPath, "utf-8");
1873
2184
  const raw = YAML3.parse(content);
1874
2185
  const projectStopHookConfig = raw?.stop_hook;
1875
2186
  const globalConfig = await loadGlobalConfig();
@@ -2126,7 +2437,7 @@ async function readStdin() {
2126
2437
  }
2127
2438
  async function fileExists2(filePath) {
2128
2439
  try {
2129
- await fs9.stat(filePath);
2440
+ await fs10.stat(filePath);
2130
2441
  return true;
2131
2442
  } catch {
2132
2443
  return false;
@@ -2203,12 +2514,12 @@ function registerStopHookCommand(program) {
2203
2514
  outputResult(adapter, createEarlyExitResult("stop_hook_active"));
2204
2515
  return;
2205
2516
  }
2206
- const quickConfigCheck = path9.join(process.cwd(), ".gauntlet", "config.yml");
2517
+ const quickConfigCheck = path10.join(process.cwd(), ".gauntlet", "config.yml");
2207
2518
  if (!await fileExists2(quickConfigCheck)) {
2208
2519
  outputResult(adapter, createEarlyExitResult("no_config"));
2209
2520
  return;
2210
2521
  }
2211
- const earlyLogDir = path9.join(process.cwd(), await getLogDir(process.cwd()));
2522
+ const earlyLogDir = path10.join(process.cwd(), await getLogDir2(process.cwd()));
2212
2523
  try {
2213
2524
  const globalConfig = await loadGlobalConfig();
2214
2525
  const projectDebugLogConfig = await getDebugLogConfig(process.cwd());
@@ -2218,16 +2529,16 @@ function registerStopHookCommand(program) {
2218
2529
  log.warn(`Debug logger init failed: ${initErr.message ?? "unknown"}`);
2219
2530
  }
2220
2531
  await debugLogger?.logCommand("stop-hook", []);
2221
- const markerLogDir = await getLogDir(process.cwd());
2222
- const markerPath = path9.join(process.cwd(), markerLogDir, STOP_HOOK_MARKER_FILE);
2532
+ const markerLogDir = await getLogDir2(process.cwd());
2533
+ const markerPath = path10.join(process.cwd(), markerLogDir, STOP_HOOK_MARKER_FILE);
2223
2534
  if (await fileExists2(markerPath)) {
2224
2535
  const STALE_MARKER_MS = 10 * 60 * 1000;
2225
2536
  try {
2226
- const stat = await fs9.stat(markerPath);
2537
+ const stat = await fs10.stat(markerPath);
2227
2538
  const ageMs = Date.now() - stat.mtimeMs;
2228
2539
  if (ageMs > STALE_MARKER_MS) {
2229
2540
  await debugLogger?.logStopHookEarlyExit("marker_stale", "proceeding", `age=${Math.round(ageMs / 1000)}s threshold=${Math.round(STALE_MARKER_MS / 1000)}s`);
2230
- await fs9.rm(markerPath, { force: true });
2541
+ await fs10.rm(markerPath, { force: true });
2231
2542
  } else {
2232
2543
  await debugLogger?.logStopHookEarlyExit("marker_fresh", "stop_hook_active", `age=${Math.round(ageMs / 1000)}s`);
2233
2544
  outputResult(adapter, createEarlyExitResult("stop_hook_active"));
@@ -2269,7 +2580,7 @@ function registerStopHookCommand(program) {
2269
2580
  log.info("Starting gauntlet validation...");
2270
2581
  const projectCwd = ctx.cwd;
2271
2582
  if (ctx.cwd !== process.cwd()) {
2272
- const configPath = path9.join(projectCwd, ".gauntlet", "config.yml");
2583
+ const configPath = path10.join(projectCwd, ".gauntlet", "config.yml");
2273
2584
  if (!await fileExists2(configPath)) {
2274
2585
  log.info("No gauntlet config found at hook cwd, allowing stop");
2275
2586
  await debugLogger?.logStopHookEarlyExit("no_config_at_cwd", "no_config", `cwd=${projectCwd}`);
@@ -2277,7 +2588,7 @@ function registerStopHookCommand(program) {
2277
2588
  return;
2278
2589
  }
2279
2590
  }
2280
- const logDir = path9.join(projectCwd, await getLogDir(projectCwd));
2591
+ const logDir = path10.join(projectCwd, await getLogDir2(projectCwd));
2281
2592
  await initLogger({
2282
2593
  mode: "stop-hook",
2283
2594
  logDir
@@ -2294,9 +2605,9 @@ function registerStopHookCommand(program) {
2294
2605
  }
2295
2606
  }
2296
2607
  await debugLogger?.logStopHookDiagnostics(diagnostics);
2297
- markerFilePath = path9.join(logDir, STOP_HOOK_MARKER_FILE);
2608
+ markerFilePath = path10.join(logDir, STOP_HOOK_MARKER_FILE);
2298
2609
  try {
2299
- await fs9.writeFile(markerFilePath, `${process.pid}`, "utf-8");
2610
+ await fs10.writeFile(markerFilePath, `${process.pid}`, "utf-8");
2300
2611
  } catch (mkErr) {
2301
2612
  const errMsg = mkErr.message ?? "unknown";
2302
2613
  log.warn(`Failed to create marker file: ${errMsg}`);
@@ -2311,7 +2622,7 @@ function registerStopHookCommand(program) {
2311
2622
  } finally {
2312
2623
  if (markerFilePath) {
2313
2624
  try {
2314
- await fs9.rm(markerFilePath, { force: true });
2625
+ await fs10.rm(markerFilePath, { force: true });
2315
2626
  } catch (rmErr) {
2316
2627
  const errMsg = rmErr.message ?? "unknown";
2317
2628
  log.warn(`Failed to remove marker file: ${errMsg}`);
@@ -2337,7 +2648,7 @@ function registerStopHookCommand(program) {
2337
2648
  outputResult(adapter, createEarlyExitResult("error", { errorMessage }));
2338
2649
  if (markerFilePath) {
2339
2650
  try {
2340
- await fs9.rm(markerFilePath, { force: true });
2651
+ await fs10.rm(markerFilePath, { force: true });
2341
2652
  } catch (rmErr) {
2342
2653
  const rmMsg = rmErr.message ?? "unknown";
2343
2654
  log.warn(`Failed to remove marker file in error handler: ${rmMsg}`);
@@ -2539,13 +2850,13 @@ class ClaudeAdapter {
2539
2850
  return ".claude/commands";
2540
2851
  }
2541
2852
  getUserCommandDir() {
2542
- return path10.join(os2.homedir(), ".claude", "commands");
2853
+ return path11.join(os2.homedir(), ".claude", "commands");
2543
2854
  }
2544
2855
  getProjectSkillDir() {
2545
2856
  return ".claude/skills";
2546
2857
  }
2547
2858
  getUserSkillDir() {
2548
- return path10.join(os2.homedir(), ".claude", "skills");
2859
+ return path11.join(os2.homedir(), ".claude", "skills");
2549
2860
  }
2550
2861
  getCommandExtension() {
2551
2862
  return ".md";
@@ -2564,24 +2875,24 @@ class ClaudeAdapter {
2564
2875
 
2565
2876
  --- DIFF ---
2566
2877
  ${opts.diff}`;
2567
- const tmpDir = os2.tmpdir();
2568
- const tmpFile = path10.join(tmpDir, `gauntlet-claude-${process.pid}-${Date.now()}.txt`);
2569
- await fs10.writeFile(tmpFile, fullContent);
2878
+ const tmpFile = path11.join(os2.tmpdir(), `gauntlet-claude-${process.pid}-${Date.now()}.txt`);
2879
+ await fs11.writeFile(tmpFile, fullContent);
2570
2880
  const args = ["-p"];
2571
2881
  if (opts.allowToolUse === false) {
2572
- args.push("--tools", "");
2882
+ args.push("--allowedTools", "Task");
2573
2883
  } else {
2574
- args.push("--allowedTools", "Read,Glob,Grep");
2884
+ args.push("--allowedTools", "Read,Glob,Grep,Task");
2575
2885
  }
2576
- args.push("--max-turns", "10");
2886
+ args.push("--max-turns", "25");
2577
2887
  const otelEnv = buildOtelEnv();
2578
2888
  const thinkingEnv = {};
2579
2889
  if (opts.thinkingBudget && opts.thinkingBudget in CLAUDE_THINKING_TOKENS) {
2580
2890
  thinkingEnv.MAX_THINKING_TOKENS = String(CLAUDE_THINKING_TOKENS[opts.thinkingBudget]);
2581
2891
  }
2582
- const cleanup = () => fs10.unlink(tmpFile).catch(() => {});
2892
+ const cleanup = () => fs11.unlink(tmpFile).catch(() => {});
2893
+ const { CLAUDECODE: _, ...parentEnv } = process.env;
2583
2894
  const execEnv = {
2584
- ...process.env,
2895
+ ...parentEnv,
2585
2896
  [GAUNTLET_STOP_HOOK_ACTIVE_ENV]: "1",
2586
2897
  ...otelEnv,
2587
2898
  ...thinkingEnv
@@ -2619,9 +2930,9 @@ ${opts.diff}`;
2619
2930
 
2620
2931
  // src/cli-adapters/codex.ts
2621
2932
  import { exec as exec4 } from "node:child_process";
2622
- import fs11 from "node:fs/promises";
2933
+ import fs12 from "node:fs/promises";
2623
2934
  import os3 from "node:os";
2624
- import path11 from "node:path";
2935
+ import path12 from "node:path";
2625
2936
  import { promisify as promisify5 } from "node:util";
2626
2937
  var execAsync4 = promisify5(exec4);
2627
2938
  var MAX_BUFFER_BYTES3 = 10 * 1024 * 1024;
@@ -2741,7 +3052,7 @@ class CodexAdapter {
2741
3052
  return null;
2742
3053
  }
2743
3054
  getUserCommandDir() {
2744
- return path11.join(os3.homedir(), ".codex", "prompts");
3055
+ return path12.join(os3.homedir(), ".codex", "prompts");
2745
3056
  }
2746
3057
  getProjectSkillDir() {
2747
3058
  return null;
@@ -2787,10 +3098,10 @@ class CodexAdapter {
2787
3098
  --- DIFF ---
2788
3099
  ${opts.diff}`;
2789
3100
  const tmpDir = os3.tmpdir();
2790
- const tmpFile = path11.join(tmpDir, `gauntlet-codex-${Date.now()}.txt`);
2791
- await fs11.writeFile(tmpFile, fullContent);
3101
+ const tmpFile = path12.join(tmpDir, `gauntlet-codex-${Date.now()}.txt`);
3102
+ await fs12.writeFile(tmpFile, fullContent);
2792
3103
  const args = this.buildArgs(opts.allowToolUse, opts.thinkingBudget);
2793
- const cleanup = () => fs11.unlink(tmpFile).catch(() => {});
3104
+ const cleanup = () => fs12.unlink(tmpFile).catch(() => {});
2794
3105
  if (opts.onOutput) {
2795
3106
  const raw = await runStreamingCommand({
2796
3107
  command: "codex",
@@ -2822,12 +3133,58 @@ ${opts.diff}`;
2822
3133
 
2823
3134
  // src/cli-adapters/cursor.ts
2824
3135
  import { exec as exec5 } from "node:child_process";
2825
- import fs12 from "node:fs/promises";
3136
+ import fs13 from "node:fs/promises";
2826
3137
  import os4 from "node:os";
2827
- import path12 from "node:path";
3138
+ import path13 from "node:path";
2828
3139
  import { promisify as promisify6 } from "node:util";
3140
+
3141
+ // src/cli-adapters/model-resolution.ts
3142
+ var TIER_SUFFIXES = ["-low", "-high", "-xhigh", "-fast"];
3143
+ var SAFE_MODEL_ID_PATTERN = /^[a-zA-Z0-9._-]+$/;
3144
+ function applyThinkingFilter(candidates, preferThinking) {
3145
+ if (preferThinking) {
3146
+ const thinking = candidates.filter((id) => id.endsWith("-thinking"));
3147
+ return thinking.length > 0 ? thinking : candidates;
3148
+ }
3149
+ return candidates.filter((id) => !id.endsWith("-thinking"));
3150
+ }
3151
+ function compareVersionsDesc(a, b) {
3152
+ if (!a && !b)
3153
+ return 0;
3154
+ if (!a)
3155
+ return 1;
3156
+ if (!b)
3157
+ return -1;
3158
+ if (a[0] !== b[0])
3159
+ return b[0] - a[0];
3160
+ return b[1] - a[1];
3161
+ }
3162
+ function resolveModelFromList(allModels, opts) {
3163
+ const candidates = allModels.filter((id) => id.split("-").includes(opts.baseName)).filter((id) => !TIER_SUFFIXES.some((s) => id.endsWith(s)));
3164
+ if (candidates.length === 0)
3165
+ return;
3166
+ const filtered = applyThinkingFilter(candidates, opts.preferThinking);
3167
+ if (filtered.length === 0)
3168
+ return;
3169
+ filtered.sort((a, b) => {
3170
+ const vA = a.match(/(\d+)\.(\d+)/);
3171
+ const vB = b.match(/(\d+)\.(\d+)/);
3172
+ return compareVersionsDesc(vA ? [Number(vA[1]), Number(vA[2])] : null, vB ? [Number(vB[1]), Number(vB[2])] : null);
3173
+ });
3174
+ return filtered[0];
3175
+ }
3176
+
3177
+ // src/cli-adapters/cursor.ts
2829
3178
  var execAsync5 = promisify6(exec5);
2830
3179
  var MAX_BUFFER_BYTES4 = 10 * 1024 * 1024;
3180
+ var log = getCategoryLogger("cursor");
3181
+ function parseModelList(output) {
3182
+ return output.split(`
3183
+ `).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => {
3184
+ const dashIndex = line.indexOf(" - ");
3185
+ return dashIndex >= 0 ? line.substring(0, dashIndex).trim() : line.trim();
3186
+ }).filter((id) => id.length > 0);
3187
+ }
2831
3188
 
2832
3189
  class CursorAdapter {
2833
3190
  name = "cursor";
@@ -2874,19 +3231,57 @@ class CursorAdapter {
2874
3231
  supportsHooks() {
2875
3232
  return true;
2876
3233
  }
3234
+ async resolveModel(baseName, thinkingBudget) {
3235
+ try {
3236
+ const stdout = await new Promise((resolve, reject) => {
3237
+ exec5("agent --list-models", { timeout: 1e4 }, (error, stdout2) => {
3238
+ if (error)
3239
+ reject(error);
3240
+ else
3241
+ resolve(stdout2);
3242
+ });
3243
+ });
3244
+ const models = parseModelList(stdout);
3245
+ const preferThinking = thinkingBudget !== undefined && thinkingBudget !== "off";
3246
+ const resolved = resolveModelFromList(models, {
3247
+ baseName,
3248
+ preferThinking
3249
+ });
3250
+ if (resolved === undefined) {
3251
+ log.warn(`No matching model found for "${baseName}"`);
3252
+ return;
3253
+ }
3254
+ if (!SAFE_MODEL_ID_PATTERN.test(resolved)) {
3255
+ log.warn(`Resolved model "${resolved}" contains unsafe characters`);
3256
+ return;
3257
+ }
3258
+ return resolved;
3259
+ } catch (err) {
3260
+ log.warn(`Failed to resolve model "${baseName}": ${err instanceof Error ? err.message : String(err)}`);
3261
+ return;
3262
+ }
3263
+ }
2877
3264
  async execute(opts) {
2878
3265
  const fullContent = `${opts.prompt}
2879
3266
 
2880
3267
  --- DIFF ---
2881
3268
  ${opts.diff}`;
2882
3269
  const tmpDir = os4.tmpdir();
2883
- const tmpFile = path12.join(tmpDir, `gauntlet-cursor-${process.pid}-${Date.now()}.txt`);
2884
- await fs12.writeFile(tmpFile, fullContent);
2885
- const cleanup = () => fs12.unlink(tmpFile).catch(() => {});
3270
+ const tmpFile = path13.join(tmpDir, `gauntlet-cursor-${process.pid}-${Date.now()}.txt`);
3271
+ await fs13.writeFile(tmpFile, fullContent);
3272
+ let resolvedModel;
3273
+ if (opts.model) {
3274
+ resolvedModel = await this.resolveModel(opts.model, opts.thinkingBudget);
3275
+ }
3276
+ const cleanup = () => fs13.unlink(tmpFile).catch(() => {});
3277
+ const args = ["--trust"];
3278
+ if (resolvedModel) {
3279
+ args.push("--model", resolvedModel);
3280
+ }
2886
3281
  if (opts.onOutput) {
2887
3282
  return runStreamingCommand({
2888
3283
  command: "agent",
2889
- args: [],
3284
+ args,
2890
3285
  tmpFile,
2891
3286
  timeoutMs: opts.timeoutMs,
2892
3287
  onOutput: opts.onOutput,
@@ -2894,7 +3289,8 @@ ${opts.diff}`;
2894
3289
  });
2895
3290
  }
2896
3291
  try {
2897
- const cmd = `cat "${tmpFile}" | agent`;
3292
+ const modelFlag = resolvedModel ? ` --model ${resolvedModel}` : "";
3293
+ const cmd = `cat "${tmpFile}" | agent --trust${modelFlag}`;
2898
3294
  const { stdout } = await execAsync5(cmd, {
2899
3295
  timeout: opts.timeoutMs,
2900
3296
  maxBuffer: MAX_BUFFER_BYTES4
@@ -2908,9 +3304,9 @@ ${opts.diff}`;
2908
3304
 
2909
3305
  // src/cli-adapters/gemini.ts
2910
3306
  import { exec as exec6 } from "node:child_process";
2911
- import fs13 from "node:fs/promises";
3307
+ import fs14 from "node:fs/promises";
2912
3308
  import os5 from "node:os";
2913
- import path13 from "node:path";
3309
+ import path14 from "node:path";
2914
3310
  import { promisify as promisify7 } from "node:util";
2915
3311
  var execAsync6 = promisify7(exec6);
2916
3312
  var MAX_BUFFER_BYTES5 = 10 * 1024 * 1024;
@@ -3042,7 +3438,7 @@ async function parseGeminiTelemetry(filePath) {
3042
3438
  const usage = {};
3043
3439
  let content;
3044
3440
  try {
3045
- content = await fs13.readFile(filePath, "utf-8");
3441
+ content = await fs14.readFile(filePath, "utf-8");
3046
3442
  } catch {
3047
3443
  return usage;
3048
3444
  }
@@ -3103,7 +3499,7 @@ class GeminiAdapter {
3103
3499
  return ".gemini/commands";
3104
3500
  }
3105
3501
  getUserCommandDir() {
3106
- return path13.join(os5.homedir(), ".gemini", "commands");
3502
+ return path14.join(os5.homedir(), ".gemini", "commands");
3107
3503
  }
3108
3504
  getProjectSkillDir() {
3109
3505
  return null;
@@ -3160,12 +3556,12 @@ ${summary}
3160
3556
  releaseLock = resolve;
3161
3557
  });
3162
3558
  await prev;
3163
- const settingsPath = path13.join(process.cwd(), ".gemini", "settings.json");
3559
+ const settingsPath = path14.join(process.cwd(), ".gemini", "settings.json");
3164
3560
  let backup = null;
3165
3561
  let existed = false;
3166
3562
  try {
3167
3563
  try {
3168
- backup = await fs13.readFile(settingsPath, "utf-8");
3564
+ backup = await fs14.readFile(settingsPath, "utf-8");
3169
3565
  existed = true;
3170
3566
  } catch {}
3171
3567
  const existing = backup ? JSON.parse(backup) : {};
@@ -3173,8 +3569,8 @@ ${summary}
3173
3569
  ...existing,
3174
3570
  thinkingConfig: { ...existing.thinkingConfig, thinkingBudget: budget }
3175
3571
  };
3176
- await fs13.mkdir(path13.dirname(settingsPath), { recursive: true });
3177
- await fs13.writeFile(settingsPath, JSON.stringify(merged, null, 2));
3572
+ await fs14.mkdir(path14.dirname(settingsPath), { recursive: true });
3573
+ await fs14.writeFile(settingsPath, JSON.stringify(merged, null, 2));
3178
3574
  } catch (err) {
3179
3575
  releaseLock();
3180
3576
  throw err;
@@ -3182,9 +3578,9 @@ ${summary}
3182
3578
  return async () => {
3183
3579
  try {
3184
3580
  if (existed && backup !== null) {
3185
- await fs13.writeFile(settingsPath, backup);
3581
+ await fs14.writeFile(settingsPath, backup);
3186
3582
  } else {
3187
- await fs13.unlink(settingsPath).catch(() => {});
3583
+ await fs14.unlink(settingsPath).catch(() => {});
3188
3584
  }
3189
3585
  } finally {
3190
3586
  releaseLock();
@@ -3223,14 +3619,14 @@ ${summary}
3223
3619
  --- DIFF ---
3224
3620
  ${opts.diff}`;
3225
3621
  const tmpDir = os5.tmpdir();
3226
- const tmpFile = path13.join(tmpDir, `gauntlet-gemini-${process.pid}-${Date.now()}.txt`);
3227
- await fs13.writeFile(tmpFile, fullContent);
3228
- const telemetryFile = path13.join(process.cwd(), `.gauntlet-gemini-telemetry-${process.pid}-${Date.now()}.log`);
3622
+ const tmpFile = path14.join(tmpDir, `gauntlet-gemini-${process.pid}-${Date.now()}.txt`);
3623
+ await fs14.writeFile(tmpFile, fullContent);
3624
+ const telemetryFile = path14.join(process.cwd(), `.gauntlet-gemini-telemetry-${process.pid}-${Date.now()}.log`);
3229
3625
  const telemetryEnv = this.buildTelemetryEnv(telemetryFile);
3230
3626
  const args = this.buildArgs(opts.allowToolUse);
3231
3627
  const cleanupThinking = await this.maybeApplyThinking(opts.thinkingBudget);
3232
- const cleanup = () => fs13.unlink(tmpFile).catch(() => {});
3233
- const cleanupTelemetry = () => fs13.unlink(telemetryFile).catch(() => {});
3628
+ const cleanup = () => fs14.unlink(tmpFile).catch(() => {});
3629
+ const cleanupTelemetry = () => fs14.unlink(telemetryFile).catch(() => {});
3234
3630
  try {
3235
3631
  if (opts.onOutput) {
3236
3632
  try {
@@ -3270,12 +3666,19 @@ ${opts.diff}`;
3270
3666
 
3271
3667
  // src/cli-adapters/github-copilot.ts
3272
3668
  import { exec as exec7 } from "node:child_process";
3273
- import fs14 from "node:fs/promises";
3669
+ import fs15 from "node:fs/promises";
3274
3670
  import os6 from "node:os";
3275
- import path14 from "node:path";
3671
+ import path15 from "node:path";
3276
3672
  import { promisify as promisify8 } from "node:util";
3277
3673
  var execAsync7 = promisify8(exec7);
3278
3674
  var MAX_BUFFER_BYTES6 = 10 * 1024 * 1024;
3675
+ var log2 = getCategoryLogger("github-copilot");
3676
+ function parseCopilotModels(helpOutput) {
3677
+ const match = helpOutput.match(/choices:\s*(.+?)\)/);
3678
+ if (!match?.[1])
3679
+ return [];
3680
+ return [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]).filter((id) => id !== undefined);
3681
+ }
3279
3682
 
3280
3683
  class GitHubCopilotAdapter {
3281
3684
  name = "github-copilot";
@@ -3322,14 +3725,47 @@ class GitHubCopilotAdapter {
3322
3725
  supportsHooks() {
3323
3726
  return false;
3324
3727
  }
3728
+ async resolveModel(baseName, _thinkingBudget) {
3729
+ try {
3730
+ const stdout = await new Promise((resolve, reject) => {
3731
+ exec7("copilot --help", { timeout: 1e4 }, (error, stdout2) => {
3732
+ if (error)
3733
+ reject(error);
3734
+ else
3735
+ resolve(stdout2);
3736
+ });
3737
+ });
3738
+ const models = parseCopilotModels(stdout);
3739
+ const resolved = resolveModelFromList(models, {
3740
+ baseName,
3741
+ preferThinking: false
3742
+ });
3743
+ if (resolved === undefined) {
3744
+ log2.warn(`No matching model found for "${baseName}"`);
3745
+ return;
3746
+ }
3747
+ if (!SAFE_MODEL_ID_PATTERN.test(resolved)) {
3748
+ log2.warn(`Resolved model "${resolved}" contains unsafe characters`);
3749
+ return;
3750
+ }
3751
+ return resolved;
3752
+ } catch (err) {
3753
+ log2.warn(`Failed to resolve model "${baseName}": ${err instanceof Error ? err.message : String(err)}`);
3754
+ return;
3755
+ }
3756
+ }
3325
3757
  async execute(opts) {
3326
3758
  const fullContent = `${opts.prompt}
3327
3759
 
3328
3760
  --- DIFF ---
3329
3761
  ${opts.diff}`;
3330
3762
  const tmpDir = os6.tmpdir();
3331
- const tmpFile = path14.join(tmpDir, `gauntlet-copilot-${process.pid}-${Date.now()}.txt`);
3332
- await fs14.writeFile(tmpFile, fullContent);
3763
+ const tmpFile = path15.join(tmpDir, `gauntlet-copilot-${process.pid}-${Date.now()}.txt`);
3764
+ await fs15.writeFile(tmpFile, fullContent);
3765
+ let resolvedModel;
3766
+ if (opts.model) {
3767
+ resolvedModel = await this.resolveModel(opts.model, opts.thinkingBudget);
3768
+ }
3333
3769
  const args = [
3334
3770
  "--allow-tool",
3335
3771
  "shell(cat)",
@@ -3344,7 +3780,10 @@ ${opts.diff}`;
3344
3780
  "--allow-tool",
3345
3781
  "shell(tail)"
3346
3782
  ];
3347
- const cleanup = () => fs14.unlink(tmpFile).catch(() => {});
3783
+ if (resolvedModel) {
3784
+ args.push("--model", resolvedModel);
3785
+ }
3786
+ const cleanup = () => fs15.unlink(tmpFile).catch(() => {});
3348
3787
  if (opts.onOutput) {
3349
3788
  return runStreamingCommand({
3350
3789
  command: "copilot",
@@ -3356,7 +3795,8 @@ ${opts.diff}`;
3356
3795
  });
3357
3796
  }
3358
3797
  try {
3359
- 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)"`;
3798
+ const modelFlag = resolvedModel ? ` --model ${resolvedModel}` : "";
3799
+ 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)"${modelFlag}`;
3360
3800
  const { stdout } = await execAsync7(cmd, {
3361
3801
  timeout: opts.timeoutMs,
3362
3802
  maxBuffer: MAX_BUFFER_BYTES6
@@ -3389,7 +3829,7 @@ ${output}` : ""}`);
3389
3829
  async function runStreamingCommand(opts) {
3390
3830
  return new Promise((resolve, reject) => {
3391
3831
  const chunks = [];
3392
- const inputStream = fs15.open(opts.tmpFile, "r").then((handle) => {
3832
+ const inputStream = fs16.open(opts.tmpFile, "r").then((handle) => {
3393
3833
  const stream = handle.createReadStream();
3394
3834
  return { stream, handle };
3395
3835
  });
@@ -3554,7 +3994,7 @@ function isValidViolationLocation(file, line, diffRanges) {
3554
3994
  }
3555
3995
 
3556
3996
  // src/gates/review.ts
3557
- var log = getCategoryLogger("gate", "review");
3997
+ var log3 = getCategoryLogger("gate", "review");
3558
3998
  var execAsync8 = promisify9(exec8);
3559
3999
  var MAX_BUFFER_BYTES7 = 10 * 1024 * 1024;
3560
4000
  var MAX_LOG_BUFFER_SIZE = 1e4;
@@ -3646,7 +4086,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3646
4086
  return logger;
3647
4087
  };
3648
4088
  try {
3649
- log.debug(`Starting review: ${config.name} | entry=${entryPointPath}`);
4089
+ log3.debug(`Starting review: ${config.name} | entry=${entryPointPath}`);
3650
4090
  await mainLogger(`Starting review: ${config.name}
3651
4091
  `);
3652
4092
  await mainLogger(`Entry point: ${entryPointPath}
@@ -3661,11 +4101,11 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3661
4101
  const diffFileRanges = parseDiff(diff);
3662
4102
  const diffFiles = diffFileRanges.size;
3663
4103
  const diffSizeMsg = `[diff-stats] files=${diffFiles} lines=${diffLines} chars=${diffChars} est_tokens=${diffEstTokens}`;
3664
- log.debug(diffSizeMsg);
4104
+ log3.debug(diffSizeMsg);
3665
4105
  await mainLogger(`${diffSizeMsg}
3666
4106
  `);
3667
4107
  if (!diff.trim()) {
3668
- log.debug(`Empty diff after trim, returning pass`);
4108
+ log3.debug(`Empty diff after trim, returning pass`);
3669
4109
  await mainLogger(`No changes found in entry point, skipping review.
3670
4110
  `);
3671
4111
  await mainLogger(`Result: pass - No changes to review
@@ -3682,31 +4122,31 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3682
4122
  const outputs = [];
3683
4123
  const preferences = config.cli_preference || [];
3684
4124
  const parallel = config.parallel ?? false;
3685
- log.debug(`Checking adapters: ${preferences.join(", ") || "(none configured)"}`);
4125
+ log3.debug(`Checking adapters: ${preferences.join(", ") || "(none configured)"}`);
3686
4126
  const healthyAdapters = [];
3687
4127
  const unhealthyMap = logDir ? await getUnhealthyAdapters(logDir) : {};
3688
4128
  for (const toolName of preferences) {
3689
4129
  const adapter = getAdapter(toolName);
3690
4130
  if (!adapter) {
3691
- log.debug(`Adapter ${toolName}: not found`);
4131
+ log3.debug(`Adapter ${toolName}: not found`);
3692
4132
  continue;
3693
4133
  }
3694
4134
  const unhealthyEntry = unhealthyMap[toolName];
3695
4135
  if (unhealthyEntry) {
3696
4136
  if (isAdapterCoolingDown(unhealthyEntry)) {
3697
- log.debug(`Adapter ${toolName}: cooling down`);
4137
+ log3.debug(`Adapter ${toolName}: cooling down`);
3698
4138
  await mainLogger(`Skipping ${toolName}: cooling down (${unhealthyEntry.reason})
3699
4139
  `);
3700
4140
  continue;
3701
4141
  }
3702
4142
  const health = await adapter.checkHealth();
3703
4143
  if (health.status === "healthy") {
3704
- log.debug(`Adapter ${toolName}: cooldown expired, binary available, clearing unhealthy flag`);
4144
+ log3.debug(`Adapter ${toolName}: cooldown expired, binary available, clearing unhealthy flag`);
3705
4145
  if (logDir) {
3706
4146
  await markAdapterHealthy(logDir, toolName);
3707
4147
  }
3708
4148
  } else {
3709
- log.debug(`Adapter ${toolName}: cooldown expired but binary missing`);
4149
+ log3.debug(`Adapter ${toolName}: cooldown expired but binary missing`);
3710
4150
  await mainLogger(`Skipping ${toolName}: ${health.message || "Missing"}
3711
4151
  `);
3712
4152
  continue;
@@ -3714,7 +4154,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3714
4154
  } else {
3715
4155
  const health = await adapter.checkHealth();
3716
4156
  if (health.status !== "healthy") {
3717
- log.debug(`Adapter ${toolName}: ${health.status}${health.message ? ` - ${health.message}` : ""}`);
4157
+ log3.debug(`Adapter ${toolName}: ${health.status}${health.message ? ` - ${health.message}` : ""}`);
3718
4158
  await mainLogger(`Skipping ${toolName}: ${health.message || "Unhealthy"}
3719
4159
  `);
3720
4160
  continue;
@@ -3724,7 +4164,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3724
4164
  }
3725
4165
  if (healthyAdapters.length === 0) {
3726
4166
  const msg = "Review dispatch failed: no healthy adapters available";
3727
- log.error(`ERROR: ${msg}`);
4167
+ log3.error(`ERROR: ${msg}`);
3728
4168
  await mainLogger(`Result: error - ${msg}
3729
4169
  `);
3730
4170
  return {
@@ -3735,7 +4175,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3735
4175
  logPaths
3736
4176
  };
3737
4177
  }
3738
- log.debug(`Healthy adapters: ${healthyAdapters.join(", ")}`);
4178
+ log3.debug(`Healthy adapters: ${healthyAdapters.join(", ")}`);
3739
4179
  const assignments = [];
3740
4180
  for (let i = 0;i < required; i++) {
3741
4181
  const adapter = healthyAdapters[i % healthyAdapters.length];
@@ -3785,12 +4225,12 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3785
4225
  }
3786
4226
  }
3787
4227
  const dispatchMsg = `Dispatching ${required} review(s) via round-robin: ${assignments.map((a) => `${a.adapter}@${a.reviewIndex}`).join(", ")}`;
3788
- log.debug(dispatchMsg);
4228
+ log3.debug(dispatchMsg);
3789
4229
  await mainLogger(`${dispatchMsg}
3790
4230
  `);
3791
4231
  const runningAssignments = assignments.filter((a) => !a.skip);
3792
4232
  const skippedAssignments = assignments.filter((a) => a.skip);
3793
- log.debug(`Running: ${runningAssignments.length}, Skipped: ${skippedAssignments.length}`);
4233
+ log3.debug(`Running: ${runningAssignments.length}, Skipped: ${skippedAssignments.length}`);
3794
4234
  const skippedSlotOutputs = [];
3795
4235
  for (const assignment of skippedAssignments) {
3796
4236
  const { logger, logPath } = await loggerFactory(assignment.adapter, assignment.reviewIndex);
@@ -3812,7 +4252,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3812
4252
  violations: [],
3813
4253
  passIteration: assignment.passIteration
3814
4254
  };
3815
- await fs16.writeFile(jsonPath, JSON.stringify(skippedOutput, null, 2));
4255
+ await fs17.writeFile(jsonPath, JSON.stringify(skippedOutput, null, 2));
3816
4256
  if (!logPathsSet.has(logPath)) {
3817
4257
  logPathsSet.add(logPath);
3818
4258
  logPaths.push(logPath);
@@ -3879,7 +4319,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3879
4319
  }
3880
4320
  const subResults = outputs.map((out) => {
3881
4321
  const specificLog = logPaths.find((p) => {
3882
- const filename = path15.basename(p);
4322
+ const filename = path16.basename(p);
3883
4323
  return filename.includes(`_${out.adapter}@${out.reviewIndex}.`) && filename.endsWith(".log");
3884
4324
  });
3885
4325
  let logPath = specificLog;
@@ -3901,7 +4341,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3901
4341
  });
3902
4342
  for (const skipped of skippedSlotOutputs) {
3903
4343
  const specificLog = logPaths.find((p) => {
3904
- const filename = path15.basename(p);
4344
+ const filename = path16.basename(p);
3905
4345
  return filename.includes(`_${skipped.adapter}@${skipped.reviewIndex}.`) && filename.endsWith(".log");
3906
4346
  });
3907
4347
  subResults.push({
@@ -3920,7 +4360,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3920
4360
  const bIndex = parseInt(b.nameSuffix.match(/@(\d+)/)?.[1] || "0", 10);
3921
4361
  return aIndex - bIndex;
3922
4362
  });
3923
- log.debug(`Complete: ${status} - ${message}`);
4363
+ log3.debug(`Complete: ${status} - ${message}`);
3924
4364
  await mainLogger(`Result: ${status} - ${message}
3925
4365
  `);
3926
4366
  return {
@@ -3936,7 +4376,7 @@ ${JSON_SYSTEM_INSTRUCTION}`;
3936
4376
  const err = error;
3937
4377
  const errMsg = err.message || "Unknown error";
3938
4378
  const errStack = err.stack || "";
3939
- log.error(`CRITICAL ERROR: ${errMsg} ${errStack}`);
4379
+ log3.error(`CRITICAL ERROR: ${errMsg} ${errStack}`);
3940
4380
  await mainLogger(`Critical Error: ${errMsg}
3941
4381
  `);
3942
4382
  await mainLogger(`Result: error
@@ -3985,7 +4425,7 @@ ${diff}
3985
4425
  const output = await adapter.execute({
3986
4426
  prompt: finalPrompt,
3987
4427
  diff,
3988
- model: config.model,
4428
+ model: adapterCfg?.model ?? config.model,
3989
4429
  timeoutMs: config.timeout ? config.timeout * 1000 : REVIEW_ADAPTER_TIMEOUT_MS,
3990
4430
  onOutput: (chunk) => {
3991
4431
  adapterLogger(chunk);
@@ -4002,7 +4442,7 @@ ${output}
4002
4442
  const reason = "Usage limit exceeded";
4003
4443
  if (logDir) {
4004
4444
  await markAdapterUnhealthy(logDir, adapter.name, reason);
4005
- log.debug(`Adapter ${adapter.name} marked unhealthy for 1 hour: ${reason}`);
4445
+ log3.debug(`Adapter ${adapter.name} marked unhealthy for 1 hour: ${reason}`);
4006
4446
  await mainLogger(`${adapter.name} marked unhealthy for 1 hour: ${reason}
4007
4447
  `);
4008
4448
  }
@@ -4120,7 +4560,7 @@ ${output}
4120
4560
  } catch (error) {
4121
4561
  const err = error;
4122
4562
  const errorMsg = `Error running ${adapter.name}@${reviewIndex}: ${err.message}`;
4123
- log.error(errorMsg);
4563
+ log3.error(errorMsg);
4124
4564
  await adapterLogger(`${errorMsg}
4125
4565
  `);
4126
4566
  await mainLogger(`${errorMsg}
@@ -4129,7 +4569,7 @@ ${output}
4129
4569
  const reason = "Usage limit exceeded";
4130
4570
  if (logDir) {
4131
4571
  await markAdapterUnhealthy(logDir, adapter.name, reason);
4132
- log.debug(`Adapter ${adapter.name} marked unhealthy for 1 hour: ${reason}`);
4572
+ log3.debug(`Adapter ${adapter.name} marked unhealthy for 1 hour: ${reason}`);
4133
4573
  await mainLogger(`${adapter.name} marked unhealthy for 1 hour: ${reason}
4134
4574
  `);
4135
4575
  }
@@ -4147,7 +4587,7 @@ ${output}
4147
4587
  }
4148
4588
  }
4149
4589
  async getDiff(entryPointPath, baseBranch, options) {
4150
- log.debug(`getDiff: entryPoint=${entryPointPath}, fixBase=${options?.fixBase ?? "none"}, uncommitted=${options?.uncommitted ?? false}, commit=${options?.commit ?? "none"}`);
4590
+ log3.debug(`getDiff: entryPoint=${entryPointPath}, fixBase=${options?.fixBase ?? "none"}, uncommitted=${options?.uncommitted ?? false}, commit=${options?.commit ?? "none"}`);
4151
4591
  if (options?.fixBase) {
4152
4592
  if (!/^[a-f0-9]+$/.test(options.fixBase)) {
4153
4593
  throw new Error(`Invalid session ref: ${options.fixBase}`);
@@ -4177,15 +4617,15 @@ ${output}
4177
4617
  }
4178
4618
  const scopedDiff = [diff, ...newUntrackedDiffs].filter(Boolean).join(`
4179
4619
  `);
4180
- log.debug(`Scoped diff via fixBase: ${scopedDiff.split(`
4620
+ log3.debug(`Scoped diff via fixBase: ${scopedDiff.split(`
4181
4621
  `).length} lines`);
4182
4622
  return scopedDiff;
4183
4623
  } catch (error) {
4184
- log.warn(`Failed to compute diff against fixBase ${options.fixBase}, falling back to full uncommitted diff. ${error instanceof Error ? error.message : error}`);
4624
+ log3.warn(`Failed to compute diff against fixBase ${options.fixBase}, falling back to full uncommitted diff. ${error instanceof Error ? error.message : error}`);
4185
4625
  }
4186
4626
  }
4187
4627
  if (options?.uncommitted) {
4188
- log.debug(`Using full uncommitted diff (no fixBase)`);
4628
+ log3.debug(`Using full uncommitted diff (no fixBase)`);
4189
4629
  const pathArg = this.pathArg(entryPointPath);
4190
4630
  const staged = await this.execDiff(`git diff --cached${pathArg}`);
4191
4631
  const unstaged = await this.execDiff(`git diff${pathArg}`);
@@ -4411,7 +4851,7 @@ The following violations were NOT marked as fixed or skipped and are still activ
4411
4851
  rawOutput,
4412
4852
  violations: json.violations || []
4413
4853
  };
4414
- await fs16.writeFile(jsonPath, JSON.stringify(fullOutput, null, 2));
4854
+ await fs17.writeFile(jsonPath, JSON.stringify(fullOutput, null, 2));
4415
4855
  return jsonPath;
4416
4856
  }
4417
4857
  parseLines(stdout) {
@@ -4597,13 +5037,13 @@ class Runner {
4597
5037
  }
4598
5038
 
4599
5039
  // src/output/console.ts
4600
- import fs18 from "node:fs/promises";
5040
+ import fs19 from "node:fs/promises";
4601
5041
  import chalk2 from "chalk";
4602
5042
 
4603
5043
  // src/utils/log-parser.ts
4604
- import fs17 from "node:fs/promises";
4605
- import path16 from "node:path";
4606
- var log2 = getCategoryLogger("log-parser");
5044
+ import fs18 from "node:fs/promises";
5045
+ import path17 from "node:path";
5046
+ var log4 = getCategoryLogger("log-parser");
4607
5047
  function parseReviewFilename(filename) {
4608
5048
  const m = filename.match(/^(.+)_([^@]+)@(\d+)\.(\d+)\.(log|json)$/);
4609
5049
  if (!m)
@@ -4621,9 +5061,9 @@ function parseReviewFilename(filename) {
4621
5061
  }
4622
5062
  async function parseJsonReviewFile(jsonPath) {
4623
5063
  try {
4624
- const content = await fs17.readFile(jsonPath, "utf-8");
5064
+ const content = await fs18.readFile(jsonPath, "utf-8");
4625
5065
  const data = JSON.parse(content);
4626
- const filename = path16.basename(jsonPath);
5066
+ const filename = path17.basename(jsonPath);
4627
5067
  const parsed = parseReviewFilename(filename);
4628
5068
  const jobId = parsed ? parsed.jobId : filename.replace(/\.\d+\.json$/, "");
4629
5069
  if (data.status === "pass" || data.status === "skipped_prior_pass") {
@@ -4657,7 +5097,7 @@ async function parseJsonReviewFile(jsonPath) {
4657
5097
  logPath: jsonPath.replace(/\.json$/, ".log")
4658
5098
  };
4659
5099
  } catch (error) {
4660
- log2.warn(`Failed to parse JSON review file: ${jsonPath} - ${error}`);
5100
+ log4.warn(`Failed to parse JSON review file: ${jsonPath} - ${error}`);
4661
5101
  return null;
4662
5102
  }
4663
5103
  }
@@ -4669,8 +5109,8 @@ function extractPrefix(filename) {
4669
5109
  }
4670
5110
  async function parseLogFile(logPath) {
4671
5111
  try {
4672
- const content = await fs17.readFile(logPath, "utf-8");
4673
- const filename = path16.basename(logPath);
5112
+ const content = await fs18.readFile(logPath, "utf-8");
5113
+ const filename = path17.basename(logPath);
4674
5114
  const parsed = parseReviewFilename(filename);
4675
5115
  const jobId = parsed ? parsed.jobId : extractPrefix(filename);
4676
5116
  if (content.includes("--- Review Output")) {
@@ -4794,7 +5234,7 @@ async function parseLogFile(logPath) {
4794
5234
  }
4795
5235
  async function reconstructHistory(logDir) {
4796
5236
  try {
4797
- const files = await fs17.readdir(logDir);
5237
+ const files = await fs18.readdir(logDir);
4798
5238
  const runNumbers = new Set;
4799
5239
  for (const file of files) {
4800
5240
  const m = file.match(/\.(\d+)\.(log|json)$/);
@@ -4818,9 +5258,9 @@ async function reconstructHistory(logDir) {
4818
5258
  const logFile = runFiles.find((f) => f.startsWith(`${prefix}.${runNum}.`) && f.endsWith(".log"));
4819
5259
  let failure = null;
4820
5260
  if (jsonFile) {
4821
- failure = await parseJsonReviewFile(path16.join(logDir, jsonFile));
5261
+ failure = await parseJsonReviewFile(path17.join(logDir, jsonFile));
4822
5262
  } else if (logFile) {
4823
- failure = await parseLogFile(path16.join(logDir, logFile));
5263
+ failure = await parseLogFile(path17.join(logDir, logFile));
4824
5264
  }
4825
5265
  if (failure) {
4826
5266
  for (const af of failure.adapterFailures) {
@@ -4878,7 +5318,7 @@ async function reconstructHistory(logDir) {
4878
5318
  }
4879
5319
  async function isJsonReviewPassing(jsonPath) {
4880
5320
  try {
4881
- const content = await fs17.readFile(jsonPath, "utf-8");
5321
+ const content = await fs18.readFile(jsonPath, "utf-8");
4882
5322
  const data = JSON.parse(content);
4883
5323
  return data.status === "pass" || data.status === "skipped_prior_pass";
4884
5324
  } catch {
@@ -4887,7 +5327,7 @@ async function isJsonReviewPassing(jsonPath) {
4887
5327
  }
4888
5328
  async function isLogReviewPassing(logPath) {
4889
5329
  try {
4890
- const content = await fs17.readFile(logPath, "utf-8");
5330
+ const content = await fs18.readFile(logPath, "utf-8");
4891
5331
  if (content.includes("Status: skipped_prior_pass")) {
4892
5332
  return true;
4893
5333
  }
@@ -4901,7 +5341,7 @@ async function isLogReviewPassing(logPath) {
4901
5341
  }
4902
5342
  async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
4903
5343
  try {
4904
- const files = await fs17.readdir(logDir);
5344
+ const files = await fs18.readdir(logDir);
4905
5345
  const gateFailures = [];
4906
5346
  const passedSlots = new Map;
4907
5347
  const reviewSlotMap = new Map;
@@ -4952,7 +5392,7 @@ async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
4952
5392
  const reviewIndex = parseInt(slotKey.substring(sepIdx + 1), 10);
4953
5393
  const parsed = parseReviewFilename(fileInfo.filename);
4954
5394
  const adapter = parsed?.adapter || "unknown";
4955
- const filePath = path16.join(logDir, fileInfo.filename);
5395
+ const filePath = path17.join(logDir, fileInfo.filename);
4956
5396
  let isPassing = false;
4957
5397
  if (fileInfo.ext === "json") {
4958
5398
  isPassing = await isJsonReviewPassing(filePath);
@@ -4987,7 +5427,7 @@ async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
4987
5427
  if (status === "skipped")
4988
5428
  continue;
4989
5429
  if (status !== "new" && status !== "fixed" && status !== "skipped") {
4990
- log2.warn(`Unexpected status "${status}" for violation in ${jobId}. Treating as "new".`);
5430
+ log4.warn(`Unexpected status "${status}" for violation in ${jobId}. Treating as "new".`);
4991
5431
  v.status = "new";
4992
5432
  }
4993
5433
  filteredViolations.push(v);
@@ -5010,7 +5450,7 @@ async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
5010
5450
  gateName: "",
5011
5451
  entryPoint: "",
5012
5452
  adapterFailures,
5013
- logPath: path16.join(logDir, `${jobId}.log`)
5453
+ logPath: path17.join(logDir, `${jobId}.log`)
5014
5454
  });
5015
5455
  }
5016
5456
  for (const [prefix, runMap] of checkPrefixMap.entries()) {
@@ -5020,9 +5460,9 @@ async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
5020
5460
  continue;
5021
5461
  let failure = null;
5022
5462
  if (exts.has("json")) {
5023
- failure = await parseJsonReviewFile(path16.join(logDir, `${prefix}.${latestRun}.json`));
5463
+ failure = await parseJsonReviewFile(path17.join(logDir, `${prefix}.${latestRun}.json`));
5024
5464
  } else if (exts.has("log")) {
5025
- failure = await parseLogFile(path16.join(logDir, `${prefix}.${latestRun}.log`));
5465
+ failure = await parseLogFile(path17.join(logDir, `${prefix}.${latestRun}.log`));
5026
5466
  }
5027
5467
  if (failure) {
5028
5468
  for (const af of failure.adapterFailures) {
@@ -5032,7 +5472,7 @@ async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
5032
5472
  if (status === "skipped")
5033
5473
  continue;
5034
5474
  if (status !== "new" && status !== "fixed" && status !== "skipped") {
5035
- log2.warn(`Unexpected status "${status}" for violation in ${failure.jobId}. Treating as "new".`);
5475
+ log4.warn(`Unexpected status "${status}" for violation in ${failure.jobId}. Treating as "new".`);
5036
5476
  v.status = "new";
5037
5477
  }
5038
5478
  filteredViolations.push(v);
@@ -5056,6 +5496,26 @@ async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
5056
5496
  return includePassedSlots ? { failures: [], passedSlots: new Map } : [];
5057
5497
  }
5058
5498
  }
5499
+ async function hasSkippedViolationsInLogs(opts) {
5500
+ const { logDir } = opts;
5501
+ try {
5502
+ const files = await fs18.readdir(logDir);
5503
+ for (const file of files) {
5504
+ if (!file.endsWith(".json"))
5505
+ continue;
5506
+ try {
5507
+ const content = await fs18.readFile(path17.join(logDir, file), "utf-8");
5508
+ const data = JSON.parse(content);
5509
+ if (data.violations?.some((v) => v.status === "skipped")) {
5510
+ return true;
5511
+ }
5512
+ } catch {}
5513
+ }
5514
+ return false;
5515
+ } catch {
5516
+ return false;
5517
+ }
5518
+ }
5059
5519
 
5060
5520
  // src/output/console.ts
5061
5521
  class ConsoleReporter {
@@ -5173,7 +5633,7 @@ ${chalk2.bold("━━━━━━━━━━━━━━━━━━━━━
5173
5633
  const allDetails = [];
5174
5634
  for (const logPath of logPaths) {
5175
5635
  try {
5176
- const logContent = await fs18.readFile(logPath, "utf-8");
5636
+ const logContent = await fs19.readFile(logPath, "utf-8");
5177
5637
  const details = this.parseLogContent(logContent, result.jobId);
5178
5638
  allDetails.push(...details);
5179
5639
  } catch (_error) {
@@ -5283,9 +5743,9 @@ ${chalk2.bold("━━━━━━━━━━━━━━━━━━━━━
5283
5743
  }
5284
5744
 
5285
5745
  // src/output/console-log.ts
5286
- import fs19 from "node:fs";
5746
+ import fs20 from "node:fs";
5287
5747
  import fsPromises2 from "node:fs/promises";
5288
- import path17 from "node:path";
5748
+ import path18 from "node:path";
5289
5749
  import { inspect } from "node:util";
5290
5750
  var ANSI_REGEX = /\x1b(?:\[[0-9;?]*[A-Za-z]|[78])/g;
5291
5751
  function stripAnsi(text) {
@@ -5295,9 +5755,9 @@ function formatArgs(args) {
5295
5755
  return args.map((a) => typeof a === "string" ? a : inspect(a, { depth: 4 })).join(" ");
5296
5756
  }
5297
5757
  function openLogFileExclusive(logDir, runNum) {
5298
- const logPath = path17.join(logDir, `console.${runNum}.log`);
5758
+ const logPath = path18.join(logDir, `console.${runNum}.log`);
5299
5759
  try {
5300
- const fd = fs19.openSync(logPath, fs19.constants.O_WRONLY | fs19.constants.O_CREAT | fs19.constants.O_EXCL);
5760
+ const fd = fs20.openSync(logPath, fs20.constants.O_WRONLY | fs20.constants.O_CREAT | fs20.constants.O_EXCL);
5301
5761
  return { fd, logPath };
5302
5762
  } catch (e) {
5303
5763
  const error = e;
@@ -5311,9 +5771,9 @@ function openLogFileExclusive(logDir, runNum) {
5311
5771
  function openLogFileFallback(logDir, startNum) {
5312
5772
  let runNum = startNum;
5313
5773
  for (let attempts = 0;attempts < 100; attempts++) {
5314
- const logPath = path17.join(logDir, `console.${runNum}.log`);
5774
+ const logPath = path18.join(logDir, `console.${runNum}.log`);
5315
5775
  try {
5316
- const fd = fs19.openSync(logPath, fs19.constants.O_WRONLY | fs19.constants.O_CREAT | fs19.constants.O_EXCL);
5776
+ const fd = fs20.openSync(logPath, fs20.constants.O_WRONLY | fs20.constants.O_CREAT | fs20.constants.O_EXCL);
5317
5777
  return { fd, logPath };
5318
5778
  } catch (e) {
5319
5779
  const error = e;
@@ -5334,7 +5794,7 @@ async function startConsoleLog(logDir, runNumber) {
5334
5794
  if (isClosed)
5335
5795
  return;
5336
5796
  try {
5337
- fs19.writeSync(fd, stripAnsi(text));
5797
+ fs20.writeSync(fd, stripAnsi(text));
5338
5798
  } catch {}
5339
5799
  };
5340
5800
  const originalLog = console.log;
@@ -5382,7 +5842,7 @@ async function startConsoleLog(logDir, runNumber) {
5382
5842
  process.stdout.write = originalStdoutWrite;
5383
5843
  process.stderr.write = originalStderrWrite;
5384
5844
  try {
5385
- fs19.closeSync(fd);
5845
+ fs20.closeSync(fd);
5386
5846
  } catch {}
5387
5847
  },
5388
5848
  writeToLogOnly: (text) => {
@@ -5390,20 +5850,20 @@ async function startConsoleLog(logDir, runNumber) {
5390
5850
  }
5391
5851
  };
5392
5852
  } catch (error) {
5393
- fs19.closeSync(fd);
5853
+ fs20.closeSync(fd);
5394
5854
  throw error;
5395
5855
  }
5396
5856
  }
5397
5857
 
5398
5858
  // src/output/logger.ts
5399
- import fs20 from "node:fs/promises";
5400
- import path18 from "node:path";
5859
+ import fs21 from "node:fs/promises";
5860
+ import path19 from "node:path";
5401
5861
  function formatTimestamp() {
5402
5862
  return new Date().toISOString();
5403
5863
  }
5404
5864
  async function computeGlobalRunNumber(logDir) {
5405
5865
  try {
5406
- const files = await fs20.readdir(logDir);
5866
+ const files = await fs21.readdir(logDir);
5407
5867
  let max = 0;
5408
5868
  for (const file of files) {
5409
5869
  if (!file.endsWith(".log") && !file.endsWith(".json"))
@@ -5429,7 +5889,7 @@ class Logger {
5429
5889
  this.logDir = logDir;
5430
5890
  }
5431
5891
  async init() {
5432
- await fs20.mkdir(this.logDir, { recursive: true });
5892
+ await fs21.mkdir(this.logDir, { recursive: true });
5433
5893
  this.globalRunNumber = await computeGlobalRunNumber(this.logDir);
5434
5894
  }
5435
5895
  async close() {}
@@ -5447,14 +5907,14 @@ class Logger {
5447
5907
  } else {
5448
5908
  filename = `${safeName}.${runNum}.log`;
5449
5909
  }
5450
- return path18.join(this.logDir, filename);
5910
+ return path19.join(this.logDir, filename);
5451
5911
  }
5452
5912
  async initFile(logPath) {
5453
5913
  if (this.initializedFiles.has(logPath)) {
5454
5914
  return;
5455
5915
  }
5456
5916
  this.initializedFiles.add(logPath);
5457
- await fs20.writeFile(logPath, "");
5917
+ await fs21.writeFile(logPath, "");
5458
5918
  }
5459
5919
  async createJobLogger(jobId) {
5460
5920
  const logPath = await this.getLogPath(jobId);
@@ -5466,7 +5926,7 @@ class Logger {
5466
5926
  if (lines.length > 0) {
5467
5927
  lines[0] = `[${timestamp}] ${lines[0]}`;
5468
5928
  }
5469
- await fs20.appendFile(logPath, lines.join(`
5929
+ await fs21.appendFile(logPath, lines.join(`
5470
5930
  `) + (text.endsWith(`
5471
5931
  `) ? "" : `
5472
5932
  `));
@@ -5483,7 +5943,7 @@ class Logger {
5483
5943
  if (lines.length > 0) {
5484
5944
  lines[0] = `[${timestamp}] ${lines[0]}`;
5485
5945
  }
5486
- await fs20.appendFile(logPath, lines.join(`
5946
+ await fs21.appendFile(logPath, lines.join(`
5487
5947
  `) + (text.endsWith(`
5488
5948
  `) ? "" : `
5489
5949
  `));
@@ -5494,8 +5954,8 @@ class Logger {
5494
5954
  }
5495
5955
 
5496
5956
  // src/commands/shared.ts
5497
- import fs21 from "node:fs/promises";
5498
- import path19 from "node:path";
5957
+ import fs22 from "node:fs/promises";
5958
+ import path20 from "node:path";
5499
5959
  var LOCK_FILENAME = ".gauntlet-run.lock";
5500
5960
  var SESSION_REF_FILENAME2 = ".session_ref";
5501
5961
  async function shouldAutoClean(logDir, baseBranch) {
@@ -5530,17 +5990,17 @@ async function performAutoClean(logDir, result, maxPreviousLogs = 3) {
5530
5990
  }
5531
5991
  async function exists(filePath) {
5532
5992
  try {
5533
- await fs21.stat(filePath);
5993
+ await fs22.stat(filePath);
5534
5994
  return true;
5535
5995
  } catch {
5536
5996
  return false;
5537
5997
  }
5538
5998
  }
5539
5999
  async function acquireLock(logDir) {
5540
- await fs21.mkdir(logDir, { recursive: true });
5541
- const lockPath = path19.resolve(logDir, LOCK_FILENAME);
6000
+ await fs22.mkdir(logDir, { recursive: true });
6001
+ const lockPath = path20.resolve(logDir, LOCK_FILENAME);
5542
6002
  try {
5543
- await fs21.writeFile(lockPath, String(process.pid), { flag: "wx" });
6003
+ await fs22.writeFile(lockPath, String(process.pid), { flag: "wx" });
5544
6004
  } catch (err) {
5545
6005
  if (typeof err === "object" && err !== null && "code" in err && err.code === "EEXIST") {
5546
6006
  console.error(`Error: A gauntlet run is already in progress (lock file: ${lockPath}).`);
@@ -5551,14 +6011,14 @@ async function acquireLock(logDir) {
5551
6011
  }
5552
6012
  }
5553
6013
  async function releaseLock(logDir) {
5554
- const lockPath = path19.resolve(logDir, LOCK_FILENAME);
6014
+ const lockPath = path20.resolve(logDir, LOCK_FILENAME);
5555
6015
  try {
5556
- await fs21.rm(lockPath, { force: true });
6016
+ await fs22.rm(lockPath, { force: true });
5557
6017
  } catch {}
5558
6018
  }
5559
6019
  async function hasExistingLogs(logDir) {
5560
6020
  try {
5561
- const entries = await fs21.readdir(logDir);
6021
+ const entries = await fs22.readdir(logDir);
5562
6022
  return entries.some((f) => (f.endsWith(".log") || f.endsWith(".json")) && f !== "previous" && !f.startsWith("console.") && !f.startsWith("."));
5563
6023
  } catch {
5564
6024
  return false;
@@ -5577,7 +6037,7 @@ function getPersistentFiles() {
5577
6037
  }
5578
6038
  async function hasCurrentLogs(logDir) {
5579
6039
  try {
5580
- const files = await fs21.readdir(logDir);
6040
+ const files = await fs22.readdir(logDir);
5581
6041
  const persistentFiles = getPersistentFiles();
5582
6042
  return files.some((f) => (f.endsWith(".log") || f.endsWith(".json")) && f !== "previous" && !persistentFiles.has(f));
5583
6043
  } catch {
@@ -5589,23 +6049,23 @@ function getCurrentLogFiles(files) {
5589
6049
  return files.filter((file) => !file.startsWith("previous") && !persistentFiles.has(file));
5590
6050
  }
5591
6051
  async function deleteCurrentLogs(logDir) {
5592
- const files = await fs21.readdir(logDir);
5593
- await Promise.all(getCurrentLogFiles(files).map((file) => fs21.rm(path19.join(logDir, file), { recursive: true, force: true })));
6052
+ const files = await fs22.readdir(logDir);
6053
+ await Promise.all(getCurrentLogFiles(files).map((file) => fs22.rm(path20.join(logDir, file), { recursive: true, force: true })));
5594
6054
  }
5595
6055
  async function rotatePreviousDirs(logDir, maxPreviousLogs) {
5596
6056
  const oldestSuffix = maxPreviousLogs - 1;
5597
6057
  const oldestDir = oldestSuffix === 0 ? "previous" : `previous.${oldestSuffix}`;
5598
- const oldestPath = path19.join(logDir, oldestDir);
6058
+ const oldestPath = path20.join(logDir, oldestDir);
5599
6059
  if (await exists(oldestPath)) {
5600
- await fs21.rm(oldestPath, { recursive: true, force: true });
6060
+ await fs22.rm(oldestPath, { recursive: true, force: true });
5601
6061
  }
5602
6062
  for (let i = oldestSuffix - 1;i >= 0; i--) {
5603
6063
  const fromName = i === 0 ? "previous" : `previous.${i}`;
5604
6064
  const toName = `previous.${i + 1}`;
5605
- const fromPath = path19.join(logDir, fromName);
5606
- const toPath = path19.join(logDir, toName);
6065
+ const fromPath = path20.join(logDir, fromName);
6066
+ const toPath = path20.join(logDir, toName);
5607
6067
  if (await exists(fromPath)) {
5608
- await fs21.rename(fromPath, toPath);
6068
+ await fs22.rename(fromPath, toPath);
5609
6069
  }
5610
6070
  }
5611
6071
  }
@@ -5620,12 +6080,12 @@ async function cleanLogs(logDir, maxPreviousLogs = 3) {
5620
6080
  return;
5621
6081
  }
5622
6082
  await rotatePreviousDirs(logDir, maxPreviousLogs);
5623
- const previousDir = path19.join(logDir, "previous");
5624
- await fs21.mkdir(previousDir, { recursive: true });
5625
- const files = await fs21.readdir(logDir);
5626
- await Promise.all(getCurrentLogFiles(files).map((file) => fs21.rename(path19.join(logDir, file), path19.join(previousDir, file))));
6083
+ const previousDir = path20.join(logDir, "previous");
6084
+ await fs22.mkdir(previousDir, { recursive: true });
6085
+ const files = await fs22.readdir(logDir);
6086
+ await Promise.all(getCurrentLogFiles(files).map((file) => fs22.rename(path20.join(logDir, file), path20.join(previousDir, file))));
5627
6087
  try {
5628
- await fs21.rm(path19.join(logDir, SESSION_REF_FILENAME2), { force: true });
6088
+ await fs22.rm(path20.join(logDir, SESSION_REF_FILENAME2), { force: true });
5629
6089
  } catch {}
5630
6090
  } catch (error) {
5631
6091
  console.warn("Failed to clean logs in", logDir, ":", error instanceof Error ? error.message : error);
@@ -5769,14 +6229,14 @@ function registerCheckCommand(program) {
5769
6229
  });
5770
6230
  }
5771
6231
  // src/commands/ci/init.ts
5772
- import fs23 from "node:fs/promises";
5773
- import path21 from "node:path";
6232
+ import fs24 from "node:fs/promises";
6233
+ import path22 from "node:path";
5774
6234
  import chalk4 from "chalk";
5775
6235
  import YAML5 from "yaml";
5776
6236
 
5777
6237
  // src/config/ci-loader.ts
5778
- import fs22 from "node:fs/promises";
5779
- import path20 from "node:path";
6238
+ import fs23 from "node:fs/promises";
6239
+ import path21 from "node:path";
5780
6240
  import YAML4 from "yaml";
5781
6241
 
5782
6242
  // src/config/ci-schema.ts
@@ -5806,17 +6266,17 @@ var ciConfigSchema = z3.object({
5806
6266
  var GAUNTLET_DIR2 = ".gauntlet";
5807
6267
  var CI_FILE = "ci.yml";
5808
6268
  async function loadCIConfig(rootDir = process.cwd()) {
5809
- const ciPath = path20.join(rootDir, GAUNTLET_DIR2, CI_FILE);
6269
+ const ciPath = path21.join(rootDir, GAUNTLET_DIR2, CI_FILE);
5810
6270
  if (!await fileExists3(ciPath)) {
5811
6271
  throw new Error(`CI configuration file not found at ${ciPath}. Run 'agent-gauntlet ci init' to create it.`);
5812
6272
  }
5813
- const content = await fs22.readFile(ciPath, "utf-8");
6273
+ const content = await fs23.readFile(ciPath, "utf-8");
5814
6274
  const raw = YAML4.parse(content);
5815
6275
  return ciConfigSchema.parse(raw);
5816
6276
  }
5817
- async function fileExists3(path21) {
6277
+ async function fileExists3(path22) {
5818
6278
  try {
5819
- const stat = await fs22.stat(path21);
6279
+ const stat = await fs23.stat(path22);
5820
6280
  return stat.isFile();
5821
6281
  } catch {
5822
6282
  return false;
@@ -5907,13 +6367,13 @@ jobs:
5907
6367
 
5908
6368
  // src/commands/ci/init.ts
5909
6369
  async function initCI() {
5910
- const workflowDir = path21.join(process.cwd(), ".github", "workflows");
5911
- const workflowPath = path21.join(workflowDir, "gauntlet.yml");
5912
- const gauntletDir = path21.join(process.cwd(), ".gauntlet");
5913
- const ciConfigPath = path21.join(gauntletDir, "ci.yml");
6370
+ const workflowDir = path22.join(process.cwd(), ".github", "workflows");
6371
+ const workflowPath = path22.join(workflowDir, "gauntlet.yml");
6372
+ const gauntletDir = path22.join(process.cwd(), ".gauntlet");
6373
+ const ciConfigPath = path22.join(gauntletDir, "ci.yml");
5914
6374
  if (!await fileExists4(ciConfigPath)) {
5915
6375
  console.log(chalk4.yellow("Creating starter .gauntlet/ci.yml..."));
5916
- await fs23.mkdir(gauntletDir, { recursive: true });
6376
+ await fs24.mkdir(gauntletDir, { recursive: true });
5917
6377
  const starterContent = `# CI Configuration for Agent Gauntlet
5918
6378
  # Define runtimes, services, and which checks to run in CI.
5919
6379
 
@@ -5935,7 +6395,7 @@ checks:
5935
6395
  # - name: linter
5936
6396
  # requires_runtimes: [ruby]
5937
6397
  `;
5938
- await fs23.writeFile(ciConfigPath, starterContent);
6398
+ await fs24.writeFile(ciConfigPath, starterContent);
5939
6399
  } else {
5940
6400
  console.log(chalk4.dim("Found existing .gauntlet/ci.yml"));
5941
6401
  }
@@ -5946,7 +6406,7 @@ checks:
5946
6406
  console.warn(chalk4.yellow("Could not load CI config to inject services. Workflow will have no services defined."));
5947
6407
  }
5948
6408
  console.log(chalk4.dim(`Generating ${workflowPath}...`));
5949
- await fs23.mkdir(workflowDir, { recursive: true });
6409
+ await fs24.mkdir(workflowDir, { recursive: true });
5950
6410
  let templateContent = workflow_default;
5951
6411
  if (ciConfig?.services && Object.keys(ciConfig.services).length > 0) {
5952
6412
  const servicesYaml = YAML5.stringify({ services: ciConfig.services });
@@ -5958,12 +6418,12 @@ checks:
5958
6418
  templateContent = templateContent.replace(` # Services will be injected here by agent-gauntlet
5959
6419
  `, "");
5960
6420
  }
5961
- await fs23.writeFile(workflowPath, templateContent);
6421
+ await fs24.writeFile(workflowPath, templateContent);
5962
6422
  console.log(chalk4.green("Successfully generated GitHub Actions workflow!"));
5963
6423
  }
5964
- async function fileExists4(path22) {
6424
+ async function fileExists4(path23) {
5965
6425
  try {
5966
- const stat = await fs23.stat(path22);
6426
+ const stat = await fs24.stat(path23);
5967
6427
  return stat.isFile();
5968
6428
  } catch {
5969
6429
  return false;
@@ -6164,12 +6624,12 @@ function printJobsByWorkDir(jobs) {
6164
6624
  }
6165
6625
  }
6166
6626
  // src/commands/health.ts
6167
- import path23 from "node:path";
6627
+ import path24 from "node:path";
6168
6628
  import chalk7 from "chalk";
6169
6629
 
6170
6630
  // src/config/validator.ts
6171
- import fs24 from "node:fs/promises";
6172
- import path22 from "node:path";
6631
+ import fs25 from "node:fs/promises";
6632
+ import path23 from "node:path";
6173
6633
  import matter2 from "gray-matter";
6174
6634
  import YAML6 from "yaml";
6175
6635
  import { ZodError } from "zod";
@@ -6180,10 +6640,10 @@ var REVIEWS_DIR2 = "reviews";
6180
6640
  async function validateConfig(rootDir = process.cwd()) {
6181
6641
  const issues = [];
6182
6642
  const filesChecked = [];
6183
- const gauntletPath = path22.join(rootDir, GAUNTLET_DIR3);
6643
+ const gauntletPath = path23.join(rootDir, GAUNTLET_DIR3);
6184
6644
  const existingCheckNames = new Set;
6185
6645
  const existingReviewNames = new Set;
6186
- const configPath = path22.join(gauntletPath, CONFIG_FILE2);
6646
+ const configPath = path23.join(gauntletPath, CONFIG_FILE2);
6187
6647
  let projectConfig = null;
6188
6648
  const checks = {};
6189
6649
  const reviews = {};
@@ -6191,7 +6651,7 @@ async function validateConfig(rootDir = process.cwd()) {
6191
6651
  try {
6192
6652
  if (await fileExists5(configPath)) {
6193
6653
  filesChecked.push(configPath);
6194
- const configContent = await fs24.readFile(configPath, "utf-8");
6654
+ const configContent = await fs25.readFile(configPath, "utf-8");
6195
6655
  try {
6196
6656
  const raw = YAML6.parse(configContent);
6197
6657
  projectConfig = gauntletConfigSchema.parse(raw);
@@ -6237,17 +6697,17 @@ async function validateConfig(rootDir = process.cwd()) {
6237
6697
  message: `Error reading file: ${err.message}`
6238
6698
  });
6239
6699
  }
6240
- const checksPath = path22.join(gauntletPath, CHECKS_DIR2);
6700
+ const checksPath = path23.join(gauntletPath, CHECKS_DIR2);
6241
6701
  if (await dirExists2(checksPath)) {
6242
6702
  try {
6243
- const checkFiles = await fs24.readdir(checksPath);
6703
+ const checkFiles = await fs25.readdir(checksPath);
6244
6704
  for (const file of checkFiles) {
6245
6705
  if (file.endsWith(".yml") || file.endsWith(".yaml")) {
6246
- const filePath = path22.join(checksPath, file);
6706
+ const filePath = path23.join(checksPath, file);
6247
6707
  filesChecked.push(filePath);
6248
- const name = path22.basename(file, path22.extname(file));
6708
+ const name = path23.basename(file, path23.extname(file));
6249
6709
  try {
6250
- const content = await fs24.readFile(filePath, "utf-8");
6710
+ const content = await fs25.readFile(filePath, "utf-8");
6251
6711
  const raw = YAML6.parse(content);
6252
6712
  const parsed = checkGateSchema.parse(raw);
6253
6713
  existingCheckNames.add(name);
@@ -6299,14 +6759,14 @@ async function validateConfig(rootDir = process.cwd()) {
6299
6759
  });
6300
6760
  }
6301
6761
  }
6302
- const reviewsPath = path22.join(gauntletPath, REVIEWS_DIR2);
6762
+ const reviewsPath = path23.join(gauntletPath, REVIEWS_DIR2);
6303
6763
  if (await dirExists2(reviewsPath)) {
6304
6764
  try {
6305
- const reviewFiles = await fs24.readdir(reviewsPath);
6765
+ const reviewFiles = await fs25.readdir(reviewsPath);
6306
6766
  const reviewNameSources = new Map;
6307
6767
  for (const file of reviewFiles) {
6308
6768
  if (file.endsWith(".md") || file.endsWith(".yml") || file.endsWith(".yaml")) {
6309
- const name = path22.basename(file, path22.extname(file));
6769
+ const name = path23.basename(file, path23.extname(file));
6310
6770
  const sources = reviewNameSources.get(name) || [];
6311
6771
  sources.push(file);
6312
6772
  reviewNameSources.set(name, sources);
@@ -6323,12 +6783,12 @@ async function validateConfig(rootDir = process.cwd()) {
6323
6783
  }
6324
6784
  for (const file of reviewFiles) {
6325
6785
  if (file.endsWith(".md")) {
6326
- const filePath = path22.join(reviewsPath, file);
6327
- const reviewName = path22.basename(file, ".md");
6786
+ const filePath = path23.join(reviewsPath, file);
6787
+ const reviewName = path23.basename(file, ".md");
6328
6788
  existingReviewNames.add(reviewName);
6329
6789
  filesChecked.push(filePath);
6330
6790
  try {
6331
- const content = await fs24.readFile(filePath, "utf-8");
6791
+ const content = await fs25.readFile(filePath, "utf-8");
6332
6792
  const { data: frontmatter, content: _promptBody } = matter2(content);
6333
6793
  if (!frontmatter || Object.keys(frontmatter).length === 0) {
6334
6794
  issues.push({
@@ -6340,7 +6800,7 @@ async function validateConfig(rootDir = process.cwd()) {
6340
6800
  }
6341
6801
  validateCliPreferenceTools(frontmatter, filePath, issues);
6342
6802
  const parsedFrontmatter = reviewPromptFrontmatterSchema.parse(frontmatter);
6343
- const name = path22.basename(file, ".md");
6803
+ const name = path23.basename(file, ".md");
6344
6804
  reviews[name] = parsedFrontmatter;
6345
6805
  reviewSourceFiles[name] = filePath;
6346
6806
  validateReviewSemantics(parsedFrontmatter, filePath, issues);
@@ -6348,12 +6808,12 @@ async function validateConfig(rootDir = process.cwd()) {
6348
6808
  handleReviewValidationError(error, filePath, issues);
6349
6809
  }
6350
6810
  } else if (file.endsWith(".yml") || file.endsWith(".yaml")) {
6351
- const filePath = path22.join(reviewsPath, file);
6352
- const reviewName = path22.basename(file, path22.extname(file));
6811
+ const filePath = path23.join(reviewsPath, file);
6812
+ const reviewName = path23.basename(file, path23.extname(file));
6353
6813
  existingReviewNames.add(reviewName);
6354
6814
  filesChecked.push(filePath);
6355
6815
  try {
6356
- const content = await fs24.readFile(filePath, "utf-8");
6816
+ const content = await fs25.readFile(filePath, "utf-8");
6357
6817
  const raw = YAML6.parse(content);
6358
6818
  validateCliPreferenceTools(raw, filePath, issues);
6359
6819
  const parsed = reviewYamlSchema.parse(raw);
@@ -6484,7 +6944,7 @@ async function validateConfig(rootDir = process.cwd()) {
6484
6944
  for (const [reviewName, reviewConfig] of Object.entries(reviews)) {
6485
6945
  const pref = reviewConfig.cli_preference;
6486
6946
  if (pref && Array.isArray(pref)) {
6487
- const reviewFile = reviewSourceFiles[reviewName] || path22.join(reviewsPath, `${reviewName}.md`);
6947
+ const reviewFile = reviewSourceFiles[reviewName] || path23.join(reviewsPath, `${reviewName}.md`);
6488
6948
  for (let i = 0;i < pref.length; i++) {
6489
6949
  const tool = pref[i];
6490
6950
  if (!allowedTools.has(tool)) {
@@ -6610,17 +7070,17 @@ function handleReviewValidationError(error, filePath, issues) {
6610
7070
  }
6611
7071
  }
6612
7072
  }
6613
- async function fileExists5(path23) {
7073
+ async function fileExists5(path24) {
6614
7074
  try {
6615
- const stat = await fs24.stat(path23);
7075
+ const stat = await fs25.stat(path24);
6616
7076
  return stat.isFile();
6617
7077
  } catch {
6618
7078
  return false;
6619
7079
  }
6620
7080
  }
6621
- async function dirExists2(path23) {
7081
+ async function dirExists2(path24) {
6622
7082
  try {
6623
- const stat = await fs24.stat(path23);
7083
+ const stat = await fs25.stat(path24);
6624
7084
  return stat.isDirectory();
6625
7085
  } catch {
6626
7086
  return false;
@@ -6636,7 +7096,7 @@ function registerHealthCommand(program) {
6636
7096
  console.log(chalk7.yellow(" No config files found"));
6637
7097
  } else {
6638
7098
  for (const file of validationResult.filesChecked) {
6639
- const relativePath = path23.relative(process.cwd(), file);
7099
+ const relativePath = path24.relative(process.cwd(), file);
6640
7100
  console.log(chalk7.dim(` ${relativePath}`));
6641
7101
  }
6642
7102
  if (validationResult.valid && validationResult.issues.length === 0) {
@@ -6644,7 +7104,7 @@ function registerHealthCommand(program) {
6644
7104
  } else {
6645
7105
  const issuesByFile = new Map;
6646
7106
  for (const issue of validationResult.issues) {
6647
- const relativeFile = path23.relative(process.cwd(), issue.file);
7107
+ const relativeFile = path24.relative(process.cwd(), issue.file);
6648
7108
  if (!issuesByFile.has(relativeFile)) {
6649
7109
  issuesByFile.set(relativeFile, []);
6650
7110
  }
@@ -6763,16 +7223,15 @@ function registerHelpCommand(program) {
6763
7223
  });
6764
7224
  }
6765
7225
  // src/commands/init.ts
6766
- import { readFileSync } from "node:fs";
6767
- import fs26 from "node:fs/promises";
6768
- import path25 from "node:path";
7226
+ import fs27 from "node:fs/promises";
7227
+ import path26 from "node:path";
6769
7228
  import { fileURLToPath } from "node:url";
6770
7229
  import chalk10 from "chalk";
6771
7230
 
6772
7231
  // src/commands/init-checksums.ts
6773
7232
  import { createHash } from "node:crypto";
6774
- import fs25 from "node:fs/promises";
6775
- import path24 from "node:path";
7233
+ import fs26 from "node:fs/promises";
7234
+ import path25 from "node:path";
6776
7235
  async function computeSkillChecksum(skillDir) {
6777
7236
  const files = await collectFiles(skillDir);
6778
7237
  files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
@@ -6783,26 +7242,6 @@ async function computeSkillChecksum(skillDir) {
6783
7242
  }
6784
7243
  return hash.digest("hex");
6785
7244
  }
6786
- function computeExpectedSkillChecksum(content, references) {
6787
- const entries = [
6788
- { relativePath: "SKILL.md", content }
6789
- ];
6790
- if (references) {
6791
- for (const [name, refContent] of Object.entries(references)) {
6792
- entries.push({
6793
- relativePath: path24.join("references", name),
6794
- content: refContent
6795
- });
6796
- }
6797
- }
6798
- entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
6799
- const hash = createHash("sha256");
6800
- for (const entry of entries) {
6801
- hash.update(entry.relativePath);
6802
- hash.update(entry.content);
6803
- }
6804
- return hash.digest("hex");
6805
- }
6806
7245
  function computeHookChecksum(entries) {
6807
7246
  const gauntletEntries = entries.filter((entry) => isGauntletHookEntry(entry));
6808
7247
  const hash = createHash("sha256");
@@ -6825,14 +7264,14 @@ function isGauntletHookEntry(entry) {
6825
7264
  async function collectFiles(dir, baseDir) {
6826
7265
  const base = baseDir ?? dir;
6827
7266
  const results = [];
6828
- const entries = await fs25.readdir(dir, { withFileTypes: true });
7267
+ const entries = await fs26.readdir(dir, { withFileTypes: true });
6829
7268
  for (const entry of entries) {
6830
- const fullPath = path24.join(dir, entry.name);
7269
+ const fullPath = path25.join(dir, entry.name);
6831
7270
  if (entry.isDirectory()) {
6832
7271
  results.push(...await collectFiles(fullPath, base));
6833
7272
  } else if (entry.isFile()) {
6834
- const content = await fs25.readFile(fullPath, "utf-8");
6835
- results.push({ relativePath: path24.relative(base, fullPath), content });
7273
+ const content = await fs26.readFile(fullPath, "utf-8");
7274
+ results.push({ relativePath: path25.relative(base, fullPath), content });
6836
7275
  }
6837
7276
  }
6838
7277
  return results;
@@ -6896,11 +7335,26 @@ async function promptHookOverwrite(hookFile, skipPrompts) {
6896
7335
  }
6897
7336
 
6898
7337
  // src/commands/init.ts
6899
- var __dirname2 = path25.dirname(fileURLToPath(import.meta.url));
6900
- function readSkillTemplate(filename) {
6901
- const templatePath = path25.join(__dirname2, "skill-templates", filename);
6902
- return readFileSync(templatePath, "utf-8");
6903
- }
7338
+ var __dirname2 = path26.dirname(fileURLToPath(import.meta.url));
7339
+ var SKILLS_SOURCE_DIR = path26.join(__dirname2, "..", "..", "skills");
7340
+ var SKILL_ACTIONS = [
7341
+ "run",
7342
+ "check",
7343
+ "push-pr",
7344
+ "fix-pr",
7345
+ "status",
7346
+ "help",
7347
+ "setup"
7348
+ ];
7349
+ var SKILL_DESCRIPTIONS = {
7350
+ run: "Run the verification suite",
7351
+ check: "Run checks only (no reviews)",
7352
+ "push-pr": "Commit, push, and create a PR",
7353
+ "fix-pr": "Fix PR review comments and CI failures",
7354
+ status: "Show gauntlet status",
7355
+ help: "Diagnose and explain gauntlet behavior",
7356
+ setup: "Configure checks and reviews interactively"
7357
+ };
6904
7358
  var CLI_PREFERENCE_ORDER = [
6905
7359
  "codex",
6906
7360
  "claude",
@@ -6911,156 +7365,19 @@ var CLI_PREFERENCE_ORDER = [
6911
7365
  var ADAPTER_CONFIG = {
6912
7366
  claude: { allow_tool_use: false, thinking_budget: "high" },
6913
7367
  codex: { allow_tool_use: false, thinking_budget: "low" },
6914
- gemini: { allow_tool_use: false, thinking_budget: "low" }
6915
- };
6916
- function buildGauntletSkillContent(mode) {
6917
- const isRun = mode === "run";
6918
- const name = isRun ? "run" : "check";
6919
- const description = isRun ? "Run the full verification gauntlet. Use this as the final step after completing a coding task — verifies quality, runs checks, and ensures all gates pass. Must be run before committing, pushing, or creating PRs." : "Run checks only (no reviews)";
6920
- const command = isRun ? "agent-gauntlet run" : "agent-gauntlet check";
6921
- const heading = isRun ? "Execute the autonomous verification suite." : "Run the gauntlet checks only — no AI reviews.";
6922
- const disableModelInvocation = isRun ? "false" : "true";
6923
- const frontmatter = `---
6924
- name: gauntlet-${name}
6925
- description: >-
6926
- ${description}
6927
- disable-model-invocation: ${disableModelInvocation}
6928
- allowed-tools: Bash
6929
- ---`;
6930
- const steps = [
6931
- `1. Run \`agent-gauntlet clean\` to archive any previous log files`,
6932
- `2. Run \`${command}\``
6933
- ];
6934
- if (isRun) {
6935
- steps.push(`3. If it fails:
6936
- - Identify the failed gates from the console output.
6937
- - For CHECK failures: Read the \`.log\` file path provided in the output. If the log contains a \`--- Fix Instructions ---\` section, follow those instructions to fix the issue. If it contains a \`--- Fix Skill: <name> ---\` section, invoke that skill.
6938
- - For REVIEW failures: Read the \`.json\` file path provided in the "Review: <path>" output.
6939
- 4. Address the violations:
6940
- - For REVIEW violations: You MUST update the \`"status"\` and \`"result"\` fields in the provided \`.json\` file for EACH violation.
6941
- - Set \`"status": "fixed"\` and add a brief description to \`"result"\` for issues you fix.
6942
- - Set \`"status": "skipped"\` and add a brief reason to \`"result"\` for issues you skip (based on the trust level).
6943
- - Do NOT modify any other attributes (file, line, issue, priority) in the JSON file.
6944
- - Apply the trust level above when deciding whether to act on AI reviewer feedback.
6945
- 5. Run \`${command}\` again to verify your fixes. Do NOT run \`agent-gauntlet clean\` between retries. The tool detects existing logs and automatically switches to verification mode.
6946
- 6. Repeat steps 3-5 until one of the following termination conditions is met:
6947
- - "Status: Passed" appears in the output (logs are automatically archived)
6948
- - "Status: Passed with warnings" appears in the output (remaining issues were skipped)
6949
- - "Status: Retry limit exceeded" appears in the output -> Run \`agent-gauntlet clean\` to archive logs for the session record. Do NOT retry after cleaning.
6950
- 7. Provide a summary of the session:
6951
- - Issues Fixed: (list key fixes)
6952
- - Issues Skipped: (list skipped items and reasons)
6953
- - Outstanding Failures: (if retry limit exceeded, list unverified fixes and remaining issues)`);
6954
- } else {
6955
- steps.push(`3. If any checks fail:
6956
- - Read the \`.log\` file path provided in the output for each failed check. If the log contains a \`--- Fix Instructions ---\` section, follow those instructions. If it contains a \`--- Fix Skill: <name> ---\` section, invoke that skill.
6957
- - Fix the issues found.
6958
- 4. Run \`${command}\` again to verify your fixes. Do NOT run \`agent-gauntlet clean\` between retries.
6959
- 5. Repeat steps 3-4 until all checks pass or you've made 3 attempts.
6960
- 6. Provide a summary of the session:
6961
- - Checks Passed: (list)
6962
- - Checks Failed: (list with brief reason)
6963
- - Fixes Applied: (list key fixes)`);
6964
- }
6965
- if (isRun) {
6966
- return `${frontmatter}
6967
- <!--
6968
- REVIEW TRUST LEVEL
6969
- Controls how aggressively the agent acts on AI reviewer feedback.
6970
- Change the trust_level value below to one of: high, medium, low
6971
-
6972
- - high: Fix all issues unless you strongly disagree or have low confidence the human wants the change.
6973
- - medium: Fix issues you reasonably agree with or believe the human wants fixed. (DEFAULT)
6974
- - low: Fix only issues you strongly agree with or are confident the human wants fixed.
6975
- -->
6976
- <!-- trust_level: medium -->
6977
-
6978
- # /gauntlet-${name}
6979
- ${heading}
6980
-
6981
- **Review trust level: medium** — Fix issues you reasonably agree with or believe the human wants to be fixed. Skip issues that are purely stylistic, subjective, or that you believe the human would not want changed. When you skip an issue, briefly state what was skipped and why.
6982
-
6983
- ${steps.join(`
6984
- `)}
6985
- `;
6986
- }
6987
- return `${frontmatter}
6988
-
6989
- # /gauntlet-${name}
6990
- ${heading}
6991
-
6992
- ${steps.join(`
6993
- `)}
6994
- `;
6995
- }
6996
- var GAUNTLET_RUN_SKILL_CONTENT = buildGauntletSkillContent("run");
6997
- var GAUNTLET_CHECK_SKILL_CONTENT = buildGauntletSkillContent("check");
6998
- var PUSH_PR_SKILL_CONTENT = readSkillTemplate("push-pr.md");
6999
- var FIX_PR_SKILL_CONTENT = readSkillTemplate("fix-pr.md");
7000
- var GAUNTLET_STATUS_SKILL_CONTENT = readSkillTemplate("status.md");
7001
- var HELP_SKILL_BUNDLE = {
7002
- content: readSkillTemplate("help-skill.md"),
7003
- references: {
7004
- "stop-hook-troubleshooting.md": readSkillTemplate("help-ref-stop-hook-troubleshooting.md"),
7005
- "config-troubleshooting.md": readSkillTemplate("help-ref-config-troubleshooting.md"),
7006
- "gate-troubleshooting.md": readSkillTemplate("help-ref-gate-troubleshooting.md"),
7007
- "lock-troubleshooting.md": readSkillTemplate("help-ref-lock-troubleshooting.md"),
7008
- "adapter-troubleshooting.md": readSkillTemplate("help-ref-adapter-troubleshooting.md"),
7009
- "ci-pr-troubleshooting.md": readSkillTemplate("help-ref-ci-pr-troubleshooting.md")
7368
+ gemini: { allow_tool_use: false, thinking_budget: "low" },
7369
+ cursor: { allow_tool_use: false, thinking_budget: "low", model: "codex" },
7370
+ "github-copilot": {
7371
+ allow_tool_use: false,
7372
+ thinking_budget: "low",
7373
+ model: "codex"
7010
7374
  }
7011
7375
  };
7012
- var SETUP_SKILL_CONTENT = readSkillTemplate("setup-skill.md");
7013
- var CHECK_CATALOG_REFERENCE = readSkillTemplate("check-catalog.md");
7014
- var PROJECT_STRUCTURE_REFERENCE = readSkillTemplate("setup-ref-project-structure.md");
7015
- var SKILL_DEFINITIONS = [
7016
- {
7017
- action: "run",
7018
- content: GAUNTLET_RUN_SKILL_CONTENT,
7019
- description: "Run the verification suite"
7020
- },
7021
- {
7022
- action: "check",
7023
- content: GAUNTLET_CHECK_SKILL_CONTENT,
7024
- description: "Run a single check gate"
7025
- },
7026
- {
7027
- action: "push-pr",
7028
- content: PUSH_PR_SKILL_CONTENT,
7029
- description: "Commit, push, and create a PR"
7030
- },
7031
- {
7032
- action: "fix-pr",
7033
- content: FIX_PR_SKILL_CONTENT,
7034
- description: "Fix PR review comments and CI failures"
7035
- },
7036
- {
7037
- action: "status",
7038
- content: GAUNTLET_STATUS_SKILL_CONTENT,
7039
- description: "Show gauntlet status"
7040
- },
7041
- {
7042
- action: "help",
7043
- content: HELP_SKILL_BUNDLE.content,
7044
- references: HELP_SKILL_BUNDLE.references,
7045
- skillsOnly: true,
7046
- description: "Diagnose and explain gauntlet behavior"
7047
- },
7048
- {
7049
- action: "setup",
7050
- content: SETUP_SKILL_CONTENT,
7051
- references: {
7052
- "check-catalog.md": CHECK_CATALOG_REFERENCE,
7053
- "project-structure.md": PROJECT_STRUCTURE_REFERENCE
7054
- },
7055
- skillsOnly: true,
7056
- description: "Configure checks and reviews interactively"
7057
- }
7058
- ];
7059
7376
  var NATIVE_CLIS = new Set(["claude", "cursor"]);
7060
7377
  function registerInitCommand(program) {
7061
7378
  program.command("init").description("Initialize .gauntlet configuration").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
7062
7379
  const projectRoot = process.cwd();
7063
- const targetDir = path25.join(projectRoot, ".gauntlet");
7380
+ const targetDir = path26.join(projectRoot, ".gauntlet");
7064
7381
  const skipPrompts = options.yes ?? false;
7065
7382
  console.log("Detecting available CLI agents...");
7066
7383
  const availableAdapters = await detectAvailableCLIs();
@@ -7108,47 +7425,50 @@ async function scaffoldGauntletDir(_projectRoot, targetDir, reviewCLINames, numR
7108
7425
  console.log(chalk10.dim(".gauntlet/ already exists, skipping scaffolding"));
7109
7426
  return;
7110
7427
  }
7111
- await fs26.mkdir(targetDir);
7112
- await fs26.mkdir(path25.join(targetDir, "checks"));
7113
- await fs26.mkdir(path25.join(targetDir, "reviews"));
7428
+ await fs27.mkdir(targetDir);
7429
+ await fs27.mkdir(path26.join(targetDir, "checks"));
7430
+ await fs27.mkdir(path26.join(targetDir, "reviews"));
7114
7431
  await writeConfigYml(targetDir, reviewCLINames);
7115
- await fs26.writeFile(path25.join(targetDir, "reviews", "code-quality.yml"), `builtin: code-quality
7432
+ await fs27.writeFile(path26.join(targetDir, "reviews", "code-quality.yml"), `builtin: code-quality
7116
7433
  num_reviews: ${numReviews}
7117
7434
  `);
7118
7435
  console.log(chalk10.green("Created .gauntlet/reviews/code-quality.yml"));
7119
7436
  }
7120
- async function writeSkillFiles(actionDir, content, references) {
7121
- await fs26.mkdir(actionDir, { recursive: true });
7122
- await fs26.writeFile(path25.join(actionDir, "SKILL.md"), content);
7123
- if (references) {
7124
- const refsDir = path25.join(actionDir, "references");
7125
- await fs26.mkdir(refsDir, { recursive: true });
7126
- for (const [fileName, fileContent] of Object.entries(references)) {
7127
- await fs26.writeFile(path25.join(refsDir, fileName), fileContent);
7437
+ async function copyDirRecursive(opts) {
7438
+ await fs27.mkdir(opts.dest, { recursive: true });
7439
+ const entries = await fs27.readdir(opts.src, { withFileTypes: true });
7440
+ for (const entry of entries) {
7441
+ const srcPath = path26.join(opts.src, entry.name);
7442
+ const destPath = path26.join(opts.dest, entry.name);
7443
+ if (entry.isDirectory()) {
7444
+ await copyDirRecursive({ src: srcPath, dest: destPath });
7445
+ } else {
7446
+ await fs27.copyFile(srcPath, destPath);
7128
7447
  }
7129
7448
  }
7130
7449
  }
7131
7450
  async function installSkillsWithChecksums(projectRoot, skipPrompts) {
7132
- const skillsDir = path25.join(projectRoot, ".claude", "skills");
7133
- for (const skill of SKILL_DEFINITIONS) {
7134
- const actionDir = path25.join(skillsDir, `gauntlet-${skill.action}`);
7135
- const skillPath = path25.join(actionDir, "SKILL.md");
7136
- const references = "references" in skill ? skill.references : undefined;
7137
- if (!await exists(actionDir)) {
7138
- await writeSkillFiles(actionDir, skill.content, references);
7139
- console.log(chalk10.green(`Created ${path25.relative(projectRoot, skillPath)}`));
7451
+ const skillsDir = path26.join(projectRoot, ".claude", "skills");
7452
+ for (const action of SKILL_ACTIONS) {
7453
+ const dirName = `gauntlet-${action}`;
7454
+ const sourceDir = path26.join(SKILLS_SOURCE_DIR, dirName);
7455
+ const targetDir = path26.join(skillsDir, dirName);
7456
+ const relativeDir = `${path26.relative(projectRoot, targetDir)}/`;
7457
+ if (!await exists(targetDir)) {
7458
+ await copyDirRecursive({ src: sourceDir, dest: targetDir });
7459
+ console.log(chalk10.green(`Created ${relativeDir}`));
7140
7460
  continue;
7141
7461
  }
7142
- const expectedChecksum = computeExpectedSkillChecksum(skill.content, references);
7143
- const actualChecksum = await computeSkillChecksum(actionDir);
7144
- if (expectedChecksum === actualChecksum)
7462
+ const sourceChecksum = await computeSkillChecksum(sourceDir);
7463
+ const targetChecksum = await computeSkillChecksum(targetDir);
7464
+ if (sourceChecksum === targetChecksum)
7145
7465
  continue;
7146
- const shouldOverwrite = await promptFileOverwrite(`gauntlet-${skill.action}`, skipPrompts);
7466
+ const shouldOverwrite = await promptFileOverwrite(dirName, skipPrompts);
7147
7467
  if (!shouldOverwrite)
7148
7468
  continue;
7149
- await fs26.rm(actionDir, { recursive: true, force: true });
7150
- await writeSkillFiles(actionDir, skill.content, references);
7151
- console.log(chalk10.green(`Updated ${path25.relative(projectRoot, skillPath)}`));
7469
+ await fs27.rm(targetDir, { recursive: true, force: true });
7470
+ await copyDirRecursive({ src: sourceDir, dest: targetDir });
7471
+ console.log(chalk10.green(`Updated ${relativeDir}`));
7152
7472
  }
7153
7473
  }
7154
7474
  async function installHookWithChecksums(target, skipPrompts) {
@@ -7156,7 +7476,7 @@ async function installHookWithChecksums(target, skipPrompts) {
7156
7476
  let existingConfig = {};
7157
7477
  if (await exists(spec.config.filePath)) {
7158
7478
  try {
7159
- existingConfig = JSON.parse(await fs26.readFile(spec.config.filePath, "utf-8"));
7479
+ existingConfig = JSON.parse(await fs27.readFile(spec.config.filePath, "utf-8"));
7160
7480
  } catch {
7161
7481
  existingConfig = {};
7162
7482
  }
@@ -7193,14 +7513,13 @@ async function installHookWithChecksums(target, skipPrompts) {
7193
7513
  [spec.config.hookKey]: newEntries
7194
7514
  }
7195
7515
  };
7196
- await fs26.mkdir(path25.dirname(spec.config.filePath), { recursive: true });
7197
- await fs26.writeFile(spec.config.filePath, `${JSON.stringify(merged, null, 2)}
7516
+ await fs27.mkdir(path26.dirname(spec.config.filePath), { recursive: true });
7517
+ await fs27.writeFile(spec.config.filePath, `${JSON.stringify(merged, null, 2)}
7198
7518
  `);
7199
7519
  console.log(chalk10.green(spec.installedMsg));
7200
7520
  }
7201
7521
  async function installExternalFiles(projectRoot, devAdapters, skipPrompts) {
7202
7522
  await installSkillsWithChecksums(projectRoot, skipPrompts);
7203
- await copyStatusScript(path25.join(projectRoot, ".gauntlet"));
7204
7523
  for (const adapter of devAdapters) {
7205
7524
  if (!adapter.supportsHooks())
7206
7525
  continue;
@@ -7228,8 +7547,8 @@ function printPostInitInstructions(devCLINames) {
7228
7547
  console.log(chalk10.bold("To complete setup, reference the setup skill in your CLI: @.claude/skills/gauntlet-setup/SKILL.md. This will guide you through configuring the static checks (unit tests, linters, etc.) that Agent Gauntlet will run."));
7229
7548
  console.log();
7230
7549
  console.log("Available skills:");
7231
- for (const s of SKILL_DEFINITIONS) {
7232
- console.log(` @.claude/skills/gauntlet-${s.action}/SKILL.md — ${s.description}`);
7550
+ for (const action of SKILL_ACTIONS) {
7551
+ console.log(` @.claude/skills/gauntlet-${action}/SKILL.md — ${SKILL_DESCRIPTIONS[action]}`);
7233
7552
  }
7234
7553
  }
7235
7554
  }
@@ -7294,14 +7613,14 @@ entry_points: []
7294
7613
  # enabled: true
7295
7614
  # format: text # Options: text, json
7296
7615
  `;
7297
- await fs26.writeFile(path25.join(targetDir, "config.yml"), content);
7616
+ await fs27.writeFile(path26.join(targetDir, "config.yml"), content);
7298
7617
  console.log(chalk10.green("Created .gauntlet/config.yml"));
7299
7618
  }
7300
7619
  async function addToGitignore(projectRoot, entry) {
7301
- const gitignorePath = path25.join(projectRoot, ".gitignore");
7620
+ const gitignorePath = path26.join(projectRoot, ".gitignore");
7302
7621
  let content = "";
7303
7622
  if (await exists(gitignorePath)) {
7304
- content = await fs26.readFile(gitignorePath, "utf-8");
7623
+ content = await fs27.readFile(gitignorePath, "utf-8");
7305
7624
  const lines = content.split(`
7306
7625
  `).map((l) => l.trim());
7307
7626
  if (lines.includes(entry)) {
@@ -7311,7 +7630,7 @@ async function addToGitignore(projectRoot, entry) {
7311
7630
  const suffix = content.length > 0 && !content.endsWith(`
7312
7631
  `) ? `
7313
7632
  ` : "";
7314
- await fs26.appendFile(gitignorePath, `${suffix}${entry}
7633
+ await fs27.appendFile(gitignorePath, `${suffix}${entry}
7315
7634
  `);
7316
7635
  console.log(chalk10.green(`Added ${entry} to .gitignore`));
7317
7636
  }
@@ -7346,9 +7665,14 @@ function buildAdapterSettingsBlock(adapterNames) {
7346
7665
  return "";
7347
7666
  const lines = items.map((name) => {
7348
7667
  const c = ADAPTER_CONFIG[name];
7349
- return ` ${name}:
7668
+ let block = ` ${name}:
7350
7669
  allow_tool_use: ${c?.allow_tool_use}
7351
7670
  thinking_budget: ${c?.thinking_budget}`;
7671
+ if (c?.model) {
7672
+ block += `
7673
+ model: ${c.model}`;
7674
+ }
7675
+ return block;
7352
7676
  });
7353
7677
  return ` # Recommended settings (see docs/eval-results.md)
7354
7678
  adapters:
@@ -7370,20 +7694,6 @@ async function detectAvailableCLIs() {
7370
7694
  }
7371
7695
  return available;
7372
7696
  }
7373
- async function copyStatusScript(targetDir) {
7374
- const statusScriptDir = path25.join(targetDir, "scripts");
7375
- const statusScriptPath = path25.join(statusScriptDir, "status.ts");
7376
- await fs26.mkdir(statusScriptDir, { recursive: true });
7377
- if (await exists(statusScriptPath))
7378
- return;
7379
- const bundledScript = path25.join(path25.dirname(new URL(import.meta.url).pathname), "..", "scripts", "status.ts");
7380
- if (await exists(bundledScript)) {
7381
- await fs26.copyFile(bundledScript, statusScriptPath);
7382
- console.log(chalk10.green("Created .gauntlet/scripts/status.ts"));
7383
- } else {
7384
- console.log(chalk10.yellow("Warning: bundled status script not found; /gauntlet-status may fail."));
7385
- }
7386
- }
7387
7697
  function hookHasCommand(entries, cmd) {
7388
7698
  return entries.some((hook) => {
7389
7699
  if (hook.command === cmd)
@@ -7401,11 +7711,11 @@ async function mergeHookConfig(opts) {
7401
7711
  wrapInHooksArray,
7402
7712
  baseConfig
7403
7713
  } = opts;
7404
- await fs26.mkdir(path25.dirname(filePath), { recursive: true });
7714
+ await fs27.mkdir(path26.dirname(filePath), { recursive: true });
7405
7715
  let existing = {};
7406
7716
  if (await exists(filePath)) {
7407
7717
  try {
7408
- existing = JSON.parse(await fs26.readFile(filePath, "utf-8"));
7718
+ existing = JSON.parse(await fs27.readFile(filePath, "utf-8"));
7409
7719
  } catch {
7410
7720
  existing = {};
7411
7721
  }
@@ -7425,7 +7735,7 @@ async function mergeHookConfig(opts) {
7425
7735
  [hookKey]: newEntries
7426
7736
  }
7427
7737
  };
7428
- await fs26.writeFile(filePath, `${JSON.stringify(merged, null, 2)}
7738
+ await fs27.writeFile(filePath, `${JSON.stringify(merged, null, 2)}
7429
7739
  `);
7430
7740
  return true;
7431
7741
  }
@@ -7500,7 +7810,7 @@ function buildHookSpec(target) {
7500
7810
  const purpose = isStop ? "gauntlet will run automatically when agent stops" : "agent will be primed with gauntlet instructions at session start";
7501
7811
  return {
7502
7812
  config: {
7503
- filePath: path25.join(projectRoot, cfg.dir, cfg.file),
7813
+ filePath: path26.join(projectRoot, cfg.dir, cfg.file),
7504
7814
  hookKey: cfg.hookKey,
7505
7815
  hookEntry: cfg.entry,
7506
7816
  deduplicateCmd: cfg.cmd,
@@ -7679,8 +7989,8 @@ function registerReviewCommand(program) {
7679
7989
  });
7680
7990
  }
7681
7991
  // src/core/run-executor.ts
7682
- import fs27 from "node:fs/promises";
7683
- import path26 from "node:path";
7992
+ import fs28 from "node:fs/promises";
7993
+ import path27 from "node:path";
7684
7994
 
7685
7995
  // src/core/diff-stats.ts
7686
7996
  import { execFile as execFile2 } from "node:child_process";
@@ -7989,25 +8299,25 @@ function isProcessAlive(pid) {
7989
8299
  }
7990
8300
  }
7991
8301
  async function tryAcquireLock(logDir) {
7992
- await fs27.mkdir(logDir, { recursive: true });
7993
- const lockPath = path26.resolve(logDir, LOCK_FILENAME2);
8302
+ await fs28.mkdir(logDir, { recursive: true });
8303
+ const lockPath = path27.resolve(logDir, LOCK_FILENAME2);
7994
8304
  try {
7995
- await fs27.writeFile(lockPath, String(process.pid), { flag: "wx" });
8305
+ await fs28.writeFile(lockPath, String(process.pid), { flag: "wx" });
7996
8306
  return true;
7997
8307
  } catch (err) {
7998
8308
  if (typeof err === "object" && err !== null && "code" in err && err.code === "EEXIST") {
7999
8309
  try {
8000
- const lockContent = await fs27.readFile(lockPath, "utf-8");
8310
+ const lockContent = await fs28.readFile(lockPath, "utf-8");
8001
8311
  const lockPid = parseInt(lockContent.trim(), 10);
8002
- const lockStat = await fs27.stat(lockPath);
8312
+ const lockStat = await fs28.stat(lockPath);
8003
8313
  const lockAgeMs = Date.now() - lockStat.mtimeMs;
8004
8314
  const pidValid = !Number.isNaN(lockPid);
8005
8315
  const pidDead = pidValid && !isProcessAlive(lockPid);
8006
8316
  const lockStale = !pidValid && lockAgeMs > STALE_LOCK_MS;
8007
8317
  if (pidDead || lockStale) {
8008
- await fs27.rm(lockPath, { force: true });
8318
+ await fs28.rm(lockPath, { force: true });
8009
8319
  try {
8010
- await fs27.writeFile(lockPath, String(process.pid), {
8320
+ await fs28.writeFile(lockPath, String(process.pid), {
8011
8321
  flag: "wx"
8012
8322
  });
8013
8323
  return true;
@@ -8023,7 +8333,7 @@ async function tryAcquireLock(logDir) {
8023
8333
  }
8024
8334
  async function findLatestConsoleLog(logDir) {
8025
8335
  try {
8026
- const files = await fs27.readdir(logDir);
8336
+ const files = await fs28.readdir(logDir);
8027
8337
  let maxNum = -1;
8028
8338
  let latestFile = null;
8029
8339
  for (const file of files) {
@@ -8039,7 +8349,7 @@ async function findLatestConsoleLog(logDir) {
8039
8349
  }
8040
8350
  }
8041
8351
  }
8042
- return latestFile ? path26.join(logDir, latestFile) : null;
8352
+ return latestFile ? path27.join(logDir, latestFile) : null;
8043
8353
  } catch {
8044
8354
  return null;
8045
8355
  }
@@ -8090,7 +8400,7 @@ async function executeRun(options = {}) {
8090
8400
  let lockAcquired = false;
8091
8401
  let consoleLogHandle;
8092
8402
  let loggerInitializedHere = false;
8093
- const log3 = getRunLogger();
8403
+ const log5 = getRunLogger();
8094
8404
  try {
8095
8405
  config = await loadConfig(cwd);
8096
8406
  if (!isLoggerConfigured()) {
@@ -8115,7 +8425,7 @@ async function executeRun(options = {}) {
8115
8425
  if (options.checkInterval) {
8116
8426
  const stopHookConfig = resolveStopHookConfig(config.project.stop_hook, globalConfig);
8117
8427
  if (!stopHookConfig.enabled) {
8118
- log3.debug("Stop hook is disabled via configuration, skipping");
8428
+ log5.debug("Stop hook is disabled via configuration, skipping");
8119
8429
  if (loggerInitializedHere) {
8120
8430
  await resetLogger();
8121
8431
  }
@@ -8129,7 +8439,7 @@ async function executeRun(options = {}) {
8129
8439
  const intervalMinutes = stopHookConfig.run_interval_minutes;
8130
8440
  const shouldRun = await shouldRunBasedOnInterval(config.project.log_dir, intervalMinutes);
8131
8441
  if (!shouldRun) {
8132
- log3.debug(`Run interval (${intervalMinutes} min) not elapsed, skipping`);
8442
+ log5.debug(`Run interval (${intervalMinutes} min) not elapsed, skipping`);
8133
8443
  if (loggerInitializedHere) {
8134
8444
  await resetLogger();
8135
8445
  }
@@ -8144,7 +8454,7 @@ async function executeRun(options = {}) {
8144
8454
  const effectiveBaseBranch = options.baseBranch || (process.env.GITHUB_BASE_REF && (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true") ? process.env.GITHUB_BASE_REF : null) || config.project.base_branch;
8145
8455
  const autoCleanResult = await shouldAutoClean(config.project.log_dir, effectiveBaseBranch);
8146
8456
  if (autoCleanResult.clean) {
8147
- log3.debug(`Auto-cleaning logs (${autoCleanResult.reason})...`);
8457
+ log5.debug(`Auto-cleaning logs (${autoCleanResult.reason})...`);
8148
8458
  await debugLogger?.logClean("auto", autoCleanResult.reason || "unknown");
8149
8459
  await performAutoClean(config.project.log_dir, autoCleanResult, config.project.max_previous_logs);
8150
8460
  }
@@ -8169,7 +8479,7 @@ async function executeRun(options = {}) {
8169
8479
  let changeOptions;
8170
8480
  let passedSlotsMap;
8171
8481
  if (isRerun) {
8172
- log3.debug("Existing logs detected — running in verification mode...");
8482
+ log5.debug("Existing logs detected — running in verification mode...");
8173
8483
  const { failures: previousFailures, passedSlots } = await findPreviousFailures(config.project.log_dir, options.gate, true);
8174
8484
  failuresMap = new Map;
8175
8485
  for (const gateFailure of previousFailures) {
@@ -8183,7 +8493,7 @@ async function executeRun(options = {}) {
8183
8493
  passedSlotsMap = passedSlots;
8184
8494
  if (previousFailures.length > 0) {
8185
8495
  const totalViolations = previousFailures.reduce((sum, gf) => sum + gf.adapterFailures.reduce((s, af) => s + af.violations.length, 0), 0);
8186
- log3.warn(`Found ${previousFailures.length} gate(s) with ${totalViolations} previous violation(s)`);
8496
+ log5.warn(`Found ${previousFailures.length} gate(s) with ${totalViolations} previous violation(s)`);
8187
8497
  }
8188
8498
  changeOptions = { uncommitted: true };
8189
8499
  const executionState = await readExecutionState(config.project.log_dir);
@@ -8195,7 +8505,7 @@ async function executeRun(options = {}) {
8195
8505
  if (executionState) {
8196
8506
  const resolved = await resolveFixBase(executionState, effectiveBaseBranch);
8197
8507
  if (resolved.warning) {
8198
- log3.warn(`Warning: ${resolved.warning}`);
8508
+ log5.warn(`Warning: ${resolved.warning}`);
8199
8509
  }
8200
8510
  if (resolved.fixBase) {
8201
8511
  changeOptions = { fixBase: resolved.fixBase };
@@ -8215,10 +8525,30 @@ async function executeRun(options = {}) {
8215
8525
  });
8216
8526
  const expander = new EntryPointExpander;
8217
8527
  const jobGen = new JobGenerator(config);
8218
- log3.debug("Detecting changes...");
8528
+ log5.debug("Detecting changes...");
8219
8529
  const changes = await changeDetector.getChangedFiles();
8220
8530
  if (changes.length === 0) {
8221
- log3.info("No changes detected.");
8531
+ if (isRerun && failuresMap && failuresMap.size === 0) {
8532
+ const hasSkipped = await hasSkippedViolationsInLogs({
8533
+ logDir: config.project.log_dir
8534
+ });
8535
+ const status2 = hasSkipped ? "passed_with_warnings" : "passed";
8536
+ if (status2 === "passed") {
8537
+ await debugLogger?.logClean("auto", "all_passed");
8538
+ await cleanLogs(config.project.log_dir, config.project.max_previous_logs);
8539
+ }
8540
+ log5.info(getStatusMessage2(status2));
8541
+ consoleLogHandle?.restore();
8542
+ if (loggerInitializedHere) {
8543
+ await resetLogger();
8544
+ }
8545
+ return {
8546
+ status: status2,
8547
+ message: getStatusMessage2(status2),
8548
+ gatesRun: 0
8549
+ };
8550
+ }
8551
+ log5.info("No changes detected.");
8222
8552
  consoleLogHandle?.restore();
8223
8553
  if (loggerInitializedHere) {
8224
8554
  await resetLogger();
@@ -8229,14 +8559,14 @@ async function executeRun(options = {}) {
8229
8559
  gatesRun: 0
8230
8560
  };
8231
8561
  }
8232
- log3.debug(`Found ${changes.length} changed files.`);
8562
+ log5.debug(`Found ${changes.length} changed files.`);
8233
8563
  const entryPoints = await expander.expand(config.project.entry_points, changes);
8234
8564
  let jobs = jobGen.generateJobs(entryPoints);
8235
8565
  if (options.gate) {
8236
8566
  jobs = jobs.filter((j) => j.name === options.gate);
8237
8567
  }
8238
8568
  if (jobs.length === 0) {
8239
- log3.warn("No applicable gates for these changes.");
8569
+ log5.warn("No applicable gates for these changes.");
8240
8570
  consoleLogHandle?.restore();
8241
8571
  if (loggerInitializedHere) {
8242
8572
  await resetLogger();
@@ -8247,7 +8577,7 @@ async function executeRun(options = {}) {
8247
8577
  gatesRun: 0
8248
8578
  };
8249
8579
  }
8250
- log3.debug(`Running ${jobs.length} gates...`);
8580
+ log5.debug(`Running ${jobs.length} gates...`);
8251
8581
  const runMode = isRerun ? "verification" : "full";
8252
8582
  const diffStats = await computeDiffStats(effectiveBaseBranch, changeOptions || {
8253
8583
  commit: options.commit,
@@ -8320,8 +8650,8 @@ function registerRunCommand(program) {
8320
8650
  });
8321
8651
  }
8322
8652
  // src/commands/start-hook.ts
8323
- import fs28 from "node:fs/promises";
8324
- import path27 from "node:path";
8653
+ import fs29 from "node:fs/promises";
8654
+ import path28 from "node:path";
8325
8655
  import YAML7 from "yaml";
8326
8656
  var START_HOOK_MESSAGE = `<IMPORTANT>
8327
8657
  This project uses Agent Gauntlet for automated quality verification.
@@ -8369,9 +8699,9 @@ function isValidConfig(content) {
8369
8699
  }
8370
8700
  function registerStartHookCommand(program) {
8371
8701
  program.command("start-hook").description("Session start hook - primes agent with gauntlet verification instructions").option("--adapter <adapter>", "Output format: claude or cursor", "claude").action(async (options) => {
8372
- const configPath = path27.join(process.cwd(), ".gauntlet", "config.yml");
8702
+ const configPath = path28.join(process.cwd(), ".gauntlet", "config.yml");
8373
8703
  try {
8374
- const content = await fs28.readFile(configPath, "utf-8");
8704
+ const content = await fs29.readFile(configPath, "utf-8");
8375
8705
  if (!isValidConfig(content)) {
8376
8706
  return;
8377
8707
  }
@@ -8381,7 +8711,7 @@ function registerStartHookCommand(program) {
8381
8711
  const adapter = options.adapter;
8382
8712
  try {
8383
8713
  const cwd = process.cwd();
8384
- const logDir = path27.join(cwd, await getLogDir(cwd));
8714
+ const logDir = path28.join(cwd, await getLogDir2(cwd));
8385
8715
  const globalConfig = await loadGlobalConfig();
8386
8716
  const projectDebugLogConfig = await getDebugLogConfig(cwd);
8387
8717
  const debugLogConfig = mergeDebugLogConfig(projectDebugLogConfig, globalConfig.debug_log);
@@ -8392,6 +8722,12 @@ function registerStartHookCommand(program) {
8392
8722
  console.log(output);
8393
8723
  });
8394
8724
  }
8725
+ // src/commands/status.ts
8726
+ function registerStatusCommand(program) {
8727
+ program.command("status").description("Show a summary of the most recent gauntlet session").action(() => {
8728
+ main();
8729
+ });
8730
+ }
8395
8731
  // src/commands/validate.ts
8396
8732
  import chalk13 from "chalk";
8397
8733
  function registerValidateCommand(program) {
@@ -8686,6 +9022,7 @@ registerHealthCommand(program);
8686
9022
  registerInitCommand(program);
8687
9023
  registerValidateCommand(program);
8688
9024
  registerStartHookCommand(program);
9025
+ registerStatusCommand(program);
8689
9026
  registerStopHookCommand(program);
8690
9027
  registerWaitCICommand(program);
8691
9028
  registerHelpCommand(program);
@@ -8694,4 +9031,4 @@ if (process.argv.length < 3) {
8694
9031
  }
8695
9032
  program.parse(process.argv);
8696
9033
 
8697
- //# debugId=27272999504866CC64756E2164756E21
9034
+ //# debugId=349F4DCD1CB50C0464756E2164756E21