@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 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, and the Effect error-channel plugin compiled to
17
- JavaScript.
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
- ## Consume it
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; see "Quality file"
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; see "Test entry point" below.
86
+ repository has not declared. See "Run the suite" below.
79
87
 
80
- `checks-lint` runs every kit gate a lint needs; see "Lint entry point"
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; see "Commit identity" below.
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
- ## Quality file
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; see "CI wiring" |
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; see "Gate selection" |
148
- | `commitIdentity.authors` | `checks-commit-identity` | the identities allowed to author and commit; see "Commit identity" |
149
- | `sources.effect` | `checks-quality` | the paths held to the Effect rules, and the files under them that are not; see "Effect rules" |
150
- | `sources.production` | `checks-size-budget`, `checks-quality` | the source the repository ships; see "Size budget" |
151
- | `size` | `checks-size-budget` | the line budget, and which production files it holds; see "Size budget" |
152
- | `features` | `featureRules`, `checks-feature-owners` | each feature's root, entries, exempt importers and proof; see "Feature owners" |
153
- | `changeSignal` | `checks-feature-owners` | `advisory` to list the feature owners a change touches; see "Feature owners" |
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
- ## Lint entry point
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`; see "Gate selection" under "CI wiring".
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` (see "Quality file"), and `origin/main` when
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
- (see "CI wiring"), and it holds the test layout unless its selection
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; see "Generated fragments" under "Quality file":
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
- ## Test layout
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
- (see "Test entry point"), and `scripts.lint` runs this check, itself or
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
- ## Test entry point
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; see "Test
512
+ `tests/quarantine/` are never run and so never reported. See "Test
502
513
  layout".
503
514
 
504
- ## Flake run
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; see "CI wiring".
570
+ fails once the schedule stops running it. See "Check the CI wiring".
560
571
 
561
- ## Dependency rules
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`; see "Feature
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
- ## Commit lint
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
- ## Commit identity
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; see "Lint entry point".
687
+ `checks-lint` runs it over each pull request's range.
688
+ See "Run every lint gate".
677
689
 
678
- ## Comment gate
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; see "Lint entry point".
719
+ `checks-lint` runs it over each pull request's range.
720
+ See "Run every lint gate".
708
721
 
709
- ## Suppressions ratchet
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; see "Lint entry point".
753
+ `checks-lint` runs it over each pull request's range.
754
+ See "Run every lint gate".
741
755
 
742
- ## Size budget
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; see "Lint entry point".
807
+ `checks-lint` runs it over each pull request's range.
808
+ See "Run every lint gate".
794
809
 
795
- ## Feature owners
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; see "Lint entry point".
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
- ## CI wiring
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 "Lint entry point" table's
1019
- order, and all nine when `gates.lint` is absent. A selection in
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
- ## Mutation compare
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)
@@ -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.12.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"