plumbbob 0.5.3 → 0.6.1

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 (81) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +9 -0
  3. package/dist/cli-core.js +4 -0
  4. package/dist/lib/agents.js +498 -0
  5. package/dist/lib/intent.js +98 -8
  6. package/dist/lib/settings.js +17 -0
  7. package/dist/lib/sidecar.js +39 -2
  8. package/dist/verbs/agent.js +312 -0
  9. package/dist/verbs/checkpoint.js +2 -1
  10. package/dist/verbs/doctor.js +96 -3
  11. package/dist/verbs/start.js +16 -3
  12. package/dist/verbs/status.js +64 -2
  13. package/node_modules/checkride/CHANGELOG.md +139 -0
  14. package/node_modules/checkride/LICENSE +21 -0
  15. package/node_modules/checkride/README.md +179 -0
  16. package/node_modules/checkride/dist/adapters.d.ts +56 -0
  17. package/node_modules/checkride/dist/adapters.d.ts.map +1 -0
  18. package/node_modules/checkride/dist/adapters.js +219 -0
  19. package/node_modules/checkride/dist/adapters.js.map +1 -0
  20. package/node_modules/checkride/dist/cli.d.ts +29 -0
  21. package/node_modules/checkride/dist/cli.d.ts.map +1 -0
  22. package/node_modules/checkride/dist/cli.js +227 -0
  23. package/node_modules/checkride/dist/cli.js.map +1 -0
  24. package/node_modules/checkride/dist/config.d.ts +74 -0
  25. package/node_modules/checkride/dist/config.d.ts.map +1 -0
  26. package/node_modules/checkride/dist/config.js +126 -0
  27. package/node_modules/checkride/dist/config.js.map +1 -0
  28. package/node_modules/checkride/dist/doctor.d.ts +76 -0
  29. package/node_modules/checkride/dist/doctor.d.ts.map +1 -0
  30. package/node_modules/checkride/dist/doctor.js +299 -0
  31. package/node_modules/checkride/dist/doctor.js.map +1 -0
  32. package/node_modules/checkride/dist/index.d.ts +18 -0
  33. package/node_modules/checkride/dist/index.d.ts.map +1 -0
  34. package/node_modules/checkride/dist/index.js +13 -0
  35. package/node_modules/checkride/dist/index.js.map +1 -0
  36. package/node_modules/checkride/dist/init.d.ts +64 -0
  37. package/node_modules/checkride/dist/init.d.ts.map +1 -0
  38. package/node_modules/checkride/dist/init.js +489 -0
  39. package/node_modules/checkride/dist/init.js.map +1 -0
  40. package/node_modules/checkride/dist/links.d.ts +23 -0
  41. package/node_modules/checkride/dist/links.d.ts.map +1 -0
  42. package/node_modules/checkride/dist/links.js +125 -0
  43. package/node_modules/checkride/dist/links.js.map +1 -0
  44. package/node_modules/checkride/dist/orchestrator.d.ts +96 -0
  45. package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -0
  46. package/node_modules/checkride/dist/orchestrator.js +234 -0
  47. package/node_modules/checkride/dist/orchestrator.js.map +1 -0
  48. package/node_modules/checkride/package.json +76 -0
  49. package/node_modules/checkride/templates/flat/fallow.toml +26 -0
  50. package/node_modules/checkride/templates/flat/pnpm-workspace.yaml +6 -0
  51. package/node_modules/checkride/templates/flat/tsconfig.json +9 -0
  52. package/node_modules/checkride/templates/hybrid/fallow.toml +42 -0
  53. package/node_modules/checkride/templates/hybrid/pnpm-workspace.yaml +6 -0
  54. package/node_modules/checkride/templates/hybrid/tsconfig.json +12 -0
  55. package/node_modules/checkride/templates/monorepo/fallow.toml +44 -0
  56. package/node_modules/checkride/templates/monorepo/pnpm-workspace.yaml +7 -0
  57. package/node_modules/checkride/templates/monorepo/tsconfig.json +7 -0
  58. package/node_modules/checkride/templates/shared/cspell.json +35 -0
  59. package/node_modules/checkride/templates/shared/gitignore +10 -0
  60. package/node_modules/checkride/templates/shared/markdownlint-cli2.jsonc +11 -0
  61. package/node_modules/checkride/templates/shared/npmrc +1 -0
  62. package/node_modules/checkride/templates/shared/oxlintrc.json +33 -0
  63. package/node_modules/checkride/templates/shared/rules/no-class.yml +8 -0
  64. package/node_modules/checkride/templates/shared/rules/no-deep-sibling-import.yml +15 -0
  65. package/node_modules/checkride/templates/shared/rules/no-default-export.yml +9 -0
  66. package/node_modules/checkride/templates/shared/rules/require-js-extension.yml +21 -0
  67. package/node_modules/checkride/templates/shared/sgconfig.yml +2 -0
  68. package/node_modules/checkride/templates/shared/tsconfig.base.json +28 -0
  69. package/node_modules/checkride/templates/shared/vitest.config.ts.template +27 -0
  70. package/package.json +4 -1
  71. package/skills/pb-build/SKILL.md +42 -7
  72. package/skills/pb-finish/SKILL.md +1 -1
  73. package/skills/pb-harvest/SKILL.md +1 -1
  74. package/skills/pb-park/SKILL.md +1 -1
  75. package/skills/pb-plan/SKILL.md +25 -4
  76. package/skills/pb-refine/SKILL.md +1 -1
  77. package/skills/pb-revert/SKILL.md +1 -1
  78. package/skills/pb-spike/SKILL.md +1 -1
  79. package/skills/pb-status/SKILL.md +1 -1
  80. package/skills/pb-step/SKILL.md +10 -2
  81. package/skills/pb-verify/SKILL.md +21 -6
