plumbbob 0.8.13 → 0.8.17

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 (93) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/lib/agents.js +1 -1
  3. package/dist/lib/check.js +9 -6
  4. package/dist/lib/intent.js +20 -0
  5. package/dist/lib/orient.js +4 -3
  6. package/dist/verbs/checkpoint.js +30 -11
  7. package/dist/verbs/finish.js +24 -8
  8. package/node_modules/checkride/CHANGELOG.md +234 -0
  9. package/node_modules/checkride/README.md +134 -31
  10. package/node_modules/checkride/dist/adapters.d.ts +50 -1
  11. package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
  12. package/node_modules/checkride/dist/adapters.js +123 -7
  13. package/node_modules/checkride/dist/adapters.js.map +1 -1
  14. package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
  15. package/node_modules/checkride/dist/agent-setup/hook.js +15 -1
  16. package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
  17. package/node_modules/checkride/dist/agent-setup/index.d.ts +0 -1
  18. package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
  19. package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
  20. package/node_modules/checkride/dist/baseline/fallow.d.ts +64 -0
  21. package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -0
  22. package/node_modules/checkride/dist/baseline/fallow.js +256 -0
  23. package/node_modules/checkride/dist/baseline/fallow.js.map +1 -0
  24. package/node_modules/checkride/dist/baseline/fingerprint.d.ts +4 -6
  25. package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
  26. package/node_modules/checkride/dist/baseline/fingerprint.js +9 -4
  27. package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
  28. package/node_modules/checkride/dist/baseline/index.d.ts +4 -5
  29. package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
  30. package/node_modules/checkride/dist/baseline/index.js +2 -2
  31. package/node_modules/checkride/dist/baseline/index.js.map +1 -1
  32. package/node_modules/checkride/dist/{baseline/command.d.ts → baseline-command.d.ts} +11 -9
  33. package/node_modules/checkride/dist/baseline-command.d.ts.map +1 -0
  34. package/node_modules/checkride/dist/{baseline/command.js → baseline-command.js} +9 -7
  35. package/node_modules/checkride/dist/baseline-command.js.map +1 -0
  36. package/node_modules/checkride/dist/cli.d.ts.map +1 -1
  37. package/node_modules/checkride/dist/cli.js +95 -8
  38. package/node_modules/checkride/dist/cli.js.map +1 -1
  39. package/node_modules/checkride/dist/config.d.ts +37 -12
  40. package/node_modules/checkride/dist/config.d.ts.map +1 -1
  41. package/node_modules/checkride/dist/config.js +268 -60
  42. package/node_modules/checkride/dist/config.js.map +1 -1
  43. package/node_modules/checkride/dist/digest/digest.d.ts +0 -6
  44. package/node_modules/checkride/dist/digest/digest.d.ts.map +1 -1
  45. package/node_modules/checkride/dist/digest/digest.js +3 -3
  46. package/node_modules/checkride/dist/digest/digest.js.map +1 -1
  47. package/node_modules/checkride/dist/digest/index.d.ts +1 -2
  48. package/node_modules/checkride/dist/digest/index.d.ts.map +1 -1
  49. package/node_modules/checkride/dist/digest/index.js +1 -1
  50. package/node_modules/checkride/dist/digest/index.js.map +1 -1
  51. package/node_modules/checkride/dist/doctor.d.ts +14 -1
  52. package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
  53. package/node_modules/checkride/dist/doctor.js +124 -55
  54. package/node_modules/checkride/dist/doctor.js.map +1 -1
  55. package/node_modules/checkride/dist/init.d.ts +6 -0
  56. package/node_modules/checkride/dist/init.d.ts.map +1 -1
  57. package/node_modules/checkride/dist/init.js +347 -121
  58. package/node_modules/checkride/dist/init.js.map +1 -1
  59. package/node_modules/checkride/dist/links.d.ts.map +1 -1
  60. package/node_modules/checkride/dist/links.js +8 -9
  61. package/node_modules/checkride/dist/links.js.map +1 -1
  62. package/node_modules/checkride/dist/orchestrator.d.ts +56 -1
  63. package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
  64. package/node_modules/checkride/dist/orchestrator.js +506 -138
  65. package/node_modules/checkride/dist/orchestrator.js.map +1 -1
  66. package/node_modules/checkride/dist/pack.d.ts +76 -0
  67. package/node_modules/checkride/dist/pack.d.ts.map +1 -0
  68. package/node_modules/checkride/dist/pack.js +217 -0
  69. package/node_modules/checkride/dist/pack.js.map +1 -0
  70. package/node_modules/checkride/dist/pm/translate.d.ts +18 -10
  71. package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
  72. package/node_modules/checkride/dist/pm/translate.js +27 -13
  73. package/node_modules/checkride/dist/pm/translate.js.map +1 -1
  74. package/node_modules/checkride/dist/smoke.d.ts +86 -0
  75. package/node_modules/checkride/dist/smoke.d.ts.map +1 -0
  76. package/node_modules/checkride/dist/smoke.js +346 -0
  77. package/node_modules/checkride/dist/smoke.js.map +1 -0
  78. package/node_modules/checkride/dist/snippets.d.ts +215 -0
  79. package/node_modules/checkride/dist/snippets.d.ts.map +1 -0
  80. package/node_modules/checkride/dist/snippets.js +360 -0
  81. package/node_modules/checkride/dist/snippets.js.map +1 -0
  82. package/node_modules/checkride/package.json +4 -2
  83. package/node_modules/checkride/schema/checkride.config.schema.json +11 -6
  84. package/package.json +3 -2
  85. package/skills/pb-build/SKILL.md +7 -0
  86. package/skills/pb-park/SKILL.md +3 -2
  87. package/skills/pb-plan/SKILL.md +30 -9
  88. package/skills/pb-refine/SKILL.md +21 -6
  89. package/skills/pb-step/SKILL.md +10 -5
  90. package/skills/pb-verify/SKILL.md +12 -1
  91. package/templates/intent.md +55 -11
  92. package/node_modules/checkride/dist/baseline/command.d.ts.map +0 -1
  93. package/node_modules/checkride/dist/baseline/command.js.map +0 -1
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Built-in `snippets` check.
3
+ *
4
+ * A doc snippet typecheck ported to match fascicle's `check-doc-snippets.mjs`
5
+ * byte-for-byte so the origin repo can adopt the slot verbatim (D11). It pulls
6
+ * fenced ```ts / ```typescript blocks out of `README.md` and the non-recursive
7
+ * `docs/*.md`, and typechecks only the ones explicitly opted in with an HTML
8
+ * comment on the line immediately above the fence:
9
+ *
10
+ * <!-- snippet: check -->
11
+ *
12
+ * Opt-in (rather than checking every fence) keeps the check honest: many doc
13
+ * fences are deliberately partial fragments that assert nothing about the API.
14
+ * A tagged snippet that fails to compile is documentation that lies — this
15
+ * turns that into a build failure. Opting the slot in but tagging *nothing* is
16
+ * a misconfiguration, not a vacuous pass, so zero tagged snippets is a hard
17
+ * error ({@link vacuousOptInError}; fascicle exits 2).
18
+ *
19
+ * The pure machinery — no filesystem, no spawning:
20
+ *
21
+ * - {@link extractSnippets} parses one doc's markdown into its ts/typescript
22
+ * fenced blocks, recording each block's start line and whether it is tagged;
23
+ * - {@link slugForDoc} / {@link snippetFileName} name the emitted scratch file
24
+ * for a block (`<slug>__<n>.ts`);
25
+ * - {@link planSnippets} folds a set of docs into the emission plan (the
26
+ * checked blocks to write, each mapped back to `<doc>:<line>`) plus the
27
+ * checked/skipped counts;
28
+ * - {@link generateSnippetTsconfig} builds the generated tsconfig (extends the
29
+ * repo's own, relaxes the three emit/style flags plus the composite-build
30
+ * fields a `tsc --build` project carries, embeds the caller's mode-specific
31
+ * `paths`).
32
+ *
33
+ * Execution (D12/Q1), consuming the primitives above:
34
+ *
35
+ * - {@link deriveSrcPaths} resolves **src mode**'s `paths` mapping: the repo's
36
+ * own tsconfig `paths` when present, else the `src/index.ts` convention,
37
+ * else null (the caller fails, recommending `snippets-dist`);
38
+ * - {@link checkSnippets} discovers the doc set, plans it, derives the mode's
39
+ * `paths`, emits the scratch files + generated tsconfig under
40
+ * `.check/doc-snippets/`, and typechecks them via a spawned
41
+ * `<pm> exec tsc --noEmit -p`, returning a {@link CheckOutcome} whose
42
+ * failure `stderr` carries tsc's raw output plus the `snippet -> source
43
+ * map:` legend.
44
+ */
45
+ import { existsSync } from 'node:fs';
46
+ import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
47
+ import { join } from 'node:path';
48
+ import { translateExec } from './pm/index.js';
49
+ /**
50
+ * The opt-in marker: an HTML comment `<!-- snippet: check -->` that must sit on
51
+ * the line immediately above a fence for its snippet to be typechecked. Exact
52
+ * fascicle regex (D11).
53
+ */
54
+ export const CHECK_MARKER_RE = /<!--\s*snippet:\s*check\s*-->/;
55
+ /** An opening fence — `ts` or `typescript` only, nothing else on the line (D11). */
56
+ export const FENCE_OPEN_RE = /^```(ts|typescript)\s*$/;
57
+ /** A closing fence: three backticks alone on the line. */
58
+ export const FENCE_CLOSE_RE = /^```\s*$/;
59
+ /**
60
+ * Select the doc files to check from a `docs/` directory listing (D11): always
61
+ * `README.md`, then every non-recursive `docs/*.md` entry, README first and the
62
+ * `docs/` entries in the order given. Pure — the caller does the (non-recursive)
63
+ * `readdir` and passes its entries; a plain entry list means a nested
64
+ * `docs/sub/x.md` never appears (its listing entry `sub` fails `.md`), matching
65
+ * fascicle's non-recursive `doc_files`.
66
+ */
67
+ export function selectDocFiles(docsEntries) {
68
+ const files = ['README.md'];
69
+ for (const name of docsEntries) {
70
+ if (name.endsWith('.md'))
71
+ files.push(`docs/${name}`);
72
+ }
73
+ return files;
74
+ }
75
+ /**
76
+ * Parse a doc's markdown into its `ts`/`typescript` fenced blocks (D11). A block
77
+ * is `checked` when a {@link CHECK_MARKER_RE} marker sits on the line immediately
78
+ * above its opening fence — a marker anywhere else does not count. Untagged
79
+ * blocks are still returned (the caller counts them as skipped). Semantics match
80
+ * fascicle's `extract_blocks` exactly.
81
+ */
82
+ export function extractSnippets(text) {
83
+ const lines = text.split('\n');
84
+ const blocks = [];
85
+ let i = 0;
86
+ while (i < lines.length) {
87
+ if (FENCE_OPEN_RE.test(lines[i] ?? '')) {
88
+ const startLine = i + 1;
89
+ const checked = i > 0 && CHECK_MARKER_RE.test(lines[i - 1] ?? '');
90
+ const body = [];
91
+ i += 1;
92
+ while (i < lines.length && !FENCE_CLOSE_RE.test(lines[i] ?? '')) {
93
+ body.push(lines[i] ?? '');
94
+ i += 1;
95
+ }
96
+ blocks.push({ code: body.join('\n'), startLine, checked });
97
+ }
98
+ i += 1;
99
+ }
100
+ return blocks;
101
+ }
102
+ /**
103
+ * The scratch-file slug for a doc's repo-relative path: every non-alphanumeric
104
+ * run collapses to a single `_`, with leading/trailing `_` trimmed
105
+ * (`README.md` → `README_md`, `docs/guide.md` → `docs_guide_md`). Exact fascicle
106
+ * algorithm. Distinct docs in the discovered namespace (`README.md` plus the
107
+ * flat `docs/*.md`) map to distinct slugs, so their emitted files never collide.
108
+ */
109
+ export function slugForDoc(relPath) {
110
+ return relPath.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
111
+ }
112
+ /**
113
+ * The scratch-file name for a block: `<slug>__<n>.ts`, where `n` is the block's
114
+ * 1-based position among *all* ts/typescript fences in its doc (checked or not) —
115
+ * so a skipped fence still advances the number, matching fascicle.
116
+ */
117
+ export function snippetFileName(relPath, blockIndex) {
118
+ return `${slugForDoc(relPath)}__${blockIndex}.ts`;
119
+ }
120
+ /**
121
+ * Fold a set of docs into the emission plan: every checked block becomes a
122
+ * {@link SnippetPlanEntry} (named, mapped back to `<doc>:<line>`), untagged
123
+ * blocks bump `skipped`. The block index for naming counts all fences in a doc,
124
+ * so a checked block keeps its position even after skipped ones (fascicle
125
+ * parity). Pure — the caller reads the docs and writes the files.
126
+ */
127
+ export function planSnippets(docs) {
128
+ const entries = [];
129
+ let checked = 0;
130
+ let skipped = 0;
131
+ for (const doc of docs) {
132
+ extractSnippets(doc.text).forEach((block, index) => {
133
+ if (!block.checked) {
134
+ skipped += 1;
135
+ return;
136
+ }
137
+ entries.push({
138
+ name: snippetFileName(doc.relPath, index + 1),
139
+ file: doc.relPath,
140
+ line: block.startLine,
141
+ code: block.code,
142
+ });
143
+ checked += 1;
144
+ });
145
+ }
146
+ return { entries, checked, skipped };
147
+ }
148
+ /**
149
+ * The vacuous-opt-in guard (D11): a plan with no checked snippets is a hard
150
+ * error, not a vacuous green — opting the slot in obligates at least one tagged
151
+ * fence. Returns the error message for step 8 to surface (fascicle exits 2), or
152
+ * null when the plan has something to check.
153
+ */
154
+ export function vacuousOptInError(plan) {
155
+ if (plan.checked > 0)
156
+ return null;
157
+ return 'no tagged snippets found — refusing to pass vacuously (add a `<!-- snippet: check -->` marker above a ts/typescript fence)';
158
+ }
159
+ /**
160
+ * Build the generated tsconfig (D11): extend the repo's own config so the
161
+ * snippets inherit its strictness, then relax exactly the three flags that
162
+ * false-positive on illustrative snippets (`verbatimModuleSyntax`,
163
+ * `isolatedModules`, `noPropertyAccessFromIndexSignature`) while keeping the
164
+ * type-correctness flags that catch real API drift. `include` clears the parent's
165
+ * `.check` exclusion so the emitted `./*.ts` are seen; `paths` is the caller's
166
+ * mode-specific module resolution (src vs dist — Q1).
167
+ *
168
+ * A `tsc --build` project (composite, `rootDir`/`outDir` pinned to `src`/`dist`
169
+ * for project-reference emit) fails TS6059 on any *source* file outside its
170
+ * `rootDir` — and the generated snippets live under `.check/doc-snippets/`,
171
+ * while src mode's `paths` mapping pulls in a real source file elsewhere in the
172
+ * repo (dist mode's self-referenced `.d.ts` is exempt: a declaration file isn't
173
+ * subject to the same check). `rootDir: '../..'` re-scopes to `cwd` — the
174
+ * common ancestor of both — so either file satisfies it; `composite`/
175
+ * `declaration`/`declarationMap`/`incremental` are turned off since this is a
176
+ * one-shot typecheck, never a build. All four are no-ops on a plain
177
+ * (non-composite) project's tsconfig, so the override is harmless there too.
178
+ */
179
+ export function generateSnippetTsconfig(opts) {
180
+ return {
181
+ extends: opts.extendsPath,
182
+ compilerOptions: {
183
+ paths: opts.paths,
184
+ noEmit: true,
185
+ verbatimModuleSyntax: false,
186
+ isolatedModules: false,
187
+ noPropertyAccessFromIndexSignature: false,
188
+ rootDir: '../..',
189
+ composite: false,
190
+ declaration: false,
191
+ declarationMap: false,
192
+ incremental: false,
193
+ },
194
+ include: ['./*.ts'],
195
+ exclude: [],
196
+ };
197
+ }
198
+ /** Where the generated snippet files + tsconfig land, relative to `cwd`. */
199
+ const OUT_DIR_REL = join('.check', 'doc-snippets');
200
+ function isRecord(value) {
201
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
202
+ }
203
+ /**
204
+ * Read the doc set (D11): `README.md` plus non-recursive `docs/*.md`, skipping
205
+ * any that don't exist — a repo with no `docs/` directory (or no `README.md`)
206
+ * still runs on what it has; an empty result is caught by
207
+ * {@link vacuousOptInError}.
208
+ */
209
+ async function discoverDocs(cwd) {
210
+ let docsEntries = [];
211
+ try {
212
+ docsEntries = await readdir(join(cwd, 'docs'));
213
+ }
214
+ catch {
215
+ // No docs/ directory — README.md alone still gets checked.
216
+ }
217
+ const found = await Promise.all(selectDocFiles(docsEntries).map(async (relPath) => {
218
+ try {
219
+ return { relPath, text: await readFile(join(cwd, relPath), 'utf8') };
220
+ }
221
+ catch {
222
+ return null;
223
+ }
224
+ }));
225
+ return found.filter((d) => d !== null);
226
+ }
227
+ /** The package name from `package.json`, or null on any read/parse failure or a missing `name`. */
228
+ async function readManifestName(cwd) {
229
+ try {
230
+ const parsed = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf8'));
231
+ const name = isRecord(parsed) ? parsed['name'] : undefined;
232
+ return typeof name === 'string' && name ? name : null;
233
+ }
234
+ catch {
235
+ return null;
236
+ }
237
+ }
238
+ /**
239
+ * The repo's own `tsconfig.json`'s **own** `compilerOptions.paths` (not resolved
240
+ * through its `extends` chain — a repo that hand-declares self-referencing
241
+ * paths, as fascicle does, puts them directly on the root file), or null when
242
+ * absent, unreadable, or empty.
243
+ */
244
+ async function readOwnTsconfigPaths(cwd) {
245
+ try {
246
+ const parsed = JSON.parse(await readFile(join(cwd, 'tsconfig.json'), 'utf8'));
247
+ const compilerOptions = isRecord(parsed) ? parsed['compilerOptions'] : undefined;
248
+ const paths = isRecord(compilerOptions) ? compilerOptions['paths'] : undefined;
249
+ if (!isRecord(paths))
250
+ return null;
251
+ const out = {};
252
+ for (const [key, value] of Object.entries(paths)) {
253
+ if (Array.isArray(value) && value.every((v) => typeof v === 'string'))
254
+ out[key] = value;
255
+ }
256
+ return Object.keys(out).length > 0 ? out : null;
257
+ }
258
+ catch {
259
+ return null;
260
+ }
261
+ }
262
+ /** Rewrite a tsconfig-root-relative target (`./src/x.ts` or `src/x.ts`) to resolve from `.check/doc-snippets/`. */
263
+ function remapRootRelative(target) {
264
+ const stripped = target.startsWith('./') ? target.slice(2) : target;
265
+ return `../../${stripped}`;
266
+ }
267
+ /**
268
+ * Derive the `paths` mapping **src mode** needs so `import { x } from
269
+ * '<pkg-name>'` resolves against source (Q1): the repo's own tsconfig `paths`
270
+ * when present (remapped two directories deeper, to where the generated
271
+ * tsconfig lives), else the `src/index.ts` convention (`<pkg-name>` maps to its
272
+ * root `src/index.ts`) when the manifest names the package and that file
273
+ * exists, else null — the caller turns a null into a failing outcome that
274
+ * recommends `snippets-dist` (whose dist-mode compilation needs no mapping at
275
+ * all: package self-reference through `exports` resolves the built types).
276
+ */
277
+ export function deriveSrcPaths(opts) {
278
+ if (opts.tsconfigPaths && Object.keys(opts.tsconfigPaths).length > 0) {
279
+ const remapped = {};
280
+ for (const [key, targets] of Object.entries(opts.tsconfigPaths)) {
281
+ remapped[key] = targets.map(remapRootRelative);
282
+ }
283
+ return remapped;
284
+ }
285
+ if (opts.manifestName !== null && opts.hasSrcIndex) {
286
+ return { [opts.manifestName]: ['../../src/index.ts'] };
287
+ }
288
+ return null;
289
+ }
290
+ function failOutcome(message) {
291
+ return {
292
+ ok: false,
293
+ exit_code: 1,
294
+ stdout: `${JSON.stringify({ ok: false, error: message }, null, 2)}\n`,
295
+ stderr: `check-snippets: ${message}\n`,
296
+ };
297
+ }
298
+ /** Compile the generated tsconfig via `<pm> exec tsc --noEmit -p <path>`, translated per PM. */
299
+ function runTsc(cwd, tsconfigRelPath, pm, spawn, timeoutSec) {
300
+ const { command, args } = translateExec('pnpm', ['exec', 'tsc', '--noEmit', '-p', tsconfigRelPath], pm);
301
+ return spawn(command, args, cwd, timeoutSec);
302
+ }
303
+ /**
304
+ * Run the `snippets` check against `cwd` in `mode` (D12: the `snippets` src
305
+ * adapter passes `'src'`, `snippets-dist` passes `'dist'`). Discovers the doc
306
+ * set, plans the checked snippets ({@link vacuousOptInError} applies first —
307
+ * zero tagged snippets is a hard error before anything is written), derives
308
+ * the mode's `paths` mapping, emits the scratch files plus a generated
309
+ * tsconfig under `.check/doc-snippets/`, and typechecks them through the
310
+ * injected `spawn`. A compile failure's `stderr` carries tsc's raw output plus
311
+ * the `snippet -> source map:` legend mapping each generated file back to
312
+ * `<doc>:<line>` (D11).
313
+ */
314
+ export async function checkSnippets(opts) {
315
+ const { cwd, mode, pm, spawn, timeoutSec } = opts;
316
+ const plan = planSnippets(await discoverDocs(cwd));
317
+ const vacuous = vacuousOptInError(plan);
318
+ if (vacuous)
319
+ return failOutcome(vacuous);
320
+ let paths = {};
321
+ if (mode === 'src') {
322
+ const [manifestName, tsconfigPaths] = await Promise.all([readManifestName(cwd), readOwnTsconfigPaths(cwd)]);
323
+ const derived = deriveSrcPaths({
324
+ manifestName,
325
+ tsconfigPaths,
326
+ hasSrcIndex: existsSync(join(cwd, 'src', 'index.ts')),
327
+ });
328
+ if (derived === null) {
329
+ return failOutcome("cannot map snippet imports to source: tsconfig.json has no self-referencing 'paths' and there is no " +
330
+ "src/index.ts — add a 'paths' entry for the package name, or opt into the 'snippets-dist' adapter " +
331
+ 'instead (dist mode needs no mapping)');
332
+ }
333
+ paths = derived;
334
+ }
335
+ const outDir = join(cwd, OUT_DIR_REL);
336
+ await rm(outDir, { recursive: true, force: true });
337
+ await mkdir(outDir, { recursive: true });
338
+ await Promise.all(plan.entries.map((e) => writeFile(join(outDir, e.name), `${e.code}\n`, 'utf8')));
339
+ const tsconfig = generateSnippetTsconfig({ extendsPath: '../../tsconfig.json', paths });
340
+ await writeFile(join(outDir, 'tsconfig.json'), `${JSON.stringify(tsconfig, null, 2)}\n`, 'utf8');
341
+ const outcome = await runTsc(cwd, join(OUT_DIR_REL, 'tsconfig.json'), pm, spawn, timeoutSec);
342
+ const status = { checked: plan.checked, skipped: plan.skipped, mode };
343
+ if (outcome.ok) {
344
+ return {
345
+ ok: true,
346
+ exit_code: 0,
347
+ stdout: `${JSON.stringify({ ok: true, ...status }, null, 2)}\n`,
348
+ stderr: `check-snippets: ${plan.checked} snippet(s) compile against ${mode} (${plan.skipped} skipped)\n`,
349
+ };
350
+ }
351
+ const legend = plan.entries.map((e) => ` ${e.name} <- ${e.file}:${e.line}`).join('\n');
352
+ const tscOutput = `${outcome.stdout}${outcome.stderr}`.trim();
353
+ return {
354
+ ok: false,
355
+ exit_code: 1,
356
+ stdout: `${JSON.stringify({ ok: false, ...status }, null, 2)}\n`,
357
+ stderr: `check-snippets: snippet(s) failed to compile:\n\n${tscOutput}\n\nsnippet -> source map:\n${legend}\n`,
358
+ };
359
+ }
360
+ //# sourceMappingURL=snippets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snippets.js","sourceRoot":"","sources":["../src/snippets.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "checkride",
3
- "version": "0.3.0",
3
+ "version": "0.5.2",
4
4
  "description": "An agent harness for TypeScript repositories: one command that is the definition of done, plus mechanically enforced module boundaries.",
