@avi2dg/checks 0.51.0 → 0.52.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 (43) hide show
  1. package/.bun-version +1 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +24 -249
  4. package/commitlint.config.ts +0 -1
  5. package/dist/presets/knip.d.ts +1 -1
  6. package/dist/presets/knip.js +1 -1
  7. package/dist/templates/how-to.md +1 -1
  8. package/dist/templates/readme.md +1 -1
  9. package/dist/templates/tutorial.md +1 -1
  10. package/docs/bins.md +67 -0
  11. package/docs/gates/checks-advisories.md +2 -2
  12. package/docs/gates/checks-browser.md +5 -1
  13. package/docs/gates/checks-ci-wiring.md +18 -9
  14. package/docs/gates/checks-commit-identity.md +3 -3
  15. package/docs/gates/checks-copy.md +4 -0
  16. package/docs/gates/checks-docs.md +24 -3
  17. package/docs/gates/checks-exports.md +1 -1
  18. package/docs/gates/checks-frontend-syntax.md +5 -1
  19. package/docs/gates/checks-lint.md +2 -2
  20. package/docs/gates/checks-mutation-baseline.md +9 -5
  21. package/docs/gates/checks-mutation-compare.md +3 -3
  22. package/docs/gates/checks-mutation.md +9 -5
  23. package/docs/gates/checks-night-run.md +1 -1
  24. package/docs/gates/checks-secrets.md +2 -2
  25. package/docs/gates/checks-unused.md +1 -1
  26. package/docs/how-to/set-up-a-repository.md +142 -0
  27. package/docs/how-to/turn-on-the-css-families.md +35 -0
  28. package/docs/how-to/upgrade-the-kit.md +42 -0
  29. package/docs/shipped-files.md +36 -0
  30. package/package.json +11 -1
  31. package/src/delivery/bun-pin.ts +19 -5
  32. package/src/delivery/ci-wiring.ts +11 -77
  33. package/src/delivery/commit-identity.ts +6 -5
  34. package/src/delivery/runner-policy.ts +385 -0
  35. package/src/delivery/workflow-references.ts +1 -1
  36. package/src/docs/doc-templates.ts +1 -1
  37. package/src/docs/doc-versions.ts +137 -0
  38. package/src/docs/docs.ts +7 -0
  39. package/src/testing/mutation-baseline-full.ts +23 -47
  40. package/src/testing/mutation-baseline-saved.ts +1 -1
  41. package/src/testing/mutation-baseline.ts +10 -8
  42. package/src/testing/mutation-scope-graph.ts +10 -19
  43. package/src/testing/mutation-scope.ts +8 -41
@@ -12,6 +12,7 @@ It also fails when a doc names a file, a heading or a script that does not exist
12
12
  It holds each doc file a change touches to the template for its kind.
13
13
  It lists every other doc file that does not match its template yet, and does not fail on it.