@@ -15,7 +15,7 @@ export function start(cwd, args) {
15
15
  const allowDirty = args.includes('--allow-dirty');
16
16
  const local = args.includes('--local');
17
17
  const title = (positionals[0] ?? '').trim();
18
- const slug = (flagValue(args, '--slug') ?? slugify(title)).trim();
18
+ const slug = (flagValue(args, '--slug') ?? datedSlug(title)).trim();
19
19
  if (title.length === 0) {
20
20
  process.stderr.write('plumbbob: start needs a title. Try: plumbbob start "what you are building".\n');
21
21
  return 1;
@@ -55,8 +55,8 @@ export function start(cwd, args) {
55
55
  // D26: `--local` keeps today's fully-untracked flat layout (whole `.plumbbob/`
56
56
  // excluded); the default plants a tracked, PR-riding `builds/<slug>/` folder
57
57
  // (D26) and points the per-worktree cursor at it (D28), excluding only control
58
- // files (D17). The slug is validated unique above the CLI refuses, never
59
- // suffixes (D38).
58
+ // files (D17). The slug is date-prefixed when derived (see datedSlug) and
59
+ // validated unique above — the CLI refuses, never suffixes (D38).
60
60
  let intentLocation;
61
61
  if (local) {
62
62
  writeFileSync(checkpointsPath(root), `baseline ${sha}\n`);
@@ -78,6 +78,19 @@ export function start(cwd, args) {
78
78
  process.stdout.write(`plumbbob: started "${title}" — baseline ${sha.slice(0, 9)}. Frame and decide in ${intentLocation}; \`build\` a step once the decisions are made.\n`);
79
79
  return 0;
80
80
  }
81
+ // Derived slugs carry a `YYYY-MM-DD-` prefix (local time) so `builds/` sorts
82
+ // chronologically under `listBuilds`' plain lexical sort — ordering by
83
+ // construction, not by titling convention. An explicit `--slug` stays verbatim
84
+ // (D38: the CLI never rewrites what the caller chose). An untitleable title
85
+ // yields `''` so the empty-slug guard fires instead of minting a date-only slug.
86
+ function datedSlug(title) {
87
+ const base = slugify(title);
88
+ if (base.length === 0)
89
+ return '';
90
+ const now = new Date();
91
+ const pad = (n) => String(n).padStart(2, '0');
92
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${base}`;
93
+ }
81
94
  // Read the value that follows a `--flag` in argv (e.g. `--slug my-build`), or
82
95
  // undefined when the flag is absent or trails with no value.
83
96
  function flagValue(args, flag) {
@@ -2,8 +2,10 @@
2
2
  // Read-only, always exits 0. Skills pre-inject this output to gate their own
3
3
  // behavior, so the `NO ACTIVE SESSION` sentinel is kept exact.
4
4
  import { readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
5
6
  import { findRepoRoot } from "../lib/git.js";
6
- import { buildLogPath, checkpointsPath, hasSession, inSpike, intentPath, listBuilds, resolveBuild, stepPath, } from "../lib/sidecar.js";
7
+ import { SLOTS, readHarnessFile, resolveAgent, } from "../lib/agents.js";
8
+ import { buildFolder, buildLogPath, checkpointsPath, hasSession, inSpike, intentPath, listBuilds, resolveBuild, stepPath, } from "../lib/sidecar.js";
7
9
  import { formatOrientation, orient } from "../lib/orient.js";
8
10
  function readOr(path) {
9
11
  try {
@@ -38,6 +40,66 @@ export function status(cwd, args = []) {
38
40
  inFlight: /^\d+$/.test(inFlightRaw) ? Number(inFlightRaw) : null,
39
41
  spiking: inSpike(root, slug),
40
42
  });
41
- process.stdout.write(`${formatOrientation(orientation)}\n`);
43
+ const lines = [formatOrientation(orientation), ...harnessSection(root, slug)];
44
+ process.stdout.write(`${lines.join('\n')}\n`);
42
45
  return 0;
43
46
  }
47
+ // The active build's harness bindings (D48): the agents bound to each slot, per
48
+ // `defaults` and per step, with a warning on any bound agent that does not resolve
49
+ // (D54 — a teammate can lack a personal agent and the loop still works). Returns []
50
+ // when the build has no harness.json (a clean no-op) or the file binds nothing, so
51
+ // the dashboard stays uncluttered for builds that use no agents. A broken
52
+ // harness.json surfaces its parse error rather than hiding.
53
+ function harnessSection(root, slug) {
54
+ const parsed = readHarnessFile(join(buildFolder(root, slug), 'harness.json'));
55
+ if (parsed === null)
56
+ return [];
57
+ if (!parsed.ok)
58
+ return ['', `harness bindings: ✗ ${parsed.error}`];
59
+ const harness = parsed.harness;
60
+ const rows = [];
61
+ for (const frag of slotFragments(harness.defaults))
62
+ rows.push(` defaults · ${frag}`);
63
+ for (const [n, binding] of [...harness.steps.entries()].sort((a, b) => a[0] - b[0])) {
64
+ for (const frag of slotFragments(binding.bindings))
65
+ rows.push(` step ${n} · ${frag}`);
66
+ if (binding.note.length > 0)
67
+ rows.push(` step ${n} · note: ${binding.note}`);
68
+ }
69
+ if (rows.length === 0)
70
+ return [];
71
+ return ['', 'harness bindings:', ...rows, ...bindingWarnings(root, harness)];
72
+ }
73
+ // Render a SlotBindings as `slot: name1, name2` fragments, in slot order. A slot
74
+ // bound to no agents (an explicit "override to none") is omitted — there is nothing
75
+ // to run and nothing to name.
76
+ function slotFragments(bindings) {
77
+ const out = [];
78
+ for (const slot of SLOTS) {
79
+ const names = bindings[slot];
80
+ if (names !== undefined && names.length > 0)
81
+ out.push(`${slot}: ${names.join(', ')}`);
82
+ }
83
+ return out;
84
+ }
85
+ // A warning line for every distinct agent bound anywhere in the harness that does
86
+ // not resolve (D54). Deduped and sorted so the report is stable; a name bound in
87
+ // several slots warns once.
88
+ function bindingWarnings(root, harness) {
89
+ const names = new Set();
90
+ for (const slot of SLOTS)
91
+ for (const name of harness.defaults[slot] ?? [])
92
+ names.add(name);
93
+ for (const binding of harness.steps.values()) {
94
+ for (const slot of SLOTS)
95
+ for (const name of binding.bindings[slot] ?? [])
96
+ names.add(name);
97
+ }
98
+ const warnings = [];
99
+ for (const name of [...names].sort()) {
100
+ const res = resolveAgent(root, name);
101
+ if (!res.ok)
102
+ warnings.push(` ⚠ bound agent "${name}" does not resolve — ${res.error}`);
103
+ }
104
+ return warnings;
105
+ }
@@ -0,0 +1,139 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.6] - 2026-06-30
8
+
9
+ ### Added
10
+
11
+ - Custom checks (config entries keyed by a name outside the built-in slot
12
+ catalogue) accept an `order` field: `"order": "first"` runs the check ahead
13
+ of every built-in, `"last"` (the default) keeps it after them. Lets a
14
+ formatter such as `biome format --write` normalize the tree before the
15
+ linters and tests run. Within each group, config key order is preserved.
16
+
17
+ ## [0.1.5] - 2026-06-30
18
+
19
+ ### Added
20
+
21
+ - `checkride --help` / `-h` and `checkride --version` / `-V`.
22
+ - Optional per-check timeout, off by default: set a global `timeout` (seconds)
23
+ in `checkride.config.json` or override it per check; `0` exempts a slot. A hung
24
+ check is killed and reported as failed with its elapsed duration.
25
+
26
+ ### Changed
27
+
28
+ - Supported Node floor lowered to 22.18 (the minimum required by the cspell and
29
+ oxlint toolchain). `init` and `doctor` reflect it, and CI now runs a Node
30
+ 22 + 24 matrix.
31
+ - Unknown commands and bad flags print a concise message plus a `checkride
32
+ --help` pointer; a malformed `checkride.config.json` now reports `invalid
33
+ checkride.config.json: <reason>` instead of a raw parser error.
34
+ - `prepublishOnly` runs the test suite before publishing, not just the build.
35
+
36
+ ### Internal
37
+
38
+ - `package.json` gains `repository`, `homepage`, and `bugs` for the npm page.
39
+ - README and cheat sheet document the stderr/stdout stream split; README gains a
40
+ header image.
41
+
42
+ ## [0.1.4] - 2026-06-26
43
+
44
+ ### Added
45
+
46
+ - Onboarding and reference documentation under `docs/`: a getting-started guide,
47
+ a command and flag cheat sheet, and a tool-installation reference (system
48
+ prerequisites plus how to install a missing slot tool such as fallow or
49
+ ast-grep). Includes a "Working with agents" section covering how Claude Code
50
+ and other agents adopt the `pnpm check` contract via the AGENTS.md stanza, and
51
+ how to enforce it with a Stop hook without double-running the pipeline.
52
+
53
+ ### Internal
54
+
55
+ - Removed the v1 build plan now that it is fully implemented.
56
+
57
+ ## [0.1.3] - 2026-06-17
58
+
59
+ ### Changed
60
+
61
+ - `checkride doctor` now reports every catalogue slot with its enablement —
62
+ `default`, `opt-in`, `disabled`, or `unavailable` — instead of listing only
63
+ the slots the default run executes. Opt-in slots (mutation, security),
64
+ config-disabled slots, and slots with no detected tool are no longer silently
65
+ omitted; each shows how to enable it. Exit-code behavior is unchanged: only
66
+ default slots are required, so the newly surfaced slots never fail the report.
67
+
68
+ ## [0.1.2] - 2026-06-12
69
+
70
+ ### Fixed
71
+
72
+ - The CLI now runs when invoked through its installed bin — `pnpm exec
73
+ checkride`, `npx checkride`, and the generated `pnpm check` alias. The 0.1.1
74
+ entrypoint guard compared unresolved paths, so launching via the
75
+ `node_modules/.bin/checkride` symlink (how every consumer runs it) silently
76
+ exited 0 without running any checks.
77
+
78
+ ### Internal
79
+
80
+ - Added an end-to-end regression test that invokes the CLI through a bin
81
+ symlink and asserts it behaves identically to a direct invocation.
82
+ - Bumped the CI GitHub Actions to their node24-runtime majors
83
+ (`actions/checkout`, `actions/setup-node`, `pnpm/action-setup`), clearing the
84
+ Node 20 deprecation warning.
85
+
86
+ ## [0.1.1] - 2026-06-12
87
+
88
+ ### Added
89
+
90
+ - `checkride init` for existing projects gained `--add <slots>`: it scaffolds
91
+ blessed-default configs for the named empty slots (lint, spell, struct, test,
92
+ docs, types, dead) and adopts them in the same run, never clobbering an
93
+ existing config.
94
+ - `checkride init` for existing projects now writes the `check: checkride` alias
95
+ to `package.json` when it is missing — additive, and never overwriting an
96
+ existing `check` script.
97
+
98
+ ### Internal
99
+
100
+ - Flattened checkride's own source to named single-file modules with a
101
+ logic-free barrel `index.ts`, relocated unit tests to `src/__tests__/`, and
102
+ added a `no-logic-in-barrel` structural rule. The deep-modules folder pattern
103
+ still ships to consumers unchanged.
104
+ - Added Stryker mutation testing, strengthened weak tests, and gitignored the
105
+ regenerated `stryker.incremental.json` cache.
106
+ - Added a GitHub Actions workflow running `pnpm check` plus the e2e suite.
107
+ - Added a `/version` release skill for cutting tagged releases.
108
+ - Reconciled the module-boundary documentation with the flat source layout.
109
+
110
+ ## [0.1.0] - 2026-06-11
111
+
112
+ The first real release. (`0.0.0` was a name-claim placeholder.)
113
+
114
+ ### Added
115
+
116
+ - `checkride` run command: the verification pipeline across ten slots (types,
117
+ lint, struct, dead, test, docs, links, spell, plus opt-in mutation and
118
+ security), writing raw per-tool output and an aggregate `.check/summary.json`
119
+ (`schema_version: 1`).
120
+ - Adapter registry with blessed defaults (`tsc`, `oxlint`, `ast-grep`,
121
+ `fallow`, `vitest`, `markdownlint-cli2`, built-in links, `cspell`, `stryker`,
122
+ `pnpm audit`) and wired alternates (`biome`, `eslint`, `knip`, `jest`).
123
+ - Zero-config detection plus `checkride.config.json` for overrides, disabled
124
+ slots, adapter swaps, and custom checks.
125
+ - `checkride init` for new projects (flat / monorepo / hybrid shapes, each green
126
+ out of the box) and existing projects (additive adoption, idempotent AGENTS.md
127
+ stanza, failing slots disabled with a report).
128
+ - `checkride doctor` (read-only environment + tooling verification) and
129
+ `checkride fix` (runs every active adapter's fix command).
130
+ - Flags: `--only`, `--skip`, `--bail`, `--json`, `--changed`, `--all`,
131
+ `--include`.
132
+
133
+ [0.1.6]: https://www.npmjs.com/package/checkride/v/0.1.6
134
+ [0.1.5]: https://www.npmjs.com/package/checkride/v/0.1.5
135
+ [0.1.4]: https://www.npmjs.com/package/checkride/v/0.1.4
136
+ [0.1.3]: https://www.npmjs.com/package/checkride/v/0.1.3
137
+ [0.1.2]: https://www.npmjs.com/package/checkride/v/0.1.2
138
+ [0.1.1]: https://www.npmjs.com/package/checkride/v/0.1.1
139
+ [0.1.0]: https://www.npmjs.com/package/checkride/v/0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rob McLarty
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,179 @@
1
+ ![A flight instructor evaluating a student pilot during a simulator checkride](./checkride_photo.png)
2
+
3
+ # checkride
4
+
5
+ **An agent harness for TypeScript repositories**, delivered as one npm package.
6
+ It gives an LLM agent two things it otherwise lacks: a definition of done, and
7
+ lanes to stay inside.
8
+
9
+ ## The thesis
10
+
11
+ Agents are good at writing code and bad at knowing when to stop. Checkride fixes
12
+ both halves of that problem.
13
+
14
+ 1. **A definition of done.** One command runs the whole verification pipeline —
15
+ types, lint, structure, dead code, tests, docs, links, spelling. **Exit 0
16
+ means the work is complete.** Agents stop guessing; humans stop re-reviewing
17
+ half-finished work.
18
+ 2. **Structured boundaries.** A module is an encapsulation boundary with a
19
+ narrow public surface. When one grows internals worth hiding it becomes a
20
+ folder whose only public surface is its `index.ts`, and siblings reach only
21
+ that index — never the internals. Enforced mechanically, boundaries keep
22
+ agents inside lanes and let humans and agents work in parallel with minimal
23
+ merge conflicts.
24
+
25
+ The consumer of the output is an LLM, so checkride never normalizes diagnostics
26
+ into a common format. Each tool writes its own raw JSON to `.check/`; the agent
27
+ reads whatever the tool emits. That deletes the layer that makes every prior
28
+ meta-runner expensive to extend.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pnpm add -D checkride
34
+ pnpm exec checkride init # set up a project (new or existing, auto-detected)
35
+ ```
36
+
37
+ `init` writes a `"check": "checkride"` alias, so daily usage is `pnpm check`
38
+ regardless of the tool's name.
39
+
40
+ ## Commands
41
+
42
+ ```text
43
+ checkride Run the default checks. Exit 0 pass / 1 fail / 2 error.
44
+ --only <a,b> --skip <a,b> --bail --json --changed --all --include <a,b>
45
+ checkride init Set up a project (new or existing — auto-detected).
46
+ --shape flat|monorepo|hybrid --name <n> --scope <@s> --license <id> --dry-run
47
+ checkride doctor Verify environment + every slot's status (read-only, exit 0/1).
48
+ checkride fix Run every active adapter's fix command (oxlint --fix, ...).
49
+ ```
50
+
51
+ During iteration, narrow the loop: `checkride --bail`, `checkride --only
52
+ types,lint`, `checkride --changed`.
53
+
54
+ Output streams: human-readable progress goes to stderr; stdout carries machine
55
+ output only — the summary JSON under `--json`, mirroring `.check/summary.json`.
56
+ So `checkride --json` produces clean JSON on stdout that is safe to pipe, and the
57
+ default run leaves stdout empty.
58
+
59
+ ## The pipeline: slots and adapters
60
+
61
+ A **slot** is a role in the pipeline (order matters — cheapest first). An
62
+ **adapter** is a concrete tool that fills a slot. There is one blessed default
63
+ per slot; alternates are wired so checkride can run them, but `init` only
64
+ generates config for the blessed default.
65
+
66
+ | Slot | Role | Blessed default | Alternates |
67
+ | ---------- | -------------------------------------- | ------------------- | ---------------- |
68
+ | `types` | Type checking | `tsc --build` | — |
69
+ | `lint` | Linting | `oxlint` | `biome`, `eslint`|
70
+ | `struct` | Structural rules (deep modules) | `ast-grep` | — |
71
+ | `dead` | Dead code, deps, cycles, boundaries | `fallow` | `knip` |
72
+ | `test` | Tests + coverage | `vitest` | `jest` |
73
+ | `docs` | Markdown lint | `markdownlint-cli2` | — |
74
+ | `links` | Relative markdown links resolve | built-in | — |
75
+ | `spell` | Spelling | `cspell` | — |
76
+ | `mutation` | Mutation testing (opt-in) | `stryker` | — |
77
+ | `security` | Dependency audit (opt-in) | `pnpm audit` | — |
78
+
79
+ Zero-config: for each slot, checkride runs the first adapter whose config file
80
+ exists, and skips slots with no detected tool. The core has **no runtime
81
+ dependency** on any checked tool — it spawns `pnpm exec <tool>`; the project
82
+ owns the pinned tool versions.
83
+
84
+ ## The `.check/` contract
85
+
86
+ Every run writes to `.check/`. This is a public API for agents; treat schema
87
+ changes as breaking.
88
+
89
+ - `summary.json` — the aggregate report:
90
+
91
+ ```jsonc
92
+ {
93
+ "schema_version": 1,
94
+ "timestamp": "…",
95
+ "ok": true,
96
+ "total_duration_ms": 4200,
97
+ "checks": [
98
+ { "name": "lint", "adapter": "oxlint", "description": "…",
99
+ "ok": true, "exit_code": 0, "duration_ms": 470, "output_file": "lint.json" }
100
+ ]
101
+ }
102
+ ```
103
+
104
+ - `<slot>.json` — the raw tool JSON when stdout parses as JSON; otherwise
105
+ `<slot>.stdout.txt` / `<slot>.stderr.txt`. Tools that write their own files
106
+ (vitest `--outputFile`, stryker) keep doing so.
107
+
108
+ To debug a failure: read `summary.json` to find the failing slot, then read that
109
+ slot's raw output for structured diagnostics.
110
+
111
+ ## Configuration
112
+
113
+ `checkride.config.json` is optional — add it only to deviate from the defaults:
114
+
115
+ ```jsonc
116
+ {
117
+ "timeout": 600, // global per-check timeout in seconds (off by default)
118
+ "checks": {
119
+ "lint": "biome", // pick an alternate adapter
120
+ "spell": false, // disable a slot
121
+ "test": { "use": "vitest", "timeout": 0, "changedArgs": ["--changed", "origin/master"] },
122
+ "format": { // a custom check that runs FIRST, ahead of the built-ins
123
+ "command": "pnpm",
124
+ "args": ["exec", "biome", "format", "--write"],
125
+ "order": "first"
126
+ },
127
+ "licenses": { // a custom check (runs last by default)
128
+ "command": "node",
129
+ "args": ["scripts/check-licenses.mjs"]
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ A custom check (one keyed by a name that isn't a built-in slot) runs *after* the
136
+ built-in catalogue by default. Set `"order": "first"` to run it ahead of every
137
+ built-in check instead — handy for a formatter like `biome format --write` that
138
+ should normalize the tree before the linters and tests look at it. `"order":
139
+ "last"` is the explicit form of the default. Within each group, custom checks
140
+ run in the order they appear in the config.
141
+
142
+ A per-check timeout guards against a hung tool. It is **off by default** — a cap
143
+ short enough to catch a hang on a small repo is short enough to kill a legitimate
144
+ slow run on a large one, and CI job timeouts already bound true hangs. Set a
145
+ global `timeout` (seconds) to opt in, override it per check (`"timeout": <n>`),
146
+ and use `"timeout": 0` to exempt a slot. Leave `dead`, `test`, and `mutation`
147
+ generous or uncapped — they legitimately run long.
148
+
149
+ ## Project shapes
150
+
151
+ `init` scaffolds three shapes. They share everything except `tsconfig.json`,
152
+ `fallow.toml`, and `pnpm-workspace.yaml`:
153
+
154
+ - **flat** — a single package using the deep-modules layout under `src/`.
155
+ - **monorepo** — a pnpm workspace of `apps/*` (deployable leaves) and `libs/*`
156
+ (reusable internals); libs may not import from apps.
157
+ - **hybrid** — a root app in `src/` plus internal packages under `packages/*`.
158
+
159
+ Every generated shape is green out of the box — an end-to-end test enforces it.
160
+
161
+ ## Conventions
162
+
163
+ Module boundaries, enforced by `ast-grep` and `fallow`:
164
+
165
+ - A module is a unit of encapsulation. A single file is a module; promote it to
166
+ a folder with a barrel `index.ts` when it grows internals worth hiding — a
167
+ one-file folder is just ceremony.
168
+ - A folder module's `index.ts` is its only public surface: it re-exports, it
169
+ holds no logic. Siblings import it via `'../<sibling>/index.js'`, never its
170
+ internals.
171
+ - Named exports only; no classes; `.js` extensions on relative imports
172
+ (NodeNext); tests colocated with the code they cover.
173
+
174
+ See [AGENTS.md](./AGENTS.md) for the contract agents follow, and
175
+ [CHANGELOG.md](./CHANGELOG.md) for release notes.
176
+
177
+ ## License
178
+
179
+ [MIT](./LICENSE)
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Adapter registry — pure data.
3
+ *
4
+ * A `Slot` is a role in the pipeline (the catalogue; order matters, cheapest
5
+ * first). An `Adapter` is a concrete tool that can fill a slot. The registry is
6
+ * data only: no logic lives here. Resolution (config vs detection) lives in
7
+ * `../config`, and execution lives in `../orchestrator`.
8
+ *
9
+ * Phase 1 ships the blessed defaults plus the two opt-in slots. Alternates
10
+ * (biome, knip, eslint, jest) land in Phase 2.
11
+ */
12
+ /** The aggregate-report schema version written to `.check/summary.json`. */
13
+ export declare const SCHEMA_VERSION = 1;
14
+ /** A role in the pipeline. */
15
+ export type Slot = {
16
+ /** Stable slot name, e.g. `'lint'`. Used by `--only`/`--skip` and the report. */
17
+ name: string;
18
+ /** Opt-in slots are excluded from the default run (need `--all`/`--include`). */
19
+ optIn?: boolean;
20
+ };
21
+ /** A concrete tool that can fill a slot. */
22
+ export type Adapter = {
23
+ /** Adapter name, e.g. `'oxlint'`. Recorded as `adapter` in the report. */
24
+ name: string;
25
+ /** The slot this adapter fills, e.g. `'lint'`. */
26
+ slot: string;
27
+ /** Human description for the report (mirrors the interim check catalogue). */
28
+ description: string;
29
+ /** Config files whose presence activates this adapter. `[]` = always available. */
30
+ detect: string[];
31
+ /** Command to spawn (usually `'pnpm'`). Ignored when `builtin` is set. */
32
+ command: string;
33
+ /** Arguments to the command. Ignored when `builtin` is set. */
34
+ args: string[];
35
+ /** `.check/<file>` target when stdout is JSON; `null` if the tool writes its own. */
36
+ outputFile: string | null;
37
+ /** Appended to `args` under `--changed`. */
38
+ changedArgs?: string[];
39
+ /** Used by `checkride fix`. */
40
+ fixArgs?: string[];
41
+ /** Per-check timeout in seconds; `0` disables the cap. Falls back to the config-level `timeout`. */
42
+ timeout?: number;
43
+ /** In-process check id (e.g. `'links'`); when set, the orchestrator runs it directly. */
44
+ builtin?: string;
45
+ /** Pinned versions `init` writes into package.json. */
46
+ devDeps: Record<string, string>;
47
+ };
48
+ /** Pipeline order. Cheapest first so `--bail` fails fast. */
49
+ export declare const SLOTS: readonly Slot[];
50
+ /**
51
+ * Adapter registry. The FIRST adapter for a slot is its blessed default: it
52
+ * wins detection when several tool configs coexist, and is the only one `init`
53
+ * generates config for. Alternates follow so checkride can still *run* them.
54
+ */
55
+ export declare const ADAPTERS: readonly Adapter[];
56
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,4EAA4E;AAC5E,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,8BAA8B;AAC9B,MAAM,MAAM,IAAI,GAAG;IACjB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,OAAO,GAAG;IACpB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qFAAqF;IACrF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,KAAK,EAAE,SAAS,IAAI,EAWhC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,OAAO,EA0LtC,CAAC"}