repo-contract 0.3.2 → 0.4.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +123 -968
  3. package/bin/package-json-patch.d.mts +9 -0
  4. package/bin/package-json-patch.mjs +221 -0
  5. package/bin/preset-catalog.d.mts +39 -0
  6. package/bin/preset-catalog.mjs +71 -0
  7. package/bin/repo-contract.mjs +222 -0
  8. package/bin/templates.d.mts +5 -0
  9. package/bin/templates.mjs +74 -0
  10. package/dist/.dts/evidence/build-evidence.d.ts.map +1 -1
  11. package/dist/.dts/helpers/exception-policy.d.ts +199 -0
  12. package/dist/.dts/helpers/exception-policy.d.ts.map +1 -0
  13. package/dist/.dts/helpers/index.d.ts +34 -0
  14. package/dist/.dts/helpers/index.d.ts.map +1 -0
  15. package/dist/.dts/helpers/load-exception-registry.d.ts +41 -0
  16. package/dist/.dts/helpers/load-exception-registry.d.ts.map +1 -0
  17. package/dist/.dts/helpers/reconcile-exceptions.d.ts +91 -0
  18. package/dist/.dts/helpers/reconcile-exceptions.d.ts.map +1 -0
  19. package/dist/.dts/helpers/write-exception-registry.d.ts +45 -0
  20. package/dist/.dts/helpers/write-exception-registry.d.ts.map +1 -0
  21. package/dist/.dts/parsing/parse-output.d.ts.map +1 -1
  22. package/dist/helpers.cjs +306 -0
  23. package/dist/helpers.cjs.map +1 -0
  24. package/dist/helpers.d.cts +1 -0
  25. package/dist/helpers.d.ts +1 -0
  26. package/dist/helpers.js +296 -0
  27. package/dist/helpers.js.map +1 -0
  28. package/dist/index.cjs +1 -7
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +1 -7
  31. package/dist/index.js.map +1 -1
  32. package/helpers/package.json +5 -0
  33. package/package.json +25 -3
  34. package/src/helpers/exception-policy.ts +421 -0
  35. package/src/helpers/index.ts +57 -0
  36. package/src/helpers/load-exception-registry.ts +136 -0
  37. package/src/helpers/reconcile-exceptions.ts +171 -0
  38. package/src/helpers/write-exception-registry.ts +136 -0
package/README.md CHANGED
@@ -2,1062 +2,217 @@
2
2
 
