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.
Files changed (71) hide show
  1. package/README.md +25 -23
  2. package/dist/index.js +9226 -0
  3. package/dist/index.js.map +65 -0
  4. package/dist/scripts/status.js +280 -0
  5. package/dist/scripts/status.js.map +10 -0
  6. package/package.json +22 -8
  7. package/src/built-in-reviews/code-quality.md +0 -25
  8. package/src/built-in-reviews/index.ts +0 -28
  9. package/src/bun-plugins.d.ts +0 -4
  10. package/src/cli-adapters/claude.ts +0 -327
  11. package/src/cli-adapters/codex.ts +0 -290
  12. package/src/cli-adapters/cursor.ts +0 -128
  13. package/src/cli-adapters/gemini.ts +0 -510
  14. package/src/cli-adapters/github-copilot.ts +0 -141
  15. package/src/cli-adapters/index.ts +0 -250
  16. package/src/cli-adapters/thinking-budget.ts +0 -23
  17. package/src/commands/check.ts +0 -311
  18. package/src/commands/ci/index.ts +0 -15
  19. package/src/commands/ci/init.ts +0 -96
  20. package/src/commands/ci/list-jobs.ts +0 -90
  21. package/src/commands/clean.ts +0 -54
  22. package/src/commands/detect.ts +0 -173
  23. package/src/commands/health.ts +0 -169
  24. package/src/commands/help.ts +0 -34
  25. package/src/commands/index.ts +0 -13
  26. package/src/commands/init.ts +0 -1878
  27. package/src/commands/list.ts +0 -33
  28. package/src/commands/review.ts +0 -311
  29. package/src/commands/run.ts +0 -29
  30. package/src/commands/shared.ts +0 -267
  31. package/src/commands/stop-hook.ts +0 -567
  32. package/src/commands/validate.ts +0 -20
  33. package/src/commands/wait-ci.ts +0 -518
  34. package/src/config/ci-loader.ts +0 -33
  35. package/src/config/ci-schema.ts +0 -28
  36. package/src/config/global.ts +0 -87
  37. package/src/config/loader.ts +0 -301
  38. package/src/config/schema.ts +0 -165
  39. package/src/config/stop-hook-config.ts +0 -130
  40. package/src/config/types.ts +0 -65
  41. package/src/config/validator.ts +0 -592
  42. package/src/core/change-detector.ts +0 -137
  43. package/src/core/diff-stats.ts +0 -442
  44. package/src/core/entry-point.ts +0 -190
  45. package/src/core/job.ts +0 -96
  46. package/src/core/run-executor.ts +0 -621
  47. package/src/core/runner.ts +0 -290
  48. package/src/gates/check.ts +0 -118
  49. package/src/gates/resolve-check-command.ts +0 -21
  50. package/src/gates/result.ts +0 -54
  51. package/src/gates/review.ts +0 -1333
  52. package/src/hooks/adapters/claude-stop-hook.ts +0 -99
  53. package/src/hooks/adapters/cursor-stop-hook.ts +0 -122
  54. package/src/hooks/adapters/types.ts +0 -94
  55. package/src/hooks/stop-hook-handler.ts +0 -748
  56. package/src/index.ts +0 -47
  57. package/src/output/app-logger.ts +0 -214
  58. package/src/output/console-log.ts +0 -168
  59. package/src/output/console.ts +0 -359
  60. package/src/output/logger.ts +0 -126
  61. package/src/output/sinks/console-sink.ts +0 -59
  62. package/src/output/sinks/file-sink.ts +0 -110
  63. package/src/scripts/status.ts +0 -433
  64. package/src/templates/workflow.yml +0 -79
  65. package/src/types/gauntlet-status.ts +0 -79
  66. package/src/utils/debug-log.ts +0 -392
  67. package/src/utils/diff-parser.ts +0 -103
  68. package/src/utils/execution-state.ts +0 -472
  69. package/src/utils/log-parser.ts +0 -696
  70. package/src/utils/sanitizer.ts +0 -3
  71. package/src/utils/session-ref.ts +0 -91