5
5
  "license": "MIT",
6
6
  "author": "Rob McLarty",
@@ -62,6 +62,7 @@
62
62
  "prepublishOnly": "tsc --build && vitest run"
63
63
  },
64
64
  "devDependencies": {
65
+ "@arethetypeswrong/cli": "0.18.4",
65
66
  "@ast-grep/cli": "0.42.1",
66
67
  "@stryker-mutator/core": "9.6.1",
67
68
  "@stryker-mutator/vitest-runner": "9.6.1",
@@ -69,10 +70,11 @@
69
70
  "@vitest/coverage-v8": "4.1.5",
70
71
  "ajv": "8.20.0",
71
72
  "cspell": "10.0.0",
72
- "fallow": "2.48.0",
73
+ "fallow": "3.5.0",
73
74
  "markdownlint-cli2": "0.22.1",
74
75
  "oxlint": "1.61.0",
75
76
  "oxlint-tsgolint": "0.21.1",
77
+ "publint": "0.3.21",
76
78
  "typescript": "6.0.3",
77
79
  "vitest": "4.1.5"
78
80
  }
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "checks": {
26
26
  "type": "object",
27
- "description": "Per-slot configuration. A key that matches a catalogue slot (types, format, lint, struct, dead, test, docs, links, spell, mutation, security, publint, attw) configures that slot; any other key is a custom check that runs alongside the catalogue. Naming an opt-in slot (format, mutation, security, publint, attw) here also opts it into the default run.",
27
+ "description": "Per-slot configuration. A key that matches a catalogue slot (types, format, lint, struct, dead, dupes, health, test, docs, links, spell, mutation, security, publint, attw) configures that slot; any other key is a custom check that runs alongside the catalogue. Naming an opt-in slot (format, dupes, health, mutation, security, publint, attw) here also opts it into the default run.",
28
28
  "additionalProperties": {
29
29
  "$ref": "#/definitions/slotConfig"
30
30
  }
@@ -62,9 +62,17 @@
62
62
  "outputFile": { "type": ["string", "null"] },
63
63
  "changedArgs": { "type": "array", "items": { "type": "string" } },
64
64
  "fixArgs": { "type": "array", "items": { "type": "string" } },
65
- "timeout": { "type": "number", "minimum": 0 }
65
+ "timeout": { "type": "number", "minimum": 0 },
66
+ "order": { "$ref": "#/definitions/order" }
66
67
  }
