@humanbased/crosscheck 1.3.0-beta.90 → 1.3.0-beta.92

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/docs/trust.md ADDED
@@ -0,0 +1,159 @@
1
+ # Trust: what Crosscheck reaches, sends, and changes
2
+
3
+ Crosscheck runs on your machine, holds your GitHub token, clones your code, and pushes commits. This document says exactly what that means, so you can decide whether to run it without reading the source.
4
+
5
+ Everything below is behaviour of the current release. Where a default is risky, it is called out as a default rather than buried.
6
+
7
+ **The short version.** Crosscheck has no server. There is no Crosscheck-operated endpoint, no telemetry, no account, no phone-home, and no analytics — install it and nothing is reported anywhere. It talks to GitHub with your token, and it runs the Claude Code and Codex CLIs on your machine under *their* credentials and *their* privacy terms. On a default `watch` setup, one third party you may not expect is in the path: the webhook tunnel. See [What leaves your machine](#what-leaves-your-machine).
8
+
9
+ ---
10
+
11
+ ## What leaves your machine
12
+
13
+ Five destinations, and nothing else.
14
+
15
+ | Destination | What goes there | When | Avoidable? |
16
+ |---|---|---|---|
17
+ | `api.github.com`, `github.com` | Your token; PR reads; git clone/fetch; the comments, commit statuses, commits, branches and PRs Crosscheck writes | Every command | No — this is the product |
18
+ | Claude Code CLI → Anthropic | The reviewer prompt, plus whatever the agent reads from the clone (it is allowed `git diff` and `git log`) — so in practice your diff and relevant source | Review, fix, recheck, conflict-resolve steps | Yes — disable the vendor in config |
19
+ | Codex CLI → OpenAI | Same, for Codex-assigned steps | Same | Yes — disable the vendor in config |
20
+ | Tunnel relay — `localhost.run` (default) or `smee.io` | GitHub's webhook deliveries for your monitored repos: PR metadata, author, branch, head sha, and PR body as GitHub sends it | `crosscheck watch` only | Yes — see below |
21
+ | `api.linear.app` | Issue reads (enrichment) and verdict comments | Only when Linear is configured; off by default | Yes — leave it off |
22
+
23
+ Two things to be precise about, because they are the parts people get wrong:
24
+
25
+ **Crosscheck does not upload your code. The vendor CLIs do.** Crosscheck clones your repo to a temp directory and runs `claude` or `codex` as a subprocess pointed at that directory. Your diff reaches Anthropic or OpenAI through those CLIs, under the credentials you already logged into them with and under their data-handling terms — the same exposure you have when you run `claude` in your own checkout. Crosscheck adds no separate upload path. If a repo cannot go to a model vendor, Crosscheck is not the tool that changes that.
26
+
27
+ **The webhook tunnel is a third party in the path.** `crosscheck watch` needs a public URL for GitHub to POST to, and by default it opens an SSH tunnel through `localhost.run`. GitHub's webhook payloads for your monitored repos therefore transit a relay operated by someone else. Deliveries are HMAC-signed and Crosscheck rejects anything whose signature does not verify (`webhook_sig_invalid`), so a relay cannot *forge* events — but it can *see* payloads, which include PR titles, bodies, branch names and author logins. Payloads carry no source diff and no token.
28
+
29
+ To avoid the relay entirely, don't run `watch`. Everything Crosscheck does is available as a one-shot command that needs no inbound connection:
30
+
31
+ ```bash
32
+ crosscheck run https://github.com/acme/web/pull/245
33
+ ```
34
+
35
+ Run that from cron, from CI, or by hand. Or use `tunnel.backend: smee` with your own [smee.io](https://smee.io/new) channel if you prefer a named relay that queues events while you are offline — same trade, different operator. Self-hosting the relay is not supported today.
36
+
37
+ ---
38
+
39
+ ## GitHub permissions
40
+
41
+ Crosscheck uses whatever token `gh auth login` already produced — it does not mint its own, and it never asks you to paste one into a config file.
42
+
43
+ | Scope | Needed for | Required? |
44
+ |---|---|---|
45
+ | `repo` | Clone, fetch, push; read PRs; post comments; write the `crosscheck/review` commit status | Yes |
46
+ | `admin:repo_hook` | Register the webhook on individual repos | `watch` with repo-level scopes only |
47
+ | `admin:org_hook` (`write:org`) | Register one webhook per org instead of per repo | `watch` with org-level scopes only |
48
+
49
+ `repo` is a coarse scope, and it is coarse because git push over HTTPS needs it. If that is more than you want to grant, the narrow path is a fine-grained PAT limited to the specific repositories you intend to review, exported as `GITHUB_TOKEN`. Crosscheck reads the token from `gh`'s keyring first and falls back to `GITHUB_TOKEN` / `GH_TOKEN`, so a scoped PAT in your shell is a drop-in.
50
+
51
+ Without the hook scopes, `watch` still runs — it just cannot register its own webhook, and you point one at the tunnel URL yourself.
52
+
53
+ ---
54
+
55
+ ## Webhooks: registration and removal
56
+
57
+ `crosscheck watch` registers hooks at startup and removes them at shutdown.
58
+
59
+ **Registered:** one hook per monitored org (preferred) or per monitored repo, `active: true`, events `pull_request` and `issue_comment` only, `content_type: json`, pointed at your tunnel URL, with an HMAC secret. If a Crosscheck hook already exists, the existing one is reused and its event list patched rather than a second one created — restarts do not accumulate hooks.
60
+
61
+ **Removed:** on `Ctrl-C` or `SIGTERM`, `watch` deletes every hook it registered in that session before exiting.
62
+
63
+ **The gap you should know about:** an abnormal exit — uncaught exception, `SIGKILL`, power loss — skips that cleanup, and the hook stays registered and pointed at a tunnel URL that no longer answers. GitHub will retry deliveries, fail, and eventually show the hook as failing. Nothing leaks (the endpoint is gone, and the payload was going to your machine), but the hook is orphaned. Two ways to clear it:
64
+
65
+ - Start `watch` again. It finds the existing hook and reuses it, which also repoints it at the new tunnel URL.
66
+ - Delete it by hand: repo or org **Settings → Webhooks**, remove the entry whose payload URL is your old tunnel.
67
+
68
+ Note that with `tunnel.backend: smee`, the channel URL is stable and hooks are *meant* to outlive the session — that is the point of using smee. They persist until you delete them.
69
+
70
+ ---
71
+
72
+ ## What Crosscheck can change
73
+
74
+ The workflow is a sequence of steps, and each step's mutations are listed below. What runs is `~/.crosscheck/workflow.yml`, narrowable per repo — so this table is also the menu of what you can switch off.
75
+
76
+ | Step | What it writes to your repo |
77
+ |---|---|
78
+ | **review** | One PR comment (the review, with a hidden `<!-- crosscheck: ... -->` annotation). One commit status, context `crosscheck/review`: `pending` while running, then `success` or `failure`. Optionally one Linear comment. **No code.** |
79
+ | **recheck** | Same as review — a second comment, and release of the pending status. **No code.** |
80
+ | **fix** | Code. Commits to the PR's own branch and pushes there, plus a "fix applied" comment. If that push cannot land (protected branch, deleted branch, fork), it pushes the same commit to `fix/cr-<pr>-review-issues`, opens a follow-up PR targeting the original branch, and labels it `cr-autofix`. In `delivery.mode: comment` it pushes nothing and posts the diff as a suggestion instead. |
81
+ | **conflict-resolve** | Code. Merges the base branch into the PR branch, resolves conflicts, and pushes the merge commit to the PR branch, plus a comment. Skipped for fork PRs. |
82
+
83
+ Boundaries that hold across every step:
84
+
85
+ - **Never merges.** No command merges a pull request — there is no code path from any verdict to a merge. `crosscheck scan` will tell you a PR looks merge-ready; a human or your own automation acts on that.
86
+ - **Never touches your base branch.** Pushes go to the PR's head branch or to a new `fix/cr-*` branch. `main` and `staging` are only ever read.
87
+ - **Never force-pushes over your work.** A rejected non-fast-forward push is retried by rebasing onto the remote branch; if that fails it gives up and falls back to the follow-up PR.
88
+ - **Never edits outside the clone.** All work happens in a `mkdtemp` directory that is deleted when the step finishes, pass or fail.
89
+ - **Repo review guidance is read from the base branch,** not from the PR — a PR cannot rewrite the rules it will be reviewed under.
90
+ - **Automation triggers are authenticated.** The comment bridge that advances a workflow only fires for annotations posted by the token's own account; a comment from anyone else carrying the same hidden marker is ignored and logged as `annotation_injection_blocked`.
91
+
92
+ One boundary is *not* Crosscheck's to enforce, and you should know which: **what the reviewer agent may execute inside the clone is the vendor CLI's decision, not Crosscheck's.** Crosscheck itself never runs your project's code — no install, no test, no build. For Claude steps it passes an explicit tool allowlist of `Bash(git diff)` and `Bash(git log)`, so a review is confined to reading history. For Codex steps it invokes `codex exec` and passes no sandbox override, so the agent operates under Codex's own default policy for non-interactive runs — which necessarily permits writing files in the clone, since that is how a Codex fix lands. If your threat model requires a specific sandbox for agent execution, set it in the Codex CLI's own configuration; Crosscheck will not override it.
93
+
94
+ ---
95
+
96
+ ## Trying it on one PR first
97
+
98
+ Fully read-only. Clones the repo, runs the review, prints the comment it *would* post, and exits. No comment, no commit status, no Linear write, no lock:
99
+
100
+ ```bash
101
+ crosscheck run https://github.com/acme/web/pull/245 --dry-run
102
+ ```
103
+
104
+ When you want the comment but still no code changes:
105
+
106
+ ```bash
107
+ crosscheck run https://github.com/acme/web/pull/245 --review-only
108
+ ```
109
+
110
+ Neither command needs a webhook, a tunnel, or `watch`. There is also a [public fixture PR](./fixture-pr.md) if you would rather not point a first run at your own code.
111
+
112
+ ---
113
+
114
+ ## Turning off auto-fix
115
+
116
+ Three levels, from narrowest to broadest.
117
+
118
+ **One repo, permanently** — review and recheck only, no code ever pushed to it:
119
+
120
+ ```bash
121
+ crosscheck alter acme/legacy-service --review-only
122
+ ```
123
+
124
+ The override lands in `~/.crosscheck/workflows/acme__legacy-service.yml` and is read per PR event, so it takes effect without restarting `watch`. `crosscheck alter acme/legacy-service --show` prints what is in force.
125
+
126
+ **Everywhere, but keep the suggestions** — fixes are posted as a diff in a comment instead of pushed:
127
+
128
+ ```yaml
129
+ post_review:
130
+ auto_fix:
131
+ delivery:
132
+ mode: comment
133
+ ```
134
+
135
+ **Everywhere, entirely** — remove the `fix` and `recheck` steps from `~/.crosscheck/workflow.yml`, or narrow each repo with `crosscheck alter <repo> --steps review`.
136
+
137
+ ---
138
+
139
+ ## Secrets and local logs
140
+
141
+ **Secrets are never written to config.** `crosscheck.config.yml` stores environment variable *names* (`api_key_env: LINEAR_API_KEY`), never values. The GitHub token comes from `gh`'s keyring or your environment at call time. The webhook HMAC secret is read from your environment.
142
+
143
+ **The one place a token is embedded is the clone URL** (`https://x-access-token:<token>@github.com/...`), which is how git authenticates over HTTPS. Every log and error path that could carry that URL runs it through a redactor first, so it appears as `x-access-token:[REDACTED]`. Use `clone_protocol: ssh` if you would rather it never be constructed.
144
+
145
+ **Logs are local files, and that is the only place they go.** `~/.crosscheck/logs/<date>.ndjson`, one JSON object per line, enabled by default (`logs.enabled`), pruned by age (`logs.retention_days`, default 30). They power `crosscheck diagnose`, `optimize`, and `impact` — all of which read these files and make no network calls.
146
+
147
+ What a standard log line contains: repo and PR number, event name, verdict, vendor and model, token counts, durations, commit shas, error category. What it deliberately does not contain: **source code, diffs, prompts, review text, PR titles or bodies, or author logins.**
148
+
149
+ Those richer fields exist behind `logs.extended.enabled`, which is **off by default and not exposed by any CLI flag or by `onboard`** — it can only be turned on by hand-editing config, and every line it writes is tagged `"_extended": true` so it can be filtered or scrubbed separately. Turning it on is a deliberate act; nothing in the product nudges you toward it.
150
+
151
+ To keep no local history at all, set `logs.enabled: false`. `diagnose`, `optimize` and `impact` will have nothing to read, which is the whole trade.
152
+
153
+ Other state under `~/.crosscheck/`: your config, per-repo workflow overrides, and small caches of already-seen commit shas and diff hashes (used to avoid re-reviewing the same code). Shas and hashes, no content.
154
+
155
+ ---
156
+
157
+ ## Reporting a security issue
158
+
159
+ Email <yi@inductive.network> rather than opening a public issue.
package/get-started.md CHANGED
@@ -26,6 +26,7 @@
26
26
  - [diagnose](#crosscheck-diagnose)
27
27
  - [optimize](#crosscheck-optimize)
28
28
  - [impact](#crosscheck-impact)
29
+ - [adoption](#crosscheck-adoption)
29
30
  - [issue](#crosscheck-issue)
30
31
  - [Configuration](#configuration)
31
32
  - [How it works](#how-it-works)
@@ -235,6 +236,8 @@ crosscheck review https://github.com/owner/repo/pull/123 --reviewer codex
235
236
 
236
237
  If this step fails, fix the specific auth, clone, reviewer, or comment-posting error before enabling `watch`.
237
238
 
239
+ If you'd rather nothing be written to the PR on a first run, use `crosscheck run <pr-url> --dry-run` instead: it clones, reviews, and prints the comment it would post without touching GitHub. (`--dry-run` lives on `run`, `fix`, `recheck`, and `resolve` — not on `review`, which always posts.) [docs/trust.md](./docs/trust.md) covers what leaves your machine at each step, which permissions each command needs, and exactly what `watch` can change.
240
+
238
241
  ## Step 3 — Choose a deployment mode
239
242
 
240
243
  After one-shot review works, run the guided setup:
@@ -978,6 +981,65 @@ crosscheck impact (all time · 47 reviews)
978
981
  | `--json` | Output the full report as JSON |
979
982
  | `-c, --config <path>` | Config file path |
980
983
 
984
+ ---
985
+
986
+ ### `crosscheck adoption`
987
+
988
+ Reports whether crosscheck is actually being used, and how long a PR waits for a verdict. Where `impact` prices reviews that happened, `adoption` measures activation and reach. Reads from `~/.crosscheck/logs/` — no network calls, nothing transmitted.
989
+
990
+ ```bash
991
+ crosscheck adoption
992
+ crosscheck adoption --since 2026-07-01
993
+ crosscheck adoption --json
994
+ ```
995
+
996
+ ```
997
+ crosscheck adoption (2026-06-15 → 2026-07-14)
998
+
999
+ Activation
1000
+ ──────────────────────────────────────────────
1001
+ onboard started 3
1002
+ onboard completed 2
1003
+ abandoned 1
1004
+
1005
+ Usage
1006
+ ──────────────────────────────────────────────
1007
+ reviews started 51
1008
+ reviews completed 47
1009
+ rechecks completed 12
1010
+ blocking findings posted 19
1011
+ fixes applied 11
1012
+ active repos 6
1013
+
1014
+ Weekly active repos
1015
+ ──────────────────────────────────────────────
1016
+ 2026-06-22 ████████ 3 repos 14 reviews
1017
+ 2026-06-29 ████████████ 4 repos 18 reviews
1018
+ 2026-07-06 ████████████████ 6 repos 15 reviews
1019
+
1020
+ PR open → verdict
1021
+ ──────────────────────────────────────────────
1022
+ median 22m
1023
+ p90 1.8h
1024
+ slowest 2d
1025
+ measured on 41 verdicts
1026
+ ⓘ 6 verdicts had no PR open time in the event — excluded
1027
+
1028
+ First-run failures (sessions that never reached a verdict)
1029
+ ──────────────────────────────────────────────
1030
+ auth 2
1031
+
1032
+ ⓘ derived from local logs only (30d retention) — nothing is transmitted. See docs/metrics.md
1033
+ ```
1034
+
1035
+ | Flag | Description |
1036
+ |---|---|
1037
+ | `--since <YYYY-MM-DD>` | Limit the analysis to logs from this date onward |
1038
+ | `--json` | Output the full report as JSON |
1039
+ | `-c, --config <path>` | Config file path |
1040
+
1041
+ Every field these numbers are derived from is inventoried in **[docs/metrics.md](./docs/metrics.md)**, including what is deliberately never written to disk.
1042
+
981
1043
  The monetary estimate formula: `(hours_saved × hourly_rate_usd) + (issues_caught × defect_cost_usd)`. Defaults: `$150/hr`, `$150/issue`. Both configurable in `crosscheck.config.yml` under `impact`.
982
1044
 
983
1045
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanbased/crosscheck",
3
- "version": "1.3.0-beta.90",
3
+ "version": "1.3.0-beta.92",
4
4
  "description": "AI code review pipeline that turns agent-written PRs into merge-ready patches",
5
5
  "bin": {
6
6
  "crosscheck": "dist/cli.js",