@@ -1,696 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import type { ReviewFullJsonOutput } from "../gates/result.js";
4
- import { getCategoryLogger } from "../output/app-logger.js";
5
-
6
- export type { PreviousViolation } from "../gates/result.js";
7
-
8
- import type { PreviousViolation } from "../gates/result.js";
9
-
10
- const log = getCategoryLogger("log-parser");
11
-
12
- export interface AdapterFailure {
13
- adapterName: string; // e.g., 'claude', 'gemini'
14
- reviewIndex?: number; // 1-based review index from @N in filename
15
- violations: PreviousViolation[];
16
- }
17
-
18
- export interface PassedSlot {
19
- reviewIndex: number; // 1-based review index
20
- passIteration: number; // Iteration number when this slot passed
21
- adapter: string; // Adapter name that passed the review
22
- }
23
-
24
- /**
25
- * Result from findPreviousFailures that includes both failures and passed slots.
26
- * passedSlots maps jobId -> reviewIndex -> { adapter, passIteration }
27
- */
28
- export interface PreviousFailuresResult {
29
- failures: GateFailures[];
30
- passedSlots: Map<string, Map<number, PassedSlot>>;
31
- }
32
-
33
- export interface GateFailures {
34
- jobId: string; // This will be the sanitized Job ID (filename without extension)
35
- gateName: string; // Parsed or empty
36
- entryPoint: string; // Parsed or empty
37
- adapterFailures: AdapterFailure[]; // Failures grouped by adapter
38
- logPath: string;
39
- }
40
-
41
- /**
42
- * Parse a review filename to extract the job ID, adapter, review index, and run number.
43
- * Pattern: <jobId>_<adapter>@<reviewIndex>.<runNumber>.(log|json)
44
- * Returns null if the filename doesn't match the review pattern.
45
- */
46
- export function parseReviewFilename(filename: string): {
47
- jobId: string;
48
- adapter: string;
49
- reviewIndex: number;
50
- runNumber: number;
51
- ext: string;
52
- } | null {
53
- // Match: <prefix>_<adapter>@<index>.<runNum>.(log|json)
54
- const m = filename.match(/^(.+)_([^@]+)@(\d+)\.(\d+)\.(log|json)$/);
55
- if (!m) return null;
56
- const [, jobId, adapter, indexStr, runStr, ext] = m;
57
- if (!jobId || !adapter || !indexStr || !runStr || !ext) return null;
58
- return {
59
- jobId,
60
- adapter,
61
- reviewIndex: parseInt(indexStr, 10),
62
- runNumber: parseInt(runStr, 10),
63
- ext,
64
- };
65
- }
66
-
67
- /**
68
- * Parses a JSON review file.
69
- */
70
- export async function parseJsonReviewFile(
71
- jsonPath: string,
72
- ): Promise<GateFailures | null> {
73
- try {
74
- const content = await fs.readFile(jsonPath, "utf-8");
75
- const data: ReviewFullJsonOutput = JSON.parse(content);
76
- const filename = path.basename(jsonPath);
77
-
78
- // Extract jobId: strip the _adapter@index.runNum.json suffix
79
- const parsed = parseReviewFilename(filename);
80
- const jobId = parsed ? parsed.jobId : filename.replace(/\.\d+\.json$/, "");
81
-
82
- if (data.status === "pass" || data.status === "skipped_prior_pass") {
83
- return null;
84
- }
85
-
86
- const violations = (data.violations || []).map((v) => ({
87
- ...v,
88
- status: v.status || "new",
89
- }));
90
-
91
- if (violations.length === 0 && data.status === "fail") {
92
- violations.push({
93
- file: "unknown",
94
- line: "?",
95
- issue: "Previous run failed but no violations found in JSON",
96
- status: "new",
97
- });
98
- }
99
-
100
- if (violations.length === 0) return null;
101
-
102
- return {
103
- jobId,
104
- gateName: "",
105
- entryPoint: "",
106
- adapterFailures: [
107
- {
108
- adapterName: data.adapter,
109
- reviewIndex: parsed?.reviewIndex,
110
- violations,
111
- },
112
- ],
113
- logPath: jsonPath.replace(/\.json$/, ".log"),
114
- };
115
- } catch (error) {
116
- log.warn(`Failed to parse JSON review file: ${jsonPath} - ${error}`);
117
- return null;
118
- }
119
- }
120
-
121
- /**
122
- * Extract the log prefix (job ID) from a numbered log filename.
123
- * Handles both patterns:
124
- * - Check: `check_src_test.2.log` -> `check_src_test`
125
- * - Review (new): `review_src_claude@1.2.log` -> `review_src_claude@1`
126
- */
127
- export function extractPrefix(filename: string): string {
128
- // Pattern: <prefix>.<number>.(log|json)
129
- const m = filename.match(/^(.+)\.\d+\.(log|json)$/);
130
- if (m?.[1]) return m[1];
131
- // Fallback for non-numbered files
132
- return filename.replace(/\.(log|json)$/, "");
133
- }
134
-
135
- /**
136
- * Parses a single log file to extract failures per adapter.
137
- * Processes both review and check gates.
138
- */
139
- export async function parseLogFile(
140
- logPath: string,
141
- ): Promise<GateFailures | null> {
142
- try {
143
- const content = await fs.readFile(logPath, "utf-8");
144
- const filename = path.basename(logPath);
145
-
146
- // Try to parse as review filename with @index pattern
147
- const parsed = parseReviewFilename(filename);
148
- const jobId = parsed ? parsed.jobId : extractPrefix(filename);
149
-
150
- // Check if it's a review log
151
- if (content.includes("--- Review Output")) {
152
- const adapterFailures: AdapterFailure[] = [];
153
- const sectionRegex = /--- Review Output \(([^)]+)\) ---/g;
154
-
155
- let match: RegExpExecArray | null;
156
- const sections: { adapter: string; startIndex: number }[] = [];
157
-
158
- for (;;) {
159
- match = sectionRegex.exec(content);
160
- if (!match || !match[1]) break;
161
- sections.push({
162
- adapter: match[1],
163
- startIndex: match.index,
164
- });
165
- }
166
-
167
- if (sections.length === 0) return null;
168
-
169
- for (let i = 0; i < sections.length; i++) {
170
- const currentSection = sections[i];
171
- if (!currentSection) continue;
172
- const nextSection = sections[i + 1];
173
- const endIndex = nextSection ? nextSection.startIndex : content.length;
174
- const sectionContent = content.substring(
175
- currentSection.startIndex,
176
- endIndex,
177
- );
178
-
179
- const violations: PreviousViolation[] = [];
180
- const parsedResultMatch = sectionContent.match(
181
- /---\s*Parsed Result(?:\s+\(([^)]+)\))?\s*---([\s\S]*?)(?:$|---)/,
182
- );
183
-
184
- if (parsedResultMatch?.[2]) {
185
- const parsedContent = parsedResultMatch[2];
186
- if (parsedContent.includes("Status: PASS")) continue;
187
- const violationRegex = /^\d+\.\s+(.+?):(\d+|NaN|\?)\s+-\s+(.+)$/gm;
188
- let vMatch: RegExpExecArray | null;
189
- for (;;) {
190
- vMatch = violationRegex.exec(parsedContent);
191
- if (!vMatch || !vMatch[1] || !vMatch[2] || !vMatch[3]) break;
192
- const file = vMatch[1].trim();
193
- let line: number | string = vMatch[2];
194
- if (line !== "NaN" && line !== "?")
195
- line = parseInt(line as string, 10);
196
- const issue = vMatch[3].trim();
197
- let fix: string | undefined;
198
- const remainder = parsedContent.substring(
199
- vMatch.index + vMatch[0].length,
200
- );
201
- const fixMatch = remainder.match(/^\s+Fix:\s+(.+)$/m);
202
- const nextViolationIndex = remainder.search(/^\d+\./m);
203
- if (
204
- fixMatch?.index !== undefined &&
205
- fixMatch[1] &&
206
- (nextViolationIndex === -1 || fixMatch.index < nextViolationIndex)
207
- ) {
208
- fix = fixMatch[1].trim();
209
- }
210
- violations.push({ file, line, issue, fix });
211
- }
212
- } else {
213
- // Fallback JSON
214
- const firstBrace = sectionContent.indexOf("{");
215
- const lastBrace = sectionContent.lastIndexOf("}");
216
- if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
217
- try {
218
- const jsonStr = sectionContent.substring(
219
- firstBrace,
220
- lastBrace + 1,
221
- );
222
- const json = JSON.parse(jsonStr);
223
- if (json.violations && Array.isArray(json.violations)) {
224
- for (const v of json.violations) {
225
- if (v.file && v.issue) {
226
- violations.push({
227
- file: v.file,
228
- line: v.line || 0,
229
- issue: v.issue,
230
- fix: v.fix,
231
- status: v.status,
232
- result: v.result,
233
- });
234
- }
235
- }
236
- }
237
- } catch (_e) {}
238
- }
239
- }
240
-
241
- if (violations.length > 0) {
242
- adapterFailures.push({
243
- adapterName: currentSection.adapter,
244
- reviewIndex: parsed?.reviewIndex,
245
- violations,
246
- });
247
- } else if (parsedResultMatch?.[2]?.includes("Status: FAIL")) {
248
- adapterFailures.push({
249
- adapterName: currentSection.adapter,
250
- reviewIndex: parsed?.reviewIndex,
251
- violations: [
252
- {
253
- file: "unknown",
254
- line: "?",
255
- issue:
256
- "Previous run failed but specific violations could not be parsed",
257
- },
258
- ],
259
- });
260
- }
261
- }
262
-
263
- if (adapterFailures.length === 0) return null;
264
- return { jobId, gateName: "", entryPoint: "", adapterFailures, logPath };
265
- } else {
266
- // Check log
267
- if (content.includes("Result: pass")) return null;
268
-
269
- const hasFailure =
270
- content.includes("Result: fail") ||
271
- content.includes("Result: error") ||
272
- content.includes("Command failed:");
273
-
274
- if (!hasFailure) return null;
275
-
276
- return {
277
- jobId,
278
- gateName: "",
279
- entryPoint: "",
280
- adapterFailures: [
281
- {
282
- adapterName: "check",
283
- violations: [{ file: "check", line: 0, issue: "Check failed" }],
284
- },
285
- ],
286
- logPath,
287
- };
288
- }
289
- } catch (_error) {
290
- return null;
291
- }
292
- }
293
-
294
- export interface RunIteration {
295
- iteration: number;
296
- fixed: Array<{
297
- jobId: string;
298
- adapter?: string;
299
- details: string;
300
- }>;
301
- skipped: Array<{
302
- jobId: string;
303
- adapter?: string;
304
- file: string;
305
- line: number | string;
306
- issue: string;
307
- result?: string | null;
308
- }>;
309
- }
310
-
311
- /**
312
- * Reconstructs the history of fixes and skips after all iterations.
313
- */
314
- export async function reconstructHistory(
315
- logDir: string,
316
- ): Promise<RunIteration[]> {
317
- try {
318
- const files = await fs.readdir(logDir);
319
- const runNumbers = new Set<number>();
320
- for (const file of files) {
321
- const m = file.match(/\.(\d+)\.(log|json)$/);
322
- if (m?.[1]) runNumbers.add(parseInt(m[1], 10));
323
- }
324
-
325
- const sortedRuns = Array.from(runNumbers).sort((a, b) => a - b);
326
- const iterations: RunIteration[] = [];
327
-
328
- let previousFailuresByJob = new Map<string, PreviousViolation[]>();
329
-
330
- for (const runNum of sortedRuns) {
331
- const currentFailuresByJob = new Map<string, PreviousViolation[]>();
332
- const iteration: RunIteration = {
333
- iteration: runNum,
334
- fixed: [],
335
- skipped: [],
336
- };
337
-
338
- const runFiles = files.filter((f) => f.includes(`.${runNum}.`));
339
- const prefixes = new Set(runFiles.map((f) => extractPrefix(f)));
340
-
341
- for (const prefix of prefixes) {
342
- const jsonFile = runFiles.find(
343
- (f) => f.startsWith(`${prefix}.${runNum}.`) && f.endsWith(".json"),
344
- );
345
- const logFile = runFiles.find(
346
- (f) => f.startsWith(`${prefix}.${runNum}.`) && f.endsWith(".log"),
347
- );
348
-
349
- let failure: GateFailures | null = null;
350
- if (jsonFile) {
351
- failure = await parseJsonReviewFile(path.join(logDir, jsonFile));
352
- } else if (logFile) {
353
- failure = await parseLogFile(path.join(logDir, logFile));
354
- }
355
-
356
- if (failure) {
357
- for (const af of failure.adapterFailures) {
358
- const key = af.reviewIndex
359
- ? `${failure.jobId}:${af.reviewIndex}`
360
- : `${failure.jobId}:${af.adapterName}`;
361
- currentFailuresByJob.set(key, af.violations);
362
-
363
- for (const v of af.violations) {
364
- if (v.status === "skipped") {
365
- iteration.skipped.push({
366
- jobId: failure.jobId,
367
- adapter: af.adapterName,
368
- file: v.file,
369
- line: v.line,
370
- issue: v.issue,
371
- result: v.result,
372
- });
373
- }
374
- }
375
- }
376
- }
377
- }
378
-
379
- for (const [key, prevViolations] of previousFailuresByJob.entries()) {
380
- const current = currentFailuresByJob.get(key);
381
- const sep = key.lastIndexOf(":");
382
- const jobId = key.substring(0, sep);
383
- const adapter = key.substring(sep + 1);
384
-
385
- const trulyFixed = prevViolations.filter((pv) => {
386
- if (pv.status === "skipped") return false;
387
- return !current?.some(
388
- (cv) =>
389
- cv.file === pv.file &&
390
- cv.line === pv.line &&
391
- cv.issue === pv.issue,
392
- );
393
- });
394
-
395
- if (trulyFixed.length > 0) {
396
- if (jobId.startsWith("check_")) {
397
- iteration.fixed.push({
398
- jobId,
399
- details: `${trulyFixed.length} violations resolved`,
400
- });
401
- } else {
402
- for (const f of trulyFixed) {
403
- iteration.fixed.push({
404
- jobId,
405
- adapter,
406
- details: `${f.file}:${f.line} ${f.issue}`,
407
- });
408
- }
409
- }
410
- }
411
- }
412
-
413
- iterations.push(iteration);
414
- previousFailuresByJob = currentFailuresByJob;
415
- }
416
-
417
- return iterations;
418
- } catch (_e) {
419
- return [];
420
- }
421
- }
422
-
423
- /**
424
- * Checks if a JSON review file has status "pass" or "skipped_prior_pass".
425
- * Skipped slots are treated as passing since they represent a previously-passed review.
426
- */
427
- async function isJsonReviewPassing(jsonPath: string): Promise<boolean> {
428
- try {
429
- const content = await fs.readFile(jsonPath, "utf-8");
430
- const data: ReviewFullJsonOutput = JSON.parse(content);
431
- return data.status === "pass" || data.status === "skipped_prior_pass";
432
- } catch {
433
- return false;
434
- }
435
- }
436
-
437
- /**
438
- * Checks if a log file represents a passing review.
439
- * Treats both "Status: PASS" and "Status: skipped_prior_pass" as passing.
440
- */
441
- async function isLogReviewPassing(logPath: string): Promise<boolean> {
442
- try {
443
- const content = await fs.readFile(logPath, "utf-8");
444
- // Check for skipped review log (skipped slots are treated as passing)
445
- if (content.includes("Status: skipped_prior_pass")) {
446
- return true;
447
- }
448
- // Check for review log passing
449
- if (content.includes("--- Review Output")) {
450
- return content.includes("Status: PASS");
451
- }
452
- // Check for check log passing
453
- return content.includes("Result: pass");
454
- } catch {
455
- return false;
456
- }
457
- }
458
-
459
- /**
460
- * Finds all previous failures and passed slots from the log directory.
461
- * For review gates with the @<index> pattern, groups by (jobId, reviewIndex)
462
- * and returns the highest-numbered run for each index.
463
- * The resulting Map keys are the review index (as string) for lookup by the review gate.
464
- *
465
- * Also returns passedSlots: a map of jobId -> reviewIndex -> passIteration
466
- * for slots that passed in their most recent run.
467
- */
468
- export async function findPreviousFailures(
469
- logDir: string,
470
- gateFilter?: string,
471
- ): Promise<GateFailures[]>;
472
- export async function findPreviousFailures(
473
- logDir: string,
474
- gateFilter: string | undefined,
475
- includePassedSlots: true,
476
- ): Promise<PreviousFailuresResult>;
477
- export async function findPreviousFailures(
478
- logDir: string,
479
- gateFilter?: string,
480
- includePassedSlots?: boolean,
481
- ): Promise<GateFailures[] | PreviousFailuresResult> {
482
- try {
483
- const files = await fs.readdir(logDir);
484
- const gateFailures: GateFailures[] = [];
485
- // Map: jobId -> reviewIndex -> { adapter, passIteration }
486
- const passedSlots = new Map<string, Map<number, PassedSlot>>();
487
-
488
- // Separate review files (with @index) from check files
489
- // Group review files by (jobId, reviewIndex) -> highest run number
490
- const reviewSlotMap = new Map<
491
- string,
492
- { filename: string; runNumber: number; ext: string }
493
- >();
494
- const checkPrefixMap = new Map<string, Map<number, Set<string>>>();
495
-
496
- for (const file of files) {
497
- const isLog = file.endsWith(".log");
498
- const isJson = file.endsWith(".json");
499
- if (!isLog && !isJson) continue;
500
- if (gateFilter && !file.includes(gateFilter)) continue;
501
-
502
- const parsed = parseReviewFilename(file);
503
- if (parsed) {
504
- // Review file with @index pattern
505
- const slotKey = `${parsed.jobId}:${parsed.reviewIndex}`;
506
- const existing = reviewSlotMap.get(slotKey);
507
- // Update if: no existing entry, higher run number, or same run number but .json (prefer .json over .log)
508
- const shouldUpdate =
509
- !existing ||
510
- parsed.runNumber > existing.runNumber ||
511
- (parsed.runNumber === existing.runNumber &&
512
- parsed.ext === "json" &&
513
- existing.ext === "log");
514
- if (shouldUpdate) {
515
- reviewSlotMap.set(slotKey, {
516
- filename: file,
517
- runNumber: parsed.runNumber,
518
- ext: parsed.ext,
519
- });
520
- }
521
- } else {
522
- // Check file or legacy review file
523
- const m = file.match(/^(.+)\.(\d+)\.(log|json)$/);
524
- if (!m || !m[1] || !m[2] || !m[3]) continue;
525
-
526
- const prefix = m[1];
527
- const runNum = parseInt(m[2], 10);
528
- const ext = m[3];
529
-
530
- let runMap = checkPrefixMap.get(prefix);
531
- if (!runMap) {
532
- runMap = new Map();
533
- checkPrefixMap.set(prefix, runMap);
534
- }
535
-
536
- let exts = runMap.get(runNum);
537
- if (!exts) {
538
- exts = new Set();
539
- runMap.set(runNum, exts);
540
- }
541
- exts.add(ext);
542
- }
543
- }
544
-
545
- // Process review files grouped by slot (jobId + reviewIndex)
546
- // Group by jobId to produce a single GateFailures per job
547
- const jobReviewFailures = new Map<string, AdapterFailure[]>();
548
-
549
- for (const [slotKey, fileInfo] of reviewSlotMap.entries()) {
550
- const sepIdx = slotKey.lastIndexOf(":");
551
- const jobId = slotKey.substring(0, sepIdx);
552
- const reviewIndex = parseInt(slotKey.substring(sepIdx + 1), 10);
553
-
554
- // Extract adapter from filename
555
- const parsed = parseReviewFilename(fileInfo.filename);
556
- const adapter = parsed?.adapter || "unknown";
557
-
558
- // Check if this slot passed
559
- const filePath = path.join(logDir, fileInfo.filename);
560
- let isPassing = false;
561
- if (fileInfo.ext === "json") {
562
- isPassing = await isJsonReviewPassing(filePath);
563
- } else {
564
- isPassing = await isLogReviewPassing(filePath);
565
- }
566
-
567
- if (isPassing && includePassedSlots) {
568
- // Record this as a passed slot with adapter info
569
- let jobSlots = passedSlots.get(jobId);
570
- if (!jobSlots) {
571
- jobSlots = new Map();
572
- passedSlots.set(jobId, jobSlots);
573
- }
574
- jobSlots.set(reviewIndex, {
575
- reviewIndex,
576
- passIteration: fileInfo.runNumber,
577
- adapter,
578
- });
579
- continue; // Don't process as failure
580
- }
581
-
582
- let failure: GateFailures | null = null;
583
- if (fileInfo.ext === "json") {
584
- failure = await parseJsonReviewFile(filePath);
585
- } else {
586
- failure = await parseLogFile(filePath);
587
- }
588
-
589
- if (failure) {
590
- // Apply status filtering
591
- for (const af of failure.adapterFailures) {
592
- af.reviewIndex = reviewIndex;
593
- const filteredViolations: PreviousViolation[] = [];
594
- for (const v of af.violations) {
595
- const status = v.status || "new";
596
- if (status === "skipped") continue;
597
- if (
598
- status !== "new" &&
599
- status !== "fixed" &&
600
- status !== "skipped"
601
- ) {
602
- log.warn(
603
- `Unexpected status "${status}" for violation in ${jobId}. Treating as "new".`,
604
- );
605
- v.status = "new";
606
- }
607
- filteredViolations.push(v);
608
- }
609
- af.violations = filteredViolations;
610
-
611
- if (af.violations.length > 0) {
612
- let failures = jobReviewFailures.get(jobId);
613
- if (!failures) {
614
- failures = [];
615
- jobReviewFailures.set(jobId, failures);
616
- }
617
- failures.push(af);
618
- }
619
- }
620
- }
621
- }
622
-
623
- for (const [jobId, adapterFailures] of jobReviewFailures.entries()) {
624
- gateFailures.push({
625
- jobId,
626
- gateName: "",
627
- entryPoint: "",
628
- adapterFailures,
629
- logPath: path.join(logDir, `${jobId}.log`),
630
- });
631
- }
632
-
633
- // Process check files (non-review)
634
- for (const [prefix, runMap] of checkPrefixMap.entries()) {
635
- const latestRun = Math.max(...runMap.keys());
636
- const exts = runMap.get(latestRun);
637
- if (!exts) continue;
638
-
639
- let failure: GateFailures | null = null;
640
- if (exts.has("json")) {
641
- failure = await parseJsonReviewFile(
642
- path.join(logDir, `${prefix}.${latestRun}.json`),
643
- );
644
- } else if (exts.has("log")) {
645
- failure = await parseLogFile(
646
- path.join(logDir, `${prefix}.${latestRun}.log`),
647
- );
648
- }
649
-
650
- if (failure) {
651
- for (const af of failure.adapterFailures) {
652
- const filteredViolations: PreviousViolation[] = [];
653
- for (const v of af.violations) {
654
- const status = v.status || "new";
655
- if (status === "skipped") continue;
656
- if (
657
- status !== "new" &&
658
- status !== "fixed" &&
659
- status !== "skipped"
660
- ) {
661
- log.warn(
662
- `Unexpected status "${status}" for violation in ${failure.jobId}. Treating as "new".`,
663
- );
664
- v.status = "new";
665
- }
666
- filteredViolations.push(v);
667
- }
668
- af.violations = filteredViolations;
669
- }
670
-
671
- const totalViolations = failure.adapterFailures.reduce(
672
- (sum, af) => sum + af.violations.length,
673
- 0,
674
- );
675
- if (totalViolations > 0) {
676
- gateFailures.push(failure);
677
- }
678
- }
679
- }
680
-
681
- if (includePassedSlots) {
682
- return { failures: gateFailures, passedSlots };
683
- }
684
- return gateFailures;
685
- } catch (error: unknown) {
686
- if (
687
- typeof error === "object" &&
688
- error !== null &&
689
- "code" in error &&
690
- (error as { code: string }).code === "ENOENT"
691
- ) {
692
- return includePassedSlots ? { failures: [], passedSlots: new Map() } : [];
693
- }
694
- return includePassedSlots ? { failures: [], passedSlots: new Map() } : [];
695
- }
696
- }
@@ -1,3 +0,0 @@
1
- export function sanitizeJobId(jobId: string): string {
2
- return jobId.replace(/[^a-zA-Z0-9._-]/g, "_");
3
- }