67
68
  },
69
+ "order": {
70
+ "description": "Scheduling order. A number places the check on the ascending wave line (equal numbers run together as one wave; decimals sequence steps within a wave); the keywords place it relative to that line — first/last before/after everything, single exclusively on its own, middle/any unordered in the main group.",
71
+ "oneOf": [
72
+ { "enum": ["first", "last", "middle", "single", "any"] },
73
+ { "type": "number" }
74
+ ]
75
+ },
68
76
  "customCheck": {
69
77
  "type": "object",
70
78
  "description": "A bare command that needs no adapter.",
@@ -87,10 +95,7 @@
87
95
  "items": { "type": "string" },
88
96
  "description": "Marker files that gate a non-catalogue custom check: it runs only when at least one listed file exists, and is skipped otherwise. Ignored on an entry that fills a catalogue slot."
89
97
  },
90
- "order": {
91
- "enum": ["first", "last"],
92
- "description": "Where a non-catalogue custom check runs relative to the catalogue. Default 'last'."
93
- }
98
+ "order": { "$ref": "#/definitions/order" }
94
99
  }
95
100
  }
96
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plumbbob",
3
- "version": "0.8.13",
3
+ "version": "0.8.17",
4
4
  "description": "Attention-first build process: 12 skills + a CLI that keep you the decider — guidance on the work, a latch on the record.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@ast-grep/cli": "0.43.0",
