altimate-receipts 0.3.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/LICENSE +202 -0
- package/README.md +251 -0
- package/dist/chunk-RQLUZ6FQ.js +262 -0
- package/dist/chunk-RQLUZ6FQ.js.map +1 -0
- package/dist/chunk-SUAQDKUV.js +529 -0
- package/dist/chunk-SUAQDKUV.js.map +1 -0
- package/dist/chunk-UHI6BGLE.js +4569 -0
- package/dist/chunk-UHI6BGLE.js.map +1 -0
- package/dist/cli.js +1523 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +293 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.js +235 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +67 -0
- package/schema/agent-execution-receipt-v1.json +248 -0
|
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,IAAM,cAAc;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,oBAAoB;AAIjC,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACtB,IAAM,YAAY,mPAAkO,aAAa;AAKjQ,IAAM,sBAAsB;AAQ5B,SAAS,aACP,OACA,OACA,SACQ;AACR,QAAM,eAAe,QAAQ,SACzB,QAAQ,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,KAAK,EAAE,KAAK,OAAO,EAAE,EAAE,KAAK,IAC3D,CAAC,2DAAsD;AAC3D,QAAM,OAAO;AAAA,IACX;AAAA,IACA,cAAc,KAAK,oBAAiB,KAAK;AAAA,IACzC;AAAA,IACA;AAAA,IACA,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACX,QAAM,QAAQ,QAAQ,SAAS,mBAAmB,QAAQ,CAAC,EAAE,KAAK,KAAK;AACvE,QAAM,KAAK,UAAU,mBAAmB,2BAA2B,CAAC,UAAU,mBAAmB,KAAK,CAAC,SAAS,mBAAmB,IAAI,CAAC;AACxI,QAAM,MAAM,GAAG,mBAAmB,IAAI,EAAE;AACxC,SAAO,6EAA4D,GAAG;AACxE;AAGA,IAAM,YAAyC,EAAE,GAAG,aAAM,GAAG,aAAM,GAAG,aAAM,GAAG,YAAK;AAGpF,IAAM,UAAuC;AAAA,EAC3C,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAGA,IAAM,UAA8C;AAAA,EAClD,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,IAAM,OAAiC,EAAE,UAAU,UAAK,MAAM,gBAAM,QAAQ,aAAM,KAAK,OAAI;AAC3F,IAAM,QAAkC,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,EAAE;AAgB3E,SAAS,gBAAgB,SAAkB,OAAyB,CAAC,GAAW;AACrF,QAAM,IAAI,QAAQ;AAClB,QAAM,KAAK,EAAE;AAOb,QAAM,gBAAgB,EAAE,SAAS,OAAO,CAAC,MAAM,eAAe,EAAE,EAAE,MAAM,UAAU,EAAE;AACpF,QAAM,gBAAgB,EAAE,SAAS;AAAA,IAC/B,CAAC,MACC,eAAe,EAAE,EAAE,MAAM,WACzB,EAAE,EAAE,GAAG,WAAW,cAAc,KAAK,uBAAuB,EAAE,KAAK;AAAA,EACvE;AACA,QAAM,IAAI,YAAY,cAAc,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,CAAC;AACtE,QAAM,OAAO,cAAc,OAAO,CAAC,MAAM,EAAE,aAAa,KAAK;AAC7D,QAAM,OAAO,WAAW,IAAI;AAE5B,QAAM,MAAgB,CAAC;AACvB,MAAI,KAAK,iBAAiB;AAG1B,MAAI,KAAK,2CAAiC,UAAU,CAAC,CAAC,UAAU,CAAC,WAAM,QAAQ,CAAC,CAAC,EAAE;AACnF,MAAI,KAAK,EAAE;AACX,MAAI,KAAK,SAAS;AAClB,MAAI,KAAK,EAAE;AAGX,MAAI,KAAK,QAAQ;AACf,UAAM,KAAK,QAAQ,CAAC,KAAK;AACzB,UAAM,OAAO,KAAK,QAAQ,SACtB,KAAK,QAAQ,IAAI,CAAC,OAAO,GAAG,MAAM,KAAK,EAAE,KAAK,IAAI,IAClD,GAAG,KAAK,MAAM,WAAW,KAAK,WAAW,IAAI,KAAK,GAAG;AACzD,QAAI,KAAK,OAAO,EAAE,GAAG;AACrB,QAAI;AAAA,MACF,OAAO,KAAK,QAAQ,UAAU,KAAK,MAAM,OAAO,KAAK,KAAK,sBAAsB,IAAI;AAAA,IACtF;AACA,QAAI,KAAK,EAAE;AACX,UAAM,SAAS,CAAC,GAAG,IAAI,EAAE;AAAA,MACvB,CAAC,GAAG,MAAM,MAAM,EAAE,QAAQ,IAAI,MAAM,EAAE,QAAQ,KAAK,EAAE,QAAQ,EAAE;AAAA,IACjE;AACA,eAAW,KAAK,OAAO,MAAM,GAAG,EAAE,GAAG;AACnC,UAAI,KAAK,YAAY,CAAC,CAAC;AAAA,IACzB;AACA,QAAI,KAAK,SAAS,IAAI;AACpB,UAAI,KAAK,gBAAW,KAAK,SAAS,EAAE,sCAAiC;AAAA,IACvE;AACA,QAAI,KAAK,EAAE;AAAA,EACb;AAGA,MAAI,KAAK,UAAU,MAAM,IAAI,CAAC,CAAC;AAC/B,MAAI,KAAK,EAAE;AAGX,QAAM,WAAW,aAAa,EAAE,QAAQ,OAAO,GAAG,IAAI;AACtD,MAAI,KAAK,cAAc,GAAG,IAAI,MAAM,eAAe,MAAM,QAAQ,CAAC;AAClE,SAAO,GAAG,IAAI,KAAK,IAAI,CAAC;AAAA;AAC1B;AAMA,SAAS,SAAS,IAA8C;AAC9D,SAAO,GAAG,eAAe,OAAO,oBAAQ,iBAAiB,GAAG,WAAW,CAAC,qBAAqB;AAC/F;AAGA,SAAS,UACP,MACA,IACA,GACQ;AACR,QAAM,SAAS,KAAK,QAAQ,SACxB,kBAAQ,KAAK,QAAQ,MAAM,IAAI,KAAK,KAAK,eACzC,YAAO,KAAK,KAAK,IAAI,KAAK,KAAK;AACnC,QAAM,QAAQ,EAAE,OAAO,SAAS,SAAU,EAAE,MAAM,SAAS,CAAC,IAAK,CAAC;AAClE,QAAM,QAAQ,CAAC,MAAM;AACrB,MAAI,MAAM,OAAQ,OAAM,KAAK,aAAM,MAAM,MAAM,QAAQ,MAAM,WAAW,IAAI,KAAK,GAAG,EAAE;AACtF,QAAM,KAAK,UAAU,EAAE,CAAC;AACxB,QAAM,OAAO,SAAS,EAAE;AACxB,MAAI,KAAM,OAAM,KAAK,IAAI;AACzB,QAAM,KAAK,IAAI,EAAE,QAAQ,KAAK,GAAG;AACjC,SAAO,MAAM,KAAK,QAAK;AACzB;AAKA,SAAS,kBAAkB,OAAkC;AAC3D,MAAI,MAAM,UAAU,IAAI;AACtB,WAAO,eAAe,MAAM,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,EAC/D;AACA,QAAM,QAAQ,oBAAI,IAAoB;AACtC,aAAW,KAAK,OAAO;AACrB,UAAM,MAAM,EAAE,SAAS,GAAG,IAAI,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC,MAAM;AACtD,UAAM,IAAI,MAAM,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC;AAAA,EAC1C;AACA,QAAM,OAAO,CAAC,GAAG,MAAM,QAAQ,CAAC,EAC7B,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAC1B,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,EAC/B,KAAK,QAAK;AACb,QAAM,WAAW,MAAM,OAAO,IAAI,WAAQ,MAAM,OAAO,CAAC,gBAAgB;AACxE,QAAM,SAAS,MACZ,MAAM,GAAG,EAAE,EACX,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EACrB,KAAK,IAAI;AACZ,QAAM,YACJ,MAAM,SAAS,KAAK,YAAO,MAAM,SAAS,EAAE,0CAA0C;AACxF,SAAO;AAAA,IACL,aAAa,MAAM,MAAM,qBAAqB,IAAI,GAAG,QAAQ;AAAA,IAC7D;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,MAAM,GAAG,SAAS;AAAA,IACrB;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAGA,SAAS,cACP,GACA,IACA,MACA,eACA,MACA,UACQ;AACR,QAAM,QAAkB,CAAC;AACzB,QAAM,QAAQ,EAAE,OAAO,SAAS,SAAU,EAAE,MAAM,SAAS,CAAC,IAAK,CAAC;AAClE,MAAI,MAAM,OAAQ,OAAM,KAAK,kBAAkB,KAAK,GAAG,EAAE;AACzD,QAAM,SAAS,aAAa,GAAG,UAAU,CAAC,CAAC;AAC3C,MAAI,OAAQ,OAAM,KAAK,QAAQ,EAAE;AACjC,QAAM;AAAA,IACJ,0BAAqB,KAAK,QAAQ,MAAM,iBAAc,KAAK,QAAQ,MAAM;AAAA,IACzE;AAAA,IACA,aAAa,IAAI;AAAA,IACjB;AAAA,EACF;AACA,QAAM,KAAK,aAAa,EAAE;AAC1B,MAAI,GAAI,OAAM,KAAK,EAAE;AACrB,QAAM,KAAK,QAAQ,WAAW,IAAI,aAAa,CAAC,QAAQ;AACxD,QAAM,SAAS,KAAK,SAChB,2CAA+B,KAAK,iBAAiB,uBAAoB,KAAK,cAAc,MAAM,EAAE,WACpG;AACJ,QAAM;AAAA,IACJ,QAAQ,MAAM,+JAAmJ,aAAa;AAAA,EAChL;AACA,QAAM,KAAK,QAAQ;AACnB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAG;AAAA,IACH;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAWA,SAAS,aAAa,IAAqD;AACzE,MAAI,GAAG,eAAe,KAAM,QAAO;AACnC,QAAM,QAAQ,GAAG,aAAa;AAC9B,QAAM,IAAI,GAAG,KAAK,QAAQ,UAAU,IAAI,KAAK,GAAG;AAChD,SAAO,mBAAc,iBAAiB,GAAG,WAAW,CAAC,SAAM,aAAa,GAAG,cAAc,CAAC,CAAC,gBAAa,CAAC;AAC3G;AAGA,SAAS,UAAU,IAA8C;AAC/D,QAAM,KAAK,GAAG;AACd,MAAI,IAAI;AACN,UAAM,MAAM;AAAA,MACV,GAAG,UAAU,OAAO,GAAG,GAAG,MAAM,YAAY;AAAA,MAC5C,GAAG,SAAS,KAAK,GAAG,MAAM,cAAc;AAAA,MACxC,GAAG,UAAU,GAAG,GAAG,OAAO,aAAa;AAAA,IACzC,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AACZ,UAAM,KAAK,CAAC,GAAG;AACf,WAAO,GAAG,KAAK,WAAM,cAAI,IAAI,OAAO,GAAG,UAAU;AAAA,EACnD;AACA,SAAO,GAAG,WAAW,eAAU;AACjC;AAGA,SAAS,aAAa,MAAiC;AACrD,QAAM,MAAM,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC;AAChE,QAAM,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,OAAO,GAAG,KAAK,GAAG,GAAG,KAAK,OAAO,EAAE,IAAI,CAAC,MAAM;AAC/E,UAAM,KAAK,IAAI,IAAI,EAAE,GAAG;AACxB,UAAM,SAAS,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC,IAAI,GAAG,KAAK,aAAa;AACjE,WAAO,KAAK,EAAE,IAAI,IAAI,EAAE,KAAK,MAAM,MAAM;AAAA,EAC3C,CAAC;AAED,OAAK,KAAK,CAAC,GAAG,OAAO,EAAE,SAAS,cAAS,IAAI,IAAI,MAAM,EAAE,SAAS,cAAS,IAAI,IAAI,EAAE;AACrF,SAAO,CAAC,sBAAsB,iBAAiB,GAAG,IAAI,EAAE,KAAK,IAAI;AACnE;AAGA,SAAS,WAAW,IAAsC,eAA+B;AACvF,QAAM,MAAM,GAAG;AACf,QAAM,MAAM,MACR,MAAM,YAAY,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,EACnC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAC3B,KAAK,IAAI,CAAC,OACb;AACJ,QAAM,KACJ,gBAAgB,IACZ,SAAM,aAAa,gCAAgC,kBAAkB,IAAI,KAAK,GAAG,KACjF;AACN,SAAO,kDAAkD,GAAG,KAAK,eAAY,GAAG,QAAQ,YAAY,GAAG,SAAM,iBAAiB,GAAG,OAAO,CAAC,SAAM,aAAa,GAAG,OAAO,KAAK,CAAC,UAAU,EAAE;AAC1L;AAGA,SAAS,YAAY,GAA2B;AAC9C,QAAM,MAAM,EAAE,cAAc,WAAM,EAAE,WAAW,KAAK;AACpD,QAAM,MAAM,EAAE,WAAW,OAAO,EAAE,QAAQ,GAAG,EAAE,OAAO,IAAI,EAAE,IAAI,KAAK,EAAE,QAAQ;AAC/E,QAAM,OAAO,KAAK,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,KAAK,GAAG,GAAG,GAAG;AAC7D,SAAO,EAAE,SAAS,GAAG,IAAI;AAAA,WAAS,EAAE,MAAM,KAAK;AACjD;","names":[]}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
computeTrends,
|
|
4
|
+
deriveTargets
|
|
5
|
+
} from "../chunk-RQLUZ6FQ.js";
|
|
6
|
+
import {
|
|
7
|
+
agentIds,
|
|
8
|
+
buildReceipt,
|
|
9
|
+
collectGuardrails,
|
|
10
|
+
deriveFindings,
|
|
11
|
+
deriveSpans,
|
|
12
|
+
getVersion,
|
|
13
|
+
listSessions,
|
|
14
|
+
loadSession,
|
|
15
|
+
redactReceipt,
|
|
16
|
+
renderCard,
|
|
17
|
+
selectSummary,
|
|
18
|
+
verifyBundle
|
|
19
|
+
} from "../chunk-UHI6BGLE.js";
|
|
20
|
+
|
|
21
|
+
// src/mcp/server.ts
|
|
22
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
23
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
25
|
+
|
|
26
|
+
// src/mcp/tools.ts
|
|
27
|
+
async function listSessionsTool(args) {
|
|
28
|
+
const sessions = await listSessions(args.agent);
|
|
29
|
+
const limit = args.limit && args.limit > 0 ? args.limit : 30;
|
|
30
|
+
return {
|
|
31
|
+
sessions: sessions.slice(0, limit).map((s) => ({
|
|
32
|
+
id: s.id,
|
|
33
|
+
source: s.source,
|
|
34
|
+
title: s.title,
|
|
35
|
+
model: s.model,
|
|
36
|
+
endedAt: s.endedAt
|
|
37
|
+
}))
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function resolve(args) {
|
|
41
|
+
const sessions = await listSessions(args.agent);
|
|
42
|
+
const summary = selectSummary(sessions, args.selector);
|
|
43
|
+
if (!summary) {
|
|
44
|
+
throw new Error(args.selector ? `no session matched "${args.selector}"` : "no sessions found");
|
|
45
|
+
}
|
|
46
|
+
const session = await loadSession(summary);
|
|
47
|
+
if (!session) {
|
|
48
|
+
throw new Error(`could not read session ${summary.id}`);
|
|
49
|
+
}
|
|
50
|
+
return { summary, session };
|
|
51
|
+
}
|
|
52
|
+
async function reportCardTool(args) {
|
|
53
|
+
const { summary, session } = await resolve(args);
|
|
54
|
+
const derived = deriveSpans(session);
|
|
55
|
+
const findings = deriveFindings(derived);
|
|
56
|
+
return { card: renderCard({ summary, derived, findings }, { color: false }) };
|
|
57
|
+
}
|
|
58
|
+
async function getReceiptTool(args) {
|
|
59
|
+
const { session } = await resolve(args);
|
|
60
|
+
const derived = deriveSpans(session);
|
|
61
|
+
const findings = deriveFindings(derived);
|
|
62
|
+
const receipt = await buildReceipt(session, derived, findings);
|
|
63
|
+
return { receipt: args.redact === false ? receipt : redactReceipt(receipt) };
|
|
64
|
+
}
|
|
65
|
+
function verifyReceiptTool(args) {
|
|
66
|
+
return verifyBundle(args.receipt);
|
|
67
|
+
}
|
|
68
|
+
async function guardrailsTool(args) {
|
|
69
|
+
const sessions = await listSessions(args.agent);
|
|
70
|
+
const targets = args.last && args.last > 0 ? sessions.slice(0, args.last) : (() => {
|
|
71
|
+
const one = selectSummary(sessions, args.selector);
|
|
72
|
+
return one ? [one] : [];
|
|
73
|
+
})();
|
|
74
|
+
const findingSets = [];
|
|
75
|
+
for (const summary of targets) {
|
|
76
|
+
const session = await loadSession(summary);
|
|
77
|
+
if (session) {
|
|
78
|
+
findingSets.push(deriveFindings(deriveSpans(session)));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { guardrails: collectGuardrails(findingSets) };
|
|
82
|
+
}
|
|
83
|
+
async function trendsTool(args) {
|
|
84
|
+
const requested = args.last && args.last > 0 ? args.last : args.selector ? 1 : 10;
|
|
85
|
+
const derivations = await deriveTargets(
|
|
86
|
+
args.last && args.last > 0 ? { agent: args.agent, last: args.last } : args.selector ? { agent: args.agent, selector: args.selector } : { agent: args.agent, last: 10 }
|
|
87
|
+
);
|
|
88
|
+
const inputs = derivations.slice().reverse().map((d) => ({ derived: d.derived, findings: d.findings }));
|
|
89
|
+
return { trends: computeTrends(inputs, requested) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/mcp/server.ts
|
|
93
|
+
var GUARD = "Read-only and deterministic \u2014 reports what an agent DID, never whether it was correct (evidence, not judgement).";
|
|
94
|
+
var agentEnum = { type: "string", enum: agentIds() };
|
|
95
|
+
var selector = {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: 'A list index (e.g. "3"), a session id, or part of the title. Omit for the most recent.'
|
|
98
|
+
};
|
|
99
|
+
var TOOLS = [
|
|
100
|
+
{
|
|
101
|
+
name: "list_sessions",
|
|
102
|
+
description: `List recent coding-agent sessions across Claude Code, Codex, and OpenClaw. ${GUARD}`,
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
agent: { ...agentEnum, description: "Limit to one agent." },
|
|
107
|
+
limit: { type: "integer", minimum: 1, description: "Max rows (default 30)." }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "get_report_card",
|
|
113
|
+
description: `Render the deterministic Agent Report Card (grade + findings + evidence) for a session. ${GUARD}`,
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: { selector, agent: agentEnum }
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "get_receipt",
|
|
121
|
+
description: `Build the vendor-neutral Receipt (in-toto Statement) for a session. Redacted by default. ${GUARD}`,
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
selector,
|
|
126
|
+
agent: agentEnum,
|
|
127
|
+
redact: { type: "boolean", description: "Mask secrets (default true)." }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "verify_receipt",
|
|
133
|
+
description: `Structurally verify a Receipt / DSSE envelope / attestation bundle. ${GUARD}`,
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: { receipt: { type: "object", description: "The Receipt or bundle to verify." } },
|
|
137
|
+
required: ["receipt"]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "get_guardrails",
|
|
142
|
+
description: `Paste-ready prevention rules (for AGENTS.md/CLAUDE.md) derived from a session's findings; --last aggregates recent sessions. ${GUARD}`,
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
selector,
|
|
147
|
+
agent: agentEnum,
|
|
148
|
+
last: { type: "integer", minimum: 1, description: "Aggregate the last N sessions." }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "get_trends",
|
|
154
|
+
description: `Cross-session digest: grade movement, the finding kinds that recur most, and evidence totals over the last N sessions (default 10). ${GUARD}`,
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
selector,
|
|
159
|
+
agent: agentEnum,
|
|
160
|
+
last: {
|
|
161
|
+
type: "integer",
|
|
162
|
+
minimum: 1,
|
|
163
|
+
description: "Span the last N sessions (default 10)."
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
];
|
|
169
|
+
var str = (v) => typeof v === "string" ? v : void 0;
|
|
170
|
+
var agentOf = (v) => agentIds().includes(v) ? v : void 0;
|
|
171
|
+
async function dispatch(name, args) {
|
|
172
|
+
switch (name) {
|
|
173
|
+
case "list_sessions":
|
|
174
|
+
return listSessionsTool({
|
|
175
|
+
agent: agentOf(args.agent),
|
|
176
|
+
limit: typeof args.limit === "number" ? args.limit : void 0
|
|
177
|
+
});
|
|
178
|
+
case "get_report_card":
|
|
179
|
+
return reportCardTool({ selector: str(args.selector), agent: agentOf(args.agent) });
|
|
180
|
+
case "get_receipt":
|
|
181
|
+
return getReceiptTool({
|
|
182
|
+
selector: str(args.selector),
|
|
183
|
+
agent: agentOf(args.agent),
|
|
184
|
+
redact: typeof args.redact === "boolean" ? args.redact : void 0
|
|
185
|
+
});
|
|
186
|
+
case "verify_receipt":
|
|
187
|
+
return verifyReceiptTool({ receipt: args.receipt });
|
|
188
|
+
case "get_guardrails":
|
|
189
|
+
return guardrailsTool({
|
|
190
|
+
selector: str(args.selector),
|
|
191
|
+
agent: agentOf(args.agent),
|
|
192
|
+
last: typeof args.last === "number" ? args.last : void 0
|
|
193
|
+
});
|
|
194
|
+
case "get_trends":
|
|
195
|
+
return trendsTool({
|
|
196
|
+
selector: str(args.selector),
|
|
197
|
+
agent: agentOf(args.agent),
|
|
198
|
+
last: typeof args.last === "number" ? args.last : void 0
|
|
199
|
+
});
|
|
200
|
+
default:
|
|
201
|
+
throw new Error(`unknown tool: ${name}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function createServer() {
|
|
205
|
+
const server = new Server(
|
|
206
|
+
{ name: "altimate-receipts", version: getVersion() },
|
|
207
|
+
{ capabilities: { tools: {} } }
|
|
208
|
+
);
|
|
209
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
210
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
211
|
+
const { name, arguments: args } = req.params;
|
|
212
|
+
try {
|
|
213
|
+
const result = await dispatch(name, args ?? {});
|
|
214
|
+
const text = name === "get_report_card" ? result.card : JSON.stringify(result, null, 2);
|
|
215
|
+
return { content: [{ type: "text", text }] };
|
|
216
|
+
} catch (err) {
|
|
217
|
+
return {
|
|
218
|
+
isError: true,
|
|
219
|
+
content: [
|
|
220
|
+
{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }
|
|
221
|
+
]
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return server;
|
|
226
|
+
}
|
|
227
|
+
async function startStdio() {
|
|
228
|
+
const server = createServer();
|
|
229
|
+
await server.connect(new StdioServerTransport());
|
|
230
|
+
}
|
|
231
|
+
export {
|
|
232
|
+
createServer,
|
|
233
|
+
startStdio
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/server.ts","../../src/mcp/tools.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { agentIds } from \"../trace/registry.js\";\nimport type { AgentSource } from \"../trace/types.js\";\nimport { getVersion } from \"../version.js\";\nimport {\n getReceiptTool,\n guardrailsTool,\n listSessionsTool,\n reportCardTool,\n trendsTool,\n verifyReceiptTool,\n} from \"./tools.js\";\n\nconst GUARD =\n \"Read-only and deterministic β reports what an agent DID, never whether it was correct (evidence, not judgement).\";\nconst agentEnum = { type: \"string\", enum: agentIds() } as const;\nconst selector = {\n type: \"string\",\n description:\n 'A list index (e.g. \"3\"), a session id, or part of the title. Omit for the most recent.',\n} as const;\n\nconst TOOLS = [\n {\n name: \"list_sessions\",\n description: `List recent coding-agent sessions across Claude Code, Codex, and OpenClaw. ${GUARD}`,\n inputSchema: {\n type: \"object\",\n properties: {\n agent: { ...agentEnum, description: \"Limit to one agent.\" },\n limit: { type: \"integer\", minimum: 1, description: \"Max rows (default 30).\" },\n },\n },\n },\n {\n name: \"get_report_card\",\n description: `Render the deterministic Agent Report Card (grade + findings + evidence) for a session. ${GUARD}`,\n inputSchema: {\n type: \"object\",\n properties: { selector, agent: agentEnum },\n },\n },\n {\n name: \"get_receipt\",\n description: `Build the vendor-neutral Receipt (in-toto Statement) for a session. Redacted by default. ${GUARD}`,\n inputSchema: {\n type: \"object\",\n properties: {\n selector,\n agent: agentEnum,\n redact: { type: \"boolean\", description: \"Mask secrets (default true).\" },\n },\n },\n },\n {\n name: \"verify_receipt\",\n description: `Structurally verify a Receipt / DSSE envelope / attestation bundle. ${GUARD}`,\n inputSchema: {\n type: \"object\",\n properties: { receipt: { type: \"object\", description: \"The Receipt or bundle to verify.\" } },\n required: [\"receipt\"],\n },\n },\n {\n name: \"get_guardrails\",\n description: `Paste-ready prevention rules (for AGENTS.md/CLAUDE.md) derived from a session's findings; --last aggregates recent sessions. ${GUARD}`,\n inputSchema: {\n type: \"object\",\n properties: {\n selector,\n agent: agentEnum,\n last: { type: \"integer\", minimum: 1, description: \"Aggregate the last N sessions.\" },\n },\n },\n },\n {\n name: \"get_trends\",\n description: `Cross-session digest: grade movement, the finding kinds that recur most, and evidence totals over the last N sessions (default 10). ${GUARD}`,\n inputSchema: {\n type: \"object\",\n properties: {\n selector,\n agent: agentEnum,\n last: {\n type: \"integer\",\n minimum: 1,\n description: \"Span the last N sessions (default 10).\",\n },\n },\n },\n },\n] as const;\n\ntype Args = Record<string, unknown>;\nconst str = (v: unknown): string | undefined => (typeof v === \"string\" ? v : undefined);\nconst agentOf = (v: unknown): AgentSource | undefined =>\n agentIds().includes(v as AgentSource) ? (v as AgentSource) : undefined;\n\nasync function dispatch(name: string, args: Args): Promise<unknown> {\n switch (name) {\n case \"list_sessions\":\n return listSessionsTool({\n agent: agentOf(args.agent),\n limit: typeof args.limit === \"number\" ? args.limit : undefined,\n });\n case \"get_report_card\":\n return reportCardTool({ selector: str(args.selector), agent: agentOf(args.agent) });\n case \"get_receipt\":\n return getReceiptTool({\n selector: str(args.selector),\n agent: agentOf(args.agent),\n redact: typeof args.redact === \"boolean\" ? args.redact : undefined,\n });\n case \"verify_receipt\":\n return verifyReceiptTool({ receipt: args.receipt });\n case \"get_guardrails\":\n return guardrailsTool({\n selector: str(args.selector),\n agent: agentOf(args.agent),\n last: typeof args.last === \"number\" ? args.last : undefined,\n });\n case \"get_trends\":\n return trendsTool({\n selector: str(args.selector),\n agent: agentOf(args.agent),\n last: typeof args.last === \"number\" ? args.last : undefined,\n });\n default:\n throw new Error(`unknown tool: ${name}`);\n }\n}\n\n/** Build the MCP server with the read-only Receipts tools registered. */\nexport function createServer(): Server {\n const server = new Server(\n { name: \"altimate-receipts\", version: getVersion() },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const { name, arguments: args } = req.params;\n try {\n const result = await dispatch(name, (args ?? {}) as Args);\n const text =\n name === \"get_report_card\"\n ? (result as { card: string }).card\n : JSON.stringify(result, null, 2);\n return { content: [{ type: \"text\", text }] };\n } catch (err) {\n return {\n isError: true,\n content: [\n { type: \"text\", text: `Error: ${err instanceof Error ? err.message : String(err)}` },\n ],\n };\n }\n });\n\n return server;\n}\n\n/** Start the server over stdio (used by `receipts mcp`). */\nexport async function startStdio(): Promise<void> {\n const server = createServer();\n await server.connect(new StdioServerTransport());\n}\n","import { deriveFindings } from \"../findings/findings.js\";\nimport { deriveSpans } from \"../findings/spans.js\";\nimport { type Receipt, buildReceipt } from \"../receipt/build.js\";\nimport { renderCard } from \"../report/card.js\";\nimport { collectGuardrails } from \"../report/guardrails.js\";\nimport { deriveTargets } from \"../report/sessions.js\";\nimport { type Trends, computeTrends } from \"../report/trends.js\";\nimport { redactReceipt } from \"../share/redact.js\";\nimport { type VerifyResult, verifyBundle } from \"../sign/verify.js\";\nimport { listSessions, loadSession, selectSummary } from \"../trace/load.js\";\nimport type { AgentSource } from \"../trace/types.js\";\n\n/**\n * Pure MCP tool handlers β no SDK, no transport, no I/O beyond the same engine the\n * CLI uses. Read-only and deterministic; zero model calls. Kept SDK-free so they\n * unit-test directly. Evidence, not judgement.\n */\n\nexport interface SessionRow {\n id: string;\n source: AgentSource;\n title?: string;\n model?: string;\n endedAt?: number;\n}\n\nexport async function listSessionsTool(args: {\n agent?: AgentSource;\n limit?: number;\n}): Promise<{ sessions: SessionRow[] }> {\n const sessions = await listSessions(args.agent);\n const limit = args.limit && args.limit > 0 ? args.limit : 30;\n return {\n sessions: sessions.slice(0, limit).map((s) => ({\n id: s.id,\n source: s.source,\n title: s.title,\n model: s.model,\n endedAt: s.endedAt,\n })),\n };\n}\n\n/** Resolve a selector (+ optional agent) to a fully loaded session, or throw. */\nasync function resolve(args: { selector?: string; agent?: AgentSource }) {\n const sessions = await listSessions(args.agent);\n const summary = selectSummary(sessions, args.selector);\n if (!summary) {\n throw new Error(args.selector ? `no session matched \"${args.selector}\"` : \"no sessions found\");\n }\n const session = await loadSession(summary);\n if (!session) {\n throw new Error(`could not read session ${summary.id}`);\n }\n return { summary, session };\n}\n\nexport async function reportCardTool(args: {\n selector?: string;\n agent?: AgentSource;\n}): Promise<{ card: string }> {\n const { summary, session } = await resolve(args);\n const derived = deriveSpans(session);\n const findings = deriveFindings(derived);\n return { card: renderCard({ summary, derived, findings }, { color: false }) };\n}\n\nexport async function getReceiptTool(args: {\n selector?: string;\n agent?: AgentSource;\n redact?: boolean;\n}): Promise<{ receipt: Receipt }> {\n const { session } = await resolve(args);\n const derived = deriveSpans(session);\n const findings = deriveFindings(derived);\n const receipt = await buildReceipt(session, derived, findings);\n // Default to redacted: an agent/IDE reading the receipt is a sharing surface.\n return { receipt: args.redact === false ? receipt : redactReceipt(receipt) };\n}\n\nexport function verifyReceiptTool(args: { receipt: unknown }): VerifyResult {\n return verifyBundle(args.receipt);\n}\n\nexport async function guardrailsTool(args: {\n selector?: string;\n agent?: AgentSource;\n last?: number;\n}): Promise<{ guardrails: ReturnType<typeof collectGuardrails> }> {\n const sessions = await listSessions(args.agent);\n const targets =\n args.last && args.last > 0\n ? sessions.slice(0, args.last)\n : (() => {\n const one = selectSummary(sessions, args.selector);\n return one ? [one] : [];\n })();\n const findingSets = [];\n for (const summary of targets) {\n const session = await loadSession(summary);\n if (session) {\n findingSets.push(deriveFindings(deriveSpans(session)));\n }\n }\n return { guardrails: collectGuardrails(findingSets) };\n}\n\nexport async function trendsTool(args: {\n agent?: AgentSource;\n last?: number;\n selector?: string;\n}): Promise<{ trends: Trends }> {\n // A trend needs a window: default to the last 10 (selector pins a single session).\n const requested = args.last && args.last > 0 ? args.last : args.selector ? 1 : 10;\n const derivations = await deriveTargets(\n args.last && args.last > 0\n ? { agent: args.agent, last: args.last }\n : args.selector\n ? { agent: args.agent, selector: args.selector }\n : { agent: args.agent, last: 10 },\n );\n const inputs = derivations\n .slice()\n .reverse()\n .map((d) => ({ derived: d.derived, findings: d.findings }));\n return { trends: computeTrends(inputs, requested) };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "altimate-receipts",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Agent-work verification, not code review β a deterministic, cross-agent Report Card of what your coding agent actually did.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"agent-verification",
|
|
8
|
+
"agent-work-verification",
|
|
9
|
+
"coding-agent",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"cursor",
|
|
12
|
+
"copilot",
|
|
13
|
+
"codex",
|
|
14
|
+
"deterministic",
|
|
15
|
+
"audit",
|
|
16
|
+
"provenance",
|
|
17
|
+
"ci",
|
|
18
|
+
"receipts",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"homepage": "https://github.com/AltimateAI/altimate-receipts#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/AltimateAI/altimate-receipts/issues"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/AltimateAI/altimate-receipts.git"
|
|
28
|
+
},
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"author": "altimate.ai",
|
|
31
|
+
"type": "module",
|
|
32
|
+
"bin": {
|
|
33
|
+
"receipts": "dist/cli.js",
|
|
34
|
+
"altimate-receipts": "dist/cli.js"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"schema/agent-execution-receipt-v1.json",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"dev": "tsup --watch",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"check": "biome check .",
|
|
52
|
+
"check:fix": "biome check --write .",
|
|
53
|
+
"prepare": "git config core.hooksPath .githooks 2>/dev/null || true && npm run build",
|
|
54
|
+
"prepublishOnly": "npm run build"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@biomejs/biome": "^1.9.4",
|
|
58
|
+
"@types/node": "^22.19.19",
|
|
59
|
+
"ajv": "^8.20.0",
|
|
60
|
+
"tsup": "^8.3.5",
|
|
61
|
+
"typescript": "^5.7.2",
|
|
62
|
+
"vitest": "^3.2.6"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://receipts.dev/agent-execution/v1/schema.json",
|
|
4
|
+
"title": "Agent Execution Receipt",
|
|
5
|
+
"description": "A deterministic, vendor-neutral record of what a coding agent did in one session, shaped as an in-toto Statement. Evidence, not judgement.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["_type", "subject", "predicateType", "predicate"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"_type": { "const": "https://in-toto.io/Statement/v1" },
|
|
11
|
+
"predicateType": { "const": "https://receipts.dev/agent-execution/v1" },
|
|
12
|
+
"subject": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"minItems": 1,
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"required": ["name", "digest"],
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"properties": {
|
|
20
|
+
"name": { "type": "string", "minLength": 1 },
|
|
21
|
+
"digest": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": ["sha256"],
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": {
|
|
26
|
+
"sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"predicate": { "$ref": "#/$defs/predicate" }
|
|
33
|
+
},
|
|
34
|
+
"$defs": {
|
|
35
|
+
"severity": { "enum": ["critical", "high", "medium", "low"] },
|
|
36
|
+
"grade": { "enum": ["A", "B", "C", "F"] },
|
|
37
|
+
"predicate": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["session", "grade", "evidence", "findings", "generator"],
|
|
40
|
+
"additionalProperties": false,
|
|
41
|
+
"properties": {
|
|
42
|
+
"session": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": ["agent"],
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"properties": {
|
|
47
|
+
"agent": { "type": "string", "minLength": 1 },
|
|
48
|
+
"model": { "type": "string" },
|
|
49
|
+
"title": { "type": "string" },
|
|
50
|
+
"startedAt": { "type": "integer" },
|
|
51
|
+
"endedAt": { "type": "integer" },
|
|
52
|
+
"durationMs": { "type": "integer", "minimum": 0 }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"grade": { "$ref": "#/$defs/grade" },
|
|
56
|
+
"evidence": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"required": [
|
|
59
|
+
"filesChanged",
|
|
60
|
+
"edits",
|
|
61
|
+
"commands",
|
|
62
|
+
"reads",
|
|
63
|
+
"destructiveOps",
|
|
64
|
+
"testsRan",
|
|
65
|
+
"tokens",
|
|
66
|
+
"costUsd",
|
|
67
|
+
"cacheHitRatio"
|
|
68
|
+
],
|
|
69
|
+
"additionalProperties": false,
|
|
70
|
+
"properties": {
|
|
71
|
+
"filesChanged": { "type": "integer", "minimum": 0 },
|
|
72
|
+
"edits": { "type": "integer", "minimum": 0 },
|
|
73
|
+
"commands": { "type": "integer", "minimum": 0 },
|
|
74
|
+
"reads": { "type": "integer", "minimum": 0 },
|
|
75
|
+
"destructiveOps": { "type": "integer", "minimum": 0 },
|
|
76
|
+
"testsRan": { "type": "boolean" },
|
|
77
|
+
"tokens": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"required": ["input", "output", "cacheRead", "cacheWrite", "total"],
|
|
80
|
+
"additionalProperties": false,
|
|
81
|
+
"properties": {
|
|
82
|
+
"input": { "type": "integer", "minimum": 0 },
|
|
83
|
+
"output": { "type": "integer", "minimum": 0 },
|
|
84
|
+
"cacheRead": { "type": "integer", "minimum": 0 },
|
|
85
|
+
"cacheWrite": { "type": "integer", "minimum": 0 },
|
|
86
|
+
"reasoning": {
|
|
87
|
+
"type": "integer",
|
|
88
|
+
"minimum": 0,
|
|
89
|
+
"description": "reasoning/thinking tokens (M41); optional, already included in total"
|
|
90
|
+
},
|
|
91
|
+
"total": { "type": "integer", "minimum": 0 }
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"costUsd": { "type": "number", "minimum": 0 },
|
|
95
|
+
"cacheHitRatio": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
96
|
+
"cacheWriteRatio": {
|
|
97
|
+
"type": "number",
|
|
98
|
+
"minimum": 0,
|
|
99
|
+
"description": "cacheWrite : cacheRead ratio (M42); optional, neutral metric"
|
|
100
|
+
},
|
|
101
|
+
"diffCostUsd": {
|
|
102
|
+
"type": "number",
|
|
103
|
+
"minimum": 0,
|
|
104
|
+
"description": "cost of the turns that edited this diff's files (M48); diff-scoped only"
|
|
105
|
+
},
|
|
106
|
+
"diffTokens": {
|
|
107
|
+
"type": "integer",
|
|
108
|
+
"minimum": 0,
|
|
109
|
+
"description": "tokens of those turns (M48)"
|
|
110
|
+
},
|
|
111
|
+
"diffTurns": {
|
|
112
|
+
"type": "integer",
|
|
113
|
+
"minimum": 0,
|
|
114
|
+
"description": "count of those turns (M48)"
|
|
115
|
+
},
|
|
116
|
+
"commandsByClass": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"description": "count of command spans by descriptive class (M31); optional",
|
|
119
|
+
"additionalProperties": { "type": "integer", "minimum": 0 },
|
|
120
|
+
"propertyNames": {
|
|
121
|
+
"enum": [
|
|
122
|
+
"read-only",
|
|
123
|
+
"mutating",
|
|
124
|
+
"transport",
|
|
125
|
+
"vcs-history",
|
|
126
|
+
"test",
|
|
127
|
+
"build",
|
|
128
|
+
"opaque"
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"testMetrics": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"description": "structured results of the last test run (M28); optional",
|
|
135
|
+
"required": ["runner", "exitStatus"],
|
|
136
|
+
"additionalProperties": false,
|
|
137
|
+
"properties": {
|
|
138
|
+
"runner": { "type": "string" },
|
|
139
|
+
"passed": { "type": "integer", "minimum": 0 },
|
|
140
|
+
"failed": { "type": "integer", "minimum": 0 },
|
|
141
|
+
"skipped": { "type": "integer", "minimum": 0 },
|
|
142
|
+
"total": { "type": "integer", "minimum": 0 },
|
|
143
|
+
"exitStatus": { "enum": ["passed", "failed", "unknown"] },
|
|
144
|
+
"durationMs": { "type": "integer", "minimum": 0 },
|
|
145
|
+
"coveragePct": { "type": "number", "minimum": 0, "maximum": 100 }
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"toolErrorRate": {
|
|
149
|
+
"type": "object",
|
|
150
|
+
"description": "per-tool success-vs-error rollup (M46); keyed by tool name; optional",
|
|
151
|
+
"additionalProperties": {
|
|
152
|
+
"type": "object",
|
|
153
|
+
"required": ["invocations", "errored", "errorRate"],
|
|
154
|
+
"additionalProperties": false,
|
|
155
|
+
"properties": {
|
|
156
|
+
"invocations": { "type": "integer", "minimum": 0 },
|
|
157
|
+
"errored": { "type": "integer", "minimum": 0 },
|
|
158
|
+
"errorRate": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"finishReasons": {
|
|
163
|
+
"type": "object",
|
|
164
|
+
"description": "count of assistant turns by normalized API stop reason (M35); optional",
|
|
165
|
+
"additionalProperties": { "type": "integer", "minimum": 0 },
|
|
166
|
+
"propertyNames": {
|
|
167
|
+
"enum": ["stop", "length", "content_filter", "tool_use", "error", "unknown"]
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"claims": {
|
|
171
|
+
"type": "array",
|
|
172
|
+
"description": "claim ledger (M15): the agent's literal claims paired with PASS/UNVERIFIED status derived from this receipt + the final summary; optional, omitted when none matched",
|
|
173
|
+
"items": {
|
|
174
|
+
"type": "object",
|
|
175
|
+
"required": ["kind", "status", "evidence"],
|
|
176
|
+
"additionalProperties": false,
|
|
177
|
+
"properties": {
|
|
178
|
+
"kind": {
|
|
179
|
+
"enum": ["committed-pushed", "ran-tests"]
|
|
180
|
+
},
|
|
181
|
+
"status": { "enum": ["PASS", "UNVERIFIED"] },
|
|
182
|
+
"evidence": { "type": "string", "minLength": 1 }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"findings": {
|
|
189
|
+
"type": "array",
|
|
190
|
+
"items": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"required": ["id", "severity", "title", "confidence", "score"],
|
|
193
|
+
"additionalProperties": false,
|
|
194
|
+
"properties": {
|
|
195
|
+
"id": { "type": "string", "minLength": 1 },
|
|
196
|
+
"severity": { "$ref": "#/$defs/severity" },
|
|
197
|
+
"title": { "type": "string", "minLength": 1 },
|
|
198
|
+
"detail": { "type": "string" },
|
|
199
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
200
|
+
"score": { "type": "number", "minimum": 0 },
|
|
201
|
+
"impactLabel": { "type": "string" },
|
|
202
|
+
"filePath": { "type": "string" },
|
|
203
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
204
|
+
"evidenceRef": { "type": "string" }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
"transcriptSignature": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "Reserved (L3): a runtime signature over the transcript, if an agent vendor provides one."
|
|
211
|
+
},
|
|
212
|
+
"scope": {
|
|
213
|
+
"type": "object",
|
|
214
|
+
"description": "How this receipt was scoped from the session. Absent β whole session. The basis (diff files / branch) is recorded so a verifier can re-derive the exact scoped receipt from the transcript (L1).",
|
|
215
|
+
"required": ["kind"],
|
|
216
|
+
"additionalProperties": false,
|
|
217
|
+
"properties": {
|
|
218
|
+
"kind": { "enum": ["diff", "branch", "session"] },
|
|
219
|
+
"base": {
|
|
220
|
+
"type": "string",
|
|
221
|
+
"description": "diff: the ref the diff was taken against (e.g. origin/main)."
|
|
222
|
+
},
|
|
223
|
+
"files": {
|
|
224
|
+
"type": "array",
|
|
225
|
+
"description": "diff: the changed-file set, sorted, repo-relative β the scope basis.",
|
|
226
|
+
"items": { "type": "string" }
|
|
227
|
+
},
|
|
228
|
+
"branch": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"description": "branch: the branch the session was sliced to."
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
"generator": {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"required": ["name", "version", "deterministic", "modelCalls"],
|
|
237
|
+
"additionalProperties": false,
|
|
238
|
+
"properties": {
|
|
239
|
+
"name": { "type": "string", "minLength": 1 },
|
|
240
|
+
"version": { "type": "string", "minLength": 1 },
|
|
241
|
+
"deterministic": { "const": true },
|
|
242
|
+
"modelCalls": { "const": 0 }
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|