altimate-receipts 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MSPULJEI.js → chunk-6QULLAVH.js} +2 -2
- package/dist/chunk-NPYC3NGR.js +859 -0
- package/dist/chunk-NPYC3NGR.js.map +1 -0
- package/dist/{chunk-ZORGM2DA.js → chunk-SGVU3CGP.js} +330 -23
- package/dist/chunk-SGVU3CGP.js.map +1 -0
- package/dist/cli.js +54 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -232
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +2 -2
- package/package.json +1 -1
- package/schema/agent-execution-receipt-v1.json +62 -0
- package/dist/chunk-FTKMO26V.js +0 -767
- package/dist/chunk-FTKMO26V.js.map +0 -1
- package/dist/chunk-ZORGM2DA.js.map +0 -1
- /package/dist/{chunk-MSPULJEI.js.map → chunk-6QULLAVH.js.map} +0 -0
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
agentIds,
|
|
4
|
+
applyDiffScope,
|
|
5
|
+
buildReceipt,
|
|
6
|
+
deriveEvidence,
|
|
7
|
+
deriveFindings,
|
|
8
|
+
deriveSpans,
|
|
9
|
+
destructiveOutsideRepo,
|
|
10
|
+
findingSurface,
|
|
11
|
+
formatCostAlways,
|
|
12
|
+
formatTokens,
|
|
13
|
+
gradeLetter,
|
|
14
|
+
loadById,
|
|
15
|
+
narrowEffort,
|
|
16
|
+
privileged,
|
|
17
|
+
redact,
|
|
18
|
+
redactReceipt,
|
|
19
|
+
renderLedger,
|
|
20
|
+
windowedEffort
|
|
21
|
+
} from "./chunk-SGVU3CGP.js";
|
|
22
|
+
|
|
23
|
+
// src/receipt/canonical.ts
|
|
24
|
+
function canonicalize(value) {
|
|
25
|
+
return serialize(value);
|
|
26
|
+
}
|
|
27
|
+
function serialize(value) {
|
|
28
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
29
|
+
return JSON.stringify(value);
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === "string") {
|
|
32
|
+
return JSON.stringify(value);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return `[${value.map(serialize).join(",")}]`;
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "object") {
|
|
38
|
+
const obj = value;
|
|
39
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
|
|
40
|
+
const body = keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(",");
|
|
41
|
+
return `{${body}}`;
|
|
42
|
+
}
|
|
43
|
+
return "null";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/report/prComment.ts
|
|
47
|
+
import { createHash } from "crypto";
|
|
48
|
+
|
|
49
|
+
// src/report/checks.ts
|
|
50
|
+
var CHECK_CATALOG = [
|
|
51
|
+
{
|
|
52
|
+
key: "destructive",
|
|
53
|
+
icon: "\u{1F6E1}\uFE0F",
|
|
54
|
+
label: "destructive ops",
|
|
55
|
+
prefixes: ["destructive", "file-shrink"]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: "git-history",
|
|
59
|
+
icon: "\u{1F33F}",
|
|
60
|
+
label: "git-history safety",
|
|
61
|
+
prefixes: ["force-push", "history-rewrite"]
|
|
62
|
+
},
|
|
63
|
+
{ key: "secrets", icon: "\u{1F511}", label: "secret leaks", prefixes: ["secret"] },
|
|
64
|
+
{ key: "cicd", icon: "\u2699\uFE0F", label: "CI/CD tampering", prefixes: ["ci-cd-touch"] },
|
|
65
|
+
{ key: "lockfile", icon: "\u{1F4E6}", label: "lockfile integrity", prefixes: ["lockfile-edit"] },
|
|
66
|
+
{
|
|
67
|
+
key: "tests",
|
|
68
|
+
icon: "\u{1F9EA}",
|
|
69
|
+
label: "test integrity",
|
|
70
|
+
prefixes: [
|
|
71
|
+
"fake-green",
|
|
72
|
+
"test-focus",
|
|
73
|
+
"test-skipped",
|
|
74
|
+
"test-trivialised",
|
|
75
|
+
"grader-edit",
|
|
76
|
+
"eval-override"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "weakening",
|
|
81
|
+
icon: "\u{1F513}",
|
|
82
|
+
label: "config/safety weakening",
|
|
83
|
+
prefixes: ["config-weaken", "hook-bypass", "pipe-sh"]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: "blind-edits",
|
|
87
|
+
icon: "\u{1F441}\uFE0F",
|
|
88
|
+
label: "blind / unverified edits",
|
|
89
|
+
prefixes: ["blind-edit", "unverified-change", "edit-reversion"]
|
|
90
|
+
},
|
|
91
|
+
{ key: "injection", icon: "\u{1F489}", label: "prompt-injection", prefixes: ["prompt-injection"] },
|
|
92
|
+
{ key: "trojan", icon: "\u{1F575}\uFE0F", label: "hidden unicode", prefixes: ["trojan-source"] },
|
|
93
|
+
{
|
|
94
|
+
key: "duplication",
|
|
95
|
+
icon: "\u{1F4CB}",
|
|
96
|
+
label: "duplicated code",
|
|
97
|
+
prefixes: ["dup-file", "duplicated-code"]
|
|
98
|
+
},
|
|
99
|
+
{ key: "swallowed", icon: "\u{1F92B}", label: "swallowed errors", prefixes: ["error-swallowed"] },
|
|
100
|
+
{ key: "stubbed", icon: "\u{1F6A7}", label: "stubbed implementation", prefixes: ["impl-stubbed"] },
|
|
101
|
+
{ key: "malformed", icon: "\u{1F9F1}", label: "malformed artifacts", prefixes: ["malformed-artifact"] },
|
|
102
|
+
{ key: "truncated", icon: "\u2702\uFE0F", label: "truncated turns", prefixes: ["truncated-turn"] },
|
|
103
|
+
{
|
|
104
|
+
key: "promises",
|
|
105
|
+
icon: "\u{1F91D}",
|
|
106
|
+
label: "unfulfilled claims",
|
|
107
|
+
prefixes: ["claimed-commit-none", "unfulfilled-promise"]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
key: "tool-calls",
|
|
111
|
+
icon: "\u{1F9EC}",
|
|
112
|
+
label: "tool-call integrity",
|
|
113
|
+
prefixes: ["tool-fabricated", "tool-malformed-args"]
|
|
114
|
+
}
|
|
115
|
+
];
|
|
116
|
+
function matches(id, check) {
|
|
117
|
+
return check.prefixes.some((p) => id === p || id.startsWith(`${p}-`));
|
|
118
|
+
}
|
|
119
|
+
function checkForId(id) {
|
|
120
|
+
return CHECK_CATALOG.find((c) => matches(id, c));
|
|
121
|
+
}
|
|
122
|
+
var SEV_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
123
|
+
function categorize(findings) {
|
|
124
|
+
const flagged = [];
|
|
125
|
+
const cleared = [];
|
|
126
|
+
for (const check of CHECK_CATALOG) {
|
|
127
|
+
const hits = findings.filter((f) => matches(f.id, check));
|
|
128
|
+
if (hits.length > 0) {
|
|
129
|
+
const severity = hits.map((f) => f.severity ?? "high").sort((a, b) => SEV_RANK[a] - SEV_RANK[b])[0];
|
|
130
|
+
flagged.push({ check, count: hits.length, severity });
|
|
131
|
+
} else {
|
|
132
|
+
cleared.push(check);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { flagged, cleared, total: CHECK_CATALOG.length };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/report/prComment.ts
|
|
139
|
+
var CLASS_ORDER = [
|
|
140
|
+
"mutating",
|
|
141
|
+
"vcs-history",
|
|
142
|
+
"transport",
|
|
143
|
+
"test",
|
|
144
|
+
"build",
|
|
145
|
+
"read-only",
|
|
146
|
+
"opaque"
|
|
147
|
+
];
|
|
148
|
+
var PR_COMMENT_MARKER = "<!-- verified-by-receipts -->";
|
|
149
|
+
var TRUST_DOC_URL = "https://github.com/AltimateAI/altimate-receipts/blob/main/docs/trust.md";
|
|
150
|
+
var ABOUT_DOC_URL = "https://github.com/AltimateAI/altimate-receipts/blob/main/docs/problems.md";
|
|
151
|
+
var FEEDBACK_ISSUE_BASE = "https://github.com/AltimateAI/altimate-receipts/issues/new";
|
|
152
|
+
var VISIBLE_ROWS = 7;
|
|
153
|
+
function visibleMergeFindings(findings) {
|
|
154
|
+
return findings.filter(
|
|
155
|
+
(f) => findingSurface(f.id) === "merge" && f.severity !== "low" && !(f.id.startsWith("destructive-") && destructiveOutsideRepo(f.title))
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
function eventKey(f) {
|
|
159
|
+
return `${f.title} ${f.filePath ?? ""}`;
|
|
160
|
+
}
|
|
161
|
+
function eventId(f) {
|
|
162
|
+
const prefix = f.id.replace(/-tool-.*$/, "").replace(/-\d+(-\d+)?$/, "");
|
|
163
|
+
let h = 2166136261;
|
|
164
|
+
for (const ch of `${prefix}:${f.filePath ?? f.title}`) {
|
|
165
|
+
h ^= ch.charCodeAt(0);
|
|
166
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
167
|
+
}
|
|
168
|
+
return h.toString(16).padStart(8, "0");
|
|
169
|
+
}
|
|
170
|
+
function renderPrComment(receipt, opts = {}) {
|
|
171
|
+
const p = receipt.predicate;
|
|
172
|
+
const ev = p.evidence;
|
|
173
|
+
const operatorCount = p.findings.filter((f) => findingSurface(f.id) === "operator").length;
|
|
174
|
+
const main = visibleMergeFindings(p.findings);
|
|
175
|
+
const cats = categorize(main);
|
|
176
|
+
const files = p.scope?.kind === "diff" ? p.scope.files ?? [] : [];
|
|
177
|
+
const priv = main.filter((f) => privileged(f.id, f.filePath));
|
|
178
|
+
const rest = main.filter((f) => !privileged(f.id, f.filePath));
|
|
179
|
+
const cleared = p.prCleared ?? [];
|
|
180
|
+
const lastPush = p.prHistory?.[p.prHistory.length - 1];
|
|
181
|
+
const out = [];
|
|
182
|
+
out.push(PR_COMMENT_MARKER);
|
|
183
|
+
if (main.length === 0 && cleared.length === 0 && coverageFull(ev, files)) {
|
|
184
|
+
out.push(masthead(p, ev, files));
|
|
185
|
+
out.push("");
|
|
186
|
+
out.push(
|
|
187
|
+
`receipts \u2014 nothing detected${coverageCell(ev, files) ? ` \xB7 ${coverageCell(ev, files)}` : ""}${ev.diffCostUsd != null ? ` \xB7 \u2248 ${formatCostAlways(ev.diffCostUsd)}` : ""} \xB7 ${testsCell(ev)}`
|
|
188
|
+
);
|
|
189
|
+
out.push("");
|
|
190
|
+
out.push(recordDetails(p, ev, cats, operatorCount, opts, feedbackLine(p.session.agent, main)));
|
|
191
|
+
out.push(footer());
|
|
192
|
+
out.push(stateBlock(p, ev, files, main, cleared));
|
|
193
|
+
return `${out.join("\n")}
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
out.push(masthead(p, ev, files));
|
|
197
|
+
out.push("");
|
|
198
|
+
const status = [
|
|
199
|
+
lastPush && p.prHistory && p.prHistory.length > 1 ? `push ${lastPush.push}${lastPush.sha ? ` \xB7 \`${lastPush.sha}\`` : ""} \u2014 +${lastPush.new} new \xB7 ${lastPush.cleared} cleared \xB7 ${lastPush.open} open` : null,
|
|
200
|
+
coverageCell(ev, files)
|
|
201
|
+
].filter(Boolean);
|
|
202
|
+
if (status.length) {
|
|
203
|
+
out.push(status.join(" \xB7 "));
|
|
204
|
+
out.push("");
|
|
205
|
+
}
|
|
206
|
+
if (priv.length) {
|
|
207
|
+
out.push("**Review as access control**");
|
|
208
|
+
for (const f of priv) {
|
|
209
|
+
out.push(eventRow(f, true, files, opts.diffLinkBase));
|
|
210
|
+
}
|
|
211
|
+
out.push("");
|
|
212
|
+
}
|
|
213
|
+
if (rest.length) {
|
|
214
|
+
out.push("**Sanity-check**");
|
|
215
|
+
for (const f of rest.slice(0, VISIBLE_ROWS)) {
|
|
216
|
+
out.push(eventRow(f, false, files, opts.diffLinkBase));
|
|
217
|
+
}
|
|
218
|
+
if (rest.length > VISIBLE_ROWS) {
|
|
219
|
+
out.push(`- _+${rest.length - VISIBLE_ROWS} more in the record below_`);
|
|
220
|
+
}
|
|
221
|
+
out.push("");
|
|
222
|
+
}
|
|
223
|
+
if (cleared.length) {
|
|
224
|
+
for (const c of cleared) {
|
|
225
|
+
const loc = c.filePath ? ` (\`${c.filePath}\`)` : "";
|
|
226
|
+
out.push(
|
|
227
|
+
`- ~~${c.title}~~${loc} \xB7 no longer detected${lastPush ? ` push ${lastPush.push}` : ""}`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
out.push("");
|
|
231
|
+
}
|
|
232
|
+
const flaggedSet = new Set(
|
|
233
|
+
main.map((f) => f.filePath ? displayPath(f.filePath, files) : null).filter(Boolean)
|
|
234
|
+
);
|
|
235
|
+
if (files.length) {
|
|
236
|
+
out.push(`Other ${Math.max(0, files.length - flaggedSet.size)} files: nothing detected.`);
|
|
237
|
+
out.push("");
|
|
238
|
+
}
|
|
239
|
+
out.push(`<sub>tests: ${testsCell(ev)}</sub>`);
|
|
240
|
+
out.push("");
|
|
241
|
+
out.push(recordDetails(p, ev, cats, operatorCount, opts, feedbackLine(p.session.agent, main)));
|
|
242
|
+
out.push(footer());
|
|
243
|
+
out.push(stateBlock(p, ev, files, main, cleared));
|
|
244
|
+
return `${out.join("\n")}
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
function masthead(p, ev, files) {
|
|
248
|
+
const parts = [`Agent work record \u2014 ${p.session.agent}`];
|
|
249
|
+
if (files.length) {
|
|
250
|
+
parts.push(`${files.length} file${files.length === 1 ? "" : "s"}`);
|
|
251
|
+
}
|
|
252
|
+
if (p.prEffort && p.prEffort.sessions > 1) {
|
|
253
|
+
parts.push(`${p.prEffort.sessions} sessions`);
|
|
254
|
+
parts.push(`spent \u2248 ${formatCostAlways(p.prEffort.totalUsd)} on this PR`);
|
|
255
|
+
} else if (ev.diffCostUsd != null) {
|
|
256
|
+
parts.push(`spent \u2248 ${formatCostAlways(ev.diffCostUsd)} on this PR`);
|
|
257
|
+
}
|
|
258
|
+
return `### ${parts.join(" \xB7 ")}`;
|
|
259
|
+
}
|
|
260
|
+
function coverageCell(ev, files) {
|
|
261
|
+
if (ev.coveredFiles == null || !files.length) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const gap = files.length - ev.coveredFiles;
|
|
265
|
+
if (gap > 0) {
|
|
266
|
+
return `\u26A0 ${gap} changed file${gap === 1 ? "" : "s"} ha${gap === 1 ? "s" : "ve"} no transcript activity (${ev.coveredFiles}/${files.length} covered)`;
|
|
267
|
+
}
|
|
268
|
+
return `transcript covers ${ev.coveredFiles}/${files.length} changed files`;
|
|
269
|
+
}
|
|
270
|
+
function coverageFull(ev, files) {
|
|
271
|
+
return ev.coveredFiles == null || !files.length || ev.coveredFiles >= files.length;
|
|
272
|
+
}
|
|
273
|
+
function displayPath(filePath, scopeFiles) {
|
|
274
|
+
for (const sf of scopeFiles) {
|
|
275
|
+
if (filePath === sf || filePath.endsWith(`/${sf}`)) {
|
|
276
|
+
return sf;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (!filePath.startsWith("/") && !filePath.startsWith("~")) {
|
|
280
|
+
return filePath;
|
|
281
|
+
}
|
|
282
|
+
const segs = filePath.split("/").filter(Boolean);
|
|
283
|
+
return segs.length > 2 ? segs.slice(-2).join("/") : filePath;
|
|
284
|
+
}
|
|
285
|
+
function diffAnchor(base2, path, line) {
|
|
286
|
+
const h = createHash("sha256").update(path).digest("hex");
|
|
287
|
+
return `${base2}#diff-${h}${line ? `R${line}` : ""}`;
|
|
288
|
+
}
|
|
289
|
+
function eventRow(f, priv, scopeFiles, linkBase) {
|
|
290
|
+
const mark = priv ? "\u25B2 " : "";
|
|
291
|
+
const shown = f.filePath ? displayPath(f.filePath, scopeFiles) : void 0;
|
|
292
|
+
const chip = shown ? `\`${shown}${f.line ? `:${f.line}` : ""}\`` : "";
|
|
293
|
+
const file = chip ? `${linkBase && shown ? `[${chip}](${diffAnchor(linkBase, shown, f.line)})` : chip} \u2014 ` : "";
|
|
294
|
+
const base2 = f.filePath?.split("/").pop();
|
|
295
|
+
let fact = f.title;
|
|
296
|
+
if (base2) {
|
|
297
|
+
fact = fact.replace(new RegExp(`:\\s*\`?${escapeRe(base2)}\`?\\s*$`), "").trim();
|
|
298
|
+
}
|
|
299
|
+
const why = f.impactLabel ? ` \xB7 ${f.impactLabel}` : "";
|
|
300
|
+
const ref = f.evidenceRef ? linkBase && shown ? ` \xB7 [evidence \`${f.evidenceRef}\`](${diffAnchor(linkBase, shown, f.line)})` : ` \xB7 evidence \`${f.evidenceRef}\`` : "";
|
|
301
|
+
return `- ${mark}${file}**${fact}**${why}${ref}`;
|
|
302
|
+
}
|
|
303
|
+
function escapeRe(s) {
|
|
304
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
305
|
+
}
|
|
306
|
+
function testsCell(ev) {
|
|
307
|
+
const tm = ev.testMetrics;
|
|
308
|
+
if (tm) {
|
|
309
|
+
const seg = [
|
|
310
|
+
tm.passed != null ? `${tm.passed} passed` : null,
|
|
311
|
+
tm.failed ? `**${tm.failed} failed**` : null,
|
|
312
|
+
tm.skipped ? `${tm.skipped} skipped` : null
|
|
313
|
+
].filter(Boolean).join(", ");
|
|
314
|
+
return `${seg || tm.exitStatus} _(parsed from runner output in transcript)_`;
|
|
315
|
+
}
|
|
316
|
+
return ev.testsRan ? "test command observed \u2014 outcome not parsed" : "no test run detected in transcript";
|
|
317
|
+
}
|
|
318
|
+
function recordDetails(p, ev, cats, operatorCount, opts, feedback) {
|
|
319
|
+
const inner = [];
|
|
320
|
+
if (p.prHistory?.length) {
|
|
321
|
+
inner.push("| push | events | change cost |", "| :-- | :-- | :-- |");
|
|
322
|
+
for (const h of p.prHistory) {
|
|
323
|
+
const id = h.sha ? ` \xB7 \`${h.sha}\`` : "";
|
|
324
|
+
const cost = h.costUsd != null ? formatCostAlways(h.costUsd) : "\u2014";
|
|
325
|
+
inner.push(
|
|
326
|
+
`| ${h.push}${id}${dateCell(h.endedAt)} | +${h.new} new \xB7 ${h.cleared} cleared \xB7 ${h.open} open | ${cost} |`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
inner.push(
|
|
330
|
+
"<sub>change cost per row = the attributed cost at that push (cumulative within a session).</sub>",
|
|
331
|
+
""
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const files = p.scope?.kind === "diff" ? p.scope.files ?? [] : [];
|
|
335
|
+
if (files.length) {
|
|
336
|
+
inner.push(changedFilesBlock(files), "");
|
|
337
|
+
}
|
|
338
|
+
const ledger = renderLedger(ev.claims ?? []);
|
|
339
|
+
if (ledger) {
|
|
340
|
+
inner.push(ledger, "");
|
|
341
|
+
}
|
|
342
|
+
inner.push(
|
|
343
|
+
`**Checks** _("not detected" means this check found nothing \u2014 not that nothing exists)_`,
|
|
344
|
+
"",
|
|
345
|
+
checksTable(cats),
|
|
346
|
+
""
|
|
347
|
+
);
|
|
348
|
+
const cl = diffCostLine(ev, p.prEffort);
|
|
349
|
+
if (cl) {
|
|
350
|
+
inner.push(cl);
|
|
351
|
+
}
|
|
352
|
+
inner.push(`<sub>${effortLine(ev, operatorCount)}</sub>`);
|
|
353
|
+
const signed = opts.signed ? `Signed (Sigstore \u2192 Rekor)${opts.attestationUrl ? ` \xB7 [attestation](${opts.attestationUrl})` : ""} \xB7 ` : "";
|
|
354
|
+
inner.push(
|
|
355
|
+
`<sub>${signed}Re-derivable (L1: \`receipts verify <receipt> --transcript <t>\`) \xB7 pull any event's tape: \`npx altimate-receipts log --ref <evidence-id>\` \xB7 deterministic \xB7 0 model calls \xB7 evidence, not judgement \xB7 [trust model](${TRUST_DOC_URL}).</sub>`
|
|
356
|
+
);
|
|
357
|
+
const hint = upgradeHint(p.generator?.version, opts.compareVersion);
|
|
358
|
+
if (hint) {
|
|
359
|
+
inner.push(hint);
|
|
360
|
+
}
|
|
361
|
+
inner.push(feedback);
|
|
362
|
+
return [
|
|
363
|
+
"<details><summary>Record \u2014 pushes, files, checks, custody (append-only)</summary>",
|
|
364
|
+
"",
|
|
365
|
+
...inner,
|
|
366
|
+
"",
|
|
367
|
+
"</details>"
|
|
368
|
+
].join("\n");
|
|
369
|
+
}
|
|
370
|
+
function dateCell(endedAt) {
|
|
371
|
+
if (!endedAt || !Number.isFinite(endedAt)) {
|
|
372
|
+
return "";
|
|
373
|
+
}
|
|
374
|
+
return ` \xB7 ${new Date(endedAt).toISOString().slice(0, 10)}`;
|
|
375
|
+
}
|
|
376
|
+
function footer() {
|
|
377
|
+
return `
|
|
378
|
+
<sub>[what is this? \u203A](${ABOUT_DOC_URL}) \u2014 a record of the agent's process, not a code review \xB7 entries are never edited or removed</sub>`;
|
|
379
|
+
}
|
|
380
|
+
function stateBlock(p, ev, files, main, cleared) {
|
|
381
|
+
const lastPush = p.prHistory?.[p.prHistory.length - 1]?.push ?? 1;
|
|
382
|
+
const events = [
|
|
383
|
+
...main.map((f) => ({
|
|
384
|
+
file: f.filePath ? displayPath(f.filePath, files) : "",
|
|
385
|
+
id: eventId({ ...f, filePath: f.filePath ? displayPath(f.filePath, files) : void 0 }),
|
|
386
|
+
privileged: privileged(f.id, f.filePath),
|
|
387
|
+
state: "open",
|
|
388
|
+
type: f.id.replace(/-tool-.*$/, "").replace(/-\d+(-\d+)?$/, "")
|
|
389
|
+
})),
|
|
390
|
+
...cleared.map((c) => ({
|
|
391
|
+
file: c.filePath ? displayPath(c.filePath, files) : "",
|
|
392
|
+
id: eventId({
|
|
393
|
+
id: "cleared",
|
|
394
|
+
title: c.title,
|
|
395
|
+
filePath: c.filePath ? displayPath(c.filePath, files) : void 0
|
|
396
|
+
}),
|
|
397
|
+
privileged: false,
|
|
398
|
+
state: "cleared",
|
|
399
|
+
type: "cleared"
|
|
400
|
+
}))
|
|
401
|
+
];
|
|
402
|
+
const state = {
|
|
403
|
+
cost_cents: ev.diffCostUsd != null ? Math.round((p.prEffort?.totalUsd ?? ev.diffCostUsd) * 100) : null,
|
|
404
|
+
coverage: ev.coveredFiles != null ? { covered: ev.coveredFiles, total: files.length } : null,
|
|
405
|
+
events,
|
|
406
|
+
push: lastPush,
|
|
407
|
+
schema_version: 1
|
|
408
|
+
};
|
|
409
|
+
return `<!-- receipts-state v1 ${JSON.stringify(state)} -->`;
|
|
410
|
+
}
|
|
411
|
+
function changedFilesBlock(files) {
|
|
412
|
+
if (files.length <= 12) {
|
|
413
|
+
return `**Changed** ${files.map((f) => `\`${f}\``).join(", ")}`;
|
|
414
|
+
}
|
|
415
|
+
const byDir = /* @__PURE__ */ new Map();
|
|
416
|
+
for (const f of files) {
|
|
417
|
+
const top = f.includes("/") ? `${f.split("/")[0]}/` : "(root)";
|
|
418
|
+
byDir.set(top, (byDir.get(top) ?? 0) + 1);
|
|
419
|
+
}
|
|
420
|
+
const dirs = [...byDir.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([d, n]) => `\`${d}\` ${n}`).join(" \xB7 ");
|
|
421
|
+
const moreDirs = byDir.size > 8 ? ` \xB7 _+${byDir.size - 8} more dirs_` : "";
|
|
422
|
+
const sample = files.slice(0, 60).map((f) => `\`${f}\``).join(", ");
|
|
423
|
+
const moreFiles = files.length > 60 ? ` _\u2026+${files.length - 60} more (full set in the receipt JSON)_` : "";
|
|
424
|
+
return [
|
|
425
|
+
`**Changed ${files.length} files** by area: ${dirs}${moreDirs}`,
|
|
426
|
+
"",
|
|
427
|
+
"<details><summary>file list</summary>",
|
|
428
|
+
"",
|
|
429
|
+
`${sample}${moreFiles}`,
|
|
430
|
+
"",
|
|
431
|
+
"</details>"
|
|
432
|
+
].join("\n");
|
|
433
|
+
}
|
|
434
|
+
function parseSemver(v) {
|
|
435
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(String(v ?? "").trim());
|
|
436
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
437
|
+
}
|
|
438
|
+
function semverLt(a, b) {
|
|
439
|
+
const pa = parseSemver(a);
|
|
440
|
+
const pb = parseSemver(b);
|
|
441
|
+
if (!pa || !pb) return false;
|
|
442
|
+
for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pa[i] < pb[i];
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
function upgradeHint(receiptVer, compareVer) {
|
|
446
|
+
if (!compareVer || !semverLt(receiptVer, compareVer)) return null;
|
|
447
|
+
return `<sub>Receipt generated by \`receipts@${receiptVer}\` \xB7 this check runs \`@${compareVer}\` \u2014 \`npm i -g altimate-receipts@latest\` (or \`npx altimate-receipts@latest\`) for the newer checks.</sub>`;
|
|
448
|
+
}
|
|
449
|
+
function feedbackLine(agent, flagged) {
|
|
450
|
+
const findingLines = flagged.length ? flagged.slice(0, 8).map((f) => `- ${f.title} (\`${f.id}\`)`) : ["- (nothing was flagged \u2014 describe what you expected)"];
|
|
451
|
+
const body = [
|
|
452
|
+
"**Repo / PR:** __PR_URL__",
|
|
453
|
+
`**Agent:** ${agent}`,
|
|
454
|
+
"",
|
|
455
|
+
"**Flagged event(s) being disputed:**",
|
|
456
|
+
...findingLines,
|
|
457
|
+
"",
|
|
458
|
+
"**Why this is a false positive / noise:**",
|
|
459
|
+
"_(your notes \u2014 what should it have done instead?)_"
|
|
460
|
+
].join("\n");
|
|
461
|
+
const title = flagged.length ? `False positive: ${flagged[0].title}` : "Receipts feedback";
|
|
462
|
+
const qs = `labels=${encodeURIComponent("false-positive,dogfooding")}&title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
|
|
463
|
+
const url = `${FEEDBACK_ISSUE_BASE}?${qs}`;
|
|
464
|
+
return `<sub>Wrong or noisy event? [Report it \u2014 prefilled \u203A](${url}) \xB7 or react \u{1F44D}/\u{1F44E} on this comment. It tunes the checks.</sub>`;
|
|
465
|
+
}
|
|
466
|
+
function checksTable(cats) {
|
|
467
|
+
const sev = new Map(cats.flagged.map((fl) => [fl.check.key, fl]));
|
|
468
|
+
const rows = [...cats.flagged.map((fl) => fl.check), ...cats.cleared].map((c) => {
|
|
469
|
+
const fl = sev.get(c.key);
|
|
470
|
+
const result = fl ? `${fl.count} detected` : "not detected";
|
|
471
|
+
return `| ${c.label} | ${result} |`;
|
|
472
|
+
});
|
|
473
|
+
rows.sort((a, b) => (a.includes("not detected") ? 1 : 0) - (b.includes("not detected") ? 1 : 0));
|
|
474
|
+
return ["| check | reading |", "| :-- | :-- |", ...rows].join("\n");
|
|
475
|
+
}
|
|
476
|
+
function diffCostLine(ev, pr) {
|
|
477
|
+
if (ev.diffCostUsd == null) return null;
|
|
478
|
+
const turns = ev.diffTurns ?? 0;
|
|
479
|
+
const t = `${turns} turn${turns === 1 ? "" : "s"}`;
|
|
480
|
+
const push = `\u2248 ${formatCostAlways(ev.diffCostUsd)} \xB7 ${formatTokens(ev.diffTokens ?? 0)} tokens \xB7 ${t}`;
|
|
481
|
+
if (pr && pr.sessions > 1) {
|
|
482
|
+
return `**Cost** \u2248 ${formatCostAlways(pr.totalUsd)} _(this PR, ${pr.sessions} sessions)_ \xB7 this push ${push} _(upper bound; the turns that edited these files)_`;
|
|
483
|
+
}
|
|
484
|
+
return `**Cost** ${push} _(this change \u2014 upper bound; the turns that edited these files)_`;
|
|
485
|
+
}
|
|
486
|
+
function effortLine(ev, operatorCount) {
|
|
487
|
+
const cbc = ev.commandsByClass;
|
|
488
|
+
const brk = cbc ? ` _(${CLASS_ORDER.filter((c) => cbc[c]).map((c) => `${cbc[c]} ${c}`).join(", ")})_` : "";
|
|
489
|
+
const op = operatorCount > 0 ? ` \xB7 ${operatorCount} efficiency/behaviour finding${operatorCount === 1 ? "" : "s"}` : "";
|
|
490
|
+
return `Session totals (may span other work): ${ev.edits} edits \xB7 ${ev.commands} commands${brk} \xB7 ${formatCostAlways(ev.costUsd)} \xB7 ${formatTokens(ev.tokens.total)} tokens${op}.`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/share/clipboard.ts
|
|
494
|
+
import { spawnSync } from "child_process";
|
|
495
|
+
function candidates() {
|
|
496
|
+
if (process.platform === "darwin") {
|
|
497
|
+
return [{ cmd: "pbcopy", args: [] }];
|
|
498
|
+
}
|
|
499
|
+
if (process.platform === "win32") {
|
|
500
|
+
return [{ cmd: "clip", args: [] }];
|
|
501
|
+
}
|
|
502
|
+
return [
|
|
503
|
+
{ cmd: "wl-copy", args: [] },
|
|
504
|
+
{ cmd: "xclip", args: ["-selection", "clipboard"] },
|
|
505
|
+
{ cmd: "xsel", args: ["--clipboard", "--input"] }
|
|
506
|
+
];
|
|
507
|
+
}
|
|
508
|
+
function copyToClipboard(text) {
|
|
509
|
+
for (const { cmd, args } of candidates()) {
|
|
510
|
+
try {
|
|
511
|
+
const res = spawnSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
512
|
+
if (!res.error && res.status === 0) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
} catch {
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/share/markdown.ts
|
|
522
|
+
var ICON = {
|
|
523
|
+
critical: "\u26D4",
|
|
524
|
+
high: "\u26A0\uFE0F",
|
|
525
|
+
medium: "\u{1F50D}",
|
|
526
|
+
low: "\xB7"
|
|
527
|
+
};
|
|
528
|
+
var base = (p) => p.split("/").pop() ?? p;
|
|
529
|
+
function renderShareMarkdown(args) {
|
|
530
|
+
const { summary, session, derived, findings } = args;
|
|
531
|
+
const { main, minor } = findings;
|
|
532
|
+
const g = gradeLetter(main);
|
|
533
|
+
const ev = deriveEvidence(session, derived);
|
|
534
|
+
const counts = ["critical", "high", "medium"].map((s) => {
|
|
535
|
+
const n = main.filter((f) => f.severity === s).length;
|
|
536
|
+
return n ? `${n} ${s}` : null;
|
|
537
|
+
}).filter(Boolean).join(" \xB7 ") || "no findings";
|
|
538
|
+
const title = redact(summary.title || "untitled session");
|
|
539
|
+
const out = [];
|
|
540
|
+
out.push(`## \u{1F9FE} Receipt \u2014 ${title} \xB7 Grade ${g}`);
|
|
541
|
+
out.push("");
|
|
542
|
+
out.push(
|
|
543
|
+
`**${main.length ? `${main.length} EVENT${main.length === 1 ? "" : "S"} ON RECORD` : "NOTHING DETECTED"}** \xB7 ${counts} \xB7 _${summary.source}_`
|
|
544
|
+
);
|
|
545
|
+
out.push("");
|
|
546
|
+
if (main.length) {
|
|
547
|
+
out.push("### Findings");
|
|
548
|
+
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
549
|
+
const sorted = [...main].sort(
|
|
550
|
+
(a, b) => order[a.severity] - order[b.severity] || b.score - a.score
|
|
551
|
+
);
|
|
552
|
+
for (const f of sorted.slice(0, 12)) {
|
|
553
|
+
const tag = f.impactLabel ? ` \u2014 ${redact(f.impactLabel)}` : "";
|
|
554
|
+
const loc = f.filePath ? ` (\`${redact(base(f.filePath))}\`)` : "";
|
|
555
|
+
out.push(`- ${ICON[f.severity]} **${redact(f.title)}**${tag}${loc}`);
|
|
556
|
+
}
|
|
557
|
+
if (minor.length) {
|
|
558
|
+
out.push(`- _+${minor.length} minor (collapsed)_`);
|
|
559
|
+
}
|
|
560
|
+
out.push("");
|
|
561
|
+
}
|
|
562
|
+
out.push("### Evidence");
|
|
563
|
+
out.push(
|
|
564
|
+
`${ev.filesChanged} files \xB7 ${ev.edits} edits \xB7 ${ev.commands} commands \xB7 ${ev.testsRan ? "tests ran \u2713" : "no test run detected"} \xB7 ${ev.destructiveOps} destructive ops`
|
|
565
|
+
);
|
|
566
|
+
out.push("");
|
|
567
|
+
out.push("_Verified by Receipts \xB7 deterministic \xB7 0 model calls \xB7 evidence, not judgement_");
|
|
568
|
+
return `${out.join("\n")}
|
|
569
|
+
`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/sign/envelope.ts
|
|
573
|
+
var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
|
|
574
|
+
function pae(payloadType, payload) {
|
|
575
|
+
const typeBytes = Buffer.from(payloadType, "utf8");
|
|
576
|
+
const bodyBytes = Buffer.from(payload, "utf8");
|
|
577
|
+
const header = `DSSEv1 ${typeBytes.length} ${payloadType} ${bodyBytes.length} `;
|
|
578
|
+
return Buffer.concat([Buffer.from(header, "utf8"), bodyBytes]);
|
|
579
|
+
}
|
|
580
|
+
function toDsseEnvelope(receipt) {
|
|
581
|
+
const canonical = canonicalize(receipt);
|
|
582
|
+
return {
|
|
583
|
+
payloadType: DSSE_PAYLOAD_TYPE,
|
|
584
|
+
payload: Buffer.from(canonical, "utf8").toString("base64"),
|
|
585
|
+
signatures: []
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function envelopePae(env) {
|
|
589
|
+
const raw = Buffer.from(env.payload, "base64").toString("utf8");
|
|
590
|
+
return pae(env.payloadType, raw);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/trace/commitMatch.ts
|
|
594
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
595
|
+
|
|
596
|
+
// src/trace/gitCommand.ts
|
|
597
|
+
var WRAPPERS = /* @__PURE__ */ new Set(["command", "exec", "nohup", "time", "env"]);
|
|
598
|
+
var GIT_VALUE_FLAGS = /* @__PURE__ */ new Set(["-C", "-c", "--git-dir", "--work-tree", "--exec-path"]);
|
|
599
|
+
function gitInvocations(command) {
|
|
600
|
+
const blanked = command.replace(/\\["']/g, " ").replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
|
|
601
|
+
const out = [];
|
|
602
|
+
for (const simple of blanked.split(/(?:&&|\|\||[;|\n])/)) {
|
|
603
|
+
const tokens = simple.trim().split(/\s+/).filter(Boolean);
|
|
604
|
+
let i = 0;
|
|
605
|
+
while (i < tokens.length && (/^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i]) || WRAPPERS.has(tokens[i]))) {
|
|
606
|
+
i++;
|
|
607
|
+
}
|
|
608
|
+
if (tokens[i] !== "git") {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
i++;
|
|
612
|
+
while (i < tokens.length && tokens[i].startsWith("-")) {
|
|
613
|
+
const flag = tokens[i];
|
|
614
|
+
i++;
|
|
615
|
+
if (GIT_VALUE_FLAGS.has(flag)) {
|
|
616
|
+
i++;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const sub = tokens[i];
|
|
620
|
+
if (sub) {
|
|
621
|
+
out.push({ sub, rest: tokens.slice(i + 1) });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return out;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/trace/commitMatch.ts
|
|
628
|
+
var SHA_CAP = 200;
|
|
629
|
+
function branchShas(base2, cwd) {
|
|
630
|
+
if (!base2) {
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
const r = spawnSync2("git", ["log", `--max-count=${SHA_CAP}`, "--format=%H", `${base2}..HEAD`], {
|
|
634
|
+
encoding: "utf8",
|
|
635
|
+
cwd
|
|
636
|
+
});
|
|
637
|
+
if (r.status !== 0) {
|
|
638
|
+
return [];
|
|
639
|
+
}
|
|
640
|
+
return r.stdout.split("\n").filter((s) => /^[0-9a-f]{40}$/.test(s));
|
|
641
|
+
}
|
|
642
|
+
var HEX_RUN = /\b[0-9a-f]{7,40}\b/g;
|
|
643
|
+
function commandOf(input) {
|
|
644
|
+
if (typeof input === "string") {
|
|
645
|
+
const t = input.trim();
|
|
646
|
+
if (t.startsWith("{")) {
|
|
647
|
+
try {
|
|
648
|
+
return commandOf(JSON.parse(t));
|
|
649
|
+
} catch {
|
|
650
|
+
return input;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return input;
|
|
654
|
+
}
|
|
655
|
+
if (input && typeof input === "object") {
|
|
656
|
+
const o = input;
|
|
657
|
+
for (const key of ["command", "cmd"]) {
|
|
658
|
+
const v = o[key];
|
|
659
|
+
if (typeof v === "string") {
|
|
660
|
+
return v;
|
|
661
|
+
}
|
|
662
|
+
if (Array.isArray(v)) {
|
|
663
|
+
return v.filter((t) => typeof t === "string").join(" ");
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return "";
|
|
668
|
+
}
|
|
669
|
+
function isGitWrite(command) {
|
|
670
|
+
return gitInvocations(command).some((g) => g.sub === "commit" || g.sub === "push");
|
|
671
|
+
}
|
|
672
|
+
function authoredBranch(spans, shas) {
|
|
673
|
+
if (shas.length === 0) {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
for (const s of spans) {
|
|
677
|
+
if (s.kind !== "tool" || typeof s.output !== "string") {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (!isGitWrite(commandOf(s.input))) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
for (const token of s.output.match(HEX_RUN) ?? []) {
|
|
684
|
+
if (shas.some((full) => full.startsWith(token))) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
function shaWindow(spans, shas) {
|
|
692
|
+
if (shas.length === 0) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
let firstOwn = -1;
|
|
696
|
+
let lastOwn = -1;
|
|
697
|
+
let lastForeignBeforeOwn = -1;
|
|
698
|
+
spans.forEach((s, i) => {
|
|
699
|
+
if (s.kind !== "tool" || typeof s.output !== "string") {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (!isGitWrite(commandOf(s.input))) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const tokens = s.output.match(HEX_RUN) ?? [];
|
|
706
|
+
if (tokens.length === 0) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const own = tokens.some((t) => shas.some((full) => full.startsWith(t)));
|
|
710
|
+
if (own) {
|
|
711
|
+
if (firstOwn === -1) {
|
|
712
|
+
firstOwn = i;
|
|
713
|
+
}
|
|
714
|
+
lastOwn = i;
|
|
715
|
+
} else if (firstOwn === -1) {
|
|
716
|
+
lastForeignBeforeOwn = i;
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
if (lastOwn === -1) {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
return { start: lastForeignBeforeOwn + 1, end: lastOwn };
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// src/trace/slice.ts
|
|
726
|
+
function branchesIn(session) {
|
|
727
|
+
const seen = [];
|
|
728
|
+
for (const m of session.messages) {
|
|
729
|
+
if (m.gitBranch && !seen.includes(m.gitBranch)) {
|
|
730
|
+
seen.push(m.gitBranch);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return seen;
|
|
734
|
+
}
|
|
735
|
+
function effectiveBranches(messages) {
|
|
736
|
+
const firstTagged = messages.find((m) => m.gitBranch)?.gitBranch;
|
|
737
|
+
let current = firstTagged;
|
|
738
|
+
return messages.map((m) => {
|
|
739
|
+
if (m.gitBranch) {
|
|
740
|
+
current = m.gitBranch;
|
|
741
|
+
}
|
|
742
|
+
return current;
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function sliceByBranch(session, branch) {
|
|
746
|
+
if (!branchesIn(session).includes(branch)) {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
const eff = effectiveBranches(session.messages);
|
|
750
|
+
const messages = session.messages.filter((_, i) => eff[i] === branch);
|
|
751
|
+
if (messages.length === 0) {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
const timestamps = messages.map((m) => m.timestamp).filter((t) => typeof t === "number");
|
|
755
|
+
const startedAt = timestamps.length ? Math.min(...timestamps) : session.startedAt;
|
|
756
|
+
const endedAt = timestamps.length ? Math.max(...timestamps) : session.endedAt;
|
|
757
|
+
return {
|
|
758
|
+
...session,
|
|
759
|
+
id: `${session.id}#${branch}`,
|
|
760
|
+
gitBranch: branch,
|
|
761
|
+
startedAt,
|
|
762
|
+
endedAt,
|
|
763
|
+
messages,
|
|
764
|
+
// Scope the subject digest to this branch's slice (reproducible by re-slicing).
|
|
765
|
+
digestSource: JSON.stringify({ id: session.id, branch, messages })
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/sign/rederive.ts
|
|
770
|
+
function sourceFromReceipt(receipt) {
|
|
771
|
+
const prefix = receipt.subject?.[0]?.name?.split("/")[0];
|
|
772
|
+
return agentIds().find((id) => id === prefix);
|
|
773
|
+
}
|
|
774
|
+
async function rederiveFromTranscript(path, opts = {}) {
|
|
775
|
+
const loaded = await loadById(opts.source ?? "claude-code", path);
|
|
776
|
+
if (!loaded) {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
const scope = opts.scope;
|
|
780
|
+
const branch = scope?.kind === "branch" ? scope.branch : opts.branch;
|
|
781
|
+
const session = branch ? sliceByBranch(loaded, branch) ?? loaded : loaded;
|
|
782
|
+
let derived = deriveSpans(session);
|
|
783
|
+
let findings = deriveFindings(derived);
|
|
784
|
+
if (scope?.kind === "diff" && scope.files) {
|
|
785
|
+
const scoped = applyDiffScope(derived, findings, scope.files, session.projectPath);
|
|
786
|
+
derived = scoped.derived;
|
|
787
|
+
findings = scoped.findings;
|
|
788
|
+
if (scope.branch) {
|
|
789
|
+
const slice = sliceByBranch(loaded, scope.branch);
|
|
790
|
+
let eff = narrowEffort(slice ? deriveSpans(slice) : null, scope.files);
|
|
791
|
+
if (!eff && scope.shas?.length) {
|
|
792
|
+
const win = shaWindow(derived.spans, scope.shas);
|
|
793
|
+
if (win) {
|
|
794
|
+
eff = windowedEffort(derived, scope.files, win);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (eff) {
|
|
798
|
+
derived = {
|
|
799
|
+
...derived,
|
|
800
|
+
diffCostUsd: eff.cost,
|
|
801
|
+
diffTokens: eff.tokens,
|
|
802
|
+
diffTurns: eff.turns
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
const receipt = await buildReceipt(session, derived, findings, { scope });
|
|
808
|
+
return opts.redact ? redactReceipt(receipt) : receipt;
|
|
809
|
+
}
|
|
810
|
+
async function compareToTranscript(committed, transcriptPath, opts = {}) {
|
|
811
|
+
const fresh = await rederiveFromTranscript(transcriptPath, {
|
|
812
|
+
redact: true,
|
|
813
|
+
...opts,
|
|
814
|
+
// Re-derive through the adapter the receipt names (its subject prefix), so a
|
|
815
|
+
// codex/cursor receipt isn't re-run through the Claude adapter. An explicit
|
|
816
|
+
// opts.source still wins.
|
|
817
|
+
source: opts.source ?? sourceFromReceipt(committed),
|
|
818
|
+
scope: committed.predicate.scope
|
|
819
|
+
});
|
|
820
|
+
if (!fresh) {
|
|
821
|
+
return { rederived: false, matches: false };
|
|
822
|
+
}
|
|
823
|
+
const strip = (r) => {
|
|
824
|
+
if (!r.predicate.prEffort && !r.predicate.prHistory && !r.predicate.prCleared) {
|
|
825
|
+
return r;
|
|
826
|
+
}
|
|
827
|
+
const { prEffort: _x, prHistory: _y, prCleared: _z, ...rest } = r.predicate;
|
|
828
|
+
return { ...r, predicate: rest };
|
|
829
|
+
};
|
|
830
|
+
return {
|
|
831
|
+
rederived: true,
|
|
832
|
+
matches: canonicalize(strip(committed)) === canonicalize(strip(fresh))
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
export {
|
|
837
|
+
gitInvocations,
|
|
838
|
+
canonicalize,
|
|
839
|
+
CHECK_CATALOG,
|
|
840
|
+
checkForId,
|
|
841
|
+
PR_COMMENT_MARKER,
|
|
842
|
+
visibleMergeFindings,
|
|
843
|
+
eventKey,
|
|
844
|
+
renderPrComment,
|
|
845
|
+
copyToClipboard,
|
|
846
|
+
renderShareMarkdown,
|
|
847
|
+
DSSE_PAYLOAD_TYPE,
|
|
848
|
+
pae,
|
|
849
|
+
toDsseEnvelope,
|
|
850
|
+
envelopePae,
|
|
851
|
+
branchShas,
|
|
852
|
+
authoredBranch,
|
|
853
|
+
shaWindow,
|
|
854
|
+
branchesIn,
|
|
855
|
+
sliceByBranch,
|
|
856
|
+
rederiveFromTranscript,
|
|
857
|
+
compareToTranscript
|
|
858
|
+
};
|
|
859
|
+
//# sourceMappingURL=chunk-NPYC3NGR.js.map
|