49
+ "@stryker-mutator/api": "9.6.1",
49
50
  "@stryker-mutator/core": "9.6.1",
50
51
  "@stryker-mutator/vitest-runner": "9.6.1",
51
52
  "@types/node": "24.13.1",
@@ -58,7 +59,7 @@
58
59
  "vitest": "4.1.9"
59
60
  },
60
61
  "dependencies": {
61
- "checkride": "0.3.0"
62
+ "checkride": "0.5.2"
62
63
  },
63
64
  "bundleDependencies": [
64
65
  "checkride"
@@ -74,6 +74,13 @@ differs from the model you're running as, say so before implementing — the hum
74
74
  build-log's `## Log`, so the history writes itself — you only supply the body (or omit
75
75
  `--body` for the deterministic done-when + seam + diffstat fallback). Do **not** bump
76
76
  the version or changelog — that is the human's `/version` call.
77
+ - **A drifted subject is reconciled at the pause, never silently (D5/D6).** The planned
78
+ title *is* the checkpoint subject (D1). If the diff drifted from it, **present** a
79
+ corrected subject at the pause — `planned title → proposed subject`, one line — for
80
+ explicit approval, and only on approval land it by adding `-m "<subject>"` to the
81
+ checkpoint. Present nothing and the deterministic title-derived subject lands
82
+ untouched: the reconcile is the human-approved exception, not a quiet `-m` swap (which
83
+ is the agent-authored subject D68 refuses).
77
84
  - **If `plumbbob status` shows a `harness bindings:` block**, fold any bound
