@openthink/stamp 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 OpenThinkAi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,429 @@
1
+ # stamp-cli
2
+
3
+ Local, headless pull-request system for agent-to-agent code review workflows.
4
+
5
+ An author-agent opens a diff, reviewer-agents consume it and return structured
6
+ feedback, the author iterates until merge rules are satisfied, and the merge
7
+ is pushed to a remote that cryptographically rejects any push that wasn't
8
+ properly reviewed and signed.
9
+
10
+ **Not a GitHub replacement.** GitHub is for humans collaborating. stamp-cli
11
+ is for agent fleets cycling fast while keeping `main` clean. No web UI, no
12
+ PR dashboard, no human comment threads in core. Just a CLI + a git hook.
13
+
14
+ Part of the [OpenThink](https://openthink.dev) suite.
15
+
16
+ **Docs:** [server quickstart](./docs/quickstart-server.md) (from-zero project on a stamp server) · [DESIGN](./DESIGN.md) (spec) · [ROADMAP](./docs/ROADMAP.md) (what's shipped + what's next) · [personas](./docs/personas.md) (writing reviewer prompts) · [troubleshooting](./docs/troubleshooting.md) · [server](./server/README.md) (Railway deploy)
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ npm install -g @openthink/stamp
22
+ ```
23
+
24
+ Node 22.5+ required (we use `node:sqlite` and `node:crypto`'s Ed25519 APIs,
25
+ both of which are built-in but gated on 22.5+).
26
+
27
+ Published tarballs carry an SLSA build attestation via npm's Trusted
28
+ Publishing, so you can verify chain-of-custody against the GitHub Actions
29
+ run that produced the release:
30
+
31
+ ```sh
32
+ npm audit signatures
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ **Pick your deployment shape first** — it determines which command you run and
38
+ what guarantees you actually get:
39
+
40
+ | Shape | Origin is… | Enforcement | Command to run |
41
+ |---|---|---|---|
42
+ | **Server-gated** (recommended) | A stamp server you deployed | The server's pre-receive hook rejects any push without a valid stamped merge | `stamp bootstrap` on a clone of a server-provisioned repo |
43
+ | **Local-only** (advisory) | GitHub / GitLab / etc. directly | None — direct `git push origin main` succeeds; the stamp config is documentation + a discipline aid | `stamp init --mode local-only` |
44
+
45
+ These are not interchangeable. Server-gated is the only shape where the gate
46
+ is actually enforced; local-only signs your merges with a verifiable
47
+ attestation but the remote does not reject anything. Pick deliberately.
48
+
49
+ ### Server-gated path
50
+
51
+ If you've deployed the stamp server (see [`docs/quickstart-server.md`](./docs/quickstart-server.md)
52
+ for the full Railway walkthrough), the from-zero flow is three commands:
53
+
54
+ ```sh
55
+ ssh stamp new-stamp-repo myproject # provision bare repo + hook
56
+ git clone ssh://stamp/srv/git/myproject.git
57
+ cd myproject && stamp bootstrap # install real reviewers + push
58
+ ```
59
+
60
+ `stamp bootstrap` detects the freshly-provisioned placeholder state, scaffolds
61
+ the three starter reviewers (security, standards, product), and lands them on
62
+ `main` via a single signed merge that the server hook accepts. From there it's
63
+ the normal review/merge cycle.
64
+
65
+ ### Local-only path
66
+
67
+ When origin is a public forge directly (GitHub etc.), `stamp init` defaults to
68
+ local-only and prints a prominent warning that the gate is unenforced. Pass
69
+ `--mode local-only` to acknowledge explicitly and silence the warning:
70
+
71
+ ```sh
72
+ cd myproject
73
+ stamp init --mode local-only # scaffolds .stamp/ + AGENTS.md (advisory mode)
74
+ git add .stamp AGENTS.md && git commit -m "stamp: advisory config"
75
+ git push origin main
76
+ ```
77
+
78
+ You can still run `stamp review` / `stamp merge` / `stamp verify` against this
79
+ repo — the merge commits carry signed attestations and `stamp verify <sha>`
80
+ validates them on any clone. What you don't get: server-side rejection of
81
+ unstamped pushes. Anyone with repo write access can `git push origin main`
82
+ of any commit, stamped or not.
83
+
84
+ ### Local-test (no server, on-disk bare repo)
85
+
86
+ Run everything on one machine using a bare git repo on disk as the "remote".
87
+ Useful for learning the shape before deploying a real server.
88
+
89
+ ```sh
90
+ # Install + initialize a fresh project
91
+ mkdir myproject && cd myproject
92
+ git init -b main
93
+ stamp init # scaffolds .stamp/ with three starter reviewers
94
+ # (security, standards, product) + keypair in
95
+ # ~/.stamp/keys/. Use --minimal to scaffold a
96
+ # single placeholder reviewer instead.
97
+
98
+ # Commit the scaffolded .stamp/ directory so the reviewers + trusted key are
99
+ # part of the repo's history before you start working.
100
+ git add .stamp && git commit -m "stamp: scaffold starter reviewers"
101
+
102
+ # Provision a bare "remote" with the verify hook
103
+ cd ..
104
+ ./node_modules/@openthink/stamp/scripts/setup-repo.sh \
105
+ /tmp/myproject.git \
106
+ ./node_modules/@openthink/stamp/dist/hooks/pre-receive.cjs \
107
+ ~/.stamp/keys/ed25519.pub
108
+
109
+ # Wire up the remote
110
+ cd myproject
111
+ git remote add origin /tmp/myproject.git
112
+
113
+ # Work:
114
+ git checkout -b feature
115
+ # ...make changes, commit...
116
+ stamp review --diff main..feature # three reviewers run in parallel
117
+ stamp status --diff main..feature # gate status; exit 0 if open, 1 if closed
118
+ git checkout main
119
+ stamp merge feature --into main # signed merge commit
120
+ stamp push main # hook verifies; main advances on remote
121
+ ```
122
+
123
+ The scaffolded reviewer prompts are generic starting points. Before relying
124
+ on them for real code review, edit them to match your project's stack,
125
+ conventions, and domain — see [`docs/personas.md`](./docs/personas.md).
126
+
127
+ Any push that isn't a properly signed stamped merge will be rejected by the
128
+ hook with a clear reason.
129
+
130
+ ## Concepts
131
+
132
+ - **Reviewer** — a persona defined by a prompt file at
133
+ `.stamp/reviewers/<name>.md`. `stamp init` scaffolds three starter
134
+ reviewers (`security`, `standards`, `product`) calibrated for generic
135
+ TS/JS projects — edit them to fit your codebase. Use `--minimal` for
136
+ a single placeholder instead. Prompts must end with a `VERDICT:
137
+ approved|changes_requested|denied` line.
138
+ - **Verdict** — a reviewer's judgment on a specific diff. Recorded per
139
+ reviewer per `(base_sha, head_sha)` in `.git/stamp/state.db`.
140
+ - **Gate** — for each required reviewer, the latest verdict must be
141
+ `approved`. Config lives at `.stamp/config.yml`.
142
+ - **Attestation** — `stamp merge` signs a payload (base/head/target/approvals)
143
+ with Ed25519 and attaches it as `Stamp-Payload` + `Stamp-Verified`
144
+ commit-message trailers on the merge commit.
145
+ - **Hook** — `stamp-verify` runs server-side on pre-receive. Reads config
146
+ and trusted keys from the target branch's tree at push time, re-runs
147
+ every check the client ran.
148
+
149
+ See [`DESIGN.md`](./DESIGN.md) for the full spec and [`docs/ROADMAP.md`](./docs/ROADMAP.md) for current status + upcoming work.
150
+
151
+ ## Commands
152
+
153
+ **Core review cycle:**
154
+
155
+ ```
156
+ stamp init [--mode <shape>] # scaffold .stamp/ + keypair; idempotent. Also ensures
157
+ # AGENTS.md at repo root with deployment-shape-aware
158
+ # guidance. --mode is server-gated|local-only;
159
+ # auto-detected from origin if omitted (forge-direct
160
+ # origins default to local-only with a loud warning).
161
+ stamp bootstrap # one-shot: replace placeholder example reviewer
162
+ # with real reviewers on a fresh server-provisioned
163
+ # repo. See `stamp bootstrap --help`.
164
+ stamp review --diff <revspec> # run all configured reviewers in parallel
165
+ stamp review --diff <revspec> --only <name> # run a single reviewer
166
+ stamp status --diff <revspec> # gate check; exit 0 if open, 1 if closed
167
+ stamp merge <branch> --into <target> # run required_checks → sign merge commit
168
+ stamp push <target> # plain git push; hook stderr forwarded
169
+ stamp verify <sha> # verify a merge commit's attestation locally
170
+ ```
171
+
172
+ **Browsing history:**
173
+
174
+ ```
175
+ stamp log # first-parent commit list w/ attestation summary
176
+ stamp log <sha> # drill-down: decoded attestation + review prose
177
+ stamp log --branch <name> # filter by branch
178
+ stamp log --reviews # raw DB-row view of every review invocation
179
+ stamp ui # interactive TUI: list → detail → review prose
180
+ ```
181
+
182
+ **Managing reviewers (persona development):**
183
+
184
+ ```
185
+ stamp reviewers list # configured reviewers + prompt file status
186
+ stamp reviewers add <name> [--no-edit] # scaffold + register; --no-edit skips $EDITOR
187
+ stamp reviewers edit <name> # open existing reviewer's prompt
188
+ stamp reviewers test <name> --diff <revspec> # invoke reviewer w/o recording to DB
189
+ stamp reviewers show <name> [--limit <n>] # verdict history + stats for calibration
190
+ stamp reviewers remove <name> [--delete-file] # de-register; optional rm of .md
191
+ stamp reviewers fetch <name> --from <source@ref> # install + pin from canonical source
192
+ # add --expect-prompt-sha <hex> (or --expect-tools-sha / --expect-mcp-sha)
193
+ # to anchor first-fetch trust against an out-of-band published manifest
194
+ stamp reviewers verify [<name>] # check prompt/tool/mcp against lock; exit 3 on drift
195
+ ```
196
+
197
+ **Key management:**
198
+
199
+ ```
200
+ stamp keys generate # create ~/.stamp/keys/ed25519{,.pub}
201
+ stamp keys list # local + trusted keys in this repo
202
+ stamp keys export # print your public key
203
+ stamp keys trust <pub-file> # deposit a key into .stamp/trusted-keys/
204
+ ```
205
+
206
+ **Maintenance:**
207
+
208
+ ```
209
+ stamp update # upgrade stamp to the latest npm release
210
+ ```
211
+
212
+ ## Configuration
213
+
214
+ `.stamp/config.yml`:
215
+
216
+ ```yaml
217
+ branches:
218
+ main:
219
+ required: [security, standards, product] # reviewers that must approve
220
+ required_checks: # mechanical checks run pre-merge
221
+ - name: build
222
+ run: npm run build
223
+ - name: typecheck
224
+ run: npx tsc --noEmit
225
+ develop:
226
+ required: [security]
227
+
228
+ reviewers:
229
+ security: { prompt: .stamp/reviewers/security.md }
230
+ standards: { prompt: .stamp/reviewers/standards.md }
231
+ product: { prompt: .stamp/reviewers/product.md }
232
+ ```
233
+
234
+ Reviewer names are arbitrary — pick whatever matches your team's review
235
+ dimensions. The prompt file is the reviewer's full system prompt; the only
236
+ contract is that it must end with a `VERDICT:` line. See
237
+ [`docs/personas.md`](./docs/personas.md) for how to write good reviewer prompts.
238
+
239
+ `required_checks` run on the **post-merge tree** before the commit is signed.
240
+ Any non-zero exit blocks the merge and rolls it back. Results are attested
241
+ into the commit's signed payload; the server hook verifies that attestation
242
+ matches the committed config.
243
+
244
+ > **Security note.** `required_checks[].run` values execute as shell commands
245
+ > on the merger's machine via `spawnSync(cmd, { shell: true })`. Anyone who
246
+ > can land a PR that touches `.stamp/config.yml` can introduce arbitrary
247
+ > code that will run on the next person to call `stamp merge`. The mitigation
248
+ > is the reviewer gate itself: `.stamp/config.yml` changes go through the
249
+ > same reviewers as any other code change, and your security reviewer prompt
250
+ > should treat `required_checks` edits as high-scrutiny. Unlike GitHub
251
+ > Actions, these commands are **not** sandboxed. See
252
+ > [`DESIGN.md`](./DESIGN.md#security-model) for the full threat model.
253
+
254
+ Optional: `.stamp/mirror.yml` enables GitHub mirroring via the post-receive
255
+ hook. See [`server/README.md`](./server/README.md).
256
+
257
+ ## Deployment shapes
258
+
259
+ Three ways to run stamp-cli in a real setting, trading setup cost for
260
+ enforcement strength. Pick based on whether your remote can run a
261
+ pre-receive hook — GitHub can't, so the choice matters.
262
+
263
+ **1. Self-hosted remote — full enforcement (recommended).**
264
+ A server you control runs `git + sshd` with stamp-cli's pre-receive
265
+ hook installed. Every push is rejected at the server if it isn't a
266
+ properly signed stamped merge, so author-agents can't bypass the gate
267
+ even with working credentials.
268
+ Easiest path: deploy `server/Dockerfile` to Railway, Fly, or any
269
+ container host — see [`server/README.md`](./server/README.md) for the
270
+ Railway walkthrough. Minimalist alternative: any Linux host with `git +
271
+ sshd + Node 22.5+` — create a bare repo, drop
272
+ `dist/hooks/pre-receive.cjs` into `hooks/pre-receive` (chmod +x), done.
273
+ The hook is self-contained.
274
+
275
+ **2. Self-hosted remote + GitHub mirror — full enforcement + GitHub ecosystem.**
276
+ Run the stamp server as source-of-truth; commit `.stamp/mirror.yml` to
277
+ mirror verified commits to a GitHub repo via the post-receive hook.
278
+ Deploy pipelines (Actions, Vercel, Netlify) integrate with the GitHub
279
+ copy. GitHub branch protection restricts pushes on the mirror to the
280
+ bot identity, so the only way anything lands on GitHub's `main` is via
281
+ a verified push through your stamp server. Humans can still fork/PR on
282
+ GitHub, but those PRs can't merge. See
283
+ [`server/README.md`](./server/README.md)'s GitHub mirror section.
284
+
285
+ **3. Local-only — weakest enforcement, lowest setup.**
286
+ Skip the server entirely. stamp-cli still produces signed merge commits
287
+ locally, and `stamp verify <sha>` validates them anywhere — but anyone
288
+ with push access to your remote (e.g. GitHub `main` without branch
289
+ protection, or with protection that doesn't require the hook) can
290
+ bypass the gate. You get the attestation audit trail without running
291
+ infrastructure. Suitable for solo use or small trusted teams.
292
+
293
+ ## For agent authors
294
+
295
+ stamp-cli is designed for agents as the primary user. What that means in
296
+ practice:
297
+
298
+ - **Output is prose**, not JSON. LLMs read prose natively. No `--json` flag.
299
+ - **Control flow is exit codes.** Agent loops branch on them.
300
+ - **State is files.** `.stamp/config.yml`, `.git/stamp/state.db` (chmoded
301
+ `0600`; parent `.git/stamp/` chmoded `0700`), git commit trailers. Easy
302
+ to inspect, hard to lose. To bound retention on long-lived repos, run
303
+ `stamp prune --older-than 30d` (use `--dry-run` first to preview).
304
+ - **Operations are idempotent.** `stamp init` is safe to re-run. `stamp
305
+ review` accumulates history; re-invoking doesn't corrupt anything.
306
+
307
+ The canonical unattended loop:
308
+
309
+ ```sh
310
+ while :; do
311
+ stamp review --diff main..$BRANCH
312
+ if stamp status --diff main..$BRANCH; then
313
+ git checkout main
314
+ stamp merge "$BRANCH" --into main
315
+ stamp push main
316
+ break
317
+ fi
318
+ # author-agent reads review output, patches code, commits, loops
319
+ done
320
+ ```
321
+
322
+ **Exit-code cheat sheet:**
323
+
324
+ | Command | 0 | non-zero (check stderr to disambiguate) |
325
+ |---|---|---|
326
+ | `stamp review` | reviewers ran and recorded | invocation failed (reviewer crash, DB error) — verdict may or may not be approved; always follow with `stamp status` to check the gate |
327
+ | `stamp status` | gate open (all required reviewers approved) | gate closed — at least one required reviewer missing or non-approved |
328
+ | `stamp merge` | merge signed, on main | stderr says which case: `gate CLOSED:` (need reviews), `pre-merge checks failed:` (merge rolled back, need fix), or a git-merge conflict message (working tree needs resolution) |
329
+ | `stamp push` | remote accepted | stderr has `remote: stamp-verify: rejecting ...` for hook rejections, or a standard git error for network/auth issues |
330
+ | `stamp verify` | attestation valid | stderr names the specific verification step that failed (signature invalid, untrusted signer, SHA mismatch, missing check, etc.) |
331
+
332
+ Distinct exit codes per failure mode are on the roadmap — for now, agents should regex on the stderr markers above to disambiguate.
333
+
334
+ **Tuning reviewer prompts.** Use `stamp reviewers test <name> --diff <revspec>`
335
+ when iterating on a prompt — it invokes the reviewer against a diff **without
336
+ recording to the DB**, so you can tweak the prompt, retest, and not pollute
337
+ history. Pattern:
338
+
339
+ ```sh
340
+ $EDITOR .stamp/reviewers/my-reviewer.md
341
+ stamp reviewers test my-reviewer --diff main..test-violations
342
+ # read output, adjust prompt, repeat
343
+ ```
344
+
345
+ See [`docs/personas.md`](./docs/personas.md) for reviewer-prompt guidance and
346
+ [`docs/troubleshooting.md`](./docs/troubleshooting.md) for common failures
347
+ with concrete fixes.
348
+
349
+ ## Data flow / privacy
350
+
351
+ stamp-cli runs reviewers by sending the diff to Anthropic. Operators
352
+ working with sensitive content should know the data-flow contract before
353
+ running their first `stamp review`.
354
+
355
+ **What gets sent to Anthropic on every `stamp review`:**
356
+
357
+ - The full unified diff between `base_sha` and `head_sha`, including all
358
+ added and removed lines, comments, fixtures, and any strings or
359
+ credentials present in the changeset.
360
+ - The reviewer's prompt file (read from the merge-base tree).
361
+ - The configured tool allowlist + MCP server names for that reviewer.
362
+
363
+ **What stays local:**
364
+
365
+ - Reviewer prose, verdicts, and tool-call traces are persisted to
366
+ `.git/stamp/state.db` (a sqlite file under the repo's git common
367
+ dir; per-machine, not committed, not pushed). The DB is chmoded
368
+ `0600` and its parent directory `0700` on every open so peer users
369
+ on shared/dev machines can't read review prose. To bound retention
370
+ — long-lived repos accumulate every review's verbatim model output
371
+ indefinitely — use `stamp prune --older-than <duration>`
372
+ (e.g. `stamp prune --older-than 30d`; `--dry-run` previews without
373
+ deleting).
374
+ - Your Ed25519 signing key (`~/.stamp/keys/`) never leaves your machine.
375
+
376
+ **What gets attached to the merge commit and mirrored to GitHub:**
377
+
378
+ - The signed `Stamp-Payload` trailer carrying approvals, base/head
379
+ SHAs, signer key fingerprint, and a tool-call audit trace (tool
380
+ names + input hashes — not the diff content itself).
381
+
382
+ **Disclosure on first run.** The first `stamp review` in a repo prints
383
+ a short note (to stderr) pointing at this section. The notice is
384
+ recorded under `.git/stamp/llm-notice-shown` and not repeated. To
385
+ suppress it unconditionally — agent loops, CI workers, environments
386
+ where the disclosure has already been baked into team docs — set
387
+ `STAMP_SUPPRESS_LLM_NOTICE=1`.
388
+
389
+ **Anthropic's data handling.** Reviewer calls go through the Claude
390
+ Agent SDK, which inherits whatever auth + retention posture you have
391
+ configured for Claude Code on your machine (Anthropic API key, Zero
392
+ Data Retention contract, etc.). See Anthropic's
393
+ [privacy policy](https://www.anthropic.com/privacy) and
394
+ [usage policy](https://www.anthropic.com/legal/aup) for the
395
+ authoritative terms; configure accordingly before running stamp on
396
+ content you're not free to share.
397
+
398
+ ## Security model
399
+
400
+ **What this protects against.** Author-agents cannot merge unreviewed code,
401
+ cannot forge merges (the signing key isn't on disk anywhere they can
402
+ exfiltrate without the operator's explicit consent), and cannot bypass the
403
+ remote's verification.
404
+
405
+ **What this doesn't protect against.** You, the human holding the signing
406
+ key, can still produce a valid signed merge for arbitrary content. That's
407
+ inherent to any local-first system. What signing gives you is
408
+ **non-repudiation** — every merge on `main` is permanently attributed to a
409
+ specific key's owner, provable from git history alone. For the
410
+ agent-can't-bypass threat model this is exactly right.
411
+
412
+ See [`DESIGN.md`](./DESIGN.md) for the full bootstrap, key-management, and
413
+ verification-rule details, including the security model around user-configured
414
+ pre-merge checks.
415
+
416
+ ## License
417
+
418
+ stamp-cli itself is MIT-licensed.
419
+
420
+ **Third-party dependency notice.** stamp-cli invokes reviewers via
421
+ [`@anthropic-ai/claude-agent-sdk`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk),
422
+ which is distributed under Anthropic's proprietary license ("SEE LICENSE IN
423
+ README.md" on the package). Running `stamp review` — or installing stamp-cli
424
+ via npm at all — binds you to Anthropic's terms of service for API usage and
425
+ their SDK's license. Review those before integrating stamp-cli into
426
+ distributed products.
427
+
428
+ All other runtime dependencies are permissively licensed (MIT, ISC, BSD,
429
+ Apache-2.0).