plumbbob 0.8.14 → 0.8.18

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 (85) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +6 -0
  3. package/dist/lib/agents.js +1 -1
  4. package/dist/lib/check.js +9 -6
  5. package/dist/verbs/finish.js +4 -2
  6. package/node_modules/checkride/CHANGELOG.md +234 -0
  7. package/node_modules/checkride/README.md +134 -31
  8. package/node_modules/checkride/dist/adapters.d.ts +50 -1
  9. package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
  10. package/node_modules/checkride/dist/adapters.js +123 -7
  11. package/node_modules/checkride/dist/adapters.js.map +1 -1
  12. package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
  13. package/node_modules/checkride/dist/agent-setup/hook.js +15 -1
  14. package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
  15. package/node_modules/checkride/dist/agent-setup/index.d.ts +0 -1
  16. package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
  17. package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
  18. package/node_modules/checkride/dist/baseline/fallow.d.ts +64 -0
  19. package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -0
  20. package/node_modules/checkride/dist/baseline/fallow.js +256 -0
  21. package/node_modules/checkride/dist/baseline/fallow.js.map +1 -0
  22. package/node_modules/checkride/dist/baseline/fingerprint.d.ts +4 -6
  23. package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
  24. package/node_modules/checkride/dist/baseline/fingerprint.js +9 -4
  25. package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
  26. package/node_modules/checkride/dist/baseline/index.d.ts +4 -5
  27. package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
  28. package/node_modules/checkride/dist/baseline/index.js +2 -2
  29. package/node_modules/checkride/dist/baseline/index.js.map +1 -1
  30. package/node_modules/checkride/dist/{baseline/command.d.ts → baseline-command.d.ts} +11 -9
  31. package/node_modules/checkride/dist/baseline-command.d.ts.map +1 -0
  32. package/node_modules/checkride/dist/{baseline/command.js → baseline-command.js} +9 -7
  33. package/node_modules/checkride/dist/baseline-command.js.map +1 -0
  34. package/node_modules/checkride/dist/cli.d.ts.map +1 -1
  35. package/node_modules/checkride/dist/cli.js +95 -8
  36. package/node_modules/checkride/dist/cli.js.map +1 -1
  37. package/node_modules/checkride/dist/config.d.ts +37 -12
  38. package/node_modules/checkride/dist/config.d.ts.map +1 -1
  39. package/node_modules/checkride/dist/config.js +268 -60
  40. package/node_modules/checkride/dist/config.js.map +1 -1
  41. package/node_modules/checkride/dist/digest/digest.d.ts +0 -6
  42. package/node_modules/checkride/dist/digest/digest.d.ts.map +1 -1
  43. package/node_modules/checkride/dist/digest/digest.js +3 -3
  44. package/node_modules/checkride/dist/digest/digest.js.map +1 -1
  45. package/node_modules/checkride/dist/digest/index.d.ts +1 -2
  46. package/node_modules/checkride/dist/digest/index.d.ts.map +1 -1
  47. package/node_modules/checkride/dist/digest/index.js +1 -1
  48. package/node_modules/checkride/dist/digest/index.js.map +1 -1
  49. package/node_modules/checkride/dist/doctor.d.ts +14 -1
  50. package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
  51. package/node_modules/checkride/dist/doctor.js +124 -55
  52. package/node_modules/checkride/dist/doctor.js.map +1 -1
  53. package/node_modules/checkride/dist/init.d.ts +6 -0
  54. package/node_modules/checkride/dist/init.d.ts.map +1 -1
  55. package/node_modules/checkride/dist/init.js +347 -121
  56. package/node_modules/checkride/dist/init.js.map +1 -1
  57. package/node_modules/checkride/dist/links.d.ts.map +1 -1
  58. package/node_modules/checkride/dist/links.js +8 -9
  59. package/node_modules/checkride/dist/links.js.map +1 -1
  60. package/node_modules/checkride/dist/orchestrator.d.ts +56 -1
  61. package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
  62. package/node_modules/checkride/dist/orchestrator.js +506 -138
  63. package/node_modules/checkride/dist/orchestrator.js.map +1 -1
  64. package/node_modules/checkride/dist/pack.d.ts +76 -0
  65. package/node_modules/checkride/dist/pack.d.ts.map +1 -0
  66. package/node_modules/checkride/dist/pack.js +217 -0
  67. package/node_modules/checkride/dist/pack.js.map +1 -0
  68. package/node_modules/checkride/dist/pm/translate.d.ts +18 -10
  69. package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
  70. package/node_modules/checkride/dist/pm/translate.js +27 -13
  71. package/node_modules/checkride/dist/pm/translate.js.map +1 -1
  72. package/node_modules/checkride/dist/smoke.d.ts +86 -0
  73. package/node_modules/checkride/dist/smoke.d.ts.map +1 -0
  74. package/node_modules/checkride/dist/smoke.js +346 -0
  75. package/node_modules/checkride/dist/smoke.js.map +1 -0
  76. package/node_modules/checkride/dist/snippets.d.ts +215 -0
  77. package/node_modules/checkride/dist/snippets.d.ts.map +1 -0
  78. package/node_modules/checkride/dist/snippets.js +360 -0
  79. package/node_modules/checkride/dist/snippets.js.map +1 -0
  80. package/node_modules/checkride/package.json +4 -2
  81. package/node_modules/checkride/schema/checkride.config.schema.json +11 -6
  82. package/package.json +4 -3
  83. package/skills/pb-finish/SKILL.md +15 -2
  84. package/node_modules/checkride/dist/baseline/command.d.ts.map +0 -1
  85. package/node_modules/checkride/dist/baseline/command.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plumbbob",