78
85
  `after`-agents into that self-review as advisory input — see **§ Running bound
79
86
  agents**.
@@ -25,8 +25,9 @@ is fine; capture is always available, which is the whole point of parking.
25
25
  ## What this skill does
26
26
 
27
27
  Take the idea, problem, or "ooh what if" the human just had and **compose it into one
28
- tidy, tagged line** — short, legible, self-contained, so it still reads cleanly weeks
29
- later. Then:
28
+ tidy, tagged line** — short, legible, self-contained, and carrying a **one-clause why**
29
+ (what it is *and* why it's worth revisiting), so it still reads cleanly weeks later when
30
+ harvest triages it cold. Then:
30
31
 
31
32
  1. **Show the composed line to the human** and wait for **in-turn approval** — they
32
33
  confirm it as-is or edit the wording.
@@ -58,13 +58,23 @@ an agent can follow with `/pb-build`. The argument only seeds how you get there.
58
58
  **explicitly NOT doing**. This is the human's convergence — propose wording, but
59
59
  the human decides every line.
60
60
  3. **Decisions & Constraints.** Record the settled calls (one line each, with the
61
- *because*) and the hard rules the build must honor. An unresolved hole goes to
62
- **Open questions**, never guessed into a Decision.
61
+ *because*) and the hard rules the build must honor. **Slug each at birth** — mint a
62
+ two- or three-word slug where the item is born (`D1 (default-waves): …`,
63
+ `C1 (no-new-deps): …`) and copy that slug at every reference site, so a later
64
+ mention reads `D4 (default-waves)` and stays legible: **never a bare `D4`/`C6`/`Q2`
65
+ reference — always carry its gloss**, so a cold reader never has to hunt the number
66
+ down. An unresolved hole goes to **Open questions**, never guessed into a Decision —
67
+ and it goes in the expanded form the template shows: a slugged opener (the hole as a
68
+ question), a `*plain:*` sub-line (what's at stake, in plain words, enough to judge it
69
+ cold), then a `*lean:*` sub-line (your proposed resolution — one answer to react to,
70
+ not a menu), so the human settles it in one pass without a round-trip. (A tiny,
71
+ obvious question may stay one bare line; the sub-lines earn their keep only when a
72
+ human decision genuinely waits on it.)
63
73
  4. **Author the Steps.** Write the **full build plan** under `## Steps` — each step a
