@jphutchins/code-review 0.1.0-alpha.46 → 0.1.0-alpha.48
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 +237 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema/VERSIONING.md +17 -12
- package/schema/findings.schema.json +34 -0
- package/schema/prices.example.json +8 -2
- package/schema/prices.schema.json +77 -24
- package/templates/comment.eta +12 -3
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ var LineNumber = t.refinement(
|
|
|
33
33
|
var Confidence = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Confidence");
|
|
34
34
|
var Likelihood = t.refinement(t.number, (n) => n >= 0 && n <= 1, "Likelihood");
|
|
35
35
|
var FiniteNumber = t.refinement(t.number, (n) => Number.isFinite(n), "FiniteNumber");
|
|
36
|
+
var NonNegativePrice = t.refinement(t.number, (n) => n >= 0, "NonNegativePrice");
|
|
36
37
|
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-]+)*)?$/;
|
|
37
38
|
var SchemaVersion = t.refinement(
|
|
38
39
|
t.string,
|
|
@@ -170,6 +171,15 @@ var ConvergenceStrict = t.refinement(
|
|
|
170
171
|
"ConvergenceStrict"
|
|
171
172
|
);
|
|
172
173
|
var ConvergenceCodec = t.exact(ConvergenceStrict);
|
|
174
|
+
var LineDelta = t.refinement(
|
|
175
|
+
t.number,
|
|
176
|
+
(n) => Number.isInteger(n) && n >= 0,
|
|
177
|
+
"LineDelta"
|
|
178
|
+
);
|
|
179
|
+
var ChangeLinesCodec = t.exact(t.type({ added: LineDelta, removed: LineDelta }));
|
|
180
|
+
var ChangeSizeCodec = t.exact(
|
|
181
|
+
t.partial({ code: ChangeLinesCodec, tests: ChangeLinesCodec, docs: ChangeLinesCodec })
|
|
182
|
+
);
|
|
173
183
|
var FindingsCodec = t.exact(
|
|
174
184
|
t.intersection([
|
|
175
185
|
t.type({
|
|
@@ -183,10 +193,20 @@ var FindingsCodec = t.exact(
|
|
|
183
193
|
// Pipeline-stamped only (issue #150); see ScopeMetastasisCodec.
|
|
184
194
|
scope_metastasis: ScopeMetastasisCodec,
|
|
185
195
|
// Pipeline-stamped only (issue #174); see ConvergenceCodec.
|
|
186
|
-
convergence: ConvergenceCodec
|
|
196
|
+
convergence: ConvergenceCodec,
|
|
197
|
+
// Agent-written best-effort (issue #182); see ChangeSizeCodec.
|
|
198
|
+
change_size: ChangeSizeCodec
|
|
187
199
|
})
|
|
188
200
|
])
|
|
189
201
|
);
|
|
202
|
+
var PIPELINE_STAMPED_FIELDS = /* @__PURE__ */ new Set([
|
|
203
|
+
"convergence",
|
|
204
|
+
"scope_metastasis"
|
|
205
|
+
]);
|
|
206
|
+
var RECOVERABLE_OPTIONAL_FIELDS = /* @__PURE__ */ new Set([
|
|
207
|
+
...PIPELINE_STAMPED_FIELDS,
|
|
208
|
+
"change_size"
|
|
209
|
+
]);
|
|
190
210
|
var TriageCodec = t.type({
|
|
191
211
|
safe: t.boolean,
|
|
192
212
|
reasons: t.string
|
|
@@ -219,6 +239,11 @@ var ResultEnvelopeCodec = t.intersection([
|
|
|
219
239
|
vendor_cost_usd: t.union([t.number, t.null]),
|
|
220
240
|
route: t.string,
|
|
221
241
|
effort: t.string,
|
|
242
|
+
// The ISO-8601 UTC instant the run completed (stamped by `adapt` when it built this envelope, issue
|
|
243
|
+
// #170). Cost recomputation prices a time-slotted model at THIS instant, so the same envelope prices
|
|
244
|
+
// to the same slot deterministically wherever it is re-rendered — not at each command's wall clock.
|
|
245
|
+
// Absent on a pre-#170 envelope (cost then falls back to the caller's instant).
|
|
246
|
+
generated_at: t.string,
|
|
222
247
|
// The run produced a notice rather than a completed review (security-gate block, agent kill, no
|
|
223
248
|
// recoverable findings). An empty `findings` array alone can't say this — a genuine clean review
|
|
224
249
|
// is also empty — so the render suppresses "clean review" and the sticky precedence guard refuses
|
|
@@ -226,12 +251,44 @@ var ResultEnvelopeCodec = t.intersection([
|
|
|
226
251
|
incomplete: t.boolean
|
|
227
252
|
})
|
|
228
253
|
]);
|
|
229
|
-
var
|
|
230
|
-
in:
|
|
231
|
-
out:
|
|
232
|
-
cache_read:
|
|
233
|
-
cache_write:
|
|
254
|
+
var FlatModelPricesShape = t.type({
|
|
255
|
+
in: NonNegativePrice,
|
|
256
|
+
out: NonNegativePrice,
|
|
257
|
+
cache_read: NonNegativePrice,
|
|
258
|
+
cache_write: NonNegativePrice
|
|
234
259
|
});
|
|
260
|
+
var FLAT_PRICE_KEYS = new Set(Object.keys(FlatModelPricesShape.props));
|
|
261
|
+
var FlatModelPricesStrict = t.refinement(
|
|
262
|
+
FlatModelPricesShape,
|
|
263
|
+
(p) => Object.keys(p).every((k) => FLAT_PRICE_KEYS.has(k)),
|
|
264
|
+
"FlatModelPricesStrict"
|
|
265
|
+
);
|
|
266
|
+
var FlatModelPricesCodec = t.exact(FlatModelPricesStrict);
|
|
267
|
+
var UTC_HHMM_RE = /^([01][0-9]|2[0-3]):[0-5][0-9]$/;
|
|
268
|
+
var UtcHHMM = t.refinement(t.string, (s) => UTC_HHMM_RE.test(s), "UtcHHMM");
|
|
269
|
+
var PriceSlotShape = t.intersection([
|
|
270
|
+
t.type({ utc_from: UtcHHMM, utc_to: UtcHHMM }),
|
|
271
|
+
FlatModelPricesShape
|
|
272
|
+
]);
|
|
273
|
+
var PRICE_SLOT_KEYS = /* @__PURE__ */ new Set(["utc_from", "utc_to", ...Object.keys(FlatModelPricesShape.props)]);
|
|
274
|
+
var PriceSlotStrict = t.refinement(
|
|
275
|
+
PriceSlotShape,
|
|
276
|
+
(s) => Object.keys(s).every((k) => PRICE_SLOT_KEYS.has(k)),
|
|
277
|
+
"PriceSlotStrict"
|
|
278
|
+
);
|
|
279
|
+
var PriceSlotCodec = t.exact(PriceSlotStrict);
|
|
280
|
+
var NonEmptyPriceSlots = t.refinement(
|
|
281
|
+
t.array(PriceSlotCodec),
|
|
282
|
+
(a) => a.length >= 1,
|
|
283
|
+
"NonEmptyPriceSlots"
|
|
284
|
+
);
|
|
285
|
+
var SlottedModelPricesStrict = t.refinement(
|
|
286
|
+
t.type({ slots: NonEmptyPriceSlots }),
|
|
287
|
+
(s) => Object.keys(s).every((k) => k === "slots"),
|
|
288
|
+
"SlottedModelPricesStrict"
|
|
289
|
+
);
|
|
290
|
+
var SlottedModelPricesCodec = t.exact(SlottedModelPricesStrict);
|
|
291
|
+
var ModelPricesCodec = t.union([FlatModelPricesCodec, SlottedModelPricesCodec]);
|
|
235
292
|
var PriceMapCodec = t.type({
|
|
236
293
|
_updated: t.string,
|
|
237
294
|
_unit: t.string,
|
|
@@ -265,35 +322,64 @@ var defaultWarn = (message) => {
|
|
|
265
322
|
process.stderr.write(`${message}
|
|
266
323
|
`);
|
|
267
324
|
};
|
|
268
|
-
var
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
|
|
325
|
+
var parseInstant = (iso) => {
|
|
326
|
+
if (iso === void 0) return void 0;
|
|
327
|
+
if (iso.includes("T") && !/(Z|[+-]\d{2}:?\d{2})$/.test(iso)) return void 0;
|
|
328
|
+
const d = new Date(iso);
|
|
329
|
+
return Number.isNaN(d.getTime()) ? void 0 : d;
|
|
330
|
+
};
|
|
331
|
+
var utcMinuteOfDay = (at) => at.getUTCHours() * 60 + at.getUTCMinutes();
|
|
332
|
+
var hhmmToMinutes = (hhmm) => {
|
|
333
|
+
const [h, m] = hhmm.split(":");
|
|
334
|
+
return Number(h) * 60 + Number(m);
|
|
335
|
+
};
|
|
336
|
+
var hhmmOf = (minute) => `${String(Math.floor(minute / 60)).padStart(2, "0")}:${String(minute % 60).padStart(2, "0")}`;
|
|
337
|
+
var slotCovers = (slot, minute) => {
|
|
338
|
+
const from = hhmmToMinutes(slot.utc_from);
|
|
339
|
+
const to = hhmmToMinutes(slot.utc_to);
|
|
340
|
+
return from < to ? minute >= from && minute < to : minute >= from || minute < to;
|
|
341
|
+
};
|
|
342
|
+
var resolveFlatPrices = (model, p, pricedAt, warn) => {
|
|
343
|
+
if (!("slots" in p)) return p;
|
|
344
|
+
if (pricedAt === void 0) {
|
|
273
345
|
warn(
|
|
274
|
-
`code-review cost:
|
|
346
|
+
`code-review cost: model "${model}" has time-slotted prices but no run instant was supplied to select a slot; cost for this model set to $0`
|
|
275
347
|
);
|
|
276
|
-
return
|
|
277
|
-
model: entry.model,
|
|
278
|
-
inputTokens: entry.input_tokens,
|
|
279
|
-
outputTokens: entry.output_tokens,
|
|
280
|
-
cacheReadTokens: cacheRead,
|
|
281
|
-
cacheWriteTokens: cacheWrite,
|
|
282
|
-
costUSD: 0
|
|
283
|
-
};
|
|
348
|
+
return null;
|
|
284
349
|
}
|
|
285
|
-
const
|
|
286
|
-
|
|
350
|
+
const minute = utcMinuteOfDay(pricedAt);
|
|
351
|
+
const covering = p.slots.filter((s) => slotCovers(s, minute));
|
|
352
|
+
if (covering.length === 1) return covering[0] ?? null;
|
|
353
|
+
warn(
|
|
354
|
+
`code-review cost: model "${model}" \u2014 ${String(covering.length)} price slots cover ${hhmmOf(minute)} UTC (expected exactly 1); a model's slots must partition the 24h day with no gap or overlap; cost for this model set to $0`
|
|
355
|
+
);
|
|
356
|
+
return null;
|
|
357
|
+
};
|
|
358
|
+
var computeModelCost = (entry, prices, pricedAt, warn) => {
|
|
359
|
+
const p = prices.models[entry.model];
|
|
360
|
+
const cacheRead = entry.cache_read_tokens ?? 0;
|
|
361
|
+
const cacheWrite = entry.cache_write_tokens ?? 0;
|
|
362
|
+
const zero = {
|
|
287
363
|
model: entry.model,
|
|
288
364
|
inputTokens: entry.input_tokens,
|
|
289
365
|
outputTokens: entry.output_tokens,
|
|
290
366
|
cacheReadTokens: cacheRead,
|
|
291
367
|
cacheWriteTokens: cacheWrite,
|
|
292
|
-
costUSD
|
|
368
|
+
costUSD: 0
|
|
293
369
|
};
|
|
370
|
+
if (!p) {
|
|
371
|
+
warn(
|
|
372
|
+
`code-review cost: unknown model "${entry.model}" \u2014 no entry in price map; cost for this model set to $0`
|
|
373
|
+
);
|
|
374
|
+
return zero;
|
|
375
|
+
}
|
|
376
|
+
const rate = resolveFlatPrices(entry.model, p, pricedAt, warn);
|
|
377
|
+
if (rate === null) return zero;
|
|
378
|
+
const costUSD = (entry.input_tokens * rate.in + entry.output_tokens * rate.out + cacheRead * rate.cache_read + cacheWrite * rate.cache_write) / 1e6;
|
|
379
|
+
return { ...zero, costUSD };
|
|
294
380
|
};
|
|
295
|
-
var computeCost = (models, prices, warn = defaultWarn) => {
|
|
296
|
-
const lines = models.map((entry) => computeModelCost(entry, prices, warn));
|
|
381
|
+
var computeCost = (models, prices, pricedAt, warn = defaultWarn) => {
|
|
382
|
+
const lines = models.map((entry) => computeModelCost(entry, prices, pricedAt, warn));
|
|
297
383
|
return {
|
|
298
384
|
lines,
|
|
299
385
|
totalInputTokens: lines.reduce((s, l) => s + l.inputTokens, 0),
|
|
@@ -477,13 +563,7 @@ var normalizeCodeCounts = (codes, priorCodes) => {
|
|
|
477
563
|
if (typeof codes !== "object" || codes === null || Array.isArray(codes)) return void 0;
|
|
478
564
|
const entries = Object.entries(codes).filter(
|
|
479
565
|
(e) => typeof e[1] === "number" && Number.isSafeInteger(e[1]) && e[1] > 0
|
|
480
|
-
)
|
|
481
|
-
if (b[1] !== a[1]) return b[1] - a[1];
|
|
482
|
-
const aPrior = hasCode(priorCodes, a[0]) ? 1 : 0;
|
|
483
|
-
const bPrior = hasCode(priorCodes, b[0]) ? 1 : 0;
|
|
484
|
-
if (aPrior !== bPrior) return bPrior - aPrior;
|
|
485
|
-
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
486
|
-
});
|
|
566
|
+
);
|
|
487
567
|
if (entries.length === 0) return void 0;
|
|
488
568
|
const sorted = entries.sort((a, b) => {
|
|
489
569
|
if (b[1] !== a[1]) return b[1] - a[1];
|
|
@@ -666,6 +746,23 @@ var priorBelowFloorNits = (priorDoc, floor = DEFAULT_NIT_VISIBILITY_FLOOR) => {
|
|
|
666
746
|
};
|
|
667
747
|
var convergenceBadge = (c) => c.converged ? `**Convergence** \u{1F3C1} ${formatScore(c.score)} \u2264 ${String(c.threshold)} \u2014 converged` : `**Convergence** \u{1F504} ${formatScore(c.score)} > ${String(c.threshold)} \u2014 iterating`;
|
|
668
748
|
var convergenceSummary = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => convergenceBadge(convergenceSignal(doc, threshold));
|
|
749
|
+
var changeSizeSummary = (changeSize) => {
|
|
750
|
+
if (changeSize === void 0) return "";
|
|
751
|
+
const cell = (label, d) => d === void 0 ? null : `+${String(d.added)} / \u2212${String(d.removed)} ${label}`;
|
|
752
|
+
return [
|
|
753
|
+
cell("code", changeSize.code),
|
|
754
|
+
cell("tests", changeSize.tests),
|
|
755
|
+
cell("docs", changeSize.docs)
|
|
756
|
+
].filter((c) => c !== null).join(" \xB7 ");
|
|
757
|
+
};
|
|
758
|
+
var nextRoundNumber = (priorTraj, priorConvRounds) => {
|
|
759
|
+
const last = (rounds) => rounds.length > 0 ? rounds[rounds.length - 1]?.round ?? rounds.length : 0;
|
|
760
|
+
return Math.max(last(priorTraj), last(priorConvRounds)) + 1;
|
|
761
|
+
};
|
|
762
|
+
var inProgressConvergence = (prior, runningRound) => {
|
|
763
|
+
const rounds = prior.rounds ?? [];
|
|
764
|
+
return rounds.length === 0 ? "" : `${roundsSummary(rounds, runningRound)} \u2192 \u23F3`;
|
|
765
|
+
};
|
|
669
766
|
var SURFACE_SCHEMA_VERSION = "0.8.0";
|
|
670
767
|
var convergenceSignal = (doc, threshold = DEFAULT_CONVERGENCE_THRESHOLD) => {
|
|
671
768
|
const score = convergenceScore(doc, threshold);
|
|
@@ -1131,7 +1228,8 @@ var render = (input) => {
|
|
|
1131
1228
|
const usageAvailable = input.envelope !== null;
|
|
1132
1229
|
const hasUsage = input.envelope !== null && input.envelope.models.length > 0;
|
|
1133
1230
|
const incomplete = (input.incomplete ?? input.envelope?.incomplete ?? false) || isIncompleteFindings(input.findings);
|
|
1134
|
-
const
|
|
1231
|
+
const pricedAt = parseInstant(input.envelope?.generated_at) ?? input.pricedAt;
|
|
1232
|
+
const costReport = input.envelope ? computeCost(input.envelope.models, input.prices, pricedAt) : null;
|
|
1135
1233
|
const pricesProvided = input.pricesProvided ?? true;
|
|
1136
1234
|
const route = input.route ?? input.envelope?.route ?? null;
|
|
1137
1235
|
const effort = input.effort ?? input.envelope?.effort ?? null;
|
|
@@ -1154,6 +1252,10 @@ var render = (input) => {
|
|
|
1154
1252
|
effort,
|
|
1155
1253
|
modelNames,
|
|
1156
1254
|
testReport: input.testReport ?? null,
|
|
1255
|
+
// The agent's best-effort role split (issue #182) and the pipeline's deterministic cloc table; both
|
|
1256
|
+
// chrome, gated to a completed review by the template's !incomplete block.
|
|
1257
|
+
changeSummary: changeSizeSummary(input.findings.change_size),
|
|
1258
|
+
clocDiff: input.clocDiff !== void 0 ? escapeFence(input.clocDiff) : null,
|
|
1157
1259
|
reviewedSha: input.reviewedSha ?? "0000000000000000000000000000000000000000",
|
|
1158
1260
|
postedAt: input.postedAt ?? "",
|
|
1159
1261
|
severityCounts,
|
|
@@ -1170,7 +1272,6 @@ var render = (input) => {
|
|
|
1170
1272
|
// (issue #174) — no separate signal or rounds marker rides beside it. post always supplies the
|
|
1171
1273
|
// precomputed marker; the standalone `render` command falls back to encoding the doc here.
|
|
1172
1274
|
findingsPointer: input.findingsPointer ?? findingsPointer(input.findings, input.jsonUrl),
|
|
1173
|
-
roundsMarker: "",
|
|
1174
1275
|
roundsSummary: roundsSummary(trajectory, input.roundCount),
|
|
1175
1276
|
metastasisNote: advisoryAllowed ? metastasisNote(trajectory) : "",
|
|
1176
1277
|
sameRootNotes: advisoryAllowed ? sameRootNotes : {},
|
|
@@ -1777,11 +1878,24 @@ var triageTable = [
|
|
|
1777
1878
|
];
|
|
1778
1879
|
var pricesTable = [
|
|
1779
1880
|
{
|
|
1881
|
+
// Retained non-latest so `validate`/`print-schema --schema-version 0.1` still resolves (the
|
|
1882
|
+
// keep-old-minors convention the findings table follows). The price map carries no version signal,
|
|
1883
|
+
// and the schema is a single unversioned file, so both entries point at it; the codec's oneOf
|
|
1884
|
+
// accepts a flat 0.1-era map unchanged.
|
|
1780
1885
|
minor: "0.1",
|
|
1781
1886
|
defaultVersion: "0.1.0",
|
|
1782
1887
|
schemaFile: "prices.schema.json",
|
|
1783
1888
|
codec: PriceMapCodec,
|
|
1784
1889
|
normalize: identity,
|
|
1890
|
+
latest: false
|
|
1891
|
+
},
|
|
1892
|
+
{
|
|
1893
|
+
// v0.2.0 (issue #170): a model's value gained the time-slotted alternative (flat | { slots }).
|
|
1894
|
+
minor: "0.2",
|
|
1895
|
+
defaultVersion: "0.2.0",
|
|
1896
|
+
schemaFile: "prices.schema.json",
|
|
1897
|
+
codec: PriceMapCodec,
|
|
1898
|
+
normalize: identity,
|
|
1785
1899
|
latest: true
|
|
1786
1900
|
}
|
|
1787
1901
|
];
|
|
@@ -1826,7 +1940,8 @@ var resolveFindings = (raw) => {
|
|
|
1826
1940
|
return decoded._tag === "Left" ? { kind: "invalid-shape", errors: formatErrors(decoded.left) } : { kind: "ok", version, value: entry.normalize(decoded.right) };
|
|
1827
1941
|
};
|
|
1828
1942
|
var resolveSingleVersion = (kind, raw) => {
|
|
1829
|
-
const
|
|
1943
|
+
const table = tableFor(kind);
|
|
1944
|
+
const entry = table.find((e) => e.latest) ?? table[0];
|
|
1830
1945
|
if (!entry) throw new Error(`Registry invariant violated \u2014 no entry for "${kind}"`);
|
|
1831
1946
|
const decoded = entry.codec.decode(raw);
|
|
1832
1947
|
return decoded._tag === "Left" ? { kind: "invalid-shape", errors: formatErrors(decoded.left) } : { kind: "ok", version: entry.defaultVersion, value: entry.normalize(decoded.right) };
|
|
@@ -2055,7 +2170,6 @@ var checkLongSuggestions = (comments) => {
|
|
|
2055
2170
|
});
|
|
2056
2171
|
return { comments: adjusted, longFiles };
|
|
2057
2172
|
};
|
|
2058
|
-
var PIPELINE_STAMPED_FIELDS = /* @__PURE__ */ new Set(["convergence", "scope_metastasis"]);
|
|
2059
2173
|
var decodeFindings = (doc) => {
|
|
2060
2174
|
const resolution = resolve("findings", doc);
|
|
2061
2175
|
switch (resolution.kind) {
|
|
@@ -2076,14 +2190,14 @@ var loadFindings = (path) => {
|
|
|
2076
2190
|
return { kind: "corrupt" };
|
|
2077
2191
|
}
|
|
2078
2192
|
const first = decodeFindings(raw);
|
|
2079
|
-
if (first.kind === "invalid-shape" && typeof raw === "object" && raw !== null && !Array.isArray(raw) && Object.keys(raw).some((k) =>
|
|
2193
|
+
if (first.kind === "invalid-shape" && typeof raw === "object" && raw !== null && !Array.isArray(raw) && Object.keys(raw).some((k) => RECOVERABLE_OPTIONAL_FIELDS.has(k))) {
|
|
2080
2194
|
const stripped = Object.fromEntries(
|
|
2081
|
-
Object.entries(raw).filter(([key2]) => !
|
|
2195
|
+
Object.entries(raw).filter(([key2]) => !RECOVERABLE_OPTIONAL_FIELDS.has(key2))
|
|
2082
2196
|
);
|
|
2083
2197
|
const retry = decodeFindings(stripped);
|
|
2084
2198
|
if (retry.kind === "ok") {
|
|
2085
2199
|
process.stderr.write(
|
|
2086
|
-
"Warning: the review draft carried an invalid
|
|
2200
|
+
"Warning: the review draft carried an invalid best-effort field (convergence/scope_metastasis/change_size) \u2014 stripped it and used the rest; the pipeline re-stamps convergence\n"
|
|
2087
2201
|
);
|
|
2088
2202
|
return retry;
|
|
2089
2203
|
}
|
|
@@ -2133,6 +2247,19 @@ var loadTestReport = (path) => {
|
|
|
2133
2247
|
}
|
|
2134
2248
|
return decoded.right;
|
|
2135
2249
|
};
|
|
2250
|
+
var loadClocDiff = (path) => {
|
|
2251
|
+
let raw;
|
|
2252
|
+
try {
|
|
2253
|
+
raw = readFileSync(path, "utf-8");
|
|
2254
|
+
} catch (err) {
|
|
2255
|
+
process.stderr.write(
|
|
2256
|
+
`Warning: could not read cloc diff at ${path}: ${errMsg(err)} \u2014 omitting the cloc collapsible
|
|
2257
|
+
`
|
|
2258
|
+
);
|
|
2259
|
+
return void 0;
|
|
2260
|
+
}
|
|
2261
|
+
return raw.trim() === "" ? void 0 : raw;
|
|
2262
|
+
};
|
|
2136
2263
|
var parseHtmlUrl = (raw) => {
|
|
2137
2264
|
const parsed = tryParseJson(raw);
|
|
2138
2265
|
const htmlUrl = parsed.ok ? asRecord(parsed.value)?.["html_url"] : void 0;
|
|
@@ -2378,8 +2505,7 @@ var post = async (input, ghApi = runGhApi) => {
|
|
|
2378
2505
|
const priorBody = existingSticky?.body ?? "";
|
|
2379
2506
|
const priorTraj = priorTrajectory(priorDoc, priorBody);
|
|
2380
2507
|
const priorConv = carriedConvergence(priorDoc, priorBody);
|
|
2381
|
-
const
|
|
2382
|
-
const priorRoundCount = Math.max(lastRound(priorTraj), lastRound(priorConv?.rounds ?? []));
|
|
2508
|
+
const priorRoundCount = nextRoundNumber(priorTraj, priorConv?.rounds ?? []) - 1;
|
|
2383
2509
|
const stampConvergence = (doc, conv) => ({
|
|
2384
2510
|
...doc,
|
|
2385
2511
|
convergence: conv ?? void 0
|
|
@@ -2511,6 +2637,7 @@ ${dropNote}` : ""}`,
|
|
|
2511
2637
|
const answeredDropNote = answeredReRaiseNote(verbatimReRaised, droppedCount) + (verbatimReRaised.length > 0 && findings.findings.length === 0 ? "\n> _The stop signal reflects the kept findings \u2014 this round carries none._" : "");
|
|
2512
2638
|
const envelope = loadEnvelope(input.envelopePath);
|
|
2513
2639
|
const testReport = input.testReportPath ? loadTestReport(input.testReportPath) : void 0;
|
|
2640
|
+
const clocDiff = input.clocDiffPath ? loadClocDiff(input.clocDiffPath) : void 0;
|
|
2514
2641
|
const effectiveRoute = input.route ?? envelope?.route;
|
|
2515
2642
|
if (envelope === null) {
|
|
2516
2643
|
const envelopelessIncomplete = isIncompleteFindings(findings);
|
|
@@ -2548,6 +2675,7 @@ ${dropNote}` : ""}`,
|
|
|
2548
2675
|
roundCount: priorRoundCount,
|
|
2549
2676
|
convergenceRound: false,
|
|
2550
2677
|
testReport,
|
|
2678
|
+
clocDiff,
|
|
2551
2679
|
inlineDisposition: { kind: "no-envelope" },
|
|
2552
2680
|
runUrl: input.runUrl,
|
|
2553
2681
|
jsonUrl: input.jsonUrl,
|
|
@@ -2626,6 +2754,7 @@ ${dropNote}` : ""}`,
|
|
|
2626
2754
|
reviewedSha: input.headSha,
|
|
2627
2755
|
effort: input.effort,
|
|
2628
2756
|
testReport,
|
|
2757
|
+
clocDiff,
|
|
2629
2758
|
severityCounts: currentCounts,
|
|
2630
2759
|
sameRootNotes,
|
|
2631
2760
|
answeredNotes: reRaisedNotes,
|
|
@@ -2639,7 +2768,8 @@ ${dropNote}` : ""}`,
|
|
|
2639
2768
|
runUrl: input.runUrl,
|
|
2640
2769
|
jsonUrl: input.jsonUrl,
|
|
2641
2770
|
findingsPointer: findingsMarker,
|
|
2642
|
-
postedAt: input.postedAt
|
|
2771
|
+
postedAt: input.postedAt,
|
|
2772
|
+
pricedAt: input.pricedAt
|
|
2643
2773
|
};
|
|
2644
2774
|
const longFilesNote = longFiles.length > 0 ? `
|
|
2645
2775
|
|
|
@@ -2724,12 +2854,22 @@ var bodyRefsRun = (body, runUrl) => {
|
|
|
2724
2854
|
const runId = runIdFromUrl(runUrl);
|
|
2725
2855
|
return runId === null ? body.includes(runUrl) : new RegExp(`/actions/runs/${runId}(?!\\d)`).test(body);
|
|
2726
2856
|
};
|
|
2727
|
-
var announceBody = (headSha, runUrl, existingBody) =>
|
|
2728
|
-
|
|
2857
|
+
var announceBody = (headSha, runUrl, existingBody) => {
|
|
2858
|
+
const priorDoc = existingBody !== void 0 ? parseFindingsMarker(existingBody) : null;
|
|
2859
|
+
const prior = existingBody !== void 0 ? carriedConvergence(priorDoc, existingBody) : null;
|
|
2860
|
+
const progress = prior !== null ? inProgressConvergence(
|
|
2861
|
+
prior,
|
|
2862
|
+
nextRoundNumber(priorTrajectory(priorDoc, existingBody ?? ""), prior.rounds ?? [])
|
|
2863
|
+
) : "";
|
|
2864
|
+
return noticeBody(
|
|
2865
|
+
`${DEFAULT_MARKER}
|
|
2729
2866
|
|
|
2730
|
-
\u{1F504} **Code review in progress** for \`${headSha.slice(0, 7)}\` \u2014 see the [workflow run](${runUrl}) for progress; this comment is updated with the review when it completes
|
|
2731
|
-
|
|
2732
|
-
|
|
2867
|
+
\u{1F504} **Code review in progress** for \`${headSha.slice(0, 7)}\` \u2014 see the [workflow run](${runUrl}) for progress; this comment is updated with the review when it completes.${progress ? `
|
|
2868
|
+
|
|
2869
|
+
${progress}` : ""}`,
|
|
2870
|
+
existingBody
|
|
2871
|
+
);
|
|
2872
|
+
};
|
|
2733
2873
|
var announce = async (input, ghApi = runGhApi) => {
|
|
2734
2874
|
const candidates = await fetchPrCandidates(input.repo, input.headSha, ghApi);
|
|
2735
2875
|
const resolution = resolvePr(candidates, input.headBranch);
|
|
@@ -3074,6 +3214,7 @@ var renderOutputs = (result) => {
|
|
|
3074
3214
|
conclusion=${result.conclusion}
|
|
3075
3215
|
diff_size=${String(result.diffSize)}
|
|
3076
3216
|
stacked=${String(result.stacked)}
|
|
3217
|
+
base_sha=${result.baseSha}
|
|
3077
3218
|
`;
|
|
3078
3219
|
}
|
|
3079
3220
|
};
|
|
@@ -3343,7 +3484,8 @@ var gather = async (input, ghApi = runGhApi, gitRun = runGit) => {
|
|
|
3343
3484
|
pr: prNumber,
|
|
3344
3485
|
conclusion: input.conclusion,
|
|
3345
3486
|
diffSize: Buffer.byteLength(prDiff, "utf8"),
|
|
3346
|
-
stacked
|
|
3487
|
+
stacked,
|
|
3488
|
+
baseSha: meta.base_sha
|
|
3347
3489
|
};
|
|
3348
3490
|
};
|
|
3349
3491
|
|
|
@@ -3576,10 +3718,16 @@ var nativeTelemetry = (native, meta) => resolveTelemetry(
|
|
|
3576
3718
|
);
|
|
3577
3719
|
var absentTelemetry = (meta) => resolveTelemetry({ models: [], turns: 0, durationMs: 0, vendorCostUsd: null }, meta);
|
|
3578
3720
|
var buildEnvelope = (telemetry, native, agentFilePath, agentFileFallbackPath, seedUnrevised) => {
|
|
3721
|
+
const generated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3579
3722
|
const outcome = findingsOutcome(native, agentFilePath, agentFileFallbackPath);
|
|
3580
3723
|
switch (outcome.kind) {
|
|
3581
3724
|
case "ok":
|
|
3582
|
-
return {
|
|
3725
|
+
return {
|
|
3726
|
+
schema_version: outcome.version,
|
|
3727
|
+
findings: outcome.findings,
|
|
3728
|
+
generated_at,
|
|
3729
|
+
...telemetry
|
|
3730
|
+
};
|
|
3583
3731
|
case "telemetry-only": {
|
|
3584
3732
|
const reason = seedUnrevised ? "the review agent did not write a review (its draft is still the pre-seeded sentinel)" : outcome.reason;
|
|
3585
3733
|
return {
|
|
@@ -3588,6 +3736,7 @@ var buildEnvelope = (telemetry, native, agentFilePath, agentFileFallbackPath, se
|
|
|
3588
3736
|
|
|
3589
3737
|
${reason}`),
|
|
3590
3738
|
incomplete: true,
|
|
3739
|
+
generated_at,
|
|
3591
3740
|
...telemetry
|
|
3592
3741
|
};
|
|
3593
3742
|
}
|
|
@@ -3792,6 +3941,7 @@ var resolvePrices = (pricesArg) => {
|
|
|
3792
3941
|
return { kind: "absent", path: bundledPath("schema", "prices.example.json") };
|
|
3793
3942
|
};
|
|
3794
3943
|
var TEST_REPORT_DESCRIPTION = 'Path to a JSON test summary: {"passed": number, "failed": number, "total": number, "failures"?: [{"name": string, "message"?: string}]}';
|
|
3944
|
+
var CLOC_DIFF_DESCRIPTION = "Path to a raw `cloc --git --diff <base> <head>` table, rendered verbatim in the sticky's cloc collapsible. Best-effort: an absent, unreadable, or empty file omits the collapsible.";
|
|
3795
3945
|
var CONVERGENCE_THRESHOLD_DESCRIPTION = "Advisory convergence tolerance: the per-finding convergence score (each finding's severity floor + confidence-and-likelihood-weighted headroom; ceilings critical 4 \xB7 major 2 \xB7 minor 1 \xB7 nit 0) at or below which the sticky reads as converged. The floor values and the systemic-likelihood rule are documented in the README and the findings schema (default: 1)";
|
|
3796
3946
|
var NIT_VISIBILITY_FLOOR_DESCRIPTION = "Nit visibility floor: nits whose confidence \xD7 likelihood falls below this are hidden from humans (no inline comment; a collapsed aside in the sticky) but kept in the machine blob as adjudicated. In [0, 1] (default: 0.25)";
|
|
3797
3947
|
var renderCmd = defineCommand({
|
|
@@ -3834,6 +3984,10 @@ var renderCmd = defineCommand({
|
|
|
3834
3984
|
type: "string",
|
|
3835
3985
|
description: TEST_REPORT_DESCRIPTION
|
|
3836
3986
|
},
|
|
3987
|
+
"cloc-diff": {
|
|
3988
|
+
type: "string",
|
|
3989
|
+
description: CLOC_DIFF_DESCRIPTION
|
|
3990
|
+
},
|
|
3837
3991
|
"convergence-threshold": {
|
|
3838
3992
|
type: "string",
|
|
3839
3993
|
description: CONVERGENCE_THRESHOLD_DESCRIPTION
|
|
@@ -3851,6 +4005,7 @@ var renderCmd = defineCommand({
|
|
|
3851
4005
|
const prices = decode(PriceMapCodec.decode(readJSON(priceResolution.path)), "prices");
|
|
3852
4006
|
const template = readFileSync(templatePath, "utf-8");
|
|
3853
4007
|
const testReport = args["test-report"] ? decode(TestSummaryCodec.decode(readJSON(args["test-report"])), "test report") : void 0;
|
|
4008
|
+
const clocDiff = args["cloc-diff"] ? loadClocDiff(args["cloc-diff"]) : void 0;
|
|
3854
4009
|
const route = args.route || envelope.route || null;
|
|
3855
4010
|
const isRound = isConvergenceRound(route, envelope.incomplete === true || isIncompleteFindings(findings)) && isReviewVerdict(findings.verdict);
|
|
3856
4011
|
const threshold = parseConvergenceThreshold(args["convergence-threshold"]);
|
|
@@ -3868,10 +4023,12 @@ var renderCmd = defineCommand({
|
|
|
3868
4023
|
route: args.route,
|
|
3869
4024
|
effort: args.effort,
|
|
3870
4025
|
testReport,
|
|
4026
|
+
clocDiff,
|
|
3871
4027
|
convergenceThreshold: threshold,
|
|
3872
4028
|
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
3873
4029
|
convergenceRound: isRound,
|
|
3874
|
-
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
4030
|
+
postedAt: formatUtc(/* @__PURE__ */ new Date()),
|
|
4031
|
+
pricedAt: /* @__PURE__ */ new Date()
|
|
3875
4032
|
});
|
|
3876
4033
|
process.stdout.write(output2);
|
|
3877
4034
|
}
|
|
@@ -3936,7 +4093,11 @@ var costCmd = defineCommand({
|
|
|
3936
4093
|
run: async ({ args }) => {
|
|
3937
4094
|
const envelope = decode(ResultEnvelopeCodec.decode(readJSON(args.envelope)), "envelope");
|
|
3938
4095
|
const prices = decode(PriceMapCodec.decode(readJSON(args.prices)), "prices");
|
|
3939
|
-
const report = computeCost(
|
|
4096
|
+
const report = computeCost(
|
|
4097
|
+
envelope.models,
|
|
4098
|
+
prices,
|
|
4099
|
+
parseInstant(envelope.generated_at) ?? /* @__PURE__ */ new Date()
|
|
4100
|
+
);
|
|
3940
4101
|
process.stdout.write(JSON.stringify(report, null, 2));
|
|
3941
4102
|
}
|
|
3942
4103
|
});
|
|
@@ -3967,7 +4128,11 @@ var checkCostCmd = defineCommand({
|
|
|
3967
4128
|
const usage = sumTranscriptUsage(tree.entries);
|
|
3968
4129
|
const priceResolution = resolvePrices(args.prices);
|
|
3969
4130
|
const prices = decode(PriceMapCodec.decode(readJSON(priceResolution.path)), "prices");
|
|
3970
|
-
const report = computeCost(
|
|
4131
|
+
const report = computeCost(
|
|
4132
|
+
usage.models,
|
|
4133
|
+
prices,
|
|
4134
|
+
usage.lastTsMs !== null ? new Date(usage.lastTsMs) : /* @__PURE__ */ new Date()
|
|
4135
|
+
);
|
|
3971
4136
|
process.stdout.write(
|
|
3972
4137
|
`${JSON.stringify(
|
|
3973
4138
|
{
|
|
@@ -4111,7 +4276,17 @@ var budgetHookCmd = defineCommand({
|
|
|
4111
4276
|
const tree = transcriptPath ? readTranscriptTree(resolve$1(transcriptPath)) : void 0;
|
|
4112
4277
|
const usage = tree ? sumTranscriptUsage(tree.entries) : void 0;
|
|
4113
4278
|
const prices = args.prices ? tryReadPrices(args.prices) : null;
|
|
4114
|
-
const spentUsd = prices !== null && usage ?
|
|
4279
|
+
const spentUsd = prices !== null && usage ? (
|
|
4280
|
+
// Price at the transcript's last activity instant, not the wall clock (issue #170 review
|
|
4281
|
+
// r2). Silent warn: this budget-steering cost is recomputed on EVERY tool event, so a
|
|
4282
|
+
// misconfigured slot map would otherwise flood stderr; the final post's cost render warns.
|
|
4283
|
+
computeCost(
|
|
4284
|
+
usage.models,
|
|
4285
|
+
prices,
|
|
4286
|
+
usage.lastTsMs !== null ? new Date(usage.lastTsMs) : /* @__PURE__ */ new Date(),
|
|
4287
|
+
() => void 0
|
|
4288
|
+
).totalCostUSD
|
|
4289
|
+
) : null;
|
|
4115
4290
|
const wallMs = args.wall ? parseWallMs(args.wall) : null;
|
|
4116
4291
|
const output2 = evaluateBudgetHook(input, {
|
|
4117
4292
|
spentUsd,
|
|
@@ -4312,7 +4487,6 @@ ${printableSchema(schemaPath)}
|
|
|
4312
4487
|
}
|
|
4313
4488
|
});
|
|
4314
4489
|
var isSurfaceStampedDoc = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) && doc["schema_version"] === SURFACE_SCHEMA_VERSION;
|
|
4315
|
-
var PIPELINE_STAMPED_FIELDS2 = /* @__PURE__ */ new Set(["scope_metastasis", "convergence"]);
|
|
4316
4490
|
var withoutScopeMetastasis = (doc) => typeof doc === "object" && doc !== null && !Array.isArray(doc) ? Object.fromEntries(Object.entries(doc).filter(([key2]) => key2 !== "scope_metastasis")) : doc;
|
|
4317
4491
|
var seedDraftCmd = defineCommand({
|
|
4318
4492
|
meta: {
|
|
@@ -4457,12 +4631,12 @@ var seedDraftCmd = defineCommand({
|
|
|
4457
4631
|
const schemaPath = args.schema ? resolve$1(args.schema) : schemaPathFor(kind, args["schema-version"]);
|
|
4458
4632
|
const barePrior = typeof priorFindings === "object" && !Array.isArray(priorFindings) ? Object.fromEntries(
|
|
4459
4633
|
Object.entries(priorFindings).filter(
|
|
4460
|
-
([key2]) => !
|
|
4634
|
+
([key2]) => !RECOVERABLE_OPTIONAL_FIELDS.has(key2)
|
|
4461
4635
|
)
|
|
4462
4636
|
) : priorFindings;
|
|
4463
4637
|
const accepts = (doc) => validateAgainstSchema(doc, schemaPath).valid && resolve("findings", doc).kind === "ok";
|
|
4464
4638
|
const seedDoc = accepts(priorFindings) ? priorFindings : accepts(barePrior) ? (process.stderr.write(
|
|
4465
|
-
`Note: the in-force schema rejects
|
|
4639
|
+
`Note: the in-force schema rejects a carried recoverable field (scope_metastasis/convergence/change_size) \u2014 seeding the prior without it (issue #150 review r2 / #182 review r2)
|
|
4466
4640
|
`
|
|
4467
4641
|
), barePrior) : null;
|
|
4468
4642
|
if (seedDoc === null) return false;
|
|
@@ -4951,6 +5125,10 @@ var postCmd = defineCommand({
|
|
|
4951
5125
|
type: "string",
|
|
4952
5126
|
description: TEST_REPORT_DESCRIPTION
|
|
4953
5127
|
},
|
|
5128
|
+
"cloc-diff": {
|
|
5129
|
+
type: "string",
|
|
5130
|
+
description: CLOC_DIFF_DESCRIPTION
|
|
5131
|
+
},
|
|
4954
5132
|
"run-url": {
|
|
4955
5133
|
type: "string",
|
|
4956
5134
|
description: "Workflow run URL (transcript/traces), rendered as a link in the LLM Disclosure aside"
|
|
@@ -4984,11 +5162,13 @@ var postCmd = defineCommand({
|
|
|
4984
5162
|
headBranch: args["head-branch"],
|
|
4985
5163
|
effort: args.effort,
|
|
4986
5164
|
testReportPath: args["test-report"],
|
|
5165
|
+
clocDiffPath: args["cloc-diff"],
|
|
4987
5166
|
runUrl: args["run-url"],
|
|
4988
5167
|
jsonUrl: args["json-url"],
|
|
4989
5168
|
convergenceThreshold: parseConvergenceThreshold(args["convergence-threshold"]),
|
|
4990
5169
|
nitVisibilityFloor: parseNitVisibilityFloor(args["nit-visibility-floor"]),
|
|
4991
|
-
postedAt: formatUtc(/* @__PURE__ */ new Date())
|
|
5170
|
+
postedAt: formatUtc(/* @__PURE__ */ new Date()),
|
|
5171
|
+
pricedAt: /* @__PURE__ */ new Date()
|
|
4992
5172
|
});
|
|
4993
5173
|
}
|
|
4994
5174
|
});
|