@jphutchins/code-review 0.1.0-alpha.36-rc.0 → 0.1.0-alpha.37
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/dist/index.js +260 -201
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/VERSIONING.md +2 -1
- package/schema/findings.schema.json +2 -2
- package/templates/comment.eta +6 -0
package/dist/index.js
CHANGED
|
@@ -4,14 +4,151 @@ import { readFileSync, writeFileSync, statSync, copyFileSync, readdirSync } from
|
|
|
4
4
|
import { randomBytes } from 'crypto';
|
|
5
5
|
import { resolve as resolve$1, join, dirname, basename, extname } from 'path';
|
|
6
6
|
import { Eta } from 'eta';
|
|
7
|
+
import * as t from 'io-ts';
|
|
7
8
|
import parseDiff from 'parse-diff';
|
|
8
9
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
9
10
|
import _addFormats from 'ajv-formats';
|
|
10
|
-
import * as t from 'io-ts';
|
|
11
11
|
import { execFile } from 'child_process';
|
|
12
12
|
import { performance } from 'perf_hooks';
|
|
13
13
|
import { PathReporter } from 'io-ts/lib/PathReporter.js';
|
|
14
14
|
|
|
15
|
+
var SeverityCodec = t.union([
|
|
16
|
+
t.literal("critical"),
|
|
17
|
+
t.literal("major"),
|
|
18
|
+
t.literal("minor"),
|
|
19
|
+
t.literal("nit")
|
|
20
|
+
]);
|
|
21
|
+
var SideCodec = t.union([t.literal("RIGHT"), t.literal("LEFT")]);
|
|
22
|
+
var VerdictCodec = t.union([
|
|
23
|
+
t.literal("approve"),
|
|
24
|
+
t.literal("comment"),
|
|
25
|
+
t.literal("changes"),
|
|
26
|
+
t.literal("error")
|
|
27
|
+
]);
|
|
28
|
+
var LineNumber = t.refinement(
|
|
29
|
+
t.number,
|
|
30
|
+
(n) => Number.isInteger(n) && n >= 1,
|
|
31
|
+
"LineNumber"
|
|
32
|
+
);
|
|
33
|
+
var Confidence = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Confidence");
|
|
34
|
+
var SCHEMA_VERSION_RE = /^(0|[1-9]\d*)\.(\d+)\.(\d+)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
35
|
+
var SchemaVersion = t.refinement(
|
|
36
|
+
t.string,
|
|
37
|
+
(s) => SCHEMA_VERSION_RE.test(s),
|
|
38
|
+
"SchemaVersion"
|
|
39
|
+
);
|
|
40
|
+
var FindingShape = t.intersection([
|
|
41
|
+
t.type({
|
|
42
|
+
path: t.string,
|
|
43
|
+
start_line: LineNumber,
|
|
44
|
+
end_line: LineNumber,
|
|
45
|
+
severity: SeverityCodec,
|
|
46
|
+
title: t.string,
|
|
47
|
+
description: t.string,
|
|
48
|
+
reasoning: t.string,
|
|
49
|
+
confidence: Confidence
|
|
50
|
+
}),
|
|
51
|
+
t.partial({
|
|
52
|
+
side: SideCodec,
|
|
53
|
+
code: t.string,
|
|
54
|
+
code_url: t.string,
|
|
55
|
+
recommendation: t.string,
|
|
56
|
+
patch: t.string
|
|
57
|
+
})
|
|
58
|
+
]);
|
|
59
|
+
var EndGeStart = t.refinement(
|
|
60
|
+
FindingShape,
|
|
61
|
+
(f) => f.end_line >= f.start_line,
|
|
62
|
+
"EndGeStart"
|
|
63
|
+
);
|
|
64
|
+
var FindingCodec = t.exact(EndGeStart);
|
|
65
|
+
var FindingsCodec = t.exact(
|
|
66
|
+
t.type({
|
|
67
|
+
schema_version: SchemaVersion,
|
|
68
|
+
summary: t.string,
|
|
69
|
+
verdict: VerdictCodec,
|
|
70
|
+
findings: t.array(FindingCodec)
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
var TriageCodec = t.type({
|
|
74
|
+
safe: t.boolean,
|
|
75
|
+
reasons: t.string
|
|
76
|
+
});
|
|
77
|
+
var TokenCount = t.refinement(
|
|
78
|
+
t.number,
|
|
79
|
+
(n) => Number.isInteger(n) && n >= 0,
|
|
80
|
+
"TokenCount"
|
|
81
|
+
);
|
|
82
|
+
var ModelUsageEntryCodec = t.intersection([
|
|
83
|
+
t.type({
|
|
84
|
+
model: t.string,
|
|
85
|
+
input_tokens: TokenCount,
|
|
86
|
+
output_tokens: TokenCount
|
|
87
|
+
}),
|
|
88
|
+
t.partial({
|
|
89
|
+
cache_read_tokens: TokenCount,
|
|
90
|
+
cache_write_tokens: TokenCount
|
|
91
|
+
})
|
|
92
|
+
]);
|
|
93
|
+
var ResultEnvelopeCodec = t.intersection([
|
|
94
|
+
t.type({
|
|
95
|
+
schema_version: t.string,
|
|
96
|
+
findings: FindingsCodec,
|
|
97
|
+
models: t.array(ModelUsageEntryCodec),
|
|
98
|
+
turns: TokenCount,
|
|
99
|
+
duration_ms: TokenCount
|
|
100
|
+
}),
|
|
101
|
+
t.partial({
|
|
102
|
+
vendor_cost_usd: t.union([t.number, t.null]),
|
|
103
|
+
route: t.string,
|
|
104
|
+
effort: t.string,
|
|
105
|
+
// The run produced a notice rather than a completed review (security-gate block, agent kill, no
|
|
106
|
+
// recoverable findings). An empty `findings` array alone can't say this — a genuine clean review
|
|
107
|
+
// is also empty — so the render suppresses "clean review" and the sticky precedence guard refuses
|
|
108
|
+
// to bury a completed review under it. Absent ⇒ a completed review.
|
|
109
|
+
incomplete: t.boolean
|
|
110
|
+
})
|
|
111
|
+
]);
|
|
112
|
+
var ModelPricesCodec = t.type({
|
|
113
|
+
in: t.number,
|
|
114
|
+
out: t.number,
|
|
115
|
+
cache_read: t.number,
|
|
116
|
+
cache_write: t.number
|
|
117
|
+
});
|
|
118
|
+
var PriceMapCodec = t.type({
|
|
119
|
+
_updated: t.string,
|
|
120
|
+
_unit: t.string,
|
|
121
|
+
models: t.record(t.string, ModelPricesCodec)
|
|
122
|
+
});
|
|
123
|
+
var TestFailureCodec = t.intersection([
|
|
124
|
+
t.type({ name: t.string }),
|
|
125
|
+
t.partial({ message: t.string })
|
|
126
|
+
]);
|
|
127
|
+
var TestSummaryCodec = t.intersection([
|
|
128
|
+
t.type({
|
|
129
|
+
passed: t.number,
|
|
130
|
+
failed: t.number,
|
|
131
|
+
total: t.number
|
|
132
|
+
}),
|
|
133
|
+
t.partial({
|
|
134
|
+
failures: t.array(TestFailureCodec)
|
|
135
|
+
})
|
|
136
|
+
]);
|
|
137
|
+
var DEFAULT_SCHEMA_VERSION = "0.5.0";
|
|
138
|
+
var emptyFindings = (summary) => ({
|
|
139
|
+
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
140
|
+
summary,
|
|
141
|
+
verdict: "comment",
|
|
142
|
+
findings: []
|
|
143
|
+
});
|
|
144
|
+
var incompleteFindings = (summary) => ({
|
|
145
|
+
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
146
|
+
summary,
|
|
147
|
+
verdict: "error",
|
|
148
|
+
findings: []
|
|
149
|
+
});
|
|
150
|
+
var isIncompleteFindings = (findings) => findings.verdict === "error" && findings.findings.length === 0;
|
|
151
|
+
|
|
15
152
|
// src/cost.ts
|
|
16
153
|
var defaultWarn = (message) => {
|
|
17
154
|
process.stderr.write(`${message}
|
|
@@ -179,6 +316,13 @@ var encodeMarker = (document, jsonUrl, limit) => {
|
|
|
179
316
|
return marker ? `${AGENTS_STOP_DIRECTIVE}
|
|
180
317
|
${marker}` : "";
|
|
181
318
|
};
|
|
319
|
+
var decodeBase64Json = (b64) => {
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(Buffer.from(b64, "base64").toString("utf-8"));
|
|
322
|
+
} catch {
|
|
323
|
+
return void 0;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
182
326
|
var findingsPointer = (findings, jsonUrl, limit = EMBED_LIMIT) => encodeMarker(findings, jsonUrl, limit);
|
|
183
327
|
var findingPointer = (finding, schemaVersion, jsonUrl, limit = EMBED_LIMIT) => encodeMarker({ schema_version: schemaVersion, findings: [finding] }, jsonUrl, limit);
|
|
184
328
|
var ZERO_SHA = "0000000000000000000000000000000000000000";
|
|
@@ -189,21 +333,40 @@ var parseReviewedSha = (body) => {
|
|
|
189
333
|
var REVIEW_COMPLETE_MARKER = "<!-- review-complete -->";
|
|
190
334
|
var parseReviewComplete = (body) => body.includes(REVIEW_COMPLETE_MARKER);
|
|
191
335
|
var parseFindingsMarker = (body) => {
|
|
192
|
-
const
|
|
193
|
-
const b64 = match?.[1];
|
|
336
|
+
const b64 = /<!-- code-review:findings-json;base64 ([A-Za-z0-9+/=]+) -->/.exec(body)?.[1];
|
|
194
337
|
if (b64 === void 0) return null;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
338
|
+
return decodeBase64Json(b64) ?? null;
|
|
339
|
+
};
|
|
340
|
+
var ROUNDS_RE = /<!-- code-review:rounds;base64 ([A-Za-z0-9+/=]+) -->/;
|
|
341
|
+
var isSeverityCounts = (u) => typeof u === "object" && u !== null && ["critical", "major", "minor", "nit"].every((k) => {
|
|
342
|
+
const v = u[k];
|
|
343
|
+
return typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
344
|
+
});
|
|
345
|
+
var parseRounds = (body) => {
|
|
346
|
+
const b64 = ROUNDS_RE.exec(body)?.[1];
|
|
347
|
+
if (b64 === void 0) return [];
|
|
348
|
+
const decoded = decodeBase64Json(b64);
|
|
349
|
+
return Array.isArray(decoded) ? decoded.filter(isSeverityCounts) : [];
|
|
350
|
+
};
|
|
351
|
+
var roundsMarker = (rounds) => rounds.length === 0 ? "" : `<!-- code-review:rounds;base64 ${Buffer.from(JSON.stringify(rounds), "utf-8").toString("base64")} -->`;
|
|
352
|
+
var roundChip = (c) => {
|
|
353
|
+
const parts = ["critical", "major", "minor", "nit"].filter((k) => c[k] > 0).map((k) => `${severityEmoji(k)}${String(c[k])}`);
|
|
354
|
+
return parts.length === 0 ? "clean" : parts.join(" ");
|
|
355
|
+
};
|
|
356
|
+
var TRAJECTORY_CHIPS = 8;
|
|
357
|
+
var roundsSummary = (rounds) => {
|
|
358
|
+
if (rounds.length === 0) return "";
|
|
359
|
+
const chips = rounds.slice(-TRAJECTORY_CHIPS).map(roundChip);
|
|
360
|
+
const trajectory = rounds.length > TRAJECTORY_CHIPS ? `\u2026 \u2192 ${chips.join(" \u2192 ")}` : chips.join(" \u2192 ");
|
|
361
|
+
return `**Round ${String(rounds.length)}** \xB7 ${trajectory}`;
|
|
200
362
|
};
|
|
201
363
|
var carryForwardMarkers = (body) => {
|
|
202
364
|
const findings = /<!-- code-review:findings-json[^>]*-->/.exec(body)?.[0];
|
|
203
365
|
const reviewedSha = /<!-- reviewed-sha: [0-9a-fA-F]{40} -->/.exec(body)?.[0];
|
|
366
|
+
const rounds = ROUNDS_RE.exec(body)?.[0];
|
|
204
367
|
const findingsBlock = findings ? `${AGENTS_STOP_DIRECTIVE}
|
|
205
368
|
${findings}` : void 0;
|
|
206
|
-
return [findingsBlock, reviewedSha].filter((m) => m !== void 0).join("\n\n");
|
|
369
|
+
return [findingsBlock, reviewedSha, rounds].filter((m) => m !== void 0).join("\n\n");
|
|
207
370
|
};
|
|
208
371
|
var escapeFence = (text) => text.replace(/```/g, "`` ` ``");
|
|
209
372
|
var projectPatch = (patch) => {
|
|
@@ -243,7 +406,7 @@ var render = (input) => {
|
|
|
243
406
|
const eta = new Eta({ autoTrim: false });
|
|
244
407
|
const usageAvailable = input.envelope !== null;
|
|
245
408
|
const hasUsage = input.envelope !== null && input.envelope.models.length > 0;
|
|
246
|
-
const incomplete = input.incomplete ?? input.envelope?.incomplete ?? false;
|
|
409
|
+
const incomplete = (input.incomplete ?? input.envelope?.incomplete ?? false) || isIncompleteFindings(input.findings);
|
|
247
410
|
const costReport = input.envelope ? computeCost(input.envelope.models, input.prices) : null;
|
|
248
411
|
const pricesProvided = input.pricesProvided ?? true;
|
|
249
412
|
const route = input.route ?? input.envelope?.route ?? null;
|
|
@@ -270,6 +433,8 @@ var render = (input) => {
|
|
|
270
433
|
runUrl: input.runUrl ?? null,
|
|
271
434
|
jsonUrl: input.jsonUrl ?? null,
|
|
272
435
|
findingsPointer: input.findingsPointer ?? findingsPointer(input.findings, input.jsonUrl),
|
|
436
|
+
roundsMarker: roundsMarker(input.rounds ?? []),
|
|
437
|
+
roundsSummary: roundsSummary(input.rounds ?? []),
|
|
273
438
|
reviewUrl: input.reviewUrl ?? null,
|
|
274
439
|
formatTokens: (n) => Number.isFinite(n) && n >= 0 ? n.toLocaleString("en-US") : "\u2014",
|
|
275
440
|
// N/A (never a false $0.00) when no real price map was provided — real tokens, no rates to price them.
|
|
@@ -287,6 +452,8 @@ var render = (input) => {
|
|
|
287
452
|
return "\u{1F4AC} comment";
|
|
288
453
|
case "changes":
|
|
289
454
|
return "\u{1F527} changes requested";
|
|
455
|
+
case "error":
|
|
456
|
+
return "\u{1F6E0}\uFE0F no review verdict";
|
|
290
457
|
default:
|
|
291
458
|
return `\u2753 ${v}`;
|
|
292
459
|
}
|
|
@@ -525,132 +692,6 @@ var readTranscriptTree = (mainPath) => {
|
|
|
525
692
|
missing: mainText === null
|
|
526
693
|
};
|
|
527
694
|
};
|
|
528
|
-
var SeverityCodec = t.union([
|
|
529
|
-
t.literal("critical"),
|
|
530
|
-
t.literal("major"),
|
|
531
|
-
t.literal("minor"),
|
|
532
|
-
t.literal("nit")
|
|
533
|
-
]);
|
|
534
|
-
var SideCodec = t.union([t.literal("RIGHT"), t.literal("LEFT")]);
|
|
535
|
-
var VerdictCodec = t.union([t.literal("approve"), t.literal("comment"), t.literal("changes")]);
|
|
536
|
-
var LineNumber = t.refinement(
|
|
537
|
-
t.number,
|
|
538
|
-
(n) => Number.isInteger(n) && n >= 1,
|
|
539
|
-
"LineNumber"
|
|
540
|
-
);
|
|
541
|
-
var Confidence = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Confidence");
|
|
542
|
-
var SCHEMA_VERSION_RE = /^(0|[1-9]\d*)\.(\d+)\.(\d+)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
543
|
-
var SchemaVersion = t.refinement(
|
|
544
|
-
t.string,
|
|
545
|
-
(s) => SCHEMA_VERSION_RE.test(s),
|
|
546
|
-
"SchemaVersion"
|
|
547
|
-
);
|
|
548
|
-
var FindingShape = t.intersection([
|
|
549
|
-
t.type({
|
|
550
|
-
path: t.string,
|
|
551
|
-
start_line: LineNumber,
|
|
552
|
-
end_line: LineNumber,
|
|
553
|
-
severity: SeverityCodec,
|
|
554
|
-
title: t.string,
|
|
555
|
-
description: t.string,
|
|
556
|
-
reasoning: t.string,
|
|
557
|
-
confidence: Confidence
|
|
558
|
-
}),
|
|
559
|
-
t.partial({
|
|
560
|
-
side: SideCodec,
|
|
561
|
-
code: t.string,
|
|
562
|
-
code_url: t.string,
|
|
563
|
-
recommendation: t.string,
|
|
564
|
-
patch: t.string
|
|
565
|
-
})
|
|
566
|
-
]);
|
|
567
|
-
var EndGeStart = t.refinement(
|
|
568
|
-
FindingShape,
|
|
569
|
-
(f) => f.end_line >= f.start_line,
|
|
570
|
-
"EndGeStart"
|
|
571
|
-
);
|
|
572
|
-
var FindingCodec = t.exact(EndGeStart);
|
|
573
|
-
var FindingsCodec = t.exact(
|
|
574
|
-
t.type({
|
|
575
|
-
schema_version: SchemaVersion,
|
|
576
|
-
summary: t.string,
|
|
577
|
-
verdict: VerdictCodec,
|
|
578
|
-
findings: t.array(FindingCodec)
|
|
579
|
-
})
|
|
580
|
-
);
|
|
581
|
-
var TriageCodec = t.type({
|
|
582
|
-
safe: t.boolean,
|
|
583
|
-
reasons: t.string
|
|
584
|
-
});
|
|
585
|
-
var TokenCount = t.refinement(
|
|
586
|
-
t.number,
|
|
587
|
-
(n) => Number.isInteger(n) && n >= 0,
|
|
588
|
-
"TokenCount"
|
|
589
|
-
);
|
|
590
|
-
var ModelUsageEntryCodec = t.intersection([
|
|
591
|
-
t.type({
|
|
592
|
-
model: t.string,
|
|
593
|
-
input_tokens: TokenCount,
|
|
594
|
-
output_tokens: TokenCount
|
|
595
|
-
}),
|
|
596
|
-
t.partial({
|
|
597
|
-
cache_read_tokens: TokenCount,
|
|
598
|
-
cache_write_tokens: TokenCount
|
|
599
|
-
})
|
|
600
|
-
]);
|
|
601
|
-
var ResultEnvelopeCodec = t.intersection([
|
|
602
|
-
t.type({
|
|
603
|
-
schema_version: t.string,
|
|
604
|
-
findings: FindingsCodec,
|
|
605
|
-
models: t.array(ModelUsageEntryCodec),
|
|
606
|
-
turns: TokenCount,
|
|
607
|
-
duration_ms: TokenCount
|
|
608
|
-
}),
|
|
609
|
-
t.partial({
|
|
610
|
-
vendor_cost_usd: t.union([t.number, t.null]),
|
|
611
|
-
route: t.string,
|
|
612
|
-
effort: t.string,
|
|
613
|
-
// The run produced a notice rather than a completed review (security-gate block, agent kill, no
|
|
614
|
-
// recoverable findings). An empty `findings` array alone can't say this — a genuine clean review
|
|
615
|
-
// is also empty — so the render suppresses "clean review" and the sticky precedence guard refuses
|
|
616
|
-
// to bury a completed review under it. Absent ⇒ a completed review.
|
|
617
|
-
incomplete: t.boolean
|
|
618
|
-
})
|
|
619
|
-
]);
|
|
620
|
-
var ModelPricesCodec = t.type({
|
|
621
|
-
in: t.number,
|
|
622
|
-
out: t.number,
|
|
623
|
-
cache_read: t.number,
|
|
624
|
-
cache_write: t.number
|
|
625
|
-
});
|
|
626
|
-
var PriceMapCodec = t.type({
|
|
627
|
-
_updated: t.string,
|
|
628
|
-
_unit: t.string,
|
|
629
|
-
models: t.record(t.string, ModelPricesCodec)
|
|
630
|
-
});
|
|
631
|
-
var TestFailureCodec = t.intersection([
|
|
632
|
-
t.type({ name: t.string }),
|
|
633
|
-
t.partial({ message: t.string })
|
|
634
|
-
]);
|
|
635
|
-
var TestSummaryCodec = t.intersection([
|
|
636
|
-
t.type({
|
|
637
|
-
passed: t.number,
|
|
638
|
-
failed: t.number,
|
|
639
|
-
total: t.number
|
|
640
|
-
}),
|
|
641
|
-
t.partial({
|
|
642
|
-
failures: t.array(TestFailureCodec)
|
|
643
|
-
})
|
|
644
|
-
]);
|
|
645
|
-
var DEFAULT_SCHEMA_VERSION = "0.4.0";
|
|
646
|
-
var noticeFindings = (summary) => ({
|
|
647
|
-
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
648
|
-
summary,
|
|
649
|
-
verdict: "comment",
|
|
650
|
-
findings: []
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
// src/validate.ts
|
|
654
695
|
var addFormats = _addFormats;
|
|
655
696
|
var validatorCache = /* @__PURE__ */ new Map();
|
|
656
697
|
var compileSchema = (schemaPath) => {
|
|
@@ -773,8 +814,7 @@ var DEFAULT_RESERVE = {
|
|
|
773
814
|
frac: 0.15,
|
|
774
815
|
growth: 0.25,
|
|
775
816
|
flatUsd: 0.02,
|
|
776
|
-
flatMs: 12e4
|
|
777
|
-
flatMem: 2 * 1024 * 1024 * 1024
|
|
817
|
+
flatMs: 12e4
|
|
778
818
|
};
|
|
779
819
|
var SOFT_MULTIPLE = 2;
|
|
780
820
|
var growingReserve = (used, limit, flat, r) => {
|
|
@@ -801,7 +841,6 @@ var decideBudget = (i) => {
|
|
|
801
841
|
const worst = [costAxis(i), timeAxis(i)].filter((a) => a !== null).reduce((max, a) => Math.max(max, axisSeverity(a)), 0);
|
|
802
842
|
return worst === 2 ? { kind: "hard" } : worst === 1 ? { kind: "soft" } : { kind: "ok" };
|
|
803
843
|
};
|
|
804
|
-
var memoryCritical = (availMemBytes, totalMemBytes, floorBytes) => availMemBytes !== null && totalMemBytes !== null && totalMemBytes > 0 && availMemBytes <= floorBytes;
|
|
805
844
|
var pct = (n) => `${String(Math.round(n * 100))}%`;
|
|
806
845
|
var money = (n) => `$${n.toFixed(2)}`;
|
|
807
846
|
var mins = (ms) => `${(ms / 6e4).toFixed(1)}m`;
|
|
@@ -856,7 +895,6 @@ var lastValidPath = (draftPath) => {
|
|
|
856
895
|
};
|
|
857
896
|
var mainHasWrittenDraft = (draftMtimeMs, seedMarkerMtimeMs) => draftMtimeMs !== null && (seedMarkerMtimeMs === null || draftMtimeMs > seedMarkerMtimeMs);
|
|
858
897
|
var spawnFloorMessage = (draftPath) => `Write your own first-pass findings to ${draftPath} before spawning subagents \u2014 a review must never depend on subagents alone, and a pre-seeded draft does not count until you have revised it yourself this run. Write ${draftPath} from what you have read so far (preliminary findings are fine), run \`code-review validate ${draftPath} --explain\` until it passes, then fan out; your subagents run in the background, so keep refining the draft as their reports arrive.`;
|
|
859
|
-
var memoryPressureMessage = (draftPath) => `System memory is critically low right now, so another subagent can't be spawned \u2014 a fresh process is the fastest way to tip the runner into an out-of-memory kill that would lose the whole review. Don't wind down: keep reading code directly and keep folding the reports from subagents already running into ${draftPath}, then try spawning again in a moment \u2014 this clears as soon as running subagents finish and free their memory.`;
|
|
860
898
|
var forceBackgroundSpawn = (toolInput) => ({
|
|
861
899
|
hookSpecificOutput: {
|
|
862
900
|
hookEventName: "PreToolUse",
|
|
@@ -902,8 +940,6 @@ var evaluateBudgetHook = (input, params) => {
|
|
|
902
940
|
if (phase.kind === "hard" && blockedDuringConvergence(toolName, rec["tool_input"]))
|
|
903
941
|
return denyPreTool(budgetMessage(inputs, phase, params.draftPath, isSubagent));
|
|
904
942
|
if (SPAWN_TOOLS.has(toolName)) {
|
|
905
|
-
if (memoryCritical(params.availMemBytes, params.totalMemBytes, params.reserve.flatMem))
|
|
906
|
-
return denyPreTool(memoryPressureMessage(params.draftPath));
|
|
907
943
|
if (!isSubagent && !params.mainDraftWritten)
|
|
908
944
|
return denyPreTool(spawnFloorMessage(params.draftPath));
|
|
909
945
|
return forceBackgroundSpawn(rec["tool_input"]);
|
|
@@ -950,21 +986,6 @@ var parseFraction = (raw, fallback) => {
|
|
|
950
986
|
const n = Number.parseFloat(raw);
|
|
951
987
|
return Number.isFinite(n) && n >= 0 && n <= 1 ? n : fallback;
|
|
952
988
|
};
|
|
953
|
-
var BYTE_UNIT = {
|
|
954
|
-
"": 1,
|
|
955
|
-
k: 1024,
|
|
956
|
-
m: 1024 * 1024,
|
|
957
|
-
g: 1024 * 1024 * 1024,
|
|
958
|
-
t: 1024 * 1024 * 1024 * 1024
|
|
959
|
-
};
|
|
960
|
-
var parseByteSize = (raw) => {
|
|
961
|
-
const m = /^(\d+(?:\.\d+)?)\s*([kmgt])?(?:i?b)?$/i.exec(raw.trim());
|
|
962
|
-
if (m === null) return null;
|
|
963
|
-
const [, num = "", unit = ""] = m;
|
|
964
|
-
const n = Number.parseFloat(num);
|
|
965
|
-
const mult = BYTE_UNIT[unit.toLowerCase()];
|
|
966
|
-
return Number.isFinite(n) && mult !== void 0 ? n * mult : null;
|
|
967
|
-
};
|
|
968
989
|
var budgetHookCommand = (draftPath, opts) => [
|
|
969
990
|
"code-review budget-hook --draft",
|
|
970
991
|
shellQuote(draftPath),
|
|
@@ -974,8 +995,7 @@ var budgetHookCommand = (draftPath, opts) => [
|
|
|
974
995
|
...opts.reserveFrac ? ["--reserve-frac", shellQuote(opts.reserveFrac)] : [],
|
|
975
996
|
...opts.reserveGrowth ? ["--reserve-growth", shellQuote(opts.reserveGrowth)] : [],
|
|
976
997
|
...opts.reserveUsd ? ["--reserve-usd", shellQuote(opts.reserveUsd)] : [],
|
|
977
|
-
...opts.reserveWall ? ["--reserve-wall", shellQuote(opts.reserveWall)] : []
|
|
978
|
-
...opts.reserveMem ? ["--reserve-mem", shellQuote(opts.reserveMem)] : []
|
|
998
|
+
...opts.reserveWall ? ["--reserve-wall", shellQuote(opts.reserveWall)] : []
|
|
979
999
|
].join(" ");
|
|
980
1000
|
|
|
981
1001
|
// src/format.ts
|
|
@@ -1011,11 +1031,24 @@ var NOTICE_KINDS = [
|
|
|
1011
1031
|
"no-output"
|
|
1012
1032
|
];
|
|
1013
1033
|
var isNoticeKind = (s) => NOTICE_KINDS.some((k) => k === s);
|
|
1034
|
+
var UNSAFE_IN_SUMMARY = /[\n\r`<>|]/;
|
|
1035
|
+
var MAX_NAMED_HOSTS = 20;
|
|
1036
|
+
var parseAgentAllowlist = (sandboxConfig) => {
|
|
1037
|
+
const domains = sandboxConfig?.network?.allowedDomains;
|
|
1038
|
+
return Array.isArray(domains) ? domains.filter(
|
|
1039
|
+
(d) => typeof d === "string" && d.length <= 256 && !UNSAFE_IN_SUMMARY.test(d)
|
|
1040
|
+
).slice(0, MAX_NAMED_HOSTS) : [];
|
|
1041
|
+
};
|
|
1042
|
+
var egressNote = (agentAllowlist) => agentAllowlist.length === 0 ? "" : `
|
|
1043
|
+
|
|
1044
|
+
If the review failed because the agent could not reach a host it needed, note that its network egress is jailed to only ${agentAllowlist.map((host) => `\`${host}\``).join(
|
|
1045
|
+
", "
|
|
1046
|
+
)}. Add any missing host to the agent's egress allowlist \u2014 the reusable workflow's \`extra_endpoints\` input, or a single-file workflow's \`--extra\` flag on \`sandbox-config\`.`;
|
|
1014
1047
|
var blockquote = (text) => text.replaceAll("\n", "\n> ");
|
|
1015
1048
|
var reasoned = (lead, noReason, reasons) => typeof reasons === "string" && reasons.trim() !== "" ? `${lead}
|
|
1016
1049
|
|
|
1017
1050
|
> ${blockquote(reasons)}` : noReason;
|
|
1018
|
-
var noticeSummary = (kind, reasons) => {
|
|
1051
|
+
var noticeSummary = (kind, reasons, agentAllowlist) => {
|
|
1019
1052
|
switch (kind) {
|
|
1020
1053
|
case "security-blocked":
|
|
1021
1054
|
return reasoned(
|
|
@@ -1028,25 +1061,25 @@ var noticeSummary = (kind, reasons) => {
|
|
|
1028
1061
|
"### \u{1F6E0}\uFE0F Security gate could not evaluate\n\nThe security triage could not produce a verdict (operational error), so the review failed closed \u2014 this is an infrastructure failure, not a finding about this diff. Re-run to retry a transient fault; a persistent one is a configuration issue (see the workflow logs). The triage step reported:",
|
|
1029
1062
|
"### \u{1F6E0}\uFE0F Security gate could not evaluate\n\nThe security triage could not produce a verdict (operational error), so the review failed closed \u2014 this is an infrastructure failure, not a finding about this diff. Re-run to retry a transient fault; a persistent one is a configuration issue (see the workflow logs).",
|
|
1030
1063
|
reasons
|
|
1031
|
-
);
|
|
1064
|
+
) + egressNote(agentAllowlist);
|
|
1032
1065
|
case "setup-failed":
|
|
1033
1066
|
return "### \u{1F6E0}\uFE0F Review did not run\n\nThe review job failed before the security triage could run (e.g. dependency install or environment setup). See the workflow logs \u2014 this is an infrastructure failure, not a security verdict.";
|
|
1034
1067
|
case "checkout-failed":
|
|
1035
1068
|
return "### \u26A0\uFE0F Could not check out the PR head\n\nThe PR head commit could not be fetched or checked out (it may have been force-pushed away, or is otherwise unavailable), so the review was skipped rather than run against the wrong tree. See workflow logs.";
|
|
1036
1069
|
case "no-output":
|
|
1037
|
-
return "### \u26A0\uFE0F Review did not complete\n\nThe diff passed triage but the review produced no output. See workflow logs.";
|
|
1070
|
+
return "### \u26A0\uFE0F Review did not complete\n\nThe diff passed triage but the review produced no output. See workflow logs." + egressNote(agentAllowlist);
|
|
1038
1071
|
}
|
|
1039
1072
|
};
|
|
1040
1073
|
var noticeEnvelope = (summary) => ({
|
|
1041
1074
|
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
1042
|
-
findings:
|
|
1075
|
+
findings: incompleteFindings(summary),
|
|
1043
1076
|
models: [],
|
|
1044
1077
|
turns: 0,
|
|
1045
1078
|
duration_ms: 0,
|
|
1046
1079
|
vendor_cost_usd: null,
|
|
1047
1080
|
incomplete: true
|
|
1048
1081
|
});
|
|
1049
|
-
var buildNoticeEnvelope = (kind, reasons) => noticeEnvelope(noticeSummary(kind, reasons));
|
|
1082
|
+
var buildNoticeEnvelope = (kind, reasons, agentAllowlist = []) => noticeEnvelope(noticeSummary(kind, reasons, agentAllowlist));
|
|
1050
1083
|
var buildUnknownNoticeEnvelope = (kind) => noticeEnvelope(
|
|
1051
1084
|
`### \u26A0\uFE0F Review could not be rendered
|
|
1052
1085
|
|
|
@@ -1056,6 +1089,14 @@ var identity = (decoded) => decoded;
|
|
|
1056
1089
|
var findingsTable = [
|
|
1057
1090
|
{
|
|
1058
1091
|
minor: "0.4",
|
|
1092
|
+
defaultVersion: "0.4.0",
|
|
1093
|
+
schemaFile: "findings.schema.json",
|
|
1094
|
+
codec: FindingsCodec,
|
|
1095
|
+
normalize: identity,
|
|
1096
|
+
latest: false
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
minor: "0.5",
|
|
1059
1100
|
defaultVersion: DEFAULT_SCHEMA_VERSION,
|
|
1060
1101
|
schemaFile: "findings.schema.json",
|
|
1061
1102
|
codec: FindingsCodec,
|
|
@@ -1554,6 +1595,7 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1554
1595
|
);
|
|
1555
1596
|
const existingComplete = existingSticky !== null && parseReviewComplete(existingSticky.body);
|
|
1556
1597
|
const wouldBuryCompleted = (incomplete) => incomplete && existingComplete;
|
|
1598
|
+
const priorRounds = existingSticky !== null ? parseRounds(existingSticky.body) : [];
|
|
1557
1599
|
const leaveInPlace = () => {
|
|
1558
1600
|
process.stderr.write(
|
|
1559
1601
|
`Review did not complete and the sticky already reflects a completed review \u2014 leaving it in place
|
|
@@ -1570,7 +1612,7 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1570
1612
|
const inlineTemplate = readFileSync(input.inlineTemplatePath, "utf-8");
|
|
1571
1613
|
const renderNotice = (message) => formatMarkdown(
|
|
1572
1614
|
render({
|
|
1573
|
-
findings:
|
|
1615
|
+
findings: incompleteFindings(`### \u26A0\uFE0F ${message}`),
|
|
1574
1616
|
envelope: null,
|
|
1575
1617
|
incomplete: true,
|
|
1576
1618
|
prices: decodedPrices.right,
|
|
@@ -1579,6 +1621,7 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1579
1621
|
route: input.route,
|
|
1580
1622
|
reviewedSha: input.headSha,
|
|
1581
1623
|
effort: input.effort,
|
|
1624
|
+
rounds: priorRounds,
|
|
1582
1625
|
runUrl: input.runUrl,
|
|
1583
1626
|
jsonUrl: input.jsonUrl,
|
|
1584
1627
|
postedAt: input.postedAt
|
|
@@ -1611,16 +1654,20 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1611
1654
|
const envelope = loadEnvelope(input.envelopePath);
|
|
1612
1655
|
const testReport = input.testReportPath ? loadTestReport(input.testReportPath) : void 0;
|
|
1613
1656
|
if (envelope === null) {
|
|
1657
|
+
const envelopelessIncomplete = isIncompleteFindings(findings);
|
|
1658
|
+
if (wouldBuryCompleted(envelopelessIncomplete)) leaveInPlace();
|
|
1614
1659
|
const body = formatMarkdown(
|
|
1615
1660
|
render({
|
|
1616
1661
|
findings,
|
|
1617
1662
|
envelope: null,
|
|
1663
|
+
incomplete: envelopelessIncomplete,
|
|
1618
1664
|
prices: decodedPrices.right,
|
|
1619
1665
|
pricesProvided: input.pricesProvided,
|
|
1620
1666
|
template,
|
|
1621
1667
|
route: input.route,
|
|
1622
1668
|
reviewedSha: input.headSha,
|
|
1623
1669
|
effort: input.effort,
|
|
1670
|
+
rounds: priorRounds,
|
|
1624
1671
|
testReport,
|
|
1625
1672
|
inlineDisposition: { kind: "no-envelope" },
|
|
1626
1673
|
runUrl: input.runUrl,
|
|
@@ -1634,7 +1681,7 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1634
1681
|
);
|
|
1635
1682
|
process.exit(0);
|
|
1636
1683
|
}
|
|
1637
|
-
const thisIncomplete = envelope.incomplete === true;
|
|
1684
|
+
const thisIncomplete = envelope.incomplete === true || isIncompleteFindings(findings);
|
|
1638
1685
|
if (wouldBuryCompleted(thisIncomplete)) leaveInPlace();
|
|
1639
1686
|
const findingsMarker = findingsPointer(findings, input.jsonUrl);
|
|
1640
1687
|
const {
|
|
@@ -1656,6 +1703,9 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1656
1703
|
}
|
|
1657
1704
|
const botReviews = await fetchBotReviews(input.repo, prNumber, input.botLogin, ghApi);
|
|
1658
1705
|
const initialDisposition = comments.length === 0 && strays.length > 0 ? { kind: "none-in-diff" } : void 0;
|
|
1706
|
+
const currentCounts = computeSeverityCounts(findings.findings);
|
|
1707
|
+
const effectiveRoute = input.route ?? envelope.route;
|
|
1708
|
+
const rounds = effectiveRoute === "full review" && !thisIncomplete ? [...priorRounds, currentCounts] : priorRounds;
|
|
1659
1709
|
const commonRenderInput = {
|
|
1660
1710
|
findings,
|
|
1661
1711
|
envelope,
|
|
@@ -1667,7 +1717,8 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
1667
1717
|
reviewedSha: input.headSha,
|
|
1668
1718
|
effort: input.effort,
|
|
1669
1719
|
testReport,
|
|
1670
|
-
severityCounts:
|
|
1720
|
+
severityCounts: currentCounts,
|
|
1721
|
+
rounds,
|
|
1671
1722
|
strays,
|
|
1672
1723
|
runUrl: input.runUrl,
|
|
1673
1724
|
jsonUrl: input.jsonUrl,
|
|
@@ -2678,7 +2729,16 @@ var nativeTelemetry = (native, meta) => resolveTelemetry(
|
|
|
2678
2729
|
meta
|
|
2679
2730
|
);
|
|
2680
2731
|
var absentTelemetry = (meta) => resolveTelemetry({ models: [], turns: 0, durationMs: 0, vendorCostUsd: null }, meta);
|
|
2681
|
-
var buildEnvelope = (telemetry, native, agentFilePath, agentFileFallbackPath) => {
|
|
2732
|
+
var buildEnvelope = (telemetry, native, agentFilePath, agentFileFallbackPath, seedUnrevised) => {
|
|
2733
|
+
if (seedUnrevised)
|
|
2734
|
+
return {
|
|
2735
|
+
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
2736
|
+
findings: incompleteFindings(
|
|
2737
|
+
"### \u26A0\uFE0F Review did not complete\n\nThe review agent did not write a review (its draft is the untouched pre-seed). See the workflow logs."
|
|
2738
|
+
),
|
|
2739
|
+
incomplete: true,
|
|
2740
|
+
...telemetry
|
|
2741
|
+
};
|
|
2682
2742
|
const outcome = findingsOutcome(native, agentFilePath, agentFileFallbackPath);
|
|
2683
2743
|
switch (outcome.kind) {
|
|
2684
2744
|
case "ok":
|
|
@@ -2686,7 +2746,7 @@ var buildEnvelope = (telemetry, native, agentFilePath, agentFileFallbackPath) =>
|
|
|
2686
2746
|
case "telemetry-only":
|
|
2687
2747
|
return {
|
|
2688
2748
|
schema_version: DEFAULT_SCHEMA_VERSION,
|
|
2689
|
-
findings:
|
|
2749
|
+
findings: incompleteFindings(`### \u26A0\uFE0F Review did not complete
|
|
2690
2750
|
|
|
2691
2751
|
${outcome.reason}`),
|
|
2692
2752
|
incomplete: true,
|
|
@@ -2704,7 +2764,8 @@ var adapt = (adapterName, native, agentFilePath, meta = {}) => {
|
|
|
2704
2764
|
absentTelemetry(meta),
|
|
2705
2765
|
void 0,
|
|
2706
2766
|
agentFilePath,
|
|
2707
|
-
meta.agentFileFallbackPath
|
|
2767
|
+
meta.agentFileFallbackPath,
|
|
2768
|
+
meta.seedUnrevised === true
|
|
2708
2769
|
)
|
|
2709
2770
|
);
|
|
2710
2771
|
const decoded = ClaudeCodeEnvelopeCodec.decode(native);
|
|
@@ -2715,7 +2776,8 @@ var adapt = (adapterName, native, agentFilePath, meta = {}) => {
|
|
|
2715
2776
|
nativeTelemetry(decoded.right, meta),
|
|
2716
2777
|
native,
|
|
2717
2778
|
agentFilePath,
|
|
2718
|
-
meta.agentFileFallbackPath
|
|
2779
|
+
meta.agentFileFallbackPath,
|
|
2780
|
+
meta.seedUnrevised === true
|
|
2719
2781
|
)
|
|
2720
2782
|
);
|
|
2721
2783
|
}
|
|
@@ -2807,6 +2869,19 @@ var readJSONOrAbsent = (path) => {
|
|
|
2807
2869
|
return void 0;
|
|
2808
2870
|
}
|
|
2809
2871
|
};
|
|
2872
|
+
var readSandboxConfigForNotice = (path) => {
|
|
2873
|
+
const text = readFileOrNull(resolve$1(path));
|
|
2874
|
+
if (text === null) return void 0;
|
|
2875
|
+
const parsed = tryParseJson(text);
|
|
2876
|
+
if (!parsed.ok) {
|
|
2877
|
+
process.stderr.write(
|
|
2878
|
+
`::warning::${annotationSafe(`code-review notice: ${path} is present but not valid JSON \u2014 omitting the agent allowlist from the notice`)}
|
|
2879
|
+
`
|
|
2880
|
+
);
|
|
2881
|
+
return void 0;
|
|
2882
|
+
}
|
|
2883
|
+
return parsed.value;
|
|
2884
|
+
};
|
|
2810
2885
|
var readStdinJSON = () => {
|
|
2811
2886
|
if (process.stdin.isTTY) return null;
|
|
2812
2887
|
const raw = (() => {
|
|
@@ -3058,18 +3133,6 @@ var snapshotIfValid = (draftPath) => {
|
|
|
3058
3133
|
);
|
|
3059
3134
|
}
|
|
3060
3135
|
};
|
|
3061
|
-
var readMemInfo = () => {
|
|
3062
|
-
try {
|
|
3063
|
-
const text = readFileSync("/proc/meminfo", "utf8");
|
|
3064
|
-
const kb = (key2) => {
|
|
3065
|
-
const m = new RegExp(`^${key2}:\\s+(\\d+)\\s+kB`, "m").exec(text);
|
|
3066
|
-
return m?.[1] !== void 0 ? Number(m[1]) * 1024 : null;
|
|
3067
|
-
};
|
|
3068
|
-
return { availBytes: kb("MemAvailable"), totalBytes: kb("MemTotal") };
|
|
3069
|
-
} catch {
|
|
3070
|
-
return { availBytes: null, totalBytes: null };
|
|
3071
|
-
}
|
|
3072
|
-
};
|
|
3073
3136
|
var budgetHookCmd = defineCommand({
|
|
3074
3137
|
meta: {
|
|
3075
3138
|
name: "budget-hook",
|
|
@@ -3108,17 +3171,12 @@ var budgetHookCmd = defineCommand({
|
|
|
3108
3171
|
"reserve-wall": {
|
|
3109
3172
|
type: "string",
|
|
3110
3173
|
description: "Flat wall-clock wind-down floor (e.g. 2m, 120s), whichever is larger with --reserve-frac (default: 2m)"
|
|
3111
|
-
},
|
|
3112
|
-
"reserve-mem": {
|
|
3113
|
-
type: "string",
|
|
3114
|
-
description: "Free-RAM floor (e.g. 2g, 1536m) below which new subagent spawns are denied until memory recovers; not a convergence axis (default: 2g)"
|
|
3115
3174
|
}
|
|
3116
3175
|
},
|
|
3117
3176
|
run: async ({ args }) => {
|
|
3118
3177
|
try {
|
|
3119
3178
|
const draftPath = resolve$1(args.draft);
|
|
3120
3179
|
const input = readStdinJSON();
|
|
3121
|
-
const mem = readMemInfo();
|
|
3122
3180
|
const transcriptPath = transcriptPathOf(input);
|
|
3123
3181
|
const tree = transcriptPath ? readTranscriptTree(resolve$1(transcriptPath)) : void 0;
|
|
3124
3182
|
const usage = tree ? sumTranscriptUsage(tree.entries) : void 0;
|
|
@@ -3135,14 +3193,11 @@ var budgetHookCmd = defineCommand({
|
|
|
3135
3193
|
nowMs: Date.now()
|
|
3136
3194
|
}),
|
|
3137
3195
|
wallMs,
|
|
3138
|
-
availMemBytes: mem.availBytes,
|
|
3139
|
-
totalMemBytes: mem.totalBytes,
|
|
3140
3196
|
reserve: {
|
|
3141
3197
|
frac: parseFraction(args["reserve-frac"], DEFAULT_RESERVE.frac),
|
|
3142
3198
|
growth: parseFraction(args["reserve-growth"], DEFAULT_RESERVE.growth),
|
|
3143
3199
|
flatUsd: parseBudgetUsd(args["reserve-usd"]) ?? DEFAULT_RESERVE.flatUsd,
|
|
3144
|
-
flatMs: args["reserve-wall"] ? parseWallMs(args["reserve-wall"]) ?? DEFAULT_RESERVE.flatMs : DEFAULT_RESERVE.flatMs
|
|
3145
|
-
flatMem: args["reserve-mem"] ? parseByteSize(args["reserve-mem"]) ?? DEFAULT_RESERVE.flatMem : DEFAULT_RESERVE.flatMem
|
|
3200
|
+
flatMs: args["reserve-wall"] ? parseWallMs(args["reserve-wall"]) ?? DEFAULT_RESERVE.flatMs : DEFAULT_RESERVE.flatMs
|
|
3146
3201
|
},
|
|
3147
3202
|
draftPath,
|
|
3148
3203
|
mainDraftWritten: mainHasWrittenDraft(
|
|
@@ -3219,10 +3274,6 @@ var printSettingsCmd = defineCommand({
|
|
|
3219
3274
|
"reserve-wall": {
|
|
3220
3275
|
type: "string",
|
|
3221
3276
|
description: "Flat wall-clock wind-down floor (e.g. 2m), whichever is larger with --reserve-frac (default: 2m)"
|
|
3222
|
-
},
|
|
3223
|
-
"reserve-mem": {
|
|
3224
|
-
type: "string",
|
|
3225
|
-
description: "Free-RAM floor (e.g. 2g) below which new subagent spawns are denied until memory recovers (default: 2g)"
|
|
3226
3277
|
}
|
|
3227
3278
|
},
|
|
3228
3279
|
run: async ({ args }) => {
|
|
@@ -3244,8 +3295,7 @@ var printSettingsCmd = defineCommand({
|
|
|
3244
3295
|
reserveFrac: args["reserve-frac"],
|
|
3245
3296
|
reserveGrowth: args["reserve-growth"],
|
|
3246
3297
|
reserveUsd: args["reserve-usd"],
|
|
3247
|
-
reserveWall: args["reserve-wall"]
|
|
3248
|
-
reserveMem: args["reserve-mem"]
|
|
3298
|
+
reserveWall: args["reserve-wall"]
|
|
3249
3299
|
}
|
|
3250
3300
|
});
|
|
3251
3301
|
process.stdout.write(`${JSON.stringify(settings)}
|
|
@@ -3387,7 +3437,7 @@ var seedDraftCmd = defineCommand({
|
|
|
3387
3437
|
};
|
|
3388
3438
|
const writeScaffold = () => {
|
|
3389
3439
|
try {
|
|
3390
|
-
writeFileSync(outPath, `${JSON.stringify(
|
|
3440
|
+
writeFileSync(outPath, `${JSON.stringify(emptyFindings(""), null, 2)}
|
|
3391
3441
|
`);
|
|
3392
3442
|
writeSeedMarker();
|
|
3393
3443
|
process.stderr.write(
|
|
@@ -3487,11 +3537,14 @@ var adaptCmd = defineCommand({
|
|
|
3487
3537
|
}
|
|
3488
3538
|
},
|
|
3489
3539
|
run: async ({ args }) => {
|
|
3540
|
+
const agentFile = args["agent-file"];
|
|
3541
|
+
const seedUnrevised = agentFile ? !mainHasWrittenDraft(mtimeMsOf(agentFile), mtimeMsOf(seedMarkerPath(agentFile))) : false;
|
|
3490
3542
|
const envelope = unwrapAdapt(
|
|
3491
|
-
adapt(requireAdapterName(args.adapter), readJSONOrAbsent(args.native),
|
|
3543
|
+
adapt(requireAdapterName(args.adapter), readJSONOrAbsent(args.native), agentFile, {
|
|
3492
3544
|
route: args.route,
|
|
3493
3545
|
effort: args.effort,
|
|
3494
3546
|
agentFileFallbackPath: args["agent-file-fallback"],
|
|
3547
|
+
seedUnrevised,
|
|
3495
3548
|
...args.transcript ? {
|
|
3496
3549
|
transcriptFallback: () => transcriptFallbackFrom(args.transcript)
|
|
3497
3550
|
} : {}
|
|
@@ -3515,6 +3568,10 @@ var noticeCmd = defineCommand({
|
|
|
3515
3568
|
reasons: {
|
|
3516
3569
|
type: "string",
|
|
3517
3570
|
description: "security-blocked / triage-error only: the triage's fail-closed reason string (empty/omitted \u21D2 the no-reason wording)"
|
|
3571
|
+
},
|
|
3572
|
+
"sandbox-config": {
|
|
3573
|
+
type: "string",
|
|
3574
|
+
description: "no-output / triage-error only: path to the agent's sandbox-runtime settings (sandbox.json); its network.allowedDomains is named in the notice so an egress-blocked review self-diagnoses. Missing or unreadable \u21D2 the allowlist is omitted (an early failure runs before the jail is set up)."
|
|
3518
3575
|
}
|
|
3519
3576
|
},
|
|
3520
3577
|
// An unrecognized kind degrades to a generic incomplete notice instead of exiting non-zero: a
|
|
@@ -3530,8 +3587,10 @@ var noticeCmd = defineCommand({
|
|
|
3530
3587
|
`);
|
|
3531
3588
|
return;
|
|
3532
3589
|
}
|
|
3590
|
+
const namesAllowlist = args.kind === "no-output" || args.kind === "triage-error";
|
|
3591
|
+
const agentAllowlist = namesAllowlist && args["sandbox-config"] ? parseAgentAllowlist(readSandboxConfigForNotice(args["sandbox-config"])) : [];
|
|
3533
3592
|
process.stdout.write(
|
|
3534
|
-
`${JSON.stringify(buildNoticeEnvelope(args.kind, args.reasons), null, 2)}
|
|
3593
|
+
`${JSON.stringify(buildNoticeEnvelope(args.kind, args.reasons, agentAllowlist), null, 2)}
|
|
3535
3594
|
`
|
|
3536
3595
|
);
|
|
3537
3596
|
}
|