altimate-receipts 0.6.0 → 0.6.1

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.
@@ -202,7 +202,7 @@ function git(args, cwd) {
202
202
  return r.status === 0 ? r.stdout : null;
203
203
  }
204
204
  var RECEIPTS_DIR_RE = /(?:^|\/)\.receipts\//;
205
- function changedFiles(baseOverride) {
205
+ function changedFiles(baseOverride, opts) {
206
206
  const root = git(["rev-parse", "--show-toplevel"])?.trim();
207
207
  if (!root) {
208
208
  return null;
@@ -210,7 +210,13 @@ function changedFiles(baseOverride) {
210
210
  let base2 = baseOverride;
211
211
  if (!base2) {
212
212
  const sym = git(["symbolic-ref", "refs/remotes/origin/HEAD"], root)?.trim();
213
- base2 = sym ? sym.replace("refs/remotes/", "") : "main";
213
+ if (sym) {
214
+ base2 = sym.replace("refs/remotes/", "");
215
+ } else if (git(["rev-parse", "--verify", "--quiet", "origin/main"], root) !== null) {
216
+ base2 = "origin/main";
217
+ } else {
218
+ base2 = "main";
219
+ }
214
220
  }
215
221
  let out = git(["diff", "--name-only", `${base2}...HEAD`], root);
216
222
  if (out === null && !baseOverride) {
@@ -219,15 +225,35 @@ function changedFiles(baseOverride) {
219
225
  base2 = "main";
220
226
  }
221
227
  }
222
- if (out === null) {
228
+ const pending = opts?.includeWorkingTree ? workingTreeFiles(root) : [];
229
+ if (out === null && pending.length === 0) {
223
230
  return null;
224
231
  }
225
- const files = out.split("\n").map((s) => s.trim()).filter(Boolean).filter((f) => !RECEIPTS_DIR_RE.test(f)).sort();
232
+ const files = [.../* @__PURE__ */ new Set([...(out ?? "").split("\n").map((s) => s.trim()), ...pending])].filter(Boolean).filter((f) => !RECEIPTS_DIR_RE.test(f)).sort();
226
233
  if (files.length === 0) {
227
234
  return null;
228
235
  }
229
236
  return { base: base2, files, repoRoot: root };
230
237
  }
238
+ function workingTreeFiles(root) {
239
+ const out = git(["status", "--porcelain", "-z", "--untracked-files=all"], root);
240
+ if (!out) {
241
+ return [];
242
+ }
243
+ const toks = out.split("\0");
244
+ const files = [];
245
+ for (let i = 0; i < toks.length; i++) {
246
+ const t = toks[i];
247
+ if (!t || t.length < 4 || t[2] !== " ") {
248
+ continue;
249
+ }
250
+ files.push(t.slice(3));
251
+ if (t[0] === "R" || t[0] === "C") {
252
+ i++;
253
+ }
254
+ }
255
+ return files;
256
+ }
231
257
  function inDiff(filePath, files) {
232
258
  const base2 = filePath.split("/").pop();
233
259
  for (const d of files) {
@@ -578,4 +604,4 @@ export {
578
604
  rederiveFromTranscript,
579
605
  compareToTranscript
580
606
  };
581
- //# sourceMappingURL=chunk-ASPLZPMQ.js.map
607
+ //# sourceMappingURL=chunk-54J2FEKI.js.map
@@ -0,0 +1 @@
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 cwdAtFirstGit,\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(\n baseOverride?: string,\n opts?: { includeWorkingTree?: boolean },\n): 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 if (sym) {\n base = sym.replace(\"refs/remotes/\", \"\");\n } else if (git([\"rev-parse\", \"--verify\", \"--quiet\", \"origin/main\"], root) !== null) {\n // origin/HEAD is only set by clone/`remote set-head`; without it, prefer the\n // remote's main over a possibly-stale local main so the diff isn't inflated\n // by upstream commits the user hasn't pulled.\n base = \"origin/main\";\n } else {\n base = \"main\";\n }\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 // An agent pre-push hook fires BEFORE a compound `git commit … && git push` runs,\n // so the in-flight commit's files aren't in base...HEAD yet — union the working\n // tree (staged + unstaged + untracked) when the caller says a commit is pending.\n const pending = opts?.includeWorkingTree ? workingTreeFiles(root) : [];\n if (out === null && pending.length === 0) {\n return null;\n }\n const files = [...new Set([...(out ?? \"\").split(\"\\n\").map((s) => s.trim()), ...pending])]\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/** Staged + unstaged + untracked paths from `git status --porcelain -z`\n * (repo-relative; rename/copy entries contribute the destination path only). */\nfunction workingTreeFiles(root: string): string[] {\n const out = git([\"status\", \"--porcelain\", \"-z\", \"--untracked-files=all\"], root);\n if (!out) {\n return [];\n }\n const toks = out.split(\"\\0\");\n const files: string[] = [];\n for (let i = 0; i < toks.length; i++) {\n const t = toks[i];\n if (!t || t.length < 4 || t[2] !== \" \") {\n continue;\n }\n files.push(t.slice(3));\n if (t[0] === \"R\" || t[0] === \"C\") {\n i++; // -z rename/copy: the next NUL-separated token is the source path\n }\n }\n return files;\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 locusFor?: (f: Pick<Finding, \"id\" | \"evidenceSpanId\">) => \"here\" | \"elsewhere\" | undefined,\n): boolean {\n // Cross-repo bleed (SPEC-0069 addendum, observed live): a session that also ran\n // `git commit --no-verify` in ANOTHER repo (`cd /other && git commit --no-verify`)\n // must not flag THIS repo's diff receipt. The locus is resolved by the caller from\n // transcript data only (span cwd + cd tracking), so the drop re-derives with no\n // git at verify time; \"elsewhere\" includes unresolvable-cd uncertainty (under-claim).\n if (locusFor?.(f) === \"elsewhere\") {\n return false;\n }\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 * Branch-narrowed effort (SPEC-0068 R4): in a session that produced several PRs, the\n * whole-session `diffEffort` counts every turn that ever touched a hot file (cli.ts,\n * README) — observed 26× inflation on a marathon session. When the session carries the\n * receipt branch's turns, the honest bound is branch turns ∩ diff-file edits. Returns\n * null when there is no slice or the slice has zero diff-file-editing turns (e.g.\n * Bash-only writes) — callers then keep the whole-session bound and must NOT record\n * `scope.branch`, so the receipt always states how its effort was computed. Shared by\n * produce (`receipts pr`) and verify (rederive) so the bytes match (L1).\n */\nexport function narrowEffort(\n sliceDerived: DerivedSummary | null,\n files: readonly string[],\n): { cost: number; tokens: number; turns: number } | null {\n if (!sliceDerived) {\n return null;\n }\n const eff = diffEffort(sliceDerived, files);\n return eff.turns > 0 ? eff : null;\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 projectPath?: string,\n): ScopedData {\n // evidenceSpanId → the span's command/cwd, so a git process/destructive finding can\n // be attributed to the diff by its touched paths and its repo. Built from\n // `derived.spans` (re-derived from the transcript) so the scope stays a pure\n // function of (derived, findings, files, projectPath) — all transcript-derived.\n const spanCmd = new Map<string, string>();\n const spanCwd = new Map<string, string>();\n for (const s of derived.spans) {\n if (s.spanId) {\n spanCmd.set(s.spanId, commandOf(s.input));\n if (s.cwd) {\n spanCwd.set(s.spanId, s.cwd);\n }\n }\n }\n const cmdOf = (f: Pick<Finding, \"evidenceSpanId\">): string | undefined =>\n f.evidenceSpanId ? spanCmd.get(f.evidenceSpanId) : undefined;\n // Did this finding's command actually run in the session's own project?\n // `cwdAtFirstGit` tracks `cd` clauses inside compounds; outside or uncertain ⇒\n // \"elsewhere\" (another repo's process risk — drop from the diff receipt).\n const locusOf = (f: Pick<Finding, \"evidenceSpanId\">): \"here\" | \"elsewhere\" | undefined => {\n if (!projectPath || !f.evidenceSpanId) {\n return undefined;\n }\n const base = spanCwd.get(f.evidenceSpanId);\n const state = cwdAtFirstGit(spanCmd.get(f.evidenceSpanId) ?? \"\", base);\n if (state.kind === \"unknown\") {\n return \"elsewhere\";\n }\n const at = state.kind === \"known\" ? state.path : base;\n if (!at) {\n return undefined;\n }\n return at === projectPath || at.startsWith(`${projectPath}/`) ? \"here\" : \"elsewhere\";\n };\n const keep = (f: Finding): boolean => keepUnderDiff(f, files, cmdOf, locusOf);\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, narrowEffort } from \"../trace/diffScope.js\";\nimport { loadById } from \"../trace/load.js\";\nimport { agentIds } from \"../trace/registry.js\";\nimport { sliceByBranch } from \"../trace/slice.js\";\nimport type { AgentSource } from \"../trace/types.js\";\n\n/**\n * The agent that produced a receipt, read from its subject prefix (`<source>/<id>`,\n * set in buildReceipt). Re-derivation must load the transcript through the SAME\n * adapter the receipt was built with, or the bytes can't match (e.g. a `codex/…`\n * receipt re-derived through the Claude adapter yields empty evidence). Returns\n * undefined for an unknown prefix so the caller keeps its default.\n */\nexport function sourceFromReceipt(receipt: Receipt): AgentSource | undefined {\n const prefix = receipt.subject?.[0]?.name?.split(\"/\")[0];\n return agentIds().find((id) => id === prefix);\n}\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, session.projectPath);\n derived = scoped.derived;\n findings = scoped.findings;\n if (scope.branch) {\n // SPEC-0068 R4: the receipt declares branch-narrowed effort — recompute it the\n // same way (branch turns ∩ diff-file edits) so the byte-compare holds.\n const slice = sliceByBranch(loaded, scope.branch);\n const eff = narrowEffort(slice ? deriveSpans(slice) : null, scope.files);\n if (eff) {\n derived = {\n ...derived,\n diffCostUsd: eff.cost,\n diffTokens: eff.tokens,\n diffTurns: eff.turns,\n };\n }\n }\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 // Re-derive through the adapter the receipt names (its subject prefix), so a\n // codex/cursor receipt isn't re-run through the Claude adapter. An explicit\n // opts.source still wins.\n source: opts.source ?? sourceFromReceipt(committed),\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"]}
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  renderShareMarkdown,
14
14
  sliceByBranch,
15
15
  toDsseEnvelope
16
- } from "./chunk-ASPLZPMQ.js";
16
+ } from "./chunk-54J2FEKI.js";
17
17
  import {
18
18
  computeTrends,
19
19
  deriveTargets,
@@ -340,6 +340,14 @@ function isGitPush(command) {
340
340
  }
341
341
  return false;
342
342
  }
343
+ function isGitCommit(command) {
344
+ for (const { sub, rest } of gitInvocations(command)) {
345
+ if (sub === "commit" && !rest.includes("--dry-run")) {
346
+ return true;
347
+ }
348
+ }
349
+ return false;
350
+ }
343
351
  function gitStdinPushesBranch(stdin) {
344
352
  return stdin.split("\n").some((line) => line.trim().startsWith("refs/heads/"));
345
353
  }
@@ -379,6 +387,7 @@ async function readStdin(stream = process.stdin) {
379
387
  return data;
380
388
  }
381
389
  async function runHookPrePush(dialect, stdin, generate) {
390
+ let pendingCommit = false;
382
391
  try {
383
392
  if (process.env.RECEIPTS_HOOK === "0") {
384
393
  return { exit: 0 };
@@ -402,6 +411,7 @@ async function runHookPrePush(dialect, stdin, generate) {
402
411
  if (!isGitPush(payload.tool_input.command)) {
403
412
  return { exit: 0 };
404
413
  }
414
+ pendingCommit = isGitCommit(payload.tool_input.command);
405
415
  } else if (!gitStdinPushesBranch(stdin)) {
406
416
  return { exit: 0 };
407
417
  }
@@ -425,7 +435,7 @@ async function runHookPrePush(dialect, stdin, generate) {
425
435
  process.stderr.write = (() => true);
426
436
  let generated;
427
437
  try {
428
- generated = await generate();
438
+ generated = await generate({ pendingCommit });
429
439
  } finally {
430
440
  process.stderr.write = write;
431
441
  }
@@ -1605,7 +1615,7 @@ async function runPr(opts) {
1605
1615
  process.stderr.write("receipts pr: not on a git branch (use --branch <name>).\n");
1606
1616
  return 1;
1607
1617
  }
1608
- const diff = opts.wholeSession ? null : changedFiles(opts.base);
1618
+ const diff = opts.wholeSession ? null : changedFiles(opts.base, { includeWorkingTree: opts.pendingCommit });
1609
1619
  const all = await listSessions();
1610
1620
  const picked = diff ? await pickForDiff(
1611
1621
  all,
@@ -2278,7 +2288,11 @@ async function runHook(kind, dialect) {
2278
2288
  return 1;
2279
2289
  }
2280
2290
  const stdin = await readStdin();
2281
- const res = await runHookPrePush(dialect, stdin, async () => runPr({}));
2291
+ const res = await runHookPrePush(
2292
+ dialect,
2293
+ stdin,
2294
+ async (o) => runPr({ pendingCommit: o?.pendingCommit })
2295
+ );
2282
2296
  if (res.message) {
2283
2297
  process.stderr.write(`${res.message}
2284
2298
  `);