3
3
  "displayName": "PlumbBob",
4
- "version": "0.8.14",
4
+ "version": "0.8.18",
5
5
  "description": "Attention-first build process — driver skills + a CLI that keep you the decider: guidance on the work, a latch on the record.",
6
6
  "author": { "name": "Rob McLarty", "email": "hello@robmclarty.com", "url": "https://robmclarty.com" },
7
7
  "homepage": "https://github.com/robmclarty/plumbbob#readme",
package/README.md CHANGED
@@ -4,6 +4,12 @@
4
4
  <img src="hero.svg" alt="A row of plumb bobs of varying shapes hanging from strings" width="600">
5
5
  </p>
6
6
 
7
+ <p align="center">
8
+ <a href="https://robmclarty.github.io/plumbbob/">Website</a> ·
9
+ <a href="https://robmclarty.github.io/plumbbob/docs.html">Docs</a> ·
10
+ <a href="https://robmclarty.github.io/plumbbob/api.html">API reference</a>
11
+ </p>
12
+
7
13
  PlumbBob is a Claude Code plugin — twelve `/pb-*` skills and a small CLI — that runs
8
14
  LLM-assisted coding as a loop you control. You and the model settle the plan in a file
9
15
  *before* any code; then the model builds one small step at a time and **stops after
