agent-gauntlet 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.js +1190 -703
- package/dist/index.js.map +22 -20
- package/dist/scripts/status.js +1 -1
- package/dist/scripts/status.js.map +1 -1
- package/package.json +1 -1
- package/skills/gauntlet-check/SKILL.md +47 -12
- package/skills/gauntlet-commit/SKILL.md +73 -0
- package/skills/gauntlet-help/SKILL.md +9 -0
- package/skills/gauntlet-issue/SKILL.md +106 -0
- package/skills/gauntlet-merge/SKILL.md +24 -0
- package/skills/gauntlet-merge/merge-state.sh +68 -0
- package/skills/gauntlet-run/SKILL.md +60 -58
- package/skills/gauntlet-run/extract-prompt.md +14 -12
package/dist/index.js
CHANGED
|
@@ -299,7 +299,7 @@ import { Command } from "commander";
|
|
|
299
299
|
// package.json
|
|
300
300
|
var package_default = {
|
|
301
301
|
name: "agent-gauntlet",
|
|
302
|
-
version: "1.
|
|
302
|
+
version: "1.2.0",
|
|
303
303
|
description: "A CLI tool for testing AI coding agents",
|
|
304
304
|
license: "MIT",
|
|
305
305
|
author: "Paul Caplan",
|
|
@@ -552,7 +552,8 @@ var reviewGateSchema = z2.object({
|
|
|
552
552
|
parallel: z2.boolean().default(true),
|
|
553
553
|
run_in_ci: z2.boolean().default(true),
|
|
554
554
|
run_locally: z2.boolean().default(true),
|
|
555
|
-
timeout: z2.number().optional()
|
|
555
|
+
timeout: z2.number().optional(),
|
|
556
|
+
enabled: z2.boolean().default(true)
|
|
556
557
|
});
|
|
557
558
|
var reviewPromptFrontmatterSchema = z2.object({
|
|
558
559
|
model: z2.string().optional(),
|
|
@@ -563,7 +564,8 @@ var reviewPromptFrontmatterSchema = z2.object({
|
|
|
563
564
|
run_locally: z2.boolean().default(true),
|
|
564
565
|
timeout: z2.number().optional(),
|
|
565
566
|
prompt_file: z2.string().optional(),
|
|
566
|
-
skill_name: z2.string().optional()
|
|
567
|
+
skill_name: z2.string().optional(),
|
|
568
|
+
enabled: z2.boolean().default(true)
|
|
567
569
|
}).refine((data) => !(data.prompt_file && data.skill_name), {
|
|
568
570
|
message: "'prompt_file' and 'skill_name' are mutually exclusive. Specify only one."
|
|
569
571
|
});
|
|
@@ -577,7 +579,8 @@ var reviewYamlSchema = z2.object({
|
|
|
577
579
|
timeout: z2.number().optional(),
|
|
578
580
|
prompt_file: z2.string().optional(),
|
|
579
581
|
skill_name: z2.string().optional(),
|
|
580
|
-
builtin: z2.string().optional()
|
|
582
|
+
builtin: z2.string().optional(),
|
|
583
|
+
enabled: z2.boolean().default(true)
|
|
581
584
|
}).superRefine((data, ctx) => {
|
|
582
585
|
const sources = [data.prompt_file, data.skill_name, data.builtin].filter(Boolean);
|
|
583
586
|
if (sources.length > 1) {
|
|
@@ -742,7 +745,8 @@ async function loadMarkdownReview(file, reviewsPath, gauntletPath) {
|
|
|
742
745
|
parallel: parsedFrontmatter.parallel,
|
|
743
746
|
run_in_ci: parsedFrontmatter.run_in_ci,
|
|
744
747
|
run_locally: parsedFrontmatter.run_locally,
|
|
745
|
-
timeout: parsedFrontmatter.timeout
|
|
748
|
+
timeout: parsedFrontmatter.timeout,
|
|
749
|
+
enabled: parsedFrontmatter.enabled
|
|
746
750
|
};
|
|
747
751
|
if (parsedFrontmatter.prompt_file) {
|
|
748
752
|
review.promptContent = await loadPromptFile(parsedFrontmatter.prompt_file, gauntletPath, `review "${name}"`);
|
|
@@ -768,7 +772,8 @@ async function loadYamlReview(file, reviewsPath, gauntletPath) {
|
|
|
768
772
|
parallel: parsed.parallel,
|
|
769
773
|
run_in_ci: parsed.run_in_ci,
|
|
770
774
|
run_locally: parsed.run_locally,
|
|
771
|
-
timeout: parsed.timeout
|
|
775
|
+
timeout: parsed.timeout,
|
|
776
|
+
enabled: parsed.enabled
|
|
772
777
|
};
|
|
773
778
|
if (parsed.prompt_file) {
|
|
774
779
|
review.promptContent = await loadPromptFile(parsed.prompt_file, gauntletPath, `review "${name}"`);
|
|
@@ -849,9 +854,10 @@ async function dirExists(path4) {
|
|
|
849
854
|
}
|
|
850
855
|
|
|
851
856
|
// src/core/change-detector.ts
|
|
852
|
-
import { exec } from "node:child_process";
|
|
857
|
+
import { exec, execFile } from "node:child_process";
|
|
853
858
|
import { promisify } from "node:util";
|
|
854
859
|
var execAsync = promisify(exec);
|
|
860
|
+
var execFileAsync = promisify(execFile);
|
|
855
861
|
function isValidGitRef(ref) {
|
|
856
862
|
return /^[a-zA-Z0-9._\-/]+$/.test(ref);
|
|
857
863
|
}
|
|
@@ -924,8 +930,40 @@ class ChangeDetector {
|
|
|
924
930
|
]);
|
|
925
931
|
return Array.from(files);
|
|
926
932
|
}
|
|
933
|
+
async resolveLocalBaseBranch() {
|
|
934
|
+
const base = this.baseBranch;
|
|
935
|
+
if (base.startsWith("origin/"))
|
|
936
|
+
return base;
|
|
937
|
+
const remoteRef = `origin/${base}`;
|
|
938
|
+
const [localExists, remoteExists] = await Promise.all([
|
|
939
|
+
execFileAsync("git", ["rev-parse", "--verify", base]).then(() => true, () => false),
|
|
940
|
+
execFileAsync("git", ["rev-parse", "--verify", remoteRef]).then(() => true, () => false)
|
|
941
|
+
]);
|
|
942
|
+
if (!remoteExists)
|
|
943
|
+
return base;
|
|
944
|
+
if (!localExists)
|
|
945
|
+
return remoteRef;
|
|
946
|
+
try {
|
|
947
|
+
const [{ stdout: remoteAhead }, { stdout: localAhead }] = await Promise.all([
|
|
948
|
+
execFileAsync("git", [
|
|
949
|
+
"rev-list",
|
|
950
|
+
"--count",
|
|
951
|
+
`${base}..${remoteRef}`
|
|
952
|
+
]),
|
|
953
|
+
execFileAsync("git", [
|
|
954
|
+
"rev-list",
|
|
955
|
+
"--count",
|
|
956
|
+
`${remoteRef}..${base}`
|
|
957
|
+
])
|
|
958
|
+
]);
|
|
959
|
+
return parseInt(localAhead.trim(), 10) > parseInt(remoteAhead.trim(), 10) ? base : remoteRef;
|
|
960
|
+
} catch {
|
|
961
|
+
return remoteRef;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
927
964
|
async getLocalChangedFiles() {
|
|
928
|
-
|
|
965
|
+
const resolvedBase = await this.resolveLocalBaseBranch();
|
|
966
|
+
return this.getDiffWithWorkingTree(resolvedBase);
|
|
929
967
|
}
|
|
930
968
|
async getCommitChangedFiles(commit) {
|
|
931
969
|
try {
|
|
@@ -1081,8 +1119,10 @@ function shouldRunGate(gateConfig, isCI) {
|
|
|
1081
1119
|
|
|
1082
1120
|
class JobGenerator {
|
|
1083
1121
|
config;
|
|
1084
|
-
|
|
1122
|
+
enableReviews;
|
|
1123
|
+
constructor(config, enableReviews = new Set) {
|
|
1085
1124
|
this.config = config;
|
|
1125
|
+
this.enableReviews = enableReviews;
|
|
1086
1126
|
}
|
|
1087
1127
|
generateJobs(expandedEntryPoints) {
|
|
1088
1128
|
const jobs = [];
|
|
@@ -1131,6 +1171,8 @@ class JobGenerator {
|
|
|
1131
1171
|
}
|
|
1132
1172
|
if (!shouldRunGate(reviewConfig, isCI))
|
|
1133
1173
|
continue;
|
|
1174
|
+
if (!(reviewConfig.enabled || this.enableReviews.has(reviewName)))
|
|
1175
|
+
continue;
|
|
1134
1176
|
jobs.push({
|
|
1135
1177
|
id: `review:${ep.path}:${reviewName}`,
|
|
1136
1178
|
type: "review",
|
|
@@ -1663,9 +1705,10 @@ import fs9 from "node:fs/promises";
|
|
|
1663
1705
|
import path8 from "node:path";
|
|
1664
1706
|
|
|
1665
1707
|
// src/utils/execution-state.ts
|
|
1666
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
1708
|
+
import { execFile as execFile2, spawn as spawn2 } from "node:child_process";
|
|
1667
1709
|
import fs8 from "node:fs/promises";
|
|
1668
1710
|
import path7 from "node:path";
|
|
1711
|
+
import { StringDecoder } from "node:string_decoder";
|
|
1669
1712
|
|
|
1670
1713
|
// src/utils/debug-log.ts
|
|
1671
1714
|
import fs7 from "node:fs/promises";
|
|
@@ -1837,6 +1880,35 @@ function getDebugLogger() {
|
|
|
1837
1880
|
// src/utils/execution-state.ts
|
|
1838
1881
|
var EXECUTION_STATE_FILENAME = ".execution_state";
|
|
1839
1882
|
var SESSION_REF_FILENAME = ".session_ref";
|
|
1883
|
+
function getStatePath(logDir) {
|
|
1884
|
+
return path7.join(logDir, EXECUTION_STATE_FILENAME);
|
|
1885
|
+
}
|
|
1886
|
+
function spawnGit(args) {
|
|
1887
|
+
return new Promise((resolve, reject) => {
|
|
1888
|
+
const child = spawn2("git", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1889
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
1890
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
1891
|
+
let stdout = "";
|
|
1892
|
+
child.stdout.on("data", (chunk) => {
|
|
1893
|
+
stdout += typeof chunk === "string" ? chunk : stdoutDecoder.write(chunk);
|
|
1894
|
+
});
|
|
1895
|
+
let stderr = "";
|
|
1896
|
+
child.stderr.on("data", (chunk) => {
|
|
1897
|
+
stderr += typeof chunk === "string" ? chunk : stderrDecoder.write(chunk);
|
|
1898
|
+
});
|
|
1899
|
+
child.on("close", (code) => {
|
|
1900
|
+
stdout += stdoutDecoder.end();
|
|
1901
|
+
stderr += stderrDecoder.end();
|
|
1902
|
+
if (code === 0)
|
|
1903
|
+
resolve(stdout.trim());
|
|
1904
|
+
else {
|
|
1905
|
+
const detail = stderr.trim();
|
|
1906
|
+
reject(new Error(detail ? `git ${args.join(" ")} failed with code ${code}: ${detail}` : `git ${args.join(" ")} failed with code ${code}`));
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
child.on("error", reject);
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1840
1912
|
function isPlainRecord(value) {
|
|
1841
1913
|
if (value == null)
|
|
1842
1914
|
return false;
|
|
@@ -1859,7 +1931,7 @@ function isValidStateData(data) {
|
|
|
1859
1931
|
}
|
|
1860
1932
|
async function readExecutionState(logDir) {
|
|
1861
1933
|
try {
|
|
1862
|
-
const statePath =
|
|
1934
|
+
const statePath = getStatePath(logDir);
|
|
1863
1935
|
const content = await fs8.readFile(statePath, "utf-8");
|
|
1864
1936
|
const data = JSON.parse(content);
|
|
1865
1937
|
if (!isValidStateData(data))
|
|
@@ -1880,38 +1952,52 @@ async function readExecutionState(logDir) {
|
|
|
1880
1952
|
return null;
|
|
1881
1953
|
}
|
|
1882
1954
|
}
|
|
1883
|
-
async function
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
if (sha)
|
|
1887
|
-
return sha;
|
|
1955
|
+
async function createWorkingTreeRef() {
|
|
1956
|
+
const hasChanges = await hasWorkingTreeChanges();
|
|
1957
|
+
if (!hasChanges) {
|
|
1888
1958
|
return getCurrentCommit();
|
|
1889
1959
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
const child = spawn2("git", ["stash", "create", "--include-untracked"], {
|
|
1897
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1898
|
-
});
|
|
1899
|
-
let stdout = "";
|
|
1900
|
-
child.stdout.on("data", (data) => {
|
|
1901
|
-
stdout += data.toString();
|
|
1902
|
-
});
|
|
1903
|
-
child.on("close", (code) => {
|
|
1904
|
-
resolveStashResult(code, stdout).then(resolve, reject);
|
|
1960
|
+
const runGit = (args) => new Promise((resolve, reject) => {
|
|
1961
|
+
execFile2("git", args, (error, stdout) => {
|
|
1962
|
+
if (error)
|
|
1963
|
+
reject(error);
|
|
1964
|
+
else
|
|
1965
|
+
resolve(stdout.trim());
|
|
1905
1966
|
});
|
|
1906
|
-
child.on("error", reject);
|
|
1907
1967
|
});
|
|
1968
|
+
const prevTop = await runGit(["rev-parse", "--verify", "stash@{0}"]).catch(() => "");
|
|
1969
|
+
try {
|
|
1970
|
+
await runGit([
|
|
1971
|
+
"stash",
|
|
1972
|
+
"push",
|
|
1973
|
+
"--include-untracked",
|
|
1974
|
+
"-m",
|
|
1975
|
+
"gauntlet-snapshot"
|
|
1976
|
+
]);
|
|
1977
|
+
} catch {
|
|
1978
|
+
return getCurrentCommit();
|
|
1979
|
+
}
|
|
1980
|
+
const newTop = await runGit(["rev-parse", "stash@{0}"]).catch(() => "");
|
|
1981
|
+
const createdStash = !!newTop && newTop !== prevTop;
|
|
1982
|
+
if (!createdStash) {
|
|
1983
|
+
if (prevTop && !newTop) {
|
|
1984
|
+
console.error("gauntlet: unable to verify stash snapshot; leaving stash untouched. Run `git stash pop` manually if needed.");
|
|
1985
|
+
}
|
|
1986
|
+
return getCurrentCommit();
|
|
1987
|
+
}
|
|
1988
|
+
try {
|
|
1989
|
+
await runGit(["stash", "pop"]);
|
|
1990
|
+
} catch {
|
|
1991
|
+
console.error("gauntlet: stash pop failed — run `git stash pop` manually to restore your working tree");
|
|
1992
|
+
}
|
|
1993
|
+
return newTop;
|
|
1908
1994
|
}
|
|
1909
1995
|
async function writeExecutionState(logDir) {
|
|
1910
|
-
const statePath =
|
|
1911
|
-
const
|
|
1996
|
+
const statePath = getStatePath(logDir);
|
|
1997
|
+
const workingTreeRef = await createWorkingTreeRef();
|
|
1998
|
+
const [branch, commit, rawState] = await Promise.all([
|
|
1912
1999
|
getCurrentBranch(),
|
|
1913
2000
|
getCurrentCommit(),
|
|
1914
|
-
createWorkingTreeRef(),
|
|
1915
2001
|
readRawState(statePath)
|
|
1916
2002
|
]);
|
|
1917
2003
|
const existingUnhealthy = extractUnhealthyAdapters(rawState);
|
|
@@ -1946,91 +2032,23 @@ async function writeExecutionState(logDir) {
|
|
|
1946
2032
|
await fs8.rm(sessionRefPath, { force: true });
|
|
1947
2033
|
} catch {}
|
|
1948
2034
|
}
|
|
1949
|
-
|
|
1950
|
-
return
|
|
1951
|
-
const child = spawn2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
1952
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1953
|
-
});
|
|
1954
|
-
let stdout = "";
|
|
1955
|
-
child.stdout.on("data", (data) => {
|
|
1956
|
-
stdout += data.toString();
|
|
1957
|
-
});
|
|
1958
|
-
child.on("close", (code) => {
|
|
1959
|
-
if (code === 0) {
|
|
1960
|
-
resolve(stdout.trim());
|
|
1961
|
-
} else {
|
|
1962
|
-
reject(new Error(`git rev-parse failed with code ${code}`));
|
|
1963
|
-
}
|
|
1964
|
-
});
|
|
1965
|
-
child.on("error", reject);
|
|
1966
|
-
});
|
|
2035
|
+
function getCurrentBranch() {
|
|
2036
|
+
return spawnGit(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
1967
2037
|
}
|
|
1968
|
-
|
|
1969
|
-
return
|
|
1970
|
-
const child = spawn2("git", ["rev-parse", "HEAD"], {
|
|
1971
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1972
|
-
});
|
|
1973
|
-
let stdout = "";
|
|
1974
|
-
child.stdout.on("data", (data) => {
|
|
1975
|
-
stdout += data.toString();
|
|
1976
|
-
});
|
|
1977
|
-
child.on("close", (code) => {
|
|
1978
|
-
if (code === 0) {
|
|
1979
|
-
resolve(stdout.trim());
|
|
1980
|
-
} else {
|
|
1981
|
-
reject(new Error(`git rev-parse failed with code ${code}`));
|
|
1982
|
-
}
|
|
1983
|
-
});
|
|
1984
|
-
child.on("error", reject);
|
|
1985
|
-
});
|
|
2038
|
+
function getCurrentCommit() {
|
|
2039
|
+
return spawnGit(["rev-parse", "HEAD"]);
|
|
1986
2040
|
}
|
|
1987
|
-
|
|
1988
|
-
return
|
|
1989
|
-
const child = spawn2("git", ["merge-base", "--is-ancestor", commit, branch], { stdio: ["ignore", "pipe", "pipe"] });
|
|
1990
|
-
child.on("close", (code) => {
|
|
1991
|
-
resolve(code === 0);
|
|
1992
|
-
});
|
|
1993
|
-
child.on("error", () => {
|
|
1994
|
-
resolve(false);
|
|
1995
|
-
});
|
|
1996
|
-
});
|
|
2041
|
+
function isCommitInBranch(commit, branch) {
|
|
2042
|
+
return spawnGit(["merge-base", "--is-ancestor", commit, branch]).then(() => true).catch(() => false);
|
|
1997
2043
|
}
|
|
1998
2044
|
function getExecutionStateFilename() {
|
|
1999
2045
|
return EXECUTION_STATE_FILENAME;
|
|
2000
2046
|
}
|
|
2001
|
-
|
|
2002
|
-
return
|
|
2003
|
-
const child = spawn2("git", ["status", "--porcelain"], {
|
|
2004
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2005
|
-
});
|
|
2006
|
-
let stdout = "";
|
|
2007
|
-
child.stdout.on("data", (data) => {
|
|
2008
|
-
stdout += data.toString();
|
|
2009
|
-
});
|
|
2010
|
-
child.on("close", (code) => {
|
|
2011
|
-
if (code === 0) {
|
|
2012
|
-
resolve(stdout.trim().length > 0);
|
|
2013
|
-
} else {
|
|
2014
|
-
resolve(true);
|
|
2015
|
-
}
|
|
2016
|
-
});
|
|
2017
|
-
child.on("error", () => {
|
|
2018
|
-
resolve(true);
|
|
2019
|
-
});
|
|
2020
|
-
});
|
|
2047
|
+
function hasWorkingTreeChanges() {
|
|
2048
|
+
return spawnGit(["status", "--porcelain"]).then((out) => out.length > 0).catch(() => true);
|
|
2021
2049
|
}
|
|
2022
|
-
|
|
2023
|
-
return
|
|
2024
|
-
const child = spawn2("git", ["cat-file", "-t", sha], {
|
|
2025
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2026
|
-
});
|
|
2027
|
-
child.on("close", (code) => {
|
|
2028
|
-
resolve(code === 0);
|
|
2029
|
-
});
|
|
2030
|
-
child.on("error", () => {
|
|
2031
|
-
resolve(false);
|
|
2032
|
-
});
|
|
2033
|
-
});
|
|
2050
|
+
function gitObjectExists(sha) {
|
|
2051
|
+
return spawnGit(["cat-file", "-t", sha]).then(() => true).catch(() => false);
|
|
2034
2052
|
}
|
|
2035
2053
|
async function resolveFixBaseForMergedCommit(working_tree_ref) {
|
|
2036
2054
|
if (!working_tree_ref) {
|
|
@@ -2074,7 +2092,7 @@ function isAdapterCoolingDown(entry) {
|
|
|
2074
2092
|
return Date.now() - markedAt < COOLDOWN_MS;
|
|
2075
2093
|
}
|
|
2076
2094
|
async function getUnhealthyAdapters(logDir) {
|
|
2077
|
-
const statePath =
|
|
2095
|
+
const statePath = getStatePath(logDir);
|
|
2078
2096
|
const rawState = await readRawState(statePath);
|
|
2079
2097
|
return extractUnhealthyAdapters(rawState) ?? {};
|
|
2080
2098
|
}
|
|
@@ -2088,7 +2106,7 @@ async function readRawState(statePath) {
|
|
|
2088
2106
|
}
|
|
2089
2107
|
async function markAdapterUnhealthy(logDir, adapterName, reason) {
|
|
2090
2108
|
await getDebugLogger()?.logAdapterHealthChange(adapterName, false, reason);
|
|
2091
|
-
const statePath =
|
|
2109
|
+
const statePath = getStatePath(logDir);
|
|
2092
2110
|
const rawData = await readRawState(statePath) ?? {};
|
|
2093
2111
|
const adapters = rawData.unhealthy_adapters ?? {};
|
|
2094
2112
|
adapters[adapterName] = {
|
|
@@ -2101,7 +2119,7 @@ async function markAdapterUnhealthy(logDir, adapterName, reason) {
|
|
|
2101
2119
|
}
|
|
2102
2120
|
async function markAdapterHealthy(logDir, adapterName) {
|
|
2103
2121
|
await getDebugLogger()?.logAdapterHealthChange(adapterName, true);
|
|
2104
|
-
const statePath =
|
|
2122
|
+
const statePath = getStatePath(logDir);
|
|
2105
2123
|
const rawData = await readRawState(statePath);
|
|
2106
2124
|
if (!rawData)
|
|
2107
2125
|
return;
|
|
@@ -2119,7 +2137,7 @@ async function markAdapterHealthy(logDir, adapterName) {
|
|
|
2119
2137
|
async function deleteExecutionState(logDir) {
|
|
2120
2138
|
try {
|
|
2121
2139
|
await getDebugLogger()?.logStateDelete();
|
|
2122
|
-
const statePath =
|
|
2140
|
+
const statePath = getStatePath(logDir);
|
|
2123
2141
|
await fs8.rm(statePath, { force: true });
|
|
2124
2142
|
} catch {}
|
|
2125
2143
|
}
|
|
@@ -2183,11 +2201,11 @@ async function checkRunInterval(logDir, intervalMinutes) {
|
|
|
2183
2201
|
return elapsedMinutes >= intervalMinutes;
|
|
2184
2202
|
}
|
|
2185
2203
|
async function hasChangesVsBaseBranch(cwd, baseBranch) {
|
|
2186
|
-
const { execFile } = await import("node:child_process");
|
|
2204
|
+
const { execFile: execFile3 } = await import("node:child_process");
|
|
2187
2205
|
const { promisify: promisify3 } = await import("node:util");
|
|
2188
|
-
const
|
|
2206
|
+
const execFileAsync2 = promisify3(execFile3);
|
|
2189
2207
|
try {
|
|
2190
|
-
const { stdout } = await
|
|
2208
|
+
const { stdout } = await execFileAsync2("git", ["diff", "--name-only", `${baseBranch}...HEAD`], { cwd });
|
|
2191
2209
|
return stdout.trim().length > 0;
|
|
2192
2210
|
} catch {
|
|
2193
2211
|
return true;
|
|
@@ -3014,6 +3032,7 @@ import os3 from "node:os";
|
|
|
3014
3032
|
import path12 from "node:path";
|
|
3015
3033
|
import { promisify as promisify4 } from "node:util";
|
|
3016
3034
|
var execAsync4 = promisify4(exec4);
|
|
3035
|
+
var _tmpCounter = 0;
|
|
3017
3036
|
function parseJsonlLine(line) {
|
|
3018
3037
|
try {
|
|
3019
3038
|
const obj = JSON.parse(line);
|
|
@@ -3176,7 +3195,7 @@ class CodexAdapter {
|
|
|
3176
3195
|
--- DIFF ---
|
|
3177
3196
|
${opts.diff}`;
|
|
3178
3197
|
const tmpDir = os3.tmpdir();
|
|
3179
|
-
const tmpFile = path12.join(tmpDir, `gauntlet-codex-${Date.now()}.txt`);
|
|
3198
|
+
const tmpFile = path12.join(tmpDir, `gauntlet-codex-${process.pid}-${Date.now()}-${_tmpCounter++}.txt`);
|
|
3180
3199
|
await fs13.writeFile(tmpFile, fullContent);
|
|
3181
3200
|
const args = this.buildArgs(opts.allowToolUse, opts.thinkingBudget);
|
|
3182
3201
|
const cleanup = () => fs13.unlink(tmpFile).catch(() => {});
|
|
@@ -3254,6 +3273,7 @@ function resolveModelFromList(allModels, opts) {
|
|
|
3254
3273
|
|
|
3255
3274
|
// src/cli-adapters/cursor.ts
|
|
3256
3275
|
var execAsync5 = promisify5(exec5);
|
|
3276
|
+
var _tmpCounter2 = 0;
|
|
3257
3277
|
var log = getCategoryLogger("cursor");
|
|
3258
3278
|
function parseModelList(output) {
|
|
3259
3279
|
return output.split(`
|
|
@@ -3344,7 +3364,7 @@ class CursorAdapter {
|
|
|
3344
3364
|
--- DIFF ---
|
|
3345
3365
|
${opts.diff}`;
|
|
3346
3366
|
const tmpDir = os4.tmpdir();
|
|
3347
|
-
const tmpFile = path13.join(tmpDir, `gauntlet-cursor-${process.pid}-${Date.now()}.txt`);
|
|
3367
|
+
const tmpFile = path13.join(tmpDir, `gauntlet-cursor-${process.pid}-${Date.now()}-${_tmpCounter2++}.txt`);
|
|
3348
3368
|
await fs14.writeFile(tmpFile, fullContent);
|
|
3349
3369
|
let resolvedModel;
|
|
3350
3370
|
if (opts.model) {
|
|
@@ -3747,6 +3767,7 @@ import os6 from "node:os";
|
|
|
3747
3767
|
import path15 from "node:path";
|
|
3748
3768
|
import { promisify as promisify7 } from "node:util";
|
|
3749
3769
|
var execAsync7 = promisify7(exec7);
|
|
3770
|
+
var _tmpCounter3 = 0;
|
|
3750
3771
|
var log2 = getCategoryLogger("github-copilot");
|
|
3751
3772
|
function parseCopilotModels(helpOutput) {
|
|
3752
3773
|
const match = helpOutput.match(/choices:\s*(.+?)\)/);
|
|
@@ -3835,7 +3856,7 @@ class GitHubCopilotAdapter {
|
|
|
3835
3856
|
--- DIFF ---
|
|
3836
3857
|
${opts.diff}`;
|
|
3837
3858
|
const tmpDir = os6.tmpdir();
|
|
3838
|
-
const tmpFile = path15.join(tmpDir, `gauntlet-copilot-${process.pid}-${Date.now()}.txt`);
|
|
3859
|
+
const tmpFile = path15.join(tmpDir, `gauntlet-copilot-${process.pid}-${Date.now()}-${_tmpCounter3++}.txt`);
|
|
3839
3860
|
await fs16.writeFile(tmpFile, fullContent);
|
|
3840
3861
|
let resolvedModel;
|
|
3841
3862
|
if (opts.model) {
|
|
@@ -4068,98 +4089,504 @@ async function handleCriticalError(error, jobId, startTime, logPaths, mainLogger
|
|
|
4068
4089
|
|
|
4069
4090
|
// src/gates/review-diff.ts
|
|
4070
4091
|
import { exec as exec8 } from "node:child_process";
|
|
4092
|
+
import fs18 from "node:fs/promises";
|
|
4093
|
+
import os7 from "node:os";
|
|
4094
|
+
import path16 from "node:path";
|
|
4095
|
+
import { promisify as promisify9 } from "node:util";
|
|
4096
|
+
|
|
4097
|
+
// src/core/diff-stats.ts
|
|
4098
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
4071
4099
|
import { promisify as promisify8 } from "node:util";
|
|
4072
|
-
var
|
|
4073
|
-
var
|
|
4074
|
-
function
|
|
4075
|
-
|
|
4076
|
-
|
|
4100
|
+
var execFileAsyncOriginal = promisify8(execFile3);
|
|
4101
|
+
var execFileAsync2 = execFileAsyncOriginal;
|
|
4102
|
+
async function gitExec(args) {
|
|
4103
|
+
const { stdout } = await execFileAsync2("git", args, {
|
|
4104
|
+
maxBuffer: MAX_BUFFER_BYTES
|
|
4105
|
+
});
|
|
4106
|
+
return stdout;
|
|
4077
4107
|
}
|
|
4078
|
-
function
|
|
4079
|
-
|
|
4108
|
+
async function computeDiffStats(baseBranch, options = {}) {
|
|
4109
|
+
if (options.commit) {
|
|
4110
|
+
return computeCommitDiffStats(options.commit);
|
|
4111
|
+
}
|
|
4112
|
+
if (options.fixBase) {
|
|
4113
|
+
return computeFixBaseDiffStats(options.fixBase);
|
|
4114
|
+
}
|
|
4115
|
+
if (options.uncommitted) {
|
|
4116
|
+
return computeUncommittedDiffStats();
|
|
4117
|
+
}
|
|
4118
|
+
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
4119
|
+
if (isCI) {
|
|
4120
|
+
return computeCIDiffStats(baseBranch);
|
|
4121
|
+
}
|
|
4122
|
+
return computeLocalDiffStats(baseBranch);
|
|
4080
4123
|
}
|
|
4081
|
-
function
|
|
4082
|
-
|
|
4124
|
+
async function computeCommitDiffStats(commit) {
|
|
4125
|
+
try {
|
|
4126
|
+
const numstat = await gitExec([
|
|
4127
|
+
"diff",
|
|
4128
|
+
"--numstat",
|
|
4129
|
+
`${commit}^..${commit}`
|
|
4130
|
+
]);
|
|
4131
|
+
const lineStats = parseNumstat(numstat);
|
|
4132
|
+
const nameStatus = await gitExec([
|
|
4133
|
+
"diff",
|
|
4134
|
+
"--name-status",
|
|
4135
|
+
`${commit}^..${commit}`
|
|
4136
|
+
]);
|
|
4137
|
+
const fileStats = parseNameStatus(nameStatus);
|
|
4138
|
+
return {
|
|
4139
|
+
baseRef: `${commit}^`,
|
|
4140
|
+
...fileStats,
|
|
4141
|
+
...lineStats
|
|
4142
|
+
};
|
|
4143
|
+
} catch {
|
|
4144
|
+
try {
|
|
4145
|
+
const numstat = await gitExec(["diff", "--numstat", "--root", commit]);
|
|
4146
|
+
const lineStats = parseNumstat(numstat);
|
|
4147
|
+
const nameStatus = await gitExec([
|
|
4148
|
+
"diff",
|
|
4149
|
+
"--name-status",
|
|
4150
|
+
"--root",
|
|
4151
|
+
commit
|
|
4152
|
+
]);
|
|
4153
|
+
const fileStats = parseNameStatus(nameStatus);
|
|
4154
|
+
return {
|
|
4155
|
+
baseRef: "root",
|
|
4156
|
+
...fileStats,
|
|
4157
|
+
...lineStats
|
|
4158
|
+
};
|
|
4159
|
+
} catch {
|
|
4160
|
+
return emptyDiffStats(commit);
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4083
4163
|
}
|
|
4084
|
-
async function
|
|
4164
|
+
async function computeUncommittedDiffStats() {
|
|
4165
|
+
const stagedNumstat = await gitExec(["diff", "--numstat", "--cached"]);
|
|
4166
|
+
const stagedLines = parseNumstat(stagedNumstat);
|
|
4167
|
+
const stagedStatus = await gitExec(["diff", "--name-status", "--cached"]);
|
|
4168
|
+
const stagedFiles = parseNameStatus(stagedStatus);
|
|
4169
|
+
const unstagedNumstat = await gitExec(["diff", "--numstat"]);
|
|
4170
|
+
const unstagedLines = parseNumstat(unstagedNumstat);
|
|
4171
|
+
const unstagedStatus = await gitExec(["diff", "--name-status"]);
|
|
4172
|
+
const unstagedFiles = parseNameStatus(unstagedStatus);
|
|
4173
|
+
const untrackedList = await gitExec([
|
|
4174
|
+
"ls-files",
|
|
4175
|
+
"--others",
|
|
4176
|
+
"--exclude-standard"
|
|
4177
|
+
]);
|
|
4178
|
+
const untrackedFiles = untrackedList.split(`
|
|
4179
|
+
`).map((f) => f.trim()).filter((f) => f.length > 0);
|
|
4180
|
+
return {
|
|
4181
|
+
baseRef: "uncommitted",
|
|
4182
|
+
total: stagedFiles.total + unstagedFiles.total + untrackedFiles.length - countOverlap(stagedStatus, unstagedStatus),
|
|
4183
|
+
newFiles: stagedFiles.newFiles + unstagedFiles.newFiles + untrackedFiles.length,
|
|
4184
|
+
modifiedFiles: stagedFiles.modifiedFiles + unstagedFiles.modifiedFiles,
|
|
4185
|
+
deletedFiles: stagedFiles.deletedFiles + unstagedFiles.deletedFiles,
|
|
4186
|
+
linesAdded: stagedLines.linesAdded + unstagedLines.linesAdded,
|
|
4187
|
+
linesRemoved: stagedLines.linesRemoved + unstagedLines.linesRemoved
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
async function getStashUntrackedFiles(fixBase, pathFilter) {
|
|
4085
4191
|
try {
|
|
4086
|
-
const
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
return stdout;
|
|
4090
|
-
} catch (error) {
|
|
4091
|
-
const err = error;
|
|
4092
|
-
if (typeof err.code === "number" && err.stdout) {
|
|
4093
|
-
return err.stdout;
|
|
4192
|
+
const args = ["ls-tree", "-r", "--name-only", `${fixBase}^3`];
|
|
4193
|
+
if (pathFilter) {
|
|
4194
|
+
args.push("--", pathFilter);
|
|
4094
4195
|
}
|
|
4095
|
-
|
|
4196
|
+
const treeFiles = await gitExec(args);
|
|
4197
|
+
return new Set(treeFiles.split(`
|
|
4198
|
+
`).map((f) => f.trim()).filter((f) => f.length > 0));
|
|
4199
|
+
} catch {
|
|
4200
|
+
return new Set;
|
|
4096
4201
|
}
|
|
4097
4202
|
}
|
|
4098
|
-
async function
|
|
4099
|
-
|
|
4100
|
-
const { stdout } = await execAsync8(`git ls-files --others --exclude-standard${pArg}`, { maxBuffer: MAX_BUFFER_BYTES });
|
|
4101
|
-
const files = parseLines(stdout);
|
|
4102
|
-
const diffs = [];
|
|
4203
|
+
async function countChangedStashUntracked(files, fixBase) {
|
|
4204
|
+
let changed = 0;
|
|
4103
4205
|
for (const file of files) {
|
|
4104
4206
|
try {
|
|
4105
|
-
const
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
`);
|
|
4112
|
-
if (msg.includes("Could not access") || msg.includes("ENOENT") || msg.includes("No such file")) {
|
|
4113
|
-
continue;
|
|
4207
|
+
const [oldHash, newHash] = await Promise.all([
|
|
4208
|
+
gitExec(["rev-parse", `${fixBase}^3:${file}`]),
|
|
4209
|
+
gitExec(["hash-object", "--", file])
|
|
4210
|
+
]);
|
|
4211
|
+
if (oldHash.trim() !== newHash.trim()) {
|
|
4212
|
+
changed++;
|
|
4114
4213
|
}
|
|
4115
|
-
|
|
4214
|
+
} catch {
|
|
4215
|
+
changed++;
|
|
4116
4216
|
}
|
|
4117
4217
|
}
|
|
4118
|
-
return
|
|
4119
|
-
`);
|
|
4120
|
-
}
|
|
4121
|
-
async function getDiff(entryPointPath, baseBranch, options) {
|
|
4122
|
-
log4.debug(`getDiff: entryPoint=${entryPointPath}, fixBase=${options?.fixBase ?? "none"}, uncommitted=${options?.uncommitted ?? false}, commit=${options?.commit ?? "none"}`);
|
|
4123
|
-
if (options?.fixBase) {
|
|
4124
|
-
const result = await getFixBaseDiff(entryPointPath, options.fixBase);
|
|
4125
|
-
if (result !== null)
|
|
4126
|
-
return result;
|
|
4127
|
-
}
|
|
4128
|
-
if (options?.uncommitted) {
|
|
4129
|
-
return getUncommittedDiff(entryPointPath);
|
|
4130
|
-
}
|
|
4131
|
-
if (options?.commit) {
|
|
4132
|
-
return getCommitDiff(entryPointPath, options.commit);
|
|
4133
|
-
}
|
|
4134
|
-
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
4135
|
-
return isCI ? getCIDiff(entryPointPath, baseBranch) : getLocalDiff(entryPointPath, baseBranch);
|
|
4218
|
+
return changed;
|
|
4136
4219
|
}
|
|
4137
|
-
async function
|
|
4138
|
-
if (!/^[a-f0-9]+$/.test(fixBase)) {
|
|
4139
|
-
throw new Error(`Invalid session ref: ${fixBase}`);
|
|
4140
|
-
}
|
|
4141
|
-
const pArg = pathArg(entryPointPath);
|
|
4220
|
+
async function computeFixBaseDiffStats(fixBase) {
|
|
4142
4221
|
try {
|
|
4143
|
-
const
|
|
4144
|
-
const
|
|
4145
|
-
const
|
|
4146
|
-
const
|
|
4147
|
-
const
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
}
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4222
|
+
const numstat = await gitExec(["diff", "--numstat", fixBase]);
|
|
4223
|
+
const lineStats = parseNumstat(numstat);
|
|
4224
|
+
const nameStatus = await gitExec(["diff", "--name-status", fixBase]);
|
|
4225
|
+
const fileStats = parseNameStatus(nameStatus);
|
|
4226
|
+
const currentUntracked = (await gitExec(["ls-files", "--others", "--exclude-standard"])).split(`
|
|
4227
|
+
`).map((f) => f.trim()).filter((f) => f.length > 0);
|
|
4228
|
+
let fixBaseTrackedFiles;
|
|
4229
|
+
try {
|
|
4230
|
+
const treeFiles = await gitExec([
|
|
4231
|
+
"ls-tree",
|
|
4232
|
+
"-r",
|
|
4233
|
+
"--name-only",
|
|
4234
|
+
fixBase
|
|
4235
|
+
]);
|
|
4236
|
+
fixBaseTrackedFiles = new Set(treeFiles.split(`
|
|
4237
|
+
`).map((f) => f.trim()).filter((f) => f.length > 0));
|
|
4238
|
+
} catch {
|
|
4239
|
+
fixBaseTrackedFiles = new Set;
|
|
4240
|
+
}
|
|
4241
|
+
const fixBaseUntrackedFiles = await getStashUntrackedFiles(fixBase);
|
|
4242
|
+
const allSnapshotFiles = new Set([
|
|
4243
|
+
...fixBaseTrackedFiles,
|
|
4244
|
+
...fixBaseUntrackedFiles
|
|
4245
|
+
]);
|
|
4246
|
+
const newUntrackedFiles = currentUntracked.filter((f) => !allSnapshotFiles.has(f));
|
|
4247
|
+
const knownUntrackedFiles = currentUntracked.filter((f) => fixBaseUntrackedFiles.has(f));
|
|
4248
|
+
const changedKnownUntracked = await countChangedStashUntracked(knownUntrackedFiles, fixBase);
|
|
4249
|
+
return {
|
|
4250
|
+
baseRef: fixBase,
|
|
4251
|
+
total: fileStats.total + newUntrackedFiles.length + changedKnownUntracked,
|
|
4252
|
+
newFiles: fileStats.newFiles + newUntrackedFiles.length,
|
|
4253
|
+
modifiedFiles: fileStats.modifiedFiles + changedKnownUntracked,
|
|
4254
|
+
deletedFiles: fileStats.deletedFiles,
|
|
4255
|
+
linesAdded: lineStats.linesAdded,
|
|
4256
|
+
linesRemoved: lineStats.linesRemoved
|
|
4257
|
+
};
|
|
4258
|
+
} catch {
|
|
4259
|
+
return emptyDiffStats(fixBase);
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
async function computeCIDiffStats(baseBranch) {
|
|
4263
|
+
const headRef = process.env.GITHUB_SHA || "HEAD";
|
|
4264
|
+
try {
|
|
4265
|
+
const numstat = await gitExec([
|
|
4266
|
+
"diff",
|
|
4267
|
+
"--numstat",
|
|
4268
|
+
`${baseBranch}...${headRef}`
|
|
4269
|
+
]);
|
|
4270
|
+
const lineStats = parseNumstat(numstat);
|
|
4271
|
+
const nameStatus = await gitExec([
|
|
4272
|
+
"diff",
|
|
4273
|
+
"--name-status",
|
|
4274
|
+
`${baseBranch}...${headRef}`
|
|
4275
|
+
]);
|
|
4276
|
+
const fileStats = parseNameStatus(nameStatus);
|
|
4277
|
+
return {
|
|
4278
|
+
baseRef: baseBranch,
|
|
4279
|
+
...fileStats,
|
|
4280
|
+
...lineStats
|
|
4281
|
+
};
|
|
4282
|
+
} catch {
|
|
4283
|
+
try {
|
|
4284
|
+
const numstat = await gitExec(["diff", "--numstat", "HEAD^...HEAD"]);
|
|
4285
|
+
const lineStats = parseNumstat(numstat);
|
|
4286
|
+
const nameStatus = await gitExec([
|
|
4287
|
+
"diff",
|
|
4288
|
+
"--name-status",
|
|
4289
|
+
"HEAD^...HEAD"
|
|
4290
|
+
]);
|
|
4291
|
+
const fileStats = parseNameStatus(nameStatus);
|
|
4292
|
+
return {
|
|
4293
|
+
baseRef: "HEAD^",
|
|
4294
|
+
...fileStats,
|
|
4295
|
+
...lineStats
|
|
4296
|
+
};
|
|
4297
|
+
} catch {
|
|
4298
|
+
return emptyDiffStats(baseBranch);
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
async function computeLocalDiffStats(baseBranch) {
|
|
4303
|
+
const committedNumstat = await gitExec([
|
|
4304
|
+
"diff",
|
|
4305
|
+
"--numstat",
|
|
4306
|
+
`${baseBranch}...HEAD`
|
|
4307
|
+
]);
|
|
4308
|
+
const committedLines = parseNumstat(committedNumstat);
|
|
4309
|
+
const committedStatus = await gitExec([
|
|
4310
|
+
"diff",
|
|
4311
|
+
"--name-status",
|
|
4312
|
+
`${baseBranch}...HEAD`
|
|
4313
|
+
]);
|
|
4314
|
+
const committedFiles = parseNameStatus(committedStatus);
|
|
4315
|
+
const uncommittedNumstat = await gitExec(["diff", "--numstat", "HEAD"]);
|
|
4316
|
+
const uncommittedLines = parseNumstat(uncommittedNumstat);
|
|
4317
|
+
const uncommittedStatus = await gitExec(["diff", "--name-status", "HEAD"]);
|
|
4318
|
+
const uncommittedFiles = parseNameStatus(uncommittedStatus);
|
|
4319
|
+
const untrackedList = await gitExec([
|
|
4320
|
+
"ls-files",
|
|
4321
|
+
"--others",
|
|
4322
|
+
"--exclude-standard"
|
|
4323
|
+
]);
|
|
4324
|
+
const untrackedFiles = untrackedList.split(`
|
|
4325
|
+
`).map((f) => f.trim()).filter((f) => f.length > 0);
|
|
4326
|
+
const totalNew = committedFiles.newFiles + uncommittedFiles.newFiles + untrackedFiles.length;
|
|
4327
|
+
const totalModified = committedFiles.modifiedFiles + uncommittedFiles.modifiedFiles;
|
|
4328
|
+
const totalDeleted = committedFiles.deletedFiles + uncommittedFiles.deletedFiles;
|
|
4329
|
+
return {
|
|
4330
|
+
baseRef: baseBranch,
|
|
4331
|
+
total: totalNew + totalModified + totalDeleted,
|
|
4332
|
+
newFiles: totalNew,
|
|
4333
|
+
modifiedFiles: totalModified,
|
|
4334
|
+
deletedFiles: totalDeleted,
|
|
4335
|
+
linesAdded: committedLines.linesAdded + uncommittedLines.linesAdded,
|
|
4336
|
+
linesRemoved: committedLines.linesRemoved + uncommittedLines.linesRemoved
|
|
4337
|
+
};
|
|
4338
|
+
}
|
|
4339
|
+
function parseNumstat(output) {
|
|
4340
|
+
let linesAdded = 0;
|
|
4341
|
+
let linesRemoved = 0;
|
|
4342
|
+
for (const line of output.split(`
|
|
4343
|
+
`)) {
|
|
4344
|
+
if (!line.trim())
|
|
4345
|
+
continue;
|
|
4346
|
+
const parts = line.split("\t");
|
|
4347
|
+
if (parts.length < 3)
|
|
4348
|
+
continue;
|
|
4349
|
+
const added = parts[0];
|
|
4350
|
+
const removed = parts[1];
|
|
4351
|
+
if (added && added !== "-") {
|
|
4352
|
+
linesAdded += parseInt(added, 10) || 0;
|
|
4353
|
+
}
|
|
4354
|
+
if (removed && removed !== "-") {
|
|
4355
|
+
linesRemoved += parseInt(removed, 10) || 0;
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
return { linesAdded, linesRemoved };
|
|
4359
|
+
}
|
|
4360
|
+
function parseNameStatus(output) {
|
|
4361
|
+
let newFiles = 0;
|
|
4362
|
+
let modifiedFiles = 0;
|
|
4363
|
+
let deletedFiles = 0;
|
|
4364
|
+
for (const line of output.split(`
|
|
4365
|
+
`)) {
|
|
4366
|
+
if (!line.trim())
|
|
4367
|
+
continue;
|
|
4368
|
+
const status = line[0];
|
|
4369
|
+
switch (status) {
|
|
4370
|
+
case "A":
|
|
4371
|
+
newFiles++;
|
|
4372
|
+
break;
|
|
4373
|
+
case "M":
|
|
4374
|
+
case "R":
|
|
4375
|
+
case "C":
|
|
4376
|
+
case "T":
|
|
4377
|
+
modifiedFiles++;
|
|
4378
|
+
break;
|
|
4379
|
+
case "D":
|
|
4380
|
+
deletedFiles++;
|
|
4381
|
+
break;
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
return {
|
|
4385
|
+
total: newFiles + modifiedFiles + deletedFiles,
|
|
4386
|
+
newFiles,
|
|
4387
|
+
modifiedFiles,
|
|
4388
|
+
deletedFiles
|
|
4389
|
+
};
|
|
4390
|
+
}
|
|
4391
|
+
function countOverlap(status1, status2) {
|
|
4392
|
+
const files1 = new Set;
|
|
4393
|
+
for (const line of status1.split(`
|
|
4394
|
+
`)) {
|
|
4395
|
+
if (!line.trim())
|
|
4396
|
+
continue;
|
|
4397
|
+
const parts = line.split("\t");
|
|
4398
|
+
const file = parts[1];
|
|
4399
|
+
if (parts.length >= 2 && file) {
|
|
4400
|
+
files1.add(file);
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
let overlap = 0;
|
|
4404
|
+
for (const line of status2.split(`
|
|
4405
|
+
`)) {
|
|
4406
|
+
if (!line.trim())
|
|
4407
|
+
continue;
|
|
4408
|
+
const parts = line.split("\t");
|
|
4409
|
+
const file = parts[1];
|
|
4410
|
+
if (parts.length >= 2 && file && files1.has(file)) {
|
|
4411
|
+
overlap++;
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
return overlap;
|
|
4415
|
+
}
|
|
4416
|
+
function emptyDiffStats(baseRef) {
|
|
4417
|
+
return {
|
|
4418
|
+
baseRef,
|
|
4419
|
+
total: 0,
|
|
4420
|
+
newFiles: 0,
|
|
4421
|
+
modifiedFiles: 0,
|
|
4422
|
+
deletedFiles: 0,
|
|
4423
|
+
linesAdded: 0,
|
|
4424
|
+
linesRemoved: 0
|
|
4425
|
+
};
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
// src/gates/review-diff.ts
|
|
4429
|
+
var log4 = getCategoryLogger("gate", "review");
|
|
4430
|
+
var execAsync8 = promisify9(exec8);
|
|
4431
|
+
function parseLines(stdout) {
|
|
4432
|
+
return stdout.split(`
|
|
4433
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
4434
|
+
}
|
|
4435
|
+
function pathArg(entryPointPath) {
|
|
4436
|
+
return ` -- ${quoteArg(entryPointPath)}`;
|
|
4437
|
+
}
|
|
4438
|
+
function quoteArg(value) {
|
|
4439
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
4440
|
+
}
|
|
4441
|
+
async function execDiff(command) {
|
|
4442
|
+
try {
|
|
4443
|
+
const { stdout } = await execAsync8(command, {
|
|
4444
|
+
maxBuffer: MAX_BUFFER_BYTES
|
|
4445
|
+
});
|
|
4446
|
+
return stdout;
|
|
4447
|
+
} catch (error) {
|
|
4448
|
+
const err = error;
|
|
4449
|
+
if (typeof err.code === "number" && err.stdout) {
|
|
4450
|
+
return err.stdout;
|
|
4451
|
+
}
|
|
4452
|
+
throw error;
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
async function untrackedDiff(entryPointPath) {
|
|
4456
|
+
const pArg = pathArg(entryPointPath);
|
|
4457
|
+
const { stdout } = await execAsync8(`git ls-files --others --exclude-standard${pArg}`, { maxBuffer: MAX_BUFFER_BYTES });
|
|
4458
|
+
const files = parseLines(stdout);
|
|
4459
|
+
const diffs = [];
|
|
4460
|
+
for (const file of files) {
|
|
4461
|
+
try {
|
|
4462
|
+
const diff = await execDiff(`git diff --no-index -- /dev/null ${quoteArg(file)}`);
|
|
4463
|
+
if (diff.trim())
|
|
4464
|
+
diffs.push(diff);
|
|
4465
|
+
} catch (error) {
|
|
4466
|
+
const err = error;
|
|
4467
|
+
const msg = [err.message, err.stderr].filter(Boolean).join(`
|
|
4468
|
+
`);
|
|
4469
|
+
if (msg.includes("Could not access") || msg.includes("ENOENT") || msg.includes("No such file")) {
|
|
4470
|
+
continue;
|
|
4471
|
+
}
|
|
4472
|
+
throw error;
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
return diffs.join(`
|
|
4476
|
+
`);
|
|
4477
|
+
}
|
|
4478
|
+
async function getDiff(entryPointPath, baseBranch, options) {
|
|
4479
|
+
log4.debug(`getDiff: entryPoint=${entryPointPath}, fixBase=${options?.fixBase ?? "none"}, uncommitted=${options?.uncommitted ?? false}, commit=${options?.commit ?? "none"}`);
|
|
4480
|
+
if (options?.fixBase) {
|
|
4481
|
+
const result = await getFixBaseDiff(entryPointPath, options.fixBase);
|
|
4482
|
+
if (result !== null)
|
|
4483
|
+
return result;
|
|
4484
|
+
}
|
|
4485
|
+
if (options?.uncommitted) {
|
|
4486
|
+
return getUncommittedDiff(entryPointPath);
|
|
4487
|
+
}
|
|
4488
|
+
if (options?.commit) {
|
|
4489
|
+
return getCommitDiff(entryPointPath, options.commit);
|
|
4490
|
+
}
|
|
4491
|
+
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
4492
|
+
return isCI ? getCIDiff(entryPointPath, baseBranch) : getLocalDiff(entryPointPath, baseBranch);
|
|
4493
|
+
}
|
|
4494
|
+
async function hasFileChangedSinceStash(file, fixBase) {
|
|
4495
|
+
const [{ stdout: oldHashOut }, { stdout: newHashOut }] = await Promise.all([
|
|
4496
|
+
execAsync8(`git rev-parse ${quoteArg(`${fixBase}^3:${file}`)}`, {
|
|
4497
|
+
maxBuffer: MAX_BUFFER_BYTES
|
|
4498
|
+
}),
|
|
4499
|
+
execAsync8(`git hash-object -- ${quoteArg(file)}`, {
|
|
4500
|
+
maxBuffer: MAX_BUFFER_BYTES
|
|
4501
|
+
})
|
|
4502
|
+
]);
|
|
4503
|
+
return oldHashOut.trim() !== newHashOut.trim();
|
|
4504
|
+
}
|
|
4505
|
+
async function writeStashFileToTemp(file, fixBase, tmpDir, counter) {
|
|
4506
|
+
const { stdout: oldContent } = await execAsync8(`git show ${quoteArg(`${fixBase}^3:${file}`)}`, { maxBuffer: MAX_BUFFER_BYTES, encoding: "buffer" });
|
|
4507
|
+
const tmpFile = path16.join(tmpDir, `${counter}-${path16.basename(file)}`);
|
|
4508
|
+
await fs18.writeFile(tmpFile, oldContent);
|
|
4509
|
+
return tmpFile;
|
|
4510
|
+
}
|
|
4511
|
+
function placeholderDiff(file) {
|
|
4512
|
+
return `diff --git a/${file} b/${file}
|
|
4513
|
+
--- /dev/null
|
|
4514
|
+
+++ b/${file}
|
|
4515
|
+
@@ -0,0 +1 @@
|
|
4516
|
+
+[gauntlet: diff unavailable for this file]`;
|
|
4517
|
+
}
|
|
4518
|
+
async function collectStashUntrackedDiffs(files, fixBase) {
|
|
4519
|
+
if (files.length === 0)
|
|
4520
|
+
return [];
|
|
4521
|
+
const diffs = [];
|
|
4522
|
+
const tmpDir = await fs18.mkdtemp(path16.join(os7.tmpdir(), "gauntlet-"));
|
|
4523
|
+
try {
|
|
4524
|
+
let counter = 0;
|
|
4525
|
+
for (const file of files) {
|
|
4526
|
+
const d = await diffSingleStashFile(file, fixBase, tmpDir, counter++);
|
|
4527
|
+
if (d)
|
|
4528
|
+
diffs.push(d);
|
|
4529
|
+
}
|
|
4530
|
+
} finally {
|
|
4531
|
+
await fs18.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
|
|
4532
|
+
}
|
|
4533
|
+
return diffs;
|
|
4534
|
+
}
|
|
4535
|
+
async function diffSingleStashFile(file, fixBase, tmpDir, counter) {
|
|
4536
|
+
try {
|
|
4537
|
+
if (!await hasFileChangedSinceStash(file, fixBase)) {
|
|
4538
|
+
return null;
|
|
4539
|
+
}
|
|
4540
|
+
const tmpFile = await writeStashFileToTemp(file, fixBase, tmpDir, counter);
|
|
4541
|
+
const d = await execDiff(`git diff --no-index -- ${quoteArg(tmpFile)} ${quoteArg(file)}`);
|
|
4542
|
+
return d.trim() || null;
|
|
4543
|
+
} catch (outerErr) {
|
|
4544
|
+
log4.debug(`Stash diff failed for ${file}, falling back to full diff: ${outerErr instanceof Error ? outerErr.message : outerErr}`);
|
|
4545
|
+
return diffFallbackToDevNull(file);
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
async function diffFallbackToDevNull(file) {
|
|
4549
|
+
try {
|
|
4550
|
+
const d = await execDiff(`git diff --no-index -- /dev/null ${quoteArg(file)}`);
|
|
4551
|
+
return d.trim() || null;
|
|
4552
|
+
} catch (innerErr) {
|
|
4553
|
+
log4.warn(`Failed to compute any diff for ${file}: ${innerErr instanceof Error ? innerErr.message : innerErr}`);
|
|
4554
|
+
return placeholderDiff(file);
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
async function getFixBaseDiff(entryPointPath, fixBase) {
|
|
4558
|
+
if (!/^[a-f0-9]+$/.test(fixBase)) {
|
|
4559
|
+
throw new Error(`Invalid session ref: ${fixBase}`);
|
|
4560
|
+
}
|
|
4561
|
+
const pArg = pathArg(entryPointPath);
|
|
4562
|
+
try {
|
|
4563
|
+
const diff = await execDiff(`git diff ${fixBase}${pArg}`);
|
|
4564
|
+
const { stdout: untrackedStdout } = await execAsync8(`git ls-files --others --exclude-standard${pArg}`, { maxBuffer: MAX_BUFFER_BYTES });
|
|
4565
|
+
const currentUntracked = new Set(parseLines(untrackedStdout));
|
|
4566
|
+
const { stdout: snapshotFilesStdout } = await execAsync8(`git ls-tree -r --name-only ${fixBase}${pArg}`, { maxBuffer: MAX_BUFFER_BYTES });
|
|
4567
|
+
const snapshotTrackedFiles = new Set(parseLines(snapshotFilesStdout));
|
|
4568
|
+
const snapshotUntrackedFiles = await getStashUntrackedFiles(fixBase, entryPointPath);
|
|
4569
|
+
const allSnapshotFiles = new Set([
|
|
4570
|
+
...snapshotTrackedFiles,
|
|
4571
|
+
...snapshotUntrackedFiles
|
|
4572
|
+
]);
|
|
4573
|
+
const newUntracked = [...currentUntracked].filter((f) => !allSnapshotFiles.has(f));
|
|
4574
|
+
const knownUntracked = [...currentUntracked].filter((f) => snapshotUntrackedFiles.has(f));
|
|
4575
|
+
const newUntrackedDiffs = await collectUntrackedDiffs(newUntracked);
|
|
4576
|
+
const knownUntrackedDiffs = await collectStashUntrackedDiffs(knownUntracked, fixBase);
|
|
4577
|
+
const scopedDiff = [diff, ...newUntrackedDiffs, ...knownUntrackedDiffs].filter(Boolean).join(`
|
|
4578
|
+
`);
|
|
4579
|
+
log4.debug(`Scoped diff via fixBase: ${scopedDiff.split(`
|
|
4580
|
+
`).length} lines (${newUntracked.length} new, ${knownUntracked.length} known untracked)`);
|
|
4581
|
+
return scopedDiff;
|
|
4582
|
+
} catch (error) {
|
|
4583
|
+
log4.warn(`Failed to compute diff against fixBase ${fixBase}, falling back to full uncommitted diff. ${error instanceof Error ? error.message : error}`);
|
|
4584
|
+
return null;
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
async function collectUntrackedDiffs(files) {
|
|
4588
|
+
const diffs = [];
|
|
4589
|
+
for (const file of files) {
|
|
4163
4590
|
try {
|
|
4164
4591
|
const d = await execDiff(`git diff --no-index -- /dev/null ${quoteArg(file)}`);
|
|
4165
4592
|
if (d.trim())
|
|
@@ -4725,7 +5152,7 @@ async function handleUsageLimit(adapter, logDir, mainLogger) {
|
|
|
4725
5152
|
}
|
|
4726
5153
|
|
|
4727
5154
|
// src/gates/review-helpers.ts
|
|
4728
|
-
import
|
|
5155
|
+
import fs19 from "node:fs/promises";
|
|
4729
5156
|
var log6 = getCategoryLogger("gate", "review");
|
|
4730
5157
|
async function collectHealthyAdapters(preferences, mainLogger, logDir) {
|
|
4731
5158
|
const healthyAdapters = [];
|
|
@@ -4853,7 +5280,7 @@ async function writeSkippedSlotLog(assignment, loggerFactory, logPathsSet, logPa
|
|
|
4853
5280
|
violations: [],
|
|
4854
5281
|
passIteration: assignment.passIteration
|
|
4855
5282
|
};
|
|
4856
|
-
await
|
|
5283
|
+
await fs19.writeFile(jsonPath, JSON.stringify(skippedOutput, null, 2));
|
|
4857
5284
|
if (!logPathsSet.has(logPath)) {
|
|
4858
5285
|
logPathsSet.add(logPath);
|
|
4859
5286
|
logPaths.push(logPath);
|
|
@@ -5250,19 +5677,19 @@ class Runner {
|
|
|
5250
5677
|
}
|
|
5251
5678
|
|
|
5252
5679
|
// src/output/console.ts
|
|
5253
|
-
import
|
|
5680
|
+
import fs22 from "node:fs/promises";
|
|
5254
5681
|
import chalk2 from "chalk";
|
|
5255
5682
|
|
|
5256
5683
|
// src/utils/log-parser.ts
|
|
5257
|
-
import
|
|
5258
|
-
import
|
|
5684
|
+
import fs21 from "node:fs/promises";
|
|
5685
|
+
import path19 from "node:path";
|
|
5259
5686
|
|
|
5260
5687
|
// src/utils/log-parser-find-helpers.ts
|
|
5261
|
-
import
|
|
5262
|
-
import
|
|
5688
|
+
import fs20 from "node:fs/promises";
|
|
5689
|
+
import path18 from "node:path";
|
|
5263
5690
|
|
|
5264
5691
|
// src/utils/log-parser-helpers.ts
|
|
5265
|
-
import
|
|
5692
|
+
import path17 from "node:path";
|
|
5266
5693
|
function parseReviewFilename(filename) {
|
|
5267
5694
|
const m = filename.match(/^(.+)_([^@]+)@(\d+)\.(\d+)\.(log|json)$/);
|
|
5268
5695
|
if (!m)
|
|
@@ -5434,9 +5861,9 @@ async function parseRunFile(logDir, runFiles, prefix, runNum, parseJsonFn, parse
|
|
|
5434
5861
|
const jsonFile = runFiles.find((f) => f.startsWith(`${prefix}.${runNum}.`) && f.endsWith(".json"));
|
|
5435
5862
|
const logFile = runFiles.find((f) => f.startsWith(`${prefix}.${runNum}.`) && f.endsWith(".log"));
|
|
5436
5863
|
if (jsonFile)
|
|
5437
|
-
return parseJsonFn(
|
|
5864
|
+
return parseJsonFn(path17.join(logDir, jsonFile));
|
|
5438
5865
|
if (logFile)
|
|
5439
|
-
return parseLogFn(
|
|
5866
|
+
return parseLogFn(path17.join(logDir, logFile));
|
|
5440
5867
|
return null;
|
|
5441
5868
|
}
|
|
5442
5869
|
async function collectIterationFailures(logDir, files, runNum, parseJsonFn, parseLogFn) {
|
|
@@ -5553,7 +5980,7 @@ function addCheckFile(checkPrefixMap, file) {
|
|
|
5553
5980
|
}
|
|
5554
5981
|
async function isJsonReviewPassing(jsonPath) {
|
|
5555
5982
|
try {
|
|
5556
|
-
const content = await
|
|
5983
|
+
const content = await fs20.readFile(jsonPath, "utf-8");
|
|
5557
5984
|
const data = JSON.parse(content);
|
|
5558
5985
|
return data.status === "pass" || data.status === "skipped_prior_pass";
|
|
5559
5986
|
} catch {
|
|
@@ -5562,7 +5989,7 @@ async function isJsonReviewPassing(jsonPath) {
|
|
|
5562
5989
|
}
|
|
5563
5990
|
async function isLogReviewPassing(logPath) {
|
|
5564
5991
|
try {
|
|
5565
|
-
const content = await
|
|
5992
|
+
const content = await fs20.readFile(logPath, "utf-8");
|
|
5566
5993
|
if (content.includes("Status: skipped_prior_pass"))
|
|
5567
5994
|
return true;
|
|
5568
5995
|
if (content.includes("--- Review Output")) {
|
|
@@ -5618,7 +6045,7 @@ async function processReviewSlots(logDir, reviewSlotMap, includePassedSlots, par
|
|
|
5618
6045
|
const reviewIndex = parseInt(slotKey.substring(sepIdx + 1), 10);
|
|
5619
6046
|
const parsed = parseReviewFilename(fileInfo.filename);
|
|
5620
6047
|
const adapter = parsed?.adapter || "unknown";
|
|
5621
|
-
const filePath =
|
|
6048
|
+
const filePath = path18.join(logDir, fileInfo.filename);
|
|
5622
6049
|
const isPassing = fileInfo.ext === "json" ? await isJsonReviewPassing(filePath) : await isLogReviewPassing(filePath);
|
|
5623
6050
|
if (isPassing && includePassedSlots) {
|
|
5624
6051
|
recordPassedSlot(passedSlots, jobId, reviewIndex, fileInfo.runNumber, adapter);
|
|
@@ -5640,9 +6067,9 @@ async function processCheckFiles(logDir, checkPrefixMap, parseJsonFn, parseLogFn
|
|
|
5640
6067
|
continue;
|
|
5641
6068
|
let failure = null;
|
|
5642
6069
|
if (exts.has("json")) {
|
|
5643
|
-
failure = await parseJsonFn(
|
|
6070
|
+
failure = await parseJsonFn(path18.join(logDir, `${prefix}.${latestRun}.json`));
|
|
5644
6071
|
} else if (exts.has("log")) {
|
|
5645
|
-
failure = await parseLogFn(
|
|
6072
|
+
failure = await parseLogFn(path18.join(logDir, `${prefix}.${latestRun}.log`));
|
|
5646
6073
|
}
|
|
5647
6074
|
if (!failure)
|
|
5648
6075
|
continue;
|
|
@@ -5660,9 +6087,9 @@ async function processCheckFiles(logDir, checkPrefixMap, parseJsonFn, parseLogFn
|
|
|
5660
6087
|
var log9 = getCategoryLogger("log-parser");
|
|
5661
6088
|
async function parseJsonReviewFile(jsonPath) {
|
|
5662
6089
|
try {
|
|
5663
|
-
const content = await
|
|
6090
|
+
const content = await fs21.readFile(jsonPath, "utf-8");
|
|
5664
6091
|
const data = JSON.parse(content);
|
|
5665
|
-
const filename =
|
|
6092
|
+
const filename = path19.basename(jsonPath);
|
|
5666
6093
|
const parsed = parseReviewFilename(filename);
|
|
5667
6094
|
const jobId = parsed ? parsed.jobId : filename.replace(/\.\d+\.json$/, "");
|
|
5668
6095
|
if (data.status === "pass" || data.status === "skipped_prior_pass") {
|
|
@@ -5702,8 +6129,8 @@ async function parseJsonReviewFile(jsonPath) {
|
|
|
5702
6129
|
}
|
|
5703
6130
|
async function parseLogFile(logPath) {
|
|
5704
6131
|
try {
|
|
5705
|
-
const content = await
|
|
5706
|
-
const filename =
|
|
6132
|
+
const content = await fs21.readFile(logPath, "utf-8");
|
|
6133
|
+
const filename = path19.basename(logPath);
|
|
5707
6134
|
const parsed = parseReviewFilename(filename);
|
|
5708
6135
|
const jobId = parsed ? parsed.jobId : extractPrefix(filename);
|
|
5709
6136
|
if (content.includes("--- Review Output")) {
|
|
@@ -5716,7 +6143,7 @@ async function parseLogFile(logPath) {
|
|
|
5716
6143
|
}
|
|
5717
6144
|
async function reconstructHistory(logDir) {
|
|
5718
6145
|
try {
|
|
5719
|
-
const files = await
|
|
6146
|
+
const files = await fs21.readdir(logDir);
|
|
5720
6147
|
const sortedRuns = collectRunNumbers(files);
|
|
5721
6148
|
const iterations = [];
|
|
5722
6149
|
let previousFailuresByJob = new Map;
|
|
@@ -5733,7 +6160,7 @@ async function reconstructHistory(logDir) {
|
|
|
5733
6160
|
}
|
|
5734
6161
|
async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
|
|
5735
6162
|
try {
|
|
5736
|
-
const files = await
|
|
6163
|
+
const files = await fs21.readdir(logDir);
|
|
5737
6164
|
const { reviewSlotMap, checkPrefixMap } = categorizeFiles(files, gateFilter);
|
|
5738
6165
|
const { jobReviewFailures, passedSlots } = await processReviewSlots(logDir, reviewSlotMap, includePassedSlots, parseJsonReviewFile, parseLogFile);
|
|
5739
6166
|
const gateFailures = [];
|
|
@@ -5743,7 +6170,7 @@ async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
|
|
|
5743
6170
|
gateName: "",
|
|
5744
6171
|
entryPoint: "",
|
|
5745
6172
|
adapterFailures,
|
|
5746
|
-
logPath:
|
|
6173
|
+
logPath: path19.join(logDir, `${jobId}.log`)
|
|
5747
6174
|
});
|
|
5748
6175
|
}
|
|
5749
6176
|
const checkFailures = await processCheckFiles(logDir, checkPrefixMap, parseJsonReviewFile, parseLogFile);
|
|
@@ -5762,12 +6189,12 @@ async function findPreviousFailures(logDir, gateFilter, includePassedSlots) {
|
|
|
5762
6189
|
async function hasSkippedViolationsInLogs(opts) {
|
|
5763
6190
|
const { logDir } = opts;
|
|
5764
6191
|
try {
|
|
5765
|
-
const files = await
|
|
6192
|
+
const files = await fs21.readdir(logDir);
|
|
5766
6193
|
for (const file of files) {
|
|
5767
6194
|
if (!file.endsWith(".json"))
|
|
5768
6195
|
continue;
|
|
5769
6196
|
try {
|
|
5770
|
-
const content = await
|
|
6197
|
+
const content = await fs21.readFile(path19.join(logDir, file), "utf-8");
|
|
5771
6198
|
const data = JSON.parse(content);
|
|
5772
6199
|
if (data.violations?.some((v) => v.status === "skipped")) {
|
|
5773
6200
|
return true;
|
|
@@ -6025,7 +6452,7 @@ ${chalk2.bold(SEPARATOR)}`);
|
|
|
6025
6452
|
const allDetails = [];
|
|
6026
6453
|
for (const logPath of logPaths) {
|
|
6027
6454
|
try {
|
|
6028
|
-
const logContent = await
|
|
6455
|
+
const logContent = await fs22.readFile(logPath, "utf-8");
|
|
6029
6456
|
const details = this.parseLogContent(logContent, result.jobId);
|
|
6030
6457
|
allDetails.push(...details);
|
|
6031
6458
|
} catch (_error) {
|
|
@@ -6064,9 +6491,9 @@ ${chalk2.bold(SEPARATOR)}`);
|
|
|
6064
6491
|
}
|
|
6065
6492
|
|
|
6066
6493
|
// src/output/console-log.ts
|
|
6067
|
-
import
|
|
6494
|
+
import fs23 from "node:fs";
|
|
6068
6495
|
import fsPromises2 from "node:fs/promises";
|
|
6069
|
-
import
|
|
6496
|
+
import path20 from "node:path";
|
|
6070
6497
|
import { inspect } from "node:util";
|
|
6071
6498
|
var ANSI_REGEX = /\x1b(?:\[[0-9;?]*[A-Za-z]|[78])/g;
|
|
6072
6499
|
function stripAnsi(text) {
|
|
@@ -6076,9 +6503,9 @@ function formatArgs(args) {
|
|
|
6076
6503
|
return args.map((a) => typeof a === "string" ? a : inspect(a, { depth: 4 })).join(" ");
|
|
6077
6504
|
}
|
|
6078
6505
|
function openLogFileExclusive(logDir, runNum) {
|
|
6079
|
-
const logPath =
|
|
6506
|
+
const logPath = path20.join(logDir, `console.${runNum}.log`);
|
|
6080
6507
|
try {
|
|
6081
|
-
const fd =
|
|
6508
|
+
const fd = fs23.openSync(logPath, fs23.constants.O_WRONLY | fs23.constants.O_CREAT | fs23.constants.O_EXCL);
|
|
6082
6509
|
return { fd, logPath };
|
|
6083
6510
|
} catch (e) {
|
|
6084
6511
|
const error = e;
|
|
@@ -6092,9 +6519,9 @@ function openLogFileExclusive(logDir, runNum) {
|
|
|
6092
6519
|
function openLogFileFallback(logDir, startNum) {
|
|
6093
6520
|
let runNum = startNum;
|
|
6094
6521
|
for (let attempts = 0;attempts < 100; attempts++) {
|
|
6095
|
-
const logPath =
|
|
6522
|
+
const logPath = path20.join(logDir, `console.${runNum}.log`);
|
|
6096
6523
|
try {
|
|
6097
|
-
const fd =
|
|
6524
|
+
const fd = fs23.openSync(logPath, fs23.constants.O_WRONLY | fs23.constants.O_CREAT | fs23.constants.O_EXCL);
|
|
6098
6525
|
return { fd, logPath };
|
|
6099
6526
|
} catch (e) {
|
|
6100
6527
|
const error = e;
|
|
@@ -6115,7 +6542,7 @@ async function startConsoleLog(logDir, runNumber) {
|
|
|
6115
6542
|
if (isClosed)
|
|
6116
6543
|
return;
|
|
6117
6544
|
try {
|
|
6118
|
-
|
|
6545
|
+
fs23.writeSync(fd, stripAnsi(text));
|
|
6119
6546
|
} catch {}
|
|
6120
6547
|
};
|
|
6121
6548
|
const originalLog = console.log;
|
|
@@ -6163,7 +6590,7 @@ async function startConsoleLog(logDir, runNumber) {
|
|
|
6163
6590
|
process.stdout.write = originalStdoutWrite;
|
|
6164
6591
|
process.stderr.write = originalStderrWrite;
|
|
6165
6592
|
try {
|
|
6166
|
-
|
|
6593
|
+
fs23.closeSync(fd);
|
|
6167
6594
|
} catch {}
|
|
6168
6595
|
},
|
|
6169
6596
|
writeToLogOnly: (text) => {
|
|
@@ -6171,20 +6598,20 @@ async function startConsoleLog(logDir, runNumber) {
|
|
|
6171
6598
|
}
|
|
6172
6599
|
};
|
|
6173
6600
|
} catch (error) {
|
|
6174
|
-
|
|
6601
|
+
fs23.closeSync(fd);
|
|
6175
6602
|
throw error;
|
|
6176
6603
|
}
|
|
6177
6604
|
}
|
|
6178
6605
|
|
|
6179
6606
|
// src/output/logger.ts
|
|
6180
|
-
import
|
|
6181
|
-
import
|
|
6607
|
+
import fs24 from "node:fs/promises";
|
|
6608
|
+
import path21 from "node:path";
|
|
6182
6609
|
function formatTimestamp() {
|
|
6183
6610
|
return new Date().toISOString();
|
|
6184
6611
|
}
|
|
6185
6612
|
async function computeGlobalRunNumber(logDir) {
|
|
6186
6613
|
try {
|
|
6187
|
-
const files = await
|
|
6614
|
+
const files = await fs24.readdir(logDir);
|
|
6188
6615
|
let max = 0;
|
|
6189
6616
|
for (const file of files) {
|
|
6190
6617
|
if (!(file.endsWith(".log") || file.endsWith(".json")))
|
|
@@ -6210,7 +6637,7 @@ class Logger {
|
|
|
6210
6637
|
this.logDir = logDir;
|
|
6211
6638
|
}
|
|
6212
6639
|
async init() {
|
|
6213
|
-
await
|
|
6640
|
+
await fs24.mkdir(this.logDir, { recursive: true });
|
|
6214
6641
|
this.globalRunNumber = await computeGlobalRunNumber(this.logDir);
|
|
6215
6642
|
}
|
|
6216
6643
|
async close() {}
|
|
@@ -6228,14 +6655,14 @@ class Logger {
|
|
|
6228
6655
|
} else {
|
|
6229
6656
|
filename = `${safeName}.${runNum}.log`;
|
|
6230
6657
|
}
|
|
6231
|
-
return
|
|
6658
|
+
return path21.join(this.logDir, filename);
|
|
6232
6659
|
}
|
|
6233
6660
|
async initFile(logPath) {
|
|
6234
6661
|
if (this.initializedFiles.has(logPath)) {
|
|
6235
6662
|
return;
|
|
6236
6663
|
}
|
|
6237
6664
|
this.initializedFiles.add(logPath);
|
|
6238
|
-
await
|
|
6665
|
+
await fs24.writeFile(logPath, "");
|
|
6239
6666
|
}
|
|
6240
6667
|
async createJobLogger(jobId) {
|
|
6241
6668
|
const logPath = await this.getLogPath(jobId);
|
|
@@ -6247,7 +6674,7 @@ class Logger {
|
|
|
6247
6674
|
if (lines.length > 0) {
|
|
6248
6675
|
lines[0] = `[${timestamp}] ${lines[0]}`;
|
|
6249
6676
|
}
|
|
6250
|
-
await
|
|
6677
|
+
await fs24.appendFile(logPath, lines.join(`
|
|
6251
6678
|
`) + (text.endsWith(`
|
|
6252
6679
|
`) ? "" : `
|
|
6253
6680
|
`));
|
|
@@ -6264,7 +6691,7 @@ class Logger {
|
|
|
6264
6691
|
if (lines.length > 0) {
|
|
6265
6692
|
lines[0] = `[${timestamp}] ${lines[0]}`;
|
|
6266
6693
|
}
|
|
6267
|
-
await
|
|
6694
|
+
await fs24.appendFile(logPath, lines.join(`
|
|
6268
6695
|
`) + (text.endsWith(`
|
|
6269
6696
|
`) ? "" : `
|
|
6270
6697
|
`));
|
|
@@ -6280,8 +6707,8 @@ function resolveBaseBranch(options, config) {
|
|
|
6280
6707
|
}
|
|
6281
6708
|
|
|
6282
6709
|
// src/commands/shared.ts
|
|
6283
|
-
import
|
|
6284
|
-
import
|
|
6710
|
+
import fs25 from "node:fs/promises";
|
|
6711
|
+
import path22 from "node:path";
|
|
6285
6712
|
var LOCK_FILENAME = ".gauntlet-run.lock";
|
|
6286
6713
|
var SESSION_REF_FILENAME2 = ".session_ref";
|
|
6287
6714
|
async function shouldAutoClean(logDir, baseBranch) {
|
|
@@ -6316,17 +6743,17 @@ async function performAutoClean(logDir, result, maxPreviousLogs = 3) {
|
|
|
6316
6743
|
}
|
|
6317
6744
|
async function exists(filePath) {
|
|
6318
6745
|
try {
|
|
6319
|
-
await
|
|
6746
|
+
await fs25.stat(filePath);
|
|
6320
6747
|
return true;
|
|
6321
6748
|
} catch {
|
|
6322
6749
|
return false;
|
|
6323
6750
|
}
|
|
6324
6751
|
}
|
|
6325
6752
|
async function acquireLock(logDir) {
|
|
6326
|
-
await
|
|
6327
|
-
const lockPath =
|
|
6753
|
+
await fs25.mkdir(logDir, { recursive: true });
|
|
6754
|
+
const lockPath = path22.resolve(logDir, LOCK_FILENAME);
|
|
6328
6755
|
try {
|
|
6329
|
-
await
|
|
6756
|
+
await fs25.writeFile(lockPath, String(process.pid), { flag: "wx" });
|
|
6330
6757
|
} catch (err) {
|
|
6331
6758
|
if (typeof err === "object" && err !== null && "code" in err && err.code === "EEXIST") {
|
|
6332
6759
|
console.error(`Error: A gauntlet run is already in progress (lock file: ${lockPath}).`);
|
|
@@ -6337,14 +6764,14 @@ async function acquireLock(logDir) {
|
|
|
6337
6764
|
}
|
|
6338
6765
|
}
|
|
6339
6766
|
async function releaseLock(logDir) {
|
|
6340
|
-
const lockPath =
|
|
6767
|
+
const lockPath = path22.resolve(logDir, LOCK_FILENAME);
|
|
6341
6768
|
try {
|
|
6342
|
-
await
|
|
6769
|
+
await fs25.rm(lockPath, { force: true });
|
|
6343
6770
|
} catch {}
|
|
6344
6771
|
}
|
|
6345
6772
|
async function hasExistingLogs(logDir) {
|
|
6346
6773
|
try {
|
|
6347
|
-
const entries = await
|
|
6774
|
+
const entries = await fs25.readdir(logDir);
|
|
6348
6775
|
return entries.some((f) => (f.endsWith(".log") || f.endsWith(".json")) && f !== "previous" && !f.startsWith("console.") && !f.startsWith("."));
|
|
6349
6776
|
} catch {
|
|
6350
6777
|
return false;
|
|
@@ -6363,7 +6790,7 @@ function getPersistentFiles() {
|
|
|
6363
6790
|
}
|
|
6364
6791
|
async function hasCurrentLogs(logDir) {
|
|
6365
6792
|
try {
|
|
6366
|
-
const files = await
|
|
6793
|
+
const files = await fs25.readdir(logDir);
|
|
6367
6794
|
const persistentFiles = getPersistentFiles();
|
|
6368
6795
|
return files.some((f) => (f.endsWith(".log") || f.endsWith(".json")) && f !== "previous" && !persistentFiles.has(f));
|
|
6369
6796
|
} catch {
|
|
@@ -6375,23 +6802,23 @@ function getCurrentLogFiles(files) {
|
|
|
6375
6802
|
return files.filter((file) => !(file.startsWith("previous") || persistentFiles.has(file)));
|
|
6376
6803
|
}
|
|
6377
6804
|
async function deleteCurrentLogs(logDir) {
|
|
6378
|
-
const files = await
|
|
6379
|
-
await Promise.all(getCurrentLogFiles(files).map((file) =>
|
|
6805
|
+
const files = await fs25.readdir(logDir);
|
|
6806
|
+
await Promise.all(getCurrentLogFiles(files).map((file) => fs25.rm(path22.join(logDir, file), { recursive: true, force: true })));
|
|
6380
6807
|
}
|
|
6381
6808
|
async function rotatePreviousDirs(logDir, maxPreviousLogs) {
|
|
6382
6809
|
const oldestSuffix = maxPreviousLogs - 1;
|
|
6383
6810
|
const oldestDir = oldestSuffix === 0 ? "previous" : `previous.${oldestSuffix}`;
|
|
6384
|
-
const oldestPath =
|
|
6811
|
+
const oldestPath = path22.join(logDir, oldestDir);
|
|
6385
6812
|
if (await exists(oldestPath)) {
|
|
6386
|
-
await
|
|
6813
|
+
await fs25.rm(oldestPath, { recursive: true, force: true });
|
|
6387
6814
|
}
|
|
6388
6815
|
for (let i = oldestSuffix - 1;i >= 0; i--) {
|
|
6389
6816
|
const fromName = i === 0 ? "previous" : `previous.${i}`;
|
|
6390
6817
|
const toName = `previous.${i + 1}`;
|
|
6391
|
-
const fromPath =
|
|
6392
|
-
const toPath =
|
|
6818
|
+
const fromPath = path22.join(logDir, fromName);
|
|
6819
|
+
const toPath = path22.join(logDir, toName);
|
|
6393
6820
|
if (await exists(fromPath)) {
|
|
6394
|
-
await
|
|
6821
|
+
await fs25.rename(fromPath, toPath);
|
|
6395
6822
|
}
|
|
6396
6823
|
}
|
|
6397
6824
|
}
|
|
@@ -6406,12 +6833,12 @@ async function cleanLogs(logDir, maxPreviousLogs = 3) {
|
|
|
6406
6833
|
return;
|
|
6407
6834
|
}
|
|
6408
6835
|
await rotatePreviousDirs(logDir, maxPreviousLogs);
|
|
6409
|
-
const previousDir =
|
|
6410
|
-
await
|
|
6411
|
-
const files = await
|
|
6412
|
-
await Promise.all(getCurrentLogFiles(files).map((file) =>
|
|
6836
|
+
const previousDir = path22.join(logDir, "previous");
|
|
6837
|
+
await fs25.mkdir(previousDir, { recursive: true });
|
|
6838
|
+
const files = await fs25.readdir(logDir);
|
|
6839
|
+
await Promise.all(getCurrentLogFiles(files).map((file) => fs25.rename(path22.join(logDir, file), path22.join(previousDir, file))));
|
|
6413
6840
|
try {
|
|
6414
|
-
await
|
|
6841
|
+
await fs25.rm(path22.join(logDir, SESSION_REF_FILENAME2), { force: true });
|
|
6415
6842
|
} catch {}
|
|
6416
6843
|
} catch (error) {
|
|
6417
6844
|
console.warn("Failed to clean logs in", logDir, ":", error instanceof Error ? error.message : error);
|
|
@@ -6511,7 +6938,7 @@ async function detectChangesAndGenerateJobs(config, effectiveBaseBranch, changeO
|
|
|
6511
6938
|
uncommitted: options.uncommitted
|
|
6512
6939
|
});
|
|
6513
6940
|
const expander = new EntryPointExpander;
|
|
6514
|
-
const jobGen = new JobGenerator(config);
|
|
6941
|
+
const jobGen = new JobGenerator(config, options.enableReviews);
|
|
6515
6942
|
console.log(chalk3.dim("Detecting changes..."));
|
|
6516
6943
|
const changes = await changeDetector.getChangedFiles();
|
|
6517
6944
|
if (changes.length === 0) {
|
|
@@ -6620,14 +7047,14 @@ function registerCheckCommand(program) {
|
|
|
6620
7047
|
program.command("check").description("Run only applicable checks for detected changes").option("-b, --base-branch <branch>", "Override base branch for change detection").option("-g, --gate <name>", "Run specific check gate only").option("-c, --commit <sha>", "Use diff for a specific commit").option("-u, --uncommitted", "Use diff for current uncommitted changes (staged and unstaged)").action((options) => executeGateCommand("check", options));
|
|
6621
7048
|
}
|
|
6622
7049
|
// src/commands/ci/init.ts
|
|
6623
|
-
import
|
|
6624
|
-
import
|
|
7050
|
+
import fs27 from "node:fs/promises";
|
|
7051
|
+
import path24 from "node:path";
|
|
6625
7052
|
import chalk4 from "chalk";
|
|
6626
7053
|
import YAML5 from "yaml";
|
|
6627
7054
|
|
|
6628
7055
|
// src/config/ci-loader.ts
|
|
6629
|
-
import
|
|
6630
|
-
import
|
|
7056
|
+
import fs26 from "node:fs/promises";
|
|
7057
|
+
import path23 from "node:path";
|
|
6631
7058
|
import YAML4 from "yaml";
|
|
6632
7059
|
|
|
6633
7060
|
// src/config/ci-schema.ts
|
|
@@ -6657,17 +7084,17 @@ var ciConfigSchema = z3.object({
|
|
|
6657
7084
|
var GAUNTLET_DIR2 = ".gauntlet";
|
|
6658
7085
|
var CI_FILE = "ci.yml";
|
|
6659
7086
|
async function loadCIConfig(rootDir = process.cwd()) {
|
|
6660
|
-
const ciPath =
|
|
7087
|
+
const ciPath = path23.join(rootDir, GAUNTLET_DIR2, CI_FILE);
|
|
6661
7088
|
if (!await fileExists3(ciPath)) {
|
|
6662
7089
|
throw new Error(`CI configuration file not found at ${ciPath}. Run 'agent-gauntlet ci init' to create it.`);
|
|
6663
7090
|
}
|
|
6664
|
-
const content = await
|
|
7091
|
+
const content = await fs26.readFile(ciPath, "utf-8");
|
|
6665
7092
|
const raw = YAML4.parse(content);
|
|
6666
7093
|
return ciConfigSchema.parse(raw);
|
|
6667
7094
|
}
|
|
6668
|
-
async function fileExists3(
|
|
7095
|
+
async function fileExists3(path24) {
|
|
6669
7096
|
try {
|
|
6670
|
-
const stat = await
|
|
7097
|
+
const stat = await fs26.stat(path24);
|
|
6671
7098
|
return stat.isFile();
|
|
6672
7099
|
} catch {
|
|
6673
7100
|
return false;
|
|
@@ -6758,15 +7185,15 @@ jobs:
|
|
|
6758
7185
|
|
|
6759
7186
|
// src/commands/ci/init.ts
|
|
6760
7187
|
async function initCI() {
|
|
6761
|
-
const workflowDir =
|
|
6762
|
-
const workflowPath =
|
|
6763
|
-
const gauntletDir =
|
|
6764
|
-
const ciConfigPath =
|
|
7188
|
+
const workflowDir = path24.join(process.cwd(), ".github", "workflows");
|
|
7189
|
+
const workflowPath = path24.join(workflowDir, "gauntlet.yml");
|
|
7190
|
+
const gauntletDir = path24.join(process.cwd(), ".gauntlet");
|
|
7191
|
+
const ciConfigPath = path24.join(gauntletDir, "ci.yml");
|
|
6765
7192
|
if (await fileExists4(ciConfigPath)) {
|
|
6766
7193
|
console.log(chalk4.dim("Found existing .gauntlet/ci.yml"));
|
|
6767
7194
|
} else {
|
|
6768
7195
|
console.log(chalk4.yellow("Creating starter .gauntlet/ci.yml..."));
|
|
6769
|
-
await
|
|
7196
|
+
await fs27.mkdir(gauntletDir, { recursive: true });
|
|
6770
7197
|
const starterContent = `# CI Configuration for Agent Gauntlet
|
|
6771
7198
|
# Define runtimes, services, and which checks to run in CI.
|
|
6772
7199
|
|
|
@@ -6788,7 +7215,7 @@ checks:
|
|
|
6788
7215
|
# - name: linter
|
|
6789
7216
|
# requires_runtimes: [ruby]
|
|
6790
7217
|
`;
|
|
6791
|
-
await
|
|
7218
|
+
await fs27.writeFile(ciConfigPath, starterContent);
|
|
6792
7219
|
}
|
|
6793
7220
|
let ciConfig;
|
|
6794
7221
|
try {
|
|
@@ -6797,7 +7224,7 @@ checks:
|
|
|
6797
7224
|
console.warn(chalk4.yellow("Could not load CI config to inject services. Workflow will have no services defined."));
|
|
6798
7225
|
}
|
|
6799
7226
|
console.log(chalk4.dim(`Generating ${workflowPath}...`));
|
|
6800
|
-
await
|
|
7227
|
+
await fs27.mkdir(workflowDir, { recursive: true });
|
|
6801
7228
|
let templateContent = workflow_default;
|
|
6802
7229
|
if (ciConfig?.services && Object.keys(ciConfig.services).length > 0) {
|
|
6803
7230
|
const servicesYaml = YAML5.stringify({ services: ciConfig.services });
|
|
@@ -6809,12 +7236,12 @@ checks:
|
|
|
6809
7236
|
templateContent = templateContent.replace(` # Services will be injected here by agent-gauntlet
|
|
6810
7237
|
`, "");
|
|
6811
7238
|
}
|
|
6812
|
-
await
|
|
7239
|
+
await fs27.writeFile(workflowPath, templateContent);
|
|
6813
7240
|
console.log(chalk4.green("Successfully generated GitHub Actions workflow!"));
|
|
6814
7241
|
}
|
|
6815
|
-
async function fileExists4(
|
|
7242
|
+
async function fileExists4(path25) {
|
|
6816
7243
|
try {
|
|
6817
|
-
const stat = await
|
|
7244
|
+
const stat = await fs27.stat(path25);
|
|
6818
7245
|
return stat.isFile();
|
|
6819
7246
|
} catch {
|
|
6820
7247
|
return false;
|
|
@@ -7024,18 +7451,18 @@ function printJobsByWorkDir(jobs) {
|
|
|
7024
7451
|
}
|
|
7025
7452
|
}
|
|
7026
7453
|
// src/commands/health.ts
|
|
7027
|
-
import
|
|
7454
|
+
import path27 from "node:path";
|
|
7028
7455
|
import chalk7 from "chalk";
|
|
7029
7456
|
|
|
7030
7457
|
// src/config/validator.ts
|
|
7031
|
-
import
|
|
7032
|
-
import
|
|
7458
|
+
import fs29 from "node:fs/promises";
|
|
7459
|
+
import path26 from "node:path";
|
|
7033
7460
|
import YAML7 from "yaml";
|
|
7034
7461
|
import { ZodError as ZodError2 } from "zod";
|
|
7035
7462
|
|
|
7036
7463
|
// src/config/validate-reviews.ts
|
|
7037
|
-
import
|
|
7038
|
-
import
|
|
7464
|
+
import fs28 from "node:fs/promises";
|
|
7465
|
+
import path25 from "node:path";
|
|
7039
7466
|
import matter2 from "gray-matter";
|
|
7040
7467
|
import YAML6 from "yaml";
|
|
7041
7468
|
import { ZodError } from "zod";
|
|
@@ -7044,7 +7471,7 @@ async function validateReviewGates(reviewsPath, issues, filesChecked) {
|
|
|
7044
7471
|
const reviewSourceFiles = {};
|
|
7045
7472
|
const existingReviewNames = new Set;
|
|
7046
7473
|
try {
|
|
7047
|
-
const reviewFiles = await
|
|
7474
|
+
const reviewFiles = await fs28.readdir(reviewsPath);
|
|
7048
7475
|
detectDuplicateReviewNames2(reviewFiles, reviewsPath, issues);
|
|
7049
7476
|
for (const file of reviewFiles) {
|
|
7050
7477
|
if (file.endsWith(".md")) {
|
|
@@ -7067,7 +7494,7 @@ function detectDuplicateReviewNames2(reviewFiles, reviewsPath, issues) {
|
|
|
7067
7494
|
const reviewNameSources = new Map;
|
|
7068
7495
|
for (const file of reviewFiles) {
|
|
7069
7496
|
if (file.endsWith(".md") || file.endsWith(".yml") || file.endsWith(".yaml")) {
|
|
7070
|
-
const name =
|
|
7497
|
+
const name = path25.basename(file, path25.extname(file));
|
|
7071
7498
|
const sources = reviewNameSources.get(name) || [];
|
|
7072
7499
|
sources.push(file);
|
|
7073
7500
|
reviewNameSources.set(name, sources);
|
|
@@ -7084,12 +7511,12 @@ function detectDuplicateReviewNames2(reviewFiles, reviewsPath, issues) {
|
|
|
7084
7511
|
}
|
|
7085
7512
|
}
|
|
7086
7513
|
async function validateMarkdownReview(file, reviewsPath, reviews, reviewSourceFiles, existingReviewNames, issues, filesChecked) {
|
|
7087
|
-
const filePath =
|
|
7088
|
-
const reviewName =
|
|
7514
|
+
const filePath = path25.join(reviewsPath, file);
|
|
7515
|
+
const reviewName = path25.basename(file, ".md");
|
|
7089
7516
|
existingReviewNames.add(reviewName);
|
|
7090
7517
|
filesChecked.push(filePath);
|
|
7091
7518
|
try {
|
|
7092
|
-
const content = await
|
|
7519
|
+
const content = await fs28.readFile(filePath, "utf-8");
|
|
7093
7520
|
const { data: frontmatter, content: _promptBody } = matter2(content);
|
|
7094
7521
|
if (!frontmatter || Object.keys(frontmatter).length === 0) {
|
|
7095
7522
|
issues.push({
|
|
@@ -7101,7 +7528,7 @@ async function validateMarkdownReview(file, reviewsPath, reviews, reviewSourceFi
|
|
|
7101
7528
|
}
|
|
7102
7529
|
validateCliPreferenceTools(frontmatter, filePath, issues);
|
|
7103
7530
|
const parsedFrontmatter = reviewPromptFrontmatterSchema.parse(frontmatter);
|
|
7104
|
-
const name =
|
|
7531
|
+
const name = path25.basename(file, ".md");
|
|
7105
7532
|
reviews[name] = parsedFrontmatter;
|
|
7106
7533
|
reviewSourceFiles[name] = filePath;
|
|
7107
7534
|
validateReviewSemantics(parsedFrontmatter, filePath, issues);
|
|
@@ -7110,12 +7537,12 @@ async function validateMarkdownReview(file, reviewsPath, reviews, reviewSourceFi
|
|
|
7110
7537
|
}
|
|
7111
7538
|
}
|
|
7112
7539
|
async function validateYamlReview(file, reviewsPath, reviews, reviewSourceFiles, existingReviewNames, issues, filesChecked) {
|
|
7113
|
-
const filePath =
|
|
7114
|
-
const reviewName =
|
|
7540
|
+
const filePath = path25.join(reviewsPath, file);
|
|
7541
|
+
const reviewName = path25.basename(file, path25.extname(file));
|
|
7115
7542
|
existingReviewNames.add(reviewName);
|
|
7116
7543
|
filesChecked.push(filePath);
|
|
7117
7544
|
try {
|
|
7118
|
-
const content = await
|
|
7545
|
+
const content = await fs28.readFile(filePath, "utf-8");
|
|
7119
7546
|
const raw = YAML6.parse(content);
|
|
7120
7547
|
validateCliPreferenceTools(raw, filePath, issues);
|
|
7121
7548
|
const parsed = reviewYamlSchema.parse(raw);
|
|
@@ -7248,8 +7675,8 @@ var CONFIG_FILE2 = "config.yml";
|
|
|
7248
7675
|
var CHECKS_DIR2 = "checks";
|
|
7249
7676
|
var REVIEWS_DIR2 = "reviews";
|
|
7250
7677
|
async function validateConfig(rootDir = process.cwd()) {
|
|
7251
|
-
const gauntletPath =
|
|
7252
|
-
const configPath =
|
|
7678
|
+
const gauntletPath = path26.join(rootDir, GAUNTLET_DIR3);
|
|
7679
|
+
const configPath = path26.join(gauntletPath, CONFIG_FILE2);
|
|
7253
7680
|
const ctx = {
|
|
7254
7681
|
gauntletPath,
|
|
7255
7682
|
configPath,
|
|
@@ -7273,7 +7700,7 @@ async function validateProjectConfig(ctx) {
|
|
|
7273
7700
|
try {
|
|
7274
7701
|
if (await fileExists5(ctx.configPath)) {
|
|
7275
7702
|
ctx.filesChecked.push(ctx.configPath);
|
|
7276
|
-
const configContent = await
|
|
7703
|
+
const configContent = await fs29.readFile(ctx.configPath, "utf-8");
|
|
7277
7704
|
return parseProjectConfig(configContent, ctx);
|
|
7278
7705
|
}
|
|
7279
7706
|
ctx.issues.push({
|
|
@@ -7315,12 +7742,12 @@ function parseProjectConfig(configContent, ctx) {
|
|
|
7315
7742
|
async function validateCheckGates(ctx) {
|
|
7316
7743
|
const checks = {};
|
|
7317
7744
|
const existingCheckNames = new Set;
|
|
7318
|
-
const checksPath =
|
|
7745
|
+
const checksPath = path26.join(ctx.gauntletPath, CHECKS_DIR2);
|
|
7319
7746
|
if (!await dirExists2(checksPath)) {
|
|
7320
7747
|
return { checks, existingCheckNames };
|
|
7321
7748
|
}
|
|
7322
7749
|
try {
|
|
7323
|
-
const checkFiles = await
|
|
7750
|
+
const checkFiles = await fs29.readdir(checksPath);
|
|
7324
7751
|
for (const file of checkFiles) {
|
|
7325
7752
|
if (file.endsWith(".yml") || file.endsWith(".yaml")) {
|
|
7326
7753
|
await parseCheckFile(file, checksPath, checks, existingCheckNames, ctx);
|
|
@@ -7337,11 +7764,11 @@ async function validateCheckGates(ctx) {
|
|
|
7337
7764
|
return { checks, existingCheckNames };
|
|
7338
7765
|
}
|
|
7339
7766
|
async function parseCheckFile(file, checksPath, checks, existingCheckNames, ctx) {
|
|
7340
|
-
const filePath =
|
|
7767
|
+
const filePath = path26.join(checksPath, file);
|
|
7341
7768
|
ctx.filesChecked.push(filePath);
|
|
7342
|
-
const name =
|
|
7769
|
+
const name = path26.basename(file, path26.extname(file));
|
|
7343
7770
|
try {
|
|
7344
|
-
const content = await
|
|
7771
|
+
const content = await fs29.readFile(filePath, "utf-8");
|
|
7345
7772
|
const raw = YAML7.parse(content);
|
|
7346
7773
|
const parsed = checkGateSchema.parse(raw);
|
|
7347
7774
|
existingCheckNames.add(name);
|
|
@@ -7371,7 +7798,7 @@ async function parseCheckFile(file, checksPath, checks, existingCheckNames, ctx)
|
|
|
7371
7798
|
}
|
|
7372
7799
|
}
|
|
7373
7800
|
async function validateReviewGatesWrapper(ctx) {
|
|
7374
|
-
const reviewsPath =
|
|
7801
|
+
const reviewsPath = path26.join(ctx.gauntletPath, REVIEWS_DIR2);
|
|
7375
7802
|
if (!await dirExists2(reviewsPath)) {
|
|
7376
7803
|
return {
|
|
7377
7804
|
reviews: {},
|
|
@@ -7515,12 +7942,12 @@ function validateDefaultPreferenceTools(defaults, ctx) {
|
|
|
7515
7942
|
}
|
|
7516
7943
|
}
|
|
7517
7944
|
function validateReviewPreferencesAgainstDefaults(defaults, reviews, reviewSourceFiles, ctx) {
|
|
7518
|
-
const reviewsPath =
|
|
7945
|
+
const reviewsPath = path26.join(ctx.gauntletPath, REVIEWS_DIR2);
|
|
7519
7946
|
const allowedTools = new Set(defaults);
|
|
7520
7947
|
for (const [reviewName, reviewConfig] of Object.entries(reviews)) {
|
|
7521
7948
|
const pref = reviewConfig.cli_preference;
|
|
7522
7949
|
if (pref && Array.isArray(pref)) {
|
|
7523
|
-
const reviewFile = reviewSourceFiles[reviewName] ||
|
|
7950
|
+
const reviewFile = reviewSourceFiles[reviewName] || path26.join(reviewsPath, `${reviewName}.md`);
|
|
7524
7951
|
for (let i = 0;i < pref.length; i++) {
|
|
7525
7952
|
const tool = pref[i];
|
|
7526
7953
|
if (!allowedTools.has(tool)) {
|
|
@@ -7553,7 +7980,7 @@ function pushYamlOrParseError(error, filePath, issues) {
|
|
|
7553
7980
|
}
|
|
7554
7981
|
async function fileExists5(filePath) {
|
|
7555
7982
|
try {
|
|
7556
|
-
const stat = await
|
|
7983
|
+
const stat = await fs29.stat(filePath);
|
|
7557
7984
|
return stat.isFile();
|
|
7558
7985
|
} catch {
|
|
7559
7986
|
return false;
|
|
@@ -7561,7 +7988,7 @@ async function fileExists5(filePath) {
|
|
|
7561
7988
|
}
|
|
7562
7989
|
async function dirExists2(dirPath) {
|
|
7563
7990
|
try {
|
|
7564
|
-
const stat = await
|
|
7991
|
+
const stat = await fs29.stat(dirPath);
|
|
7565
7992
|
return stat.isDirectory();
|
|
7566
7993
|
} catch {
|
|
7567
7994
|
return false;
|
|
@@ -7582,7 +8009,7 @@ function formatHealthResult(health) {
|
|
|
7582
8009
|
function displayValidationIssues(validationResult) {
|
|
7583
8010
|
const issuesByFile = new Map;
|
|
7584
8011
|
for (const issue of validationResult.issues) {
|
|
7585
|
-
const relativeFile =
|
|
8012
|
+
const relativeFile = path27.relative(process.cwd(), issue.file);
|
|
7586
8013
|
if (!issuesByFile.has(relativeFile)) {
|
|
7587
8014
|
issuesByFile.set(relativeFile, []);
|
|
7588
8015
|
}
|
|
@@ -7605,7 +8032,7 @@ async function validateAndDisplayConfig() {
|
|
|
7605
8032
|
return;
|
|
7606
8033
|
}
|
|
7607
8034
|
for (const file of validationResult.filesChecked) {
|
|
7608
|
-
const relativePath =
|
|
8035
|
+
const relativePath = path27.relative(process.cwd(), file);
|
|
7609
8036
|
console.log(chalk7.dim(` ${relativePath}`));
|
|
7610
8037
|
}
|
|
7611
8038
|
if (validationResult.valid && validationResult.issues.length === 0) {
|
|
@@ -7712,15 +8139,15 @@ function registerHelpCommand(program) {
|
|
|
7712
8139
|
// src/commands/init.ts
|
|
7713
8140
|
import { execFileSync } from "node:child_process";
|
|
7714
8141
|
import { statSync } from "node:fs";
|
|
7715
|
-
import
|
|
7716
|
-
import
|
|
8142
|
+
import fs32 from "node:fs/promises";
|
|
8143
|
+
import path30 from "node:path";
|
|
7717
8144
|
import { fileURLToPath } from "node:url";
|
|
7718
8145
|
import chalk11 from "chalk";
|
|
7719
8146
|
|
|
7720
8147
|
// src/commands/init-checksums.ts
|
|
7721
8148
|
import { createHash } from "node:crypto";
|
|
7722
|
-
import
|
|
7723
|
-
import
|
|
8149
|
+
import fs30 from "node:fs/promises";
|
|
8150
|
+
import path28 from "node:path";
|
|
7724
8151
|
async function computeSkillChecksum(skillDir) {
|
|
7725
8152
|
const files = await collectFiles(skillDir);
|
|
7726
8153
|
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
@@ -7753,22 +8180,22 @@ function isGauntletHookEntry(entry) {
|
|
|
7753
8180
|
async function collectFiles(dir, baseDir) {
|
|
7754
8181
|
const base = baseDir ?? dir;
|
|
7755
8182
|
const results = [];
|
|
7756
|
-
const entries = await
|
|
8183
|
+
const entries = await fs30.readdir(dir, { withFileTypes: true });
|
|
7757
8184
|
for (const entry of entries) {
|
|
7758
|
-
const fullPath =
|
|
8185
|
+
const fullPath = path28.join(dir, entry.name);
|
|
7759
8186
|
if (entry.isDirectory()) {
|
|
7760
8187
|
results.push(...await collectFiles(fullPath, base));
|
|
7761
8188
|
} else if (entry.isFile()) {
|
|
7762
|
-
const content = await
|
|
7763
|
-
results.push({ relativePath:
|
|
8189
|
+
const content = await fs30.readFile(fullPath, "utf-8");
|
|
8190
|
+
results.push({ relativePath: path28.relative(base, fullPath), content });
|
|
7764
8191
|
}
|
|
7765
8192
|
}
|
|
7766
8193
|
return results;
|
|
7767
8194
|
}
|
|
7768
8195
|
|
|
7769
8196
|
// src/commands/init-hooks.ts
|
|
7770
|
-
import
|
|
7771
|
-
import
|
|
8197
|
+
import fs31 from "node:fs/promises";
|
|
8198
|
+
import path29 from "node:path";
|
|
7772
8199
|
import chalk10 from "chalk";
|
|
7773
8200
|
|
|
7774
8201
|
// src/commands/init-prompts.ts
|
|
@@ -7846,11 +8273,11 @@ async function mergeHookConfig(opts) {
|
|
|
7846
8273
|
wrapInHooksArray,
|
|
7847
8274
|
baseConfig
|
|
7848
8275
|
} = opts;
|
|
7849
|
-
await
|
|
8276
|
+
await fs31.mkdir(path29.dirname(filePath), { recursive: true });
|
|
7850
8277
|
let existing = {};
|
|
7851
8278
|
if (await exists(filePath)) {
|
|
7852
8279
|
try {
|
|
7853
|
-
existing = JSON.parse(await
|
|
8280
|
+
existing = JSON.parse(await fs31.readFile(filePath, "utf-8"));
|
|
7854
8281
|
} catch {
|
|
7855
8282
|
existing = {};
|
|
7856
8283
|
}
|
|
@@ -7867,7 +8294,7 @@ async function mergeHookConfig(opts) {
|
|
|
7867
8294
|
...existing,
|
|
7868
8295
|
hooks: { ...existingHooks, [hookKey]: newEntries }
|
|
7869
8296
|
};
|
|
7870
|
-
await
|
|
8297
|
+
await fs31.writeFile(filePath, `${JSON.stringify(merged, null, 2)}
|
|
7871
8298
|
`);
|
|
7872
8299
|
return true;
|
|
7873
8300
|
}
|
|
@@ -7944,7 +8371,7 @@ function buildHookSpec(target) {
|
|
|
7944
8371
|
const purpose = isStop ? "gauntlet will run automatically when agent stops" : "agent will be primed with gauntlet instructions at session start";
|
|
7945
8372
|
return {
|
|
7946
8373
|
config: {
|
|
7947
|
-
filePath:
|
|
8374
|
+
filePath: path29.join(projectRoot, cfg.dir, cfg.file),
|
|
7948
8375
|
hookKey: cfg.hookKey,
|
|
7949
8376
|
hookEntry: cfg.entry,
|
|
7950
8377
|
deduplicateCmd: cfg.cmd,
|
|
@@ -7960,7 +8387,7 @@ async function installHookWithChecksums(target, skipPrompts) {
|
|
|
7960
8387
|
let existingConfig = {};
|
|
7961
8388
|
if (await exists(spec.config.filePath)) {
|
|
7962
8389
|
try {
|
|
7963
|
-
existingConfig = JSON.parse(await
|
|
8390
|
+
existingConfig = JSON.parse(await fs31.readFile(spec.config.filePath, "utf-8"));
|
|
7964
8391
|
} catch {
|
|
7965
8392
|
existingConfig = {};
|
|
7966
8393
|
}
|
|
@@ -7994,8 +8421,8 @@ async function installHookWithChecksums(target, skipPrompts) {
|
|
|
7994
8421
|
...existingConfig,
|
|
7995
8422
|
hooks: { ...existingHooks, [spec.config.hookKey]: newEntries }
|
|
7996
8423
|
};
|
|
7997
|
-
await
|
|
7998
|
-
await
|
|
8424
|
+
await fs31.mkdir(path29.dirname(spec.config.filePath), { recursive: true });
|
|
8425
|
+
await fs31.writeFile(spec.config.filePath, `${JSON.stringify(merged, null, 2)}
|
|
7999
8426
|
`);
|
|
8000
8427
|
console.log(chalk10.green(spec.installedMsg));
|
|
8001
8428
|
}
|
|
@@ -8013,10 +8440,10 @@ async function installHooksForAdapters(projectRoot, devAdapters, skipPrompts) {
|
|
|
8013
8440
|
}
|
|
8014
8441
|
|
|
8015
8442
|
// src/commands/init.ts
|
|
8016
|
-
var __dirname2 =
|
|
8443
|
+
var __dirname2 = path30.dirname(fileURLToPath(import.meta.url));
|
|
8017
8444
|
var SKILLS_SOURCE_DIR = (() => {
|
|
8018
|
-
const bundled =
|
|
8019
|
-
const dev =
|
|
8445
|
+
const bundled = path30.join(__dirname2, "..", "skills");
|
|
8446
|
+
const dev = path30.join(__dirname2, "..", "..", "skills");
|
|
8020
8447
|
try {
|
|
8021
8448
|
statSync(bundled);
|
|
8022
8449
|
return bundled;
|
|
@@ -8027,14 +8454,10 @@ var SKILLS_SOURCE_DIR = (() => {
|
|
|
8027
8454
|
throw err;
|
|
8028
8455
|
}
|
|
8029
8456
|
})();
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
status: "Show gauntlet status",
|
|
8035
|
-
help: "Diagnose and explain gauntlet behavior",
|
|
8036
|
-
setup: "Configure checks and reviews interactively"
|
|
8037
|
-
};
|
|
8457
|
+
async function getSkillDirNames() {
|
|
8458
|
+
const entries = await fs32.readdir(SKILLS_SOURCE_DIR, { withFileTypes: true });
|
|
8459
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
8460
|
+
}
|
|
8038
8461
|
var CLI_PREFERENCE_ORDER = [
|
|
8039
8462
|
"codex",
|
|
8040
8463
|
"claude",
|
|
@@ -8061,7 +8484,7 @@ function registerInitCommand(program) {
|
|
|
8061
8484
|
}
|
|
8062
8485
|
async function runInit(options) {
|
|
8063
8486
|
const projectRoot = process.cwd();
|
|
8064
|
-
const targetDir =
|
|
8487
|
+
const targetDir = path30.join(projectRoot, ".gauntlet");
|
|
8065
8488
|
const skipPrompts = options.yes ?? false;
|
|
8066
8489
|
console.log("Detecting available CLI agents...");
|
|
8067
8490
|
const availableAdapters = await detectAvailableCLIs();
|
|
@@ -8093,7 +8516,7 @@ async function runInit(options) {
|
|
|
8093
8516
|
}
|
|
8094
8517
|
await installExternalFiles(projectRoot, hookAdapters, skipPrompts);
|
|
8095
8518
|
await addToGitignore(projectRoot, "gauntlet_logs");
|
|
8096
|
-
printPostInitInstructions(instructionCLINames);
|
|
8519
|
+
await printPostInitInstructions(instructionCLINames);
|
|
8097
8520
|
}
|
|
8098
8521
|
function printNoCLIsMessage() {
|
|
8099
8522
|
console.log();
|
|
@@ -8108,35 +8531,34 @@ async function scaffoldGauntletDir(_projectRoot, targetDir, reviewCLINames, numR
|
|
|
8108
8531
|
console.log(chalk11.dim(".gauntlet/ already exists, skipping scaffolding"));
|
|
8109
8532
|
return;
|
|
8110
8533
|
}
|
|
8111
|
-
await
|
|
8112
|
-
await
|
|
8113
|
-
await
|
|
8534
|
+
await fs32.mkdir(targetDir);
|
|
8535
|
+
await fs32.mkdir(path30.join(targetDir, "checks"));
|
|
8536
|
+
await fs32.mkdir(path30.join(targetDir, "reviews"));
|
|
8114
8537
|
await writeConfigYml(targetDir, reviewCLINames);
|
|
8115
|
-
await
|
|
8538
|
+
await fs32.writeFile(path30.join(targetDir, "reviews", "code-quality.yml"), `builtin: code-quality
|
|
8116
8539
|
num_reviews: ${numReviews}
|
|
8117
8540
|
`);
|
|
8118
8541
|
console.log(chalk11.green("Created .gauntlet/reviews/code-quality.yml"));
|
|
8119
8542
|
}
|
|
8120
8543
|
async function copyDirRecursive(opts) {
|
|
8121
|
-
await
|
|
8122
|
-
const entries = await
|
|
8544
|
+
await fs32.mkdir(opts.dest, { recursive: true });
|
|
8545
|
+
const entries = await fs32.readdir(opts.src, { withFileTypes: true });
|
|
8123
8546
|
for (const entry of entries) {
|
|
8124
|
-
const srcPath =
|
|
8125
|
-
const destPath =
|
|
8547
|
+
const srcPath = path30.join(opts.src, entry.name);
|
|
8548
|
+
const destPath = path30.join(opts.dest, entry.name);
|
|
8126
8549
|
if (entry.isDirectory()) {
|
|
8127
8550
|
await copyDirRecursive({ src: srcPath, dest: destPath });
|
|
8128
8551
|
} else {
|
|
8129
|
-
await
|
|
8552
|
+
await fs32.copyFile(srcPath, destPath);
|
|
8130
8553
|
}
|
|
8131
8554
|
}
|
|
8132
8555
|
}
|
|
8133
8556
|
async function installSkillsWithChecksums(projectRoot, skipPrompts) {
|
|
8134
|
-
const skillsDir =
|
|
8135
|
-
for (const
|
|
8136
|
-
const
|
|
8137
|
-
const
|
|
8138
|
-
const
|
|
8139
|
-
const relativeDir = `${path29.relative(projectRoot, targetDir)}/`;
|
|
8557
|
+
const skillsDir = path30.join(projectRoot, ".claude", "skills");
|
|
8558
|
+
for (const dirName of await getSkillDirNames()) {
|
|
8559
|
+
const sourceDir = path30.join(SKILLS_SOURCE_DIR, dirName);
|
|
8560
|
+
const targetDir = path30.join(skillsDir, dirName);
|
|
8561
|
+
const relativeDir = `${path30.relative(projectRoot, targetDir)}/`;
|
|
8140
8562
|
if (!await exists(targetDir)) {
|
|
8141
8563
|
await copyDirRecursive({ src: sourceDir, dest: targetDir });
|
|
8142
8564
|
console.log(chalk11.green(`Created ${relativeDir}`));
|
|
@@ -8149,7 +8571,7 @@ async function installSkillsWithChecksums(projectRoot, skipPrompts) {
|
|
|
8149
8571
|
const shouldOverwrite = await promptFileOverwrite(dirName, skipPrompts);
|
|
8150
8572
|
if (!shouldOverwrite)
|
|
8151
8573
|
continue;
|
|
8152
|
-
await
|
|
8574
|
+
await fs32.rm(targetDir, { recursive: true, force: true });
|
|
8153
8575
|
await copyDirRecursive({ src: sourceDir, dest: targetDir });
|
|
8154
8576
|
console.log(chalk11.green(`Updated ${relativeDir}`));
|
|
8155
8577
|
}
|
|
@@ -8158,7 +8580,7 @@ async function installExternalFiles(projectRoot, devAdapters, skipPrompts) {
|
|
|
8158
8580
|
await installSkillsWithChecksums(projectRoot, skipPrompts);
|
|
8159
8581
|
await installHooksForAdapters(projectRoot, devAdapters, skipPrompts);
|
|
8160
8582
|
}
|
|
8161
|
-
function printPostInitInstructions(devCLINames) {
|
|
8583
|
+
async function printPostInitInstructions(devCLINames) {
|
|
8162
8584
|
const hasNative = devCLINames.some((name) => NATIVE_CLIS.has(name));
|
|
8163
8585
|
const nonNativeNames = devCLINames.filter((name) => !NATIVE_CLIS.has(name));
|
|
8164
8586
|
const hasNonNative = nonNativeNames.length > 0;
|
|
@@ -8170,8 +8592,8 @@ function printPostInitInstructions(devCLINames) {
|
|
|
8170
8592
|
console.log(chalk11.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."));
|
|
8171
8593
|
console.log();
|
|
8172
8594
|
console.log("Available skills:");
|
|
8173
|
-
for (const
|
|
8174
|
-
console.log(` @.claude/skills
|
|
8595
|
+
for (const dirName of await getSkillDirNames()) {
|
|
8596
|
+
console.log(` @.claude/skills/${dirName}/SKILL.md`);
|
|
8175
8597
|
}
|
|
8176
8598
|
}
|
|
8177
8599
|
}
|
|
@@ -8233,14 +8655,14 @@ entry_points: []
|
|
|
8233
8655
|
# enabled: true
|
|
8234
8656
|
# format: text # Options: text, json
|
|
8235
8657
|
`;
|
|
8236
|
-
await
|
|
8658
|
+
await fs32.writeFile(path30.join(targetDir, "config.yml"), content);
|
|
8237
8659
|
console.log(chalk11.green("Created .gauntlet/config.yml"));
|
|
8238
8660
|
}
|
|
8239
8661
|
async function addToGitignore(projectRoot, entry) {
|
|
8240
|
-
const gitignorePath =
|
|
8662
|
+
const gitignorePath = path30.join(projectRoot, ".gitignore");
|
|
8241
8663
|
let content = "";
|
|
8242
8664
|
if (await exists(gitignorePath)) {
|
|
8243
|
-
content = await
|
|
8665
|
+
content = await fs32.readFile(gitignorePath, "utf-8");
|
|
8244
8666
|
const lines = content.split(`
|
|
8245
8667
|
`).map((l) => l.trim());
|
|
8246
8668
|
if (lines.includes(entry))
|
|
@@ -8249,7 +8671,7 @@ async function addToGitignore(projectRoot, entry) {
|
|
|
8249
8671
|
const suffix = content.length > 0 && !content.endsWith(`
|
|
8250
8672
|
`) ? `
|
|
8251
8673
|
` : "";
|
|
8252
|
-
await
|
|
8674
|
+
await fs32.appendFile(gitignorePath, `${suffix}${entry}
|
|
8253
8675
|
`);
|
|
8254
8676
|
console.log(chalk11.green(`Added ${entry} to .gitignore`));
|
|
8255
8677
|
}
|
|
@@ -8298,348 +8720,411 @@ ${lines.join(`
|
|
|
8298
8720
|
`;
|
|
8299
8721
|
}
|
|
8300
8722
|
async function detectAvailableCLIs() {
|
|
8301
|
-
const allAdapters = [...getAllAdapters()].sort((a, b) => CLI_PREFERENCE_ORDER.indexOf(a.name) - CLI_PREFERENCE_ORDER.indexOf(b.name));
|
|
8302
|
-
const available = [];
|
|
8303
|
-
for (const adapter of allAdapters) {
|
|
8304
|
-
const isAvailable = await adapter.isAvailable();
|
|
8305
|
-
if (isAvailable) {
|
|
8306
|
-
console.log(chalk11.green(` ✓ ${adapter.name}`));
|
|
8307
|
-
available.push(adapter);
|
|
8308
|
-
} else {
|
|
8309
|
-
console.log(chalk11.dim(` ✗ ${adapter.name} (not installed)`));
|
|
8310
|
-
}
|
|
8311
|
-
}
|
|
8312
|
-
return available;
|
|
8313
|
-
}
|
|
8314
|
-
// src/commands/list.ts
|
|
8315
|
-
import chalk12 from "chalk";
|
|
8316
|
-
function registerListCommand(program) {
|
|
8317
|
-
program.command("list").description("List configured gates").action(async () => {
|
|
8318
|
-
try {
|
|
8319
|
-
const config = await loadConfig();
|
|
8320
|
-
console.log(chalk12.bold("Check Gates:"));
|
|
8321
|
-
for (const c of Object.values(config.checks)) {
|
|
8322
|
-
console.log(` - ${c.name}`);
|
|
8323
|
-
}
|
|
8324
|
-
console.log(chalk12.bold(`
|
|
8325
|
-
Review Gates:`));
|
|
8326
|
-
for (const r of Object.values(config.reviews)) {
|
|
8327
|
-
console.log(` - ${r.name} (Tools: ${r.cli_preference?.join(", ")})`);
|
|
8328
|
-
}
|
|
8329
|
-
console.log(chalk12.bold(`
|
|
8330
|
-
Entry Points:`));
|
|
8331
|
-
for (const ep of config.project.entry_points) {
|
|
8332
|
-
console.log(` - ${ep.path}`);
|
|
8333
|
-
if (ep.checks)
|
|
8334
|
-
console.log(` Checks: ${ep.checks.join(", ")}`);
|
|
8335
|
-
if (ep.reviews)
|
|
8336
|
-
console.log(` Reviews: ${ep.reviews.join(", ")}`);
|
|
8337
|
-
}
|
|
8338
|
-
} catch (error) {
|
|
8339
|
-
const err = error;
|
|
8340
|
-
console.error(chalk12.red("Error:"), err.message);
|
|
8341
|
-
}
|
|
8342
|
-
});
|
|
8343
|
-
}
|
|
8344
|
-
// src/commands/review.ts
|
|
8345
|
-
function registerReviewCommand(program) {
|
|
8346
|
-
program.command("review").description("Run only applicable reviews for detected changes").option("-b, --base-branch <branch>", "Override base branch for change detection").option("-g, --gate <name>", "Run specific review gate only").option("-c, --commit <sha>", "Use diff for a specific commit").option("-u, --uncommitted", "Use diff for current uncommitted changes (staged and unstaged)").action((options) => executeGateCommand("review", options));
|
|
8347
|
-
}
|
|
8348
|
-
// src/core/diff-stats.ts
|
|
8349
|
-
import { execFile } from "node:child_process";
|
|
8350
|
-
import { promisify as promisify9 } from "node:util";
|
|
8351
|
-
var execFileAsyncOriginal = promisify9(execFile);
|
|
8352
|
-
var execFileAsync = execFileAsyncOriginal;
|
|
8353
|
-
async function gitExec(args) {
|
|
8354
|
-
const { stdout } = await execFileAsync("git", args);
|
|
8355
|
-
return stdout;
|
|
8356
|
-
}
|
|
8357
|
-
async function computeDiffStats(baseBranch, options = {}) {
|
|
8358
|
-
if (options.commit) {
|
|
8359
|
-
return computeCommitDiffStats(options.commit);
|
|
8360
|
-
}
|
|
8361
|
-
if (options.fixBase) {
|
|
8362
|
-
return computeFixBaseDiffStats(options.fixBase);
|
|
8363
|
-
}
|
|
8364
|
-
if (options.uncommitted) {
|
|
8365
|
-
return computeUncommittedDiffStats();
|
|
8366
|
-
}
|
|
8367
|
-
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
8368
|
-
if (isCI) {
|
|
8369
|
-
return computeCIDiffStats(baseBranch);
|
|
8370
|
-
}
|
|
8371
|
-
return computeLocalDiffStats(baseBranch);
|
|
8372
|
-
}
|
|
8373
|
-
async function computeCommitDiffStats(commit) {
|
|
8374
|
-
try {
|
|
8375
|
-
const numstat = await gitExec([
|
|
8376
|
-
"diff",
|
|
8377
|
-
"--numstat",
|
|
8378
|
-
`${commit}^..${commit}`
|
|
8379
|
-
]);
|
|
8380
|
-
const lineStats = parseNumstat(numstat);
|
|
8381
|
-
const nameStatus = await gitExec([
|
|
8382
|
-
"diff",
|
|
8383
|
-
"--name-status",
|
|
8384
|
-
`${commit}^..${commit}`
|
|
8385
|
-
]);
|
|
8386
|
-
const fileStats = parseNameStatus(nameStatus);
|
|
8387
|
-
return {
|
|
8388
|
-
baseRef: `${commit}^`,
|
|
8389
|
-
...fileStats,
|
|
8390
|
-
...lineStats
|
|
8391
|
-
};
|
|
8392
|
-
} catch {
|
|
8393
|
-
try {
|
|
8394
|
-
const numstat = await gitExec(["diff", "--numstat", "--root", commit]);
|
|
8395
|
-
const lineStats = parseNumstat(numstat);
|
|
8396
|
-
const nameStatus = await gitExec([
|
|
8397
|
-
"diff",
|
|
8398
|
-
"--name-status",
|
|
8399
|
-
"--root",
|
|
8400
|
-
commit
|
|
8401
|
-
]);
|
|
8402
|
-
const fileStats = parseNameStatus(nameStatus);
|
|
8403
|
-
return {
|
|
8404
|
-
baseRef: "root",
|
|
8405
|
-
...fileStats,
|
|
8406
|
-
...lineStats
|
|
8407
|
-
};
|
|
8408
|
-
} catch {
|
|
8409
|
-
return emptyDiffStats(commit);
|
|
8410
|
-
}
|
|
8411
|
-
}
|
|
8412
|
-
}
|
|
8413
|
-
async function computeUncommittedDiffStats() {
|
|
8414
|
-
const stagedNumstat = await gitExec(["diff", "--numstat", "--cached"]);
|
|
8415
|
-
const stagedLines = parseNumstat(stagedNumstat);
|
|
8416
|
-
const stagedStatus = await gitExec(["diff", "--name-status", "--cached"]);
|
|
8417
|
-
const stagedFiles = parseNameStatus(stagedStatus);
|
|
8418
|
-
const unstagedNumstat = await gitExec(["diff", "--numstat"]);
|
|
8419
|
-
const unstagedLines = parseNumstat(unstagedNumstat);
|
|
8420
|
-
const unstagedStatus = await gitExec(["diff", "--name-status"]);
|
|
8421
|
-
const unstagedFiles = parseNameStatus(unstagedStatus);
|
|
8422
|
-
const untrackedList = await gitExec([
|
|
8423
|
-
"ls-files",
|
|
8424
|
-
"--others",
|
|
8425
|
-
"--exclude-standard"
|
|
8426
|
-
]);
|
|
8427
|
-
const untrackedFiles = untrackedList.split(`
|
|
8428
|
-
`).filter((f) => f.trim().length > 0);
|
|
8429
|
-
return {
|
|
8430
|
-
baseRef: "uncommitted",
|
|
8431
|
-
total: stagedFiles.total + unstagedFiles.total + untrackedFiles.length - countOverlap(stagedStatus, unstagedStatus),
|
|
8432
|
-
newFiles: stagedFiles.newFiles + unstagedFiles.newFiles + untrackedFiles.length,
|
|
8433
|
-
modifiedFiles: stagedFiles.modifiedFiles + unstagedFiles.modifiedFiles,
|
|
8434
|
-
deletedFiles: stagedFiles.deletedFiles + unstagedFiles.deletedFiles,
|
|
8435
|
-
linesAdded: stagedLines.linesAdded + unstagedLines.linesAdded,
|
|
8436
|
-
linesRemoved: stagedLines.linesRemoved + unstagedLines.linesRemoved
|
|
8437
|
-
};
|
|
8438
|
-
}
|
|
8439
|
-
async function computeFixBaseDiffStats(fixBase) {
|
|
8440
|
-
try {
|
|
8441
|
-
const numstat = await gitExec(["diff", "--numstat", fixBase]);
|
|
8442
|
-
const lineStats = parseNumstat(numstat);
|
|
8443
|
-
const nameStatus = await gitExec(["diff", "--name-status", fixBase]);
|
|
8444
|
-
const fileStats = parseNameStatus(nameStatus);
|
|
8445
|
-
const currentUntracked = (await gitExec(["ls-files", "--others", "--exclude-standard"])).split(`
|
|
8446
|
-
`).filter((f) => f.trim().length > 0);
|
|
8447
|
-
let fixBaseFiles;
|
|
8448
|
-
try {
|
|
8449
|
-
const treeFiles = await gitExec([
|
|
8450
|
-
"ls-tree",
|
|
8451
|
-
"-r",
|
|
8452
|
-
"--name-only",
|
|
8453
|
-
fixBase
|
|
8454
|
-
]);
|
|
8455
|
-
fixBaseFiles = new Set(treeFiles.split(`
|
|
8456
|
-
`).filter((f) => f.trim().length > 0));
|
|
8457
|
-
} catch {
|
|
8458
|
-
fixBaseFiles = new Set;
|
|
8723
|
+
const allAdapters = [...getAllAdapters()].sort((a, b) => CLI_PREFERENCE_ORDER.indexOf(a.name) - CLI_PREFERENCE_ORDER.indexOf(b.name));
|
|
8724
|
+
const available = [];
|
|
8725
|
+
for (const adapter of allAdapters) {
|
|
8726
|
+
const isAvailable = await adapter.isAvailable();
|
|
8727
|
+
if (isAvailable) {
|
|
8728
|
+
console.log(chalk11.green(` ✓ ${adapter.name}`));
|
|
8729
|
+
available.push(adapter);
|
|
8730
|
+
} else {
|
|
8731
|
+
console.log(chalk11.dim(` ✗ ${adapter.name} (not installed)`));
|
|
8459
8732
|
}
|
|
8460
|
-
const newUntrackedFiles = currentUntracked.filter((f) => !fixBaseFiles.has(f));
|
|
8461
|
-
return {
|
|
8462
|
-
baseRef: fixBase,
|
|
8463
|
-
total: fileStats.total + newUntrackedFiles.length,
|
|
8464
|
-
newFiles: fileStats.newFiles + newUntrackedFiles.length,
|
|
8465
|
-
modifiedFiles: fileStats.modifiedFiles,
|
|
8466
|
-
deletedFiles: fileStats.deletedFiles,
|
|
8467
|
-
linesAdded: lineStats.linesAdded,
|
|
8468
|
-
linesRemoved: lineStats.linesRemoved
|
|
8469
|
-
};
|
|
8470
|
-
} catch {
|
|
8471
|
-
return emptyDiffStats(fixBase);
|
|
8472
8733
|
}
|
|
8734
|
+
return available;
|
|
8473
8735
|
}
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
"diff",
|
|
8479
|
-
"--numstat",
|
|
8480
|
-
`${baseBranch}...${headRef}`
|
|
8481
|
-
]);
|
|
8482
|
-
const lineStats = parseNumstat(numstat);
|
|
8483
|
-
const nameStatus = await gitExec([
|
|
8484
|
-
"diff",
|
|
8485
|
-
"--name-status",
|
|
8486
|
-
`${baseBranch}...${headRef}`
|
|
8487
|
-
]);
|
|
8488
|
-
const fileStats = parseNameStatus(nameStatus);
|
|
8489
|
-
return {
|
|
8490
|
-
baseRef: baseBranch,
|
|
8491
|
-
...fileStats,
|
|
8492
|
-
...lineStats
|
|
8493
|
-
};
|
|
8494
|
-
} catch {
|
|
8736
|
+
// src/commands/list.ts
|
|
8737
|
+
import chalk12 from "chalk";
|
|
8738
|
+
function registerListCommand(program) {
|
|
8739
|
+
program.command("list").description("List configured gates").action(async () => {
|
|
8495
8740
|
try {
|
|
8496
|
-
const
|
|
8497
|
-
|
|
8498
|
-
const
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
const
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8741
|
+
const config = await loadConfig();
|
|
8742
|
+
console.log(chalk12.bold("Check Gates:"));
|
|
8743
|
+
for (const c of Object.values(config.checks)) {
|
|
8744
|
+
console.log(` - ${c.name}`);
|
|
8745
|
+
}
|
|
8746
|
+
console.log(chalk12.bold(`
|
|
8747
|
+
Review Gates:`));
|
|
8748
|
+
for (const r of Object.values(config.reviews)) {
|
|
8749
|
+
console.log(` - ${r.name} (Tools: ${r.cli_preference?.join(", ")})`);
|
|
8750
|
+
}
|
|
8751
|
+
console.log(chalk12.bold(`
|
|
8752
|
+
Entry Points:`));
|
|
8753
|
+
for (const ep of config.project.entry_points) {
|
|
8754
|
+
console.log(` - ${ep.path}`);
|
|
8755
|
+
if (ep.checks)
|
|
8756
|
+
console.log(` Checks: ${ep.checks.join(", ")}`);
|
|
8757
|
+
if (ep.reviews)
|
|
8758
|
+
console.log(` Reviews: ${ep.reviews.join(", ")}`);
|
|
8759
|
+
}
|
|
8760
|
+
} catch (error) {
|
|
8761
|
+
const err = error;
|
|
8762
|
+
console.error(chalk12.red("Error:"), err.message);
|
|
8511
8763
|
}
|
|
8512
|
-
}
|
|
8764
|
+
});
|
|
8513
8765
|
}
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
const
|
|
8527
|
-
const
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
const
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
const
|
|
8766
|
+
// src/commands/review.ts
|
|
8767
|
+
function registerReviewCommand(program) {
|
|
8768
|
+
program.command("review").description("Run only applicable reviews for detected changes").option("-b, --base-branch <branch>", "Override base branch for change detection").option("-g, --gate <name>", "Run specific review gate only").option("-c, --commit <sha>", "Use diff for a specific commit").option("-u, --uncommitted", "Use diff for current uncommitted changes (staged and unstaged)").option("-e, --enable-review <name>", "Activate a disabled review for this run (repeatable)", (value, prev = []) => [...prev, value], []).action((options) => executeGateCommand("review", {
|
|
8769
|
+
...options,
|
|
8770
|
+
enableReviews: new Set(options.enableReview ?? [])
|
|
8771
|
+
}));
|
|
8772
|
+
}
|
|
8773
|
+
// src/scripts/review-audit.ts
|
|
8774
|
+
import fs33 from "node:fs";
|
|
8775
|
+
import path31 from "node:path";
|
|
8776
|
+
import readline from "node:readline";
|
|
8777
|
+
function parseKeyValue2(text) {
|
|
8778
|
+
const result = {};
|
|
8779
|
+
for (const [, key, value] of text.matchAll(/(\w+)=(\S+)/g))
|
|
8780
|
+
if (key && value)
|
|
8781
|
+
result[key] = value;
|
|
8782
|
+
return result;
|
|
8783
|
+
}
|
|
8784
|
+
var parseTimestamp2 = (line) => line.match(/^\[([^\]]+)\]/)?.[1] ?? "";
|
|
8785
|
+
var parseEventType2 = (line) => line.match(/^\[[^\]]+\]\s+(\S+)/)?.[1] ?? "";
|
|
8786
|
+
var parseEventBody2 = (line) => line.match(/^\[[^\]]+\]\s+\S+\s*(.*)/)?.[1] ?? "";
|
|
8787
|
+
var safeNum = (v) => {
|
|
8788
|
+
const n = Number(v ?? 0);
|
|
8789
|
+
return Number.isNaN(n) ? 0 : n;
|
|
8790
|
+
};
|
|
8791
|
+
var parseDuration = (d) => {
|
|
8792
|
+
const m = d.match(/^([\d.]+)(ms|s|m)?$/);
|
|
8793
|
+
const val = safeNum(m?.[1]);
|
|
8794
|
+
if (m?.[2] === "ms")
|
|
8795
|
+
return val / 1000;
|
|
8796
|
+
if (m?.[2] === "m")
|
|
8797
|
+
return val * 60;
|
|
8798
|
+
return val;
|
|
8799
|
+
};
|
|
8800
|
+
function getLogDir3(cwd) {
|
|
8801
|
+
const configPath = path31.join(cwd, ".gauntlet", "config.yml");
|
|
8802
|
+
try {
|
|
8803
|
+
const content = fs33.readFileSync(configPath, "utf-8");
|
|
8804
|
+
const match = content.match(/^log_dir:\s*(.+)$/m);
|
|
8805
|
+
if (match?.[1])
|
|
8806
|
+
return match[1].trim();
|
|
8807
|
+
} catch {}
|
|
8808
|
+
return "gauntlet_logs";
|
|
8809
|
+
}
|
|
8810
|
+
function handleRunStart(ts, body) {
|
|
8811
|
+
const kv = parseKeyValue2(body);
|
|
8541
8812
|
return {
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8813
|
+
timestamp: ts,
|
|
8814
|
+
mode: kv.mode ?? "unknown",
|
|
8815
|
+
linesAdded: safeNum(kv.lines_added),
|
|
8816
|
+
linesRemoved: safeNum(kv.lines_removed),
|
|
8817
|
+
reviewGates: [],
|
|
8818
|
+
priorPassSkips: 0,
|
|
8819
|
+
telemetry: []
|
|
8549
8820
|
};
|
|
8550
8821
|
}
|
|
8551
|
-
function
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
if (removed && removed !== "-") {
|
|
8567
|
-
linesRemoved += parseInt(removed, 10) || 0;
|
|
8568
|
-
}
|
|
8822
|
+
function handleGateResult(current, body) {
|
|
8823
|
+
const gateIdMatch = body.match(/^(\S+)/);
|
|
8824
|
+
const gateId = gateIdMatch?.[1] ?? "";
|
|
8825
|
+
if (!gateId.startsWith("review:"))
|
|
8826
|
+
return;
|
|
8827
|
+
const kv = parseKeyValue2(body);
|
|
8828
|
+
if (kv.cli) {
|
|
8829
|
+
current.reviewGates.push({
|
|
8830
|
+
reviewType: gateId.split(":").at(-1) ?? "other",
|
|
8831
|
+
cli: kv.cli,
|
|
8832
|
+
durationS: parseDuration(kv.duration ?? "0s"),
|
|
8833
|
+
violations: safeNum(kv.violations)
|
|
8834
|
+
});
|
|
8835
|
+
} else {
|
|
8836
|
+
current.priorPassSkips++;
|
|
8569
8837
|
}
|
|
8570
|
-
return { linesAdded, linesRemoved };
|
|
8571
8838
|
}
|
|
8572
|
-
function
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
}
|
|
8596
|
-
return {
|
|
8597
|
-
total: newFiles + modifiedFiles + deletedFiles,
|
|
8598
|
-
newFiles,
|
|
8599
|
-
modifiedFiles,
|
|
8600
|
-
deletedFiles
|
|
8839
|
+
function handleTelemetry(current, body) {
|
|
8840
|
+
const kv = parseKeyValue2(body);
|
|
8841
|
+
if (!kv.adapter)
|
|
8842
|
+
return;
|
|
8843
|
+
current.telemetry.push({
|
|
8844
|
+
adapter: kv.adapter,
|
|
8845
|
+
inTokens: safeNum(kv.in),
|
|
8846
|
+
cacheTokens: safeNum(kv.cache),
|
|
8847
|
+
outTokens: safeNum(kv.out),
|
|
8848
|
+
thoughtTokens: safeNum(kv.thought),
|
|
8849
|
+
toolTokens: safeNum(kv.tool),
|
|
8850
|
+
apiRequests: safeNum(kv.api_requests),
|
|
8851
|
+
cacheRead: safeNum(kv.cacheRead),
|
|
8852
|
+
cacheWrite: safeNum(kv.cacheWrite)
|
|
8853
|
+
});
|
|
8854
|
+
}
|
|
8855
|
+
function handleRunEnd(current, body) {
|
|
8856
|
+
const kv = parseKeyValue2(body);
|
|
8857
|
+
current.end = {
|
|
8858
|
+
status: kv.status ?? "unknown",
|
|
8859
|
+
fixed: safeNum(kv.fixed),
|
|
8860
|
+
skipped: safeNum(kv.skipped),
|
|
8861
|
+
failed: safeNum(kv.failed)
|
|
8601
8862
|
};
|
|
8602
8863
|
}
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8864
|
+
var emptyStat = () => ({
|
|
8865
|
+
count: 0,
|
|
8866
|
+
totalDuration: 0,
|
|
8867
|
+
totalViolations: 0
|
|
8868
|
+
});
|
|
8869
|
+
function addGate(s, g) {
|
|
8870
|
+
s.count++;
|
|
8871
|
+
s.totalDuration += g.durationS;
|
|
8872
|
+
s.totalViolations += g.violations;
|
|
8873
|
+
}
|
|
8874
|
+
function getOrCreate(map, key, init) {
|
|
8875
|
+
if (!map.has(key))
|
|
8876
|
+
map.set(key, init());
|
|
8877
|
+
return map.get(key);
|
|
8878
|
+
}
|
|
8879
|
+
var REVIEW_TYPES = ["code-quality", "task-compliance", "artifact-review"];
|
|
8880
|
+
function accumulateBlock(block, a) {
|
|
8881
|
+
for (const g of block.reviewGates) {
|
|
8882
|
+
const inner = getOrCreate(a.cells, g.reviewType, () => new Map);
|
|
8883
|
+
addGate(getOrCreate(inner, g.cli, emptyStat), g);
|
|
8884
|
+
addGate(getOrCreate(a.cliTotals, g.cli, emptyStat), g);
|
|
8885
|
+
addGate(getOrCreate(a.typeTotals, g.reviewType, emptyStat), g);
|
|
8886
|
+
addGate(a.grandTotal, g);
|
|
8887
|
+
}
|
|
8888
|
+
const diff = block.linesAdded + block.linesRemoved;
|
|
8889
|
+
if (diff <= 0)
|
|
8890
|
+
return;
|
|
8891
|
+
for (const cli of new Set(block.reviewGates.map((g) => g.cli))) {
|
|
8892
|
+
const dur = block.reviewGates.filter((g) => g.cli === cli).reduce((s, g) => s + g.durationS, 0);
|
|
8893
|
+
const p = getOrCreate(a.per100, cli, () => ({ dur: 0, diff: 0 }));
|
|
8894
|
+
p.dur += dur;
|
|
8895
|
+
p.diff += diff;
|
|
8896
|
+
}
|
|
8897
|
+
}
|
|
8898
|
+
function buildCrossTab(blocks) {
|
|
8899
|
+
const a = {
|
|
8900
|
+
cells: new Map,
|
|
8901
|
+
cliTotals: new Map,
|
|
8902
|
+
typeTotals: new Map,
|
|
8903
|
+
per100: new Map,
|
|
8904
|
+
grandTotal: emptyStat()
|
|
8905
|
+
};
|
|
8906
|
+
for (const block of blocks)
|
|
8907
|
+
accumulateBlock(block, a);
|
|
8908
|
+
const allTypes = [
|
|
8909
|
+
...REVIEW_TYPES.filter((t) => a.typeTotals.has(t)),
|
|
8910
|
+
...[...a.typeTotals.keys()].filter((t) => !REVIEW_TYPES.includes(t))
|
|
8911
|
+
];
|
|
8912
|
+
return { ...a, allTypes, allClis: [...a.cliTotals.keys()] };
|
|
8913
|
+
}
|
|
8914
|
+
var emptyTokenStats = (adapter) => ({
|
|
8915
|
+
adapter,
|
|
8916
|
+
inTokens: 0,
|
|
8917
|
+
cacheTokens: 0,
|
|
8918
|
+
outTokens: 0,
|
|
8919
|
+
thoughtTokens: 0,
|
|
8920
|
+
toolTokens: 0,
|
|
8921
|
+
apiRequests: 0,
|
|
8922
|
+
cacheRead: 0,
|
|
8923
|
+
cacheWrite: 0,
|
|
8924
|
+
runsWithTelemetry: 0
|
|
8925
|
+
});
|
|
8926
|
+
function accumulateTelemetryEntry(t, statsMap) {
|
|
8927
|
+
const s = statsMap.get(t.adapter) ?? emptyTokenStats(t.adapter);
|
|
8928
|
+
statsMap.set(t.adapter, s);
|
|
8929
|
+
s.inTokens += t.inTokens;
|
|
8930
|
+
s.cacheTokens += t.cacheTokens;
|
|
8931
|
+
s.outTokens += t.outTokens;
|
|
8932
|
+
s.thoughtTokens += t.thoughtTokens;
|
|
8933
|
+
s.toolTokens += t.toolTokens;
|
|
8934
|
+
s.apiRequests += t.apiRequests;
|
|
8935
|
+
s.cacheRead += t.cacheRead;
|
|
8936
|
+
s.cacheWrite += t.cacheWrite;
|
|
8937
|
+
}
|
|
8938
|
+
function aggregateTokenStats(blocks) {
|
|
8939
|
+
const statsMap = new Map;
|
|
8940
|
+
for (const block of blocks) {
|
|
8941
|
+
const adaptersInBlock = new Set(block.telemetry.map((t) => t.adapter));
|
|
8942
|
+
for (const t of block.telemetry)
|
|
8943
|
+
accumulateTelemetryEntry(t, statsMap);
|
|
8944
|
+
for (const adapter of adaptersInBlock) {
|
|
8945
|
+
const s = statsMap.get(adapter);
|
|
8946
|
+
if (s)
|
|
8947
|
+
s.runsWithTelemetry++;
|
|
8948
|
+
}
|
|
8949
|
+
}
|
|
8950
|
+
return Array.from(statsMap.values());
|
|
8951
|
+
}
|
|
8952
|
+
var formatNum = (n) => n.toLocaleString("en-US");
|
|
8953
|
+
var padRight = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
8954
|
+
var padLeft = (s, w) => " ".repeat(Math.max(0, w - s.length)) + s;
|
|
8955
|
+
var capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
8956
|
+
var fmtType = (t) => t.split("-").map(capitalize).join("-");
|
|
8957
|
+
function formatCrossTable(title, rowLabels, colLabels, cell, rowTotal, colTotal, grandTotal) {
|
|
8958
|
+
const rlW = Math.max(17, ...rowLabels.map((r) => fmtType(r).length)) + 2;
|
|
8959
|
+
const cW = 12;
|
|
8960
|
+
const hdr = padRight("", rlW) + colLabels.map((c) => padRight(capitalize(c), cW)).join("") + "Total";
|
|
8961
|
+
const rows = rowLabels.map((r) => padRight(fmtType(r), rlW) + colLabels.map((c) => padRight(cell(r, c), cW)).join("") + rowTotal(r));
|
|
8962
|
+
const totalRow = padRight("Total", rlW) + colLabels.map((c) => padRight(colTotal(c), cW)).join("") + grandTotal;
|
|
8963
|
+
return [title, hdr, ...rows, totalRow, ""];
|
|
8964
|
+
}
|
|
8965
|
+
function formatRunCounts(ct) {
|
|
8966
|
+
return formatCrossTable("=== Run Counts ===", ct.allTypes, ct.allClis, (t, c) => String(ct.cells.get(t)?.get(c)?.count ?? 0), (t) => String(ct.typeTotals.get(t)?.count ?? 0), (c) => String(ct.cliTotals.get(c)?.count ?? 0), String(ct.grandTotal.count));
|
|
8967
|
+
}
|
|
8968
|
+
function formatTiming(ct) {
|
|
8969
|
+
const avg = (s) => s && s.count > 0 ? `${(s.totalDuration / s.count).toFixed(1)}s` : "n/a";
|
|
8970
|
+
const lines = formatCrossTable("=== Timing ===", ct.allTypes, ct.allClis, (t, c) => avg(ct.cells.get(t)?.get(c)), (t) => avg(ct.typeTotals.get(t)), (c) => avg(ct.cliTotals.get(c)), avg(ct.grandTotal));
|
|
8971
|
+
const p100parts = ct.allClis.map((c) => {
|
|
8972
|
+
const p = ct.per100.get(c);
|
|
8973
|
+
return p && p.diff > 0 ? `${c}=${(p.dur / p.diff * 100).toFixed(1)}s` : "";
|
|
8974
|
+
}).filter(Boolean);
|
|
8975
|
+
if (p100parts.length > 0)
|
|
8976
|
+
lines.splice(lines.length - 1, 0, `Per 100 diff lines (excl. zero-diff): ${p100parts.join(" ")}`);
|
|
8977
|
+
return lines;
|
|
8978
|
+
}
|
|
8979
|
+
function formatViolations(ct) {
|
|
8980
|
+
const avg = (s) => s && s.count > 0 ? (s.totalViolations / s.count).toFixed(2) : "n/a";
|
|
8981
|
+
return formatCrossTable("=== Violations (avg per run) ===", ct.allTypes, ct.allClis, (t, c) => avg(ct.cells.get(t)?.get(c)), (t) => avg(ct.typeTotals.get(t)), (c) => avg(ct.cliTotals.get(c)), avg(ct.grandTotal));
|
|
8982
|
+
}
|
|
8983
|
+
function formatTokenEntry(t, totalRuns) {
|
|
8984
|
+
const out = [
|
|
8985
|
+
`${capitalize(t.adapter)} (${t.runsWithTelemetry} of ${totalRuns} runs had telemetry):`
|
|
8986
|
+
];
|
|
8987
|
+
if (t.inTokens > 0 || t.cacheTokens > 0) {
|
|
8988
|
+
const total = t.inTokens + t.cacheTokens;
|
|
8989
|
+
out.push(` Input: ${padLeft(formatNum(total), 12)} (non-cached: ${formatNum(t.inTokens)} | cached: ${formatNum(t.cacheTokens)})`);
|
|
8990
|
+
}
|
|
8991
|
+
if (t.outTokens > 0)
|
|
8992
|
+
out.push(` Output: ${padLeft(formatNum(t.outTokens), 12)}`);
|
|
8993
|
+
if (t.thoughtTokens > 0)
|
|
8994
|
+
out.push(` Thinking: ${padLeft(formatNum(t.thoughtTokens), 12)}`);
|
|
8995
|
+
if (t.toolTokens > 0)
|
|
8996
|
+
out.push(` Tool tokens: ${padLeft(formatNum(t.toolTokens), 12)}`);
|
|
8997
|
+
if (t.cacheRead > 0 || t.cacheWrite > 0) {
|
|
8998
|
+
out.push(` Cache reads: ${padLeft(formatNum(t.cacheRead), 12)}`);
|
|
8999
|
+
out.push(` Cache writes: ${padLeft(formatNum(t.cacheWrite), 12)}`);
|
|
9000
|
+
}
|
|
9001
|
+
if (t.apiRequests > 0) {
|
|
9002
|
+
const avg = t.runsWithTelemetry > 0 ? (t.apiRequests / t.runsWithTelemetry).toFixed(1) : "?";
|
|
9003
|
+
out.push(` API requests: ${padLeft(formatNum(t.apiRequests), 12)} (avg ${avg}/run)`);
|
|
9004
|
+
}
|
|
9005
|
+
out.push("");
|
|
9006
|
+
return out;
|
|
9007
|
+
}
|
|
9008
|
+
function formatTokenUsage(ts, m) {
|
|
9009
|
+
if (ts.length === 0)
|
|
9010
|
+
return ["=== Token Usage ===", "No telemetry data found.", ""];
|
|
9011
|
+
return [
|
|
9012
|
+
"=== Token Usage ===",
|
|
9013
|
+
...ts.flatMap((t) => formatTokenEntry(t, m.get(t.adapter) ?? t.runsWithTelemetry))
|
|
9014
|
+
];
|
|
9015
|
+
}
|
|
9016
|
+
function formatFixSkip(blocks) {
|
|
9017
|
+
const withEnd = blocks.filter((b) => b.end);
|
|
9018
|
+
const total = withEnd.length;
|
|
9019
|
+
const passed = withEnd.filter((b) => b.end?.status === "pass").length;
|
|
9020
|
+
const fixed = withEnd.reduce((s, b) => s + (b.end?.fixed ?? 0), 0);
|
|
9021
|
+
const skipped = withEnd.reduce((s, b) => s + (b.end?.skipped ?? 0), 0);
|
|
9022
|
+
const failed = withEnd.reduce((s, b) => s + (b.end?.failed ?? 0), 0);
|
|
9023
|
+
const priorPass = blocks.reduce((s, b) => s + b.priorPassSkips, 0);
|
|
9024
|
+
const lines = [
|
|
9025
|
+
"=== Fix / Skip ===",
|
|
9026
|
+
`Gauntlet runs: ${total} total (${passed} passed, ${total - passed} failed)`,
|
|
9027
|
+
` Violations fixed: ${fixed}`,
|
|
9028
|
+
` Violations skipped: ${skipped}`,
|
|
9029
|
+
` Gates failed: ${failed}`,
|
|
9030
|
+
` Review gates skipped (prior pass): ${priorPass}`
|
|
9031
|
+
];
|
|
9032
|
+
const totalFixedSkipped = fixed + skipped;
|
|
9033
|
+
if (totalFixedSkipped > 0) {
|
|
9034
|
+
const fp = (fixed / totalFixedSkipped * 100).toFixed(1);
|
|
9035
|
+
const sp = (skipped / totalFixedSkipped * 100).toFixed(1);
|
|
9036
|
+
lines.push(` (fixed: ${fp}% | skipped: ${sp}% of fixed+skipped)`);
|
|
8614
9037
|
}
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
9038
|
+
return lines;
|
|
9039
|
+
}
|
|
9040
|
+
function formatAuditReport(blocks, date) {
|
|
9041
|
+
if (blocks.length === 0)
|
|
9042
|
+
return `Review Execution Audit — ${date}
|
|
9043
|
+
|
|
9044
|
+
No gauntlet runs found for this date.`;
|
|
9045
|
+
const ct = buildCrossTab(blocks);
|
|
9046
|
+
const tokenStats = aggregateTokenStats(blocks);
|
|
9047
|
+
const cliBlockCount = new Map;
|
|
9048
|
+
for (const block of blocks) {
|
|
9049
|
+
for (const cli of new Set(block.reviewGates.map((g) => g.cli)))
|
|
9050
|
+
cliBlockCount.set(cli, (cliBlockCount.get(cli) ?? 0) + 1);
|
|
9051
|
+
}
|
|
9052
|
+
return [
|
|
9053
|
+
`Review Execution Audit — ${date}`,
|
|
9054
|
+
"",
|
|
9055
|
+
...formatRunCounts(ct),
|
|
9056
|
+
...formatTiming(ct),
|
|
9057
|
+
...formatViolations(ct),
|
|
9058
|
+
...formatTokenUsage(tokenStats, cliBlockCount),
|
|
9059
|
+
...formatFixSkip(blocks)
|
|
9060
|
+
].join(`
|
|
9061
|
+
`);
|
|
9062
|
+
}
|
|
9063
|
+
function todayLocalDate() {
|
|
9064
|
+
const now = new Date;
|
|
9065
|
+
const y = now.getFullYear();
|
|
9066
|
+
const mo = String(now.getMonth() + 1).padStart(2, "0");
|
|
9067
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
9068
|
+
return `${y}-${mo}-${d}`;
|
|
9069
|
+
}
|
|
9070
|
+
async function readBlocks(filePath, date) {
|
|
9071
|
+
const rl = readline.createInterface({
|
|
9072
|
+
input: fs33.createReadStream(filePath)
|
|
9073
|
+
});
|
|
9074
|
+
const blocks = [];
|
|
9075
|
+
let current = null;
|
|
9076
|
+
for await (const line of rl) {
|
|
8618
9077
|
if (!line.trim())
|
|
8619
9078
|
continue;
|
|
8620
|
-
const
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
9079
|
+
const ts = parseTimestamp2(line);
|
|
9080
|
+
if (!ts.startsWith(date))
|
|
9081
|
+
continue;
|
|
9082
|
+
const event = parseEventType2(line);
|
|
9083
|
+
const body = parseEventBody2(line);
|
|
9084
|
+
if (event === "RUN_START") {
|
|
9085
|
+
current = handleRunStart(ts, body);
|
|
9086
|
+
blocks.push(current);
|
|
9087
|
+
continue;
|
|
8624
9088
|
}
|
|
9089
|
+
if (!current)
|
|
9090
|
+
continue;
|
|
9091
|
+
if (event === "GATE_RESULT")
|
|
9092
|
+
handleGateResult(current, body);
|
|
9093
|
+
else if (event === "TELEMETRY")
|
|
9094
|
+
handleTelemetry(current, body);
|
|
9095
|
+
else if (event === "RUN_END")
|
|
9096
|
+
handleRunEnd(current, body);
|
|
8625
9097
|
}
|
|
8626
|
-
return
|
|
9098
|
+
return blocks;
|
|
8627
9099
|
}
|
|
8628
|
-
function
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
9100
|
+
async function main2(date) {
|
|
9101
|
+
const cwd = process.cwd();
|
|
9102
|
+
if (date && !/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
9103
|
+
console.error("Invalid --date. Expected YYYY-MM-DD");
|
|
9104
|
+
process.exit(1);
|
|
9105
|
+
}
|
|
9106
|
+
const targetDate = date ?? todayLocalDate();
|
|
9107
|
+
const debugLogPath = path31.join(cwd, getLogDir3(cwd), ".debug.log");
|
|
9108
|
+
if (!fs33.existsSync(debugLogPath)) {
|
|
9109
|
+
console.log(`No debug log found. (looked in ${getLogDir3(cwd)}/.debug.log)`);
|
|
9110
|
+
process.exit(0);
|
|
9111
|
+
}
|
|
9112
|
+
const blocks = await readBlocks(debugLogPath, targetDate);
|
|
9113
|
+
console.log(formatAuditReport(blocks, targetDate));
|
|
8638
9114
|
}
|
|
9115
|
+
var isDirectRun2 = (import.meta.url === `file://${process.argv[1]}` || typeof Bun !== "undefined" && import.meta.url === `file://${Bun.main}`) && (process.argv[1]?.endsWith("review-audit.ts") || process.argv[1]?.endsWith("review-audit.js"));
|
|
9116
|
+
if (isDirectRun2)
|
|
9117
|
+
main2();
|
|
8639
9118
|
|
|
9119
|
+
// src/commands/review-audit.ts
|
|
9120
|
+
function registerReviewAuditCommand(program) {
|
|
9121
|
+
program.command("review-audit").description("Audit review execution for a given date from the debug log").option("--date <YYYY-MM-DD>", "Date to filter (default: today)").action(async (opts) => {
|
|
9122
|
+
await main2(opts.date);
|
|
9123
|
+
});
|
|
9124
|
+
}
|
|
8640
9125
|
// src/core/run-executor-lock.ts
|
|
8641
|
-
import
|
|
8642
|
-
import
|
|
9126
|
+
import fs34 from "node:fs/promises";
|
|
9127
|
+
import path32 from "node:path";
|
|
8643
9128
|
var LOCK_FILENAME2 = ".gauntlet-run.lock";
|
|
8644
9129
|
var STALE_LOCK_MS = 10 * 60 * 1000;
|
|
8645
9130
|
function isProcessAlive(pid) {
|
|
@@ -8655,9 +9140,9 @@ function isProcessAlive(pid) {
|
|
|
8655
9140
|
}
|
|
8656
9141
|
async function isLockStale(lockPath) {
|
|
8657
9142
|
try {
|
|
8658
|
-
const lockContent = await
|
|
9143
|
+
const lockContent = await fs34.readFile(lockPath, "utf-8");
|
|
8659
9144
|
const lockPid = Number.parseInt(lockContent.trim(), 10);
|
|
8660
|
-
const lockStat = await
|
|
9145
|
+
const lockStat = await fs34.stat(lockPath);
|
|
8661
9146
|
const lockAgeMs = Date.now() - lockStat.mtimeMs;
|
|
8662
9147
|
const pidValid = !Number.isNaN(lockPid);
|
|
8663
9148
|
if (pidValid && !isProcessAlive(lockPid)) {
|
|
@@ -8672,10 +9157,10 @@ async function isLockStale(lockPath) {
|
|
|
8672
9157
|
}
|
|
8673
9158
|
}
|
|
8674
9159
|
async function tryAcquireLock(logDir) {
|
|
8675
|
-
await
|
|
8676
|
-
const lockPath =
|
|
9160
|
+
await fs34.mkdir(logDir, { recursive: true });
|
|
9161
|
+
const lockPath = path32.resolve(logDir, LOCK_FILENAME2);
|
|
8677
9162
|
try {
|
|
8678
|
-
await
|
|
9163
|
+
await fs34.writeFile(lockPath, String(process.pid), { flag: "wx" });
|
|
8679
9164
|
return true;
|
|
8680
9165
|
} catch (err) {
|
|
8681
9166
|
const isExist = typeof err === "object" && err !== null && "code" in err && err.code === "EEXIST";
|
|
@@ -8686,9 +9171,9 @@ async function tryAcquireLock(logDir) {
|
|
|
8686
9171
|
if (!stale) {
|
|
8687
9172
|
return false;
|
|
8688
9173
|
}
|
|
8689
|
-
await
|
|
9174
|
+
await fs34.rm(lockPath, { force: true });
|
|
8690
9175
|
try {
|
|
8691
|
-
await
|
|
9176
|
+
await fs34.writeFile(lockPath, String(process.pid), { flag: "wx" });
|
|
8692
9177
|
return true;
|
|
8693
9178
|
} catch {
|
|
8694
9179
|
return false;
|
|
@@ -8697,7 +9182,7 @@ async function tryAcquireLock(logDir) {
|
|
|
8697
9182
|
}
|
|
8698
9183
|
async function findLatestConsoleLog(logDir) {
|
|
8699
9184
|
try {
|
|
8700
|
-
const files = await
|
|
9185
|
+
const files = await fs34.readdir(logDir);
|
|
8701
9186
|
let maxNum = -1;
|
|
8702
9187
|
let latestFile = null;
|
|
8703
9188
|
for (const file of files) {
|
|
@@ -8713,7 +9198,7 @@ async function findLatestConsoleLog(logDir) {
|
|
|
8713
9198
|
}
|
|
8714
9199
|
}
|
|
8715
9200
|
}
|
|
8716
|
-
return latestFile ?
|
|
9201
|
+
return latestFile ? path32.join(logDir, latestFile) : null;
|
|
8717
9202
|
} catch {
|
|
8718
9203
|
return null;
|
|
8719
9204
|
}
|
|
@@ -8894,7 +9379,7 @@ async function detectAndPrepareChanges(ctx, isRerun, failuresMap, changeOptions)
|
|
|
8894
9379
|
};
|
|
8895
9380
|
const changeDetector = new ChangeDetector(ctx.effectiveBaseBranch, effectiveChangeOptions);
|
|
8896
9381
|
const expander = new EntryPointExpander;
|
|
8897
|
-
const jobGen = new JobGenerator(ctx.config);
|
|
9382
|
+
const jobGen = new JobGenerator(ctx.config, ctx.options.enableReviews);
|
|
8898
9383
|
log10.debug("Detecting changes...");
|
|
8899
9384
|
const changes = await changeDetector.getChangedFiles();
|
|
8900
9385
|
if (changes.length === 0 && isRerun) {
|
|
@@ -9071,12 +9556,13 @@ async function executeRun(options = {}) {
|
|
|
9071
9556
|
|
|
9072
9557
|
// src/commands/run.ts
|
|
9073
9558
|
function registerRunCommand(program) {
|
|
9074
|
-
program.command("run").description("Run gates for detected changes").option("-b, --base-branch <branch>", "Override base branch for change detection").option("-g, --gate <name>", "Run specific gate only").option("-c, --commit <sha>", "Use diff for a specific commit").option("-u, --uncommitted", "Use diff for current uncommitted changes (staged and unstaged)").action(async (options) => {
|
|
9559
|
+
program.command("run").description("Run gates for detected changes").option("-b, --base-branch <branch>", "Override base branch for change detection").option("-g, --gate <name>", "Run specific gate only").option("-c, --commit <sha>", "Use diff for a specific commit").option("-u, --uncommitted", "Use diff for current uncommitted changes (staged and unstaged)").option("-e, --enable-review <name>", "Activate a disabled review for this run (repeatable)", (value, prev = []) => [...prev, value], []).action(async (options) => {
|
|
9075
9560
|
const result = await executeRun({
|
|
9076
9561
|
baseBranch: options.baseBranch,
|
|
9077
9562
|
gate: options.gate,
|
|
9078
9563
|
commit: options.commit,
|
|
9079
|
-
uncommitted: options.uncommitted
|
|
9564
|
+
uncommitted: options.uncommitted,
|
|
9565
|
+
enableReviews: new Set(options.enableReview ?? [])
|
|
9080
9566
|
});
|
|
9081
9567
|
const code = isSuccessStatus(result.status) ? 0 : 1;
|
|
9082
9568
|
process.exit(code);
|
|
@@ -9114,8 +9600,8 @@ function registerSkipCommand(program) {
|
|
|
9114
9600
|
});
|
|
9115
9601
|
}
|
|
9116
9602
|
// src/commands/start-hook.ts
|
|
9117
|
-
import
|
|
9118
|
-
import
|
|
9603
|
+
import fs35 from "node:fs/promises";
|
|
9604
|
+
import path33 from "node:path";
|
|
9119
9605
|
import YAML8 from "yaml";
|
|
9120
9606
|
var START_HOOK_MESSAGE = `<IMPORTANT>
|
|
9121
9607
|
This project uses Agent Gauntlet for automated quality verification.
|
|
@@ -9163,9 +9649,9 @@ function isValidConfig(content) {
|
|
|
9163
9649
|
}
|
|
9164
9650
|
function registerStartHookCommand(program) {
|
|
9165
9651
|
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) => {
|
|
9166
|
-
const configPath =
|
|
9652
|
+
const configPath = path33.join(process.cwd(), ".gauntlet", "config.yml");
|
|
9167
9653
|
try {
|
|
9168
|
-
const content = await
|
|
9654
|
+
const content = await fs35.readFile(configPath, "utf-8");
|
|
9169
9655
|
if (!isValidConfig(content)) {
|
|
9170
9656
|
return;
|
|
9171
9657
|
}
|
|
@@ -9175,7 +9661,7 @@ function registerStartHookCommand(program) {
|
|
|
9175
9661
|
const adapter = options.adapter;
|
|
9176
9662
|
try {
|
|
9177
9663
|
const cwd = process.cwd();
|
|
9178
|
-
const logDir =
|
|
9664
|
+
const logDir = path33.join(cwd, await getLogDir2(cwd));
|
|
9179
9665
|
const globalConfig = await loadGlobalConfig();
|
|
9180
9666
|
const projectDebugLogConfig = await getDebugLogConfig(cwd);
|
|
9181
9667
|
const debugLogConfig = mergeDebugLogConfig(projectDebugLogConfig, globalConfig.debug_log);
|
|
@@ -9214,6 +9700,7 @@ registerRunCommand(program);
|
|
|
9214
9700
|
registerCheckCommand(program);
|
|
9215
9701
|
registerCICommand(program);
|
|
9216
9702
|
registerCleanCommand(program);
|
|
9703
|
+
registerReviewAuditCommand(program);
|
|
9217
9704
|
registerReviewCommand(program);
|
|
9218
9705
|
registerDetectCommand(program);
|
|
9219
9706
|
registerListCommand(program);
|
|
@@ -9230,4 +9717,4 @@ if (process.argv.length < 3) {
|
|
|
9230
9717
|
}
|
|
9231
9718
|
program.parse(process.argv);
|
|
9232
9719
|
|
|
9233
|
-
//# debugId=
|
|
9720
|
+
//# debugId=CAFA132ED75F0B9564756E2164756E21
|