agent-gauntlet 0.10.0 → 0.11.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 +25 -23
- package/dist/index.js +9226 -0
- package/dist/index.js.map +65 -0
- package/dist/scripts/status.js +280 -0
- package/dist/scripts/status.js.map +10 -0
- package/package.json +22 -8
- package/src/built-in-reviews/code-quality.md +0 -25
- package/src/built-in-reviews/index.ts +0 -28
- package/src/bun-plugins.d.ts +0 -4
- package/src/cli-adapters/claude.ts +0 -327
- package/src/cli-adapters/codex.ts +0 -290
- package/src/cli-adapters/cursor.ts +0 -128
- package/src/cli-adapters/gemini.ts +0 -510
- package/src/cli-adapters/github-copilot.ts +0 -141
- package/src/cli-adapters/index.ts +0 -250
- package/src/cli-adapters/thinking-budget.ts +0 -23
- package/src/commands/check.ts +0 -311
- package/src/commands/ci/index.ts +0 -15
- package/src/commands/ci/init.ts +0 -96
- package/src/commands/ci/list-jobs.ts +0 -90
- package/src/commands/clean.ts +0 -54
- package/src/commands/detect.ts +0 -173
- package/src/commands/health.ts +0 -169
- package/src/commands/help.ts +0 -34
- package/src/commands/index.ts +0 -13
- package/src/commands/init.ts +0 -1878
- package/src/commands/list.ts +0 -33
- package/src/commands/review.ts +0 -311
- package/src/commands/run.ts +0 -29
- package/src/commands/shared.ts +0 -267
- package/src/commands/stop-hook.ts +0 -567
- package/src/commands/validate.ts +0 -20
- package/src/commands/wait-ci.ts +0 -518
- package/src/config/ci-loader.ts +0 -33
- package/src/config/ci-schema.ts +0 -28
- package/src/config/global.ts +0 -87
- package/src/config/loader.ts +0 -301
- package/src/config/schema.ts +0 -165
- package/src/config/stop-hook-config.ts +0 -130
- package/src/config/types.ts +0 -65
- package/src/config/validator.ts +0 -592
- package/src/core/change-detector.ts +0 -137
- package/src/core/diff-stats.ts +0 -442
- package/src/core/entry-point.ts +0 -190
- package/src/core/job.ts +0 -96
- package/src/core/run-executor.ts +0 -621
- package/src/core/runner.ts +0 -290
- package/src/gates/check.ts +0 -118
- package/src/gates/resolve-check-command.ts +0 -21
- package/src/gates/result.ts +0 -54
- package/src/gates/review.ts +0 -1333
- package/src/hooks/adapters/claude-stop-hook.ts +0 -99
- package/src/hooks/adapters/cursor-stop-hook.ts +0 -122
- package/src/hooks/adapters/types.ts +0 -94
- package/src/hooks/stop-hook-handler.ts +0 -748
- package/src/index.ts +0 -47
- package/src/output/app-logger.ts +0 -214
- package/src/output/console-log.ts +0 -168
- package/src/output/console.ts +0 -359
- package/src/output/logger.ts +0 -126
- package/src/output/sinks/console-sink.ts +0 -59
- package/src/output/sinks/file-sink.ts +0 -110
- package/src/scripts/status.ts +0 -433
- package/src/templates/workflow.yml +0 -79
- package/src/types/gauntlet-status.ts +0 -79
- package/src/utils/debug-log.ts +0 -392
- package/src/utils/diff-parser.ts +0 -103
- package/src/utils/execution-state.ts +0 -472
- package/src/utils/log-parser.ts +0 -696
- package/src/utils/sanitizer.ts +0 -3
- package/src/utils/session-ref.ts +0 -91
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/scripts/status.ts
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
function parseKeyValue(text) {
|
|
7
|
+
const result = {};
|
|
8
|
+
for (const match of text.matchAll(/(\w+)=(\S+)/g)) {
|
|
9
|
+
const key = match[1];
|
|
10
|
+
const value = match[2];
|
|
11
|
+
if (key && value)
|
|
12
|
+
result[key] = value;
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
function parseTimestamp(line) {
|
|
17
|
+
const m = line.match(/^\[([^\]]+)\]/);
|
|
18
|
+
return m?.[1] ?? "";
|
|
19
|
+
}
|
|
20
|
+
function parseEventType(line) {
|
|
21
|
+
const m = line.match(/^\[[^\]]+\]\s+(\S+)/);
|
|
22
|
+
return m?.[1] ?? "";
|
|
23
|
+
}
|
|
24
|
+
function parseEventBody(line) {
|
|
25
|
+
const m = line.match(/^\[[^\]]+\]\s+\S+\s*(.*)/);
|
|
26
|
+
return m?.[1] ?? "";
|
|
27
|
+
}
|
|
28
|
+
function parseDebugLog(content, sessionStartTime) {
|
|
29
|
+
const lines = content.split(`
|
|
30
|
+
`).filter((l) => l.trim());
|
|
31
|
+
const sessions = [];
|
|
32
|
+
let current = null;
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
const event = parseEventType(line);
|
|
35
|
+
const body = parseEventBody(line);
|
|
36
|
+
const ts = parseTimestamp(line);
|
|
37
|
+
switch (event) {
|
|
38
|
+
case "RUN_START": {
|
|
39
|
+
if (sessionStartTime && new Date(ts) < sessionStartTime) {
|
|
40
|
+
current = null;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
const kv = parseKeyValue(body);
|
|
44
|
+
current = {
|
|
45
|
+
start: {
|
|
46
|
+
timestamp: ts,
|
|
47
|
+
mode: kv.mode ?? "unknown",
|
|
48
|
+
baseRef: kv.base_ref,
|
|
49
|
+
filesChanged: Number(kv.files_changed ?? kv.changes ?? 0),
|
|
50
|
+
linesAdded: Number(kv.lines_added ?? 0),
|
|
51
|
+
linesRemoved: Number(kv.lines_removed ?? 0),
|
|
52
|
+
gates: Number(kv.gates ?? 0)
|
|
53
|
+
},
|
|
54
|
+
gates: []
|
|
55
|
+
};
|
|
56
|
+
sessions.push(current);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case "GATE_RESULT": {
|
|
60
|
+
if (!current)
|
|
61
|
+
break;
|
|
62
|
+
const gateIdMatch = body.match(/^(\S+)/);
|
|
63
|
+
const kv = parseKeyValue(body);
|
|
64
|
+
current.gates.push({
|
|
65
|
+
timestamp: ts,
|
|
66
|
+
gateId: gateIdMatch?.[1] ?? "unknown",
|
|
67
|
+
cli: kv.cli,
|
|
68
|
+
status: kv.status ?? "unknown",
|
|
69
|
+
duration: kv.duration ?? "?",
|
|
70
|
+
violations: kv.violations !== undefined ? Number(kv.violations) : undefined
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "RUN_END": {
|
|
75
|
+
if (!current)
|
|
76
|
+
break;
|
|
77
|
+
const kv = parseKeyValue(body);
|
|
78
|
+
current.end = {
|
|
79
|
+
timestamp: ts,
|
|
80
|
+
status: kv.status ?? "unknown",
|
|
81
|
+
fixed: Number(kv.fixed ?? 0),
|
|
82
|
+
skipped: Number(kv.skipped ?? 0),
|
|
83
|
+
failed: Number(kv.failed ?? 0),
|
|
84
|
+
iterations: Number(kv.iterations ?? 0),
|
|
85
|
+
duration: kv.duration ?? "?"
|
|
86
|
+
};
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "STOP_HOOK": {
|
|
90
|
+
if (!current)
|
|
91
|
+
break;
|
|
92
|
+
const kv = parseKeyValue(body);
|
|
93
|
+
current.stopHook = {
|
|
94
|
+
timestamp: ts,
|
|
95
|
+
decision: kv.decision ?? "unknown",
|
|
96
|
+
reason: kv.reason ?? "unknown"
|
|
97
|
+
};
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return sessions;
|
|
103
|
+
}
|
|
104
|
+
function getSessionStartTime(logDir) {
|
|
105
|
+
const entries = fs.readdirSync(logDir).filter((f) => !f.startsWith(".") && f !== "previous");
|
|
106
|
+
let earliest;
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const mtime = fs.statSync(path.join(logDir, entry)).mtimeMs;
|
|
109
|
+
if (earliest === undefined || mtime < earliest) {
|
|
110
|
+
earliest = mtime;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return earliest !== undefined ? new Date(earliest) : undefined;
|
|
114
|
+
}
|
|
115
|
+
function formatFileInventory(logDir) {
|
|
116
|
+
const lines = [];
|
|
117
|
+
const entries = fs.readdirSync(logDir).filter((f) => !f.startsWith(".") && f !== "previous");
|
|
118
|
+
if (entries.length === 0)
|
|
119
|
+
return lines;
|
|
120
|
+
const checks = [];
|
|
121
|
+
const reviews = [];
|
|
122
|
+
const other = [];
|
|
123
|
+
for (const entry of entries.sort()) {
|
|
124
|
+
const fullPath = path.join(logDir, entry);
|
|
125
|
+
const stat = fs.statSync(fullPath);
|
|
126
|
+
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
127
|
+
const line = `- ${fullPath} (${sizeKB} KB)`;
|
|
128
|
+
if (entry.startsWith("review_")) {
|
|
129
|
+
reviews.push(line);
|
|
130
|
+
} else if (entry.startsWith("check_")) {
|
|
131
|
+
checks.push(line);
|
|
132
|
+
} else {
|
|
133
|
+
other.push(line);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
lines.push("### Log Files");
|
|
137
|
+
lines.push("");
|
|
138
|
+
if (checks.length > 0) {
|
|
139
|
+
lines.push("**Check logs:**");
|
|
140
|
+
lines.push(...checks);
|
|
141
|
+
}
|
|
142
|
+
if (reviews.length > 0) {
|
|
143
|
+
lines.push("**Review logs/JSON:**");
|
|
144
|
+
lines.push(...reviews);
|
|
145
|
+
}
|
|
146
|
+
if (other.length > 0) {
|
|
147
|
+
lines.push("**Other:**");
|
|
148
|
+
lines.push(...other);
|
|
149
|
+
}
|
|
150
|
+
lines.push("");
|
|
151
|
+
return lines;
|
|
152
|
+
}
|
|
153
|
+
function formatStatusLine(end) {
|
|
154
|
+
return end.status === "pass" ? "PASSED" : end.status === "fail" ? "FAILED" : end.status.toUpperCase();
|
|
155
|
+
}
|
|
156
|
+
function formatAllRuns(sessions) {
|
|
157
|
+
const lines = [];
|
|
158
|
+
lines.push("### All Runs in Session");
|
|
159
|
+
lines.push("");
|
|
160
|
+
for (let i = 0;i < sessions.length; i++) {
|
|
161
|
+
const s = sessions[i];
|
|
162
|
+
if (!s)
|
|
163
|
+
continue;
|
|
164
|
+
const status = s.end ? s.end.status : "in-progress";
|
|
165
|
+
const duration = s.end ? s.end.duration : "?";
|
|
166
|
+
lines.push(`${i + 1}. [${s.start.timestamp}] mode=${s.start.mode} status=${status} duration=${duration}`);
|
|
167
|
+
}
|
|
168
|
+
lines.push("");
|
|
169
|
+
return lines;
|
|
170
|
+
}
|
|
171
|
+
function formatSession(sessions, logDir) {
|
|
172
|
+
if (sessions.length === 0) {
|
|
173
|
+
return "No gauntlet runs found in logs.";
|
|
174
|
+
}
|
|
175
|
+
const lastComplete = [...sessions].reverse().find((s) => s.end);
|
|
176
|
+
const session = lastComplete ?? sessions[sessions.length - 1];
|
|
177
|
+
if (!session)
|
|
178
|
+
return "No gauntlet runs found in logs.";
|
|
179
|
+
const lines = [];
|
|
180
|
+
lines.push("## Gauntlet Session Summary");
|
|
181
|
+
lines.push("");
|
|
182
|
+
if (session.end) {
|
|
183
|
+
lines.push(`**Status:** ${formatStatusLine(session.end)}`);
|
|
184
|
+
lines.push(`**Iterations:** ${session.end.iterations}`);
|
|
185
|
+
lines.push(`**Duration:** ${session.end.duration}`);
|
|
186
|
+
lines.push(`**Fixed:** ${session.end.fixed} | **Skipped:** ${session.end.skipped} | **Failed:** ${session.end.failed}`);
|
|
187
|
+
} else {
|
|
188
|
+
lines.push("**Status:** In Progress (no RUN_END found)");
|
|
189
|
+
}
|
|
190
|
+
lines.push("");
|
|
191
|
+
lines.push("### Diff Stats");
|
|
192
|
+
lines.push(`- Mode: ${session.start.mode}`);
|
|
193
|
+
if (session.start.baseRef) {
|
|
194
|
+
lines.push(`- Base ref: ${session.start.baseRef}`);
|
|
195
|
+
}
|
|
196
|
+
lines.push(`- Files changed: ${session.start.filesChanged}`);
|
|
197
|
+
lines.push(`- Lines: +${session.start.linesAdded} / -${session.start.linesRemoved}`);
|
|
198
|
+
lines.push(`- Gates: ${session.start.gates}`);
|
|
199
|
+
lines.push("");
|
|
200
|
+
lines.push("### Gate Results");
|
|
201
|
+
lines.push("");
|
|
202
|
+
lines.push("| Gate | CLI | Status | Duration | Violations |");
|
|
203
|
+
lines.push("|------|-----|--------|----------|------------|");
|
|
204
|
+
for (const gate of session.gates) {
|
|
205
|
+
const violations = gate.violations !== undefined ? String(gate.violations) : "-";
|
|
206
|
+
const statusIcon = gate.status === "pass" ? "pass" : "FAIL";
|
|
207
|
+
lines.push(`| ${gate.gateId} | ${gate.cli ?? "-"} | ${statusIcon} | ${gate.duration} | ${violations} |`);
|
|
208
|
+
}
|
|
209
|
+
lines.push("");
|
|
210
|
+
if (session.stopHook) {
|
|
211
|
+
lines.push("### Stop Hook");
|
|
212
|
+
lines.push(`- Decision: ${session.stopHook.decision}`);
|
|
213
|
+
lines.push(`- Reason: ${session.stopHook.reason}`);
|
|
214
|
+
lines.push("");
|
|
215
|
+
}
|
|
216
|
+
lines.push(...formatFileInventory(logDir));
|
|
217
|
+
if (sessions.length > 1) {
|
|
218
|
+
lines.push(...formatAllRuns(sessions));
|
|
219
|
+
}
|
|
220
|
+
return lines.join(`
|
|
221
|
+
`);
|
|
222
|
+
}
|
|
223
|
+
function getLogDir(cwd) {
|
|
224
|
+
const configPath = path.join(cwd, ".gauntlet", "config.yml");
|
|
225
|
+
try {
|
|
226
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
227
|
+
const match = content.match(/^log_dir:\s*(.+)$/m);
|
|
228
|
+
if (match?.[1])
|
|
229
|
+
return match[1].trim();
|
|
230
|
+
} catch {}
|
|
231
|
+
return "gauntlet_logs";
|
|
232
|
+
}
|
|
233
|
+
function resolveLogPaths(activeDir) {
|
|
234
|
+
const previousDir = path.join(activeDir, "previous");
|
|
235
|
+
const debugLogPath = path.join(activeDir, ".debug.log");
|
|
236
|
+
const activeHasLogs = fs.existsSync(activeDir) && fs.readdirSync(activeDir).some((f) => !f.startsWith(".") && f !== "previous");
|
|
237
|
+
if (activeHasLogs) {
|
|
238
|
+
return { logDir: activeDir, debugLogPath };
|
|
239
|
+
}
|
|
240
|
+
if (!fs.existsSync(previousDir)) {
|
|
241
|
+
console.log("No gauntlet_logs directory found.");
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const logDir = resolvePreviousLogDir(previousDir);
|
|
245
|
+
if (!logDir)
|
|
246
|
+
return null;
|
|
247
|
+
return { logDir, debugLogPath };
|
|
248
|
+
}
|
|
249
|
+
function resolvePreviousLogDir(previousDir) {
|
|
250
|
+
const prevEntries = fs.readdirSync(previousDir);
|
|
251
|
+
const hasDirectFiles = prevEntries.some((f) => f.endsWith(".log") || f.endsWith(".json"));
|
|
252
|
+
if (hasDirectFiles)
|
|
253
|
+
return previousDir;
|
|
254
|
+
const prevDirs = prevEntries.map((d) => path.join(previousDir, d)).filter((d) => fs.statSync(d).isDirectory()).sort().reverse();
|
|
255
|
+
if (prevDirs.length === 0) {
|
|
256
|
+
console.log("No gauntlet logs found.");
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
return prevDirs[0];
|
|
260
|
+
}
|
|
261
|
+
function main() {
|
|
262
|
+
const cwd = process.cwd();
|
|
263
|
+
const logDirName = getLogDir(cwd);
|
|
264
|
+
const activeDir = path.join(cwd, logDirName);
|
|
265
|
+
const paths = resolveLogPaths(activeDir);
|
|
266
|
+
if (!paths) {
|
|
267
|
+
process.exit(0);
|
|
268
|
+
}
|
|
269
|
+
let sessions = [];
|
|
270
|
+
if (fs.existsSync(paths.debugLogPath)) {
|
|
271
|
+
const debugContent = fs.readFileSync(paths.debugLogPath, "utf-8");
|
|
272
|
+
const sessionStart = getSessionStartTime(paths.logDir);
|
|
273
|
+
sessions = parseDebugLog(debugContent, sessionStart);
|
|
274
|
+
}
|
|
275
|
+
const output = formatSession(sessions, paths.logDir);
|
|
276
|
+
console.log(output);
|
|
277
|
+
}
|
|
278
|
+
main();
|
|
279
|
+
|
|
280
|
+
//# debugId=4BED01809E659CB464756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/scripts/status.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"#!/usr/bin/env node\n/**\n * Gauntlet Status Script\n *\n * Parses the configured log_dir (default: gauntlet_logs/) to produce a structured\n * summary of the most recent gauntlet session from the .debug.log, plus a file\n * inventory of all log/JSON files for further inspection.\n *\n * This script handles structured data only (debug log events). Detailed failure\n * analysis (reading individual check logs, review JSONs) is left to the caller\n * (the /gauntlet-status skill) since log formats vary by check type.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n// --- Types ---\n\ninterface RunStart {\n\ttimestamp: string;\n\tmode: string;\n\tbaseRef?: string;\n\tfilesChanged: number;\n\tlinesAdded: number;\n\tlinesRemoved: number;\n\tgates: number;\n}\n\ninterface GateResult {\n\ttimestamp: string;\n\tgateId: string;\n\tcli?: string;\n\tstatus: string;\n\tduration: string;\n\tviolations?: number;\n}\n\ninterface RunEnd {\n\ttimestamp: string;\n\tstatus: string;\n\tfixed: number;\n\tskipped: number;\n\tfailed: number;\n\titerations: number;\n\tduration: string;\n}\n\ninterface StopHookEntry {\n\ttimestamp: string;\n\tdecision: string;\n\treason: string;\n}\n\ninterface SessionRun {\n\tstart: RunStart;\n\tgates: GateResult[];\n\tend?: RunEnd;\n\tstopHook?: StopHookEntry;\n}\n\n// --- Parsing helpers ---\n\nfunction parseKeyValue(text: string): Record<string, string> {\n\tconst result: Record<string, string> = {};\n\tfor (const match of text.matchAll(/(\\w+)=(\\S+)/g)) {\n\t\tconst key = match[1];\n\t\tconst value = match[2];\n\t\tif (key && value) result[key] = value;\n\t}\n\treturn result;\n}\n\nfunction parseTimestamp(line: string): string {\n\tconst m = line.match(/^\\[([^\\]]+)\\]/);\n\treturn m?.[1] ?? \"\";\n}\n\nfunction parseEventType(line: string): string {\n\tconst m = line.match(/^\\[[^\\]]+\\]\\s+(\\S+)/);\n\treturn m?.[1] ?? \"\";\n}\n\nfunction parseEventBody(line: string): string {\n\tconst m = line.match(/^\\[[^\\]]+\\]\\s+\\S+\\s*(.*)/);\n\treturn m?.[1] ?? \"\";\n}\n\n// --- Debug log parsing ---\n\nfunction parseDebugLog(content: string, sessionStartTime?: Date): SessionRun[] {\n\tconst lines = content.split(\"\\n\").filter((l) => l.trim());\n\tconst sessions: SessionRun[] = [];\n\tlet current: SessionRun | null = null;\n\n\tfor (const line of lines) {\n\t\tconst event = parseEventType(line);\n\t\tconst body = parseEventBody(line);\n\t\tconst ts = parseTimestamp(line);\n\n\t\tswitch (event) {\n\t\t\tcase \"RUN_START\": {\n\t\t\t\t// Skip runs that predate the current session's log files\n\t\t\t\tif (sessionStartTime && new Date(ts) < sessionStartTime) {\n\t\t\t\t\tcurrent = null;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tconst kv = parseKeyValue(body);\n\t\t\t\tcurrent = {\n\t\t\t\t\tstart: {\n\t\t\t\t\t\ttimestamp: ts,\n\t\t\t\t\t\tmode: kv.mode ?? \"unknown\",\n\t\t\t\t\t\tbaseRef: kv.base_ref,\n\t\t\t\t\t\tfilesChanged: Number(kv.files_changed ?? kv.changes ?? 0),\n\t\t\t\t\t\tlinesAdded: Number(kv.lines_added ?? 0),\n\t\t\t\t\t\tlinesRemoved: Number(kv.lines_removed ?? 0),\n\t\t\t\t\t\tgates: Number(kv.gates ?? 0),\n\t\t\t\t\t},\n\t\t\t\t\tgates: [],\n\t\t\t\t};\n\t\t\t\tsessions.push(current);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"GATE_RESULT\": {\n\t\t\t\tif (!current) break;\n\t\t\t\tconst gateIdMatch = body.match(/^(\\S+)/);\n\t\t\t\tconst kv = parseKeyValue(body);\n\t\t\t\tcurrent.gates.push({\n\t\t\t\t\ttimestamp: ts,\n\t\t\t\t\tgateId: gateIdMatch?.[1] ?? \"unknown\",\n\t\t\t\t\tcli: kv.cli,\n\t\t\t\t\tstatus: kv.status ?? \"unknown\",\n\t\t\t\t\tduration: kv.duration ?? \"?\",\n\t\t\t\t\tviolations:\n\t\t\t\t\t\tkv.violations !== undefined ? Number(kv.violations) : undefined,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"RUN_END\": {\n\t\t\t\tif (!current) break;\n\t\t\t\tconst kv = parseKeyValue(body);\n\t\t\t\tcurrent.end = {\n\t\t\t\t\ttimestamp: ts,\n\t\t\t\t\tstatus: kv.status ?? \"unknown\",\n\t\t\t\t\tfixed: Number(kv.fixed ?? 0),\n\t\t\t\t\tskipped: Number(kv.skipped ?? 0),\n\t\t\t\t\tfailed: Number(kv.failed ?? 0),\n\t\t\t\t\titerations: Number(kv.iterations ?? 0),\n\t\t\t\t\tduration: kv.duration ?? \"?\",\n\t\t\t\t};\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"STOP_HOOK\": {\n\t\t\t\tif (!current) break;\n\t\t\t\tconst kv = parseKeyValue(body);\n\t\t\t\tcurrent.stopHook = {\n\t\t\t\t\ttimestamp: ts,\n\t\t\t\t\tdecision: kv.decision ?? \"unknown\",\n\t\t\t\t\treason: kv.reason ?? \"unknown\",\n\t\t\t\t};\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn sessions;\n}\n\n/**\n * Find the earliest mtime of non-hidden log files in the directory.\n * This marks the start of the current session.\n */\nfunction getSessionStartTime(logDir: string): Date | undefined {\n\tconst entries = fs\n\t\t.readdirSync(logDir)\n\t\t.filter((f) => !f.startsWith(\".\") && f !== \"previous\");\n\tlet earliest: number | undefined;\n\tfor (const entry of entries) {\n\t\tconst mtime = fs.statSync(path.join(logDir, entry)).mtimeMs;\n\t\tif (earliest === undefined || mtime < earliest) {\n\t\t\tearliest = mtime;\n\t\t}\n\t}\n\treturn earliest !== undefined ? new Date(earliest) : undefined;\n}\n\n// --- File inventory ---\n\nfunction formatFileInventory(logDir: string): string[] {\n\tconst lines: string[] = [];\n\tconst entries = fs\n\t\t.readdirSync(logDir)\n\t\t.filter((f) => !f.startsWith(\".\") && f !== \"previous\");\n\tif (entries.length === 0) return lines;\n\n\tconst checks: string[] = [];\n\tconst reviews: string[] = [];\n\tconst other: string[] = [];\n\n\tfor (const entry of entries.sort()) {\n\t\tconst fullPath = path.join(logDir, entry);\n\t\tconst stat = fs.statSync(fullPath);\n\t\tconst sizeKB = (stat.size / 1024).toFixed(1);\n\t\tconst line = `- ${fullPath} (${sizeKB} KB)`;\n\n\t\tif (entry.startsWith(\"review_\")) {\n\t\t\treviews.push(line);\n\t\t} else if (entry.startsWith(\"check_\")) {\n\t\t\tchecks.push(line);\n\t\t} else {\n\t\t\tother.push(line);\n\t\t}\n\t}\n\n\tlines.push(\"### Log Files\");\n\tlines.push(\"\");\n\tif (checks.length > 0) {\n\t\tlines.push(\"**Check logs:**\");\n\t\tlines.push(...checks);\n\t}\n\tif (reviews.length > 0) {\n\t\tlines.push(\"**Review logs/JSON:**\");\n\t\tlines.push(...reviews);\n\t}\n\tif (other.length > 0) {\n\t\tlines.push(\"**Other:**\");\n\t\tlines.push(...other);\n\t}\n\tlines.push(\"\");\n\n\treturn lines;\n}\n\n// --- Summary output ---\n\nfunction formatStatusLine(end: RunEnd): string {\n\treturn end.status === \"pass\"\n\t\t? \"PASSED\"\n\t\t: end.status === \"fail\"\n\t\t\t? \"FAILED\"\n\t\t\t: end.status.toUpperCase();\n}\n\nfunction formatAllRuns(sessions: SessionRun[]): string[] {\n\tconst lines: string[] = [];\n\tlines.push(\"### All Runs in Session\");\n\tlines.push(\"\");\n\tfor (let i = 0; i < sessions.length; i++) {\n\t\tconst s = sessions[i];\n\t\tif (!s) continue;\n\t\tconst status = s.end ? s.end.status : \"in-progress\";\n\t\tconst duration = s.end ? s.end.duration : \"?\";\n\t\tlines.push(\n\t\t\t`${i + 1}. [${s.start.timestamp}] mode=${s.start.mode} status=${status} duration=${duration}`,\n\t\t);\n\t}\n\tlines.push(\"\");\n\treturn lines;\n}\n\nfunction formatSession(sessions: SessionRun[], logDir: string): string {\n\tif (sessions.length === 0) {\n\t\treturn \"No gauntlet runs found in logs.\";\n\t}\n\n\tconst lastComplete = [...sessions].reverse().find((s) => s.end);\n\tconst session = lastComplete ?? sessions[sessions.length - 1];\n\tif (!session) return \"No gauntlet runs found in logs.\";\n\n\tconst lines: string[] = [];\n\n\t// Header\n\tlines.push(\"## Gauntlet Session Summary\");\n\tlines.push(\"\");\n\n\t// Overall status\n\tif (session.end) {\n\t\tlines.push(`**Status:** ${formatStatusLine(session.end)}`);\n\t\tlines.push(`**Iterations:** ${session.end.iterations}`);\n\t\tlines.push(`**Duration:** ${session.end.duration}`);\n\t\tlines.push(\n\t\t\t`**Fixed:** ${session.end.fixed} | **Skipped:** ${session.end.skipped} | **Failed:** ${session.end.failed}`,\n\t\t);\n\t} else {\n\t\tlines.push(\"**Status:** In Progress (no RUN_END found)\");\n\t}\n\tlines.push(\"\");\n\n\t// Diff stats\n\tlines.push(\"### Diff Stats\");\n\tlines.push(`- Mode: ${session.start.mode}`);\n\tif (session.start.baseRef) {\n\t\tlines.push(`- Base ref: ${session.start.baseRef}`);\n\t}\n\tlines.push(`- Files changed: ${session.start.filesChanged}`);\n\tlines.push(\n\t\t`- Lines: +${session.start.linesAdded} / -${session.start.linesRemoved}`,\n\t);\n\tlines.push(`- Gates: ${session.start.gates}`);\n\tlines.push(\"\");\n\n\t// Gate results\n\tlines.push(\"### Gate Results\");\n\tlines.push(\"\");\n\tlines.push(\"| Gate | CLI | Status | Duration | Violations |\");\n\tlines.push(\"|------|-----|--------|----------|------------|\");\n\tfor (const gate of session.gates) {\n\t\tconst violations =\n\t\t\tgate.violations !== undefined ? String(gate.violations) : \"-\";\n\t\tconst statusIcon = gate.status === \"pass\" ? \"pass\" : \"FAIL\";\n\t\tlines.push(\n\t\t\t`| ${gate.gateId} | ${gate.cli ?? \"-\"} | ${statusIcon} | ${gate.duration} | ${violations} |`,\n\t\t);\n\t}\n\tlines.push(\"\");\n\n\t// Stop hook\n\tif (session.stopHook) {\n\t\tlines.push(\"### Stop Hook\");\n\t\tlines.push(`- Decision: ${session.stopHook.decision}`);\n\t\tlines.push(`- Reason: ${session.stopHook.reason}`);\n\t\tlines.push(\"\");\n\t}\n\n\t// File inventory\n\tlines.push(...formatFileInventory(logDir));\n\n\t// All sessions summary (if multiple runs)\n\tif (sessions.length > 1) {\n\t\tlines.push(...formatAllRuns(sessions));\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// --- Main ---\n\n/**\n * Read the configured log_dir from .gauntlet/config.yml.\n * Falls back to \"gauntlet_logs\" if not found.\n */\nfunction getLogDir(cwd: string): string {\n\tconst configPath = path.join(cwd, \".gauntlet\", \"config.yml\");\n\ttry {\n\t\tconst content = fs.readFileSync(configPath, \"utf-8\");\n\t\tconst match = content.match(/^log_dir:\\s*(.+)$/m);\n\t\tif (match?.[1]) return match[1].trim();\n\t} catch {\n\t\t// Config not found — use default\n\t}\n\treturn \"gauntlet_logs\";\n}\n\n/**\n * Resolve the log directory and debug log path.\n * Returns null if no logs are found (after printing a message).\n */\nfunction resolveLogPaths(\n\tactiveDir: string,\n): { logDir: string; debugLogPath: string } | null {\n\tconst previousDir = path.join(activeDir, \"previous\");\n\tconst debugLogPath = path.join(activeDir, \".debug.log\");\n\n\t// Check active directory first for non-debug log files\n\tconst activeHasLogs =\n\t\tfs.existsSync(activeDir) &&\n\t\tfs\n\t\t\t.readdirSync(activeDir)\n\t\t\t.some((f) => !f.startsWith(\".\") && f !== \"previous\");\n\n\tif (activeHasLogs) {\n\t\treturn { logDir: activeDir, debugLogPath };\n\t}\n\n\tif (!fs.existsSync(previousDir)) {\n\t\tconsole.log(\"No gauntlet_logs directory found.\");\n\t\treturn null;\n\t}\n\n\t// Fall back to previous directory — cleanLogs archives files directly here\n\tconst logDir = resolvePreviousLogDir(previousDir);\n\tif (!logDir) return null;\n\n\t// Debug log stays in the main gauntlet_logs dir, not in previous/\n\treturn { logDir, debugLogPath };\n}\n\nfunction resolvePreviousLogDir(previousDir: string): string | null {\n\tconst prevEntries = fs.readdirSync(previousDir);\n\tconst hasDirectFiles = prevEntries.some(\n\t\t(f) => f.endsWith(\".log\") || f.endsWith(\".json\"),\n\t);\n\n\tif (hasDirectFiles) return previousDir;\n\n\t// Legacy: check for timestamped subdirectories\n\tconst prevDirs = prevEntries\n\t\t.map((d) => path.join(previousDir, d))\n\t\t.filter((d) => fs.statSync(d).isDirectory())\n\t\t.sort()\n\t\t.reverse();\n\n\tif (prevDirs.length === 0) {\n\t\tconsole.log(\"No gauntlet logs found.\");\n\t\treturn null;\n\t}\n\n\treturn prevDirs[0] as string;\n}\n\nfunction main(): void {\n\tconst cwd = process.cwd();\n\tconst logDirName = getLogDir(cwd);\n\tconst activeDir = path.join(cwd, logDirName);\n\n\tconst paths = resolveLogPaths(activeDir);\n\tif (!paths) {\n\t\tprocess.exit(0);\n\t}\n\n\t// Parse debug log, filtering to current session based on log file timestamps\n\tlet sessions: SessionRun[] = [];\n\tif (fs.existsSync(paths.debugLogPath)) {\n\t\tconst debugContent = fs.readFileSync(paths.debugLogPath, \"utf-8\");\n\t\tconst sessionStart = getSessionStartTime(paths.logDir);\n\t\tsessions = parseDebugLog(debugContent, sessionStart);\n\t}\n\n\t// Format and output\n\tconst output = formatSession(sessions, paths.logDir);\n\tconsole.log(output);\n}\n\nmain();\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;AAaA;AACA;AAgDA,SAAS,aAAa,CAAC,MAAsC;AAAA,EAC5D,MAAM,SAAiC,CAAC;AAAA,EACxC,WAAW,SAAS,KAAK,SAAS,cAAc,GAAG;AAAA,IAClD,MAAM,MAAM,MAAM;AAAA,IAClB,MAAM,QAAQ,MAAM;AAAA,IACpB,IAAI,OAAO;AAAA,MAAO,OAAO,OAAO;AAAA,EACjC;AAAA,EACA,OAAO;AAAA;AAGR,SAAS,cAAc,CAAC,MAAsB;AAAA,EAC7C,MAAM,IAAI,KAAK,MAAM,eAAe;AAAA,EACpC,OAAO,IAAI,MAAM;AAAA;AAGlB,SAAS,cAAc,CAAC,MAAsB;AAAA,EAC7C,MAAM,IAAI,KAAK,MAAM,qBAAqB;AAAA,EAC1C,OAAO,IAAI,MAAM;AAAA;AAGlB,SAAS,cAAc,CAAC,MAAsB;AAAA,EAC7C,MAAM,IAAI,KAAK,MAAM,0BAA0B;AAAA,EAC/C,OAAO,IAAI,MAAM;AAAA;AAKlB,SAAS,aAAa,CAAC,SAAiB,kBAAuC;AAAA,EAC9E,MAAM,QAAQ,QAAQ,MAAM;AAAA,CAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;AAAA,EACxD,MAAM,WAAyB,CAAC;AAAA,EAChC,IAAI,UAA6B;AAAA,EAEjC,WAAW,QAAQ,OAAO;AAAA,IACzB,MAAM,QAAQ,eAAe,IAAI;AAAA,IACjC,MAAM,OAAO,eAAe,IAAI;AAAA,IAChC,MAAM,KAAK,eAAe,IAAI;AAAA,IAE9B,QAAQ;AAAA,WACF,aAAa;AAAA,QAEjB,IAAI,oBAAoB,IAAI,KAAK,EAAE,IAAI,kBAAkB;AAAA,UACxD,UAAU;AAAA,UACV;AAAA,QACD;AAAA,QACA,MAAM,KAAK,cAAc,IAAI;AAAA,QAC7B,UAAU;AAAA,UACT,OAAO;AAAA,YACN,WAAW;AAAA,YACX,MAAM,GAAG,QAAQ;AAAA,YACjB,SAAS,GAAG;AAAA,YACZ,cAAc,OAAO,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAAA,YACxD,YAAY,OAAO,GAAG,eAAe,CAAC;AAAA,YACtC,cAAc,OAAO,GAAG,iBAAiB,CAAC;AAAA,YAC1C,OAAO,OAAO,GAAG,SAAS,CAAC;AAAA,UAC5B;AAAA,UACA,OAAO,CAAC;AAAA,QACT;AAAA,QACA,SAAS,KAAK,OAAO;AAAA,QACrB;AAAA,MACD;AAAA,WACK,eAAe;AAAA,QACnB,IAAI,CAAC;AAAA,UAAS;AAAA,QACd,MAAM,cAAc,KAAK,MAAM,QAAQ;AAAA,QACvC,MAAM,KAAK,cAAc,IAAI;AAAA,QAC7B,QAAQ,MAAM,KAAK;AAAA,UAClB,WAAW;AAAA,UACX,QAAQ,cAAc,MAAM;AAAA,UAC5B,KAAK,GAAG;AAAA,UACR,QAAQ,GAAG,UAAU;AAAA,UACrB,UAAU,GAAG,YAAY;AAAA,UACzB,YACC,GAAG,eAAe,YAAY,OAAO,GAAG,UAAU,IAAI;AAAA,QACxD,CAAC;AAAA,QACD;AAAA,MACD;AAAA,WACK,WAAW;AAAA,QACf,IAAI,CAAC;AAAA,UAAS;AAAA,QACd,MAAM,KAAK,cAAc,IAAI;AAAA,QAC7B,QAAQ,MAAM;AAAA,UACb,WAAW;AAAA,UACX,QAAQ,GAAG,UAAU;AAAA,UACrB,OAAO,OAAO,GAAG,SAAS,CAAC;AAAA,UAC3B,SAAS,OAAO,GAAG,WAAW,CAAC;AAAA,UAC/B,QAAQ,OAAO,GAAG,UAAU,CAAC;AAAA,UAC7B,YAAY,OAAO,GAAG,cAAc,CAAC;AAAA,UACrC,UAAU,GAAG,YAAY;AAAA,QAC1B;AAAA,QACA;AAAA,MACD;AAAA,WACK,aAAa;AAAA,QACjB,IAAI,CAAC;AAAA,UAAS;AAAA,QACd,MAAM,KAAK,cAAc,IAAI;AAAA,QAC7B,QAAQ,WAAW;AAAA,UAClB,WAAW;AAAA,UACX,UAAU,GAAG,YAAY;AAAA,UACzB,QAAQ,GAAG,UAAU;AAAA,QACtB;AAAA,QACA;AAAA,MACD;AAAA;AAAA,EAEF;AAAA,EAEA,OAAO;AAAA;AAOR,SAAS,mBAAmB,CAAC,QAAkC;AAAA,EAC9D,MAAM,UAAU,GACd,YAAY,MAAM,EAClB,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,KAAK,MAAM,UAAU;AAAA,EACtD,IAAI;AAAA,EACJ,WAAW,SAAS,SAAS;AAAA,IAC5B,MAAM,QAAQ,GAAG,SAAS,KAAK,KAAK,QAAQ,KAAK,CAAC,EAAE;AAAA,IACpD,IAAI,aAAa,aAAa,QAAQ,UAAU;AAAA,MAC/C,WAAW;AAAA,IACZ;AAAA,EACD;AAAA,EACA,OAAO,aAAa,YAAY,IAAI,KAAK,QAAQ,IAAI;AAAA;AAKtD,SAAS,mBAAmB,CAAC,QAA0B;AAAA,EACtD,MAAM,QAAkB,CAAC;AAAA,EACzB,MAAM,UAAU,GACd,YAAY,MAAM,EAClB,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,KAAK,MAAM,UAAU;AAAA,EACtD,IAAI,QAAQ,WAAW;AAAA,IAAG,OAAO;AAAA,EAEjC,MAAM,SAAmB,CAAC;AAAA,EAC1B,MAAM,UAAoB,CAAC;AAAA,EAC3B,MAAM,QAAkB,CAAC;AAAA,EAEzB,WAAW,SAAS,QAAQ,KAAK,GAAG;AAAA,IACnC,MAAM,WAAW,KAAK,KAAK,QAAQ,KAAK;AAAA,IACxC,MAAM,OAAO,GAAG,SAAS,QAAQ;AAAA,IACjC,MAAM,UAAU,KAAK,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC3C,MAAM,OAAO,KAAK,aAAa;AAAA,IAE/B,IAAI,MAAM,WAAW,SAAS,GAAG;AAAA,MAChC,QAAQ,KAAK,IAAI;AAAA,IAClB,EAAO,SAAI,MAAM,WAAW,QAAQ,GAAG;AAAA,MACtC,OAAO,KAAK,IAAI;AAAA,IACjB,EAAO;AAAA,MACN,MAAM,KAAK,IAAI;AAAA;AAAA,EAEjB;AAAA,EAEA,MAAM,KAAK,eAAe;AAAA,EAC1B,MAAM,KAAK,EAAE;AAAA,EACb,IAAI,OAAO,SAAS,GAAG;AAAA,IACtB,MAAM,KAAK,iBAAiB;AAAA,IAC5B,MAAM,KAAK,GAAG,MAAM;AAAA,EACrB;AAAA,EACA,IAAI,QAAQ,SAAS,GAAG;AAAA,IACvB,MAAM,KAAK,uBAAuB;AAAA,IAClC,MAAM,KAAK,GAAG,OAAO;AAAA,EACtB;AAAA,EACA,IAAI,MAAM,SAAS,GAAG;AAAA,IACrB,MAAM,KAAK,YAAY;AAAA,IACvB,MAAM,KAAK,GAAG,KAAK;AAAA,EACpB;AAAA,EACA,MAAM,KAAK,EAAE;AAAA,EAEb,OAAO;AAAA;AAKR,SAAS,gBAAgB,CAAC,KAAqB;AAAA,EAC9C,OAAO,IAAI,WAAW,SACnB,WACA,IAAI,WAAW,SACd,WACA,IAAI,OAAO,YAAY;AAAA;AAG5B,SAAS,aAAa,CAAC,UAAkC;AAAA,EACxD,MAAM,QAAkB,CAAC;AAAA,EACzB,MAAM,KAAK,yBAAyB;AAAA,EACpC,MAAM,KAAK,EAAE;AAAA,EACb,SAAS,IAAI,EAAG,IAAI,SAAS,QAAQ,KAAK;AAAA,IACzC,MAAM,IAAI,SAAS;AAAA,IACnB,IAAI,CAAC;AAAA,MAAG;AAAA,IACR,MAAM,SAAS,EAAE,MAAM,EAAE,IAAI,SAAS;AAAA,IACtC,MAAM,WAAW,EAAE,MAAM,EAAE,IAAI,WAAW;AAAA,IAC1C,MAAM,KACL,GAAG,IAAI,OAAO,EAAE,MAAM,mBAAmB,EAAE,MAAM,eAAe,mBAAmB,UACpF;AAAA,EACD;AAAA,EACA,MAAM,KAAK,EAAE;AAAA,EACb,OAAO;AAAA;AAGR,SAAS,aAAa,CAAC,UAAwB,QAAwB;AAAA,EACtE,IAAI,SAAS,WAAW,GAAG;AAAA,IAC1B,OAAO;AAAA,EACR;AAAA,EAEA,MAAM,eAAe,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,GAAG;AAAA,EAC9D,MAAM,UAAU,gBAAgB,SAAS,SAAS,SAAS;AAAA,EAC3D,IAAI,CAAC;AAAA,IAAS,OAAO;AAAA,EAErB,MAAM,QAAkB,CAAC;AAAA,EAGzB,MAAM,KAAK,6BAA6B;AAAA,EACxC,MAAM,KAAK,EAAE;AAAA,EAGb,IAAI,QAAQ,KAAK;AAAA,IAChB,MAAM,KAAK,eAAe,iBAAiB,QAAQ,GAAG,GAAG;AAAA,IACzD,MAAM,KAAK,mBAAmB,QAAQ,IAAI,YAAY;AAAA,IACtD,MAAM,KAAK,iBAAiB,QAAQ,IAAI,UAAU;AAAA,IAClD,MAAM,KACL,cAAc,QAAQ,IAAI,wBAAwB,QAAQ,IAAI,yBAAyB,QAAQ,IAAI,QACpG;AAAA,EACD,EAAO;AAAA,IACN,MAAM,KAAK,4CAA4C;AAAA;AAAA,EAExD,MAAM,KAAK,EAAE;AAAA,EAGb,MAAM,KAAK,gBAAgB;AAAA,EAC3B,MAAM,KAAK,WAAW,QAAQ,MAAM,MAAM;AAAA,EAC1C,IAAI,QAAQ,MAAM,SAAS;AAAA,IAC1B,MAAM,KAAK,eAAe,QAAQ,MAAM,SAAS;AAAA,EAClD;AAAA,EACA,MAAM,KAAK,oBAAoB,QAAQ,MAAM,cAAc;AAAA,EAC3D,MAAM,KACL,aAAa,QAAQ,MAAM,iBAAiB,QAAQ,MAAM,cAC3D;AAAA,EACA,MAAM,KAAK,YAAY,QAAQ,MAAM,OAAO;AAAA,EAC5C,MAAM,KAAK,EAAE;AAAA,EAGb,MAAM,KAAK,kBAAkB;AAAA,EAC7B,MAAM,KAAK,EAAE;AAAA,EACb,MAAM,KAAK,iDAAiD;AAAA,EAC5D,MAAM,KAAK,iDAAiD;AAAA,EAC5D,WAAW,QAAQ,QAAQ,OAAO;AAAA,IACjC,MAAM,aACL,KAAK,eAAe,YAAY,OAAO,KAAK,UAAU,IAAI;AAAA,IAC3D,MAAM,aAAa,KAAK,WAAW,SAAS,SAAS;AAAA,IACrD,MAAM,KACL,KAAK,KAAK,YAAY,KAAK,OAAO,SAAS,gBAAgB,KAAK,cAAc,cAC/E;AAAA,EACD;AAAA,EACA,MAAM,KAAK,EAAE;AAAA,EAGb,IAAI,QAAQ,UAAU;AAAA,IACrB,MAAM,KAAK,eAAe;AAAA,IAC1B,MAAM,KAAK,eAAe,QAAQ,SAAS,UAAU;AAAA,IACrD,MAAM,KAAK,aAAa,QAAQ,SAAS,QAAQ;AAAA,IACjD,MAAM,KAAK,EAAE;AAAA,EACd;AAAA,EAGA,MAAM,KAAK,GAAG,oBAAoB,MAAM,CAAC;AAAA,EAGzC,IAAI,SAAS,SAAS,GAAG;AAAA,IACxB,MAAM,KAAK,GAAG,cAAc,QAAQ,CAAC;AAAA,EACtC;AAAA,EAEA,OAAO,MAAM,KAAK;AAAA,CAAI;AAAA;AASvB,SAAS,SAAS,CAAC,KAAqB;AAAA,EACvC,MAAM,aAAa,KAAK,KAAK,KAAK,aAAa,YAAY;AAAA,EAC3D,IAAI;AAAA,IACH,MAAM,UAAU,GAAG,aAAa,YAAY,OAAO;AAAA,IACnD,MAAM,QAAQ,QAAQ,MAAM,oBAAoB;AAAA,IAChD,IAAI,QAAQ;AAAA,MAAI,OAAO,MAAM,GAAG,KAAK;AAAA,IACpC,MAAM;AAAA,EAGR,OAAO;AAAA;AAOR,SAAS,eAAe,CACvB,WACkD;AAAA,EAClD,MAAM,cAAc,KAAK,KAAK,WAAW,UAAU;AAAA,EACnD,MAAM,eAAe,KAAK,KAAK,WAAW,YAAY;AAAA,EAGtD,MAAM,gBACL,GAAG,WAAW,SAAS,KACvB,GACE,YAAY,SAAS,EACrB,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,KAAK,MAAM,UAAU;AAAA,EAErD,IAAI,eAAe;AAAA,IAClB,OAAO,EAAE,QAAQ,WAAW,aAAa;AAAA,EAC1C;AAAA,EAEA,IAAI,CAAC,GAAG,WAAW,WAAW,GAAG;AAAA,IAChC,QAAQ,IAAI,mCAAmC;AAAA,IAC/C,OAAO;AAAA,EACR;AAAA,EAGA,MAAM,SAAS,sBAAsB,WAAW;AAAA,EAChD,IAAI,CAAC;AAAA,IAAQ,OAAO;AAAA,EAGpB,OAAO,EAAE,QAAQ,aAAa;AAAA;AAG/B,SAAS,qBAAqB,CAAC,aAAoC;AAAA,EAClE,MAAM,cAAc,GAAG,YAAY,WAAW;AAAA,EAC9C,MAAM,iBAAiB,YAAY,KAClC,CAAC,MAAM,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,OAAO,CAChD;AAAA,EAEA,IAAI;AAAA,IAAgB,OAAO;AAAA,EAG3B,MAAM,WAAW,YACf,IAAI,CAAC,MAAM,KAAK,KAAK,aAAa,CAAC,CAAC,EACpC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,YAAY,CAAC,EAC1C,KAAK,EACL,QAAQ;AAAA,EAEV,IAAI,SAAS,WAAW,GAAG;AAAA,IAC1B,QAAQ,IAAI,yBAAyB;AAAA,IACrC,OAAO;AAAA,EACR;AAAA,EAEA,OAAO,SAAS;AAAA;AAGjB,SAAS,IAAI,GAAS;AAAA,EACrB,MAAM,MAAM,QAAQ,IAAI;AAAA,EACxB,MAAM,aAAa,UAAU,GAAG;AAAA,EAChC,MAAM,YAAY,KAAK,KAAK,KAAK,UAAU;AAAA,EAE3C,MAAM,QAAQ,gBAAgB,SAAS;AAAA,EACvC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,KAAK,CAAC;AAAA,EACf;AAAA,EAGA,IAAI,WAAyB,CAAC;AAAA,EAC9B,IAAI,GAAG,WAAW,MAAM,YAAY,GAAG;AAAA,IACtC,MAAM,eAAe,GAAG,aAAa,MAAM,cAAc,OAAO;AAAA,IAChE,MAAM,eAAe,oBAAoB,MAAM,MAAM;AAAA,IACrD,WAAW,cAAc,cAAc,YAAY;AAAA,EACpD;AAAA,EAGA,MAAM,SAAS,cAAc,UAAU,MAAM,MAAM;AAAA,EACnD,QAAQ,IAAI,MAAM;AAAA;AAGnB,KAAK;",
|
|
8
|
+
"debugId": "4BED01809E659CB464756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-gauntlet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "A CLI tool for testing AI coding agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Paul Caplan",
|
|
@@ -17,34 +17,47 @@
|
|
|
17
17
|
"ai",
|
|
18
18
|
"agent",
|
|
19
19
|
"testing",
|
|
20
|
-
"
|
|
20
|
+
"node"
|
|
21
21
|
],
|
|
22
22
|
"files": [
|
|
23
|
-
"
|
|
23
|
+
"dist",
|
|
24
24
|
"README.md",
|
|
25
25
|
"LICENSE"
|
|
26
26
|
],
|
|
27
27
|
"bin": {
|
|
28
|
-
"agent-gauntlet": "
|
|
28
|
+
"agent-gauntlet": "dist/index.js"
|
|
29
29
|
},
|
|
30
30
|
"type": "module",
|
|
31
31
|
"engines": {
|
|
32
|
-
"
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
-
"build": "bun build --compile --minify --sourcemap ./src/index.ts --outfile bin/agent-gauntlet",
|
|
35
|
+
"build:bin": "bun build --compile --minify --sourcemap ./src/index.ts --outfile bin/agent-gauntlet",
|
|
36
|
+
"build:npm": "bun build.ts",
|
|
37
|
+
"prepublishOnly": "bun run build:npm",
|
|
36
38
|
"test": "bun test",
|
|
39
|
+
"test:e2e": "bun run build:npm && bun test/integration/stop-hook-e2e.ts",
|
|
37
40
|
"lint": "biome check src",
|
|
38
41
|
"typecheck": "tsc --noEmit && tsc --noEmit -p test/tsconfig.json",
|
|
39
42
|
"changeset": "changeset",
|
|
40
43
|
"version": "changeset version",
|
|
41
|
-
"release": "changeset publish"
|
|
44
|
+
"release": "changeset publish",
|
|
45
|
+
"run": "bun src/index.ts run",
|
|
46
|
+
"check": "bun src/index.ts check",
|
|
47
|
+
"clean": "bun src/index.ts clean",
|
|
48
|
+
"review": "bun src/index.ts review",
|
|
49
|
+
"detect": "bun src/index.ts detect",
|
|
50
|
+
"list": "bun src/index.ts list",
|
|
51
|
+
"health": "bun src/index.ts health",
|
|
52
|
+
"validate": "bun src/index.ts validate",
|
|
53
|
+
"wait-ci": "bun src/index.ts wait-ci"
|
|
42
54
|
},
|
|
43
55
|
"devDependencies": {
|
|
44
56
|
"@biomejs/biome": "^2.3.11",
|
|
45
57
|
"@changesets/changelog-github": "^0.5.2",
|
|
46
58
|
"@changesets/cli": "^2.29.8",
|
|
47
|
-
"@types/bun": "latest"
|
|
59
|
+
"@types/bun": "latest",
|
|
60
|
+
"@types/picomatch": "^4.0.2"
|
|
48
61
|
},
|
|
49
62
|
"peerDependencies": {
|
|
50
63
|
"typescript": "^5"
|
|
@@ -54,6 +67,7 @@
|
|
|
54
67
|
"chalk": "^5.6.2",
|
|
55
68
|
"commander": "^14.0.2",
|
|
56
69
|
"gray-matter": "^4.0.3",
|
|
70
|
+
"picomatch": "^4.0.3",
|
|
57
71
|
"yaml": "^2.8.2",
|
|
58
72
|
"zod": "^4.3.5"
|
|
59
73
|
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Code Quality Review
|
|
2
|
-
|
|
3
|
-
You are a senior software engineer performing a code review. Your primary goal is to identify **real problems** that could cause bugs, security vulnerabilities, or performance issues in production. Do not report style, formatting, naming conventions, or maintainability suggestions unless you see something egregious.
|
|
4
|
-
|
|
5
|
-
## Focus Areas (in priority order)
|
|
6
|
-
|
|
7
|
-
1. **Bugs** — Logic errors, null/undefined issues, race conditions, unhandled edge cases, resource leaks
|
|
8
|
-
2. **Security** — Injection vulnerabilities, auth/authz flaws, sensitive data exposure, input validation gaps
|
|
9
|
-
3. **Performance** — Algorithmic complexity issues, N+1 queries, blocking operations, memory problems
|
|
10
|
-
4. **Maintainability** — Unclear code, missing error handling, duplication
|
|
11
|
-
|
|
12
|
-
## Do NOT Report
|
|
13
|
-
|
|
14
|
-
- Style, formatting, or naming preferences
|
|
15
|
-
- Missing documentation, comments, or type annotations
|
|
16
|
-
- Suggestions for "better" abstractions or patterns that aren't broken
|
|
17
|
-
- Hypothetical issues that require unlikely preconditions
|
|
18
|
-
- Issues in code that wasn't changed in this diff
|
|
19
|
-
|
|
20
|
-
## Guidelines
|
|
21
|
-
|
|
22
|
-
- **Threshold**: only report issues you would block a PR over
|
|
23
|
-
- Explain **why** each issue is a problem with a concrete failure scenario
|
|
24
|
-
- Provide a **concrete fix** with corrected code
|
|
25
|
-
- If the status quo works correctly, it's not a violation
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// @ts-expect-error Bun text import
|
|
2
|
-
import codeQualityContent from "./code-quality.md" with { type: "text" };
|
|
3
|
-
|
|
4
|
-
const BUILT_IN_PREFIX = "built-in:";
|
|
5
|
-
|
|
6
|
-
const builtInSources: Record<string, string> = {
|
|
7
|
-
"code-quality": codeQualityContent,
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Check if a review name uses the built-in prefix.
|
|
12
|
-
*/
|
|
13
|
-
export function isBuiltInReview(name: string): boolean {
|
|
14
|
-
return name.startsWith(BUILT_IN_PREFIX);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Load a built-in review prompt by name. Returns the raw markdown content.
|
|
19
|
-
*/
|
|
20
|
-
export function loadBuiltInReview(name: string): string {
|
|
21
|
-
const source = builtInSources[name];
|
|
22
|
-
|
|
23
|
-
if (!source) {
|
|
24
|
-
throw new Error(`Unknown built-in review: "${name}"`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return source;
|
|
28
|
-
}
|
package/src/bun-plugins.d.ts
DELETED