@avi2dg/checks 0.20.0 → 0.22.0

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 (54) hide show
  1. package/CHANGELOG.md +65 -39
  2. package/CONTRIBUTING.md +11 -8
  3. package/README.md +15 -24
  4. package/bunfig.toml +1 -1
  5. package/docs/configs/commit-messages.md +5 -1
  6. package/docs/configs/dependency-rules.md +5 -2
  7. package/docs/configs/effect-rules.md +32 -33
  8. package/docs/configs/native-settings.md +74 -0
  9. package/docs/configs/typescript-rules.md +4 -0
  10. package/docs/design.md +24 -25
  11. package/docs/gates/checks-backtest.md +4 -0
  12. package/docs/gates/checks-ci-wiring.md +30 -93
  13. package/docs/gates/checks-comment-gate.md +4 -0
  14. package/docs/gates/checks-commit-identity.md +23 -27
  15. package/docs/gates/checks-docs.md +23 -21
  16. package/docs/gates/checks-flake.md +4 -10
  17. package/docs/gates/checks-lint-coverage.md +5 -1
  18. package/docs/gates/checks-lint.md +22 -104
  19. package/docs/gates/checks-mutation-compare.md +4 -0
  20. package/docs/gates/checks-quarantine-clock.md +4 -0
  21. package/docs/gates/checks-repetition.md +26 -41
  22. package/docs/gates/checks-subsumed-tests.md +4 -0
  23. package/docs/gates/checks-suppressions-ratchet.md +4 -0
  24. package/docs/gates/checks-test-layout.md +16 -8
  25. package/docs/gates/checks-test.md +68 -36
  26. package/docs/gates/checks-vendor.md +20 -13
  27. package/package.json +8 -21
  28. package/scripts/ci-wiring.ts +28 -97
  29. package/scripts/commit-identity.ts +32 -4
  30. package/scripts/doc-rules.ts +26 -11
  31. package/scripts/doc-templates.ts +2 -1
  32. package/scripts/docs.ts +4 -7
  33. package/scripts/gates.ts +0 -29
  34. package/scripts/git.ts +24 -1
  35. package/scripts/lint.ts +15 -34
  36. package/scripts/range-gate.ts +1 -2
  37. package/scripts/repetition.ts +40 -40
  38. package/scripts/shell-command.ts +7 -1
  39. package/scripts/swc.ts +46 -0
  40. package/scripts/test-layout.ts +40 -56
  41. package/scripts/test-skips.ts +180 -0
  42. package/scripts/test.ts +33 -38
  43. package/scripts/vendor.ts +55 -11
  44. package/dist/feature-rules.js +0 -349
  45. package/docs/configs/quality-file.md +0 -101
  46. package/docs/gates/checks-feature-owners.md +0 -113
  47. package/docs/gates/checks-quality.md +0 -109
  48. package/docs/gates/checks-size-budget.md +0 -107
  49. package/quality.schema.json +0 -496
  50. package/scripts/feature-owners.ts +0 -139
  51. package/scripts/quality-file.ts +0 -347
  52. package/scripts/quality.ts +0 -342
  53. package/scripts/size-budget.ts +0 -285
  54. package/scripts/size-rules.ts +0 -126
