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.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/lib/agents.js +1 -1
- package/dist/lib/check.js +9 -6
- package/dist/lib/intent.js +20 -0
- package/dist/lib/orient.js +4 -3
- package/dist/verbs/checkpoint.js +30 -11
- package/dist/verbs/finish.js +24 -8
- package/node_modules/checkride/CHANGELOG.md +234 -0
- package/node_modules/checkride/README.md +134 -31
- package/node_modules/checkride/dist/adapters.d.ts +50 -1
- package/node_modules/checkride/dist/adapters.d.ts.map +1 -1
- package/node_modules/checkride/dist/adapters.js +123 -7
- package/node_modules/checkride/dist/adapters.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/hook.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/hook.js +15 -1
- package/node_modules/checkride/dist/agent-setup/hook.js.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.d.ts +0 -1
- package/node_modules/checkride/dist/agent-setup/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/agent-setup/index.js.map +1 -1
- package/node_modules/checkride/dist/baseline/fallow.d.ts +64 -0
- package/node_modules/checkride/dist/baseline/fallow.d.ts.map +1 -0
- package/node_modules/checkride/dist/baseline/fallow.js +256 -0
- package/node_modules/checkride/dist/baseline/fallow.js.map +1 -0
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts +4 -6
- package/node_modules/checkride/dist/baseline/fingerprint.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/fingerprint.js +9 -4
- package/node_modules/checkride/dist/baseline/fingerprint.js.map +1 -1
- package/node_modules/checkride/dist/baseline/index.d.ts +4 -5
- package/node_modules/checkride/dist/baseline/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/baseline/index.js +2 -2
- package/node_modules/checkride/dist/baseline/index.js.map +1 -1
- package/node_modules/checkride/dist/{baseline/command.d.ts → baseline-command.d.ts} +11 -9
- package/node_modules/checkride/dist/baseline-command.d.ts.map +1 -0
- package/node_modules/checkride/dist/{baseline/command.js → baseline-command.js} +9 -7
- package/node_modules/checkride/dist/baseline-command.js.map +1 -0
- package/node_modules/checkride/dist/cli.d.ts.map +1 -1
- package/node_modules/checkride/dist/cli.js +95 -8
- package/node_modules/checkride/dist/cli.js.map +1 -1
- package/node_modules/checkride/dist/config.d.ts +37 -12
- package/node_modules/checkride/dist/config.d.ts.map +1 -1
- package/node_modules/checkride/dist/config.js +268 -60
- package/node_modules/checkride/dist/config.js.map +1 -1
- package/node_modules/checkride/dist/digest/digest.d.ts +0 -6
- package/node_modules/checkride/dist/digest/digest.d.ts.map +1 -1
- package/node_modules/checkride/dist/digest/digest.js +3 -3
- package/node_modules/checkride/dist/digest/digest.js.map +1 -1
- package/node_modules/checkride/dist/digest/index.d.ts +1 -2
- package/node_modules/checkride/dist/digest/index.d.ts.map +1 -1
- package/node_modules/checkride/dist/digest/index.js +1 -1
- package/node_modules/checkride/dist/digest/index.js.map +1 -1
- package/node_modules/checkride/dist/doctor.d.ts +14 -1
- package/node_modules/checkride/dist/doctor.d.ts.map +1 -1
- package/node_modules/checkride/dist/doctor.js +124 -55
- package/node_modules/checkride/dist/doctor.js.map +1 -1
- package/node_modules/checkride/dist/init.d.ts +6 -0
- package/node_modules/checkride/dist/init.d.ts.map +1 -1
- package/node_modules/checkride/dist/init.js +347 -121
- package/node_modules/checkride/dist/init.js.map +1 -1
- package/node_modules/checkride/dist/links.d.ts.map +1 -1
- package/node_modules/checkride/dist/links.js +8 -9
- package/node_modules/checkride/dist/links.js.map +1 -1
- package/node_modules/checkride/dist/orchestrator.d.ts +56 -1
- package/node_modules/checkride/dist/orchestrator.d.ts.map +1 -1
- package/node_modules/checkride/dist/orchestrator.js +506 -138
- package/node_modules/checkride/dist/orchestrator.js.map +1 -1
- package/node_modules/checkride/dist/pack.d.ts +76 -0
- package/node_modules/checkride/dist/pack.d.ts.map +1 -0
- package/node_modules/checkride/dist/pack.js +217 -0
- package/node_modules/checkride/dist/pack.js.map +1 -0
- package/node_modules/checkride/dist/pm/translate.d.ts +18 -10
- package/node_modules/checkride/dist/pm/translate.d.ts.map +1 -1
- package/node_modules/checkride/dist/pm/translate.js +27 -13
- package/node_modules/checkride/dist/pm/translate.js.map +1 -1
- package/node_modules/checkride/dist/smoke.d.ts +86 -0
- package/node_modules/checkride/dist/smoke.d.ts.map +1 -0
- package/node_modules/checkride/dist/smoke.js +346 -0
- package/node_modules/checkride/dist/smoke.js.map +1 -0
- package/node_modules/checkride/dist/snippets.d.ts +215 -0
- package/node_modules/checkride/dist/snippets.d.ts.map +1 -0
- package/node_modules/checkride/dist/snippets.js +360 -0
- package/node_modules/checkride/dist/snippets.js.map +1 -0
- package/node_modules/checkride/package.json +4 -2
- package/node_modules/checkride/schema/checkride.config.schema.json +11 -6
- package/package.json +3 -2
- package/skills/pb-build/SKILL.md +7 -0
- package/skills/pb-park/SKILL.md +3 -2
- package/skills/pb-plan/SKILL.md +30 -9
- package/skills/pb-refine/SKILL.md +21 -6
- package/skills/pb-step/SKILL.md +10 -5
- package/skills/pb-verify/SKILL.md +12 -1
- package/templates/intent.md +55 -11
- package/node_modules/checkride/dist/baseline/command.d.ts.map +0 -1
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,+BAA+B,CAAC;AAE/D,oFAAoF;AACpF,MAAM,CAAC,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAEvD,0DAA0D;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AA6DzC;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,WAA8B;IAC3D,MAAM,KAAK,GAAG,CAAC,WAAW,CAAC,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACxB,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACvC,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAClE,MAAM,IAAI,GAAa,EAAE,CAAC;YAC1B,CAAC,IAAI,CAAC,CAAC;YACP,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;gBAChE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC1B,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,CAAC,IAAI,CAAC,CAAC;IACT,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,UAAkB;IACjE,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,UAAU,KAAK,CAAC;AACpD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAyB;IACpD,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YACjD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,IAAI,CAAC,CAAC;gBACb,OAAO;YACT,CAAC;YACD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC;gBAC7C,IAAI,EAAE,GAAG,CAAC,OAAO;gBACjB,IAAI,EAAE,KAAK,CAAC,SAAS;gBACrB,IAAI,EAAE,KAAK,CAAC,IAAI;aACjB,CAAC,CAAC;YACH,OAAO,IAAI,CAAC,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAkB;IAClD,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,4HAA4H,CAAC;AACtI,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAGvC;IACC,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,WAAW;QACzB,eAAe,EAAE;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,IAAI;YACZ,oBAAoB,EAAE,KAAK;YAC3B,eAAe,EAAE,KAAK;YACtB,kCAAkC,EAAE,KAAK;YACzC,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,KAAK;YAClB,cAAc,EAAE,KAAK;YACrB,WAAW,EAAE,KAAK;SACnB;QACD,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC;AAeD,4EAA4E;AAC5E,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAEnD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,YAAY,CAAC,GAAW;IACrC,IAAI,WAAW,GAAa,EAAE,CAAC;IAC/B,IAAI,CAAC;QACH,WAAW,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,2DAA2D;IAC7D,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,cAAc,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAA4B,EAAE;QAC1E,IAAI,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAiB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,mGAAmG;AACnG,KAAK,UAAU,gBAAgB,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACtF,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3D,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,oBAAoB,CAAC,GAAW;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACvF,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjF,MAAM,KAAK,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/E,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAClC,MAAM,GAAG,GAA6B,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC1F,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,mHAAmH;AACnH,SAAS,iBAAiB,CAAC,MAAc;IACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACpE,OAAO,SAAS,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,IAI9B;IACC,IAAI,IAAI,CAAC,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrE,MAAM,QAAQ,GAA6B,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAChE,QAAQ,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,IAAI,CAAC,YAAY,KAAK,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACnD,OAAO,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,CAAC;IACzD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO;QACL,EAAE,EAAE,KAAK;QACT,SAAS,EAAE,CAAC;QACZ,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;QACrE,MAAM,EAAE,mBAAmB,OAAO,IAAI;KACvC,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,SAAS,MAAM,CACb,GAAW,EACX,eAAuB,EACvB,EAAkB,EAClB,KAAmB,EACnB,UAA8B;IAE9B,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC;IACxG,OAAO,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAMnC;IACC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAElD,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,OAAO;QAAE,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;IAEzC,IAAI,KAAK,GAA6B,EAAE,CAAC;IACzC,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,MAAM,CAAC,YAAY,EAAE,aAAa,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5G,MAAM,OAAO,GAAG,cAAc,CAAC;YAC7B,YAAY;YACZ,aAAa;YACb,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;SACtD,CAAC,CAAC;QACH,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,OAAO,WAAW,CAChB,sGAAsG;gBACpG,mGAAmG;gBACnG,sCAAsC,CACzC,CAAC;QACJ,CAAC;QACD,KAAK,GAAG,OAAO,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACtC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAEnG,MAAM,QAAQ,GAAG,uBAAuB,CAAC,EAAE,WAAW,EAAE,qBAAqB,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEjG,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IAC7F,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;IAEtE,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,OAAO;YACL,EAAE,EAAE,IAAI;YACR,SAAS,EAAE,CAAC;YACZ,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;YAC/D,MAAM,EAAE,mBAAmB,IAAI,CAAC,OAAO,+BAA+B,IAAI,KAAK,IAAI,CAAC,OAAO,aAAa;SACzG,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1F,MAAM,SAAS,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;IAC9D,OAAO;QACL,EAAE,EAAE,KAAK;QACT,SAAS,EAAE,CAAC;QACZ,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI;QAChE,MAAM,EAAE,oDAAoD,SAAS,+BAA+B,MAAM,IAAI;KAC/G,CAAC;AACJ,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "checkride",
|
|
3
|
-
"version": "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": "
|
|
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.
|
|
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.
|
|
62
|
+
"checkride": "0.5.2"
|
|
62
63
|
},
|
|
63
64
|
"bundleDependencies": [
|
|
64
65
|
"checkride"
|
package/skills/pb-build/SKILL.md
CHANGED
|
@@ -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**.
|
package/skills/pb-park/SKILL.md
CHANGED
|
@@ -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,
|
|
29
|
-
later
|
|
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.
|
package/skills/pb-plan/SKILL.md
CHANGED
|
@@ -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.
|
|
62
|
-
|
|
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
|
-
**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
how each step lands a *real* `feat`/`fix`/`chore` in
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
package/skills/pb-step/SKILL.md
CHANGED
|
@@ -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.
|
|
43
|
-
`<title>`
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|