64
74
  small, verifiable increment in the exact format the parser reads:
65
75
 
66
76
  ```markdown
67
- 1. [ ] <title> — **done when:** <criterion, ideally a test or check result>
77
+ 1. [ ] feat(scope): <title> — **done when:** <criterion, ideally a test or check result>
68
78
  - seam: `<file>`, `<file>`
69
79
  - model: <optional — smallest that can carry it, with the one-phrase why>
70
80
  ```
@@ -74,12 +84,23 @@ an agent can follow with `/pb-build`. The argument only seeds how you get there.
74
84
  sharpened just-in-time when you reach them with `/pb-step`. Keep each small enough to
75
85
  verify in one review pass.
76
86
 
77
- **Give the title a Conventional-Commit type where the change has one** *(optional)*:
78
- a `<title>` that opens with `fix:`, `feat(escape-hatch):`, `chore:`, … becomes the
79
- checkpoint's commit subject verbatim (its scope and breaking `!` honored) — this is
80
- how each step lands a *real* `feat`/`fix`/`chore` in `git log`. Skip the prefix and
81
- the checkpoint defaults the type to `feat` and draws the scope from the build slug, so
82
- a plain title still yields a clean `feat(<slug>): <title>` ([D68](../../docs/decisions.md#d68)).
87
+ **The title *is* the commit subject — author it as one** *(D1)*: write each
88
+ `<title>` as a plain, single-line Conventional-Commit subject,
89
+ `type(scope): description`, and it lands in `git log` verbatim (its breaking `!`
90
+ honored) — this is how each step lands a *real* `feat`/`fix`/`chore` in the history.
91
+ Keep load-bearing detail — file paths, module names — in `seam` and `done-when`; it is
92
+ **never jammed into the title** (D2), which has to read as plain English and as a clean
93
+ subject at once. Aim for **≤72 characters** (GitHub's subject convention) — soft
94
+ guidance you eyeball at plan time, no lint, no gate (D9).
95
+
96
+ **Set the build's default scope, then let steps override it** *(D8)*: fill the
97
+ `**Scope:**` header once with a short feature-level name — the catch-all every step
98
+ inherits — and give a step its own `(scope)` when it touches a distinct code area
99
+ (`pb-plan`, `commitmsg`, `docs`). A step's `(scope)` names the *code area*; the header
100
+ names the *feature*, so scopes stay consistent and greppable across builds. Scope
101
+ resolves title-scope → `**Scope:**` default → build slug → bare, and type resolves
102
+ title-type → `feat`, so a scopeless plain title still yields a clean
103
+ `feat(<slug>): <title>` unchanged ([D68](../../docs/decisions.md#d68), D3).
83
104
 
84
105
  **Recommend a model per step where the signal is clear** *(optional)*: the
85
106
  `- model:` sub-line names the **smallest model that can carry the step**, with the
@@ -27,13 +27,28 @@ is fine — refining is always available.
27
27
  - **Attack (diverge in the problem space).** Hand the **Frame**, **Decisions**, and
28
28
  **Architecture sketch** a cold, adversarial read and surface holes — ambiguities,
29
29
  unhandled edge cases, hidden assumptions, collisions with the existing code. Append
30
- each as a one-line question under `## Open questions`, **never** as a Decision:
31
- resolving a hole is the human's convergence, not yours. This mode surfaces; it does
32
- not decide.
30
+ each under `## Open questions` in the expanded form the template shows — an opener
31
+ line (the hole as a question, slugged at birth: `- Q2 (some-slug): …`), a `*plain:*`
32
+ sub-line (what's at stake in plain words, enough to judge it cold), and a `*lean:*`
33
+ sub-line (your proposed resolution — one answer to react to, not a menu). Never
34
+ append a hole as a Decision: resolving it is the human's convergence, not yours —
35
+ the lean proposes; this mode surfaces, it does not decide. (A tiny, obvious question
36
+ may stay one bare line; the sub-lines earn their keep only when a human decision
37
+ genuinely waits on it.)
38
+
39
+ Then walk the human through it in the chat: list every hole you appended, each as
40
+ its explanation first and its lean second, and invite per-question answers — the
41
+ human can settle any of them right there in one message, no doc-flipping, no extra
42
+ round-trip per hole.
33
43
  - **Repair (re-sync to reality).** When the plan has drifted — a Decision was overtaken
34
- by what you built, a Constraint changed, a Step no longer matches the seam — propose
35
- the edits that bring `intent.md` back in line with the truth. Show the before/after for
36
- each, and **write only what the human approves**.
44
+ by what you built, a Constraint changed, a Step no longer matches the seam, or a
45
+ **step's title no longer describes what the diff actually does** — propose the edits
46
+ that bring `intent.md` back in line with the truth. A step title *is* its checkpoint
47
+ commit subject (D1), so bringing a drifted one back in line means keeping it a plain,
48
+ single-line `type(scope): description` subject, with load-bearing detail (file paths,
49
+ module names) in `seam` and `done-when`, **never jammed into the title** (D2) — the
50
+ checkpoint body pass then reconciles and lands that subject at the verify pause (D5).
51
+ Show the before/after for each, and **write only what the human approves**.
37
52
 
38
53
  ## The hard contracts
39
54
 
@@ -39,11 +39,16 @@ grew — but its everyday job is to sharpen, not to invent.
39
39
  a step that sharpened into rote work can drop to a smaller model; one that grew subtle
40
40
  earns a frontier one. Advisory, plain text (no backticks), never a gate.
41
41
  3. **Get the human's OK**, then write it into `## Steps` in the standard format —
42
- `N. [ ] <title> — **done when:** <criterion>` with a `- seam:` sub-line. Give the
43
- `<title>` an optional Conventional-Commit prefix (`fix:`, `feat(scope):`, …) when the
44
- change has a clear type — it becomes the checkpoint's commit subject; a plain title
45
- defaults to `feat` with the build slug as scope ([D68](../../docs/decisions.md#d68)).
46
- Revise the existing step in place; only append when you are genuinely adding an increment.
42
+ `N. [ ] <title> — **done when:** <criterion>` with a `- seam:` sub-line. Keep the
43
+ sharpened `<title>` a plain, single-line Conventional-Commit subject,
44
+ `type(scope): description` — it lands in `git log` verbatim, so as the step tightens
45
+ keep load-bearing detail (file paths, module names) in `seam` and `done-when`,
46
+ **never jammed into the title** (D1/D2). A sharpened step carries its own `(scope)`
47
+ when it touches a distinct code area — one that **overrides the build-default**
48
+ `**Scope:**` header; drop the scope and it falls back to that default → build slug →
49
+ bare, and the type to `feat` ([D68](../../docs/decisions.md#d68), D8). Aim for a soft
50
+ ≤72 chars, no gate (D9). Revise the existing step in place; only append when you are
51
+ genuinely adding an increment.
47
52
  4. **Revise the step's harness bindings if they drifted too** *(optional)*. If the
48
53
  build carries a `harness.json` (beside `intent.md`) and the reality that moved the
49
54
  step also changed which agents it wants, sharpen that step's slot bindings