@@ -1,496 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "type": "object",
4
- "properties": {
5
- "$schema": {
6
- "type": "string"
7
- },
8
- "defaultBranch": {
9
- "type": "string",
10
- "minLength": 1,
11
- "description": "The branch pull requests merge into; main when absent"
12
- },
13
- "gates": {
14
- "type": "object",
15
- "properties": {
16
- "ci": {
17
- "type": "array",
18
- "prefixItems": [
19
- {
20
- "$ref": "#/$defs/Command"
21
- }
22
- ],
23
- "minItems": 1,
24
- "items": {
25
- "$ref": "#/$defs/Command"
26
- },
27
- "description": "The commands CI runs on every pull request to the default branch, each one plain command"
28
- },
29
- "scheduled": {
30
- "type": "array",
31
- "items": {
32
- "$ref": "#/$defs/Command"
33
- },
34
- "description": "The commands a cron-scheduled workflow runs"
35
- },
36
- "lint": {
37
- "type": "array",
38
- "items": {
39
- "type": "string",
40
- "enum": [
41
- "checks-lint-coverage",
42
- "checks-test-layout",
43
- "checks-commit-identity",
44
- "checks-comment-gate",
45
- "checks-suppressions-ratchet",
46
- "checks-ci-wiring",
47
- "checks-docs",
48
- "checks-quality",
49
- "checks-size-budget",
50
- "checks-repetition",
51
- "checks-feature-owners",
52
- "checks-quarantine-clock"
53
- ]
54
- },
55
- "allOf": [
56
- {
57
- "contains": {
58
- "const": "checks-commit-identity"
59
- }
60
- },
61
- {
62
- "contains": {
63
- "const": "checks-comment-gate"
64
- }
65
- },
66
- {
67
- "contains": {
68
- "const": "checks-suppressions-ratchet"
69
- }
70
- },
71
- {
72
- "contains": {
73
- "const": "checks-ci-wiring"
74
- }
75
- },
76
- {
77
- "contains": {
78
- "const": "checks-docs"
79
- }
80
- },
81
- {
82
- "contains": {
83
- "const": "checks-quarantine-clock"
84
- }
85
- }
86
- ]
87
- }
88
- },
89
- "additionalProperties": false
90
- },
91
- "commitIdentity": {
92
- "type": "object",
93
- "properties": {
94
- "authors": {
95
- "type": "array",
96
- "prefixItems": [
97
- {
98
- "$ref": "#/$defs/Identity"
99
- }
100
- ],
101
- "minItems": 1,
102
- "items": {
103
- "$ref": "#/$defs/Identity"
104
- },
105
- "description": "The identities allowed to author and commit, in place of the kit's default owner"
106
- }
107
- },
108
- "required": [
109
- "authors"
110
- ],
111
- "additionalProperties": false
112
- },
113
- "sources": {
114
- "type": "object",
115
- "properties": {
116
- "production": {
117
- "type": "array",
118
- "items": {
119
- "$ref": "#/$defs/PathGlob"
120
- },
121
- "description": "The source the repository ships, as against tests and tooling"
122
- },
123
- "libraries": {
124
- "type": "array",
125
- "items": {
126
- "$ref": "#/$defs/Library"
127
- }
128
- },
129
- "effect": {
130
- "type": "object",
131
- "properties": {
132
- "paths": {
133
- "type": "array",
134
- "prefixItems": [
135
- {
136
- "$ref": "#/$defs/PathGlob"
137
- }
138
- ],
139
- "minItems": 1,
140
- "items": {
141
- "$ref": "#/$defs/PathGlob"
142
- },
143
- "description": "Where source is written in Effect, held to the Effect rules of oxlint and the language service"
144
- },
145
- "exempt": {
146
- "type": "array",
147
- "items": {
148
- "$ref": "#/$defs/PathGlob"
149
- },
150
- "description": "Files under paths the Effect rules pass over"
151
- }
152
- },
153
- "required": [
154
- "paths"
155
- ],
156
- "additionalProperties": false
157
- }
158
- },
159
- "additionalProperties": false
160
- },
161
- "size": {
162
- "type": "object",
163
- "properties": {
164
- "applies": {
165
- "type": "string",
166
- "enum": [
167
- "ratchet",
168
- "all"
169
- ],
170
- "description": "Which production and test files the budget holds: ratchet, the ones a range adds or changes, to no more overrun per rule than at the range's base; all, every one. ratchet when absent. The rest are listed as advisory"
171
- },
172
- "production": {
173
- "type": "object",
174
- "properties": {
175
- "fileLines": {
176
- "type": "integer",
177
- "exclusiveMinimum": 0,
178
- "description": "The most lines a file may hold, blank and comment lines counted; 400 when absent"
179
- },
180
- "functionLines": {
181
- "type": "integer",
182
- "exclusiveMinimum": 0,
183
- "description": "The most lines a function may span, blank and comment lines counted; 100 when absent"
184
- },
185
- "statements": {
186
- "type": "integer",
187
- "exclusiveMinimum": 0,
188
- "description": "The most statements a function may hold; 30 when absent"
189
- },
190
- "complexity": {
191
- "type": "integer",
192
- "exclusiveMinimum": 0,
193
- "description": "The highest cognitive complexity a function may reach, a switch counted once; 15 when absent"
194
- },
195
- "depth": {
196
- "type": "integer",
197
- "exclusiveMinimum": 0,
198
- "description": "The deepest a block may nest inside a function; 4 when absent"
199
- }
200
- },
201
- "additionalProperties": false,
202
- "description": "The budget of the files under sources.production, and of the files listed as advisory outside tests/"
203
- },
204
- "tests": {
205
- "type": "object",
206
- "properties": {
207
- "fileLines": {
208
- "type": "integer",
209
- "exclusiveMinimum": 0,
210
- "description": "The most lines a file may hold, blank and comment lines counted; 600 when absent"
211
- },
212
- "statements": {
213
- "type": "integer",
214
- "exclusiveMinimum": 0,
215
- "description": "The most statements a function may hold; 50 when absent"
216
- },
217
- "complexity": {
218
- "type": "integer",
219
- "exclusiveMinimum": 0,
220
- "description": "The highest cognitive complexity a function may reach, a switch counted once; 15 when absent"
221
- },
222
- "depth": {
223
- "type": "integer",
224
- "exclusiveMinimum": 0,
225
- "description": "The deepest a block may nest inside a function; 4 when absent"
226
- }
227
- },
228
- "additionalProperties": false,
229
- "description": "The budget of the files under tests/, which sets no limit on a function's lines"
230
- }
231
- },
232
- "additionalProperties": false,
233
- "description": "The size budget oxlint holds production and test files to, read by checks-size-budget"
234
- },
235
- "features": {
236
- "type": "array",
237
- "items": {
238
- "type": "object",
239
- "properties": {
240
- "name": {
241
- "type": "string",
242
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
243
- "description": "The owner the dependency rule and the change signal name"
244
- },
245
- "root": {
246
- "$ref": "#/$defs/DirectoryPath"
247
- },
248
- "entries": {
249
- "type": "array",
250
- "prefixItems": [
251
- {
252
- "$ref": "#/$defs/FilePath_1"
253
- }
254
- ],
255
- "minItems": 1,
256
- "items": {
257
- "$ref": "#/$defs/FilePath_1"
258
- },
259
- "description": "The files under root that code outside it imports the feature through"
260
- },
261
- "allowFrom": {
262
- "type": "array",
263
- "items": {
264
- "$ref": "#/$defs/PathGlob"
265
- },
266
- "description": "Files outside root that may import past its entries, such as a CLI or a harness; tests/ always may"
267
- },
268
- "proof": {
269
- "$ref": "#/$defs/ProofPath"
270
- }
271
- },
272
- "required": [
273
- "name",
274
- "root",
275
- "entries",
276
- "proof"
277
- ],
278
- "additionalProperties": false
279
- }
280
- },
281
- "changeSignal": {
282
- "type": "string",
283
- "enum": [
284
- "advisory"
285
- ],
286
- "description": "Report which feature owners a change touches, without failing on it"
287
- },
288
- "agentRules": {
289
- "type": "object",
290
- "properties": {
291
- "on": {
292
- "type": "array",
293
- "items": {
294
- "$ref": "#/$defs/RuleName"
295
- },
296
- "description": "Catalogued Rules switched on here"
297
- },
298
- "off": {
299
- "type": "array",
300
- "items": {
301
- "$ref": "#/$defs/RuleName"
302
- },
303
- "description": "Catalogued Rules switched off here"
304
- }
305
- },
306
- "additionalProperties": false
307
- },
308
- "docs": {
309
- "type": "object",
310
- "properties": {
311
- "pages": {
312
- "type": "object",
313
- "properties": {
314
- "tutorial": {
315
- "type": "array",
316
- "items": {
317
- "$ref": "#/$defs/PathGlob"
318
- },
319
- "description": "The pages written as a tutorial, which teaches by building one thing"
320
- },
321
- "how-to": {
322
- "type": "array",
323
- "items": {
324
- "$ref": "#/$defs/PathGlob"
325
- },
326
- "description": "The pages written as a how-to, which walks one task"
327
- },
328
- "reference": {
329
- "type": "array",
330
- "items": {
331
- "$ref": "#/$defs/PathGlob"
332
- },
333
- "description": "The pages written as reference, which describes a thing to be looked up"
334
- },
335
- "explanation": {
336
- "type": "array",
337
- "items": {
338
- "$ref": "#/$defs/PathGlob"
339
- },
340
- "description": "The pages written as an explanation, which says why"
341
- }
342
- },
343
- "additionalProperties": false,
344
- "description": "The Diátaxis mode of each page, whose template checks-docs holds the page to; a page under docs/ needs one"
345
- },
346
- "forConsumers": {
347
- "type": "array",
348
- "items": {
349
- "$ref": "#/$defs/DocGlob"
350
- },
351
- "description": "The living docs that speak to a repository installing this one, whose bun run commands checks-docs does not hold to this package.json"
352
- }
353
- },
354
- "additionalProperties": false,
355
- "description": "What checks-docs reads to map a doc file to its template"
356
- }
357
- },
358
- "additionalProperties": false,
359
- "title": "quality.json",
360
- "description": "What a repository has opted into from @avi2dg/checks, read by its bins and agent Rule selection",
361
- "allOf": [
362
- {
363
- "if": {
364
- "required": [
365
- "size"
366
- ]
367
- },
368
- "then": {
369
- "required": [
370
- "sources"
371
- ],
372
- "properties": {
373
- "sources": {
374
- "required": [
375
- "production"
376
- ],
377
- "properties": {
378
- "production": {
379
- "minItems": 1
380
- }
381
- }
382
- }
383
- }
384
- }
385
- },
386
- {
387
- "if": {
388
- "required": [
389
- "changeSignal"
390
- ]
391
- },
392
- "then": {
393
- "required": [
394
- "features"
395
- ],
396
- "properties": {
397
- "features": {
398
- "minItems": 1
399
- }
400
- }
401
- }
402
- }
403
- ],
404
- "$defs": {
405
- "Command": {
406
- "type": "string",
407
- "minLength": 1
408
- },
409
- "Identity": {
410
- "type": "object",
411
- "properties": {
412
- "name": {
413
- "type": "string",
414
- "minLength": 1
415
- },
416
- "email": {
417
- "type": "string",
418
- "minLength": 1
419
- }
420
- },
421
- "required": [
422
- "name",
423
- "email"
424
- ],
425
- "additionalProperties": false
426
- },
427
- "PathGlob": {
428
- "type": "string",
429
- "pattern": "^(?!\\.\\.?(?:\\/|$))(?:\\*\\*|(?:[\\w.@+-]|\\*(?!\\*))+)(?:\\/(?!\\.\\.?(?:\\/|$))(?:\\*\\*|(?:[\\w.@+-]|\\*(?!\\*))+))*\\/(?:[\\w.@+-]|\\*(?!\\*))*\\.\\w+$",
430
- "description": "A glob from the repository root that oxlint, the Effect language service and git read alike: a directory first, * within a segment, ** as a whole one, a file name with an extension last, and no braces, ?, [ or leading ./"
431
- },
432
- "FilePath": {
433
- "type": "string",
434
- "pattern": "^(?:(?!\\.\\.?(?:\\/|$))[\\w.@+-]+\\/)*[\\w.@+-]*\\.\\w+$",
435
- "description": "The manifest inside the clone holding the version; package.json when absent"
436
- },
437
- "Library": {
438
- "type": "object",
439
- "properties": {
440
- "name": {
441
- "type": "string",
442
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
443
- "description": "The link checks-vendor manages under repos/"
444
- },
445
- "package": {
446
- "type": "string",
447
- "minLength": 1,
448
- "description": "The npm package whose installed version picks the tag"
449
- },
450
- "repository": {
451
- "type": "string",
452
- "minLength": 1,
453
- "description": "The git remote checks-vendor clones the tag from"
454
- },
455
- "tag": {
456
- "type": "string",
457
- "pattern": "\\{version\\}",
458
- "description": "The tag template, with {version} for the installed version"
459
- },
460
- "path": {
461
- "$ref": "#/$defs/FilePath"
462
- }
463
- },
464
- "required": [
465
- "name",
466
- "package",
467
- "repository",
468
- "tag"
469
- ],
470
- "additionalProperties": false
471
- },
472
- "DirectoryPath": {
473
- "type": "string",
474
- "pattern": "^(?!\\.\\.?(?:\\/|$))[\\w.@+-]+(?:\\/(?!\\.\\.?(?:\\/|$))[\\w.@+-]+)*$",
475
- "description": "The directory the feature owns"
476
- },
477
- "FilePath_1": {
478
- "type": "string",
479
- "pattern": "^(?:(?!\\.\\.?(?:\\/|$))[\\w.@+-]+\\/)*[\\w.@+-]*\\.\\w+$"
480
- },
481
- "ProofPath": {
482
- "type": "string",
483
- "pattern": "^tests\\/e2e\\/(?:(?!\\.\\.?(?:\\/|$))[\\w.@+-]+\\/)*[\\w.@+-]+\\.test\\.tsx?$",
484
- "description": "The end-to-end test that imports one of entries"
485
- },
486
- "RuleName": {
487
- "type": "string",
488
- "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
489
- },
490
- "DocGlob": {
491
- "type": "string",
492
- "pattern": "^(?:(?!\\.\\.?(?:\\/|$))(?:\\*\\*|(?:[\\w.@+-]|\\*(?!\\*))+)\\/)*(?:[\\w.@+-]|\\*(?!\\*))*\\.\\w+$",
493
- "description": "A glob from the repository root that checks-docs reads: * within a segment, ** as a whole one, a file name with an extension last"
494
- }
495
- }
496
- }
@@ -1,139 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { parse } from "@swc/core";
3
- import { Console, Effect, Path, Schema } from "effect";
4
- import { changedPaths, git, pathsAt, rangeEnds, type Change } from "./git.ts";
5
- import { runMain } from "./main.ts";
6
- import { PROOF_DIRECTORY, type Feature } from "./quality-file.ts";
7
- import { rangeGateInputs } from "./range-gate.ts";
8
-
9
- type Unproven = {
10
- readonly feature: string;
11
- readonly reason: string;
12
- };
13
-
14
- type Touch = {
15
- readonly feature: string;
16
- readonly paths: readonly string[];
17
- };
18
-
19
- class ProofUnparsed extends Schema.TaggedError<ProofUnparsed>()("ProofUnparsed", {
20
- message: Schema.String,
21
- }) {}
22
-
23
- const NAME = "feature-owners";
24
- const USAGE = "usage: feature-owners.ts <ref> | <base-ref> <head-ref>";
25
- const RELATIVE = /^\.\.?\//;
26
- const SCRIPT_EXTENSIONS = new Map([
27
- [".js", [".ts", ".tsx"]],
28
- [".jsx", [".tsx"]],
29
- [".mjs", [".mts"]],
30
- [".cjs", [".cts"]],
31
- ]);
32
-
33
- const runtimeSpecifiers = Effect.fn("runtimeSpecifiers")(function* (file: string, source: string) {
34
- const module = yield* Effect.tryPromise({
35
- try: () => parse(source, { syntax: "typescript", tsx: file.endsWith(".tsx"), target: "esnext" }),
36
- catch: (error) => new ProofUnparsed({ message: `${file} does not parse: ${String(error)}` }),
37
- });
38
- return module.body.flatMap((item) => {
39
- if (item.type === "ImportDeclaration" && !item.typeOnly) return [item.source.value];
40
- // swc types source as optional, but a local export such as `export { x }` carries null.
41
- if (item.type === "ExportNamedDeclaration" && !item.typeOnly && item.source) return [item.source.value];
42
- if (item.type === "ExportAllDeclaration") return [item.source.value];
43
- return [];
44
- });
45
- });
46
-
47
- const candidatesFor = Effect.fn("candidatesFor")(function* (importer: string, specifier: string) {
48
- if (!RELATIVE.test(specifier)) return [];
49
- const path = yield* Path.Path;
50
- const target = path.join(path.dirname(importer), specifier);
51
- const extension = path.extname(target);
52
- const sources = SCRIPT_EXTENSIONS.get(extension);
53
- if (sources !== undefined) return [target, ...sources.map((source) => `${target.slice(0, -extension.length)}${source}`)];
54
- if (extension !== "") return [target];
55
- return [`${target}.ts`, `${target}.tsx`, `${target}/index.ts`, `${target}/index.tsx`];
56
- });
57
-
58
- const proofImportsAnEntry = Effect.fn("proofImportsAnEntry")(function* (root: string, head: string, feature: Feature) {
59
- const source = yield* git(["cat-file", "blob", `${head}:${feature.proof}`], root);
60
- const specifiers = yield* runtimeSpecifiers(feature.proof, source);
61
- const reached = yield* Effect.forEach(specifiers, (specifier) => candidatesFor(feature.proof, specifier));
62
- return reached.flat().some((path) => feature.entries.includes(path));
63
- });
64
-
65
- const unproven = Effect.fn("unproven")(function* (root: string, head: string, features: readonly Feature[]) {
66
- const declared = features.flatMap((feature) => [feature.proof, ...feature.entries]);
67
- const present = new Set(yield* pathsAt(head, declared.map((path) => `:(literal)${path}`), root));
68
- const found: Unproven[] = [];
69
- for (const feature of features) {
70
- const refuse = (reason: string) => found.push({ feature: feature.name, reason });
71
- for (const entry of feature.entries.filter((path) => !present.has(path))) refuse(`entry ${entry} is not in the head commit`);
72
- if (!present.has(feature.proof)) {
73
- refuse(`proof ${feature.proof} is not in the head commit`);
74
- continue;
75
- }
76
- const reason = yield* proofImportsAnEntry(root, head, feature).pipe(
77
- Effect.map((imports) => (imports ? undefined : `proof ${feature.proof} imports none of its entries, ${feature.entries.join(", ")}`)),
78
- Effect.catchTag("ProofUnparsed", (error) => Effect.succeed(`proof ${error.message}`)),
79
- );
80
- if (reason !== undefined) refuse(reason);
81
- }
82
- return found;
83
- });
84
-
85
- function ownerOf(features: readonly Feature[], path: string): string | undefined {
86
- return features.find((feature) => path.startsWith(`${feature.root}/`) || path === feature.proof)?.name;
87
- }
88
-
89
- function touches(features: readonly Feature[], changes: readonly Change[]): readonly Touch[] {
90
- const byOwner = new Map<string, string[]>();
91
- for (const change of changes) {
92
- for (const path of change.kind === "renamed" ? [change.from, change.path] : [change.path]) {
93
- const owner = ownerOf(features, path);
94
- if (owner === undefined) continue;
95
- byOwner.set(owner, [...(byOwner.get(owner) ?? []), path]);
96
- }
97
- }
98
- return features.flatMap((feature) => {
99
- const paths = byOwner.get(feature.name);
100
- return paths === undefined ? [] : [{ feature: feature.name, paths }];
101
- });
102
- }
103
-
104
- function proofReport(features: readonly Feature[], found: readonly Unproven[]): string {
105
- if (found.length === 0) {
106
- return `${NAME}: ${features.length} feature(s) keep a proof under ${PROOF_DIRECTORY} that imports an entry`;
107
- }
108
- return [
109
- `${NAME}: ${found.length} problem(s) with the features' runnable proofs:`,
110
- ...found.map(({ feature, reason }) => ` ${feature}: ${reason}`),
111
- ].join("\n");
112
- }
113
-
114
- function signalReport(touched: readonly Touch[]): string {
115
- if (touched.length === 0) return `${NAME}: advisory, the range touches no feature owner`;
116
- const judge = touched.length > 1 ? "; a reviewer judges whether they make one slice" : "";
117
- return [
118
- `${NAME}: advisory, the range touches ${touched.length} feature owner(s)${judge}:`,
119
- ...touched.map(({ feature, paths }) => ` ${feature}: ${paths.join(", ")}`),
120
- ].join("\n");
121
- }
122
-
123
- const owners = Effect.gen(function* () {
124
- const { refs, root, source, quality } = yield* rangeGateInputs(USAGE);
125
- const features = quality.features ?? [];
126
- if (features.length === 0) {
127
- yield* Console.log(`${NAME}: ${source} declares no feature`);
128
- return true;
129
- }
130
- const { base, head } = yield* rangeEnds(refs.first, refs.second, root);
131
- const found = yield* unproven(root, head, features);
132
- yield* Console.log(proofReport(features, found));
133
- if (quality.changeSignal === "advisory") {
134
- yield* Console.log(signalReport(touches(features, yield* changedPaths(base, head, [], root))));
135
- }
136
- return found.length === 0;
137
- });
138
-
139
- if (import.meta.main) runMain(NAME, owners);