@erclx/aitk 3.52.0 → 3.52.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.
@@ -0,0 +1,412 @@
1
+ import {
2
+ auditSet,
3
+ heroStamp,
4
+ markdownBans,
5
+ type Measure,
6
+ pluginManifests,
7
+ sandboxCoverage,
8
+ seedStandards,
9
+ standardCriteria,
10
+ unreferencedRules,
11
+ } from '@/gate/measures'
12
+
13
+ /**
14
+ * One thing a stage does, as an argument vector rather than a shell line.
15
+ *
16
+ * The script this replaces held every check as text and handed it to `eval`,
17
+ * which made each stage a quoting question: whether the string wanted a shell
18
+ * or was an argument list that should never see one. Nothing in the set turned
19
+ * out to depend on expansion once the drift assertion stopped being a shell
20
+ * line, so the whole table is vectors and the hazard is gone rather than
21
+ * carried forward.
22
+ */
23
+ export type Check =
24
+ /** Any binary on PATH, or a script named by path under the project root. */
25
+ | {
26
+ readonly kind: 'command'
27
+ readonly argv: readonly string[]
28
+ readonly failure: string
29
+ }
30
+ /** This checkout's own CLI, so a worktree gate never reads the main tree. */
31
+ | {
32
+ readonly kind: 'cli'
33
+ readonly argv: readonly string[]
34
+ readonly failure: string
35
+ }
36
+ /**
37
+ * A regenerated surface asserted against the index and the working tree.
38
+ *
39
+ * Both halves are read, since a regen emitting a file nobody ever committed
40
+ * satisfies a diff and fails here. The pathspec is one argument rather than a
41
+ * shell word, which is what git already receives: `*` matches a slash in a
42
+ * pathspec, so `*index.md` reaches a nested index the way it always did.
43
+ */
44
+ | {
45
+ readonly kind: 'drift'
46
+ readonly pathspec: string
47
+ readonly failure: string
48
+ }
49
+ /** A reading whose verdict is a comparison rather than an exit code. */
50
+ | { readonly kind: 'measure'; readonly measure: Measure }
51
+
52
+ export interface Stage {
53
+ readonly id: string
54
+ /** The heading this stage prints, matching the frame it replaces. */
55
+ readonly label: string
56
+ /**
57
+ * The changed-file pattern this stage needs before it runs. Absent means the
58
+ * stage always runs, which is correct wherever the input is diffuse enough
59
+ * that no path predicts it.
60
+ */
61
+ readonly scope?: RegExp
62
+ /** What a scoped-out stage says, so a skip never reads as a pass. */
63
+ readonly skipped?: string
64
+ /**
65
+ * Whether this stage belongs in the run at all, read off the write grant
66
+ * rather than the changed set. Absent means it always belongs.
67
+ */
68
+ readonly when?: (options: { readonly write: boolean }) => boolean
69
+ readonly checks: readonly Check[]
70
+ /** The line a clean stage prints, where the checks emit none of their own. */
71
+ readonly success?: string
72
+ }
73
+
74
+ /**
75
+ * Corpora a `src/` test asserts over from outside `src/`, censused in
76
+ * `.claude/context/development/verification.md`. This list and that census are
77
+ * two copies of one set with nothing comparing them, so a corpus joining the
78
+ * census joins this list in the same change. The first four are directory
79
+ * prefixes because their tests walk the tree whole, which is what reaches a
80
+ * rule or a skill a branch adds rather than edits.
81
+ */
82
+ export const TEST_CORPORA_PATTERNS = [
83
+ '^claude/skills/',
84
+ '^governance/rules/',
85
+ '^\\.claude/hooks/',
86
+ '^tooling/claude/seeds/\\.claude/hooks/',
87
+ '^standards/markdown\\.md$',
88
+ '^tooling/base/reference\\.md$',
89
+ '^tooling/web/configs/scripts/worktree-port\\.sh$',
90
+ '^\\.cspell/banned-spellings\\.txt$',
91
+ '^scripts/lib/worktree\\.sh$',
92
+ '^scripts/core/check-ignore-parity\\.sh$',
93
+ ]
94
+
95
+ const TESTS_SCOPE = new RegExp(
96
+ [
97
+ '^src/',
98
+ '^vitest\\.config\\.ts$',
99
+ '^tsconfig\\.json$',
100
+ '^package\\.json$',
101
+ ...TEST_CORPORA_PATTERNS,
102
+ ].join('|'),
103
+ )
104
+
105
+ const script = (name: string, failure: string): Check => ({
106
+ kind: 'command',
107
+ argv: ['bash', `scripts/core/${name}`],
108
+ failure,
109
+ })
110
+
111
+ /**
112
+ * Every stage the gate runs, in the order it runs them.
113
+ *
114
+ * Order is the product here rather than an accident of the file. The four
115
+ * regenerating stages run before anything reads what they wrote, the stages
116
+ * calling this checkout's CLI sit together so a reader meets one group rather
117
+ * than the same kind of stage at intervals, and the three scoped stages sit
118
+ * last where a skip costs the run nothing it already paid for.
119
+ *
120
+ * A stage halts the run when one of its checks fails, so clearing a
121
+ * regenerate-then-assert stage reveals the next one behind it rather than the
122
+ * whole set at once.
123
+ */
124
+ export const STAGES: readonly Stage[] = [
125
+ {
126
+ id: 'format',
127
+ label: 'Formatting',
128
+ when: ({ write }) => write,
129
+ checks: [
130
+ {
131
+ kind: 'command',
132
+ argv: ['bun', 'run', 'format'],
133
+ failure: 'Format failed',
134
+ },
135
+ ],
136
+ success: 'Format applied',
137
+ },
138
+ {
139
+ id: 'format-check',
140
+ label: 'Format check',
141
+ when: ({ write }) => !write,
142
+ checks: [
143
+ {
144
+ kind: 'command',
145
+ argv: ['bun', 'run', 'check:format'],
146
+ failure: 'Format check failed',
147
+ },
148
+ ],
149
+ success: 'Format check passed',
150
+ },
151
+ {
152
+ id: 'indexes',
153
+ label: 'Indexes',
154
+ checks: [
155
+ script('regen-indexes.sh', 'Index regen failed'),
156
+ {
157
+ kind: 'drift',
158
+ pathspec: '*index.md',
159
+ failure:
160
+ 'Indexes drifted. Run bun run check and commit the updated index files.',
161
+ },
162
+ ],
163
+ success: 'Indexes clean',
164
+ },
165
+ {
166
+ id: 'consumed-copies',
167
+ label: 'Consumed copies',
168
+ checks: [
169
+ script('regen-claude-copies.sh', 'Consumed-copy regen failed'),
170
+ {
171
+ kind: 'drift',
172
+ pathspec: '.claude/rules',
173
+ failure:
174
+ 'Consumed copies drifted. Run bun run check and commit .claude/rules.',
175
+ },
176
+ ],
177
+ success: 'Consumed copies clean',
178
+ },
179
+ {
180
+ // Only the markup is asserted for drift. The image beside it is a chromium
181
+ // render whose bytes move with the browser version, so a drift check over it
182
+ // would fail on a machine whose chromium differs rather than on a stale
183
+ // count. The stamp measure below is what covers the image instead.
184
+ id: 'hero',
185
+ label: 'Hero',
186
+ checks: [
187
+ script('regen-hero.sh', 'Hero regen failed'),
188
+ {
189
+ kind: 'drift',
190
+ pathspec: 'assets/hero.html',
191
+ failure:
192
+ 'Hero counts drifted. Run bun run check, then aitk capture assets/hero.html, and commit assets/hero.html with assets/hero.png and assets/hero.stamp.',
193
+ },
194
+ { kind: 'measure', measure: heroStamp },
195
+ ],
196
+ success: 'Hero clean',
197
+ },
198
+ {
199
+ id: 'tooling-paths',
200
+ label: 'Tooling paths',
201
+ checks: [
202
+ script('regen-tooling-paths.sh', 'Tooling-path regen failed'),
203
+ {
204
+ kind: 'drift',
205
+ pathspec: 'claude/skills/aitk-cli/SKILL.md',
206
+ failure:
207
+ 'The overwrite contract drifted from what the stacks hold. Run bun run check and commit claude/skills/aitk-cli/SKILL.md.',
208
+ },
209
+ ],
210
+ success: 'Tooling paths clean',
211
+ },
212
+ {
213
+ // The claude manifest is the only route a target's ignore set travels, and
214
+ // it is hand-maintained beside this repository's own `.gitignore` with
215
+ // nothing comparing the two. A drift between them reaches every target on
216
+ // the next `aitk tooling sync` and surfaces to nobody, which is why this
217
+ // gates rather than reports. It is not a drift assert: no generator
218
+ // produces either list, so there is nothing to regenerate and diff.
219
+ id: 'ignore-parity',
220
+ label: 'Ignore parity',
221
+ checks: [
222
+ script(
223
+ 'check-ignore-parity.sh',
224
+ "The ignore set a target receives disagrees with this repository's own.",
225
+ ),
226
+ ],
227
+ success: 'Ignore parity clean',
228
+ },
229
+ {
230
+ id: 'skill-paths',
231
+ label: 'Skill paths',
232
+ checks: [
233
+ script(
234
+ 'check-skill-paths.sh',
235
+ 'Shipped skills reference a repo-local path.',
236
+ ),
237
+ ],
238
+ success: 'Skill paths clean',
239
+ },
240
+ {
241
+ id: 'plugin-boundary',
242
+ label: 'Plugin boundary',
243
+ checks: [
244
+ script(
245
+ 'check-plugin-boundary.sh',
246
+ 'Plugin ships toolkit-internal content.',
247
+ ),
248
+ ],
249
+ success: 'Plugin boundary clean',
250
+ },
251
+ {
252
+ // Seed prose is installed into every scaffolded project and read there as
253
+ // instruction about that project, so a line naming this repository's CLI
254
+ // hands a target a verb it may not be able to run. This gates for the
255
+ // reason the seed-standards stage below gates: a defect authored once
256
+ // propagates into every project scaffolded after it.
257
+ id: 'seed-independence',
258
+ label: 'Seed independence',
259
+ checks: [
260
+ script('check-seed-independence.sh', 'Seed prose cites the toolkit CLI.'),
261
+ ],
262
+ success: 'Seed prose cites no toolkit CLI',
263
+ },
264
+ {
265
+ // A stack entry naming a rule folder takes every rule in it, which is what
266
+ // stops a new rule from needing a second edit to reach a target. The
267
+ // failure it leaves open is a rule authored into a folder no stack names,
268
+ // which `aitk gov install` never reaches on its own.
269
+ id: 'unreferenced-rules',
270
+ label: 'Unreferenced rules',
271
+ checks: [{ kind: 'measure', measure: unreferencedRules }],
272
+ },
273
+ {
274
+ // Only the citation half of the audit gates. Length, depth, table, and
275
+ // index findings are judgment thresholds, and failing a push on one would
276
+ // make the stage something to route around.
277
+ id: 'context-citations',
278
+ label: 'Context citations',
279
+ checks: [
280
+ {
281
+ kind: 'cli',
282
+ argv: ['context', 'audit', '--citations-only'],
283
+ failure:
284
+ 'A cited context path does not resolve. Run bun src/cli.ts context audit.',
285
+ },
286
+ ],
287
+ success: 'Context citations resolve',
288
+ },
289
+ {
290
+ // A rule citing a file that moved fails silently. The consumed-copy drift
291
+ // stage passes an authored rule and its copy that are wrong together, and
292
+ // nothing else resolves the path until a session opens it. A rule whose
293
+ // frontmatter glob names a directory that moved fails the same way, by
294
+ // never firing again. The classes where absence is correct are separated
295
+ // inside the verb rather than left as a threshold here.
296
+ id: 'rule-citations',
297
+ label: 'Rule citations',
298
+ checks: [
299
+ {
300
+ kind: 'cli',
301
+ argv: ['gov', 'citations'],
302
+ failure:
303
+ 'A path a rule cites, or an internal frontmatter glob, does not resolve. Run bun src/cli.ts gov citations.',
304
+ },
305
+ ],
306
+ success: 'Rule citations resolve',
307
+ },
308
+ {
309
+ id: 'markdown-bans',
310
+ label: 'Markdown bans',
311
+ checks: [{ kind: 'measure', measure: markdownBans }],
312
+ },
313
+ {
314
+ id: 'seed-standards',
315
+ label: 'Seed standards',
316
+ checks: [{ kind: 'measure', measure: seedStandards }],
317
+ },
318
+ {
319
+ // Presence of a required file is a fact, so it gates. The name,
320
+ // description, folder, and requirement-section measures beside it report
321
+ // and are read from a bare run.
322
+ id: 'skill-requirements',
323
+ label: 'Skill requirements',
324
+ checks: [
325
+ {
326
+ kind: 'cli',
327
+ argv: ['claude', 'skills', 'audit', '--requirements-only'],
328
+ failure:
329
+ 'A skill folder carries no REQUIREMENT.md. Run bun src/cli.ts claude skills audit.',
330
+ },
331
+ ],
332
+ success: 'Skill requirements present',
333
+ },
334
+ {
335
+ id: 'standard-criteria',
336
+ label: 'Standard success criteria',
337
+ checks: [{ kind: 'measure', measure: standardCriteria }],
338
+ },
339
+ {
340
+ id: 'sandbox-coverage',
341
+ label: 'Sandbox coverage',
342
+ checks: [{ kind: 'measure', measure: sandboxCoverage }],
343
+ },
344
+ {
345
+ id: 'audit-set',
346
+ label: 'Audit set',
347
+ checks: [{ kind: 'measure', measure: auditSet }],
348
+ },
349
+ {
350
+ id: 'plugin-manifests',
351
+ label: 'Plugin manifests',
352
+ checks: [{ kind: 'measure', measure: pluginManifests }],
353
+ },
354
+ {
355
+ id: 'spelling',
356
+ label: 'Spelling',
357
+ checks: [
358
+ {
359
+ kind: 'command',
360
+ argv: ['bun', 'run', 'check:spell'],
361
+ failure: 'Spell check failed',
362
+ },
363
+ ],
364
+ success: 'Spell check passed',
365
+ },
366
+ {
367
+ id: 'shell',
368
+ label: 'Shell',
369
+ scope: /\.sh$|^package\.json$/,
370
+ skipped: 'Skipped, no shell changes',
371
+ checks: [
372
+ {
373
+ kind: 'command',
374
+ argv: ['bun', 'run', 'check:shell'],
375
+ failure: 'Shell check failed',
376
+ },
377
+ script(
378
+ 'check-color-source.sh',
379
+ 'A color escape is defined outside scripts/lib/ui.sh.',
380
+ ),
381
+ ],
382
+ success: 'Shell check passed',
383
+ },
384
+ {
385
+ id: 'types',
386
+ label: 'Types',
387
+ scope: /^src\/|^tsconfig\.json$|^package\.json$/,
388
+ skipped: 'Skipped, no TypeScript changes',
389
+ checks: [
390
+ {
391
+ kind: 'command',
392
+ argv: ['bun', 'run', 'check:types'],
393
+ failure: 'Typecheck failed',
394
+ },
395
+ ],
396
+ success: 'Typecheck passed',
397
+ },
398
+ {
399
+ id: 'tests',
400
+ label: 'Tests',
401
+ scope: TESTS_SCOPE,
402
+ skipped: 'Skipped, no TypeScript or asserted-corpus changes',
403
+ checks: [
404
+ {
405
+ kind: 'command',
406
+ argv: ['bun', 'run', 'test'],
407
+ failure: 'Tests failed',
408
+ },
409
+ ],
410
+ success: 'Tests passed',
411
+ },
412
+ ]