altimate-receipts 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -38
- package/dist/{chunk-UHI6BGLE.js → chunk-2QTR3AF4.js} +1 -1
- package/dist/chunk-2QTR3AF4.js.map +1 -0
- package/dist/{chunk-RQLUZ6FQ.js → chunk-QGUQOQXO.js} +2 -2
- package/dist/{chunk-SUAQDKUV.js → chunk-WISVSYA7.js} +22 -2
- package/dist/chunk-WISVSYA7.js.map +1 -0
- package/dist/cli.js +574 -73
- package/dist/cli.js.map +1 -1
- package/dist/index.js +19 -2
- 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 +1 -1
- package/dist/chunk-SUAQDKUV.js.map +0 -1
- package/dist/chunk-UHI6BGLE.js.map +0 -1
- /package/dist/{chunk-RQLUZ6FQ.js.map → chunk-QGUQOQXO.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
renderShareMarkdown,
|
|
15
15
|
sliceByBranch,
|
|
16
16
|
toDsseEnvelope
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-WISVSYA7.js";
|
|
18
18
|
import {
|
|
19
19
|
PREDICATE_TYPE,
|
|
20
20
|
adapterFor,
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
upsertGuardrailsSection,
|
|
49
49
|
validateReceiptShape,
|
|
50
50
|
verifyBundle
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-2QTR3AF4.js";
|
|
52
52
|
|
|
53
53
|
// src/report/prComment.ts
|
|
54
54
|
var CLASS_ORDER = [
|
|
@@ -176,6 +176,21 @@ function changedFilesBlock(files) {
|
|
|
176
176
|
"</details>"
|
|
177
177
|
].join("\n");
|
|
178
178
|
}
|
|
179
|
+
function parseSemver(v) {
|
|
180
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(String(v ?? "").trim());
|
|
181
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
182
|
+
}
|
|
183
|
+
function semverLt(a, b) {
|
|
184
|
+
const pa = parseSemver(a);
|
|
185
|
+
const pb = parseSemver(b);
|
|
186
|
+
if (!pa || !pb) return false;
|
|
187
|
+
for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pa[i] < pb[i];
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
function upgradeHint(receiptVer, compareVer) {
|
|
191
|
+
if (!compareVer || !semverLt(receiptVer, compareVer)) return null;
|
|
192
|
+
return `<sub>\u{1F9FE} 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>`;
|
|
193
|
+
}
|
|
179
194
|
function fullBreakdown(p, ev, cats, operatorCount, opts, feedback) {
|
|
180
195
|
const inner = [];
|
|
181
196
|
const files = p.scope?.kind === "diff" ? p.scope.files ?? [] : [];
|
|
@@ -195,6 +210,8 @@ function fullBreakdown(p, ev, cats, operatorCount, opts, feedback) {
|
|
|
195
210
|
inner.push(
|
|
196
211
|
`<sub>${signed}Re-derivable (L1: \`receipts verify <receipt> --transcript <t>\`) \xB7 deterministic \xB7 0 model calls \xB7 **evidence, not judgement** \xB7 [trust model](${TRUST_DOC_URL}).</sub>`
|
|
197
212
|
);
|
|
213
|
+
const hint = upgradeHint(p.generator?.version, opts.compareVersion);
|
|
214
|
+
if (hint) inner.push(hint);
|
|
198
215
|
inner.push(feedback);
|
|
199
216
|
return [
|
|
200
217
|
"<details><summary>Full breakdown \u2014 files, checks, cost, provenance</summary>",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/report/prComment.ts"],"sourcesContent":["import type { Severity } from \"../findings/findings.js\";\nimport { formatCostAlways, formatTokens } from \"../findings/format.js\";\nimport { type GradeLetter, gradeLetter } from \"../findings/grade.js\";\nimport { destructiveOutsideRepo, findingSurface } from \"../findings/surface.js\";\nimport type { Receipt, ReceiptFinding } from \"../receipt/build.js\";\nimport { type CategorizedChecks, categorize } from \"./checks.js\";\nimport { renderLedger } from \"./ledger.js\";\n\n// command classes ordered most-notable first, for the effort breakdown.\nconst CLASS_ORDER = [\n \"mutating\",\n \"vcs-history\",\n \"transport\",\n \"test\",\n \"build\",\n \"read-only\",\n \"opaque\",\n];\n\n/** Stable marker so the Action can find and update its own comment. */\nexport const PR_COMMENT_MARKER = \"<!-- verified-by-receipts -->\";\n\n// Canonical docs (absolute so the links work from any repo's PR comment) + the one-line\n// plain-English explainer for first-time viewers. Kept identical in action/verified-by.mjs.\nconst TRUST_DOC_URL = \"https://github.com/AltimateAI/altimate-receipts/blob/main/docs/trust.md\";\nconst ABOUT_DOC_URL = \"https://github.com/AltimateAI/altimate-receipts/blob/main/docs/problems.md\";\nconst EXPLAINER = `<sub>🤖 A deterministic check that reads the coding agent's **own transcript** and reports what it changed, ran, and claimed on this PR — evidence, **not a code-quality verdict**. New here? [What the grade & checks mean ›](${ABOUT_DOC_URL})</sub>`;\n// Dogfooding feedback channel — a prefilled issue against the receipts repo so a false\n// positive (the thing that kills the near-zero-FP trust pitch) is one click to report and\n// collects centrally. The reaction ask uses GitHub's native comment reactions (queryable).\n// Kept identical in action/verified-by.mjs.\nconst FEEDBACK_ISSUE_BASE = \"https://github.com/AltimateAI/altimate-receipts/issues/new\";\n\n/**\n * Build the feedback footer with a **prefilled** false-positive issue link — title from\n * the top flagged finding, body pre-populated with the PR link (a `__PR_URL__` token the\n * Action substitutes, like `__ATTESTATION_URL__`), agent, grade, and the flagged\n * finding(s) by title+id — so the reporter only writes *why*. Identical in both renderers.\n */\nfunction feedbackLine(\n agent: string,\n grade: GradeLetter,\n flagged: readonly ReceiptFinding[],\n): string {\n const findingLines = flagged.length\n ? flagged.slice(0, 8).map((f) => `- ${f.title} (\\`${f.id}\\`)`)\n : [\"- (nothing was flagged — describe what you expected)\"];\n const body = [\n \"**Repo / PR:** __PR_URL__\",\n `**Agent:** ${agent} · **Grade:** ${grade}`,\n \"\",\n \"**Flagged finding(s) being disputed:**\",\n ...findingLines,\n \"\",\n \"**Why this is a false positive / noise:**\",\n \"_(your notes — what should it have done instead?)_\",\n ].join(\"\\n\");\n const title = flagged.length ? `False positive: ${flagged[0].title}` : \"Receipts feedback\";\n const qs = `labels=${encodeURIComponent(\"false-positive,dogfooding\")}&title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;\n const url = `${FEEDBACK_ISSUE_BASE}?${qs}`;\n return `<sub>💬 Wrong call or noise? **[Report it — prefilled ›](${url})** (only the \"why\" is left to write) · or use the 🙂 reaction button on this comment to vote 👍 / 👎. It tunes the checks.</sub>`;\n}\n\n/** Traffic-light dot per grade — a glanceable status glyph in the headline. */\nconst GRADE_DOT: Record<GradeLetter, string> = { A: \"🟢\", B: \"🟡\", C: \"🟠\", F: \"🔴\" };\n\n/** One-line verdict, scaled to the grade. */\nconst VERDICT: Record<GradeLetter, string> = {\n A: \"Safe to merge\",\n B: \"Minor things to check\",\n C: \"Needs a close review\",\n F: \"Do not merge without review\",\n};\n\n/** GitHub alert kind used to frame a flagged change — none on a clean run (no alarm). */\nconst CALLOUT: Record<GradeLetter, string | null> = {\n A: null,\n B: \"NOTE\",\n C: \"WARNING\",\n F: \"CAUTION\",\n};\n\nconst ICON: Record<Severity, string> = { critical: \"⛔\", high: \"⚠️\", medium: \"🔍\", low: \"·\" };\nconst ORDER: Record<Severity, number> = { critical: 0, high: 1, medium: 2, low: 3 };\n\nexport interface PrCommentOptions {\n /** link to the signed attestation / Rekor entry, when available */\n attestationUrl?: string;\n /** whether the Receipt was Sigstore-signed in this run */\n signed?: boolean;\n}\n\n/**\n * Render the \"Verified-by: Receipts\" PR comment (Markdown). The Receipt is assumed\n * already redacted. Two zones (progressive disclosure): an always-visible verdict +\n * what-changed + the flagged findings, then the full 14-check catalog collapsed in a\n * `<details>` (open only when something fired). Effort/cost is demoted to a muted\n * footer — it's session-level, not this diff. Includes the trust caveat (SPEC-0005 R6).\n */\nexport function renderPrComment(receipt: Receipt, opts: PrCommentOptions = {}): string {\n const p = receipt.predicate;\n const ev = p.evidence;\n\n // Merge gate: show only findings about the merged artifact. Operator/efficiency\n // findings (loops, retries, cost) live on `receipts` / `receipts trends`; a\n // destructive op on a scratch path (`rm -rf \"$TMP\"`) isn't a merge risk. The\n // verdict is recomputed from what's shown, so it matches the visible findings\n // (the committed receipt is unchanged — re-derivation/L1 is unaffected).\n const operatorCount = p.findings.filter((f) => findingSurface(f.id) === \"operator\").length;\n const mergeFindings = p.findings.filter(\n (f) =>\n findingSurface(f.id) === \"merge\" &&\n !(f.id.startsWith(\"destructive-\") && destructiveOutsideRepo(f.title)),\n );\n const g = gradeLetter(mergeFindings.filter((f) => f.confidence >= 0.5));\n const main = mergeFindings.filter((f) => f.severity !== \"low\");\n const cats = categorize(main);\n\n const out: string[] = [];\n out.push(PR_COMMENT_MARKER);\n\n // Headline — the verdict is folded in so the status reads in one glance.\n out.push(`### 🧾 Verified by Receipts · ${GRADE_DOT[g]} Grade ${g} — ${VERDICT[g]}`);\n out.push(\"\");\n out.push(EXPLAINER); // one-line plain-English \"what is this\" for first-time viewers\n out.push(\"\");\n\n // When something fired, shout: a callout + the actionable findings. Silent when clean.\n if (main.length) {\n const co = CALLOUT[g] ?? \"WARNING\";\n const what = cats.flagged.length\n ? cats.flagged.map((fl) => fl.check.label).join(\", \")\n : `${main.length} finding${main.length === 1 ? \"\" : \"s\"}`;\n out.push(`> [!${co}]`);\n out.push(\n `> **${cats.flagged.length || main.length} of ${cats.total} checks flagged:** ${what}.`,\n );\n out.push(\"\");\n const sorted = [...main].sort(\n (a, b) => ORDER[a.severity] - ORDER[b.severity] || b.score - a.score,\n );\n for (const f of sorted.slice(0, 12)) {\n out.push(findingLine(f));\n }\n if (main.length > 12) {\n out.push(`- _…and ${main.length - 12} more — see the full breakdown_`);\n }\n out.push(\"\");\n }\n\n // One-glance stat strip — always present, cost included.\n out.push(statStrip(cats, ev, p));\n out.push(\"\");\n\n // The single \"dig deeper\": files, claim ledger, every check, cost detail, session, trust.\n const feedback = feedbackLine(p.session.agent, g, main);\n out.push(fullBreakdown(p, ev, cats, operatorCount, opts, feedback));\n return `${out.join(\"\\n\")}\\n`;\n}\n\n/** Compact cost chip for the strip — ALWAYS this change's diff-scoped cost (an upper\n * bound), never the whole-session total. The session figure would dwarf a small PR and\n * mislead, so it never appears here; the session totals live, clearly labelled, in the\n * breakdown. Omitted only when there's no diff scope at all (a non-PR receipt). */\nfunction costStat(ev: Receipt[\"predicate\"][\"evidence\"]): string {\n return ev.diffCostUsd != null ? `💰 ≈ ${formatCostAlways(ev.diffCostUsd)} _(this change)_` : \"\";\n}\n\n/** The one-glance status strip: checks · files · tests · cost · agent. */\nfunction statStrip(\n cats: CategorizedChecks,\n ev: Receipt[\"predicate\"][\"evidence\"],\n p: Receipt[\"predicate\"],\n): string {\n const checks = cats.flagged.length\n ? `⚠️ **${cats.flagged.length}/${cats.total} flagged**`\n : `✅ **${cats.total}/${cats.total} checks clear**`;\n const files = p.scope?.kind === \"diff\" ? (p.scope.files ?? []) : [];\n const parts = [checks];\n if (files.length) parts.push(`📄 ${files.length} file${files.length === 1 ? \"\" : \"s\"}`);\n parts.push(testsCell(ev));\n const cost = costStat(ev);\n if (cost) parts.push(cost);\n parts.push(`_${p.session.agent}_`);\n return parts.join(\" · \");\n}\n\n/** Changed-file block for the breakdown. Lists files when few; for a big PR, groups by\n * top-level dir with counts and tucks a capped file list in a nested `<details>` — never\n * a hundreds-of-paths dump (GitHub caps a comment at ~65 KB). */\nfunction changedFilesBlock(files: readonly string[]): string {\n if (files.length <= 12) {\n return `**Changed** ${files.map((f) => `\\`${f}\\``).join(\", \")}`;\n }\n const byDir = new Map<string, number>();\n for (const f of files) {\n const top = f.includes(\"/\") ? `${f.split(\"/\")[0]}/` : \"(root)\";\n byDir.set(top, (byDir.get(top) ?? 0) + 1);\n }\n const dirs = [...byDir.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 8)\n .map(([d, n]) => `\\`${d}\\` ${n}`)\n .join(\" · \");\n const moreDirs = byDir.size > 8 ? ` · _+${byDir.size - 8} more dirs_` : \"\";\n const sample = files\n .slice(0, 60)\n .map((f) => `\\`${f}\\``)\n .join(\", \");\n const moreFiles =\n files.length > 60 ? ` _…+${files.length - 60} more (full set in the receipt JSON)_` : \"\";\n return [\n `**Changed ${files.length} files** by area: ${dirs}${moreDirs}`,\n \"\",\n \"<details><summary>file list</summary>\",\n \"\",\n `${sample}${moreFiles}`,\n \"\",\n \"</details>\",\n ].join(\"\\n\");\n}\n\n/** The single deep-dive expander — everything jargon-y or large lives here. */\nfunction fullBreakdown(\n p: Receipt[\"predicate\"],\n ev: Receipt[\"predicate\"][\"evidence\"],\n cats: CategorizedChecks,\n operatorCount: number,\n opts: PrCommentOptions,\n feedback: string,\n): string {\n const inner: string[] = [];\n const files = p.scope?.kind === \"diff\" ? (p.scope.files ?? []) : [];\n if (files.length) inner.push(changedFilesBlock(files), \"\");\n const ledger = renderLedger(ev.claims ?? []);\n if (ledger) inner.push(ledger, \"\");\n inner.push(\n `**Risk checks** — ${cats.flagged.length} flagged · ${cats.cleared.length} clear`,\n \"\",\n catalogTable(cats),\n \"\",\n );\n const cl = diffCostLine(ev);\n if (cl) inner.push(cl);\n inner.push(`<sub>${effortLine(ev, operatorCount)}</sub>`);\n const signed = opts.signed\n ? `🔏 Signed (Sigstore → Rekor)${opts.attestationUrl ? ` · [attestation](${opts.attestationUrl})` : \"\"} · `\n : \"\";\n inner.push(\n `<sub>${signed}Re-derivable (L1: \\`receipts verify <receipt> --transcript <t>\\`) · deterministic · 0 model calls · **evidence, not judgement** · [trust model](${TRUST_DOC_URL}).</sub>`,\n );\n inner.push(feedback);\n return [\n \"<details><summary>Full breakdown — files, checks, cost, provenance</summary>\",\n \"\",\n ...inner,\n \"\",\n \"</details>\",\n ].join(\"\\n\");\n}\n\n/**\n * The diff-cost line — ALWAYS this change's diff-scoped cost, never the session total.\n * It is an **upper bound**: it sums whole generation turns that edited a diff file, each\n * carrying its full (cache-discounted) context cost, so in a long/multi-change session it\n * over-states — hence the explicit \"upper bound\" label rather than a false-precise figure.\n * Cache reads are already priced at the discounted rate (cost.ts). Returns null only when\n * no diff cost was recorded (a non-PR receipt). The whole-session total never appears\n * here; it lives, clearly labelled, in the session-totals line.\n */\nfunction diffCostLine(ev: Receipt[\"predicate\"][\"evidence\"]): string | null {\n if (ev.diffCostUsd == null) return null;\n const turns = ev.diffTurns ?? 0;\n const t = `${turns} turn${turns === 1 ? \"\" : \"s\"}`;\n return `**Cost** ≈ ${formatCostAlways(ev.diffCostUsd)} · ${formatTokens(ev.diffTokens ?? 0)} tokens · ${t} _(this change — upper bound; the turns that edited these files)_`;\n}\n\n/** The tests cell — structured pass/fail counts (M28) when parsed, else the boolean. */\nfunction testsCell(ev: Receipt[\"predicate\"][\"evidence\"]): string {\n const tm = ev.testMetrics;\n if (tm) {\n const seg = [\n tm.passed != null ? `${tm.passed} passed` : null,\n tm.failed ? `**${tm.failed} failed**` : null,\n tm.skipped ? `${tm.skipped} skipped` : null,\n ]\n .filter(Boolean)\n .join(\", \");\n const ok = !tm.failed;\n return `${ok ? \"✅\" : \"⚠️\"} ${seg || tm.exitStatus}`;\n }\n return ev.testsRan ? \"✅ ran\" : \"— no test run detected\";\n}\n\n/** The full check catalog as a Markdown table (flagged first), inlined in the breakdown. */\nfunction catalogTable(cats: CategorizedChecks): string {\n const sev = new Map(cats.flagged.map((fl) => [fl.check.key, fl]));\n const rows = [...cats.flagged.map((fl) => fl.check), ...cats.cleared].map((c) => {\n const fl = sev.get(c.key);\n const result = fl ? `${ICON[fl.severity]} ${fl.count} flagged` : \"✅ clear\";\n return `| ${c.icon} ${c.label} | ${result} |`;\n });\n // flagged first, then cleared (both keep catalog order within each group)\n rows.sort((a, b) => (a.includes(\"✅ clear\") ? 1 : 0) - (b.includes(\"✅ clear\") ? 1 : 0));\n return [\"| Check | Result |\", \"| :-- | :-- |\", ...rows].join(\"\\n\");\n}\n\n/** Session-level effort, explicitly demoted — these totals are the run, not this diff. */\nfunction effortLine(ev: Receipt[\"predicate\"][\"evidence\"], operatorCount: number): string {\n const cbc = ev.commandsByClass;\n const brk = cbc\n ? ` _(${CLASS_ORDER.filter((c) => cbc[c])\n .map((c) => `${cbc[c]} ${c}`)\n .join(\", \")})_`\n : \"\";\n const op =\n operatorCount > 0\n ? ` · ${operatorCount} efficiency/behaviour finding${operatorCount === 1 ? \"\" : \"s\"}`\n : \"\";\n return `Session totals (the whole run, not this diff): ${ev.edits} edits · ${ev.commands} commands${brk} · ${formatCostAlways(ev.costUsd)} · ${formatTokens(ev.tokens.total)} tokens${op}.`;\n}\n\n/** One finding line: severity glyph + title + impact tag + path, with a \"what to check\" sub-bullet. */\nfunction findingLine(f: ReceiptFinding): string {\n const tag = f.impactLabel ? ` — ${f.impactLabel}` : \"\";\n const loc = f.filePath ? ` (\\`${f.filePath}${f.line ? `:${f.line}` : \"\"}\\`)` : \"\";\n const head = `- ${ICON[f.severity]} **${f.title}**${tag}${loc}`;\n return f.detail ? `${head}\\n ↳ ${f.detail}` : head;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/report/prComment.ts"],"sourcesContent":["import type { Severity } from \"../findings/findings.js\";\nimport { formatCostAlways, formatTokens } from \"../findings/format.js\";\nimport { type GradeLetter, gradeLetter } from \"../findings/grade.js\";\nimport { destructiveOutsideRepo, findingSurface } from \"../findings/surface.js\";\nimport type { Receipt, ReceiptFinding } from \"../receipt/build.js\";\nimport { type CategorizedChecks, categorize } from \"./checks.js\";\nimport { renderLedger } from \"./ledger.js\";\n\n// command classes ordered most-notable first, for the effort breakdown.\nconst CLASS_ORDER = [\n \"mutating\",\n \"vcs-history\",\n \"transport\",\n \"test\",\n \"build\",\n \"read-only\",\n \"opaque\",\n];\n\n/** Stable marker so the Action can find and update its own comment. */\nexport const PR_COMMENT_MARKER = \"<!-- verified-by-receipts -->\";\n\n// Canonical docs (absolute so the links work from any repo's PR comment) + the one-line\n// plain-English explainer for first-time viewers. Kept identical in action/verified-by.mjs.\nconst TRUST_DOC_URL = \"https://github.com/AltimateAI/altimate-receipts/blob/main/docs/trust.md\";\nconst ABOUT_DOC_URL = \"https://github.com/AltimateAI/altimate-receipts/blob/main/docs/problems.md\";\nconst EXPLAINER = `<sub>🤖 A deterministic check that reads the coding agent's **own transcript** and reports what it changed, ran, and claimed on this PR — evidence, **not a code-quality verdict**. New here? [What the grade & checks mean ›](${ABOUT_DOC_URL})</sub>`;\n// Dogfooding feedback channel — a prefilled issue against the receipts repo so a false\n// positive (the thing that kills the near-zero-FP trust pitch) is one click to report and\n// collects centrally. The reaction ask uses GitHub's native comment reactions (queryable).\n// Kept identical in action/verified-by.mjs.\nconst FEEDBACK_ISSUE_BASE = \"https://github.com/AltimateAI/altimate-receipts/issues/new\";\n\n/**\n * Build the feedback footer with a **prefilled** false-positive issue link — title from\n * the top flagged finding, body pre-populated with the PR link (a `__PR_URL__` token the\n * Action substitutes, like `__ATTESTATION_URL__`), agent, grade, and the flagged\n * finding(s) by title+id — so the reporter only writes *why*. Identical in both renderers.\n */\nfunction feedbackLine(\n agent: string,\n grade: GradeLetter,\n flagged: readonly ReceiptFinding[],\n): string {\n const findingLines = flagged.length\n ? flagged.slice(0, 8).map((f) => `- ${f.title} (\\`${f.id}\\`)`)\n : [\"- (nothing was flagged — describe what you expected)\"];\n const body = [\n \"**Repo / PR:** __PR_URL__\",\n `**Agent:** ${agent} · **Grade:** ${grade}`,\n \"\",\n \"**Flagged finding(s) being disputed:**\",\n ...findingLines,\n \"\",\n \"**Why this is a false positive / noise:**\",\n \"_(your notes — what should it have done instead?)_\",\n ].join(\"\\n\");\n const title = flagged.length ? `False positive: ${flagged[0].title}` : \"Receipts feedback\";\n const qs = `labels=${encodeURIComponent(\"false-positive,dogfooding\")}&title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;\n const url = `${FEEDBACK_ISSUE_BASE}?${qs}`;\n return `<sub>💬 Wrong call or noise? **[Report it — prefilled ›](${url})** (only the \"why\" is left to write) · or use the 🙂 reaction button on this comment to vote 👍 / 👎. It tunes the checks.</sub>`;\n}\n\n/** Traffic-light dot per grade — a glanceable status glyph in the headline. */\nconst GRADE_DOT: Record<GradeLetter, string> = { A: \"🟢\", B: \"🟡\", C: \"🟠\", F: \"🔴\" };\n\n/** One-line verdict, scaled to the grade. */\nconst VERDICT: Record<GradeLetter, string> = {\n A: \"Safe to merge\",\n B: \"Minor things to check\",\n C: \"Needs a close review\",\n F: \"Do not merge without review\",\n};\n\n/** GitHub alert kind used to frame a flagged change — none on a clean run (no alarm). */\nconst CALLOUT: Record<GradeLetter, string | null> = {\n A: null,\n B: \"NOTE\",\n C: \"WARNING\",\n F: \"CAUTION\",\n};\n\nconst ICON: Record<Severity, string> = { critical: \"⛔\", high: \"⚠️\", medium: \"🔍\", low: \"·\" };\nconst ORDER: Record<Severity, number> = { critical: 0, high: 1, medium: 2, low: 3 };\n\nexport interface PrCommentOptions {\n /** link to the signed attestation / Rekor entry, when available */\n attestationUrl?: string;\n /** whether the Receipt was Sigstore-signed in this run */\n signed?: boolean;\n /** M68 — the verifier's version to compare the receipt's `generator.version` against;\n * only the CI Action sets this. When older, a muted upgrade line is appended. The local\n * CLI passes nothing (no compare target ⇒ no line — it can't know a remote version). */\n compareVersion?: string;\n}\n\n/**\n * Render the \"Verified-by: Receipts\" PR comment (Markdown). The Receipt is assumed\n * already redacted. Two zones (progressive disclosure): an always-visible verdict +\n * what-changed + the flagged findings, then the full 14-check catalog collapsed in a\n * `<details>` (open only when something fired). Effort/cost is demoted to a muted\n * footer — it's session-level, not this diff. Includes the trust caveat (SPEC-0005 R6).\n */\nexport function renderPrComment(receipt: Receipt, opts: PrCommentOptions = {}): string {\n const p = receipt.predicate;\n const ev = p.evidence;\n\n // Merge gate: show only findings about the merged artifact. Operator/efficiency\n // findings (loops, retries, cost) live on `receipts` / `receipts trends`; a\n // destructive op on a scratch path (`rm -rf \"$TMP\"`) isn't a merge risk. The\n // verdict is recomputed from what's shown, so it matches the visible findings\n // (the committed receipt is unchanged — re-derivation/L1 is unaffected).\n const operatorCount = p.findings.filter((f) => findingSurface(f.id) === \"operator\").length;\n const mergeFindings = p.findings.filter(\n (f) =>\n findingSurface(f.id) === \"merge\" &&\n !(f.id.startsWith(\"destructive-\") && destructiveOutsideRepo(f.title)),\n );\n const g = gradeLetter(mergeFindings.filter((f) => f.confidence >= 0.5));\n const main = mergeFindings.filter((f) => f.severity !== \"low\");\n const cats = categorize(main);\n\n const out: string[] = [];\n out.push(PR_COMMENT_MARKER);\n\n // Headline — the verdict is folded in so the status reads in one glance.\n out.push(`### 🧾 Verified by Receipts · ${GRADE_DOT[g]} Grade ${g} — ${VERDICT[g]}`);\n out.push(\"\");\n out.push(EXPLAINER); // one-line plain-English \"what is this\" for first-time viewers\n out.push(\"\");\n\n // When something fired, shout: a callout + the actionable findings. Silent when clean.\n if (main.length) {\n const co = CALLOUT[g] ?? \"WARNING\";\n const what = cats.flagged.length\n ? cats.flagged.map((fl) => fl.check.label).join(\", \")\n : `${main.length} finding${main.length === 1 ? \"\" : \"s\"}`;\n out.push(`> [!${co}]`);\n out.push(\n `> **${cats.flagged.length || main.length} of ${cats.total} checks flagged:** ${what}.`,\n );\n out.push(\"\");\n const sorted = [...main].sort(\n (a, b) => ORDER[a.severity] - ORDER[b.severity] || b.score - a.score,\n );\n for (const f of sorted.slice(0, 12)) {\n out.push(findingLine(f));\n }\n if (main.length > 12) {\n out.push(`- _…and ${main.length - 12} more — see the full breakdown_`);\n }\n out.push(\"\");\n }\n\n // One-glance stat strip — always present, cost included.\n out.push(statStrip(cats, ev, p));\n out.push(\"\");\n\n // The single \"dig deeper\": files, claim ledger, every check, cost detail, session, trust.\n const feedback = feedbackLine(p.session.agent, g, main);\n out.push(fullBreakdown(p, ev, cats, operatorCount, opts, feedback));\n return `${out.join(\"\\n\")}\\n`;\n}\n\n/** Compact cost chip for the strip — ALWAYS this change's diff-scoped cost (an upper\n * bound), never the whole-session total. The session figure would dwarf a small PR and\n * mislead, so it never appears here; the session totals live, clearly labelled, in the\n * breakdown. Omitted only when there's no diff scope at all (a non-PR receipt). */\nfunction costStat(ev: Receipt[\"predicate\"][\"evidence\"]): string {\n return ev.diffCostUsd != null ? `💰 ≈ ${formatCostAlways(ev.diffCostUsd)} _(this change)_` : \"\";\n}\n\n/** The one-glance status strip: checks · files · tests · cost · agent. */\nfunction statStrip(\n cats: CategorizedChecks,\n ev: Receipt[\"predicate\"][\"evidence\"],\n p: Receipt[\"predicate\"],\n): string {\n const checks = cats.flagged.length\n ? `⚠️ **${cats.flagged.length}/${cats.total} flagged**`\n : `✅ **${cats.total}/${cats.total} checks clear**`;\n const files = p.scope?.kind === \"diff\" ? (p.scope.files ?? []) : [];\n const parts = [checks];\n if (files.length) parts.push(`📄 ${files.length} file${files.length === 1 ? \"\" : \"s\"}`);\n parts.push(testsCell(ev));\n const cost = costStat(ev);\n if (cost) parts.push(cost);\n parts.push(`_${p.session.agent}_`);\n return parts.join(\" · \");\n}\n\n/** Changed-file block for the breakdown. Lists files when few; for a big PR, groups by\n * top-level dir with counts and tucks a capped file list in a nested `<details>` — never\n * a hundreds-of-paths dump (GitHub caps a comment at ~65 KB). */\nfunction changedFilesBlock(files: readonly string[]): string {\n if (files.length <= 12) {\n return `**Changed** ${files.map((f) => `\\`${f}\\``).join(\", \")}`;\n }\n const byDir = new Map<string, number>();\n for (const f of files) {\n const top = f.includes(\"/\") ? `${f.split(\"/\")[0]}/` : \"(root)\";\n byDir.set(top, (byDir.get(top) ?? 0) + 1);\n }\n const dirs = [...byDir.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 8)\n .map(([d, n]) => `\\`${d}\\` ${n}`)\n .join(\" · \");\n const moreDirs = byDir.size > 8 ? ` · _+${byDir.size - 8} more dirs_` : \"\";\n const sample = files\n .slice(0, 60)\n .map((f) => `\\`${f}\\``)\n .join(\", \");\n const moreFiles =\n files.length > 60 ? ` _…+${files.length - 60} more (full set in the receipt JSON)_` : \"\";\n return [\n `**Changed ${files.length} files** by area: ${dirs}${moreDirs}`,\n \"\",\n \"<details><summary>file list</summary>\",\n \"\",\n `${sample}${moreFiles}`,\n \"\",\n \"</details>\",\n ].join(\"\\n\");\n}\n\n// M68 — upgrade-skew notice (MIRRORS action/verified-by.mjs). Plain x.y.z compare (no\n// pre-release handling — receipts ships plain semver); false on any parse failure so a\n// malformed version never nags.\nfunction parseSemver(v?: string): [number, number, number] | null {\n const m = /^(\\d+)\\.(\\d+)\\.(\\d+)/.exec(String(v ?? \"\").trim());\n return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;\n}\nfunction semverLt(a?: string, b?: string): boolean {\n const pa = parseSemver(a);\n const pb = parseSemver(b);\n if (!pa || !pb) return false;\n for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pa[i] < pb[i];\n return false;\n}\n/** One muted footer line when the receipt was made by a CLI older than `compareVer` (the\n * verifier's version). A mechanical fact (X < Y), never a finding and never a grade change;\n * null when current/ahead/equal/unparseable or when there is no compare target. */\nexport function upgradeHint(receiptVer?: string, compareVer?: string): string | null {\n if (!compareVer || !semverLt(receiptVer, compareVer)) return null;\n return `<sub>🧾 Receipt generated by \\`receipts@${receiptVer}\\` · this check runs \\`@${compareVer}\\` — \\`npm i -g altimate-receipts@latest\\` (or \\`npx altimate-receipts@latest\\`) for the newer checks.</sub>`;\n}\n\n/** The single deep-dive expander — everything jargon-y or large lives here. */\nfunction fullBreakdown(\n p: Receipt[\"predicate\"],\n ev: Receipt[\"predicate\"][\"evidence\"],\n cats: CategorizedChecks,\n operatorCount: number,\n opts: PrCommentOptions,\n feedback: string,\n): string {\n const inner: string[] = [];\n const files = p.scope?.kind === \"diff\" ? (p.scope.files ?? []) : [];\n if (files.length) inner.push(changedFilesBlock(files), \"\");\n const ledger = renderLedger(ev.claims ?? []);\n if (ledger) inner.push(ledger, \"\");\n inner.push(\n `**Risk checks** — ${cats.flagged.length} flagged · ${cats.cleared.length} clear`,\n \"\",\n catalogTable(cats),\n \"\",\n );\n const cl = diffCostLine(ev);\n if (cl) inner.push(cl);\n inner.push(`<sub>${effortLine(ev, operatorCount)}</sub>`);\n const signed = opts.signed\n ? `🔏 Signed (Sigstore → Rekor)${opts.attestationUrl ? ` · [attestation](${opts.attestationUrl})` : \"\"} · `\n : \"\";\n inner.push(\n `<sub>${signed}Re-derivable (L1: \\`receipts verify <receipt> --transcript <t>\\`) · deterministic · 0 model calls · **evidence, not judgement** · [trust model](${TRUST_DOC_URL}).</sub>`,\n );\n const hint = upgradeHint(p.generator?.version, opts.compareVersion);\n if (hint) inner.push(hint);\n inner.push(feedback);\n return [\n \"<details><summary>Full breakdown — files, checks, cost, provenance</summary>\",\n \"\",\n ...inner,\n \"\",\n \"</details>\",\n ].join(\"\\n\");\n}\n\n/**\n * The diff-cost line — ALWAYS this change's diff-scoped cost, never the session total.\n * It is an **upper bound**: it sums whole generation turns that edited a diff file, each\n * carrying its full (cache-discounted) context cost, so in a long/multi-change session it\n * over-states — hence the explicit \"upper bound\" label rather than a false-precise figure.\n * Cache reads are already priced at the discounted rate (cost.ts). Returns null only when\n * no diff cost was recorded (a non-PR receipt). The whole-session total never appears\n * here; it lives, clearly labelled, in the session-totals line.\n */\nfunction diffCostLine(ev: Receipt[\"predicate\"][\"evidence\"]): string | null {\n if (ev.diffCostUsd == null) return null;\n const turns = ev.diffTurns ?? 0;\n const t = `${turns} turn${turns === 1 ? \"\" : \"s\"}`;\n return `**Cost** ≈ ${formatCostAlways(ev.diffCostUsd)} · ${formatTokens(ev.diffTokens ?? 0)} tokens · ${t} _(this change — upper bound; the turns that edited these files)_`;\n}\n\n/** The tests cell — structured pass/fail counts (M28) when parsed, else the boolean. */\nfunction testsCell(ev: Receipt[\"predicate\"][\"evidence\"]): string {\n const tm = ev.testMetrics;\n if (tm) {\n const seg = [\n tm.passed != null ? `${tm.passed} passed` : null,\n tm.failed ? `**${tm.failed} failed**` : null,\n tm.skipped ? `${tm.skipped} skipped` : null,\n ]\n .filter(Boolean)\n .join(\", \");\n const ok = !tm.failed;\n return `${ok ? \"✅\" : \"⚠️\"} ${seg || tm.exitStatus}`;\n }\n return ev.testsRan ? \"✅ ran\" : \"— no test run detected\";\n}\n\n/** The full check catalog as a Markdown table (flagged first), inlined in the breakdown. */\nfunction catalogTable(cats: CategorizedChecks): string {\n const sev = new Map(cats.flagged.map((fl) => [fl.check.key, fl]));\n const rows = [...cats.flagged.map((fl) => fl.check), ...cats.cleared].map((c) => {\n const fl = sev.get(c.key);\n const result = fl ? `${ICON[fl.severity]} ${fl.count} flagged` : \"✅ clear\";\n return `| ${c.icon} ${c.label} | ${result} |`;\n });\n // flagged first, then cleared (both keep catalog order within each group)\n rows.sort((a, b) => (a.includes(\"✅ clear\") ? 1 : 0) - (b.includes(\"✅ clear\") ? 1 : 0));\n return [\"| Check | Result |\", \"| :-- | :-- |\", ...rows].join(\"\\n\");\n}\n\n/** Session-level effort, explicitly demoted — these totals are the run, not this diff. */\nfunction effortLine(ev: Receipt[\"predicate\"][\"evidence\"], operatorCount: number): string {\n const cbc = ev.commandsByClass;\n const brk = cbc\n ? ` _(${CLASS_ORDER.filter((c) => cbc[c])\n .map((c) => `${cbc[c]} ${c}`)\n .join(\", \")})_`\n : \"\";\n const op =\n operatorCount > 0\n ? ` · ${operatorCount} efficiency/behaviour finding${operatorCount === 1 ? \"\" : \"s\"}`\n : \"\";\n return `Session totals (the whole run, not this diff): ${ev.edits} edits · ${ev.commands} commands${brk} · ${formatCostAlways(ev.costUsd)} · ${formatTokens(ev.tokens.total)} tokens${op}.`;\n}\n\n/** One finding line: severity glyph + title + impact tag + path, with a \"what to check\" sub-bullet. */\nfunction findingLine(f: ReceiptFinding): string {\n const tag = f.impactLabel ? ` — ${f.impactLabel}` : \"\";\n const loc = f.filePath ? ` (\\`${f.filePath}${f.line ? `:${f.line}` : \"\"}\\`)` : \"\";\n const head = `- ${ICON[f.severity]} **${f.title}**${tag}${loc}`;\n return f.detail ? `${head}\\n ↳ ${f.detail}` : head;\n}\n"],"mappings":"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","names":[]}
|
package/dist/mcp/server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
computeTrends,
|
|
4
4
|
deriveTargets
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-QGUQOQXO.js";
|
|
6
6
|
import {
|
|
7
7
|
agentIds,
|
|
8
8
|
buildReceipt,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
renderCard,
|
|
17
17
|
selectSummary,
|
|
18
18
|
verifyBundle
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-2QTR3AF4.js";
|
|
20
20
|
|
|
21
21
|
// src/mcp/server.ts
|
|
22
22
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
package/package.json
CHANGED
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
},
|
|
228
228
|
"branch": {
|
|
229
229
|
"type": "string",
|
|
230
|
-
"description": "branch: the branch the session was sliced to."
|
|
230
|
+
"description": "branch: the branch the session was sliced to. With kind \"diff\": the effort metrics (diffCostUsd/Tokens/Turns) were narrowed to this branch's turns (SPEC-0068); absent ⇒ whole-session upper bound."
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/receipt/canonical.ts","../src/share/clipboard.ts","../src/share/markdown.ts","../src/sign/envelope.ts","../src/trace/diffScope.ts","../src/findings/surface.ts","../src/trace/slice.ts","../src/sign/rederive.ts","../src/report/checks.ts"],"sourcesContent":["/**\n * Canonical JSON (RFC 8785-style): object keys sorted lexicographically, minimal\n * whitespace, arrays in order. Used now for deterministic hashing/equality and\n * reused verbatim as M4's DSSE signing payload, so a Receipt and its signature\n * always agree on the exact bytes.\n *\n * Scope note: this is a pragmatic subset — all numbers in a Receipt are integers\n * or values we round before emitting, so JSON.stringify's number formatting is\n * already stable. We do not implement RFC 8785's full number canonicalization.\n */\nexport function canonicalize(value: unknown): string {\n return serialize(value);\n}\n\nfunction serialize(value: unknown): string {\n if (value === null || typeof value === \"number\" || typeof value === \"boolean\") {\n return JSON.stringify(value);\n }\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n if (Array.isArray(value)) {\n return `[${value.map(serialize).join(\",\")}]`;\n }\n if (typeof value === \"object\") {\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj)\n .filter((k) => obj[k] !== undefined)\n .sort();\n const body = keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(\",\");\n return `{${body}}`;\n }\n // undefined / function / symbol — not valid Receipt content\n return \"null\";\n}\n","import { spawnSync } from \"node:child_process\";\n\ninterface ClipboardTool {\n cmd: string;\n args: string[];\n}\n\n/** OS clipboard tools, by platform, in preference order. */\nfunction candidates(): ClipboardTool[] {\n if (process.platform === \"darwin\") {\n return [{ cmd: \"pbcopy\", args: [] }];\n }\n if (process.platform === \"win32\") {\n return [{ cmd: \"clip\", args: [] }];\n }\n // linux / other: Wayland first, then X11\n return [\n { cmd: \"wl-copy\", args: [] },\n { cmd: \"xclip\", args: [\"-selection\", \"clipboard\"] },\n { cmd: \"xsel\", args: [\"--clipboard\", \"--input\"] },\n ];\n}\n\n/**\n * Best-effort copy to the OS clipboard. Returns true on success, false if no tool\n * is available or the copy failed — the caller prints a note and still exits 0.\n * No npm dependency; shells out to the platform tool.\n */\nexport function copyToClipboard(text: string): boolean {\n for (const { cmd, args } of candidates()) {\n try {\n const res = spawnSync(cmd, args, { input: text, stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n if (!res.error && res.status === 0) {\n return true;\n }\n } catch {\n // try the next tool\n }\n }\n return false;\n}\n","import type { FindingSet, Severity } from \"../findings/findings.js\";\nimport { type GradeLetter, gradeLetter } from \"../findings/grade.js\";\nimport type { DerivedSummary } from \"../findings/spans.js\";\nimport { deriveEvidence } from \"../receipt/build.js\";\nimport type { Session, SessionSummary } from \"../trace/types.js\";\nimport { redact } from \"./redact.js\";\n\nconst VERDICT: Record<GradeLetter, string> = {\n F: \"DO NOT MERGE WITHOUT REVIEW\",\n C: \"NEEDS A CLOSE REVIEW\",\n B: \"MINOR THINGS TO CHECK\",\n A: \"LOOKS CLEAN\",\n};\n\nconst ICON: Record<Severity, string> = {\n critical: \"⛔\",\n high: \"⚠️\",\n medium: \"🔍\",\n low: \"·\",\n};\n\nconst base = (p: string): string => p.split(\"/\").pop() ?? p;\n\n/**\n * A redacted, paste-ready Markdown summary of a session — the `--share` artifact.\n * Pure function of the derived data; all user-text is passed through `redact()`.\n */\nexport function renderShareMarkdown(args: {\n summary: SessionSummary;\n session: Session;\n derived: DerivedSummary;\n findings: FindingSet;\n}): string {\n const { summary, session, derived, findings } = args;\n const { main, minor } = findings;\n const g = gradeLetter(main);\n const ev = deriveEvidence(session, derived);\n\n const counts =\n ([\"critical\", \"high\", \"medium\"] as const)\n .map((s) => {\n const n = main.filter((f) => f.severity === s).length;\n return n ? `${n} ${s}` : null;\n })\n .filter(Boolean)\n .join(\" · \") || \"no findings\";\n\n const title = redact(summary.title || \"untitled session\");\n const out: string[] = [];\n out.push(`## 🧾 Receipt — ${title} · Grade ${g}`);\n out.push(\"\");\n out.push(`**${VERDICT[g]}** · ${counts} · _${summary.source}_`);\n out.push(\"\");\n\n if (main.length) {\n out.push(\"### Findings\");\n const order: Record<Severity, number> = { critical: 0, high: 1, medium: 2, low: 3 };\n const sorted = [...main].sort(\n (a, b) => order[a.severity] - order[b.severity] || b.score - a.score,\n );\n for (const f of sorted.slice(0, 12)) {\n const tag = f.impactLabel ? ` — ${redact(f.impactLabel)}` : \"\";\n const loc = f.filePath ? ` (\\`${redact(base(f.filePath))}\\`)` : \"\";\n out.push(`- ${ICON[f.severity]} **${redact(f.title)}**${tag}${loc}`);\n }\n if (minor.length) {\n out.push(`- _+${minor.length} minor (collapsed)_`);\n }\n out.push(\"\");\n }\n\n out.push(\"### Evidence\");\n out.push(\n `${ev.filesChanged} files · ${ev.edits} edits · ${ev.commands} commands · ` +\n `${ev.testsRan ? \"tests ran ✓\" : \"no test run detected\"} · ` +\n `${ev.destructiveOps} destructive ops`,\n );\n out.push(\"\");\n out.push(\"_Verified by Receipts · deterministic · 0 model calls · evidence, not judgement_\");\n return `${out.join(\"\\n\")}\\n`;\n}\n","import type { Receipt } from \"../receipt/build.js\";\nimport { canonicalize } from \"../receipt/canonical.js\";\n\n/** in-toto payloads ride in DSSE with this type. */\nexport const DSSE_PAYLOAD_TYPE = \"application/vnd.in-toto+json\";\n\nexport interface DsseSignature {\n sig: string;\n keyid?: string;\n}\n\nexport interface DsseEnvelope {\n payloadType: string;\n /** base64 of the canonical Receipt JSON */\n payload: string;\n signatures: DsseSignature[];\n}\n\n/**\n * DSSE Pre-Authentication Encoding (PAE) — the exact bytes a signer signs:\n * \"DSSEv1\" SP len(payloadType) SP payloadType SP len(payload) SP payload\n * Lengths count UTF-8 bytes; `payload` here is the raw (un-base64) payload.\n */\nexport function pae(payloadType: string, payload: string): Buffer {\n const typeBytes = Buffer.from(payloadType, \"utf8\");\n const bodyBytes = Buffer.from(payload, \"utf8\");\n const header = `DSSEv1 ${typeBytes.length} ${payloadType} ${bodyBytes.length} `;\n return Buffer.concat([Buffer.from(header, \"utf8\"), bodyBytes]);\n}\n\n/**\n * Wrap a Receipt as an UNSIGNED DSSE envelope. The payload is the canonical\n * Receipt JSON (so signature and content always agree on the exact bytes). The\n * signature array is filled by the signer (the GitHub Action), not here.\n */\nexport function toDsseEnvelope(receipt: Receipt): DsseEnvelope {\n const canonical = canonicalize(receipt);\n return {\n payloadType: DSSE_PAYLOAD_TYPE,\n payload: Buffer.from(canonical, \"utf8\").toString(\"base64\"),\n signatures: [],\n };\n}\n\n/** The PAE bytes for an envelope (what a verifier checks the signature against). */\nexport function envelopePae(env: DsseEnvelope): Buffer {\n const raw = Buffer.from(env.payload, \"base64\").toString(\"utf8\");\n return pae(env.payloadType, raw);\n}\n","import { spawnSync } from \"node:child_process\";\nimport { estimateCost } from \"../findings/cost.js\";\nimport type { Finding, FindingSet } from \"../findings/findings.js\";\nimport {\n destroysGitData,\n isNoVerify,\n parseGitInvocations,\n rewritesHistory,\n touchedPaths,\n} from \"../findings/gitParse.js\";\nimport { type DerivedSummary, destructiveMatch } from \"../findings/spans.js\";\nimport { destructiveOutsideRepo, regenerableReceiptRm } from \"../findings/surface.js\";\nimport { commandOf, filePathOf, isEditTool } from \"../findings/toolRoles.js\";\n\n/**\n * Diff-scoping for `receipts pr` (SPEC-0013). The PR's changed-file set is read from\n * git at produce time and recorded in `predicate.scope.files`; every scoping decision\n * below is a pure function of that file list (+ the repo-root-free destructive\n * heuristic), so a verifier reproduces the exact scoped receipt from the recorded\n * `files` alone — no git at verify time. That's what keeps L1 re-derivation intact.\n */\n\nfunction git(args: string[], cwd?: string): string | null {\n const r = spawnSync(\"git\", args, { encoding: \"utf8\", cwd });\n return r.status === 0 ? r.stdout : null;\n}\n\nexport interface DiffResult {\n /** the ref the diff was taken against */\n base: string;\n /** changed files, sorted, repo-relative */\n files: string[];\n repoRoot: string;\n}\n\n/** Receipts' own bookkeeping dir. A receipt must NOT scope over `.receipts/` — else\n * committing the receipt changes the diff, which re-scopes the next receipt: an endless\n * regeneration loop in the pre-push hook. Excluded from the diff scope here AND in the\n * Action's scope-honesty check (action/verified-by.mjs) so the two stay consistent. */\nexport const RECEIPTS_DIR_RE = /(?:^|\\/)\\.receipts\\//;\n\n/**\n * The PR's changed files via `git diff --name-only <base>...HEAD`, sorted, with the\n * `.receipts/` bookkeeping dir excluded (see RECEIPTS_DIR_RE). `base` defaults to the\n * default branch (origin/HEAD → main). Returns null when no repo, base, or non-empty\n * diff is resolvable (caller falls back honestly).\n */\nexport function changedFiles(baseOverride?: string): DiffResult | null {\n const root = git([\"rev-parse\", \"--show-toplevel\"])?.trim();\n if (!root) {\n return null;\n }\n let base = baseOverride;\n if (!base) {\n const sym = git([\"symbolic-ref\", \"refs/remotes/origin/HEAD\"], root)?.trim();\n base = sym ? sym.replace(\"refs/remotes/\", \"\") : \"main\";\n }\n let out = git([\"diff\", \"--name-only\", `${base}...HEAD`], root);\n if (out === null && !baseOverride) {\n // base may be unfetched (e.g. origin/main absent locally) — try local main.\n out = git([\"diff\", \"--name-only\", \"main...HEAD\"], root);\n if (out !== null) {\n base = \"main\";\n }\n }\n if (out === null) {\n return null;\n }\n const files = out\n .split(\"\\n\")\n .map((s) => s.trim())\n .filter(Boolean)\n .filter((f) => !RECEIPTS_DIR_RE.test(f)) // a receipt never scopes over .receipts/\n .sort();\n if (files.length === 0) {\n return null; // nothing to scope to (or a receipts-only diff) → caller falls back\n }\n return { base, files, repoRoot: root };\n}\n\n/** Does a finding's file path belong to the changed-file set? Matches exact, path\n * suffix, or basename (a PR's diff is small; basename collisions are unlikely and\n * fail toward surfacing). */\nexport function inDiff(filePath: string, files: readonly string[]): boolean {\n const base = filePath.split(\"/\").pop();\n for (const d of files) {\n if (d === filePath || d.endsWith(`/${filePath}`) || filePath.endsWith(`/${d}`)) {\n return true;\n }\n if (base && d.split(\"/\").pop() === base) {\n return true;\n }\n }\n return false;\n}\n\n// Integrity findings that are about the *act*, not a path — kept regardless of the\n// diff (a `curl | sh`, a fabricated tool call, a `--no-verify` are merge-relevant\n// even though they name no changed file). `force-push` is NOT here: it's a git op\n// scoped by its touched paths below (it touches none ⇒ session hygiene, not a merge\n// risk for this change).\n// Path-free integrity findings kept regardless of the diff — a `curl|sh` or a\n// fabricated/malformed tool call is a session-integrity problem no matter which files\n// changed. `hook-bypass` is NOT here: it's scoped below (a `push --no-verify` only skips\n// pre-push hooks ⇒ session hygiene, not a merge risk for this diff).\nconst PROCESS_PREFIXES = [\"pipe-sh\", \"tool-fabricated\", \"tool-malformed-args\"];\nconst isProcessFinding = (id: string): boolean =>\n PROCESS_PREFIXES.some((p) => id === p || id.startsWith(`${p}-`));\nconst isDestructive = (id: string): boolean => id.startsWith(\"destructive-\");\nconst isGitProcess = (id: string): boolean =>\n id.startsWith(\"force-push\") || id.startsWith(\"destructive-git\");\n\n/** True when the **destructive** git op in this command touches a diff file. Only the\n * destructive / history-rewriting invocation is judged — a benign sibling clause in the\n * same compound command (e.g. `git add <diff-file> && git reset --hard <ref>`) must NOT\n * make the unrelated reset look diff-relevant. Ref/history/worktree-wide ops\n * (`reset --hard <branch>`, `push -f`, `branch -D`) have no touched path ⇒ false ⇒\n * session hygiene, not a merge risk for this change. */\n/** True when every `--no-verify` in this command is on a `push` (no `commit --no-verify`).\n * A `push --no-verify` skips only the pre-push hooks (no committed content changes), so it\n * is session hygiene, not a merge risk for this diff. A `commit --no-verify` skipped the\n * pre-commit checks on committed content ⇒ NOT push-only ⇒ kept. */\nfunction hookBypassIsPushOnly(command: string): boolean {\n const noVerify = parseGitInvocations(command).filter(isNoVerify);\n return noVerify.length > 0 && noVerify.every((inv) => inv.subcommand === \"push\");\n}\n\nfunction gitTouchesDiff(command: string, files: readonly string[]): boolean {\n for (const inv of parseGitInvocations(command)) {\n if (!destroysGitData(inv) && !rewritesHistory(inv)) {\n continue; // a `git add`/`commit`/`checkout <branch>` clause is not the destructive op\n }\n if (touchedPaths(inv).some((p) => inDiff(p, files))) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Keep a finding under a diff scope:\n * - path-free integrity findings (hook-bypass, pipe-sh, fabricated/malformed) — always,\n * - a git process/destructive op only when its touched paths fall in the diff\n * (`reset --hard <branch>`/`push -f` touch nothing ⇒ dropped to the session card),\n * - a non-git destructive whose target is a repo path (not a `$TMP`/`.receipts` scratch),\n * - any other finding about a changed file.\n * Pure function of the finding + the recorded file list (+ the finding's own command,\n * itself re-derived from the transcript) ⇒ reproducible at verify.\n */\nexport function keepUnderDiff(\n f: Pick<Finding, \"id\" | \"title\"> & { filePath?: string; evidenceSpanId?: string },\n files: readonly string[],\n commandFor?: (f: Pick<Finding, \"id\" | \"evidenceSpanId\">) => string | undefined,\n): boolean {\n if (isProcessFinding(f.id)) {\n return true;\n }\n // hook-bypass: a `push --no-verify` skips only pre-push hooks (no code change) ⇒ session\n // hygiene, dropped from the diff; a `commit --no-verify` (or an unresolvable command) is\n // kept — it may have skipped pre-commit checks on the committed change.\n if (f.id === \"hook-bypass\" || f.id.startsWith(\"hook-bypass-\")) {\n const cmd = commandFor?.(f);\n return cmd ? !hookBypassIsPushOnly(cmd) : true;\n }\n // git ref/history surgery is a merge risk only if it touched a changed file.\n if (isGitProcess(f.id)) {\n const cmd = commandFor?.(f);\n return cmd ? gitTouchesDiff(cmd, files) : false;\n }\n if (isDestructive(f.id)) {\n // scratch (`/tmp`) and the tool's own regenerable `.receipts/*.json` are both\n // out-of-scope for \"did this change do damage?\" — drop either from the diff.\n if (destructiveOutsideRepo(f.title) || regenerableReceiptRm(f.title)) {\n return false;\n }\n // a git destructive (`git reset --hard <branch>`, `git rm`) only counts when it hit\n // the diff; a non-git filesystem removal (`rm -rf src/old`) keeps the repo-path\n // behaviour. The command comes from the span resolver, falling back to the title's\n // clause (`Destructive op: <cmd>`), so this holds even without a resolver.\n const cmd =\n commandFor?.(f) ?? f.title.replace(/^Destructive op:\\s*/i, \"\").replace(/\\s*×\\d+\\s*$/, \"\");\n if (/(^|[\\s;&|])git\\s/.test(cmd)) {\n return gitTouchesDiff(cmd, files);\n }\n return true;\n }\n return f.filePath ? inDiff(f.filePath, files) : false;\n}\n\nexport interface ScopedData {\n derived: DerivedSummary;\n findings: FindingSet;\n}\n\n/**\n * Cost/tokens of just the generation turns that edited a file in the diff — \"what\n * producing *this change* cost\", versus the whole-session total. Each edit tool span\n * links to the assistant turn that emitted it via `parentSpanId`; we collect those\n * generations and sum their recorded/estimated cost + tokens. Pure function of\n * `derived.spans` (re-derived from the transcript) + `files` ⇒ reproducible at verify.\n *\n * Honest bound: a turn that edited a diff file *and* others is counted whole, and the\n * turn's cost includes its shared context overhead — so this is an upper estimate of the\n * diff's marginal cost, not a precise split. It is always ≤ the session total and, in a\n * session that produced several PRs, far below it.\n */\nexport function diffEffort(\n derived: DerivedSummary,\n files: readonly string[],\n): { cost: number; tokens: number; turns: number } {\n const editedGens = new Set<string>();\n for (const s of derived.spans) {\n if (s.kind !== \"tool\" || !s.parentSpanId || !isEditTool(s.name)) {\n continue;\n }\n const fp = filePathOf(s.input);\n if (fp && inDiff(fp, files)) {\n editedGens.add(s.parentSpanId);\n }\n }\n let cost = 0;\n let tokens = 0;\n for (const s of derived.spans) {\n if (s.kind === \"generation\" && editedGens.has(s.spanId)) {\n // recorded cost when present, else estimate from the turn's tokens + model (the\n // generation span's name is the model id) — the same basis as the session total.\n cost += s.cost || estimateCost(s.tokens, s.name);\n tokens += s.tokens?.total ?? 0;\n }\n }\n return { cost, tokens, turns: editedGens.size };\n}\n\n/**\n * Apply a diff scope to derived evidence + findings. Scopes the risk-relevant\n * evidence (`filesChanged`, `destructiveOps`) to the change and computes the diff's own\n * cost (`diffCostUsd`/`diffTokens`/`diffTurns`); leaves whole-session effort metrics\n * (`edits`/`commands`/`reads`/total tokens/cost) intact for the footer. Deterministic;\n * uses only `files` so re-derivation reproduces it from `predicate.scope.files`.\n */\nexport function applyDiffScope(\n derived: DerivedSummary,\n findings: FindingSet,\n files: readonly string[],\n): ScopedData {\n // evidenceSpanId → the span's command, so a git process/destructive finding can be\n // attributed to the diff by its touched paths. Built from `derived.spans` (re-derived\n // from the transcript) so the scope stays a pure function of (derived, findings, files).\n const spanCmd = new Map<string, string>();\n for (const s of derived.spans) {\n if (s.spanId) {\n spanCmd.set(s.spanId, commandOf(s.input));\n }\n }\n const cmdOf = (f: Pick<Finding, \"evidenceSpanId\">): string | undefined =>\n f.evidenceSpanId ? spanCmd.get(f.evidenceSpanId) : undefined;\n const keep = (f: Finding): boolean => keepUnderDiff(f, files, cmdOf);\n const scopedFindings: FindingSet = {\n main: findings.main.filter(keep),\n minor: findings.minor.filter(keep),\n };\n const filesChanged = derived.filesChanged.filter((fc) => inDiff(fc.path, files));\n const destructiveCount = derived.spans\n .filter((s) => s.destructive)\n .filter((s) => {\n const cmd = commandOf(s.input);\n const clause = destructiveMatch(cmd) ?? cmd;\n if (destructiveOutsideRepo(clause) || regenerableReceiptRm(clause)) {\n return false;\n }\n // git ref/history surgery that touched no changed file isn't this diff's damage.\n if (/(^|[\\s;&|])git\\s/.test(cmd)) {\n return gitTouchesDiff(cmd, files);\n }\n return true;\n }).length;\n const eff = diffEffort(derived, files);\n return {\n derived: {\n ...derived,\n filesChanged,\n destructiveCount,\n diffCostUsd: eff.cost,\n diffTokens: eff.tokens,\n diffTurns: eff.turns,\n },\n findings: scopedFindings,\n };\n}\n","/**\n * Which surface a finding belongs to. The **merge gate** (the PR comment) is about\n * the *merged artifact* — what a reviewer must check before merging. The **operator**\n * view (the `receipts` card and `receipts trends`) is about *how the agent worked* —\n * efficiency, cost, loops, retries. One source of truth, shared by the PR-comment\n * renderer and its standalone Action mirror.\n */\n\n/** Kinds that describe agent behaviour/efficiency, not the merged change. */\nconst OPERATOR_KINDS = new Set([\n \"loop\",\n \"bottleneck\",\n \"cost-concentration\",\n \"cache-opportunity\",\n \"model-downgrade\",\n]);\n\nexport type Surface = \"merge\" | \"operator\";\n\nexport function findingSurface(id: string): Surface {\n // `errcluster-<tool>` (\"X failed N× before succeeding\") is an efficiency signal.\n if (OPERATOR_KINDS.has(id) || id.startsWith(\"errcluster-\")) {\n return \"operator\";\n }\n return \"merge\";\n}\n\n/**\n * True when a destructive finding's target is a **scratch path** (a `$VAR`, `/tmp`,\n * `/var/folders`, or `~`), so it can't be a merge risk and shouldn't gate the PR.\n * Parsed from the finding title's clause (the `destructiveMatch` output, e.g.\n * `Destructive op: rm -rf \"$TMP\"`). Conservative: only returns true when **every**\n * `rm`/`rmdir` operand is clearly non-repo — `rm -rf ./src`, `rm -rf build`,\n * `git reset --hard`, `DROP TABLE` are all kept (return false).\n */\nexport function destructiveOutsideRepo(title: string): boolean {\n const clause = title.replace(/^Destructive op:\\s*/i, \"\").replace(/\\s*×\\d+\\s*$/, \"\");\n // Only filesystem removals carry a path target we can judge; git/SQL/infra\n // destructives act on the repo or an external system → never \"outside repo\".\n const m = clause.match(/^\\s*(rm|rmdir)\\b(.*)$/i);\n if (!m) {\n return false;\n }\n const operands = m[2].split(/\\s+/).filter((t) => t && !t.startsWith(\"-\"));\n if (operands.length === 0) {\n return false;\n }\n const scratch = (t: string): boolean => {\n const p = t.replace(/^[\"']|[\"']$/g, \"\");\n return (\n /\\$/.test(p) ||\n p.startsWith(\"/tmp\") ||\n p.startsWith(\"/var/folders\") ||\n p.startsWith(\"/private/var/folders\") ||\n p.startsWith(\"~\")\n );\n };\n return operands.every(scratch);\n}\n\n/**\n * True when a destructive finding deletes only the tool's **own regenerable Receipt\n * artifact** (`rm .receipts/<branch>.json`), e.g. when regenerating it against a\n * corrected base. Like {@link destructiveOutsideRepo} this is a scope-layer carve-out:\n * a `.receipts/*.json` is inside the repo but is rewritten by `receipts pr`, so it\n * isn't a merge risk in the change under review. Conservative: only `rm`/`rmdir` whose\n * **every** operand is a `.receipts/<name>.json` file — a mixed command\n * (`rm .receipts/x.json src/app.ts`) or the whole dir (`rm -rf .receipts`) returns\n * false and keeps flagging.\n */\nexport function regenerableReceiptRm(title: string): boolean {\n const clause = title.replace(/^Destructive op:\\s*/i, \"\").replace(/\\s*×\\d+\\s*$/, \"\");\n const m = clause.match(/^\\s*(rm|rmdir)\\b(.*)$/i);\n if (!m) {\n return false;\n }\n const operands = m[2].split(/\\s+/).filter((t) => t && !t.startsWith(\"-\"));\n if (operands.length === 0) {\n return false;\n }\n // a flat `.receipts/<name>.json` file (receipts are one-per-branch, not nested) —\n // the dir itself (`.receipts`, `.receipts/`) has no `.json` operand, so it's kept.\n const receiptJson = (t: string): boolean =>\n /(?:^|\\/)\\.receipts\\/[^/]+\\.json$/.test(t.replace(/^[\"']|[\"']$/g, \"\"));\n return operands.every(receiptJson);\n}\n","import type { Session, SessionMessage } from \"./types.js\";\n\n/** The distinct branches that appear across a session's messages, in first-seen order. */\nexport function branchesIn(session: Session): string[] {\n const seen: string[] = [];\n for (const m of session.messages) {\n if (m.gitBranch && !seen.includes(m.gitBranch)) {\n seen.push(m.gitBranch);\n }\n }\n return seen;\n}\n\n/**\n * Assign every message an effective branch: its own `gitBranch`, else the nearest\n * preceding tagged branch (carry-forward); leading untagged messages take the\n * first branch that appears. Deterministic.\n */\nfunction effectiveBranches(messages: SessionMessage[]): (string | undefined)[] {\n const firstTagged = messages.find((m) => m.gitBranch)?.gitBranch;\n let current = firstTagged;\n return messages.map((m) => {\n if (m.gitBranch) {\n current = m.gitBranch;\n }\n return current;\n });\n}\n\n/**\n * Slice a session down to the messages belonging to `branch` (non-contiguous\n * visits included), recomputing the per-session `digestSource` over the slice so\n * the Receipt's subject digest is scoped to this branch's work. Deterministic.\n *\n * Returns null when the branch isn't present in the session at all.\n */\nexport function sliceByBranch(session: Session, branch: string): Session | null {\n if (!branchesIn(session).includes(branch)) {\n return null;\n }\n const eff = effectiveBranches(session.messages);\n const messages = session.messages.filter((_, i) => eff[i] === branch);\n if (messages.length === 0) {\n return null;\n }\n const timestamps = messages\n .map((m) => m.timestamp)\n .filter((t): t is number => typeof t === \"number\");\n const startedAt = timestamps.length ? Math.min(...timestamps) : session.startedAt;\n const endedAt = timestamps.length ? Math.max(...timestamps) : session.endedAt;\n\n return {\n ...session,\n id: `${session.id}#${branch}`,\n gitBranch: branch,\n startedAt,\n endedAt,\n messages,\n // Scope the subject digest to this branch's slice (reproducible by re-slicing).\n digestSource: JSON.stringify({ id: session.id, branch, messages }),\n };\n}\n","import { deriveFindings } from \"../findings/findings.js\";\nimport { deriveSpans } from \"../findings/spans.js\";\nimport { type Receipt, type ReceiptScope, buildReceipt } from \"../receipt/build.js\";\nimport { canonicalize } from \"../receipt/canonical.js\";\nimport { redactReceipt } from \"../share/redact.js\";\nimport { applyDiffScope } from \"../trace/diffScope.js\";\nimport { loadById } from \"../trace/load.js\";\nimport { sliceByBranch } from \"../trace/slice.js\";\nimport type { AgentSource } from \"../trace/types.js\";\n\nexport interface RederiveOptions {\n /** agent that produced the transcript (default: claude-code) */\n source?: AgentSource;\n /** scope to one branch's turns before deriving */\n branch?: string;\n /** the committed receipt's recorded scope — re-applied so the re-derivation\n * matches a scoped receipt byte-for-byte (SPEC-0013 L1). Its `files`/`branch`\n * come from the receipt, so no git is needed at verify time. */\n scope?: ReceiptScope;\n /** apply secret redaction (to compare against a redacted committed Receipt) */\n redact?: boolean;\n}\n\n/**\n * Re-run the deterministic engine on a transcript file to reproduce its Receipt.\n * Because the pipeline is deterministic, a verifier with the transcript gets the\n * exact same Receipt — that's what proves a committed Receipt is faithful and not\n * fabricated (SPEC-0009 Part B, L1). When the committed receipt was scoped\n * (SPEC-0013), the same recorded scope is re-applied so the bytes still match.\n */\nexport async function rederiveFromTranscript(\n path: string,\n opts: RederiveOptions = {},\n): Promise<Receipt | null> {\n const loaded = await loadById(opts.source ?? \"claude-code\", path);\n if (!loaded) {\n return null;\n }\n // Resolve scope: the recorded `predicate.scope` takes precedence; fall back to a\n // bare `--branch` for pre-SPEC-0013 (branch-only) receipts.\n const scope = opts.scope;\n const branch = scope?.kind === \"branch\" ? scope.branch : opts.branch;\n const session = branch ? (sliceByBranch(loaded, branch) ?? loaded) : loaded;\n\n let derived = deriveSpans(session);\n let findings = deriveFindings(derived);\n if (scope?.kind === \"diff\" && scope.files) {\n const scoped = applyDiffScope(derived, findings, scope.files);\n derived = scoped.derived;\n findings = scoped.findings;\n }\n\n const receipt = await buildReceipt(session, derived, findings, { scope });\n return opts.redact ? redactReceipt(receipt) : receipt;\n}\n\nexport interface RederiveCompare {\n /** the re-derivation produced a Receipt at all */\n rederived: boolean;\n /** the committed Receipt byte-equals the re-derivation (faithful, not fabricated) */\n matches: boolean;\n}\n\n/**\n * Compare a committed Receipt against a fresh re-derivation from its transcript.\n * `matches` true ⇒ the Receipt is the deterministic function of that transcript.\n */\nexport async function compareToTranscript(\n committed: Receipt,\n transcriptPath: string,\n opts: RederiveOptions = {},\n): Promise<RederiveCompare> {\n // Re-apply the committed receipt's own recorded scope so a scoped receipt\n // re-derives byte-for-byte (the basis is in the receipt, so no git is needed).\n const fresh = await rederiveFromTranscript(transcriptPath, {\n redact: true,\n ...opts,\n scope: committed.predicate.scope,\n });\n if (!fresh) {\n return { rederived: false, matches: false };\n }\n return { rederived: true, matches: canonicalize(committed) === canonicalize(fresh) };\n}\n","/**\n * The catalog of merge-surface checks the engine runs on every diff. The PR comment\n * lists it so a reviewer sees *what was mechanically scanned for* — the cleared checks\n * are the trust surface (\"a deterministic auditor looked for these and found nothing\"),\n * the flagged ones point at the Findings above. This is a render-time partition of the\n * existing finding ids — it adds no detector and makes no judgement: \"cleared\" means\n * **no detector fired on this diff**, never \"this code is good\" (SPEC-0000 R2).\n *\n * Keep this in lock-step with the detectors: every merge-surface finding id prefix MUST\n * map to a category here (the `categorize` self-check throws in dev if one doesn't), so\n * the catalog never silently under-claims or claims a check that doesn't run.\n */\n\nexport interface Check {\n key: string;\n icon: string;\n label: string;\n /** matches the finding-id prefixes this category covers */\n prefixes: string[];\n}\n\n/** Ordered by review priority. Operator/efficiency findings (loop, errcluster, …) are\n * deliberately absent — they live on the `receipts` card, not the merge gate. */\nexport const CHECK_CATALOG: readonly Check[] = [\n {\n key: \"destructive\",\n icon: \"🛡️\",\n label: \"destructive ops\",\n prefixes: [\"destructive\", \"file-shrink\"],\n },\n {\n key: \"git-history\",\n icon: \"🌿\",\n label: \"git-history safety\",\n prefixes: [\"force-push\", \"history-rewrite\"],\n },\n { key: \"secrets\", icon: \"🔑\", label: \"secret leaks\", prefixes: [\"secret\"] },\n { key: \"cicd\", icon: \"⚙️\", label: \"CI/CD tampering\", prefixes: [\"ci-cd-touch\"] },\n { key: \"lockfile\", icon: \"📦\", label: \"lockfile integrity\", prefixes: [\"lockfile-edit\"] },\n {\n key: \"tests\",\n icon: \"🧪\",\n label: \"test integrity\",\n prefixes: [\n \"fake-green\",\n \"test-focus\",\n \"test-skipped\",\n \"test-trivialised\",\n \"grader-edit\",\n \"eval-override\",\n ],\n },\n {\n key: \"weakening\",\n icon: \"🔓\",\n label: \"config/safety weakening\",\n prefixes: [\"config-weaken\", \"hook-bypass\", \"pipe-sh\"],\n },\n {\n key: \"blind-edits\",\n icon: \"👁️\",\n label: \"blind / unverified edits\",\n prefixes: [\"blind-edit\", \"unverified-change\", \"edit-reversion\"],\n },\n { key: \"injection\", icon: \"💉\", label: \"prompt-injection\", prefixes: [\"prompt-injection\"] },\n { key: \"trojan\", icon: \"🕵️\", label: \"hidden unicode\", prefixes: [\"trojan-source\"] },\n {\n key: \"duplication\",\n icon: \"📋\",\n label: \"duplicated code\",\n prefixes: [\"dup-file\", \"duplicated-code\"],\n },\n { key: \"swallowed\", icon: \"🤫\", label: \"swallowed errors\", prefixes: [\"error-swallowed\"] },\n { key: \"stubbed\", icon: \"🚧\", label: \"stubbed implementation\", prefixes: [\"impl-stubbed\"] },\n { key: \"malformed\", icon: \"🧱\", label: \"malformed artifacts\", prefixes: [\"malformed-artifact\"] },\n { key: \"truncated\", icon: \"✂️\", label: \"truncated turns\", prefixes: [\"truncated-turn\"] },\n {\n key: \"promises\",\n icon: \"🤝\",\n label: \"unfulfilled claims\",\n prefixes: [\"claimed-commit-none\", \"unfulfilled-promise\"],\n },\n {\n key: \"tool-calls\",\n icon: \"🧬\",\n label: \"tool-call integrity\",\n prefixes: [\"tool-fabricated\", \"tool-malformed-args\"],\n },\n] as const;\n\n/** A finding id matches a category if it equals or `<prefix>-`-starts one of its prefixes. */\nfunction matches(id: string, check: Check): boolean {\n return check.prefixes.some((p) => id === p || id.startsWith(`${p}-`));\n}\n\n/** The catalog check a finding id belongs to (for cross-receipt rollups), or undefined. */\nexport function checkForId(id: string): Check | undefined {\n return CHECK_CATALOG.find((c) => matches(id, c));\n}\n\nexport type CheckSeverity = \"critical\" | \"high\" | \"medium\" | \"low\";\nconst SEV_RANK: Record<CheckSeverity, number> = { critical: 0, high: 1, medium: 2, low: 3 };\n\nexport interface FlaggedCheck {\n check: Check;\n count: number;\n /** worst severity among the findings that matched this category */\n severity: CheckSeverity;\n}\n\nexport interface CategorizedChecks {\n /** categories with ≥1 merge finding on this diff, each with count + worst severity */\n flagged: FlaggedCheck[];\n /** categories that ran and found nothing — the trust surface */\n cleared: Check[];\n /** total categories in the catalog */\n total: number;\n}\n\n/**\n * Partition the catalog against a set of (merge-surface) findings. A category is\n * `flagged` when ≥1 finding matches it (carrying the count + worst severity), else\n * `cleared`. The order of {@link CHECK_CATALOG} is preserved in both lists.\n */\nexport function categorize(\n findings: readonly { id: string; severity?: CheckSeverity }[],\n): CategorizedChecks {\n const flagged: FlaggedCheck[] = [];\n const cleared: Check[] = [];\n for (const check of CHECK_CATALOG) {\n const hits = findings.filter((f) => matches(f.id, check));\n if (hits.length > 0) {\n const severity = hits\n .map((f) => f.severity ?? \"high\")\n .sort((a, b) => SEV_RANK[a] - SEV_RANK[b])[0];\n flagged.push({ check, count: hits.length, severity });\n } else {\n cleared.push(check);\n }\n }\n return { flagged, cleared, total: CHECK_CATALOG.length };\n}\n"],"mappings":"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","names":["spawnSync","spawnSync","base"]}
|