14
14
  It holds each line a change adds or edits in a living doc or an agent file to the prose rules, as [The prose rules](#the-prose-rules) says.
15
+ It fails when such a line copies a version `package.json` or `.bun-version` pins, as [Pinned versions](#pinned-versions) says.
15
16
  It fails when a living doc or an agent file names a path, link or command that does not resolve, and the range added or broke it.
16
17
  It fails when an agent file holds more than 3,000 characters or a `## Maintaining this file` section, whatever the range touches, as [Agent files](#agent-files) says.
17
18
  It fails when an entry in an agent file names no tracked path, link or `bun run` command, whatever the range touches.
@@ -119,6 +120,24 @@ The cap is 20 words in an ordered list item and 25 words elsewhere.
119
120
  The package exports it as `@avi2dg/checks/scripts/prose-matchers.ts`.
120
121
  A host such as a write-time hook can copy that one file into a directory with no `node_modules`, and refuse the same lines.
121
122
 
123
+ ## Pinned versions
124
+
125
+ A version a manifest pins has one home, and a doc that copies it by hand goes stale when the pin moves.
126
+ Prose and code are judged alike.
127
+ A line a change adds or edits in a living doc or an agent file fails when it carries one of these:
128
+
129
+ - A package `package.json` lists in `dependencies`, `devDependencies`, `peerDependencies` or `optionalDependencies` at one version, written as `name@version` or beside the name in prose or a table.
130
+ - The `version`, `packageManager` or an `engines` version from `package.json`, or the version in `.bun-version`, written on its own.
131
+
132
+ The doc names the file that holds the version instead, as in `Bun, at the version .bun-version pins`.
133
+ A command that needs the exact version sits in a generated block, between a `<!-- generated <name>: ... -->` line and its `<!-- end generated <name> -->` line, which a build writes from the manifest.
134
+ No line in a complete same-named generated block is judged.
135
+ A version the manifests do not pin passes, such as a sample's `lodash@4.17.20`.
136
+ So does a longer version that opens with a pin.
137
+ A manifest range with no single version pins nothing, as in `>=1.2.3 <2` in `package.json`.
138
+ A doc line that carries a pinned version still fails when that version sits inside a range.
139
+ It reads the root `package.json` and `.bun-version`, so a nested package's pins are not checked.
140
+
122
141
  ## Paths, links and commands
123
142
 
124
143
  Each reference a living doc or an agent file names has to resolve at the head commit:
@@ -177,6 +196,7 @@ It reads the lines the range adds or edits from the diff, with renames detected,
177
196
  It reads the files tracked at both ends of the range, and the `scripts` of each `package.json` a living doc or an agent file sits under.
178
197
  It compares each code span a living doc or an agent file names with the text git tracks outside the docs at both ends of the range.
179
198
  It reads the repository's own name and its direct dependencies from the root `package.json` at the head commit.
199
+ It reads the versions the root `package.json` and `.bun-version` pin at the head commit.
180
200
  It reads each agent file at the head commit for the ceiling, its sections and its entries, whatever the range touches.
181
201
  A name that an installed direct dependency still holds counts as present.
182
202
  From the working tree it reads the ignore files git reads, `node_modules/.bin`, and the directory of each direct dependency under `node_modules`.
@@ -195,16 +215,17 @@ With one it is that commit against its parent, or against the empty tree for a r
195
215
 
196
216
  | Code | When |
197
217
  | --- | --- |
198
- | 0 | every doc file the range touches holds to its template, every line it adds to a living doc or an agent file holds to the prose rules, it adds or breaks no reference that does not resolve, every agent file holds to the ceiling and holds no `## Maintaining this file`, every entry in an agent file names a tracked path, a link or a command, and no code span a living doc or an agent file names vanished from every file outside the docs |
199
- | 1 | a doc file the range touches does not hold to its template, a line the range adds to a living doc or an agent file breaks a prose rule, the range adds or breaks a reference that does not resolve, an agent file is over the ceiling or holds `## Maintaining this file`, an entry in an agent file names no tracked path, link or command, or the range removes a name a living doc or an agent file still carries |
218
+ | 0 | every doc file the range touches holds to its template, every line it adds to a living doc or an agent file holds to the prose rules and copies no pinned version, it adds or breaks no reference that does not resolve, every agent file holds to the ceiling and holds no `## Maintaining this file`, every entry in an agent file names a tracked path, a link or a command, and no code span a living doc or an agent file names vanished from every file outside the docs |
219
+ | 1 | a doc file the range touches does not hold to its template, a line the range adds to a living doc or an agent file breaks a prose rule or copies a pinned version, the range adds or breaks a reference that does not resolve, an agent file is over the ceiling or holds `## Maintaining this file`, an entry in an agent file names no tracked path, link or command, or the range removes a name a living doc or an agent file still carries |
200
220
  | 2 | a `package.json` does not decode, a ref does not resolve, or `grep` cannot read an installed direct dependency |
201
221
 
202
222
  ## Sample output
203
223
 
204
224
  ```
205
- docs: 6 violation(s):
225
+ docs: 7 violation(s):
206
226
  README.md:1: lacks `## Before you begin`
207
227
  README.md:12: carries `;`, a semicolon. Use two sentences
228
+ README.md:13: carries `1.3.5`, a version .bun-version pins. Name .bun-version instead, or let a build write the line between generated markers
208
229
  README.md:14: carries `former`, a report about the past. Say what is true now, and leave what changed to the changelog, a commit message or a decision record
209
230
  README.md:20: names `scripts/bild.ts`, which is not in the repository
210
231
  docs/parts.md: is a page under docs/ with no mode; add kind: tutorial, how-to, reference, explanation in YAML front matter
@@ -34,7 +34,7 @@ import { defineConfig } from "@avi2dg/checks/knip";
34
34
  export default defineConfig({ entry: ["src/index.ts"] });
35
35
  ```
36
36
 
37
- The builder sets `include` to unreferenced files alone, and adds `tests/**/*.test.ts` and `dependency-cruiser.config.ts` to the entries.
37
+ The builder sets `include` to unreferenced files alone, and adds `tests/**/*.test.ts`, `dependency-cruiser.config.ts` and `oxlint.config.ts` to the entries.
38
38
  The gate resolves the Knip binary from the installed kit, so a consumer installs nothing beyond the kit.
39
39
 
40
40
  ## Arguments
@@ -133,10 +133,14 @@ A config not built with `defineConfig` names its own `customSyntax` for markup,
133
133
 
134
134
  The repository installs the optional peers the gate loads, at the versions the kit pins:
135
135
 
136
+ <!-- generated install: bun run build writes it from package.json and scripts/doc-blocks.ts -->
137
+
136
138
  ```sh
137
- bun add -d stylelint@17.16.0 postcss-html@2.0.0 stylelint-config-standard@40.0.0 stylelint-plugin-use-baseline@1.4.7 postcss-value-parser@4.2.0
139
+ bun add -d postcss-html@2.0.0 postcss-value-parser@4.2.0 stylelint@17.16.0 stylelint-config-standard@40.0.0 stylelint-plugin-use-baseline@1.4.7
138
140
  ```
139
141
 
142
+ <!-- end generated install -->
143
+
140
144
  ## Arguments
141
145
 
142
146
  ```sh
@@ -8,7 +8,7 @@ audience: consumers
8
8
 
9
9
  ## What it checks
10
10
 
11
- It runs the gates under [What runs](../../README.md#what-runs), each in its own process.
11
+ It runs the gates under [What runs](../bins.md#what-runs), each in its own process.
12
12
  Gates requiring tracked TypeScript files begin running when the repository tracks TypeScript.
13
13
  `checks-lint-coverage` and `checks-unused` also begin running when the repository tracks an `.astro` file.
14
14
  `checks-advisories` begins running when the repository tracks `bun.lock`.
@@ -62,5 +62,5 @@ It never runs `checks-browser`, which a product runs after it builds its site.
62
62
 
63
63
  ## Related topics
64
64
 
65
- - [What runs](../../README.md#what-runs)
65
+ - [What runs](../bins.md#what-runs)
66
66
  - [checks-ci-wiring](checks-ci-wiring.md)
@@ -22,12 +22,15 @@ With `--full` it lists up to 50 `schedule` runs and up to 50 `workflow_dispatch`
22
22
  It tries those runs newest first, and restores the first `mutation-baseline-full` artifact that has not expired, downloads and holds the report.
23
23
  It tries no run after that one.
24
24
  It takes the report from `mutation.json` at the artifact's root, or else from `mutation/mutation.json`, and copies it to the destination.
25
- With an incremental destination it also copies `stryker-incremental.json` when the artifact holds one, so the next full run retests the mutants its changes reach, while inputs a test reads at runtime stay untracked.
25
+ With an incremental destination it also copies `stryker-incremental.json` when the artifact holds one, so the next full run retests the mutants its changes reach, while a result made stale by a file a test reads at runtime lasts until the next 28-day refresh.
26
26
  The restore then drops each test that reads a changed file and each mutant result for a changed source from the copied state.
27
27
  It drops non-killed results when any test goes stale.
28
- It drops the whole state when a Stryker config, stryker.preset.js, the lockfile, the Bun version, package.json apart from its version, a patch its patchedDependencies names, a file the active config loads at either revision, or the bunfig preload set or a file it loads changed.
28
+ It drops the whole state when a Stryker config, stryker.preset.js, the lockfile, the Bun version, a file the active config reaches through a relative import at either revision, or the bunfig preload set or a file it reaches through a relative import changed.
29
29
  It walks the config and preload dependencies at both revisions, so a file only the older revision resolved still drops the state when it changes.
30
+ A file a config or preload reaches only through a package main escapes invalidation until the next 28-day refresh.
30
31
  A state that does not parse fails the restore instead of running on unknown results.
32
+ It drops the whole state when the restored run started before the current 28-day refresh window, and the windows count from the Unix epoch.
33
+ Every state a window keeps was built inside it, so each reused result is at most 28 days old, and a weekly full baseline retests every mutant every fourth week.
31
34
  Without the state file the report still restores.
32
35
 
33
36
  It creates the directories each destination needs.
@@ -53,9 +56,10 @@ It reads `RUNNER_ENVIRONMENT` and `HOME` to place the runner cache, and `GITHUB_
53
56
  `--save` and `--prune` read `GITHUB_RUN_ID`.
54
57
  With `--full` and an incremental destination it reads the run's head through `gh run view <run> --json headSha`.
55
58
  It diffs that head against `HEAD`, which needs the checkout's full history.
56
- It shows `bunfig.toml`, `package.json`, every tracked `package.json`, and the files the active Stryker config imports at both revisions.
59
+ It shows `bunfig.toml` and the files the active Stryker config imports at both revisions.
57
60
  It reads every tracked `.ts`, `.tsx`, `.mts`, `.cts`, `.js`, `.jsx`, `.mjs`, and `.cjs` file from the working tree for their relative imports.
58
- Inputs a test reads at runtime without a static import, such as markdown files, escape invalidation as a known limitation.
61
+ Inputs a test reads at runtime without a static import, such as markdown files, escape invalidation until the next refresh window.
62
+ It reads the restored run's `createdAt` from the run list to place that run in a refresh window.
59
63
 
60
64
  ## The runner cache
61
65
 
@@ -178,7 +182,7 @@ The saved baseline uses none of the account's Actions storage, which every priva
178
182
  That storage accrues by the hour over the month, so an artifact deleted early still counts until the month ends.
179
183
  A baseline job on a GitHub-hosted runner uploads the same two files as a `mutation-baseline` artifact with `retention-days: 7` instead, since the restore reads only the newest `main` run.
180
184
 
181
- A full baseline job restores the previous full state before its incremental Stryker run, so it retests the mutants its changes reach, while inputs a test reads at runtime stay untracked.
185
+ A full baseline job restores the previous full state before its incremental Stryker run, so it retests the mutants its changes reach, while a result made stale by a file a test reads at runtime lasts until the next 28-day refresh.
182
186
  The checkout behind that step fetches the full history, so the restore can diff the restored run against `HEAD`:
183
187
 
184
188
  ```yaml
@@ -136,9 +136,9 @@ A changed unit test that `main` edited after that baseline widens the scope to e
136
136
  Start a full baseline with `gh workflow run mutation`, as [checks-mutation](checks-mutation.md#running-it-in-ci) shows.
137
137
  A run with `--mutate` is scoped, so the preset lets it through without `CI=true`.
138
138
  The base worktree's path carries the run's id and attempt, and the last step removes it even when a run fails, so a runner kept between jobs starts each job clean.
139
- A public repository keeps `runs-on: ubuntu-latest`, as [checks-ci-wiring](checks-ci-wiring.md#runners) allows a public repository on every job.
140
- A private repository sets `runs-on: [self-hosted, Linux, X64, winbox]` instead, which is the fleet's self-hosted Linux runner where it sends its mutation runs.
141
- The job never reads `CI_RUNS_ON`, so an override that moves a repository's other jobs during a runner outage leaves the comparison waiting for `winbox`.
139
+ A public repository keeps `runs-on: ubuntu-latest`, as [checks-ci-wiring](checks-ci-wiring.md#runners) requires of a job a fork can reach.
140
+ A private repository sets `runs-on` to the labels it declares in `package.json` under `checks.mutationRunner` instead, which is where it sends its mutation runs.
141
+ The job never reads `CI_RUNS_ON`, so an override that moves a repository's other jobs during a runner outage leaves the comparison waiting for those labels.
142
142
  [checks-ci-wiring](checks-ci-wiring.md#runners) refuses a mutation job that names it in `runs-on`.
143
143
 
144
144
  ## Related topics
@@ -82,7 +82,7 @@ An `--incremental` run with no report stops before instrumenting with the missin
82
82
  A repository runs full baselines from the mutation workflow on `workflow_dispatch`.
83
83
  A repository that picks a branch scope from a baseline starts that workflow weekly with `gh workflow run mutation`, because a GitHub schedule can start hours late.
84
84
  Run scoped checks locally during development.
85
- A first baseline with no state costs a full Stryker run, and each later one retests the mutants its changes reach, while inputs a test reads at runtime stay untracked.
85
+ A first baseline with no state costs a full Stryker run, and each later one retests the mutants its changes reach, while a result made stale by a file a test reads at runtime lasts until the next 28-day refresh.
86
86
 
87
87
  ## Running it in CI
88
88
 
@@ -97,7 +97,7 @@ jobs:
97
97
  permissions:
98
98
  contents: read
99
99
  actions: read
100
- runs-on: [self-hosted, Linux, X64, winbox]
100
+ runs-on: [self-hosted, gpu, x64]
101
101
  steps:
102
102
  - uses: actions/checkout@v5
103
103
  with:
@@ -121,7 +121,11 @@ jobs:
121
121
 
122
122
  A first baseline with no state tests every mutant.
123
123
  Each later restore drops the tests that read a changed file and the mutant results for a changed source from the restored state, and drops non-killed results when any test goes stale.
124
- It drops the whole state when a Stryker config, stryker.preset.js, the lockfile, the Bun version, package.json apart from its version, a patch its patchedDependencies names, a file the active config loads at either revision, or the bunfig preload set or a file it loads changed, and fails the restore when the state does not parse, so the run retests every mutant those changes reach and reuses the rest, while inputs a test reads at runtime stay untracked.
124
+ It drops the whole state when a Stryker config, stryker.preset.js, the lockfile, the Bun version, a file the active config reaches through a relative import at either revision, or the bunfig preload set or a file it reaches through a relative import changed.
125
+ It fails the restore when the state does not parse.
126
+ The run retests every mutant those changes reach and reuses the rest.
127
+ A result made stale by a file a test reads at runtime lasts until the next 28-day refresh.
128
+ A file a config or preload reaches only through a package main escapes invalidation until the next 28-day refresh.
125
129
  The checkout fetches the full history, so the restore can tell which files changed since the previous full run.
126
130
  Its report names every test that covers each covered mutant and the first test that killed each killed mutant.
127
131
  [checks-mutation-scope](checks-mutation-scope.md#what-it-reads) says why that one killer is enough.
@@ -131,8 +135,8 @@ The restore reads only a successful run of `mutation.yml` on `main`, so a sweep
131
135
  The artifact keeps 30 days, so the newest full baseline outlives the week between full runs.
132
136
  The upload holds the slimmed report and the slimmed state without each mutant's `statusReason`, as [checks-mutation-baseline](checks-mutation-baseline.md#the-saved-baselines) says, so the one directory holds about 10 MB in place of about 500 MB.
133
137
 
134
- The job runs on the fleet's self-hosted Linux runner labelled `winbox`, which is where a repository sends its full sweeps.
135
- It never reads `CI_RUNS_ON`, so an override that moves a repository's other jobs during a runner outage leaves the sweep waiting for `winbox`.
138
+ The job runs on the self-hosted labels the repository declares in `package.json` under `checks.mutationRunner`, which is where a repository sends its full sweeps.
139
+ It never reads `CI_RUNS_ON`, so an override that moves a repository's other jobs during a runner outage leaves the sweep waiting for those labels.
136
140
  [checks-ci-wiring](checks-ci-wiring.md#runners) refuses a mutation job that names it in `runs-on`.
137
141
  The `name: mutation` line is what `gh workflow run mutation` looks up.
138
142
  GitHub sets `CI=true` on every runner, so the preset lets the full run through there.
@@ -177,7 +177,7 @@ The concurrency group queues a run started by hand behind the scheduled one, so
177
177
  A dispatch on a branch runs both jobs and lists its findings in the report job's log, and `if-no-files-found: ignore` lets that run upload no record.
178
178
  `issues: write` on the report job is the one permission beyond reading the repository and its runs.
179
179
  A public repository keeps `runs-on: ubuntu-latest` on the mutation job.
180
- A private repository sets `runs-on: [self-hosted, Linux, X64, winbox]` on the mutation job, and [checks-ci-wiring](checks-ci-wiring.md#runners) refuses a mutation job that names `CI_RUNS_ON`.
180
+ A private repository sets `runs-on` on the mutation job to the labels declared under `checks.mutationRunner` in `package.json`, and [checks-ci-wiring](checks-ci-wiring.md#runners) refuses a mutation job that names `CI_RUNS_ON`.
181
181
  The report job reads `CI_RUNS_ON`, because it runs no mutant.
182
182
  A successful run uploads `night-run`, so the next night compares with it, and a failed run uploads nothing a later night reads.
183
183
  The artifact keeps 90 days, so a gap of nights still leaves a previous night to compare with.
@@ -46,8 +46,8 @@ It reads the commits of the range from git, not the working tree.
46
46
  It needs git 2.36 or newer, which diffs a merge against the merge git would make on its own, and it exits 2 on an older git.
47
47
  It reads its rules from `src/delivery/gitleaks.toml` in the installed package.
48
48
 
49
- It runs gitleaks 8.30.1, pinned by the SHA-256 of each platform's archive and of the binary inside it.
50
- On first use it downloads the archive from the scanner's GitHub release and unpacks the binary into `~/.cache/avi2dg-checks/gitleaks/8.30.1/`.
49
+ It runs the gitleaks release `src/delivery/gitleaks.ts` pins, by the SHA-256 of each platform's archive and of the binary inside it.
50
+ On first use it downloads the archive from the scanner's GitHub release and unpacks the binary into `~/.cache/avi2dg-checks/gitleaks/<version>/`.
51
51
  It checks the binary's SHA-256 again on every run and exits 2 on a copy that differs.
52
52
  Builds are pinned for macOS and Linux, each on x64 and arm64.
53
53
  On any other platform it exits 2, and no setting runs a scanner other than the pinned build.
@@ -26,7 +26,7 @@ import { defineConfig } from "@avi2dg/checks/knip";
26
26
  export default defineConfig({ entry: ["src/index.ts"] });
27
27
  ```
28
28
 
29
- The builder sets `include` to unreferenced files alone, and adds `tests/**/*.test.ts` and `dependency-cruiser.config.ts` to the entries.
29
+ The builder sets `include` to unreferenced files alone, and adds `tests/**/*.test.ts`, `dependency-cruiser.config.ts` and `oxlint.config.ts` to the entries.
30
30
  In a repository that depends on `astro`, Knip reads `.astro` imports, so a file only an `.astro` entry imports counts as used and an `.astro` file no entry reaches is named.
31
31
  The gate resolves the Knip binary from the installed kit, so a consumer installs nothing beyond the kit.
32
32
 
@@ -0,0 +1,142 @@
1
+ ---
2
+ kind: how-to
3
+ audience: consumers
4
+ ---
5
+ # Set up a repository
6
+
7
+ A repository owner follows these steps once, after [Install](../../README.md#install), so each gate `checks-lint` runs finds the file it reads.
8
+
9
+ ## Before you begin
10
+
11
+ - The kit in `devDependencies` and the kit's `.bun-version` copied, as [Install](../../README.md#install) says.
12
+
13
+ ## Write the tool configs
14
+
15
+ To give each tool the config it reads:
16
+
17
+ 1. Write `oxlint.config.ts` with the kit's builder, saying whether the sources are Effect programs:
18
+
19
+ ```ts
20
+ import { defineConfig } from "@avi2dg/checks/oxlint";
21
+
22
+ export default defineConfig({ effect: true });
23
+ ```
24
+
25
+ 1. Keep oxlint out of `node_modules/` through `.gitignore`:
26
+
27
+ ```
28
+ node_modules/
29
+ ```
30
+
31
+ 1. Add the Bun types, which the kit's config builders need to typecheck:
32
+
33
+ ```sh
34
+ bun add -d @types/bun
35
+ ```
36
+
37
+ 1. Extend the tsconfig fragment in `tsconfig.json`, naming the Bun types:
38
+
39
+ ```json
40
+ {
41
+ "extends": "@avi2dg/checks/tsconfig.effect.json",
42
+ "compilerOptions": { "types": ["bun"] }
43
+ }
44
+ ```
45
+
46
+ 1. Copy the bunfig preset:
47
+
48
+ ```sh
49
+ cp node_modules/@avi2dg/checks/bunfig.toml bunfig.toml
50
+ ```
51
+
52
+ 1. Name the files nothing imports in `knip.config.ts`:
53
+
54
+ ```ts
55
+ import { defineConfig } from "@avi2dg/checks/knip";
56
+
57
+ export default defineConfig({ entry: ["src/index.ts"] });
58
+ ```
59
+
60
+ 1. Write `dependency-cruiser.config.ts` only when the kit's import rules need an entry point or a boundary of the repository's own:
61
+
62
+ ```ts
63
+ import { defineConfig } from "@avi2dg/checks/dependency-cruiser";
64
+
65
+ export default defineConfig({ orphans: ["^src/bin[.]ts$"] });
66
+ ```
67
+
68
+ ## Run the checks on every pull request
69
+
70
+ To run the gates in CI:
71
+
72
+ 1. Add scripts to `package.json`, replacing the build entry with the repository's own build command:
73
+
74
+ ```json
75
+ "build": "bun build src/index.ts --outdir dist --target node && checks-effect-scope",
76
+ "lint": "oxlint && checks-lint",
77
+ "typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
78
+ "test": "checks-test"
79
+ ```
80
+
81
+ 1. Run the scripts on every pull request in `.github/workflows/ci.yml`, fetching the whole history the range needs:
82
+
83
+ ```yaml
84
+ on:
85
+ pull_request:
86
+ jobs:
87
+ checks:
88
+ runs-on: ubuntu-latest
89
+ steps:
90
+ - uses: actions/checkout@v5
91
+ with:
92
+ fetch-depth: 0
93
+ - uses: oven-sh/setup-bun@v2
94
+ with:
95
+ bun-version-file: .bun-version
96
+ - run: bun install --frozen-lockfile
97
+ - run: bun run build
98
+ - run: git diff --exit-code
99
+ - run: bun run lint
100
+ - run: bun run typecheck
101
+ - run: bun run test
102
+ ```
103
+
104
+ Lint the pull request title in the same job with one step:
105
+
106
+ ```yaml
107
+ - id: title
108
+ name: lint the title
109
+ env:
110
+ PR_TITLE: ${{ github.event.pull_request.title }}
111
+ GH_TOKEN: ${{ github.token }}
112
+ run: bunx checks-commit-title
113
+ ```
114
+
115
+ A repository whose branch protection requires a `commitlint` status reports the title step under that name using the following step.
116
+ The same job reports the title step's outcome under that name on every run, which needs `statuses: write`:
117
+
118
+ ```yaml
119
+ - name: report the title as commitlint
120
+ if: always()
121
+ env:
122
+ GH_TOKEN: ${{ github.token }}
123
+ HEAD_SHA: ${{ github.event.pull_request.head.sha }}
124
+ OUTCOME: ${{ steps.title.outcome }}
125
+ run: |
126
+ state=failure
127
+ if [ "$OUTCOME" = success ]; then state=success; fi
128
+ gh api "repos/$GITHUB_REPOSITORY/statuses/${HEAD_SHA:-$GITHUB_SHA}" -f state="$state" -f context=commitlint -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" --silent
129
+ ```
130
+
131
+ A private repository sets `runs-on: ${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}` on each job, as [checks-ci-wiring](../gates/checks-ci-wiring.md#runners) requires.
132
+ `checks-effect-scope` writes the Effect paths of `oxlint.config.ts` and the kit's severities into `tsconfig.json`, as [The Effect rules](../configs/effect-rules.md#language-service) says.
133
+ `bun run lint` then ends with `checks-lint: <count> gate(s) pass`.
134
+
135
+ ## Related topics
136
+
137
+ - [The bins](../bins.md)
138
+ - [Native settings](../configs/native-settings.md)
139
+ - [The Effect rules](../configs/effect-rules.md)
140
+ - [The TypeScript rules](../configs/typescript-rules.md)
141
+ - [The dependency rules](../configs/dependency-rules.md)
142
+ - [The commit message lint](../configs/commit-messages.md)
@@ -0,0 +1,35 @@
1
+ ---
2
+ kind: how-to
3
+ audience: consumers
4
+ ---
5
+ # Turn on the CSS families
6
+
7
+ A repository owner follows these steps to judge the repository's CSS with the stylelint families it needs.
8
+
9
+ ## Turn them on
10
+
11
+ To turn on the CSS families:
12
+
13
+ 1. Install the peers [checks-frontend-syntax](../gates/checks-frontend-syntax.md#what-it-reads) loads, with the `bun add` line on its page.
14
+ 1. Declare `frontend-syntax.json`, which turns the gate on, as its page says.
15
+ 1. Write `stylelint.config.ts` with the families the repository needs:
16
+
17
+ ```ts
18
+ import { defineConfig } from "@avi2dg/checks/stylelint";
19
+
20
+ export default defineConfig({
21
+ baseline: "widely",
22
+ tokens: ["src/styles/tokens.css"],
23
+ bem: true,
24
+ flatSelectors: true,
25
+ noPageHas: true,
26
+ });
27
+ ```
28
+
29
+ [checks-frontend-syntax](../gates/checks-frontend-syntax.md#what-it-reads) says what each key takes and which family it turns on.
30
+ `noPageHas` sees a `:has` nested under a page selector only together with `flatSelectors`, which refuses the nesting.
31
+
32
+ ## Related topics
33
+
34
+ - [checks-frontend-syntax](../gates/checks-frontend-syntax.md)
35
+ - [checks-css-size](../gates/checks-css-size.md)
@@ -0,0 +1,42 @@
1
+ ---
2
+ kind: how-to
3
+ audience: consumers
4
+ ---
5
+ # Upgrade the kit
6
+
7
+ A repository owner follows these steps to move a repository the kit holds to a newer release.
8
+
9
+ ## Before you begin
10
+
11
+ - A repository set up as [Set up a repository](set-up-a-repository.md) says.
12
+
13
+ ## Upgrade
14
+
15
+ To move a repository to a newer release of the kit:
16
+
17
+ 1. Rerun the [install line](../../README.md#install), which moves the kit to its newest release.
18
+ A repository that lists a required peer explicitly also reruns the upgrade line below.
19
+ The upgrade line keeps each declaration the repository imports.
20
+ The upgrade line writes the lockfile, so the checks below run on the new pins.
21
+
22
+ <!-- generated install: bun run build writes it from package.json and scripts/doc-blocks.ts -->
23
+
24
+ ```sh
25
+ bun add -d @effect/tsgo@0.45.0 @swc/core@1.16.2 dependency-cruiser@18.4.0 effect@4.0.0 jscpd@5.3.2 oxlint@1.83.0 oxlint-tsgolint@7.0.2002 typescript@7.0.2
26
+ ```
27
+
28
+ <!-- end generated install -->
29
+
30
+ A repository that opted in to `checks-browser`, `checks-frontend-syntax` or `checks-copy` also reruns the `bun add` line on its page.
31
+ 1. Run `bun run build`, whose `checks-effect-scope` rewrites the Effect override and the kit's severities in `tsconfig.json` when a release changes them.
32
+ A severity key the release drops stays in `tsconfig.json` until the repository removes it.
33
+ 1. Copy `node_modules/@avi2dg/checks/bunfig.toml` over `bunfig.toml` again, since `checks-test-layout` compares the copy with the installed preset.
34
+ 1. Run `bun run lint`, `bun run typecheck` and `bun run test`.
35
+
36
+ The repository's lockfile pins the kit, so a repository moves only when it runs these steps.
37
+ [CHANGELOG.md](../../CHANGELOG.md), shipped in the package, lists what each release changed.
38
+
39
+ ## Related topics
40
+
41
+ - [The shipped files](../shipped-files.md)
42
+ - [CHANGELOG.md](../../CHANGELOG.md)
@@ -0,0 +1,36 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
5
+ # The shipped files
6
+
7
+ The package ships these paths, and a repository reads or copies them from its install.
8
+
9
+ ## Where things are
10
+
11
+ Every path is relative to the installed package, `node_modules/@avi2dg/checks/`.
12
+
13
+ <!-- generated shipped: bun run build writes it from package.json and scripts/doc-blocks.ts -->
14
+
15
+ | Path | What it holds |
16
+ | --- | --- |
17
+ | `CHANGELOG.md` | every release, and what it changed |
18
+ | `docs/` | the how-to guides, a reference page per bin and per shared config, and why the kit is shaped this way |
19
+ | `bunfig.toml` | the bunfig preset a repository copies |
20
+ | `.bun-version` | the fleet bun version the bun pin check reads |
21
+ | `commitlint.config.ts` | the shared commitlint config |
22
+ | `dependency-cruiser.config.js` | the kit's dependency-cruiser rules as a base a `.dependency-cruiser.cjs` extends by path, which the build writes |
23
+ | `knip-base.json` | the Knip `include` setting, for a configuration that spreads it |
24
+ | `src/` | every bin, which a package script calls by its `checks-` name, the modules the bins import, and the Effect language service severities under `src/quality/presets/` |
25
+ | `dist/` | the compiled oxlint plugins, the config builders `@avi2dg/checks/oxlint`, `@avi2dg/checks/knip`, `@avi2dg/checks/dependency-cruiser` and `@avi2dg/checks/stylelint` resolve to, and the doc templates, one template per kind of doc file |
26
+ | `oxlintrc.json` | the oxlint `base` as a config a `.oxlintrc.json` extends by path, which the build writes |
27
+ | `stryker.preset.js` | the Stryker mutation-testing preset, which refuses a full run outside CI |
28
+ | `tsconfig.effect.json` | the tsconfig fragment with the shared compiler options and the Effect language-service block |
29
+ | `ts-reset.d.ts` | the two ts-reset rules `tsconfig.effect.json` lists in `files` |
30
+
31
+ <!-- end generated shipped -->
32
+
33
+ ## Related topics
34
+
35
+ - [Set up a repository](how-to/set-up-a-repository.md)
36
+ - [Upgrade the kit](how-to/upgrade-the-kit.md)
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@avi2dg/checks",
3
- "version": "0.51.0",
3
+ "version": "0.52.0",
4
4
  "description": "Deterministic checks shared across a set of TypeScript repositories",
5
+ "author": {
6
+ "name": "avi2d",
7
+ "email": "avi2dg@gmail.com"
8
+ },
5
9
  "license": "MIT",
6
10
  "repository": {
7
11
  "type": "git",
@@ -13,10 +17,14 @@
13
17
  "type": "module",
14
18
  "files": [
15
19
  "CHANGELOG.md",
20
+ "docs/bins.md",
16
21
  "docs/configs/",
17
22
  "docs/design.md",
18
23
  "docs/gates/",
24
+ "docs/how-to/",
25
+ "docs/shipped-files.md",
19
26
  "bunfig.toml",
27
+ ".bun-version",
20
28
  "commitlint.config.ts",
21
29
  "dependency-cruiser.config.js",
22
30
  "knip-base.json",
@@ -60,6 +68,7 @@
60
68
  "src/delivery/workflow-references.ts",
61
69
  "src/delivery/bun-pin.ts",
62
70
  "src/delivery/mutation-job.ts",
71
+ "src/delivery/runner-policy.ts",
63
72
  "src/delivery/secrets.ts",
64
73
  "src/delivery/gitleaks.ts",
65
74
  "src/delivery/gitleaks.toml",
@@ -95,6 +104,7 @@
95
104
  "src/docs/doc-snapshot.ts",
96
105
  "src/docs/doc-templates.ts",
97
106
  "src/docs/doc-names.ts",
107
+ "src/docs/doc-versions.ts",
98
108
  "dist/templates/",
99
109
  "src/quality/presets/effect.language-service.json",
100
110
  "src/quality/presets/oxlint.ts",
@@ -2,12 +2,12 @@ import { Effect, FileSystem, Path, Schema } from "effect";
2
2
  import { within } from "./mutation-job.ts";
3
3
  import { isRecord, locate, workflowJobs, type Workflow } from "./workflow-references.ts";
4
4
 
5
- export const FLEET_BUN_VERSION = "1.4.3";
6
5
  const ROOT_MANIFEST = "package.json";
7
6
 
8
7
  export type BunPinPolicy = {
9
8
  readonly files: ReadonlyMap<string, string>;
10
9
  readonly manifests: ReadonlyMap<string, unknown>;
10
+ readonly fleet: string;
11
11
  };
12
12
 
13
13
  export type BunPinFault = {
@@ -94,12 +94,12 @@ function bunPinRequest(policy: BunPinPolicy, step: Readonly<Record<string, unkno
94
94
  function bunPinFault(policy: BunPinPolicy, step: Readonly<Record<string, unknown>>): string | undefined {
95
95
  const request = bunPinRequest(policy, step);
96
96
  if (request.expression !== undefined) {
97
- return `requests bun ${request.expression} from ${request.source}, which cannot be shown equal to ${FLEET_BUN_VERSION} before the run`;
97
+ return `requests bun ${request.expression} from ${request.source}, which cannot be shown equal to ${policy.fleet} before the run`;
98
98
  }
99
99
  if (request.version === undefined) {
100
- return `pins no bun version from ${request.source}, so setup-bun installs latest, want ${FLEET_BUN_VERSION}`;
100
+ return `pins no bun version from ${request.source}, so setup-bun installs latest, want ${policy.fleet}`;
101
101
  }
102
- return request.version === FLEET_BUN_VERSION ? undefined : `requests bun ${request.version} from ${request.source}, want ${FLEET_BUN_VERSION}`;
102
+ return request.version === policy.fleet ? undefined : `requests bun ${request.version} from ${request.source}, want ${policy.fleet}`;
103
103
  }
104
104
 
105
105
  function unknownRefFault(ref: string): string {
@@ -156,6 +156,20 @@ const readJsonFile = Effect.fn("readJsonFile")(function* (file: string) {
156
156
  );
157
157
  });
158
158
 
159
+ export class BunPinError extends Schema.TaggedError<BunPinError>()("BunPinError", {
160
+ message: Schema.String,
161
+ }) {}
162
+
163
+ // The kit ships its own .bun-version, so the check reads the fleet pin from that file rather than a constant.
164
+ const kitBunVersion = Effect.gen(function* () {
165
+ const file = (yield* Path.Path).join(import.meta.dir, "..", "..", ".bun-version");
166
+ const version = (yield* (yield* FileSystem.FileSystem).readFileString(file).pipe(
167
+ Effect.mapError((cause) => new BunPinError({ message: `cannot read the kit .bun-version at ${file}: ${cause.message}` })),
168
+ )).trim();
169
+ if (version === "") return yield* new BunPinError({ message: `the kit .bun-version at ${file} pins no version` });
170
+ return version;
171
+ });
172
+
159
173
  export const readBunPinPolicy = Effect.fn("readBunPinPolicy")(function* (root: string, workflows: readonly Workflow[]) {
160
174
  const path = yield* Path.Path;
161
175
  const fs = yield* FileSystem.FileSystem;
@@ -170,5 +184,5 @@ export const readBunPinPolicy = Effect.fn("readBunPinPolicy")(function* (root: s
170
184
  if (text !== undefined) files.set(name, text);
171
185
  }
172
186
  }
173
- return { files, manifests };
187
+ return { files, manifests, fleet: yield* kitBunVersion };
174
188
  });