{"_id":"@applesnort/crosscheck","_rev":"8-353c682a00baca489bc7575d63737241","name":"@applesnort/crosscheck","dist-tags":{"latest":"0.8.0"},"versions":{"0.2.0":{"name":"@applesnort/crosscheck","version":"0.2.0","keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"author":{"name":"Joel Mangin"},"license":"MIT","_id":"@applesnort/crosscheck@0.2.0","maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"homepage":"https://github.com/applesnort/crosscheck#readme","bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"bin":{"crosscheck":"bin/crosscheck.mjs"},"dist":{"shasum":"424b241386dd60f5e547449493f2545d30c59659","tarball":"https://registry.npmjs.org/@applesnort/crosscheck/-/crosscheck-0.2.0.tgz","fileCount":23,"integrity":"sha512-KmcLBSSqASQNr40zf9gz/TpAJfRSfJHiXJT7c6WgY6ZEu7pcOsOFt/tJNGsQcRocXB9hNuptt2447xIVNdByaA==","signatures":[{"sig":"MEUCIFN3JEdw0sg2G6RYViHiK17+crcRBAqDpfXtsa7sIWO5AiEAnLtwfq7vi+LBK0uP49g8xo1MGRL/Mq566aHvDjDZ4P0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":148079},"type":"module","engines":{"node":">=20"},"exports":{".":"./lib/merge.mjs","./run":"./lib/run.mjs","./merge":"./lib/merge.mjs","./parse":"./lib/parse.mjs","./sarif":"./lib/sarif.mjs","./lenses":"./lib/lenses.mjs","./prompt":"./lib/prompt.mjs","./baseline":"./lib/baseline.mjs","./calibrate":"./lib/calibrate.mjs"},"gitHead":"7502a12bb57fb97deb0ad00e233ea7adcc7eb2e9","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"applesnort","email":"applesnort@gmail.com"},"repository":{"url":"git+https://github.com/applesnort/crosscheck.git","type":"git"},"_npmVersion":"11.16.0","description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/crosscheck_0.2.0_1786035803349_0.06533264087951784","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@applesnort/crosscheck","version":"0.2.1","keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"author":{"name":"Joel Mangin"},"license":"MIT","_id":"@applesnort/crosscheck@0.2.1","maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"homepage":"https://github.com/applesnort/crosscheck#readme","bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"bin":{"crosscheck":"bin/crosscheck.mjs"},"dist":{"shasum":"c3d52fb721ff0089e15812cc411e2efebab8a4b9","tarball":"https://registry.npmjs.org/@applesnort/crosscheck/-/crosscheck-0.2.1.tgz","fileCount":24,"integrity":"sha512-+L6Q4L07IBmeumAnq7tc85MQU8CLL9toNZwcCr0hK8nBblPONcrJ/pDbPZ7n0DwB4FRhAsBUAFAZmquUiPrXTQ==","signatures":[{"sig":"MEQCIBnNe8Kunj647oGYSBQrTnCUQF8DJEq/l3FMoMwNEoV6AiBZW3W3m3/So3Xu7Eh8EKXeCdE7dwTqL+vrer018YhSBA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":153892},"type":"module","engines":{"node":">=20"},"exports":{".":"./lib/merge.mjs","./run":"./lib/run.mjs","./merge":"./lib/merge.mjs","./parse":"./lib/parse.mjs","./sarif":"./lib/sarif.mjs","./lenses":"./lib/lenses.mjs","./prompt":"./lib/prompt.mjs","./baseline":"./lib/baseline.mjs","./calibrate":"./lib/calibrate.mjs"},"gitHead":"3ac30e426c2baa3db4056054aa13718146e15a59","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"applesnort","email":"applesnort@gmail.com"},"repository":{"url":"git+https://github.com/applesnort/crosscheck.git","type":"git"},"_npmVersion":"11.16.0","description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/crosscheck_0.2.1_1786036745663_0.09337793467872868","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@applesnort/crosscheck","version":"0.2.2","keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"author":{"name":"Joel Mangin"},"license":"MIT","_id":"@applesnort/crosscheck@0.2.2","maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"homepage":"https://github.com/applesnort/crosscheck#readme","bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"bin":{"crosscheck":"bin/crosscheck.mjs"},"dist":{"shasum":"7b100b0f0780e61d5c1bfca96bd22b50989a05ee","tarball":"https://registry.npmjs.org/@applesnort/crosscheck/-/crosscheck-0.2.2.tgz","fileCount":24,"integrity":"sha512-XJJB/o56nX39i9pBjV0BP1nY46oKqXnnX9MGpSK5NNQ9WVvBnzGR3y8gZvaeC/QOazureF3hx5oY81K+O5Uotg==","signatures":[{"sig":"MEUCIGtRGLWj+vLFCv2JoXB+KbuQKJ9kV69zxDChYJuJXl+PAiEA6LwPOH7GDE3UahsDS1AGInhlEZO3P8FiZhZhgnxHvcY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":156700},"type":"module","engines":{"node":">=20"},"exports":{".":"./lib/merge.mjs","./run":"./lib/run.mjs","./merge":"./lib/merge.mjs","./parse":"./lib/parse.mjs","./sarif":"./lib/sarif.mjs","./config":"./lib/config.mjs","./lenses":"./lib/lenses.mjs","./prompt":"./lib/prompt.mjs","./baseline":"./lib/baseline.mjs","./calibrate":"./lib/calibrate.mjs"},"gitHead":"0112839edb4cc3c1b7d54dc878454b12f3103388","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"applesnort","email":"applesnort@gmail.com"},"repository":{"url":"git+https://github.com/applesnort/crosscheck.git","type":"git"},"_npmVersion":"11.16.0","description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/crosscheck_0.2.2_1786036831289_0.32766344528460545","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@applesnort/crosscheck","version":"0.3.0","keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"author":{"name":"Joel Mangin"},"license":"MIT","_id":"@applesnort/crosscheck@0.3.0","maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"homepage":"https://github.com/applesnort/crosscheck#readme","bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"bin":{"crosscheck":"bin/crosscheck.mjs"},"dist":{"shasum":"689cac872bb9d97e25c342c70ae1e872c6a26f45","tarball":"https://registry.npmjs.org/@applesnort/crosscheck/-/crosscheck-0.3.0.tgz","fileCount":24,"integrity":"sha512-CdGHRhHIUdl044r1Jv3nkDblEdYpE4VhRkphef6Deo3QDr7XbJ7l+zrvfJZGvhPXFySpoD93FrMNC6TfduuA5g==","signatures":[{"sig":"MEUCIQDBAHrT97lFJhuwp0U04ZCqEX8exXKuoycH5X+yhMSpKgIgNj6bTVGEsTM3CSUPFVVmAf5IdeWClZ8kr8u22qZOfkQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":162895},"type":"module","engines":{"node":">=20"},"exports":{".":"./lib/merge.mjs","./run":"./lib/run.mjs","./merge":"./lib/merge.mjs","./parse":"./lib/parse.mjs","./sarif":"./lib/sarif.mjs","./config":"./lib/config.mjs","./lenses":"./lib/lenses.mjs","./prompt":"./lib/prompt.mjs","./baseline":"./lib/baseline.mjs","./calibrate":"./lib/calibrate.mjs"},"gitHead":"2c471a7a76aacb40f7c2829086c58449056fc499","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"applesnort","email":"applesnort@gmail.com"},"repository":{"url":"git+https://github.com/applesnort/crosscheck.git","type":"git"},"_npmVersion":"11.16.0","description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/crosscheck_0.3.0_1786037727024_0.3243963001808299","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@applesnort/crosscheck","version":"0.6.0","keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"author":{"name":"Joel Mangin"},"license":"MIT","_id":"@applesnort/crosscheck@0.6.0","maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"homepage":"https://github.com/applesnort/crosscheck#readme","bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"bin":{"crosscheck":"bin/crosscheck.mjs"},"dist":{"shasum":"d019402d89ec9d80bf46af8d96cf9e1c20295a9c","tarball":"https://registry.npmjs.org/@applesnort/crosscheck/-/crosscheck-0.6.0.tgz","fileCount":27,"integrity":"sha512-zFRgWRGXWkKzT4bJYWXM6Wq0r5KAAL5aT7nM/n39OSG1iekIy5AJvkkePqGMLgU5hOQNmuzxNTWIPU1KPRLpyw==","signatures":[{"sig":"MEYCIQDgHpkcMfJetKXmPCrm5xMQveNz0GKkmu6Vr+J2nsvllQIhAKMJRsjy39+q/iYvs1//uJlgxbOY227yvJ4OgZMZunmU","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":202183},"type":"module","engines":{"node":">=20"},"exports":{".":"./lib/merge.mjs","./run":"./lib/run.mjs","./cache":"./lib/cache.mjs","./merge":"./lib/merge.mjs","./parse":"./lib/parse.mjs","./sarif":"./lib/sarif.mjs","./config":"./lib/config.mjs","./lenses":"./lib/lenses.mjs","./prompt":"./lib/prompt.mjs","./target":"./lib/target.mjs","./comment":"./lib/comment.mjs","./baseline":"./lib/baseline.mjs","./calibrate":"./lib/calibrate.mjs"},"gitHead":"7044098958f56ec2cb01593da285e4bc2d71fb36","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"applesnort","email":"applesnort@gmail.com"},"repository":{"url":"git+https://github.com/applesnort/crosscheck.git","type":"git"},"_npmVersion":"11.16.0","description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/crosscheck_0.6.0_1786106052243_0.8255732315909885","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@applesnort/crosscheck","version":"0.7.0","keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"author":{"name":"Joel Mangin"},"license":"MIT","_id":"@applesnort/crosscheck@0.7.0","maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"homepage":"https://github.com/applesnort/crosscheck#readme","bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"bin":{"crosscheck":"bin/crosscheck.mjs"},"dist":{"shasum":"63d2156c230fbb9f92fea83ff9af508bb98c3f38","tarball":"https://registry.npmjs.org/@applesnort/crosscheck/-/crosscheck-0.7.0.tgz","fileCount":28,"integrity":"sha512-Qzk3qc2QC8SdLml9TgS+58Gm4/cmEQ23wz2UPB4o8Yquj+EVbkN42Q+1vgQ3b83/jGQfYRzEJo3Xhp3Q6s0BHA==","signatures":[{"sig":"MEUCID0wS4SR+CXSytZpqGhVwew5pSYS77FuKQgIVpd5kXmYAiEAvBhrAWjdgIWvqpmPDWjyOC/CbiK4Lor6F7B8+qjBqic=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":206086},"type":"module","engines":{"node":">=20"},"exports":{".":"./lib/merge.mjs","./run":"./lib/run.mjs","./cache":"./lib/cache.mjs","./merge":"./lib/merge.mjs","./parse":"./lib/parse.mjs","./sarif":"./lib/sarif.mjs","./config":"./lib/config.mjs","./lenses":"./lib/lenses.mjs","./prompt":"./lib/prompt.mjs","./target":"./lib/target.mjs","./comment":"./lib/comment.mjs","./baseline":"./lib/baseline.mjs","./calibrate":"./lib/calibrate.mjs"},"gitHead":"59d73a5231ba1e134dc80c9cd403cd94336977e3","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"applesnort","email":"applesnort@gmail.com"},"repository":{"url":"git+https://github.com/applesnort/crosscheck.git","type":"git"},"_npmVersion":"11.17.0","description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","directories":{},"_nodeVersion":"26.4.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/crosscheck_0.7.0_1786640209739_0.5624579164907553","host":"s3://npm-registry-packages-npm-production"}},"0.7.1":{"name":"@applesnort/crosscheck","version":"0.7.1","keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"author":{"name":"Joel Mangin"},"license":"MIT","_id":"@applesnort/crosscheck@0.7.1","maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"homepage":"https://github.com/applesnort/crosscheck#readme","bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"bin":{"crosscheck":"bin/crosscheck.mjs"},"dist":{"shasum":"20983ec4a25b619b94892f1cbb742cc034cfe55e","tarball":"https://registry.npmjs.org/@applesnort/crosscheck/-/crosscheck-0.7.1.tgz","fileCount":28,"integrity":"sha512-UubSapp138fni3ZQ5SqwyHnEqWY6kpYjym3gM1JQ0qiNcAA1uDZTgLnzt+gORwd2uLDmXigKC0g7jzxCB8sWrw==","signatures":[{"sig":"MEUCIAC5NT47+qnV28gAi8bU+W5b8yQ2KteFLHJAksYzPHKhAiEAhSVU/0Ei/Gff7sHYYjsNnla3sGOgshjAgcVz7pqfQ3w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":211378},"type":"module","engines":{"node":">=20"},"exports":{".":"./lib/merge.mjs","./run":"./lib/run.mjs","./cache":"./lib/cache.mjs","./merge":"./lib/merge.mjs","./parse":"./lib/parse.mjs","./sarif":"./lib/sarif.mjs","./config":"./lib/config.mjs","./lenses":"./lib/lenses.mjs","./prompt":"./lib/prompt.mjs","./target":"./lib/target.mjs","./comment":"./lib/comment.mjs","./baseline":"./lib/baseline.mjs","./calibrate":"./lib/calibrate.mjs"},"gitHead":"569b44359ec5c99d0e4f0387ef2b6788d4a1018a","scripts":{"test":"node --test"},"_npmUser":{"name":"applesnort","email":"applesnort@gmail.com"},"repository":{"url":"git+https://github.com/applesnort/crosscheck.git","type":"git"},"_npmVersion":"11.17.0","description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","directories":{},"_nodeVersion":"26.4.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/crosscheck_0.7.1_1787191948134_0.2191076976320112","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@applesnort/crosscheck","version":"0.8.0","description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","license":"MIT","author":{"name":"Joel Mangin"},"type":"module","engines":{"node":">=20"},"bin":{"crosscheck":"bin/crosscheck.mjs"},"exports":{".":"./lib/merge.mjs","./merge":"./lib/merge.mjs","./parse":"./lib/parse.mjs","./sarif":"./lib/sarif.mjs","./baseline":"./lib/baseline.mjs","./lenses":"./lib/lenses.mjs","./calibrate":"./lib/calibrate.mjs","./run":"./lib/run.mjs","./prompt":"./lib/prompt.mjs","./config":"./lib/config.mjs","./target":"./lib/target.mjs","./cache":"./lib/cache.mjs","./comment":"./lib/comment.mjs"},"scripts":{"test":"node --test"},"keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"repository":{"type":"git","url":"git+https://github.com/applesnort/crosscheck.git"},"publishConfig":{"access":"public"},"gitHead":"16ccae5c8b0604cebaaa8586c46543765ecdc61d","_id":"@applesnort/crosscheck@0.8.0","bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"homepage":"https://github.com/applesnort/crosscheck#readme","_nodeVersion":"26.4.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-o/kYBZvjtCTmsuqfcrK4AfJBic7BLvmSMVlTchLDEDa8QiVgKJiLxMoRtd6VnxIDthel0EgdQCI6z795ASBafg==","shasum":"844c6dae5d87d36ae7ec41a22477264e7afb73ac","tarball":"https://registry.npmjs.org/@applesnort/crosscheck/-/crosscheck-0.8.0.tgz","fileCount":32,"unpackedSize":223544,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDcJXBp7pO1rMamneOT7gBN4YEBag0L3OJzb8Q4EimTAwIgb7Nh20SZZZEHUJOXR8zNzqbZqj0GgWF/FSkuvSAcPQ8="}]},"_npmUser":{"name":"applesnort","email":"applesnort@gmail.com"},"directories":{},"maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/crosscheck_0.8.0_1788016355626_0.6150510507934324"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-06T17:03:23.199Z","modified":"2026-08-29T15:12:35.925Z","0.2.0":"2026-08-06T17:03:23.599Z","0.2.1":"2026-08-06T17:19:05.804Z","0.2.2":"2026-08-06T17:20:31.430Z","0.3.0":"2026-08-06T17:35:27.198Z","0.6.0":"2026-08-07T12:34:12.406Z","0.7.0":"2026-08-13T16:56:49.871Z","0.7.1":"2026-08-20T02:12:28.276Z","0.8.0":"2026-08-29T15:12:35.770Z"},"bugs":{"url":"https://github.com/applesnort/crosscheck/issues"},"author":{"name":"Joel Mangin"},"license":"MIT","homepage":"https://github.com/applesnort/crosscheck#readme","keywords":["code-review","sarif","static-analysis","llm","agents","consensus"],"repository":{"type":"git","url":"git+https://github.com/applesnort/crosscheck.git"},"description":"Run independent review lenses in parallel and merge their findings into one deduped, consensus-ranked report — with SARIF output.","maintainers":[{"name":"applesnort","email":"applesnort@gmail.com"}],"readme":"# crosscheck\n\nRun several independent review lenses over the same change in parallel, merge their\nfindings into one deduped report, and emit it as **SARIF** — so LLM review findings\nland in the same places static-analysis findings already do.\n\n```bash\n# run a panel: crosscheck builds the prompts and merges the results,\n# your --exec command supplies the model\nnpx @applesnort/crosscheck run lib/ --exec 'claude -p' --sarif panel.sarif\n\n# or merge output a panel already produced\nnpx @applesnort/crosscheck report --in run.json\n```\n\n**crosscheck never talks to a model itself.** `--exec` names any command that takes\none lens prompt on stdin and returns findings on stdout — `claude -p`, `llm -m ...`,\nor your own wrapper. crosscheck owns prompt construction, routing, fan-out, dedupe,\nand output; you own the model. `--dry-run` prints the roster and prompts without\nspawning anything.\n\nNo dependencies, no install step, 267 tests.\n\n## Getting set up\n\n```bash\nnpx @applesnort/crosscheck init\n```\n\nScaffolds `.crosscheckrc.json`, a `.crosscheck/lenses/` directory with a note on\nwriting lenses, and a `.github/workflows/crosscheck.yml` that reviews each pull\nrequest, uploads SARIF to code scanning, and posts a summary comment. Existing\nfiles are left alone unless you pass `--force`.\n\nSet `exec` to whatever runs your model, then:\n\n```bash\ncrosscheck run --diff --dry-run\n```\n\nNode 20 or newer, on Linux, macOS or Windows. On Windows, prefer setting `exec`\nin `.crosscheckrc.json` rather than passing it on the command line: `--exec` is\nhanded to the shell, and `cmd.exe` does not treat single quotes as quoting, so\nthe POSIX spelling `--exec 'claude -p'` arrives with the quotes still attached.\n\n## In CI\n\nThe scaffolded workflow reviews the diff against the base branch, and does two\nthings with the result:\n\n- **SARIF to code scanning** — per-line annotations in the Files changed view,\n  which is where a reviewer is already looking.\n- **A summary comment** via `--comment-file`, carrying what per-line annotations\n  cannot: which lenses were skipped and why, which died, what the baseline\n  suppressed, and how many findings verification refuted.\n\nThe comment embeds a marker so later runs **edit** it rather than stacking. A pull\nrequest with eleven bot comments gets muted, and a muted reviewer finds nothing.\n\nThe workflow checks out with `fetch-depth: 0`, because reviewing a diff needs the\nbase commit. crosscheck itself never talks to a model — supply whatever your\n`exec` command needs via the workflow's `env`.\n\n## Configuration\n\nRetyping `--exec` on every run pushes people toward shell aliases nobody else on\nthe team can see. Commit a `.crosscheckrc.json` instead:\n\n```json\n{\n  \"exec\": \"claude -p\",\n  \"concurrency\": 2,\n  \"skip\": [\"ux\"],\n  \"sarif\": \"panel.sarif\"\n}\n```\n\nThen the whole command is:\n\n```bash\nnpx @applesnort/crosscheck run src/\n```\n\nThe nearest config is used, searching upward from the working directory and\nstopping at a repo root, so running from a subdirectory still picks up the\nproject's settings. The loaded path is printed on every run — a run silently\nreshaped by a forgotten file is the kind of thing this tool refuses everywhere\nelse. Command-line flags override the file, `--config <file>` points elsewhere,\nand an unrecognised key is an error rather than a silent no-op, because a\nmisspelled `exec` that quietly does nothing is worse than a crash.\n\nAccepted keys: `exec`, `lenses`, `concurrency`, `only`, `skip`, `mixed`, `out`,\n`sarif`, `baseline`, `overlap`, `preflight`, `context`, `verify`, `since`,\n`max-dispatches`, `no-cache`, `cache-dir`, `comment-file`. `exec` accepts a string\nor a per-lens map. Keys beginning `//` are treated as comments.\n\n> **v0.x — the API is unstable.** The CLI commands and the `lib/` exports may\n> change shape before 1.0. Pin an exact version if you depend on it.\n>\n> Published as `@applesnort/crosscheck`; npm rejects the unscoped name as too\n> similar to the (abandoned) `cross-check`. Installed, the command is\n> `crosscheck`.\n\n## Reviewing a change, not a directory\n\nNobody reviews a whole tree — they review a diff. Auditing paths makes cost scale\nwith repository size instead of change size, and re-reads code nobody touched.\n\n```bash\ncrosscheck run --diff             # everything uncommitted\ncrosscheck run --staged           # what you are about to commit\ncrosscheck run --since origin/main   # the branch, for a PR\n```\n\nEach lens is told which lines moved:\n\n```\n- src/a.js  (changed lines: 5-27, 61-84)\n```\n\nRanges are widened by `--context` lines (20 by default) so a lens sees the code\naround a change, and the prompt is explicit that changed lines are the *priority*\nrather than the boundary — a defect elsewhere that the change causes or depends on\nis still worth reporting, while a pre-existing one it never touches is not what the\nreview is for. An empty diff exits cleanly rather than falling back to reviewing\neverything.\n\n## Verification is on by default\n\n`BLOCK` findings are refuted before they are reported. Each one gets a skeptic that\nis handed **the file** rather than the finding's account of it, told to default to\nrefuted when it cannot name a concrete trigger. Refuted findings are removed and\n**the count is always printed, including zero** — a finding that vanished without a\nnumber is indistinguishable from one that was never found.\n\n```\ncrosscheck: verifying 3 finding(s)\n  ✓ confirmed src/session.js:44\n  ✗ refuted   src/cache.js:12\nRefuted in verification: 1.\n```\n\nFalse positives cost more than misses: a panel that cries wolf twice stops being\nread, and its true findings go unread with the rest. `--verify` extends the pass to\nevery severity; `--no-verify` skips it, at that cost.\n\nA verifier that fails to run is **not** treated as agreement — the finding stands\nand the failure is reported.\n\n## Your own gate, before anything is dispatched\n\n```json\n{ \"preflight\": \"scripts/check-clean-worktree.sh\" }\n```\n\nA non-zero exit aborts before a single model call. That lets a project enforce a\nrule crosscheck knows nothing about — data classification, branch policy, a clean\nworktree — without the rule having to exist upstream.\n\n## Cost control\n\nA tool that costs real money per run gets switched off, and a switched-off tool\nfinds nothing.\n\n**A cheap lens should not pay for an expensive model.** `exec` may be a map:\n\n```json\n{\n  \"exec\": {\n    \"default\": \"claude -p\",\n    \"conventions\": \"llm -m claude-haiku-4-5\"\n  }\n}\n```\n\nPrecedence is the lens's own `exec` in its frontmatter, then the map entry, then\n`default`. A rostered lens with no command anywhere fails loudly and names itself\n— it is never quietly skipped.\n\n**Unchanged files are not re-reviewed.** Results are cached under\n`.crosscheck/cache`, keyed on the lens definition, the files, and their contents.\nThe definition is part of the key deliberately: editing a lens must invalidate its\nresults, or you would be served answers from the previous prompt with no way to\ntell. `--no-cache` disables it, `--cache-dir` relocates it. A failed lens is never\ncached, so it is retried rather than permanently wrong.\n\n**`--max-dispatches N` caps the run, and says what it dropped:**\n\n```\ncrosscheck: BUDGET REACHED — 3 lens(es) not run: check, security-check, taint\n```\n\nThe unit is dispatches, not dollars. crosscheck cannot see tokens or cost —\n`--exec` is an arbitrary command — so a monetary budget would be a number invented\nfrom nothing. Truncation is always named, because a run that quietly stopped early\nlooks exactly like a run that found nothing.\n\n## SARIF output\n\nFindings are emitted as [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html),\nthe OASIS interchange format static analyzers already speak. Every other\nmulti-persona review panel emits prose for a human to read once. This one produces\na document GitHub code scanning, editor problem panels, and security dashboards\nalready know how to ingest — none of them needing to know a model wrote it.\n\n- One **rule per lens**, carrying the standards that lens cites, so consumers can\n  filter and configure by lens.\n- `partialFingerprints` for stable identity across runs, independent of severity,\n  fix text, and ordering.\n- Lens attribution and consensus score ride in `properties`, since SARIF has no\n  native concept for either.\n- A lens that died sets `executionSuccessful: false` and emits a\n  `toolExecutionNotification`. The gap is machine-readable, not a line of prose a\n  dashboard will drop.\n\nThat last point is the design rule throughout: **every omission is disclosed.** A\nlens skipped for irrelevance, a lens whose agent died, and a finding refuted during\nverification are three different things and must read differently. A partial panel\npresented as a complete one is worse than no panel.\n\n## Why lenses instead of one review pass\n\nA single review pass optimizes for one kind of defect at a time. Ask for \"problems\"\nand you get whichever category the model reaches for first. Ask several narrow\nspecialists — each told explicitly what it does *not* own — and the union covers\nmore ground, because none of them is trading correctness findings against usability\nfindings inside one context.\n\nThe panel is a foreman, not a reviewer. It resolves the target, decides which lenses\nare relevant, dispatches them, and synthesizes what comes back. The lenses do the\nlooking; the deterministic half — parse, dedupe, score, emit — is code with tests\nrather than prompt instructions. See [`foreman.md`](foreman.md).\n\n## Measuring whether your lenses are redundant\n\nMost implementations stop at \"N agents agreed.\" That over-credits lenses whose\nremits overlap: two lenses looking at the same things agreeing tells you less than\ntwo that do not. So overlap is **measured** from a real run rather than assumed:\n\n```bash\ncrosscheck overlap --in run.json --out overlap.json   # measure it\ncrosscheck report  --in run.json --overlap overlap.json\n```\n\nThis has produced discriminating results in both directions on real data. The same\ntwo lenses — written with deliberately opposed methods, a CWE taxonomy walk and\nsink-first flow tracing — measured **0.45** overlap against 66 OWASP Benchmark\ncases and **1.0** against a 20-case corpus where they returned byte-for-byte\nidentical detections.\n\nAt 1.0, agreement is scored as **one** effective confirmation rather than two,\nbecause that is what it is worth. That is a result you can act on: drop the\nredundant lens, or replace it with one that fails differently.\n\nFindings are ranked by those effective confirmations — 1 for a single lens, and for\na set, 1 plus the summed independence of each distinct pair. **Whether that ranking\npredicts correctness is unproven**; six calibration rounds could not test it,\nbecause the lenses almost never erred. It is kept as a documented hypothesis rather\nthan a validated feature — [the full record is here](fixtures/calibration/PREREGISTERED.md),\nincluding every failed prediction.\n\n### Matching is fuzzy, because real lens output is\n\nTwo lenses never phrase a defect identically, and they anchor it on different lines.\nIn one measured run, three lenses each found the same swallowed error and cited it\nat lines **54, 56, and 57**. An exact-match merge reports that as three findings and\nzero agreement.\n\nSo findings cluster on line proximity (±3) **plus** issue similarity — a Jaccard\nindex over content words, thresholded at `0.12`. Both defaults come from\nmeasurement: same-defect pairs scored `0.161`–`0.538`, while different defects\nsharing a line scored `0.038`–`0.050`. The threshold sits in that gap.\n`test/merge-realdata.test.mjs` pins both bands using verbatim lens output, so they\ncannot drift unnoticed.\n\n## Baselines\n\nOn an existing codebase the first run returns everything already wrong, and the\nreport gets closed unread. Record it, then report only what changed:\n\n```bash\ncrosscheck baseline --in first-run.json --out .crosscheck-baseline.json\ncrosscheck report   --in run.json --baseline .crosscheck-baseline.json\n```\n\nSuppressed counts are always reported, and baseline entries that stopped appearing\nare flagged — either they were fixed, or a lens quietly stopped running. A baseline\nthat hides its own size is just a way to declare a codebase's problems normal.\n\n## Calibration\n\nA review panel is otherwise unfalsifiable: you cannot tell whether it works, whether\na new lens helped, or whether a prompt edit made it worse.\n\n```bash\ncrosscheck calibrate --in run.json --expected fixtures/calibration/expected.json\n```\n\nreports recall, precision, per-lens recall against only the defects that lens owns,\nseverity agreement, and consensus precision beside single-lens precision. It exits\nnon-zero when a planted defect was missed, so it works as a CI gate on the panel\nitself. `lib/corpus.mjs` scores externally authored corpora at case level, and\n`scripts/fetch-corpus.sh` pulls one without vendoring it.\n\nSix rounds have been run and recorded — two self-authored fixtures, an external\ncorpus of 2,740 labeled cases, two model tiers, a purpose-built deception corpus,\nand a controlled prompt ablation. The results, the criteria fixed in advance of each\nround, every failed prediction, and one methodological error that voided a round are\nall in [`fixtures/calibration/PREREGISTERED.md`](fixtures/calibration/PREREGISTERED.md).\n\nThe short version: the lenses were nearly always right, which is why the consensus\nclaim above remains untested. The one false positive across all six rounds was\nproduced by **both** lenses at once — both had to resolve a single opaque helper and\nboth inferred its behaviour from its name. Independence of *method* does not give\nindependence of *failure*.\n\n## What's here\n\n```\nforeman.md              the dispatch / verify / merge / report method\nlenses/\n  architect.md          structure, data shape, reversibility\n  check.md              correctness — boundaries, absent values, error paths\n  security-check.md     OWASP-framed application security\n  taint.md              sink-first data flow to a dangerous operation\n  ux.md                 usability under interruption and extreme states\nlib/\n  parse.mjs             lens text -> findings\n  merge.mjs             normalize, dedupe, consensus scoring\n  sarif.mjs             SARIF 2.1.0 writer\n  baseline.mjs          baseline record / filter / staleness\n  lenses.mjs            frontmatter, glob routing, roster validation\n  calibrate.mjs         score a run against planted defects\n  corpus.mjs            external corpora, case-level scoring\nlib/\n  prompt.mjs            lens prompt construction\n  run.mjs               roster planning and bounded fan-out\n  config.mjs            .crosscheckrc.json discovery and validation\n  target.mjs            diff parsing, changed line ranges\n  cache.mjs             content-addressed result cache\n  comment.mjs           pull-request summary comment\nbin/crosscheck.mjs      CLI: init | run | lenses | report | sarif |\n                             baseline | overlap | calibrate\nfixtures/calibration/   planted defects, ground truth, and the calibration record\nfixtures/deception/     20 modules that look safe and are not, or the reverse\nPROVENANCE.md           where all of this came from\n```\n\nEvery lens shares one output contract, so the merge needs no per-lens parsing:\n\n```\nfile:line — SEVERITY — issue — fix\n```\n\n`SEVERITY` is `BLOCK`, `FIX`, or `CONSIDER`; other vocabularies normalize onto it. A\nlens with nothing to say returns exactly `NO FINDINGS` — deliberately distinct from\na lens that failed to run. Lines that do not match the contract are reported as\nunparsed rather than discarded, because a lens that starts narrating instead of\nreporting should not look like a clean one.\n\nRouting is declared in each lens's frontmatter (`when`, `owns`, `not-owns`), so a\nroster validates before any agent is dispatched. `not-owns` is required: a lens that\nnever declines dilutes the signal everything else depends on.\n\n> **Note:** `fixtures/deception/src/` contains deliberately exploitable code and\n> helpers that behave differently from what their names promise. It exists to test\n> reviewers. Do not copy any of it into real software.\n\n## Using it\n\nThe lenses are plain markdown prompts — nothing is tied to a particular agent\nframework. Any harness that can run N prompts concurrently and collect their text\ncan drive this; feed the results in as `[{\"lens\": \"check\", \"output\": \"...\"}]`, with\n`null` for a lens that died.\n\n`crosscheck run` does this for you, bounded by `--concurrency`, and writes the raw\nlens text with `--out` so a run can be rescored later without paying the model\nagain. Routing comes from each lens's `when` globs; `--only` and `--skip` override\nit, and every skip is reported with its reason.\n\nOne property worth preserving if you build your own dispatcher: **dispatch out of\nband.** A parallel fan-out that renders inline floods the session you are working\nin and has to be killed to recover it.\n\n```bash\nnpm test   # 183 tests, no dependencies\n```\n\n## Adding your own lenses\n\nLens sources layer, in increasing precedence:\n\n1. the lenses packaged with crosscheck\n2. `./lenses` or `./.crosscheck/lenses` in your project, if present\n3. anything named by `--lenses dir,dir` or the config's `lenses` key\n\nA later source **adds** to the earlier ones. A lens whose `name` matches an\nearlier one **overrides** it, and the override is printed — so customising the\nstock `check` costs one file rather than forking all five and losing upstream\nchanges. `--no-builtin` drops the packaged set entirely.\n\n```bash\nmkdir -p .crosscheck/lenses\n$EDITOR .crosscheck/lenses/chaos.md\nnpx @applesnort/crosscheck lenses     # what resolved, and from where\n```\n\nA lens is a markdown file: five frontmatter keys, then the prompt.\n\n```markdown\n---\nname: chaos\nsummary: adversarial user trying to break the flow\nwhen: [**/*.{js,jsx,tsx,vue}]\nowns: states reachable by misuse — double-submit, back button, hostile input\nnot-owns: correctness, security categories, architecture\n---\n\n# Lens: chaos\n\nYou are trying to break this, not review it. ...\n\nFindings only: `file:line — SEVERITY — issue — fix`. SEVERITY is BLOCK, FIX, or\nCONSIDER. If nothing here can be broken, reply exactly `NO FINDINGS`.\n```\n\n`when` routes it — a lens whose globs match nothing in the target is skipped, with\nthe reason printed. `not-owns` is required: a lens that never declines dilutes the\nsignal everything else depends on. The body should end by restating the output\ncontract, since that is what the parser expects back.\n\n`crosscheck lenses` prints the resolved set with each lens's origin and globs,\nwhich is the fastest way to see why something did or did not run.\n\n### Local lenses stay local\n\nNothing in `./lenses` or `./.crosscheck/lenses` is packaged, uploaded, or shared\nby crosscheck. Those files are read off your disk at dispatch time and go nowhere\nelse. That has two practical consequences worth knowing before you write any.\n\n**A local lens can be as specific as you like.** The packaged lenses are\ndeliberately generic, which also makes them shallow: they cannot know your\nstorage conventions, your framework's failure modes, the mistake your team makes\nevery quarter, or the review standard one colleague applies better than anyone\nelse. A lens that encodes any of that will outperform a generic one on your\ncodebase, and it belongs in your repo rather than upstream. The useful ones\nusually aren't portable.\n\n**Distribution is where obligations start, and you are not distributing.** If you\nadapt a lens from a published persona set, a standards document, or a colleague's\nreview checklist, keeping it local puts you in the same position as any private\nnote-taking. Those materials often carry licences requiring attribution — the UK\ngovernment's accessibility personas are published under the Open Government\nLicence, for one — and that condition attaches when you *publish* a derivative,\nnot when you run one. If a local lens later becomes something you want to share,\nthat is the moment to check what it derives from and credit it.\n\nThe corollary: a lens holding a named individual's review preferences, or a\ndescription of an unreleased product, is a local lens permanently. Publishing it\nshares something about a person or a project, not just a prompt.\n\n## Writing a good lens\n\nA lens earns its place by finding what the others miss. Give it a remit narrow\nenough that it declines most changes, state what it does *not* own, and make it name\nthe concrete trigger for every finding. Then measure it: add defects it should catch\nto the calibration fixture and check whether recall actually moved, and check the\noverlap figure to see whether it is telling you anything the existing lenses were\nnot.\n\nIf a lens encodes your product's domain, your storage conventions, or a particular\nreviewer's standards, keep it in your own project. The ones here are deliberately\ngeneric; the useful ones usually aren't.\n\n## Prior art\n\nMultiple critic personas reviewing code is not a new idea and is not claimed as one.\nClaude Code ships a parallel multi-agent code review; community multi-agent review\npanels exist; aggregating several analyzers and weighting their agreement is\nlong-standing practice, formalized in SARIF.\n\nWhat is offered here is narrower: LLM review lenses as a **SARIF producer**, with\nlens redundancy measured rather than assumed, and a calibration harness that reports\nwhat it cannot establish as readily as what it can. See\n[`PROVENANCE.md`](PROVENANCE.md).\n\n## License\n\nMIT — see [`LICENSE`](LICENSE).\n","readmeFilename":"README.md"}