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
package/src/scripts/status.ts
DELETED
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Gauntlet Status Script
|
|
4
|
-
*
|
|
5
|
-
* Parses the configured log_dir (default: gauntlet_logs/) to produce a structured
|
|
6
|
-
* summary of the most recent gauntlet session from the .debug.log, plus a file
|
|
7
|
-
* inventory of all log/JSON files for further inspection.
|
|
8
|
-
*
|
|
9
|
-
* This script handles structured data only (debug log events). Detailed failure
|
|
10
|
-
* analysis (reading individual check logs, review JSONs) is left to the caller
|
|
11
|
-
* (the /gauntlet-status skill) since log formats vary by check type.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import fs from "node:fs";
|
|
15
|
-
import path from "node:path";
|
|
16
|
-
|
|
17
|
-
// --- Types ---
|
|
18
|
-
|
|
19
|
-
interface RunStart {
|
|
20
|
-
timestamp: string;
|
|
21
|
-
mode: string;
|
|
22
|
-
baseRef?: string;
|
|
23
|
-
filesChanged: number;
|
|
24
|
-
linesAdded: number;
|
|
25
|
-
linesRemoved: number;
|
|
26
|
-
gates: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface GateResult {
|
|
30
|
-
timestamp: string;
|
|
31
|
-
gateId: string;
|
|
32
|
-
cli?: string;
|
|
33
|
-
status: string;
|
|
34
|
-
duration: string;
|
|
35
|
-
violations?: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface RunEnd {
|
|
39
|
-
timestamp: string;
|
|
40
|
-
status: string;
|
|
41
|
-
fixed: number;
|
|
42
|
-
skipped: number;
|
|
43
|
-
failed: number;
|
|
44
|
-
iterations: number;
|
|
45
|
-
duration: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface StopHookEntry {
|
|
49
|
-
timestamp: string;
|
|
50
|
-
decision: string;
|
|
51
|
-
reason: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface SessionRun {
|
|
55
|
-
start: RunStart;
|
|
56
|
-
gates: GateResult[];
|
|
57
|
-
end?: RunEnd;
|
|
58
|
-
stopHook?: StopHookEntry;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// --- Parsing helpers ---
|
|
62
|
-
|
|
63
|
-
function parseKeyValue(text: string): Record<string, string> {
|
|
64
|
-
const result: Record<string, string> = {};
|
|
65
|
-
for (const match of text.matchAll(/(\w+)=(\S+)/g)) {
|
|
66
|
-
const key = match[1];
|
|
67
|
-
const value = match[2];
|
|
68
|
-
if (key && value) result[key] = value;
|
|
69
|
-
}
|
|
70
|
-
return result;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function parseTimestamp(line: string): string {
|
|
74
|
-
const m = line.match(/^\[([^\]]+)\]/);
|
|
75
|
-
return m?.[1] ?? "";
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function parseEventType(line: string): string {
|
|
79
|
-
const m = line.match(/^\[[^\]]+\]\s+(\S+)/);
|
|
80
|
-
return m?.[1] ?? "";
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function parseEventBody(line: string): string {
|
|
84
|
-
const m = line.match(/^\[[^\]]+\]\s+\S+\s*(.*)/);
|
|
85
|
-
return m?.[1] ?? "";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// --- Debug log parsing ---
|
|
89
|
-
|
|
90
|
-
function parseDebugLog(content: string, sessionStartTime?: Date): SessionRun[] {
|
|
91
|
-
const lines = content.split("\n").filter((l) => l.trim());
|
|
92
|
-
const sessions: SessionRun[] = [];
|
|
93
|
-
let current: SessionRun | null = null;
|
|
94
|
-
|
|
95
|
-
for (const line of lines) {
|
|
96
|
-
const event = parseEventType(line);
|
|
97
|
-
const body = parseEventBody(line);
|
|
98
|
-
const ts = parseTimestamp(line);
|
|
99
|
-
|
|
100
|
-
switch (event) {
|
|
101
|
-
case "RUN_START": {
|
|
102
|
-
// Skip runs that predate the current session's log files
|
|
103
|
-
if (sessionStartTime && new Date(ts) < sessionStartTime) {
|
|
104
|
-
current = null;
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
const kv = parseKeyValue(body);
|
|
108
|
-
current = {
|
|
109
|
-
start: {
|
|
110
|
-
timestamp: ts,
|
|
111
|
-
mode: kv.mode ?? "unknown",
|
|
112
|
-
baseRef: kv.base_ref,
|
|
113
|
-
filesChanged: Number(kv.files_changed ?? kv.changes ?? 0),
|
|
114
|
-
linesAdded: Number(kv.lines_added ?? 0),
|
|
115
|
-
linesRemoved: Number(kv.lines_removed ?? 0),
|
|
116
|
-
gates: Number(kv.gates ?? 0),
|
|
117
|
-
},
|
|
118
|
-
gates: [],
|
|
119
|
-
};
|
|
120
|
-
sessions.push(current);
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
case "GATE_RESULT": {
|
|
124
|
-
if (!current) break;
|
|
125
|
-
const gateIdMatch = body.match(/^(\S+)/);
|
|
126
|
-
const kv = parseKeyValue(body);
|
|
127
|
-
current.gates.push({
|
|
128
|
-
timestamp: ts,
|
|
129
|
-
gateId: gateIdMatch?.[1] ?? "unknown",
|
|
130
|
-
cli: kv.cli,
|
|
131
|
-
status: kv.status ?? "unknown",
|
|
132
|
-
duration: kv.duration ?? "?",
|
|
133
|
-
violations:
|
|
134
|
-
kv.violations !== undefined ? Number(kv.violations) : undefined,
|
|
135
|
-
});
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
case "RUN_END": {
|
|
139
|
-
if (!current) break;
|
|
140
|
-
const kv = parseKeyValue(body);
|
|
141
|
-
current.end = {
|
|
142
|
-
timestamp: ts,
|
|
143
|
-
status: kv.status ?? "unknown",
|
|
144
|
-
fixed: Number(kv.fixed ?? 0),
|
|
145
|
-
skipped: Number(kv.skipped ?? 0),
|
|
146
|
-
failed: Number(kv.failed ?? 0),
|
|
147
|
-
iterations: Number(kv.iterations ?? 0),
|
|
148
|
-
duration: kv.duration ?? "?",
|
|
149
|
-
};
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
case "STOP_HOOK": {
|
|
153
|
-
if (!current) break;
|
|
154
|
-
const kv = parseKeyValue(body);
|
|
155
|
-
current.stopHook = {
|
|
156
|
-
timestamp: ts,
|
|
157
|
-
decision: kv.decision ?? "unknown",
|
|
158
|
-
reason: kv.reason ?? "unknown",
|
|
159
|
-
};
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return sessions;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Find the earliest mtime of non-hidden log files in the directory.
|
|
170
|
-
* This marks the start of the current session.
|
|
171
|
-
*/
|
|
172
|
-
function getSessionStartTime(logDir: string): Date | undefined {
|
|
173
|
-
const entries = fs
|
|
174
|
-
.readdirSync(logDir)
|
|
175
|
-
.filter((f) => !f.startsWith(".") && f !== "previous");
|
|
176
|
-
let earliest: number | undefined;
|
|
177
|
-
for (const entry of entries) {
|
|
178
|
-
const mtime = fs.statSync(path.join(logDir, entry)).mtimeMs;
|
|
179
|
-
if (earliest === undefined || mtime < earliest) {
|
|
180
|
-
earliest = mtime;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return earliest !== undefined ? new Date(earliest) : undefined;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// --- File inventory ---
|
|
187
|
-
|
|
188
|
-
function formatFileInventory(logDir: string): string[] {
|
|
189
|
-
const lines: string[] = [];
|
|
190
|
-
const entries = fs
|
|
191
|
-
.readdirSync(logDir)
|
|
192
|
-
.filter((f) => !f.startsWith(".") && f !== "previous");
|
|
193
|
-
if (entries.length === 0) return lines;
|
|
194
|
-
|
|
195
|
-
const checks: string[] = [];
|
|
196
|
-
const reviews: string[] = [];
|
|
197
|
-
const other: string[] = [];
|
|
198
|
-
|
|
199
|
-
for (const entry of entries.sort()) {
|
|
200
|
-
const fullPath = path.join(logDir, entry);
|
|
201
|
-
const stat = fs.statSync(fullPath);
|
|
202
|
-
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
203
|
-
const line = `- ${fullPath} (${sizeKB} KB)`;
|
|
204
|
-
|
|
205
|
-
if (entry.startsWith("review_")) {
|
|
206
|
-
reviews.push(line);
|
|
207
|
-
} else if (entry.startsWith("check_")) {
|
|
208
|
-
checks.push(line);
|
|
209
|
-
} else {
|
|
210
|
-
other.push(line);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
lines.push("### Log Files");
|
|
215
|
-
lines.push("");
|
|
216
|
-
if (checks.length > 0) {
|
|
217
|
-
lines.push("**Check logs:**");
|
|
218
|
-
lines.push(...checks);
|
|
219
|
-
}
|
|
220
|
-
if (reviews.length > 0) {
|
|
221
|
-
lines.push("**Review logs/JSON:**");
|
|
222
|
-
lines.push(...reviews);
|
|
223
|
-
}
|
|
224
|
-
if (other.length > 0) {
|
|
225
|
-
lines.push("**Other:**");
|
|
226
|
-
lines.push(...other);
|
|
227
|
-
}
|
|
228
|
-
lines.push("");
|
|
229
|
-
|
|
230
|
-
return lines;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// --- Summary output ---
|
|
234
|
-
|
|
235
|
-
function formatStatusLine(end: RunEnd): string {
|
|
236
|
-
return end.status === "pass"
|
|
237
|
-
? "PASSED"
|
|
238
|
-
: end.status === "fail"
|
|
239
|
-
? "FAILED"
|
|
240
|
-
: end.status.toUpperCase();
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function formatAllRuns(sessions: SessionRun[]): string[] {
|
|
244
|
-
const lines: string[] = [];
|
|
245
|
-
lines.push("### All Runs in Session");
|
|
246
|
-
lines.push("");
|
|
247
|
-
for (let i = 0; i < sessions.length; i++) {
|
|
248
|
-
const s = sessions[i];
|
|
249
|
-
if (!s) continue;
|
|
250
|
-
const status = s.end ? s.end.status : "in-progress";
|
|
251
|
-
const duration = s.end ? s.end.duration : "?";
|
|
252
|
-
lines.push(
|
|
253
|
-
`${i + 1}. [${s.start.timestamp}] mode=${s.start.mode} status=${status} duration=${duration}`,
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
lines.push("");
|
|
257
|
-
return lines;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function formatSession(sessions: SessionRun[], logDir: string): string {
|
|
261
|
-
if (sessions.length === 0) {
|
|
262
|
-
return "No gauntlet runs found in logs.";
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const lastComplete = [...sessions].reverse().find((s) => s.end);
|
|
266
|
-
const session = lastComplete ?? sessions[sessions.length - 1];
|
|
267
|
-
if (!session) return "No gauntlet runs found in logs.";
|
|
268
|
-
|
|
269
|
-
const lines: string[] = [];
|
|
270
|
-
|
|
271
|
-
// Header
|
|
272
|
-
lines.push("## Gauntlet Session Summary");
|
|
273
|
-
lines.push("");
|
|
274
|
-
|
|
275
|
-
// Overall status
|
|
276
|
-
if (session.end) {
|
|
277
|
-
lines.push(`**Status:** ${formatStatusLine(session.end)}`);
|
|
278
|
-
lines.push(`**Iterations:** ${session.end.iterations}`);
|
|
279
|
-
lines.push(`**Duration:** ${session.end.duration}`);
|
|
280
|
-
lines.push(
|
|
281
|
-
`**Fixed:** ${session.end.fixed} | **Skipped:** ${session.end.skipped} | **Failed:** ${session.end.failed}`,
|
|
282
|
-
);
|
|
283
|
-
} else {
|
|
284
|
-
lines.push("**Status:** In Progress (no RUN_END found)");
|
|
285
|
-
}
|
|
286
|
-
lines.push("");
|
|
287
|
-
|
|
288
|
-
// Diff stats
|
|
289
|
-
lines.push("### Diff Stats");
|
|
290
|
-
lines.push(`- Mode: ${session.start.mode}`);
|
|
291
|
-
if (session.start.baseRef) {
|
|
292
|
-
lines.push(`- Base ref: ${session.start.baseRef}`);
|
|
293
|
-
}
|
|
294
|
-
lines.push(`- Files changed: ${session.start.filesChanged}`);
|
|
295
|
-
lines.push(
|
|
296
|
-
`- Lines: +${session.start.linesAdded} / -${session.start.linesRemoved}`,
|
|
297
|
-
);
|
|
298
|
-
lines.push(`- Gates: ${session.start.gates}`);
|
|
299
|
-
lines.push("");
|
|
300
|
-
|
|
301
|
-
// Gate results
|
|
302
|
-
lines.push("### Gate Results");
|
|
303
|
-
lines.push("");
|
|
304
|
-
lines.push("| Gate | CLI | Status | Duration | Violations |");
|
|
305
|
-
lines.push("|------|-----|--------|----------|------------|");
|
|
306
|
-
for (const gate of session.gates) {
|
|
307
|
-
const violations =
|
|
308
|
-
gate.violations !== undefined ? String(gate.violations) : "-";
|
|
309
|
-
const statusIcon = gate.status === "pass" ? "pass" : "FAIL";
|
|
310
|
-
lines.push(
|
|
311
|
-
`| ${gate.gateId} | ${gate.cli ?? "-"} | ${statusIcon} | ${gate.duration} | ${violations} |`,
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
lines.push("");
|
|
315
|
-
|
|
316
|
-
// Stop hook
|
|
317
|
-
if (session.stopHook) {
|
|
318
|
-
lines.push("### Stop Hook");
|
|
319
|
-
lines.push(`- Decision: ${session.stopHook.decision}`);
|
|
320
|
-
lines.push(`- Reason: ${session.stopHook.reason}`);
|
|
321
|
-
lines.push("");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// File inventory
|
|
325
|
-
lines.push(...formatFileInventory(logDir));
|
|
326
|
-
|
|
327
|
-
// All sessions summary (if multiple runs)
|
|
328
|
-
if (sessions.length > 1) {
|
|
329
|
-
lines.push(...formatAllRuns(sessions));
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return lines.join("\n");
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// --- Main ---
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Read the configured log_dir from .gauntlet/config.yml.
|
|
339
|
-
* Falls back to "gauntlet_logs" if not found.
|
|
340
|
-
*/
|
|
341
|
-
function getLogDir(cwd: string): string {
|
|
342
|
-
const configPath = path.join(cwd, ".gauntlet", "config.yml");
|
|
343
|
-
try {
|
|
344
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
345
|
-
const match = content.match(/^log_dir:\s*(.+)$/m);
|
|
346
|
-
if (match?.[1]) return match[1].trim();
|
|
347
|
-
} catch {
|
|
348
|
-
// Config not found — use default
|
|
349
|
-
}
|
|
350
|
-
return "gauntlet_logs";
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Resolve the log directory and debug log path.
|
|
355
|
-
* Returns null if no logs are found (after printing a message).
|
|
356
|
-
*/
|
|
357
|
-
function resolveLogPaths(
|
|
358
|
-
activeDir: string,
|
|
359
|
-
): { logDir: string; debugLogPath: string } | null {
|
|
360
|
-
const previousDir = path.join(activeDir, "previous");
|
|
361
|
-
const debugLogPath = path.join(activeDir, ".debug.log");
|
|
362
|
-
|
|
363
|
-
// Check active directory first for non-debug log files
|
|
364
|
-
const activeHasLogs =
|
|
365
|
-
fs.existsSync(activeDir) &&
|
|
366
|
-
fs
|
|
367
|
-
.readdirSync(activeDir)
|
|
368
|
-
.some((f) => !f.startsWith(".") && f !== "previous");
|
|
369
|
-
|
|
370
|
-
if (activeHasLogs) {
|
|
371
|
-
return { logDir: activeDir, debugLogPath };
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (!fs.existsSync(previousDir)) {
|
|
375
|
-
console.log("No gauntlet_logs directory found.");
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Fall back to previous directory — cleanLogs archives files directly here
|
|
380
|
-
const logDir = resolvePreviousLogDir(previousDir);
|
|
381
|
-
if (!logDir) return null;
|
|
382
|
-
|
|
383
|
-
// Debug log stays in the main gauntlet_logs dir, not in previous/
|
|
384
|
-
return { logDir, debugLogPath };
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function resolvePreviousLogDir(previousDir: string): string | null {
|
|
388
|
-
const prevEntries = fs.readdirSync(previousDir);
|
|
389
|
-
const hasDirectFiles = prevEntries.some(
|
|
390
|
-
(f) => f.endsWith(".log") || f.endsWith(".json"),
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
if (hasDirectFiles) return previousDir;
|
|
394
|
-
|
|
395
|
-
// Legacy: check for timestamped subdirectories
|
|
396
|
-
const prevDirs = prevEntries
|
|
397
|
-
.map((d) => path.join(previousDir, d))
|
|
398
|
-
.filter((d) => fs.statSync(d).isDirectory())
|
|
399
|
-
.sort()
|
|
400
|
-
.reverse();
|
|
401
|
-
|
|
402
|
-
if (prevDirs.length === 0) {
|
|
403
|
-
console.log("No gauntlet logs found.");
|
|
404
|
-
return null;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return prevDirs[0] as string;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function main(): void {
|
|
411
|
-
const cwd = process.cwd();
|
|
412
|
-
const logDirName = getLogDir(cwd);
|
|
413
|
-
const activeDir = path.join(cwd, logDirName);
|
|
414
|
-
|
|
415
|
-
const paths = resolveLogPaths(activeDir);
|
|
416
|
-
if (!paths) {
|
|
417
|
-
process.exit(0);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Parse debug log, filtering to current session based on log file timestamps
|
|
421
|
-
let sessions: SessionRun[] = [];
|
|
422
|
-
if (fs.existsSync(paths.debugLogPath)) {
|
|
423
|
-
const debugContent = fs.readFileSync(paths.debugLogPath, "utf-8");
|
|
424
|
-
const sessionStart = getSessionStartTime(paths.logDir);
|
|
425
|
-
sessions = parseDebugLog(debugContent, sessionStart);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Format and output
|
|
429
|
-
const output = formatSession(sessions, paths.logDir);
|
|
430
|
-
console.log(output);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
main();
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
name: Gauntlet CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
discover:
|
|
11
|
-
name: Discover Jobs
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
outputs:
|
|
14
|
-
matrix: ${{ steps.discover.outputs.matrix }}
|
|
15
|
-
runtimes: ${{ steps.discover.outputs.runtimes }}
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v4
|
|
18
|
-
|
|
19
|
-
- name: Install agent-gauntlet
|
|
20
|
-
run: |
|
|
21
|
-
curl -fsSL https://bun.sh/install | bash
|
|
22
|
-
echo "$HOME/.bun/bin" >> $GITHUB_PATH
|
|
23
|
-
export PATH="$HOME/.bun/bin:$PATH"
|
|
24
|
-
bun add -g pacaplan/agent-gauntlet
|
|
25
|
-
|
|
26
|
-
- name: Discover gauntlet jobs
|
|
27
|
-
id: discover
|
|
28
|
-
run: |
|
|
29
|
-
output=$(agent-gauntlet ci list-jobs)
|
|
30
|
-
echo "matrix=$(echo "$output" | jq -c '.matrix')" >> $GITHUB_OUTPUT
|
|
31
|
-
echo "runtimes=$(echo "$output" | jq -c '.runtimes')" >> $GITHUB_OUTPUT
|
|
32
|
-
|
|
33
|
-
checks:
|
|
34
|
-
name: ${{ matrix.job.name }} (${{ matrix.job.entry_point }})
|
|
35
|
-
runs-on: ubuntu-latest
|
|
36
|
-
needs: discover
|
|
37
|
-
if: ${{ needs.discover.outputs.matrix != '[]' }}
|
|
38
|
-
strategy:
|
|
39
|
-
fail-fast: false
|
|
40
|
-
matrix:
|
|
41
|
-
job: ${{ fromJson(needs.discover.outputs.matrix) }}
|
|
42
|
-
|
|
43
|
-
# Services will be injected here by agent-gauntlet
|
|
44
|
-
|
|
45
|
-
steps:
|
|
46
|
-
- uses: actions/checkout@v4
|
|
47
|
-
|
|
48
|
-
- name: Set up Ruby
|
|
49
|
-
if: contains(matrix.job.runtimes, 'ruby')
|
|
50
|
-
uses: ruby/setup-ruby@v1
|
|
51
|
-
with:
|
|
52
|
-
ruby-version: ${{ fromJson(needs.discover.outputs.runtimes).ruby.version }}
|
|
53
|
-
bundler-cache: ${{ fromJson(needs.discover.outputs.runtimes).ruby.bundler_cache }}
|
|
54
|
-
working-directory: ${{ matrix.job.working_directory }}
|
|
55
|
-
|
|
56
|
-
- name: Set up Node
|
|
57
|
-
if: contains(matrix.job.runtimes, 'node')
|
|
58
|
-
uses: actions/setup-node@v4
|
|
59
|
-
with:
|
|
60
|
-
node-version: ${{ fromJson(needs.discover.outputs.runtimes).node.version }}
|
|
61
|
-
|
|
62
|
-
- name: Set up Bun
|
|
63
|
-
if: contains(matrix.job.runtimes, 'bun')
|
|
64
|
-
uses: oven-sh/setup-bun@v1
|
|
65
|
-
with:
|
|
66
|
-
bun-version: ${{ fromJson(needs.discover.outputs.runtimes).bun.version }}
|
|
67
|
-
|
|
68
|
-
- name: Run global setup
|
|
69
|
-
if: ${{ matrix.job.global_setup != '' }}
|
|
70
|
-
run: ${{ matrix.job.global_setup }}
|
|
71
|
-
|
|
72
|
-
- name: Run check setup
|
|
73
|
-
if: ${{ matrix.job.setup != '' }}
|
|
74
|
-
working-directory: ${{ matrix.job.working_directory }}
|
|
75
|
-
run: ${{ matrix.job.setup }}
|
|
76
|
-
|
|
77
|
-
- name: Run check
|
|
78
|
-
working-directory: ${{ matrix.job.working_directory }}
|
|
79
|
-
run: ${{ matrix.job.command }}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* All possible outcomes from gauntlet operations.
|
|
3
|
-
* Used by both the run executor and stop-hook - NO MAPPING REQUIRED.
|
|
4
|
-
*/
|
|
5
|
-
export type GauntletStatus =
|
|
6
|
-
// Run outcomes (from executor)
|
|
7
|
-
| "passed" // All gates passed
|
|
8
|
-
| "passed_with_warnings" // Some issues were skipped
|
|
9
|
-
| "no_applicable_gates" // No gates matched current changes
|
|
10
|
-
| "no_changes" // No changes detected
|
|
11
|
-
| "failed" // Gates failed, retries remaining
|
|
12
|
-
| "retry_limit_exceeded" // Max retries reached
|
|
13
|
-
| "lock_conflict" // Another run in progress
|
|
14
|
-
| "error" // Unexpected error (includes config errors)
|
|
15
|
-
| "pr_push_required" // Gates passed but PR needs to be created/updated
|
|
16
|
-
// CI workflow statuses (after PR is pushed)
|
|
17
|
-
| "ci_pending" // CI checks still running
|
|
18
|
-
| "ci_failed" // CI checks failed or review changes requested
|
|
19
|
-
| "ci_passed" // CI checks passed, no blocking reviews
|
|
20
|
-
| "ci_timeout" // CI wait attempts exhausted
|
|
21
|
-
// Stop-hook pre-checks (before running executor)
|
|
22
|
-
| "no_config" // No .gauntlet/config.yml found
|
|
23
|
-
| "stop_hook_active" // Infinite loop prevention
|
|
24
|
-
| "interval_not_elapsed" // Run interval hasn't passed
|
|
25
|
-
| "invalid_input" // Failed to parse hook JSON input
|
|
26
|
-
| "stop_hook_disabled"; // Stop hook disabled via configuration
|
|
27
|
-
|
|
28
|
-
export interface RunResult {
|
|
29
|
-
status: GauntletStatus;
|
|
30
|
-
/** Human-friendly message explaining the outcome */
|
|
31
|
-
message: string;
|
|
32
|
-
/** Number of gates that ran */
|
|
33
|
-
gatesRun?: number;
|
|
34
|
-
/** Number of gates that failed */
|
|
35
|
-
gatesFailed?: number;
|
|
36
|
-
/** Path to latest console log file */
|
|
37
|
-
consoleLogPath?: string;
|
|
38
|
-
/** Error message if status is "error" */
|
|
39
|
-
errorMessage?: string;
|
|
40
|
-
/** Interval minutes (when status is "interval_not_elapsed") */
|
|
41
|
-
intervalMinutes?: number;
|
|
42
|
-
/** Individual gate results (available when gates were executed) */
|
|
43
|
-
gateResults?: Array<{
|
|
44
|
-
jobId: string;
|
|
45
|
-
status: "pass" | "fail" | "error";
|
|
46
|
-
logPath?: string;
|
|
47
|
-
logPaths?: string[];
|
|
48
|
-
subResults?: Array<{
|
|
49
|
-
nameSuffix: string;
|
|
50
|
-
status: "pass" | "fail" | "error";
|
|
51
|
-
logPath?: string;
|
|
52
|
-
}>;
|
|
53
|
-
}>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Determine if a status should block the stop hook.
|
|
58
|
-
*/
|
|
59
|
-
export function isBlockingStatus(status: GauntletStatus): boolean {
|
|
60
|
-
return (
|
|
61
|
-
status === "failed" ||
|
|
62
|
-
status === "pr_push_required" ||
|
|
63
|
-
status === "ci_pending" ||
|
|
64
|
-
status === "ci_failed"
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Determine if a status indicates successful completion (exit code 0).
|
|
70
|
-
*/
|
|
71
|
-
export function isSuccessStatus(status: GauntletStatus): boolean {
|
|
72
|
-
return (
|
|
73
|
-
status === "passed" ||
|
|
74
|
-
status === "passed_with_warnings" ||
|
|
75
|
-
status === "no_applicable_gates" ||
|
|
76
|
-
status === "no_changes" ||
|
|
77
|
-
status === "ci_passed"
|
|
78
|
-
);
|
|
79
|
-
}
|