3
3
  [![CI](https://github.com/MaverickCER/repo-contract/actions/workflows/ci.yml/badge.svg)](https://github.com/MaverickCER/repo-contract/actions/workflows/ci.yml)
4
4
  [![npm version](https://img.shields.io/npm/v/repo-contract.svg)](https://www.npmjs.com/package/repo-contract)
5
+ [![Node](https://img.shields.io/node/v/repo-contract.svg)](https://www.npmjs.com/package/repo-contract)
5
6
  [![License](https://img.shields.io/npm/l/repo-contract.svg)](LICENSE)
6
- [![Coverage](https://img.shields.io/badge/coverage-%E2%89%A585%25-brightgreen)](scripts/coverage-thresholds.mjs)
7
- [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](tsconfig.json)
8
- [![Node](https://img.shields.io/node/v/repo-contract.svg)](package.json)
9
- [![Socket Badge](https://badge.socket.dev/npm/package/repo-contract/latest)](https://badge.socket.dev/npm/package/repo-contract/latest)
10
7
 
11
- **Turn your repository's engineering standards into enforceable contracts.**
8
+ **Define your engineering standards once. Share them across repositories. Get actionable rationale for every outcome.**
12
9
 
13
- repo-contract is a tool-agnostic contract execution and evidence layer. You define the engineering standards your repository cares about — tests, coverage, mutation testing, linting, security scanning, documentation, dependency health, or anything else that runs as a command — and repo-contract turns those standards into enforceable, machine-readable contracts.
10
+ repo-contract is a TypeScript library that turns the engineering rules scattered across CI, scripts, configuration, and documentation into a single contract you can version, review, and share. It runs the tools you already use, evaluates their results against your standards, and gives actionable information for every outcome. You replace none of your existing tools; your CI, scripts, and development workflow stay intact.
14
11
 
15
- Each check executes a command, captures what actually happened as evidence, and hands that evidence to a policy function **you write**. The policy decides whether the evidence satisfies your repository's standard:
12
+ [See it run](#see-it-run) · [Quick Start](#quick-start) · [Across repositories](#from-one-repo-to-a-whole-org)
16
13
 
17
- ```ts
18
- { outcome: "pass" | "fail" | "warn", rationale: string }
19
- ```
20
-
21
- repo-contract aggregates every policy result into one verdict.
22
-
23
- It does not decide what "good code" means.
24
-
25
- **Your repository does.**
26
-
27
- This makes repo-contract different from a test runner, linter, CI provider, or quality analyzer. It does not replace ESLint, Vitest, Stryker, npm audit, or similar tools. It executes them, captures their results, and gives your repository a programmable enforcement layer across all of them.
28
-
29
- Because it is a plain function call with no CLI and no hidden state, the same contract can run locally and in CI. Wire it into a `precommit`, `prepublishOnly`, CI job, or whatever workflow your repository already uses.
30
-
31
- The result is not merely "the tests passed."
32
-
33
- It is a repository-defined engineering contract with evidence explaining why.
34
-
35
- This README is a narrative walkthrough. For the precise reference — every exported type, field, and error code — see the generated [API report](docs/api-report/repo-contract.api.md) (and its [presets counterpart](docs/api-report/repo-contract-presets.api.md) for `repo-contract/presets`), produced straight from source by [API Extractor](https://api-extractor.com/) so it can never drift from what the package actually exports.
36
-
37
- **When this isn't worth adopting:** if your CI already runs each tool as its own separate step with its own separate pass/fail gate, and you do not need unified evidence or policies that reason across checks, plain shell scripts in your CI configuration may be all you need. repo-contract earns its keep when you want a single programmatic contract across multiple engineering standards, evidence you can persist or diff over time, or policies that reason about more than one check's output.
38
-
39
- ## Why contracts?
40
-
41
- Modern repositories accumulate engineering standards faster than they accumulate enforcement.
42
-
43
- A team may agree that:
44
-
45
- - tests must pass;
46
- - coverage must remain above a threshold;
47
- - mutation scores must remain above a threshold;
48
- - dependencies must have no known high-severity vulnerabilities;
49
- - generated files must remain synchronized;
50
- - public APIs must remain compatible;
51
- - documentation must accompany changes;
52
- - new code must satisfy architectural boundaries.
53
-
54
- Those standards are often scattered across CI YAML, package scripts, documentation, code review conventions, and institutional knowledge.
55
-
56
- repo-contract gives those standards a single executable boundary.
57
-
58
- ```text
59
- repository standards
60
- |
61
- v
62
- repo-contract
63
- |
64
- +--> execute checks
65
- |
66
- +--> collect evidence
67
- |
68
- +--> interpret evidence with repository-owned policies
69
- |
70
- v
71
- enforceable verdict
72
- ```
73
-
74
- The important distinction is that repo-contract does not own the standards.
75
-
76
- Your repository does.
77
-
78
- ## Installation
79
-
80
- ```sh
81
- npm install --save-dev repo-contract
82
- ```
83
-
84
- Requires Node.js `>=20.0.0`.
85
-
86
- While repo-contract is pre-1.0, pin a tilde range (`"repo-contract": "~0.1.0"`): a `0.x` **minor** bump can carry a breaking change to the Stable tier (see [VERSIONING.md](VERSIONING.md)), so read the [CHANGELOG](CHANGELOG.md)'s breaking-changes notes on every minor upgrade, not just majors.
87
-
88
- `yaml` is an optional peer dependency, needed only if a check requests `output: { format: "yaml" }`:
89
-
90
- ```sh
91
- npm install --save-dev yaml
92
- ```
93
-
94
- ### Runtime support matrix
95
-
96
- repo-contract's checks run as spawned processes and read the ambient environment — but repo-contract never imports a process-spawning implementation or reads `process.env` itself (see [Supplying `spawn`/`env`](#supplying-spawnenv) below); it is server/CLI-only by design regardless, not an isomorphic/browser package, since the capability a consumer supplies is itself always a real Node-only spawning mechanism.
97
-
98
- | Environment | Supported |
99
- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
100
- | Node.js `>=20.0.0` (macOS, Linux, Windows) | Yes |
101
- | Bun (latest release) | Yes — tested in CI against the real published package shape. No non-default permissions needed. |
102
- | Deno (latest release) | Yes — tested in CI against the real published package shape. Requires `--allow-read --allow-run --allow-env` (see [ADR 0003](specs/decisions/0003-cross-platform-command-execution-and-process-cleanup.md)). |
103
- | Browser | No — this package executes local processes |
104
-
105
- See [ADR 0003](specs/decisions/0003-cross-platform-command-execution-and-process-cleanup.md) for what "tested" covers here and why.
106
-
107
- ### Accessibility
108
-
109
- repo-contract has no user interface. It produces machine-readable `Evidence`/`Verdict` objects and typed errors; any rendering — a terminal summary, a CI annotation, a dashboard — is the consumer's surface, and WCAG / accessibility conformance applies there, not here.
110
-
111
- ## Quick start
112
-
113
- Define your repository's standards as checks:
114
-
115
- ```ts
116
- // repo-contract.config.ts
117
- import { spawn } from "node:child_process"
118
- import { defineRepoContract } from "repo-contract"
119
-
120
- export default defineRepoContract({
121
- // repo-contract never spawns a process or reads process.env itself -- see
122
- // "Supplying spawn/env" below for why, and what to pass on Windows.
123
- spawn,
124
- env: process.env,
125
- checks: {
126
- tests: {
127
- run: "npm test",
128
-
129
- policy: ({ result }) =>
130
- result.exitCode === 0
131
- ? {
132
- outcome: "pass",
133
- rationale: "Tests exited 0.",
134
- }
135
- : {
136
- outcome: "fail",
137
- rationale: "Tests must pass.",
138
- },
139
- },
140
-
141
- mutation: {
142
- run: "npm run mutation",
143
- output: { format: "json" },
144
-
145
- policy: ({ result }) => {
146
- if (!result.output?.success) {
147
- return {
148
- outcome: "fail",
149
- rationale: "Mutation report was not valid JSON.",
150
- }
151
- }
152
-
153
- const score = (result.output.value as { mutationScore: number }).mutationScore
154
-
155
- return score >= 90
156
- ? {
157
- outcome: "pass",
158
- rationale: `Mutation score was ${score}%.`,
159
- }
160
- : {
161
- outcome: "fail",
162
- rationale: `Mutation score must be at least 90% (got ${score}%).`,
163
- }
164
- },
165
- },
166
- },
167
- })
168
- ```
169
-
170
- Execute the contract:
171
-
172
- ```ts
173
- import { runRepoContract } from "repo-contract"
174
- import config from "./repo-contract.config.js"
175
-
176
- const { evidence, verdict } = await runRepoContract(config)
177
-
178
- console.log(verdict.passed)
179
- console.log(verdict.checks.mutation)
180
-
181
- process.exitCode = verdict.passed ? 0 : 1
182
- ```
183
-
184
- `runRepoContract()` never calls `process.exit()` itself. Your integration decides what to do with the result.
185
-
186
- There is no CLI, no config-file discovery magic, and no hidden state.
187
-
188
- ### Supplying `spawn`/`env`
189
-
190
- `spawn` and `env` are required fields on the config — repo-contract never imports a process-spawning implementation or reads `process.env` internally (see [ADR 0011](specs/decisions/0011-process-spawning-and-ambient-environment-access-are-consumer-supplied-capabilities-not-package-owned.md)). You supply both, as trusted capabilities repo-contract calls with a resolved command/argv/options — it does not inspect, wrap, or sanitize them.
191
-
192
- **macOS/Linux** — plain `node:child_process` is enough:
193
-
194
- ```ts
195
- import { spawn } from "node:child_process"
196
-
197
- export default defineRepoContract({
198
- spawn,
199
- env: process.env,
200
- checks: {/* ... */},
201
- })
202
- ```
203
-
204
- **Windows** — most npm-installed CLI tools (`eslint`, `prettier`, `tsc`, …) resolve to `.cmd` shims, and plain `node:child_process.spawn` refuses to run those at all without `shell: true` (Node's own CVE-2024-27980 mitigation). Install [`cross-spawn`](https://www.npmjs.com/package/cross-spawn) and pass it instead — it resolves `.cmd`/`.bat` shims and quotes arguments safely for `cmd.exe`, **without** turning on shell metacharacter interpretation:
205
-
206
- ```ts
207
- import crossSpawn, { sync as crossSpawnSync } from "cross-spawn"
208
-
209
- export default defineRepoContract({
210
- spawn: crossSpawn,
211
- env: process.env,
212
- // Optional: lets a timed-out/aborted/Ctrl+C-killed check's full process
213
- // tree (not just its immediate process) get cleaned up on Windows too.
214
- killProcessTree: crossSpawnSync,
215
- checks: {/* ... */},
216
- })
217
- ```
218
-
219
- **`cross-spawn` does not mean "shell execution."** These are two independent choices:
220
-
221
- | `Spawner` choice | `shell` option | Result |
222
- | ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
223
- | native `spawn` | `false` (default) | argv-only, no shell interpretation |
224
- | `cross-spawn` | `false` (default) | Windows `.cmd`/`.bat` resolution + safe `cmd.exe` quoting, still argv-only |
225
- | either | `true` | shell metacharacters (`&&`, `\|`, …) interpreted — a per-check or global opt-in, see [`shell`](docs/api-report/repo-contract.api.md) |
226
-
227
- Passing `cross-spawn` fixes Windows command resolution; it does not by itself enable shell metacharacter interpretation. `check.shell` (or the config-level `shell` default) is the separate, explicit opt-in for that — see `SECURITY.md` before enabling it.
228
-
229
- `repo-contract` doesn't ship a ready-made spawner of its own, on purpose (see ADR 0011's Alternatives) — the two snippets above are the whole integration.
230
-
231
- ## The model
232
-
233
- ```text
234
- check configuration
235
- |
236
- v
237
- execute checks
238
- |
239
- v
240
- collect evidence
241
- |
242
- v
243
- optionally parse explicitly requested output
244
- |
245
- v
246
- execute per-check policy
247
- |
248
- v
249
- PolicyResult {
250
- outcome: "pass" | "fail" | "warn",
251
- rationale: string
252
- }
253
- |
254
- v
255
- aggregate verdict
256
- |
257
- v
258
- { evidence, verdict }
259
- ```
260
-
261
- **Evidence** describes what happened: the command, its exit code, signal, timing, captured stdout/stderr, and, if requested, parsed output.
262
-
263
- Evidence never decides whether the result was acceptable.
264
-
265
- **Verdict** describes whether the result was acceptable according to your policies.
266
-
267
- **Policies are repository-owned.** A policy returns:
268
-
269
- ```ts
270
- {
271
- outcome: "pass" | "fail" | "warn"
272
- rationale: string
273
- }
274
- ```
275
-
276
- `rationale` is mandatory for every outcome, including `"pass"`. It should contain enough actionable detail — file/line locations, rule IDs, test names, counts, or other relevant information — that a human, CI system, or AI agent can understand the result without rerunning the check.
277
-
278
- `"warn"` is non-blocking. It means the policy's requirements were satisfied but the evidence is worth surfacing for review.
279
-
280
- ## AI guardrails
281
-
282
- AI coding systems have become capable of producing substantial amounts of working software. The problem is no longer simply whether an AI can write code.
283
-
284
- The problem is whether the code consistently satisfies the engineering standards of the repository.
285
-
286
- Robert C. Martin ("Uncle Bob") has discussed this problem publicly, describing AI-generated code that can appear productive while still leaving significant amounts of poor-quality code behind. His experience highlighted an important distinction: giving an AI more and more software-development guidance is not necessarily the best way to improve the result.
287
-
288
- Instead of attempting to put every engineering principle, convention, and quality rule into an AI's context window, put the important rules around the AI as **enforceable guardrails**.
289
-
290
- repo-contract lets your repository define those guardrails.
291
-
292
- For example:
293
-
294
- ```text
295
- AI writes code
296
- |
297
- v
298
- repo-contract
299
- |
300
- +--> tests
301
- +--> typecheck
302
- +--> lint
303
- +--> coverage
304
- +--> mutation testing
305
- +--> architecture
306
- +--> security
307
- +--> API compatibility
308
- +--> repository-specific standards
309
- |
310
- v
311
- actionable verdict
312
- |
313
- v
314
- AI fixes what failed
315
- ```
316
-
317
- The AI does not need to memorize every rule.
318
-
319
- It needs to satisfy the repository's contract.
320
-
321
- This is particularly useful for quality checks that are difficult to express through instructions alone. Mutation testing can test whether a test suite actually detects meaningful code changes. CRAP reports can expose code that combines complexity with insufficient test coverage. Your policy can simply require a low CRAP score or it can require both a low score and a maximum complexity. Security checks can enforce dependency and secret-management standards. Architecture checks can enforce dependency boundaries.
322
-
323
- The result is a feedback loop:
324
-
325
- ```text
326
- generate
327
- |
328
- v
329
- verify
330
- |
331
- v
332
- explain failure
333
- |
334
- v
335
- fix
336
- |
337
- v
338
- verify again
339
- ```
340
-
341
- The policy rationale is important here. A result such as:
342
-
343
- ```text
344
- FAIL: mutation score must be at least 90% (got 82%)
345
- ```
346
-
347
- is useful to a human.
348
-
349
- A result such as:
350
-
351
- ```text
352
- FAIL: 3 mutants survived in src/auth/session.ts;
353
- ```
354
-
355
- is useful to both a human and an AI agent.
356
-
357
- repo-contract does not attempt to become an AI coding agent. It provides the executable boundary against which an agent's work can be evaluated.
358
-
359
- **The repository supplies the guardrails. The AI supplies the implementation.**
360
-
361
- ## Open source contribution guardrails
362
-
363
- Open source projects face a different version of the same problem.
364
-
365
- A maintainer may know exactly what a contribution needs to satisfy, while a first-time contributor has no way to know all of those expectations.
366
-
367
- The result can be repeated review cycles:
368
-
369
- ```text
370
- contributor submits PR
371
- |
372
- v
373
- maintainer finds issue
374
- |
375
- v
376
- contributor fixes issue
377
- |
378
- v
379
- another issue is discovered
380
- |
381
- v
382
- repeat
383
- ```
384
-
385
- repo-contract can move those expectations into an executable contract.
386
-
387
- A project can enforce standards for:
388
-
389
- - tests and test coverage;
390
- - mutation scores;
391
- - formatting and linting;
392
- - type safety;
393
- - architecture;
394
- - documentation;
395
- - dependency security;
396
- - licenses;
397
- - public API compatibility;
398
- - generated artifacts;
399
- - package publishing;
400
- - repository-specific conventions.
401
-
402
- More importantly, policies can interpret the evidence and provide actionable guidance.
403
-
404
- Instead of:
405
-
406
- ```text
407
- CI failed.
408
- ```
409
-
410
- a contributor can receive:
411
-
412
- ```text
413
- FAIL: public API compatibility check failed.
414
-
415
- 2 breaking changes were detected:
416
-
417
- - Removed export: ContractResult
418
- - Changed parameter type: runRepoContract(config)
419
-
420
- Restore the export or document the breaking change according to
421
- the repository's versioning policy.
422
- ```
423
-
424
- This reduces the amount of repository knowledge a contributor must acquire before making a successful contribution.
425
-
426
- It also gives maintainers a consistent enforcement mechanism that does not depend on a particular maintainer remembering every rule during review.
427
-
428
- The goal is not to eliminate human review.
429
-
430
- The goal is to make human review focus on the things that require human judgment rather than repeatedly identifying mechanical violations.
431
-
432
- ## Defining checks
433
-
434
- Each check owns its identifier, command, output interpretation, and policy:
435
-
436
- ```ts
437
- checks: {
438
- lint: {
439
- run: ["eslint", ".", "--format", "json"],
440
- output: { format: "json" },
441
-
442
- policy: ({ result }) => {
443
- if (!result.output?.success) {
444
- return {
445
- outcome: "fail",
446
- rationale: "ESLint output was not valid JSON.",
447
- }
448
- }
449
-
450
- const files = result.output.value as { errorCount: number }[]
451
- const errors = files.reduce((sum, file) => sum + file.errorCount, 0)
452
-
453
- return errors === 0
454
- ? {
455
- outcome: "pass",
456
- rationale: "ESLint reported 0 errors.",
457
- }
458
- : {
459
- outcome: "fail",
460
- rationale: `${errors} lint error(s).`,
461
- }
462
- },
463
- },
464
- }
465
- ```
466
-
467
- ### `run`
468
-
469
- A `string` is tokenized into an executable and its arguments **without invoking a shell**.
470
-
471
- Shell operators such as `;`, `&`, `|`, backticks, `$(...)`, `<`, `>`, and newlines are not interpreted. A string containing one is rejected with a configuration error because it indicates an assumption that shell interpretation is occurring.
472
-
473
- Glob characters such as `*`, `?`, `~`, `[`, `]`, `{`, and `}` are not rejected. Many CLI tools expand their own arguments internally, and each argument is passed through as its own, separately-escaped element rather than concatenated into a command line, so those characters do not create shell injection behavior. (On Windows, resolving a `.cmd`/`.bat` shim unavoidably routes through `cmd.exe` — see [Security model](#security-model) and [SECURITY.md](SECURITY.md) for that platform-specific nuance.)
474
-
475
- A `readonly string[]` bypasses tokenization entirely and is used as argv verbatim. This is the recommended form for arguments containing characters that should never be interpreted.
476
-
477
- ```ts
478
- run: "eslint . --max-warnings 0"
479
- run: ["eslint", ".", "--max-warnings", "0"]
480
- run: "npm run build && npm test" // throws: run string contains an unquoted "&" -- use array form or "shell: true"
481
- ```
482
-
483
- To opt into real shell execution, including pipes, redirects, and `&&`, set `shell: true`. In that mode `run` must be a string and is passed to the platform shell as-is.
484
-
485
- See [Security model](#security-model) before enabling this.
486
-
487
- ### `output`
488
-
489
- By default, check output is not parsed. `result.stdout` and `result.stderr` contain the captured raw text.
14
+ ## See it run
490
15
 
491
- Request parsing explicitly:
492
-
493
- ```ts
494
- output: {
495
- format: "json"
496
- }
497
- ```
498
-
499
- Uses `JSON.parse`. Malformed output produces:
500
-
501
- ```ts
502
- {
503
- success: false,
504
- error: string
505
- }
506
- ```
507
-
508
- and never throws.
509
-
510
- ```ts
511
- output: {
512
- format: "yaml"
513
- }
514
- ```
515
-
516
- Requires the optional `yaml` peer dependency.
517
-
518
- ```ts
519
- output: {
520
- format: "text"
521
- }
522
- ```
523
-
524
- Provides trimmed text passthrough and always succeeds.
525
-
526
- `result.output.value` is `unknown` for every format. repo-contract has no schema knowledge of what an external tool prints, so your policy narrows or casts it according to the tool's actual output.
527
-
528
- `result.output` is `undefined` when a check does not request a format. If a policy reads `result.output.value` (or `.success`/`.error`/`.format`) without narrowing first, and that check never configured `output`, `runRepoContract()` rejects with [`PolicyReadUnrequestedOutputError`](docs/api-report/repo-contract.api.md), which names the check and tells you to add `output: { format: "json" }` (or `"yaml"`/`"text"`) -- rather than the generic `PolicyThrewError` you'd otherwise have to debug from a bare "Cannot read properties of undefined" stack trace.
529
-
530
- The sibling mistake -- reading `result.output.value` when the format _was_ requested but the parse itself failed (`result.output.success === false`, so `result.output` has `error`, not `value`) -- similarly rejects with [`PolicyReadFailedParseValueError`](docs/api-report/repo-contract.api.md) instead of a generic `PolicyThrewError`. Check `result.output.success` before reading `.value` to handle a parse failure explicitly rather than hitting either error.
531
-
532
- #### Validating parsed output with a schema
533
-
534
- `output.schema` accepts any object implementing [Standard Schema](https://standardschema.dev) --
535
- Zod, Valibot, ArkType, and others already do. repo-contract does not install or depend on any of
536
- them; it hand-vendors the (pure-type, zero-runtime-code) `StandardSchemaV1` interface itself (see
537
- [ADR 0012](specs/decisions/0012-hand-vendored-standard-schema-support-for-optional-output-validation.md)). Bring whichever schema
538
- library your own repo already uses -- `npm install zod` (or `valibot`, or `arktype`) is your call,
539
- not repo-contract's. The smallest possible example, using a hand-written object satisfying the
540
- interface directly rather than any particular library:
541
-
542
- ```ts
543
- output: {
544
- format: "json",
545
- schema: {
546
- "~standard": {
547
- version: 1,
548
- vendor: "example",
549
- validate: (value) => {
550
- const errorCount =
551
- value !== null && typeof value === "object"
552
- ? (value as { errorCount?: unknown }).errorCount
553
- : undefined
554
- return typeof errorCount === "number"
555
- ? { value: { errorCount } }
556
- : { issues: [{ message: "errorCount must be a number", path: ["errorCount"] }] }
557
- },
558
- },
559
- },
560
- }
561
- ```
562
-
563
- A successful validation _replaces_ `result.output.value` with the schema's own (possibly
564
- transformed or coerced) output -- one of the most useful aspects of Standard Schema, not just a
565
- boolean check: a schema can normalize, default, or reshape its input, and the policy receives that
566
- result, not the raw parsed value. A failing validation becomes a normal `ParsedOutputFailure` --
567
- indistinguishable in shape from a malformed-JSON parse failure, joining every issue's own path and
568
- message into `result.output.error` (e.g. `Schema validation failed: errorCount: errorCount must be
569
- a number`). `result.output.value`'s declared TypeScript type stays `unknown` regardless -- narrow
570
- or cast it yourself, same as without a schema.
571
-
572
- `schema["~standard"].validate()` itself throwing or rejecting (rather than returning a `Result`) is
573
- different from a failed validation -- it means the schema is broken, not the check's output, so it
574
- rejects `runRepoContract()` with `StandardSchemaValidateThrewError` instead of becoming evidence.
575
-
576
- ### `dependsOn`
577
-
578
- By default, every check runs independently and in parallel.
579
-
580
- Name other check IDs to establish explicit execution ordering:
16
+ **Actionable rationale for every outcome.** Here's the real output of the tiny contract in [`examples/demo/`](examples/demo/README.md) — three published presets against one deliberately imperfect file:
581
17
 
582
18
  ```ts
583
19
  checks: {
584
- build: {
585
- run: "npm run build",
586
-
587
- policy: ({ result }) =>
588
- result.exitCode === 0
589
- ? {
590
- outcome: "pass",
591
- rationale: "Build succeeded.",
592
- }
593
- : {
594
- outcome: "fail",
595
- rationale: "Build failed.",
596
- },
597
- },
598
-
599
- integration: {
600
- run: "npm run test:integration",
601
- dependsOn: ["build"],
602
-
603
- policy: ({ result }) =>
604
- result.exitCode === 0
605
- ? {
606
- outcome: "pass",
607
- rationale: "Integration tests passed.",
608
- }
609
- : {
610
- outcome: "fail",
611
- rationale: "Integration tests failed.",
612
- },
613
- },
614
- }
615
- ```
616
-
617
- Independent checks run concurrently. `dependsOn` allows checks to express ordering only when ordering is actually required.
618
-
619
- `dependsOn` does not cause repo-contract to decide whether the dependent check should run based on the dependency's policy result. That decision belongs to the dependent check's command or policy.
620
-
621
- Artifacts needed by another check should flow through the filesystem or another system designed for data transfer. `dependsOn` controls execution ordering; it does not move data between checks.
622
-
623
- Every named dependency must exist in `checks`, a check cannot depend on itself, and the entire dependency graph must be acyclic. These conditions are validated synchronously before anything is spawned.
624
-
625
- ### `policy`
626
-
627
- ```ts
628
- interface PolicyResult {
629
- outcome: "pass" | "fail" | "warn"
630
- rationale: string
20
+ typecheck,
21
+ format: { ...format, run: ["prettier", "--check", "."] },
22
+ lint: lint(),
631
23
  }
632
-
633
- policy: (ctx) => PolicyResult | Promise<PolicyResult>
634
24
  ```
635
25
 
636
- `outcome` is the policy's own judgment:
637
-
638
- - `"pass"` — the evidence satisfies the repository's requirements.
639
- - `"fail"` — it does not.
640
- - `"warn"` — the requirements are satisfied, but something worth reviewing should be surfaced.
641
-
642
- `"warn"` never fails `verdict.passed`.
643
-
644
- `rationale` is required for every outcome. It should contain enough actionable information that the consumer can understand the result without rerunning the command or re-parsing its output.
645
-
646
- Prefer:
647
-
648
26
  ```text
649
- ESLint reported 2 errors:
650
- - src/foo.ts:12:4 [no-explicit-any]: Unexpected any.
651
- - src/bar.ts:8:7 [no-unused-vars]: 'value' is defined but never used.
652
- ```
27
+ $ npm run demo
653
28
 
654
- over:
29
+ [PASS] typecheck
30
+ tsc reported no type errors.
655
31
 
656
- ```text
657
- See output above.
658
- ```
659
-
660
- `ctx.result` is the current check's evidence.
661
-
662
- `ctx.evidence` is the entire run's evidence, including every sibling check. Every configured check completes execution and has its evidence fully assembled before policies run, allowing policies to reason across checks:
663
-
664
- ```ts
665
- policy: ({ result, evidence }) => {
666
- const testsPassed = evidence.checks.tests?.exitCode === 0
32
+ [FAIL] format
33
+ Prettier reported formatting failures:
34
+ Checking formatting...
35
+ [warn] src/greet.ts
36
+ [warn] Code style issues found in the above file. Run Prettier with --write to fix.
667
37
 
668
- if (!testsPassed) {
669
- return {
670
- outcome: "fail",
671
- rationale: "Mutation policy requires the test suite to pass.",
672
- }
673
- }
674
-
675
- const score = Number(result.stdout)
676
-
677
- return score >= 90
678
- ? {
679
- outcome: "pass",
680
- rationale: `Mutation score was ${score}%.`,
681
- }
682
- : {
683
- outcome: "fail",
684
- rationale: `Mutation score must be at least 90% (got ${score}%).`,
685
- }
686
- }
38
+ [WARN] lint
39
+ ESLint reported 0 errors but 1 warning(s):
40
+ - src/greet.ts:15:3 [no-console]: Unexpected console statement.
687
41
  ```
688
42
 
689
- `ctx.dependencies` contains this check's declared `dependsOn` evidence, keyed by ID. It is `{}` for a check without dependencies and is never `undefined`.
43
+ Real output, not a mockup — run it yourself with `npm run demo` from [`examples/demo/`](examples/demo/README.md). Not "CI failed": each line says what happened, where, why the repository treats it that way, and — when there's something to fix — how. `warn` doesn't block the run; `fail` does.
690
44
 
691
- ```ts
692
- policy: (ctx) => {
693
- const buildOutput = ctx.dependencies.build?.stdout
45
+ ## Why it exists
694
46
 
695
- return buildOutput?.includes("Compiled successfully")
696
- ? {
697
- outcome: "pass",
698
- rationale: "Build output confirmed compilation succeeded.",
699
- }
700
- : {
701
- outcome: "fail",
702
- rationale: "Build output did not confirm successful compilation.",
703
- }
704
- }
705
- ```
47
+ Green CI does not mean your standard held.
706
48
 
707
- Dependency policy results are not included in `ctx.dependencies`. They remain available at the top-level `Verdict`.
49
+ - Coverage stays above 85%, but the file you just added has almost none — and whether that's acceptable isn't written down anywhere executable.
50
+ - The mutation score moved because the test suite moved — and nothing reconciles the two.
51
+ - A contributor gets "CI failed" and no idea what the repository expects them to do.
708
52
 
709
- A policy is always invoked for every check, regardless of how that check's process ended: completed, timed out, killed by a signal, or failed to spawn.
53
+ Individual tools answer individual questions. Your engineering standard is the answer to all of them together — and today it lives scattered across CI YAML, `package.json` scripts, configuration, docs, and review habits. repo-contract makes that standard one executable thing.
710
54
 
711
- repo-contract does not decide what those execution outcomes mean. Your policy does.
55
+ Tools run checks. Quality gates aggregate their exit codes. repo-contract sits a layer under both: it turns each tool's output into structured evidence, then lets your own policies decide what that evidence means — including reading one check's evidence to judge another's.
712
56
 
713
- A policy that throws is different. A thrown or rejected policy represents a bug in policy code, not a failed engineering check. `runRepoContract()` rejects with `PolicyThrewError`, or an `AggregateError` when multiple policies throw.
714
-
715
- Other execution options include:
716
-
717
- - `cwd`
718
- - `env`
719
- - `inheritEnv`
720
- - `timeoutMs`
721
-
722
- `inheritEnv` defaults to `true`. Set it to `false` when a check requires a minimal environment.
57
+ ## Quick start
723
58
 
724
- ## Preset checks
59
+ **Your standards, defined once as typed code.**
725
60
 
726
- You do not have to hand-write every common check.
61
+ ```sh
62
+ npm install --save-dev repo-contract tsx typescript vitest eslint
63
+ npx repo-contract init
64
+ npm run contract
65
+ ```
727
66
 
728
- `repo-contract/presets` ships a curated, growing catalog of ready-made `CheckDefinitionConfig`s for tools commonly used by TypeScript and JavaScript repositories.
67
+ `init` reads your `package.json`'s existing devDependencies (`typescript`, `vitest`, and `eslint`
68
+ above — install whichever of the [presets](GUIDE.md#presets) apply to you, `init` only wires up
69
+ what it finds), writes the same two files shown below
70
+ from them, and adds (or, if one's already there, leaves untouched) that same `"contract"` entry in
71
+ `package.json`'s own `scripts` — nothing it generates is required to run a contract; it's the same
72
+ scaffold you'd otherwise type by hand, once, so it stops being the first thing between you and
73
+ seeing this work. Point your pre-commit hook and your CI job at that same `npm run contract`.
74
+ `init` is Experimental (see [VERSIONING.md](VERSIONING.md)) — everything it writes is yours from
75
+ the moment it lands, so that classification is about the generator, never about what it produces.
729
76
 
730
- A preset encodes how to execute and interpret a common tool.
77
+ ### Already know how you want it configured? Create it by hand
731
78
 
732
- It does **not** encode your repository's definition of quality.
79
+ A hand-authored contract is the same two files and the same `package.json` entry `init` writes,
80
+ with nothing hidden.
733
81
 
734
- Import a preset, spread it into your own `checks` record, and override whatever you need — most often `policy`:
82
+ `tsx` runs the TypeScript config and runner. Each check invokes its own tool, so install those too — `typescript`, `vitest`, and `eslint` for the three below. repo-contract bundles none of them.
735
83
 
736
84
  ```ts
85
+ // repo-contract.config.mts — your standard, as typed code
737
86
  import { spawn } from "node:child_process"
738
87
  import { defineRepoContract } from "repo-contract"
739
- import { format, typecheck, license } from "repo-contract/presets"
88
+ import { lint, test, typecheck } from "repo-contract/presets"
740
89
 
741
90
  export default defineRepoContract({
742
91
  spawn,
743
92
  env: process.env,
744
- checks: {
745
- format,
746
- typecheck: {
747
- ...typecheck,
748
- timeoutMs: 60_000,
749
- },
750
- license: {
751
- ...license,
752
- policy: myStricterLicensePolicy,
753
- },
754
- },
93
+ checks: { typecheck, test, lint: lint() },
755
94
  })
756
95
  ```
757
96
 
758
- Some presets are factories because they expose options that change what gets executed:
97
+ `.mts`, not `.ts`: an `.mts` file is always ESM to Node, regardless of whether your own `package.json` has `"type": "module"` set (`npm init`'s default output doesn't). A plain `.ts` config would compile to CommonJS in that case while the `.mjs` runner below — ESM by its own extension — imports it, and Node's CJS/ESM default-export interop would silently hand `runRepoContract` the wrong shape.
759
98
 
760
99
  ```ts
761
- import { spawn } from "node:child_process"
762
- import { defineRepoContract } from "repo-contract"
763
- import { lint, deadCode } from "repo-contract/presets"
764
-
765
- export default defineRepoContract({
766
- spawn,
767
- env: process.env,
768
- checks: {
769
- lint: lint({ path: "src" }),
770
- deadCode: deadCode({
771
- exemptUnusedDevDependencies: ["some-cli-only-tool"],
772
- }),
773
- },
774
- })
775
- ```
776
-
777
- **Preset options are the preferred way to change what a preset executes.** A direct `run` override is an escape hatch.
778
-
779
- Using options keeps your configuration decoupled from a preset's exact command representation, which can evolve independently.
780
-
781
- Every execution-affecting preset option is threaded through the actual command line so evidence records the exact options used in `evidence.checks.<id>.args`.
782
-
783
- Every preset's policy fails with an actionable message if its underlying tool is not installed, with one deliberate exception: `securityDeps` shells out to `npm` itself, which cannot be "missing" in any environment capable of running `npm run <script>` at all. repo-contract never installs, bundles, or implicitly depends on these tools.
784
-
785
- Each preset assumes its CLI is already a devDependency of your repository:
786
-
787
- ```sh
788
- npm install --save-dev prettier
789
- ```
790
-
791
- or:
792
-
793
- ```sh
794
- pnpm add -D prettier
795
- yarn add -D prettier
796
- bun add -d prettier
797
- ```
798
-
799
- | Category | Preset | Wraps |
800
- | ------------------- | ------------------------ | ------------------------------------------------------------------------------ |
801
- | Testing | `test` | `vitest run --reporter=json` |
802
- | Testing | `e2e` | `playwright test --reporter=json` |
803
- | Code quality | `lint(options?)` | `eslint <path> --format json` |
804
- | Code quality | `format` | `prettier --write .` |
805
- | Code quality | `typecheck` | `tsc --noEmit -p tsconfig.json` |
806
- | Code quality | `deadCode(options?)` | `knip --reporter json` |
807
- | Code quality | `duplication(options?)` | `jscpd <path> --reporters json --output reports/jscpd --silent` |
808
- | Code quality | `stylelint(options?)` | `stylelint <glob> --formatter json` |
809
- | Docs | `markdownlint(options?)` | `markdownlint-cli2 <glob>` — requires repository configuration for JSON output |
810
- | Docs | `brokenLinks(options?)` | `linkinator <start> --recurse --format json --skip node_modules` |
811
- | Security/governance | `securityDeps` | `npm audit --omit=dev --json` |
812
- | Security/governance | `securitySecrets` | `secretlint --format json --output reports/secretlint.json **/*` |
813
- | Security/governance | `license` | `licensee --production --osi --errors-only --ndjson` |
814
- | Security/governance | `commitlint(options?)` | `commitlint --from <from> --to <to>` |
815
- | Publishing | `publint` | `publint run` |
816
- | Publishing | `arethetypeswrong` | `attw --pack . --format json` |
817
-
818
- Not every test runner has a preset. Jest, Cypress, and Mocha have different reporter formats and may require bespoke presets. The underlying pattern remains the same: execute the tool, capture evidence, interpret its output, and apply your repository's policy.
819
-
820
- Unlike its neighbors, `format` auto-fixes (`--write`) and therefore cannot itself fail on unformatted input — `prettier --write` reports success once it finishes rewriting files. If you want a hard gate on formatting (in CI, for example), run `prettier --check .` directly instead of this preset.
821
-
822
- ## Evidence
823
-
824
- See the generated [API report](docs/api-report/repo-contract.api.md) for the full field-by-field reference on `Evidence` and `CheckEvidence`.
825
-
826
- ```ts
827
- interface Evidence {
828
- version: 1
829
- startedAt: string
830
- completedAt: string
831
- durationMs: number
832
-
833
- checks: Record<
834
- string,
835
- {
836
- command: string
837
- args: readonly string[]
838
- startedAt: string
839
- completedAt: string
840
- durationMs: number
841
- exitCode: number | null
842
- signal: NodeJS.Signals | null
843
- stdout: string
844
- stderr: string
845
- status: "completed" | "timed_out" | "signaled" | "host_terminated" | "spawn_error" | "aborted"
846
- spawnError?: string
847
- output?:
848
- | {
849
- format: string
850
- success: true
851
- value: unknown
852
- }
853
- | {
854
- format: string
855
- success: false
856
- error: string
857
- }
858
- }
859
- >
860
- }
861
- ```
862
-
863
- `status` distinguishes **why** a process ended in its terminal state, independently of whether the policy considered that result acceptable.
864
-
865
- A non-zero exit code is `status: "completed"`, not an execution error.
866
-
867
- ## Verdict
868
-
869
- See the generated [API report](docs/api-report/repo-contract.api.md) for the full field-by-field reference on `Verdict`.
870
-
871
- ```ts
872
- interface Verdict {
873
- version: 2
874
- passed: boolean
875
-
876
- checks: Record<
877
- string,
878
- {
879
- outcome: "pass" | "fail" | "warn"
880
- rationale: string
881
- }
882
- >
883
- }
884
- ```
885
-
886
- `checks[id]` is that check's own `PolicyResult`, exactly as returned by its policy.
887
-
888
- `passed` is `true` only when every check's outcome is `"pass"` or `"warn"`.
889
-
890
- `"fail"` is the only outcome that fails the run.
891
-
892
- `Verdict` is returned alongside `Evidence`, never merged into it. Consumers can inspect:
893
-
894
- ```ts
895
- evidence.checks[id]
896
- ```
897
-
898
- to understand what happened and:
899
-
900
- ```ts
901
- verdict.checks[id]
902
- ```
903
-
904
- to understand what the repository concluded about it.
905
-
906
- See [Evidence, policy rationale, and consumer judgment](specs/architecture.md#evidence-policy-rationale-and-consumer-judgment) for why these responsibilities remain separate.
907
-
908
- ## Regression detection
909
-
910
- repo-contract has no built-in baseline system and no persistence layer.
911
-
912
- The core engine (validation, execution, evidence, policy) does not read or write files on its own initiative beyond spawning the commands you configure and, where you set `output: { format: "json" | "yaml" }`, parsing that command's own captured stdout. A handful of published presets (`securitySecrets`, `duplication`, `markdownlint`) additionally read back a fixed report file their own `run` command was told to write, as part of interpreting that tool's JSON output — always that preset's own single, hardcoded, tool-specific path, never a scan or discovery of arbitrary files.
913
-
914
- If your repository wants regression detection, persist the evidence or relevant measurements yourself and compare them in your policy:
915
-
916
- ```ts
917
- import baseline from "./baseline.json"
918
-
919
- policy: ({ result }) => {
920
- const current = (result.output?.value as { score: number }).score
921
-
922
- return current >= baseline.mutation.score
923
- ? {
924
- outcome: "pass",
925
- rationale: `Mutation score was ${current}.`,
926
- }
927
- : {
928
- outcome: "fail",
929
- rationale: `Mutation score regressed from ${baseline.mutation.score} to ${current}.`,
930
- }
931
- }
932
- ```
933
-
934
- This keeps persistence and baseline semantics under repository control.
935
-
936
- ## CI integration
937
-
938
- repo-contract has no CLI and no config-discovery magic.
939
-
940
- Call `runRepoContract()` from a small script and map the result to a process exit code yourself:
941
-
942
- ```ts
943
- // scripts/run-contract.mjs
100
+ // scripts/contract.mjs — the entry point; this is the whole thing
944
101
  import { runRepoContract } from "repo-contract"
945
- import config from "../repo-contract.config.js"
102
+ import config from "../repo-contract.config.mjs"
946
103
 
947
104
  const { verdict } = await runRepoContract(config)
948
-
949
105
  for (const [id, result] of Object.entries(verdict.checks)) {
950
106
  console.log(`[${result.outcome.toUpperCase()}] ${id}: ${result.rationale}`)
951
107
  }
952
-
953
108
  process.exitCode = verdict.passed ? 0 : 1
954
109
  ```
955
110
 
956
111
  ```json
957
- {
958
- "scripts": {
959
- "contract": "tsx scripts/run-contract.mjs"
960
- }
961
- }
112
+ { "scripts": { "contract": "tsx scripts/contract.mjs" } }
962
113
  ```
963
114
 
964
- The same contract can then be used by local development and CI:
965
-
966
115
  ```sh
967
116
  npm run contract
968
117
  ```
969
118
 
970
- Point a `precommit`, `prepublishOnly`, or CI job at the same command to enforce the same engineering standards in each environment.
971
-
972
- This repository uses its own `repo-contract.config.ts` to validate itself. See [CONTRIBUTING.md](CONTRIBUTING.md).
973
-
974
- ## Example: layered organizational governance
119
+ Point your pre-commit hook and your CI job at that same `npm run contract`. The [Guide](GUIDE.md#the-runner-and-ci-integration) covers the runner, the `spawn`/`env` capability model, and Windows. Node.js `>=20` (Bun and Deno are tested too).
975
120
 
976
- repo-contract is the mechanism, not the policy owner. An organization can express its engineering standard for a project type **once** -- as an internal contract package that wraps repo-contract and owns the executors -- and start every project of that type from a matching boilerplate that extends the shared configuration rather than redefining it.
121
+ Two patterns worth knowing about early, not just once you're rolling this out across an org: the
122
+ [**ratchet**](examples/day-one-walkthrough/README.md) (a new requirement lands as a dated `warn` →
123
+ `fail`, never an overnight red build) and [**governed exceptions**](examples/exceptions-walkthrough/README.md)
124
+ (_Experimental_ — a justified, reviewed waiver for one specific finding, without weakening the
125
+ check for everything else).
977
126
 
978
- [`examples/`](examples/README.md) is a minimal, runnable end-to-end wiring of that model: an `internal-boilerplate-contract` package (three read-only checks, exported ESLint/Prettier/TypeScript baselines, a reusable CI workflow) and a trivial `boilerplate` that consumes it. See the [walkthrough](examples/README.md) and [ADR 0010](specs/decisions/0010-review-driven-contracts-and-shared-internal-system-contracts.md) for the architecture.
127
+ ## What a policy can express
979
128
 
980
- ## Enterprise / locked-down environments
129
+ **Checks produce evidence. Policies decide whether that evidence meets your standard** — and a policy can read any other check's result to do it:
981
130
 
982
- The package's entire shipped surface (`src/**` -- the programmatic API and every published preset) has:
983
-
984
- - no CLI;
985
- - no network calls;
986
- - no telemetry;
987
- - no automatic package installation;
988
- - no hidden configuration.
989
-
990
- It only does what your configuration tells it to do: execute the commands you define with the environment and options you specify. The core engine never discovers or reads a file you didn't ask it to; a small number of presets (`securitySecrets`, `duplication`, `markdownlint`) read back their own tool's fixed, hardcoded report path after running it — see [Regression detection](#regression-detection) above — which is deterministic per preset, not discovery of arbitrary filesystem state.
991
-
992
- This makes it suitable for locked-down enterprise environments where tools such as `npx` or network access may be unavailable.
993
-
994
- The "no network calls" guarantee is mechanically enforced, not merely documented: an ESLint rule and an independent, ESLint-free repository check both reject network-capable imports, globals, and unreviewed spawned commands anywhere in the shipped surface. See [SECURITY.md](SECURITY.md) for the full threat model and [ADR 0007](specs/decisions/0007-no-network-surface.md) for what's covered, what's deliberately excluded, and why.
995
-
996
- ## Security model
997
-
998
- Command execution is the core of this package and therefore a security-sensitive boundary.
999
-
1000
- The default `run` behavior — whether a string or array — never explicitly invokes a shell. On POSIX this means no shell is invoked at all; on Windows, resolving a `.cmd`/`.bat` shim (how most npm-installed CLIs are actually invoked there) unavoidably routes through `cmd.exe`, with argument escaping — not shell absence — providing the same safety property. See [SECURITY.md](SECURITY.md) for the full platform-specific detail.
1001
-
1002
- No untrusted value is interpolated into a command line in a way that lets it inject a second command, a redirect, or a pipeline.
1003
-
1004
- `shell: true` is an explicit opt-in exception with different security properties. Whatever you put in `run` is handed to the platform shell verbatim.
131
+ ```ts
132
+ checks: {
133
+ tests: test,
134
+
135
+ mutation: {
136
+ run: ["npm", "run", "mutation"],
137
+ dependsOn: ["tests"], // run after tests; hand this policy their result
138
+ policy: ({ dependencies }) =>
139
+ dependencies.tests?.exitCode !== 0
140
+ ? { outcome: "fail", rationale: "Tests must pass before a mutation score means anything." }
141
+ : { outcome: "pass", rationale: "Tests passed; mutation score is meaningful." },
142
+ },
143
+ }
144
+ ```
1005
145
 
1006
- Never construct a `run` string by concatenating untrusted input, such as content originating from a pull request.
146
+ That relationship — "don't evaluate X until Y passed" — is easy to state in the contract and awkward to maintain as independent CI steps. The [Guide](GUIDE.md#a-fuller-example-cross-check-policy) has the full version: coverage floors, per-file warnings, surviving-mutant rationales.
1007
147
 
1008
- See [SECURITY.md](SECURITY.md) for the complete threat model, including environment variables, command execution, captured output, and shell execution.
148
+ Presets exist for the common tools — TypeScript, ESLint, Vitest, Prettier, security auditing, dependency and dead-code analysis, package validation, and more. Each assumes its CLI is already a devDependency; repo-contract never installs anything. [Full catalog →](GUIDE.md#presets)
1009
149
 
1010
- ## Errors
150
+ ## Why not just add more CI steps?
1011
151
 
1012
- See the generated [API report](docs/api-report/repo-contract.api.md) for each error class's exact shape and `code` string.
152
+ **repo-contract distributes the whole engineering standard, not just individual checks.** One versioned package defines what your repositories consider acceptable — typed code, reviewed like any other code, and runnable on a developer's laptop as well as in CI.
1013
153
 
1014
- repo-contract distinguishes several failure categories and does not conflate them.
154
+ You can express any individual check in CI. The problem is that the standard governing those checks ends up scattered across CI YAML, shared configs, and reusable workflows — infrastructure that distributes **how checks run**, rather than **what your organization considers acceptable**.
1015
155
 
1016
- **Configuration errors** (`InvalidRepoContractConfigError`, `InvalidCheckConfigError`) indicate structurally invalid configuration. They are thrown synchronously before anything spawns.
156
+ Shared ESLint configs, reusable workflows, and template repos distribute individual checks. repo-contract distributes the **whole standard**: the checks, the policies that relate them, and the rationale for their outcomes.
1017
157
 
1018
- **Execution outcomes** include missing binaries, timeouts, non-zero exits, signals, and aborted processes. These are recorded as evidence rather than thrown so that repository policies can decide what they mean.
158
+ ```text
159
+ GitHub Actions repo-contract
160
+ lint ✓ one policy reads another check's result
161
+ test ✓ the same contract runs in a pre-commit hook and in CI
162
+ coverage ✓ it's typed code in your repo, reviewed like any code
163
+ mutation ✓ every outcome carries a rationale you can act on
164
+ ── but does that meet
165
+ our standard? ──
166
+ ```
1019
167
 
1020
- **Parser errors** occur when requested output cannot be parsed. They are recorded as:
168
+ ## From one repo to a whole org
1021
169
 
1022
- ```ts
1023
- {
1024
- success: false,
1025
- error: string
1026
- }
170
+ ```text
171
+ typecheck + lint + test
172
+ | add policy
173
+ coverage + mutation + security
174
+ | share it
175
+ one contract package, inherited by every repository
176
+ | roll out safely
177
+ new requirements land as `warn`, become `fail` on a date
1027
178
  ```
1028
179
 
1029
- on `result.output`, while raw stdout remains available.
180
+ **One definition, shared across every repository.** An organization expresses its engineering standard for a project type **once** — an internal package that wraps repo-contract and owns the executors — and every project of that type extends it instead of redefining it. Change the shared standard, and consuming repositories receive it through their normal dependency updates, including ones created from an older boilerplate.
1030
181
 
1031
- **Policy failures** occur when your policy returns:
182
+ - [`examples/`](examples/README.md) — a minimal, runnable end-to-end wiring of that model.
183
+ - [`examples/day-one-walkthrough/`](examples/day-one-walkthrough/README.md) — the **ratchet** pattern: rolling out a new shared requirement as a dated `warn` → `fail`, not an overnight red build.
184
+ - [`examples/exceptions-walkthrough/`](examples/exceptions-walkthrough/README.md) — _Experimental:_ a governed, justified waiver for one finding, without weakening the check.
185
+ - [ADR 0010](specs/decisions/0010-review-driven-contracts-and-shared-internal-system-contracts.md) — the reasoning.
1032
186
 
1033
- ```ts
1034
- {
1035
- outcome: "fail",
1036
- rationale: string
1037
- }
1038
- ```
187
+ ## You probably don't need it when
1039
188
 
1040
- This is not an error in repo-contract.
189
+ - `npm test` plus a linter is the whole story;
190
+ - each CI check is already independent and that's fine;
191
+ - you don't need any policy logic on top of exit codes.
1041
192
 
1042
- It is the contract working correctly.
193
+ ## Works with automation
1043
194
 
1044
- A **policy throwing** is different. A synchronous throw or rejected promise from policy code indicates a bug in the policy itself and causes `runRepoContract()` to reject with `PolicyThrewError`, or an `AggregateError` when multiple policies throw.
195
+ AI coding agents, CI bots, and release automation consume the same contract as human contributors. An actionable rationale gives automation a concrete thing to fix rather than a bare "a check failed" — making regenerate-until-green workflows much easier to converge.
1045
196
 
1046
- Two specific mistakes get their own error instead of a plain `PolicyThrewError`, both naming the check: reading `result.output.value` (or `.success`/`.error`/`.format`) on a check that never configured `output` throws `PolicyReadUnrequestedOutputError`, telling you to add `output: { format: "json" }` (or `"yaml"`/`"text"`); reading `result.output.value` on a check whose requested parse actually failed throws `PolicyReadFailedParseValueError`, telling you to check `result.output.success` first -- see [`output`](#output).
197
+ ## Status
1047
198
 
1048
- A **schema throwing** (`output.schema["~standard"].validate()` itself throwing or rejecting, rather than returning a `Result` -- see [Validating parsed output with a schema](#validating-parsed-output-with-a-schema)) is treated the same as a throwing policy: `StandardSchemaValidateThrewError`, or an `AggregateError` when multiple checks' schemas throw in the same run. A schema _returning_ failure `issues`, by contrast, is not an error at all -- it becomes an ordinary parser-error-shaped `result.output`, exactly like the **Parser errors** case above.
199
+ Pre-1.0. Per [VERSIONING.md](VERSIONING.md), a `0.x` minor may carry a breaking change to the Stable tier before 1.0 — pin accordingly and read the [CHANGELOG](CHANGELOG.md) on every minor upgrade.
1049
200
 
1050
- ## Status and versioning
201
+ ## Learn more
1051
202
 
1052
- repo-contract is pre-1.0.
203
+ - **[Guide](GUIDE.md)** — how to integrate it, define checks, parse output, write cross-check policies, use presets, and handle errors.
204
+ - **[Security](SECURITY.md)** — how commands run, which capabilities the consumer supplies, and the enforced no-network guarantee.
205
+ - **[API reference](https://maverickcer.github.io/repo-contract/api/)** — every exported type, field, and error code, generated from source so it cannot drift.
206
+ - **[Architecture](specs/architecture.md)** and the **[ADRs](https://github.com/MaverickCER/repo-contract/tree/main/specs/decisions)** — why it is built this way.
207
+ - **[examples/](examples/README.md)** — runnable end-to-end wiring, plus the two deep-dive walkthroughs.
1053
208
 
1054
- Per [VERSIONING.md](VERSIONING.md), minor versions may include breaking changes to the Stable tier before 1.0.
209
+ ## If this is useful
1055
210
 
1056
- See [CHANGELOG.md](CHANGELOG.md) for release history.
211
+ If this model matches how you think about repository standards, try the demo and examples. If it doesn't fit your repo, [open an issue](https://github.com/MaverickCER/repo-contract/issues) and say why.
1057
212
 
1058
213
  ## Contributing
1059
214
 
1060
- See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and how this repository's own `repo-contract.config.ts` uses the package to validate itself, and [RELEASING.md](RELEASING.md) for the release process.
215
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and how this repository uses its own `repo-contract.config.ts` to validate itself, and [RELEASING.md](RELEASING.md) for the release process.
1061
216
 
1062
217
  ## License
1063
218