@avi2dg/checks 0.12.0 → 0.13.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.
- package/README.md +151 -159
- package/dist/feature-rules.js +14 -1
- package/package.json +19 -3
- package/quality.schema.json +48 -0
- package/scripts/doc-outline.ts +215 -0
- package/scripts/doc-rules.ts +176 -0
- package/scripts/doc-templates.ts +203 -0
- package/scripts/docs.ts +90 -0
- package/scripts/gates.ts +1 -0
- package/scripts/quality-file.ts +21 -0
- package/templates/adr.md +29 -0
- package/templates/agents.md +17 -0
- package/templates/changelog.md +37 -0
- package/templates/claude.md +2 -0
- package/templates/explanation.md +15 -0
- package/templates/how-to.md +33 -0
- package/templates/readme.md +43 -0
- package/templates/reference.md +15 -0
- package/templates/tutorial.md +31 -0
package/README.md
CHANGED
|
@@ -13,12 +13,20 @@ compiles into it, the test-layout check with its bunfig preset, the commit-ident
|
|
|
13
13
|
comment gate with its backtest, the oxlint suppressions ratchet, the
|
|
14
14
|
size budget, the feature-owner change signal and proof check, the
|
|
15
15
|
Stryker mutation-testing preset with its no-regression comparator, the
|
|
16
|
-
CI-wiring check,
|
|
17
|
-
|
|
16
|
+
CI-wiring check, a template for each kind of doc file with the gate that
|
|
17
|
+
holds each doc file to its template, and the Effect error-channel plugin
|
|
18
|
+
compiled to JavaScript.
|
|
18
19
|
|
|
19
20
|
Published as `@avi2dg/checks` on the public npm registry.
|
|
20
21
|
|
|
21
|
-
##
|
|
22
|
+
## Before you begin
|
|
23
|
+
|
|
24
|
+
- Bun, which runs every bin.
|
|
25
|
+
The kit is tested on the version its own `.bun-version` pins.
|
|
26
|
+
- A git repository, whose history the range gates read.
|
|
27
|
+
- The peer versions the install line below pins, which `peerDependencies` in `package.json` holds.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
22
30
|
|
|
23
31
|
From the consuming repo:
|
|
24
32
|
|
|
@@ -50,7 +58,7 @@ node_modules/
|
|
|
50
58
|
```
|
|
51
59
|
|
|
52
60
|
`quality.json` at the repository root declares what the repository
|
|
53
|
-
opts into, starting with the commands its CI runs
|
|
61
|
+
opts into, starting with the commands its CI runs. See "Declare policy in the quality file"
|
|
54
62
|
below:
|
|
55
63
|
|
|
56
64
|
```json
|
|
@@ -75,9 +83,9 @@ cp node_modules/@avi2dg/checks/bunfig.toml bunfig.toml
|
|
|
75
83
|
```
|
|
76
84
|
|
|
77
85
|
`checks-test` runs `bun test --randomize` and fails on a skip the
|
|
78
|
-
repository has not declared
|
|
86
|
+
repository has not declared. See "Run the suite" below.
|
|
79
87
|
|
|
80
|
-
`checks-lint` runs every kit gate a lint needs
|
|
88
|
+
`checks-lint` runs every kit gate a lint needs. See "Run every lint gate"
|
|
81
89
|
below. Three of them:
|
|
82
90
|
|
|
83
91
|
`lint-coverage.sh` fails when oxlint silently skips a tracked `.ts` or
|
|
@@ -89,7 +97,7 @@ or its config does not parse.
|
|
|
89
97
|
`test-layout.ts` decides the test layout described below.
|
|
90
98
|
|
|
91
99
|
`commit-identity.ts` refuses a commit with an author other than the
|
|
92
|
-
repository owner
|
|
100
|
+
repository owner. See "Check commit identities" below.
|
|
93
101
|
|
|
94
102
|
A repo that runs mutation testing installs `@stryker-mutator/core` and
|
|
95
103
|
`@hughescr/stryker-bun-runner`, then spreads the shipped preset in
|
|
@@ -106,7 +114,7 @@ export default {
|
|
|
106
114
|
The registry version is pinned by the consumer's lockfile; bump
|
|
107
115
|
`@avi2dg/checks` to adopt a new release.
|
|
108
116
|
|
|
109
|
-
##
|
|
117
|
+
## Declare policy in the quality file
|
|
110
118
|
|
|
111
119
|
`quality.json` at the repository root says what the repository has
|
|
112
120
|
opted into. The kit's bins find it at the git root and read it there:
|
|
@@ -135,23 +143,25 @@ opted into. The kit's bins find it at the git root and read it there:
|
|
|
135
143
|
}
|
|
136
144
|
],
|
|
137
145
|
"changeSignal": "advisory",
|
|
138
|
-
"agentRules": { "on": [], "off": [] }
|
|
146
|
+
"agentRules": { "on": [], "off": [] },
|
|
147
|
+
"docs": { "pages": { "reference": ["docs/gates/*.md"], "explanation": ["docs/design.md"] } }
|
|
139
148
|
}
|
|
140
149
|
```
|
|
141
150
|
|
|
142
151
|
| Key | Read by | Holds |
|
|
143
152
|
| --- | --- | --- |
|
|
144
153
|
| `defaultBranch` | `checks-lint`, `checks-ci-wiring` | the branch pull requests merge into, `main` when absent |
|
|
145
|
-
| `gates.ci` | `checks-ci-wiring` | the commands CI runs on every pull request
|
|
154
|
+
| `gates.ci` | `checks-ci-wiring` | the commands CI runs on every pull request. See "Check the CI wiring" |
|
|
146
155
|
| `gates.scheduled` | `checks-ci-wiring` | the commands a schedule runs |
|
|
147
|
-
| `gates.lint` | `checks-lint`, `checks-ci-wiring` | the gates `checks-lint` runs when not all apply
|
|
148
|
-
| `commitIdentity.authors` | `checks-commit-identity` | the identities allowed to author and commit
|
|
149
|
-
| `sources.effect` | `checks-quality` | the paths held to the Effect rules, and the files under them that are not
|
|
150
|
-
| `sources.production` | `checks-size-budget`, `checks-quality` | the source the repository ships
|
|
151
|
-
| `size` | `checks-size-budget` | the line budget, and which production files it holds
|
|
152
|
-
| `features` | `featureRules`, `checks-feature-owners` | each feature's root, entries, exempt importers and proof
|
|
153
|
-
| `changeSignal` | `checks-feature-owners` | `advisory` to list the feature owners a change touches
|
|
156
|
+
| `gates.lint` | `checks-lint`, `checks-ci-wiring` | the gates `checks-lint` runs when not all apply. See "Gate selection" |
|
|
157
|
+
| `commitIdentity.authors` | `checks-commit-identity` | the identities allowed to author and commit. See "Check commit identities" |
|
|
158
|
+
| `sources.effect` | `checks-quality` | the paths held to the Effect rules, and the files under them that are not. See "Enforce the Effect rules" |
|
|
159
|
+
| `sources.production` | `checks-size-budget`, `checks-quality` | the source the repository ships. See "Hold files to a size budget" |
|
|
160
|
+
| `size` | `checks-size-budget` | the line budget, and which production files it holds. See "Hold files to a size budget" |
|
|
161
|
+
| `features` | `featureRules`, `checks-feature-owners` | each feature's root, entries, exempt importers and proof. See "Declare feature owners" |
|
|
162
|
+
| `changeSignal` | `checks-feature-owners` | `advisory` to list the feature owners a change touches. See "Declare feature owners" |
|
|
154
163
|
| `agentRules.on`, `agentRules.off` | agent Rule selection, not the kit | catalogued Rules switched on or off for this repository |
|
|
164
|
+
| `docs.pages` | `checks-docs` | the Diátaxis mode of each page, by glob. See "Hold docs to their templates" |
|
|
155
165
|
|
|
156
166
|
Every key is optional. The bins decode the file with one Effect
|
|
157
167
|
`Schema`, and the package ships `quality.schema.json` emitted from that
|
|
@@ -258,12 +268,12 @@ pin both:
|
|
|
258
268
|
because one that leaves any of the kit's out turns on the category
|
|
259
269
|
rules of the plugins it adds under every declared path.
|
|
260
270
|
|
|
261
|
-
##
|
|
271
|
+
## Run every lint gate
|
|
262
272
|
|
|
263
273
|
`checks-lint` runs each of the kit's lint gates in turn and names every
|
|
264
274
|
one that fails, rather than stopping at the first. A repository whose
|
|
265
275
|
tracked files give a gate nothing to check can leave it out through
|
|
266
|
-
`gates.lint
|
|
276
|
+
`gates.lint`. See "Gate selection" under "Check the CI wiring".
|
|
267
277
|
|
|
268
278
|
| Gate | Reads |
|
|
269
279
|
| --- | --- |
|
|
@@ -273,6 +283,7 @@ tracked files give a gate nothing to check can leave it out through
|
|
|
273
283
|
| `checks-comment-gate` | the range |
|
|
274
284
|
| `checks-suppressions-ratchet` | the range |
|
|
275
285
|
| `checks-ci-wiring` | the working tree |
|
|
286
|
+
| `checks-docs` | the range |
|
|
276
287
|
| `checks-quality` | the working tree |
|
|
277
288
|
| `checks-size-budget` | the range |
|
|
278
289
|
| `checks-feature-owners` | the range |
|
|
@@ -287,7 +298,7 @@ Locally, and on any event other than a pull request, the range ends at
|
|
|
287
298
|
`HEAD` and starts where `HEAD` branched from the origin default branch:
|
|
288
299
|
`origin/HEAD`, or when `origin/HEAD` is not set, as in an
|
|
289
300
|
`actions/checkout` clone, `origin/<defaultBranch>` from the
|
|
290
|
-
repository's `quality.json
|
|
301
|
+
repository's `quality.json`, as "Declare policy in the quality file" says, and `origin/main` when
|
|
291
302
|
that is not declared. In a GitHub Actions pull request, where
|
|
292
303
|
`GITHUB_EVENT_NAME` is `pull_request`, it ends
|
|
293
304
|
at the event's head sha and starts where that branched from
|
|
@@ -333,8 +344,8 @@ checks-lint: 3 of 9 gate(s) failed: checks-commit-identity, checks-comment-gate,
|
|
|
333
344
|
|
|
334
345
|
It exits 1 when any gate found a violation, and 2 when the range or the
|
|
335
346
|
selection does not resolve, or no failing gate could decide. ci-wiring
|
|
336
|
-
always runs, so a repository on `checks-lint` declares `gates.ci
|
|
337
|
-
|
|
347
|
+
always runs, so a repository on `checks-lint` declares `gates.ci`,
|
|
348
|
+
as "Check the CI wiring" says, and it holds the test layout unless its selection
|
|
338
349
|
leaves out `checks-test-layout`.
|
|
339
350
|
|
|
340
351
|
CI runs it through `lint`. The checkout fetches the whole history, which
|
|
@@ -355,7 +366,7 @@ jobs:
|
|
|
355
366
|
- run: bun run lint
|
|
356
367
|
```
|
|
357
368
|
|
|
358
|
-
## Effect rules
|
|
369
|
+
## Enforce the Effect rules
|
|
359
370
|
|
|
360
371
|
The base config loads the `effect-channel` plugin and turns on
|
|
361
372
|
`effect-channel/no-error-channel-escape`, which refuses `Effect.ignore`,
|
|
@@ -376,7 +387,7 @@ through `Effect.fail`, a throwing call wrapped in `Effect.try` or
|
|
|
376
387
|
|
|
377
388
|
A repository turns them on for the paths it writes in Effect by
|
|
378
389
|
declaring those paths in `quality.json` and extending the generated
|
|
379
|
-
fragments
|
|
390
|
+
fragments. See "Generated fragments" under "Declare policy in the quality file":
|
|
380
391
|
|
|
381
392
|
```json
|
|
382
393
|
"sources": {
|
|
@@ -412,7 +423,7 @@ the tsconfig fragment's override, whose severities are
|
|
|
412
423
|
effect-tsgo keeps the severities `tsconfig.effect.json` sets when a later
|
|
413
424
|
config in `extends` restates the plugin with only its overrides.
|
|
414
425
|
|
|
415
|
-
##
|
|
426
|
+
## Lay out the tests
|
|
416
427
|
|
|
417
428
|
`checks-test-layout` fails unless the repo holds this shape, and names the
|
|
418
429
|
file and the path to move it to when it does not:
|
|
@@ -435,7 +446,7 @@ file and the path to move it to when it does not:
|
|
|
435
446
|
with swc and reads import specifiers and identifier use, so a test that
|
|
436
447
|
only carries `"node:child_process"` as a string is not a violation.
|
|
437
448
|
- `scripts.test` is exactly `checks-test`, which runs `bun test --randomize`
|
|
438
|
-
|
|
449
|
+
as "Run the suite" says, and `scripts.lint` runs this check, itself or
|
|
439
450
|
through `checks-lint` called by its bare bin name.
|
|
440
451
|
- `bunfig.toml` carries every `[test]` key of the shipped preset with the
|
|
441
452
|
same value, and `[test].pathIgnorePatterns` is always
|
|
@@ -455,7 +466,7 @@ still run on demand:
|
|
|
455
466
|
bun test --path-ignore-patterns='' tests/quarantine
|
|
456
467
|
```
|
|
457
468
|
|
|
458
|
-
##
|
|
469
|
+
## Run the suite
|
|
459
470
|
|
|
460
471
|
`checks-test` runs the whole suite with `bun test --randomize`, passes
|
|
461
472
|
bun's output through, and then reads bun's JUnit report of the same run.
|
|
@@ -498,10 +509,10 @@ passed without writing its report. It takes no arguments: a `-t`
|
|
|
498
509
|
filter reports every test it leaves out as skipped and a path filter
|
|
499
510
|
drops files a declaration names, so a narrowed run is plain
|
|
500
511
|
`bun test --randomize` with the arguments. Files under
|
|
501
|
-
`tests/quarantine/` are never run and so never reported
|
|
512
|
+
`tests/quarantine/` are never run and so never reported. See "Test
|
|
502
513
|
layout".
|
|
503
514
|
|
|
504
|
-
##
|
|
515
|
+
## Find flaky tests
|
|
505
516
|
|
|
506
517
|
A green run proves nothing failed in that run, not that no test is
|
|
507
518
|
flaky. `checks-flake` runs the whole suite several times, each with its
|
|
@@ -556,9 +567,9 @@ jobs:
|
|
|
556
567
|
```
|
|
557
568
|
|
|
558
569
|
and declares the step in `gates.scheduled`, so `checks-ci-wiring`
|
|
559
|
-
fails once the schedule stops running it
|
|
570
|
+
fails once the schedule stops running it. See "Check the CI wiring".
|
|
560
571
|
|
|
561
|
-
##
|
|
572
|
+
## Enforce dependency rules
|
|
562
573
|
|
|
563
574
|
`.dependency-cruiser.cjs` extends the shared base, which carries
|
|
564
575
|
`no-circular`, `no-orphans`, `not-to-dev-dep` (shipped source importing
|
|
@@ -587,7 +598,7 @@ by field, which is how an entry point stops being an orphan:
|
|
|
587
598
|
redeclare `no-orphans` with your entry added to its `pathNot`.
|
|
588
599
|
This repo's own `.dependency-cruiser.cjs` does that for the plugin
|
|
589
600
|
entry. A repository that declares feature owners spreads the rules
|
|
590
|
-
`quality.json` compiles to into the same `forbidden
|
|
601
|
+
`quality.json` compiles to into the same `forbidden`. See "Feature
|
|
591
602
|
owners".
|
|
592
603
|
|
|
593
604
|
`package.json` gains the script:
|
|
@@ -608,7 +619,7 @@ jobs:
|
|
|
608
619
|
- run: bun run lint:deps
|
|
609
620
|
```
|
|
610
621
|
|
|
611
|
-
##
|
|
622
|
+
## Lint commit messages
|
|
612
623
|
|
|
613
624
|
Commits follow `@commitlint/config-conventional` plus the house
|
|
614
625
|
prefixes listed in `commitlint.config.js`, shared from
|
|
@@ -647,7 +658,7 @@ It never sees a commit's author or committer fields, nor the
|
|
|
647
658
|
squashes, so it cannot enforce who a commit belongs to. The
|
|
648
659
|
commit-identity check below is the enforcement.
|
|
649
660
|
|
|
650
|
-
##
|
|
661
|
+
## Check commit identities
|
|
651
662
|
|
|
652
663
|
`scripts/commit-identity.ts` walks every commit in a range and fails when
|
|
653
664
|
one carries an identity other than the repository owner's:
|
|
@@ -673,9 +684,10 @@ owners restates it in `quality.json`:
|
|
|
673
684
|
}
|
|
674
685
|
```
|
|
675
686
|
|
|
676
|
-
`checks-lint` runs it over each pull request's range
|
|
687
|
+
`checks-lint` runs it over each pull request's range.
|
|
688
|
+
See "Run every lint gate".
|
|
677
689
|
|
|
678
|
-
##
|
|
690
|
+
## Refuse banned comments
|
|
679
691
|
|
|
680
692
|
`scripts/comment-gate.ts` runs the comment check over a diff and fails
|
|
681
693
|
when an added line carries a banned comment:
|
|
@@ -704,9 +716,10 @@ and import it as `@avi2dg/checks/scripts/comment-matchers.ts`. The repo's
|
|
|
704
716
|
cruise fails when it gains an import. `scripts/comments.ts` wraps the
|
|
705
717
|
same matchers in Effect for the gate and the backtest.
|
|
706
718
|
|
|
707
|
-
`checks-lint` runs it over each pull request's range
|
|
719
|
+
`checks-lint` runs it over each pull request's range.
|
|
720
|
+
See "Run every lint gate".
|
|
708
721
|
|
|
709
|
-
##
|
|
722
|
+
## Ratchet the suppressions
|
|
710
723
|
|
|
711
724
|
`scripts/suppressions-ratchet.ts` holds oxlint's bulk-suppression
|
|
712
725
|
baseline, `oxlint-suppressions.json`, to counts that only fall. oxlint
|
|
@@ -737,9 +750,10 @@ suppressions-ratchet: 2 count(s) in oxlint-suppressions.json rose or appeared; f
|
|
|
737
750
|
src/dispatch.ts typescript/no-non-null-assertion rose from 12 to 13
|
|
738
751
|
```
|
|
739
752
|
|
|
740
|
-
`checks-lint` runs it over each pull request's range
|
|
753
|
+
`checks-lint` runs it over each pull request's range.
|
|
754
|
+
See "Run every lint gate".
|
|
741
755
|
|
|
742
|
-
##
|
|
756
|
+
## Hold files to a size budget
|
|
743
757
|
|
|
744
758
|
`checks-size-budget` holds production files to the line budget
|
|
745
759
|
`quality.json` declares, and lists every other file over it without
|
|
@@ -790,9 +804,10 @@ glob that matches no file. Moving `applies` from `changed` to `all`
|
|
|
790
804
|
tightens the budget to every production file, once the advisory list
|
|
791
805
|
names none.
|
|
792
806
|
|
|
793
|
-
`checks-lint` runs it over each pull request's range
|
|
807
|
+
`checks-lint` runs it over each pull request's range.
|
|
808
|
+
See "Run every lint gate".
|
|
794
809
|
|
|
795
|
-
##
|
|
810
|
+
## Declare feature owners
|
|
796
811
|
|
|
797
812
|
A repository opts a feature in by declaring, in `quality.json`, the
|
|
798
813
|
directory it owns, the files code outside it imports it through, the
|
|
@@ -891,9 +906,78 @@ repository that declares no feature passes, and `quality.json` refuses
|
|
|
891
906
|
a `changeSignal` without features, which would map a change to no
|
|
892
907
|
owner.
|
|
893
908
|
|
|
894
|
-
`checks-lint` runs it over each pull request's range
|
|
909
|
+
`checks-lint` runs it over each pull request's range.
|
|
910
|
+
See "Run every lint gate".
|
|
911
|
+
|
|
912
|
+
## Hold docs to their templates
|
|
913
|
+
|
|
914
|
+
`checks-docs` holds each doc file a change touches to the template for its kind, and lists every other doc file that does not conform yet without failing.
|
|
915
|
+
The package ships one template per kind under `templates/`, and a repository starts a new doc file by copying one:
|
|
916
|
+
|
|
917
|
+
```sh
|
|
918
|
+
cp node_modules/@avi2dg/checks/templates/how-to.md docs/add-a-supplier.md
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
| File | Kind | Template |
|
|
922
|
+
| --- | --- | --- |
|
|
923
|
+
| `README.md` | readme | `templates/readme.md` |
|
|
924
|
+
| `CHANGELOG.md` | changelog | `templates/changelog.md` |
|
|
925
|
+
| `AGENTS.md` | agents | `templates/agents.md` |
|
|
926
|
+
| `CLAUDE.md` | claude | `templates/claude.md` |
|
|
927
|
+
| each file in `docs/adr/` but its generated index, `README.md` | adr | `templates/adr.md` |
|
|
928
|
+
| a page `docs.pages` declares | tutorial, how-to, reference or explanation | `templates/<mode>.md` |
|
|
929
|
+
|
|
930
|
+
The first four are the files at the repository root.
|
|
931
|
+
No other Markdown file is judged, save a page under `docs/`, which needs a mode.
|
|
932
|
+
Which Diátaxis mode a page is written in is a judgment, so `quality.json` declares it:
|
|
933
|
+
|
|
934
|
+
```json
|
|
935
|
+
"docs": {
|
|
936
|
+
"pages": {
|
|
937
|
+
"reference": ["docs/gates/*.md"],
|
|
938
|
+
"explanation": ["docs/design.md"]
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
A template decides a file's structure, and the template file itself is the reference for each kind:
|
|
944
|
+
|
|
945
|
+
- A file opens with one `# ` title on its first line and has text before its first section.
|
|
946
|
+
It skips no heading level, and no heading is Overview, Introduction or How it works.
|
|
947
|
+
- Its sections are the template's headings in the template's order.
|
|
948
|
+
A heading in angle brackets is one the writer names.
|
|
949
|
+
One marked verb first is left to review, since no program tells a verb from a noun there.
|
|
950
|
+
A heading the template does not have, in that place, is refused.
|
|
951
|
+
- A record in `docs/adr/` is named for its four-digit number, and its title opens with the same number.
|
|
952
|
+
A `Date: YYYY-MM-DD` line follows the title, the first word under Status is Proposed, Accepted, Rejected, Deprecated, Superseded or Retired, and no other record holds its number.
|
|
953
|
+
- A changelog lists its releases newest first, each opening with a `Released YYYY-MM-DD.` line.
|
|
954
|
+
- A how-to or tutorial page numbers its steps.
|
|
955
|
+
- `CLAUDE.md` is its template word for word.
|
|
956
|
+
|
|
957
|
+
```sh
|
|
958
|
+
checks-docs <base-ref> <head-ref>
|
|
959
|
+
checks-docs <ref>
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
It reads each file from the head commit.
|
|
963
|
+
With two arguments the range starts where the head branched from the base, at their merge-base.
|
|
964
|
+
With one it is that commit against its parent, or against the empty tree for a repository's first commit.
|
|
965
|
+
A file the range adds, changes or renames is held to its template, and a file it deletes is not.
|
|
895
966
|
|
|
896
|
-
|
|
967
|
+
```
|
|
968
|
+
docs: 2 violation(s) in the doc files the range touches:
|
|
969
|
+
README.md:1: lacks `## Where things are`
|
|
970
|
+
docs/parts.md: is a page under docs/ with no mode; declare it under docs.pages in quality.json as tutorial, how-to, reference, explanation
|
|
971
|
+
docs: advisory, 1 doc file(s) the range leaves alone do not hold to their templates yet:
|
|
972
|
+
docs/adr/0001-quality-gates.md: 5 violation(s)
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
It exits 1 on a violation in a file the range touches, and 2 when `quality.json` does not decode or a ref does not resolve.
|
|
976
|
+
|
|
977
|
+
`checks-lint` runs it over each pull request's range.
|
|
978
|
+
See "Run every lint gate".
|
|
979
|
+
|
|
980
|
+
## Check the CI wiring
|
|
897
981
|
|
|
898
982
|
`checks-ci-wiring` fails when a command the repository's CI must run no
|
|
899
983
|
longer runs on pull requests to the default branch. No local check sees
|
|
@@ -1010,13 +1094,14 @@ missing `bun test` script and `bunfig.toml`. It declares the gates
|
|
|
1010
1094
|
"checks-comment-gate",
|
|
1011
1095
|
"checks-suppressions-ratchet",
|
|
1012
1096
|
"checks-ci-wiring",
|
|
1097
|
+
"checks-docs",
|
|
1013
1098
|
"checks-quality"
|
|
1014
1099
|
]
|
|
1015
1100
|
}
|
|
1016
1101
|
```
|
|
1017
1102
|
|
|
1018
|
-
`checks-lint` runs exactly those, in the "
|
|
1019
|
-
order, and all
|
|
1103
|
+
`checks-lint` runs exactly those, in the "Run every lint gate" table's
|
|
1104
|
+
order, and all ten when `gates.lint` is absent. A selection in
|
|
1020
1105
|
`quality.json` always keeps `checks-quality`, since the file it sits in
|
|
1021
1106
|
is what makes that gate apply. A step running
|
|
1022
1107
|
`checks-lint` then counts only for a declared gate that `gates.lint`
|
|
@@ -1032,6 +1117,7 @@ A selection may leave out only a gate that does not apply:
|
|
|
1032
1117
|
| `checks-comment-gate` | always |
|
|
1033
1118
|
| `checks-suppressions-ratchet` | always |
|
|
1034
1119
|
| `checks-ci-wiring` | always |
|
|
1120
|
+
| `checks-docs` | always |
|
|
1035
1121
|
| `checks-quality` | tracks a `quality.json` |
|
|
1036
1122
|
| `checks-size-budget` | tracks a `.ts` or `.tsx` file |
|
|
1037
1123
|
| `checks-feature-owners` | tracks a `.ts` or `.tsx` file |
|
|
@@ -1067,7 +1153,7 @@ does not evaluate:
|
|
|
1067
1153
|
command itself does, the shell's options, and a step or job that
|
|
1068
1154
|
fails or times out before the gate step.
|
|
1069
1155
|
|
|
1070
|
-
## Backtest
|
|
1156
|
+
## Backtest the comment check
|
|
1071
1157
|
|
|
1072
1158
|
`scripts/backtest.ts` reports what the comment check would have refused
|
|
1073
1159
|
at each recent commit, so a repository can measure its own history:
|
|
@@ -1083,7 +1169,7 @@ comment text as a share of added lines.
|
|
|
1083
1169
|
`generated/`, `vendor/`, `repos/`, `node_modules/` and `dist/` are out
|
|
1084
1170
|
of reach, so the figures are authored code.
|
|
1085
1171
|
|
|
1086
|
-
##
|
|
1172
|
+
## Compare mutation scores
|
|
1087
1173
|
|
|
1088
1174
|
`checks-mutation-compare` gates a pull request on no-regression rather
|
|
1089
1175
|
than an absolute threshold: the head mutation score may not fall below
|
|
@@ -1129,117 +1215,6 @@ jobs:
|
|
|
1129
1215
|
The shared Stryker preset's `json` reporter writes
|
|
1130
1216
|
`reports/mutation/mutation.json` in each worktree.
|
|
1131
1217
|
|
|
1132
|
-
## Why it is shaped this way
|
|
1133
|
-
|
|
1134
|
-
- Every config in an oxlint `extends` chain brings its own `plugins`,
|
|
1135
|
-
and one that sets none brings oxlint's default plugins, whose
|
|
1136
|
-
category rules the base's `categories` then turn on across the tree.
|
|
1137
|
-
`rules`, `categories` and `jsPlugins` inherit as expected. That is why
|
|
1138
|
-
the consumer snippet restates `plugins` and nothing else, and why the
|
|
1139
|
-
generated fragment always sets them.
|
|
1140
|
-
- `node_modules/` is excluded through the consumer's `.gitignore`, not
|
|
1141
|
-
`ignorePatterns`: oxlint still walks the installed package when only
|
|
1142
|
-
`ignorePatterns` names it.
|
|
1143
|
-
- `files` in package.json is the published surface: `tests/`, `AGENTS.md`
|
|
1144
|
-
and the `.ts` plugin source never reach an install. npm adds
|
|
1145
|
-
`package.json`, `README` and `LICENSE` to the tarball whatever `files`
|
|
1146
|
-
says. `bun pm pack` builds the same tarball the registry serves, which
|
|
1147
|
-
is what the packed-tarball consumer e2e test installs.
|
|
1148
|
-
- The plugin ships compiled as `dist/index.js`, built with
|
|
1149
|
-
`bun build effect-channel/index.ts --outdir dist --target node --format esm`.
|
|
1150
|
-
Node refuses to type-strip a `.ts` plugin under `node_modules`, so the
|
|
1151
|
-
`.ts` source would fail to load from an installed package.
|
|
1152
|
-
- `featureRules` ships compiled as `dist/feature-rules.js` for the same
|
|
1153
|
-
reason, with `effect` left out of the bundle so it resolves the
|
|
1154
|
-
consumer's own copy. dependency-cruiser uses a config's export as it
|
|
1155
|
-
is and never awaits it, so the declaration decodes synchronously, and
|
|
1156
|
-
`quality.json` exempts that one file from the Effect rules.
|
|
1157
|
-
- `checks-size-budget` writes the head commit's files to a temporary
|
|
1158
|
-
directory and runs oxlint there, with a configuration that sets no
|
|
1159
|
-
plugin and turns every category off, so the consumer's own
|
|
1160
|
-
`.oxlintrc.json`, its ignore files and its other rules never reach the
|
|
1161
|
-
count.
|
|
1162
|
-
- `dist/` is committed. No `prepack` or `prepublishOnly` builds it, so a
|
|
1163
|
-
publish ships whatever bundle the publishing worktree holds. Rebuild it
|
|
1164
|
-
after pulling with `bun run build`; CI fails when the committed bundle
|
|
1165
|
-
is stale. `bun run build` also emits `quality.schema.json`, which is
|
|
1166
|
-
committed the same way, and a test fails when it differs from what
|
|
1167
|
-
the schema emits.
|
|
1168
|
-
- `quality.json` is JSON, not TOML or a TypeScript module: a bun bin, a
|
|
1169
|
-
hook running without `node_modules`, a `.cjs` or `.mjs` config and
|
|
1170
|
-
`jq` all parse it with nothing installed, and nobody runs a
|
|
1171
|
-
repository's own code to learn its policy. It holds declarations
|
|
1172
|
-
only. The kit's bins read it directly; oxlint and tsc read nothing but
|
|
1173
|
-
their own JSON, so they extend generated fragments, which
|
|
1174
|
-
`checks-quality --check` holds to the declarations.
|
|
1175
|
-
- `quality.json` refuses a key its schema does not name, so a kit that
|
|
1176
|
-
cannot enforce a newer key refuses it rather than let the repository
|
|
1177
|
-
believe it enforced.
|
|
1178
|
-
- The base parses with swc because typescript 7 (tsgo) has no compiler
|
|
1179
|
-
API for dependency-cruiser to use. Without `@swc/core` installed the
|
|
1180
|
-
cruise silently skips every `.ts` file, so this repo's test asserts its
|
|
1181
|
-
own TypeScript is cruised.
|
|
1182
|
-
- `bunfig.toml` has no `extends` and no include: bun ignores an unknown
|
|
1183
|
-
top-level key in silence, so a preset cannot be inherited and the
|
|
1184
|
-
consumer's copy is compared key by key against the installed one
|
|
1185
|
-
instead. `[test] pathIgnorePatterns` is a real bunfig key, and an empty
|
|
1186
|
-
`--path-ignore-patterns` flag overrides the file's own list.
|
|
1187
|
-
- The Stryker preset is a JavaScript module, not JSON: Stryker 10 does
|
|
1188
|
-
not resolve `extends` in a JSON config, but a `.mjs` config that
|
|
1189
|
-
spreads an imported object consumes it. Keys the consumer sets after
|
|
1190
|
-
the spread win.
|
|
1191
|
-
- The `.ts` bins are written in Effect, so `effect` is a peer dependency
|
|
1192
|
-
and `@effect/platform-bun`, which only the bins use, is a dependency.
|
|
1193
|
-
`@effect/platform-node-shared` is a direct dependency at the same exact
|
|
1194
|
-
version only to pin it: `@effect/platform-bun` asks for it with a `^`
|
|
1195
|
-
range, and a newer rc peers on a newer `effect` than consumers install,
|
|
1196
|
-
so all three move together.
|
|
1197
|
-
- Each runnable script ships a `checks-` bin entry, so consumer
|
|
1198
|
-
`package.json` scripts call the short name, which the package manager
|
|
1199
|
-
puts on `PATH` only there; a shell runs it through `bun run`, which
|
|
1200
|
-
never falls back to the registry the way `bunx` does. The `.ts` checks
|
|
1201
|
-
keep a `bun` shebang, which needs no build step and no `dist/`
|
|
1202
|
-
entry, unlike the oxlint plugin that node loads.
|
|
1203
|
-
- `checks-lint` runs each gate as its own bin in a child process rather
|
|
1204
|
-
than importing it, so a gate behaves the same called alone or through
|
|
1205
|
-
the entry point, and `lint-coverage.sh` stays a shell script. The
|
|
1206
|
-
gates run one at a time with their output passed straight through, so
|
|
1207
|
-
each report reads whole and in the table's order.
|
|
1208
|
-
- A gate selection is checked against the repository's contents rather
|
|
1209
|
-
than trusted, so it cannot skip a gate that applies. ci-wiring does
|
|
1210
|
-
that check, which is why a selection without it, or without another
|
|
1211
|
-
gate that applies everywhere, is refused as `checks-lint` reads it:
|
|
1212
|
-
nothing would check the selection otherwise.
|
|
1213
|
-
- `checks-test` runs bun itself rather than reading a report some other
|
|
1214
|
-
run left: a skip taken only on CI is visible only in CI's own run, and
|
|
1215
|
-
an earlier run's report may be stale or narrowed. It reads the JUnit
|
|
1216
|
-
report bun writes to a temporary directory, since bun has no other
|
|
1217
|
-
per-test output meant for a program.
|
|
1218
|
-
- `checks-ci-wiring` runs inside `lint`, not in a workflow of its own:
|
|
1219
|
-
deleting the step that runs a check is the violation it catches, so the
|
|
1220
|
-
local `lint` is where it has to fail.
|
|
1221
|
-
- Workflows are parsed with `Bun.YAML`, which the `bun` shebang already
|
|
1222
|
-
provides, so the check adds no dependency. It reads `on` as a string
|
|
1223
|
-
key, not as the YAML 1.1 boolean.
|
|
1224
|
-
- `bun` counts as a built-in module. Nothing installed resolves it except
|
|
1225
|
-
`@types/bun`, which would otherwise make every runtime `bun` import look
|
|
1226
|
-
like a dev-only dependency.
|
|
1227
|
-
- The pull request merge commit GitHub builds is authored by `GitHub
|
|
1228
|
-
<noreply@github.com>`, which commit-identity refuses as an author.
|
|
1229
|
-
`checks-lint` ends a pull request's range at the event's head sha, so
|
|
1230
|
-
the merge commit is never in it. A `lint` that calls
|
|
1231
|
-
`checks-commit-identity HEAD` itself checks out
|
|
1232
|
-
`github.event.pull_request.head.sha` instead of the default merge ref.
|
|
1233
|
-
- `no-deep-imports` judges the import specifier, never the resolved file.
|
|
1234
|
-
The base honours `exports` maps, so a subpath the map publishes resolves
|
|
1235
|
-
and passes, one it omits fails to resolve and is reported, and a package
|
|
1236
|
-
without an `exports` map publishes every file. A bare import always
|
|
1237
|
-
passes whatever file its entry lives in. Setting your own
|
|
1238
|
-
`options.enhancedResolveOptions` replaces the base's, so restate
|
|
1239
|
-
`exportsFields` and `conditionNames` if you do.
|
|
1240
|
-
- dependency-cruiser `extends` merges same-name `forbidden` rules with the
|
|
1241
|
-
child's fields winning. That is the entry-point and layer recipe above.
|
|
1242
|
-
|
|
1243
1218
|
## Develop
|
|
1244
1219
|
|
|
1245
1220
|
```sh
|
|
@@ -1273,3 +1248,20 @@ every later tag publishes through the workflow.
|
|
|
1273
1248
|
|
|
1274
1249
|
`publishConfig.access` in package.json is what makes the scoped package
|
|
1275
1250
|
public.
|
|
1251
|
+
|
|
1252
|
+
## Where things are
|
|
1253
|
+
|
|
1254
|
+
| Path | What it holds |
|
|
1255
|
+
| --- | --- |
|
|
1256
|
+
| `scripts/` | every bin, and the modules they share |
|
|
1257
|
+
| `effect-channel/` | the Effect error-channel oxlint plugin |
|
|
1258
|
+
| `dist/` | the committed bundles of the plugin and of `featureRules` |
|
|
1259
|
+
| `presets/` | the Effect presets `checks-quality` builds its fragments from |
|
|
1260
|
+
| `templates/` | one template per kind of doc file, which `bun run build` renders |
|
|
1261
|
+
| `tests/` | the suite, with the tests that spawn a process under `tests/e2e/` |
|
|
1262
|
+
| `docs/` | pages for whoever develops the kit |
|
|
1263
|
+
| the root configs | `oxlintrc.json`, `tsconfig.effect.json`, `bunfig.toml`, `commitlint.config.js`, `dependency-cruiser.config.js`, `stryker.preset.js` and `quality.schema.json`, which a consuming repository extends or copies |
|
|
1264
|
+
|
|
1265
|
+
## Related topics
|
|
1266
|
+
|
|
1267
|
+
- [Why it is shaped this way](docs/design.md)
|
package/dist/feature-rules.js
CHANGED
|
@@ -17,6 +17,7 @@ var KIT_GATES = [
|
|
|
17
17
|
{ bin: "checks-comment-gate", script: "comment-gate.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
18
18
|
{ bin: "checks-suppressions-ratchet", script: "suppressions-ratchet.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
19
19
|
{ bin: "checks-ci-wiring", script: "ci-wiring.ts", reads: "tree", appliesTo: EVERY_REPOSITORY },
|
|
20
|
+
{ bin: "checks-docs", script: "docs.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
20
21
|
{ bin: "checks-quality", script: "quality.ts", reads: "tree", args: ["--check"], appliesTo: QUALITY_DECLARATION },
|
|
21
22
|
{ bin: "checks-size-budget", script: "size-budget.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
22
23
|
{ bin: "checks-feature-owners", script: "feature-owners.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE }
|
|
@@ -123,6 +124,17 @@ var AgentRules = Schema2.Struct({
|
|
|
123
124
|
const both = on.filter((rule) => off.includes(rule));
|
|
124
125
|
return both.length === 0 || `switches ${both.join(", ")} both on and off`;
|
|
125
126
|
}));
|
|
127
|
+
var pagesIn = (mode) => Schema2.optionalKey(Schema2.Array(PathGlob).annotate({ description: `The pages written as ${mode}` }));
|
|
128
|
+
var Docs = Schema2.Struct({
|
|
129
|
+
pages: Schema2.optionalKey(Schema2.Struct({
|
|
130
|
+
tutorial: pagesIn("a tutorial, which teaches by building one thing"),
|
|
131
|
+
"how-to": pagesIn("a how-to, which walks one task"),
|
|
132
|
+
reference: pagesIn("reference, which describes a thing to be looked up"),
|
|
133
|
+
explanation: pagesIn("an explanation, which says why")
|
|
134
|
+
}).annotate({
|
|
135
|
+
description: "The Diátaxis mode of each page, whose template checks-docs holds the page to; a page under docs/ needs one"
|
|
136
|
+
}))
|
|
137
|
+
});
|
|
126
138
|
var Quality = Schema2.Struct({
|
|
127
139
|
$schema: Schema2.optionalKey(Schema2.String),
|
|
128
140
|
defaultBranch: Schema2.optionalKey(Schema2.NonEmptyString.annotate({ description: "The branch pull requests merge into; main when absent" })),
|
|
@@ -136,7 +148,8 @@ var Quality = Schema2.Struct({
|
|
|
136
148
|
changeSignal: Schema2.optionalKey(Schema2.Literal("advisory").annotate({
|
|
137
149
|
description: "Report which feature owners a change touches, without failing on it"
|
|
138
150
|
})),
|
|
139
|
-
agentRules: Schema2.optionalKey(AgentRules)
|
|
151
|
+
agentRules: Schema2.optionalKey(AgentRules),
|
|
152
|
+
docs: Schema2.optionalKey(Docs.annotate({ description: "What checks-docs reads to map a doc file to its template" }))
|
|
140
153
|
}).annotate({
|
|
141
154
|
title: QUALITY_FILE,
|
|
142
155
|
description: "What a repository has opted into from @avi2dg/checks, read by its bins and agent Rule selection"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avi2dg/checks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Deterministic checks shared across the captain's TypeScript repos",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"scripts/quality.ts",
|
|
37
37
|
"scripts/size-budget.ts",
|
|
38
38
|
"scripts/feature-owners.ts",
|
|
39
|
+
"scripts/docs.ts",
|
|
40
|
+
"scripts/doc-outline.ts",
|
|
41
|
+
"scripts/doc-rules.ts",
|
|
42
|
+
"scripts/doc-templates.ts",
|
|
43
|
+
"templates/",
|
|
39
44
|
"presets/effect.oxlint.json",
|
|
40
45
|
"presets/effect.language-service.json",
|
|
41
46
|
"quality.schema.json",
|
|
@@ -66,6 +71,16 @@
|
|
|
66
71
|
"./scripts/quality.ts": "./scripts/quality.ts",
|
|
67
72
|
"./scripts/size-budget.ts": "./scripts/size-budget.ts",
|
|
68
73
|
"./scripts/feature-owners.ts": "./scripts/feature-owners.ts",
|
|
74
|
+
"./scripts/docs.ts": "./scripts/docs.ts",
|
|
75
|
+
"./templates/readme.md": "./templates/readme.md",
|
|
76
|
+
"./templates/changelog.md": "./templates/changelog.md",
|
|
77
|
+
"./templates/adr.md": "./templates/adr.md",
|
|
78
|
+
"./templates/agents.md": "./templates/agents.md",
|
|
79
|
+
"./templates/claude.md": "./templates/claude.md",
|
|
80
|
+
"./templates/tutorial.md": "./templates/tutorial.md",
|
|
81
|
+
"./templates/how-to.md": "./templates/how-to.md",
|
|
82
|
+
"./templates/reference.md": "./templates/reference.md",
|
|
83
|
+
"./templates/explanation.md": "./templates/explanation.md",
|
|
69
84
|
"./presets/effect.oxlint.json": "./presets/effect.oxlint.json",
|
|
70
85
|
"./presets/effect.language-service.json": "./presets/effect.language-service.json",
|
|
71
86
|
"./quality.schema.json": "./quality.schema.json",
|
|
@@ -89,10 +104,11 @@
|
|
|
89
104
|
"checks-backtest": "scripts/backtest.ts",
|
|
90
105
|
"checks-quality": "scripts/quality.ts",
|
|
91
106
|
"checks-size-budget": "scripts/size-budget.ts",
|
|
92
|
-
"checks-feature-owners": "scripts/feature-owners.ts"
|
|
107
|
+
"checks-feature-owners": "scripts/feature-owners.ts",
|
|
108
|
+
"checks-docs": "scripts/docs.ts"
|
|
93
109
|
},
|
|
94
110
|
"scripts": {
|
|
95
|
-
"build": "bun build effect-channel/index.ts --outdir dist --target node --format esm && bun build scripts/feature-rules.ts --outdir dist --target node --format esm --packages external && bun scripts/quality-schema.ts",
|
|
111
|
+
"build": "bun build effect-channel/index.ts --outdir dist --target node --format esm && bun build scripts/feature-rules.ts --outdir dist --target node --format esm --packages external && bun scripts/quality-schema.ts && bun scripts/doc-templates-write.ts",
|
|
96
112
|
"lint": "oxlint --type-aware && bun scripts/lint.ts && depcruise --config .dependency-cruiser.cjs .",
|
|
97
113
|
"typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
|
|
98
114
|
"test": "bun scripts/test.ts"
|