@@ -327,7 +327,7 @@ function skipWarnings(heading, skipped) {
327
327
  // is the author's error and is refused loud. Slot *contents* stay lenient (D61): a
328
328
  // slot value may be one name or a list, and blanks/non-strings drop rather than
329
329
  // refuse, because bindings feed a spawn, not a git-gating parse.
330
- export function parseHarness(raw) {
330
+ function parseHarness(raw) {
331
331
  if (!isObject(raw)) {
332
332
  return { ok: false, error: 'harness.json must be a JSON object.' };
333
333
  }
package/dist/lib/check.js CHANGED
@@ -31,12 +31,15 @@ export async function detectGate(root) {
31
31
  }
32
32
  }
33
33
  // Checkride's always-on repo checks: the adapters its doctor reports even for
34
- // an EMPTY directory (links, the pnpm-audit security scan, and the
35
- // publint/attw package-shape probes). They exercise the repo's plumbing, not
36
- // its code — a gate made only of these green-lights every checkpoint while the
37
- // human believes their work is being checked. Coupled to checkride's built-in
38
- // set by construction; the doctor.test.ts probe pins the coupling.
39
- const ALWAYS_ON_ADAPTERS = ['links', 'pnpm-audit', 'publint', 'attw'];
34
+ // an EMPTY directory — the built-in `links` slot, the pnpm-audit security
35
+ // scan, the publint/attw package-shape probes, and (checkride 0.5.0's
36
+ // publish bundle) the built-in pack/smoke/snippets slots, which resolve on any
37
+ // package with no tool config of their own. They exercise the repo's plumbing
38
+ // or its shipped artifact, not its code — a gate made only of these green-lights
39
+ // every checkpoint while the human believes their work is being checked. Coupled
40
+ // to checkride's built-in set by construction; the check.test.ts and
41
+ // doctor.test.ts probes pin the coupling.
42
+ const ALWAYS_ON_ADAPTERS = ['links', 'pnpm-audit', 'publint', 'attw', 'pack', 'smoke', 'snippets'];
40
43
  // The detection rule, shared by `start`'s probe and doctor's callout: some tool
41
44
  // slot beyond the always-on family has an adapter — i.e. checkride can see the
42
45
  // CODE (a tsconfig, a test runner, a linter…), not just the repo.
@@ -80,9 +80,11 @@ function buildDefaultScope(root, slug) {
80
80
  // `--body` reads the final-commit body from stdin (the single-quoted heredoc of
81
81
  // D34), so the pb-finish skill can compose a proportional close-out message. Returns
82
82
  // null when the flag is absent or stdin is empty — the commit then carries subject
83
- // only. A read error (no stdin attached) degrades to null rather than throwing.
83
+ // only. Reading fd 0 blocks until EOF, which the heredoc supplies; a read error (no
84
+ // stdin attached) degrades to null, and an interactive TTY — which would never send
85
+ // EOF — skips the read instead of hanging (twin of checkpoint.ts's guard).
84
86
  function bodyArg(args) {
85
- if (!args.includes('--body')) {
87
+ if (!args.includes('--body') || process.stdin.isTTY === true) {
86
88
  return null;
87
89
  }
88
90
  try {
@@ -4,8 +4,235 @@ All notable changes to this project are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.5.2] - 2026-07-19
8
+
9
+ ### Internal
10
+
11
+ - Split the release workflow into a gated `publish.yaml` (npm publish with
12
+ provenance, now paused behind a required-reviewer approval on the
13
+ `npm-publish` GitHub Environment) and a separate `release.yaml` (GitHub
14
+ Release from the CHANGELOG), matching the fascicle/plumbbob pattern so the
15
+ npm publish credential and the release-authoring permission never share a
16
+ job.
17
+
18
+ ## [0.5.1] - 2026-07-19
19
+
20
+ ### Added
21
+
22
+ - **A GitHub Pages marketing site** under `site/` — a homepage pitching the
23
+ elevator case for checkride, a docs hub aggregating the existing
24
+ `docs/*.md` guides into a concise sidebar reference, and a full
25
+ CLI/config/programmatic API reference — plus the GitHub Actions workflow
26
+ that deploys it.
27
+ - `docs/why.md` — the case for adopting checkride: what it's selling, the ROI
28
+ against ad-hoc scripts and task runners, and straight answers to the
29
+ objections that come up in an adoption conversation.
30
+
31
+ ## [0.5.0] - 2026-07-19
32
+
33
+ ### Contract
34
+
35
+ - **The `order` field is now a first-class, promised scheduling surface.** A
36
+ config entry's `order` accepts a **number** — a wave, where distinct wave
37
+ numbers run in ascending order with a barrier between them, checks sharing a
38
+ number run concurrently, and decimals sequence steps within a wave (`1` before
39
+ `1.1`) — or one of `first`, `last`, `middle`, `single`, `any`. It is honored
40
+ on every object-form entry: a slot's `{ use, order }` and a custom check
41
+ alike. `first`/`last` keep their exact historical meaning, pinned by a
42
+ backward-compat contract test. See `docs/contract.md` §"Check ordering and
43
+ concurrency".
44
+ - **Two deliberate default-placement changes**, both noted in `docs/contract.md`:
45
+ a config-only custom check with **no** `order` now defaults to `any` (the main
46
+ group) instead of the old implicit `last` — set `"order": "last"` to restore
47
+ the previous placement — and a catalogue-filling custom entry's `order`, which
48
+ earlier releases documented as ignored, is now **honored**. A sequential
49
+ default run is unaffected in verdicts and summary order; the difference is
50
+ visible only under concurrency or beside a numbered wave.
51
+ - **New `--concurrency <n>` flag.** Sets the size of the pool that runs a wave's
52
+ checks concurrently (`1` = sequential; the default is a conservative cap
53
+ derived from the CPU count). `--bail` overrides it: the run goes fully
54
+ sequential and a one-line stderr note reports that `--concurrency` was ignored
55
+ (the combination is safe, just slower — not a usage error). Additive to the
56
+ flag contract and contract-tested.
57
+ - **`total_duration_ms` is now the run's wall-clock duration.** Under
58
+ concurrency the summary's total is measured wall-to-wall rather than summed
59
+ across checks; the two are identical for any sequential run (including
60
+ `--bail`). The `checks` array stays in deterministic group order — the run's
61
+ scheduling sequence, never completion order. `schema_version` is unchanged.
62
+
63
+ ### Added
64
+
65
+ - **A publish-ready bundle of four opt-in slots** that take the definition of
66
+ done past static publishing lint (`publint`, `attw`) and out to the shipped
67
+ artifact. Each is a built-in or runs the consumer's own `build`/`tsc`, so
68
+ enabling them adds **zero devDependencies**:
69
+ - **`build`** (wave 10) runs the consumer's `build` script, so the artifact
70
+ checks below inspect fresh output rather than a stale `dist/`. An opted-in
71
+ `build` on a repo with no build script stands down as a skip, never a red
72
+ check.
73
+ - **`pack`** (wave 20) packs the tarball with a dry-run and fails if a required
74
+ file (a resolved `exports`/`main`/`types`/`bin` target, or `README`) is
75
+ missing or a forbidden one (`src/`, tests, `.ts` sources — the
76
+ `dist/**/*.d.ts` declarations excepted) is shipped. npm/pnpm only; yarn/bun
77
+ report **unavailable** until a per-manager adapter lands, like `security`.
78
+ - **`smoke`** (wave 20) imports every `exports` entry of the built package
79
+ through its own resolution map and asserts each declared value export is live
80
+ at runtime — a liveness check, not a type check.
81
+ - **`snippets`** (wave 20 / `any`) type-checks the fenced code blocks tagged
82
+ `<!-- snippet: check -->` in `README.md` and `docs/*.md`. The default
83
+ `snippets` adapter checks against source; a second `snippets-dist` adapter
84
+ checks against the built `.d.ts`. A slot opted in with zero tagged fences is
85
+ a hard error.
86
+
87
+ All four are opt-in (`--all`, `--include`, or config), so a default run is
88
+ byte-for-byte unchanged. They **order themselves**: `build` at wave 10 precedes
89
+ `pack`/`smoke`/`snippets`/`publint`/`attw` at wave 20, so `checkride --all`
90
+ builds before it inspects the artifact with no ordering config. `checkride
91
+ init` on a library can scaffold the bundle.
92
+
93
+ ### Changed
94
+
95
+ - **A dependency can now activate a slot, not just its config file.** Adapters
96
+ whose tool runs correctly with zero config — `oxlint` (lint), `knip` (dead),
97
+ `vitest` (test), `cspell` (spell), and `prettier` (the opt-in format slot) —
98
+ now also activate when the package appears in `dependencies`/`devDependencies`,
99
+ as a backup to the detect-file signal. A repo that installed one of these but
100
+ never wrote its config file gains that check on upgrade; `doctor` names which
101
+ signal matched. This widens the default run for those slots and is a
102
+ deliberate, noted behavior change.
103
+ - **Checks now run concurrently within a wave by default.** The orchestrator
104
+ schedules the wave sequence — `first`s, the numeric line ascending (equal
105
+ values through a bounded pool, a barrier between distinct values), `single`s
106
+ exclusively, then `last`s — instead of strictly one-at-a-time cheapest-first.
107
+ `--bail` keeps the sequential fail-fast path. The catalogue ships ordered so
108
+ existing default runs produce the same verdicts and the same summary order as
109
+ before, only faster; `mutation` runs as a `single` (exclusive) because Stryker
110
+ saturates every core and races the real test run's cache.
111
+ - **`mutation` now ships uncapped (`timeout: 0`) by default.** A real Stryker run
112
+ legitimately outlives the 600s per-check cap; because `mutation` is opt-in and
113
+ never part of the definition-of-done gate the cap protects, its adapter runs to
114
+ completion instead of tripping a timeout under `checkride --all`. Every other
115
+ slot keeps the safe-by-default cap; override per check or globally with
116
+ `timeout`.
117
+
118
+ ## [0.4.3] - 2026-07-18
119
+
120
+ ### Internal
121
+
122
+ - Refactored for maintainability: decomposed high-complexity functions across
123
+ the orchestrator, `init`, `doctor`, `config`, and the baseline code into small
124
+ single-purpose helpers, extracted shared option/stanza helpers, and broke
125
+ internal import cycles. No change to runtime behavior or the public API.
126
+ - Zeroed the project's grandfathered debt: cleared the `lint`, `dead`, `dupes`,
127
+ and `health` baselines and removed checkride's own `checkride.baseline.json`,
128
+ so the repo passes a full `pnpm check` with no suppressions.
129
+ - The repo's own default `pnpm check` now also runs the `dupes` and `health`
130
+ slots. Mutation stays opt-in (`pnpm mutation`): a cold full pass has no warm
131
+ incremental cache in CI and would time out the gate.
132
+
133
+ ## [0.4.2] - 2026-07-18
134
+
135
+ ### Fixed
136
+
137
+ - **The `dead` (fallow) slot now actually gates.** checkride ran fallow in JSON
138
+ mode, which exits `0` even with findings, so the slot reported ✔ while fallow
139
+ had real issues. checkride now reads fallow's JSON report and derives the
140
+ verdict from the issue count instead of the (unreliable) exit code — so the
141
+ slot fails `pnpm check` on new findings and passes only when clean or fully
142
+ baselined. An **unrecognized fallow report fails loudly** (explicit
143
+ "unsupported schema_version" / "unrecognized kind") rather than passing
144
+ silently.
145
+ - **fallow ≥ 3.5 support.** The dead-code parser reads fallow's current
146
+ `schema_version` 7 JSON (2.x emitted schema 4 with an incompatible layout).
147
+ The pinned devDependency moves from `fallow@2.48.0` to `fallow@3.5.0`.
148
+
149
+ ### Added
150
+
151
+ - **`checkride baseline` now grandfathers fallow findings.** A fingerprint
152
+ extractor keys each fallow finding by kind + file + symbol, so fallow slots
153
+ participate in `checkride.baseline.json` like `lint`/`struct`/`spell` (and
154
+ ratchet the same way). Repos that prefer fallow's native `--save-baseline`
155
+ suppression can still use it — see `docs/tools.md`.
156
+ - **New opt-in `dupes` and `health` slots.** fallow's duplication and
157
+ complexity analyses are now first-class checks (`--include dupes,health` or
158
+ `"dupes": "fallow"` in config), each with gating and baselines. They stay
159
+ opt-in so adopting checkride never fails a repo on duplication/complexity it
160
+ never signed up for.
161
+
162
+ ## [0.4.1] - 2026-07-11
163
+
164
+ ### Internal
165
+
166
+ - Docs refresh: updated the reported mutation score to 69% and bumped the README `$schema` example to v0.4.0.
167
+
168
+ ## [0.4.0] - 2026-07-11
169
+
170
+ ### Contract
171
+
172
+ - **An unknown slot name is now a usage error.** An unrecognized slot in
173
+ `--only`, `--skip`, or `--include` (e.g. `checkride --only lints`) exits **2**,
174
+ naming the bad slot and the valid set (catalogue slots plus config
175
+ custom-check names). It previously matched nothing and exited **0** — a typo
176
+ could silently disable the gate, the worst vacuous green in a definition-of-done
177
+ check. See `docs/contract.md` §CLI.
178
+
179
+ ### Added
180
+
181
+ - New-project `checkride init` refuses to overwrite existing files, listing
182
+ every collision, and writes nothing; `--force` overrides. Existing-project
183
+ mode (additive-only) is unchanged.
184
+ - Per-command `--help` (`checkride init --help`, etc.); new-project `init` ends
185
+ by printing the next command to run; `checkride baseline` now rejects stray
186
+ flags instead of ignoring them.
187
+
188
+ ### Changed
189
+
190
+ - `checkride init` scaffolds an exact checkride version (no caret), and the
191
+ README install uses `pnpm add -D -E checkride`, matching the pre-1.0
192
+ exact-pin policy consumers are told to follow.
193
+ - Malformed consumer JSON (`.claude/settings.json`, a project `package.json`)
194
+ now produces an error naming the offending file instead of a bare stack trace.
195
+
196
+ ### Fixed
197
+
198
+ - **Interrupts no longer orphan checks.** Ctrl-C (SIGINT) or SIGTERM on a
199
+ running `checkride` is forwarded to every in-flight check and group-kills its
200
+ whole process tree before exit, then re-raises so the shell still sees the
201
+ conventional signal exit (130/143). Since checks run in detached process
202
+ groups (for the timeout kill), a plain interrupt previously left them running.
203
+ - `doctor` distinguishes a version probe that **timed out** from one that
204
+ **could not be parsed** (30s probe), so a slow `pnpm --version` is no longer
205
+ misdiagnosed.
206
+ - `checkride init --baseline --dry-run` no longer writes a real
207
+ `checkride.baseline.json` — a dry run now truly writes nothing.
208
+ - A timed-out check's whole process group is killed (wrapper-spawned
209
+ grandchildren included), and output is captured with a UTF-8 decoder so a
210
+ multibyte character split across read chunks survives intact.
211
+ - `checkride fix` runs under the detected package manager (e.g. `npx` under
212
+ npm), matching the run path instead of assuming pnpm.
213
+ - A slot's stale `.check/` artifacts are cleared before it re-runs, so a leaner
214
+ or empty run can't leave the previous run's output behind as authoritative.
215
+
216
+ ### Internal
217
+
218
+ - Docs pass: README restructure linking all six `docs/` files and splitting the
219
+ existing-repo vs new-project install paths, getting-started/tools sync,
220
+ reconciliation of the "locked by `test/contract/`" claim with real tests, and
221
+ a batch of drift corrections.
222
+ - Release automation: tag↔version guard on release, CI concurrency group,
223
+ security-only Dependabot; `publint` and `attw` added as dev checks and
224
+ dogfooded; explicit test timeouts for slow-spawn machines; npm publishing
225
+ switched to Trusted Publishing (OIDC).
226
+
7
227
  ## [0.3.0] - 2026-07-10
8
228
 
229
+ ### Contract
230
+
231
+ - Three additions to the promised surfaces, each recorded in `docs/contract.md`
232
+ and locked by `test/contract/`: the `summary.json` `checks_run` field, the
233
+ `--strict` flag, and the `DEFAULT_TIMEOUT_SECONDS` export. (Heading added
234
+ retroactively — the full entries are under **Added** below.)
235
+
9
236
  ### Added
10
237
 
11
238
  - **Vacuous-green signal.** `summary.json` gains a top-level `checks_run` count
@@ -238,6 +465,13 @@ The first real release. (`0.0.0` was a name-claim placeholder.)
238
465
  - Flags: `--only`, `--skip`, `--bail`, `--json`, `--changed`, `--all`,
239
466
  `--include`.
240
467
 
468
+ [0.5.2]: https://www.npmjs.com/package/checkride/v/0.5.2
469
+ [0.5.1]: https://www.npmjs.com/package/checkride/v/0.5.1
470
+ [0.5.0]: https://www.npmjs.com/package/checkride/v/0.5.0
471
+ [0.4.3]: https://www.npmjs.com/package/checkride/v/0.4.3
472
+ [0.4.2]: https://www.npmjs.com/package/checkride/v/0.4.2
473
+ [0.4.1]: https://www.npmjs.com/package/checkride/v/0.4.1
474
+ [0.4.0]: https://www.npmjs.com/package/checkride/v/0.4.0
241
475
  [0.3.0]: https://www.npmjs.com/package/checkride/v/0.3.0
242
476
  [0.2.1]: https://www.npmjs.com/package/checkride/v/0.2.1
243
477
  [0.2.0]: https://www.npmjs.com/package/checkride/v/0.2.0
@@ -18,7 +18,9 @@ both halves of that problem.
18
18
  2. **Structured boundaries.** A module is an encapsulation boundary with a
19
19
  narrow public surface. When one grows internals worth hiding it becomes a
20
20
  folder whose only public surface is its `index.ts`, and siblings reach only
21
- that index — never the internals. Enforced mechanically, boundaries keep
21
+ that index — never the internals. These are **deep modules** — Ousterhout's
22
+ term for a small interface hiding a substantial implementation (*A
23
+ Philosophy of Software Design*). Enforced mechanically, boundaries keep
22
24
  agents inside lanes and let humans and agents work in parallel with minimal
23
25
  merge conflicts.
24
26
 
@@ -29,33 +31,79 @@ meta-runner expensive to extend.
29
31
 
30
32
  ## Install
31
33
 
34
+ For an **existing repository**, install checkride (exact-pinned — see the
35
+ [pin policy](./docs/contract.md)) and let `init` adopt the tools you already
36
+ have:
37
+
38
+ ```bash
39
+ pnpm add -D -E checkride
40
+ pnpm exec checkride init
41
+ ```
42
+
43
+ For a **new project**, run `init` in an empty directory — no install first;
44
+ `dlx` fetches checkride, and the generated `package.json` pins it:
45
+
32
46
  ```bash
33
- pnpm add -D checkride
34
- pnpm exec checkride init # set up a project (new or existing, auto-detected)
47
+ pnpm dlx checkride init --shape flat --name my-app
48
+ pnpm install
49
+ pnpm check
35
50
  ```
36
51
 
37
- `init` writes a `"check": "checkride"` alias, so daily usage is `pnpm check`
52
+ Either way — `init` auto-detects which case it is in — it writes a
53
+ `"check": "checkride"` alias, so daily usage is `pnpm check`
38
54
  regardless of the tool's name. It also writes the agent contract: an AGENTS.md
39
55
  stanza (the "exit 0 = done" rule) and a Claude Code **Stop hook** in
40
56
  `.claude/settings.json` that blocks an agent from finishing while the pipeline is
41
57
  red. The hook uses your detected package manager (`pnpm`/`npm`/`yarn`/`bun run
42
- check`); skip it with `--no-hook`, or add both to a repo you already set up with
43
- `checkride agent-setup`.
58
+ check`); skip it with `--no-hook`, or add all of this — alias, stanza, and hook —
59
+ to a repo you already set up with `checkride agent-setup`.
60
+
61
+ ## Docs
62
+
63
+ Task-focused guides live in [docs/](./docs/README.md):
64
+
65
+ - [Why checkride](./docs/why.md) — the case for adopting it: what it's
66
+ selling, the ROI versus ad-hoc scripts or a task runner, and answers to the
67
+ common objections.
68
+ - [Getting started](./docs/getting-started.md) — prerequisites, adding
69
+ checkride to a project (new or existing), your first run, and the daily loop.
70
+ - [Cheat sheet](./docs/cheatsheet.md) — one-screen reference for commands,
71
+ flags, npm-script aliases, and the `.check/` output files.
72
+ - [Tools and installation](./docs/tools.md) — what each pipeline check runs,
73
+ and how to install a missing tool when `doctor` reports one.
74
+ - [Running in CI](./docs/ci.md) — a copy-paste GitHub Actions recipe (and
75
+ npm/yarn/bun variants), and why gates should pass `--strict`.
76
+ - [The contract](./docs/contract.md) — the surfaces consumers may rely on:
77
+ exit codes, the `summary.json` schema discipline, flags, exports, and the
78
+ pin policy.
79
+ - [Reliability](./docs/reliability.md) — why checkride is safe to build a
80
+ gate on.
81
+
82
+ The rest of this README is the reference: the command surface, the pipeline
83
+ model, the `.check/` output contract, configuration, and the baseline.
44
84
 
45
85
  ## Commands
46
86
 
87
+ The pipeline is a sequence of **slots** — roles like `types`, `lint`, `test` —
88
+ each filled by an **adapter**, the concrete tool that runs it (the full
89
+ catalogue is [below](#the-pipeline-slots-and-adapters)). The commands refer to
90
+ both:
91
+
47
92
  ```text
48
93
  checkride Run the default checks. Exit 0 pass / 1 fail / 2 error.
49
94
  --only <a,b> --skip <a,b> --bail --json --changed --all --include <a,b>
50
95
  --digest --strict (zero checks running = exit 2 — use wherever checkride gates)
51
96
  checkride init Set up a project (new or existing — auto-detected).
52
- --shape flat|monorepo|hybrid --name <n> --scope <@s> --license <id> --dry-run
97
+ --shape flat|monorepo|hybrid --name <n> --scope <@s> --license <id> --author <a>
98
+ --add <a,b> (existing mode) scaffold blessed configs for the named empty slots
53
99
  --baseline (existing mode) grandfather current debt instead of disabling slots
100
+ --force (new mode) overwrite existing files instead of refusing
54
101
  --no-hook skip writing the Claude Code Stop hook
102
+ --dry-run plan only; write nothing
55
103
  checkride doctor Verify environment + every slot's status (read-only, exit 0/1).
56
104
  checkride fix Run every active adapter's fix command (oxlint --fix, ...).
57
105
  checkride baseline Record current diagnostics as a committed baseline.
58
- checkride agent-setup Add the AGENTS.md stanza + Claude Code Stop hook to a repo.
106
+ checkride agent-setup Add the "check" alias, AGENTS.md stanza + Stop hook to a repo.
59
107
  --no-hook skip the Stop hook (write only the stanza)
60
108
  ```
61
109
 
@@ -80,7 +128,9 @@ generates config for the blessed default.
80
128
  | `format` | Formatting (opt-in) | `prettier` | `biome` |
81
129
  | `lint` | Linting | `oxlint` | `biome`, `eslint`|
82
130
  | `struct` | Structural rules (deep modules) | `ast-grep` | — |
83
- | `dead` | Dead code, deps, cycles, boundaries | `fallow` | `knip` |
131
+ | `dead` | Dead code, deps, cycles, boundaries | `fallow` (dead-code)| `knip` |
132
+ | `dupes` | Code duplication (opt-in) | `fallow` (dupes) | — |
133
+ | `health` | Complexity / maintainability (opt-in) | `fallow` (health) | — |
84
134
  | `test` | Tests + coverage | `vitest` | `jest` |
85
135
  | `docs` | Markdown lint | `markdownlint-cli2` | — |
86
136
  | `links` | Relative markdown links resolve | built-in | — |
@@ -136,7 +186,7 @@ fields are additive-only under a given `schema_version`.
136
186
  "schema_version": 1,
137
187
  "timestamp": "…",
138
188
  "ok": true,
139
- "checks_run": 8, // checks that actually executed; 0 + ok = vacuous green
189
+ "checks_run": 8, // checks that actually executed
140
190
  "total_duration_ms": 4200,
141
191
  "checks": [
142
192
  { "name": "lint", "adapter": "oxlint", "description": "…",
@@ -146,10 +196,11 @@ fields are additive-only under a given `schema_version`.
146
196
  ```
147
197
 
148
198
  `ok: true` with `checks_run: 0` means **nothing was verified** — every slot
149
- sat out (no tool detected, disabled, or skipped). The run warns loudly on
150
- stderr when that happens, and `--strict` turns it into exit 2, so a gate can
151
- never mistake "nothing ran" for "everything passed". Anything that gates on
152
- checkride (CI, commit hooks) should pass `--strict`.
199
+ sat out (no tool detected, disabled, or skipped). That state is a **vacuous
200
+ green**: a pass that verified nothing. The run warns loudly on stderr when it
201
+ happens, and `--strict` turns it into exit 2, so a gate can never mistake
202
+ "nothing ran" for "everything passed". Anything that gates on checkride (CI,
203
+ commit hooks) should pass `--strict`.
153
204
 
154
205
  - `<slot>.json` — the raw tool JSON when stdout parses as JSON; otherwise
155
206
  `<slot>.stdout.txt` / `<slot>.stderr.txt`. Tools that write their own files
@@ -161,10 +212,12 @@ runs with no baseline, so `schema_version` is unchanged.
161
212
 
162
213
  - `digest.md` — written only under `--digest`: a **token-bounded** Markdown
163
214
  excerpt of the *failing* slots, so an agent working through a big red repo
164
- reads a capped index instead of every raw file. Each section lists the first few
215
+ reads a capped index instead of every raw file. Each section lists the first
165
216
  findings (reusing the baseline fingerprint extractors, or a tail of raw text
166
217
  for slots without one) and links the authoritative `.check/<slot>.json`, which
167
- is never modified. It **truncates, never normalizes**; a green run leaves no
218
+ is never modified. The bound: **ten findings per slot, 8 kB overall** —
219
+ roughly two thousand tokens; past the cap, remaining failing slots are named
220
+ but not rendered. It **truncates, never normalizes**; a green run leaves no
168
221
  digest (any stale one is removed), so its presence always means "this run
169
222
  failed". It is a file, never stdout — the machine-output split holds.
170
223
 
@@ -178,14 +231,14 @@ slot's raw output for structured diagnostics. On a large repo, `--digest` writes
178
231
 
179
232
  ```jsonc
180
233
  {
181
- "$schema": "https://raw.githubusercontent.com/robmclarty/checkride/v0.2.1/schema/checkride.config.schema.json",
234
+ "$schema": "https://raw.githubusercontent.com/robmclarty/checkride/v0.4.0/schema/checkride.config.schema.json",
182
235
  "extends": "@acme/checkride-preset", // inherit a shared preset, then override below
183
236
  "timeout": 1200, // global per-check timeout in seconds (default: 600)
184
237
  "checks": {
185
238
  "format": "prettier", // enable the opt-in format slot (blessed: prettier)
186
239
  "lint": "biome", // pick an alternate adapter
187
240
  "spell": false, // disable a slot
188
- "test": { "use": "vitest", "timeout": 0, "changedArgs": ["--changed", "origin/master"] },
241
+ "test": { "use": "vitest", "timeout": 0, "changedArgs": ["--changed", "origin/main"] },
189
242
  "tidy": { // a bespoke custom check that runs FIRST, ahead of the built-ins
190
243
  "command": "pnpm",
191
244
  "args": ["exec", "some-formatter", "--write"],
@@ -199,21 +252,15 @@ slot's raw output for structured diagnostics. On a large repo, `--digest` writes
199
252
  }
200
253
  ```
201
254
 
255
+ ### The `$schema` pointer
256
+
202
257
  The `"$schema"` pointer is optional but recommended: it turns on validation and
203
258
  autocompletion for `checkride.config.json` in editors that understand JSON
204
259
  Schema (VS Code and friends). `checkride init` writes a version-pinned pointer
205
260
  into the config it generates; the schema itself ships in the package at
206
261
  [`schema/checkride.config.schema.json`](./schema/checkride.config.schema.json).
207
262
 
208
- Use `"extends"` to inherit a shared preset — a file path (`"./base.json"`) or an
209
- installed package (`"@acme/checkride-preset"`), or an array of them to layer
210
- several. Bases merge left to right and your local config wins over all of them:
211
- objects deep-merge (so overriding one field of a check keeps the rest), while
212
- arrays and scalars replace outright — arrays are **not** concatenated. Pair it
213
- with `detect` above to publish one org-wide preset that stays safe across repos
214
- that don't all use the same tools. An `extends` that can't be found, or a config
215
- that extends itself in a loop, fails fast with
216
- `invalid checkride.config.json: <reason>`.
263
+ ### Custom checks
217
264
 
218
265
  A custom check (one keyed by a name that isn't a built-in slot) runs *after* the
219
266
  built-in catalogue by default. Set `"order": "first"` to run it ahead of every
@@ -227,6 +274,8 @@ enable it with `"format": "prettier"` and `checkride fix` writes formatting for
227
274
  The `order: "first"` custom-check hatch coexists with it, for a one-off formatter the
228
275
  slot doesn't cover; the slot didn't retire it.
229
276
 
277
+ ### Gating a custom check with `detect`
278
+
230
279
  Add `"detect": ["<file>", …]` to a custom check to gate it on marker files: it
231
280
  runs only when at least one listed file exists in the repo, and is skipped —
232
281
  skipped, not failed — otherwise. This keeps a shared config safe across repos
@@ -235,6 +284,20 @@ stands down instead of lighting up red. `detect` applies only to custom checks
235
284
  that run alongside the catalogue; a custom check that fills a built-in slot
236
285
  always runs.
237
286
 
287
+ ### Sharing presets with `extends`
288
+
289
+ Use `"extends"` to inherit a shared preset — a file path (`"./base.json"`) or an
290
+ installed package (`"@acme/checkride-preset"`), or an array of them to layer
291
+ several. Bases merge left to right and your local config wins over all of them:
292
+ objects deep-merge (so overriding one field of a check keeps the rest), while
293
+ arrays and scalars replace outright — arrays are **not** concatenated. Pair it
294
+ with `detect` above to publish one org-wide preset that stays safe across repos
295
+ that don't all use the same tools. An `extends` that can't be found, or a config
296
+ that extends itself in a loop, fails fast with
297
+ `invalid checkride.config.json: <reason>`.
298
+
299
+ ### Timeouts
300
+
238
301
  A per-check timeout guards against a hung tool, and it is **on by default**: a
239
302
  definition-of-done gate that can hang forever fails its one job on the worst
240
303
  day. The default cap is a generous **600 seconds per check**; a check that hits
@@ -285,10 +348,22 @@ Once it exists, every normal run is **baseline-aware**:
285
348
  - Never add to the baseline to make a check pass; fix the finding, or re-run
286
349
  `checkride baseline` deliberately to re-grandfather.
287
350
 
351
+ Two operational notes. **Merge conflicts:** the file is canonical (slots and
352
+ keys are sorted), so parallel branches usually merge cleanly; when they do
353
+ conflict, resolve by keeping both sides' entries and running a full
354
+ `checkride` — the ratchet prunes anything already fixed, so an over-generous
355
+ union self-heals, while a dropped entry that still fails simply resurfaces as
356
+ a red check. **Deliberate re-baseline:** `checkride baseline` re-records
357
+ *everything* currently failing — including brand-new debt you might rather
358
+ fix — so treat re-running it as a reviewed change: do it deliberately (say,
359
+ after adopting a stricter rule set) and read the `checkride.baseline.json`
360
+ diff in the PR like code.
361
+
288
362
  Only slots whose tool has a fingerprint extractor participate (currently `lint` via
289
- oxlint, `struct` via ast-grep, `spell` via cspell); other slots (`types`, `dead`,
290
- `test`, …) never appear in the baseline. A crash or empty output is never masked —
291
- a slot only goes green when there are findings and all of them are grandfathered.
363
+ oxlint, `struct` via ast-grep, `spell` via cspell, and the fallow slots `dead`/`dupes`/
364
+ `health`); other slots (`types`, `test`, …) never appear in the baseline. A crash or
365
+ empty output is never masked — a slot only goes green when there are findings and all
366
+ of them are grandfathered.
292
367
 
293
368
  To adopt on an existing repo, `checkride init --baseline` grandfathers today's
294
369
  failing (fingerprintable) slots into the baseline and keeps them enabled, instead of
@@ -307,6 +382,14 @@ disable.
307
382
 
308
383
  Every generated shape is green out of the box — an end-to-end test enforces it.
309
384
 
385
+ At runtime, checkride itself is workspace-agnostic: whatever the shape, it runs
386
+ each tool **once, from the repo root** — one pipeline, one `.check/`. Workspace
387
+ awareness comes from the tools' own configs, which is what the monorepo scaffold
388
+ sets up: `tsc --build` follows the root tsconfig's project references, and
389
+ vitest, oxlint, ast-grep, and the rest walk the whole tree. There is no
390
+ per-package orchestration and no way to check a single package — narrow a run
391
+ with `--only`/`--changed`, not per-directory.
392
+
310
393
  ## Conventions
311
394
 
312
395
  Module boundaries, enforced by `ast-grep` and `fallow`:
@@ -332,13 +415,33 @@ The promised surfaces (exit codes, `summary.json` shape, flags, exports) are
332
415
  locked by a dedicated [contract suite](./test/contract/) — see
333
416
  [docs/contract.md](./docs/contract.md). Beyond line coverage (enforced at 70%),
334
417
  the test suite is itself tested: Stryker mutation testing runs with a hard
335
- floor of 55; the current mutation score is **67%** (`pnpm mutation` to
418
+ floor of 55; the current mutation score is **69%** (`pnpm mutation` to
336
419
  reproduce).
337
420
 
338
421
  See [AGENTS.md](./AGENTS.md) for the contract agents follow,
339
422
  [CONTRIBUTING.md](./CONTRIBUTING.md) for the release ritual, and
340
423
  [CHANGELOG.md](./CHANGELOG.md) for release notes.
341
424
 
425
+ ## Programmatic API
426
+
427
+ The CLI is the primary interface, but every command is also a function. Import
428
+ from the package root to run the pipeline inside your own tooling; the result
429
+ carries the same summary written to `.check/summary.json`, plus the exit code the
430
+ CLI would return:
431
+
432
+ <!-- snippet: check -->
433
+ ```ts
434
+ import { runChecks, type RunResult } from 'checkride';
435
+
436
+ const result: RunResult = await runChecks({ cwd: process.cwd(), strict: true });
437
+ process.exitCode = result.exitCode;
438
+ ```
439
+
440
+ `runChecks` sits alongside `runInit`, `runDoctor`, `runFix`, and the
441
+ `loadConfig`/`resolveChecks` pair; the adapter registry (`SLOTS`, `ADAPTERS`) and
442
+ every public type are re-exported too. The full surface is the package's
443
+ `exports`, and it is part of the [contract](./docs/contract.md).
444
+
342
445
  ## License
343
446
 
344
447
  [MIT](./LICENSE)