@avi2dg/checks 0.21.0 → 0.23.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 (57) hide show
  1. package/CHANGELOG.md +66 -40
  2. package/CONTRIBUTING.md +14 -10
  3. package/README.md +15 -24
  4. package/bunfig.toml +1 -1
  5. package/dist/effect-channel/index.js +122 -0
  6. package/dist/{index.js → readability/index.js} +8 -120
  7. package/docs/configs/commit-messages.md +5 -1
  8. package/docs/configs/dependency-rules.md +5 -2
  9. package/docs/configs/effect-rules.md +32 -33
  10. package/docs/configs/native-settings.md +74 -0
  11. package/docs/configs/typescript-rules.md +4 -0
  12. package/docs/design.md +26 -27
  13. package/docs/gates/checks-backtest.md +4 -0
  14. package/docs/gates/checks-ci-wiring.md +30 -93
  15. package/docs/gates/checks-comment-gate.md +4 -0
  16. package/docs/gates/checks-commit-identity.md +23 -27
  17. package/docs/gates/checks-docs.md +23 -21
  18. package/docs/gates/checks-flake.md +4 -10
  19. package/docs/gates/checks-lint-coverage.md +5 -1
  20. package/docs/gates/checks-lint.md +22 -104
  21. package/docs/gates/checks-mutation-compare.md +4 -0
  22. package/docs/gates/checks-quarantine-clock.md +4 -0
  23. package/docs/gates/checks-repetition.md +27 -41
  24. package/docs/gates/checks-subsumed-tests.md +4 -0
  25. package/docs/gates/checks-suppressions-ratchet.md +4 -0
  26. package/docs/gates/checks-test-layout.md +16 -8
  27. package/docs/gates/checks-test.md +68 -36
  28. package/docs/gates/checks-vendor.md +20 -13
  29. package/oxlintrc.json +1 -1
  30. package/package.json +10 -21
  31. package/scripts/ci-wiring.ts +28 -97
  32. package/scripts/commit-identity.ts +32 -4
  33. package/scripts/doc-rules.ts +26 -11
  34. package/scripts/doc-templates.ts +2 -1
  35. package/scripts/docs.ts +4 -7
  36. package/scripts/gates.ts +0 -29
  37. package/scripts/git.ts +24 -1
  38. package/scripts/lint.ts +15 -34
  39. package/scripts/range-gate.ts +1 -2
  40. package/scripts/repetition.ts +51 -40
  41. package/scripts/shell-command.ts +7 -1
  42. package/scripts/swc.ts +46 -0
  43. package/scripts/test-layout.ts +40 -56
  44. package/scripts/test-skips.ts +180 -0
  45. package/scripts/test.ts +33 -38
  46. package/scripts/vendor.ts +55 -11
  47. package/dist/feature-rules.js +0 -354
  48. package/docs/configs/quality-file.md +0 -103
  49. package/docs/gates/checks-feature-owners.md +0 -113
  50. package/docs/gates/checks-quality.md +0 -111
  51. package/docs/gates/checks-size-budget.md +0 -107
  52. package/quality.schema.json +0 -514
  53. package/scripts/feature-owners.ts +0 -139
  54. package/scripts/quality-file.ts +0 -353
  55. package/scripts/quality.ts +0 -363
  56. package/scripts/size-budget.ts +0 -285
  57. package/scripts/size-rules.ts +0 -126
@@ -1,4 +1,4 @@
1
- // effect-channel/cognitive-nodes.ts
1
+ // readability/cognitive-nodes.ts
2
2
  var CONTROL_TYPES = ["IfStatement", "ConditionalExpression", "SwitchStatement", "SwitchCase", "TryStatement", "CatchClause"];
3
3
  var LOOP_TYPES = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement", "LabeledStatement"];
4
4
  var CALL_TYPES = ["LogicalExpression", "BreakStatement", "ContinueStatement", "CallExpression", "NewExpression", "ImportExpression"];
@@ -7,7 +7,7 @@ var PLAIN_A_TYPES = ["BlockStatement", "ExpressionStatement", "ReturnStatement",
7
7
  var PLAIN_B_TYPES = ["ClassDeclaration", "ClassExpression", "ClassBody", "MethodDefinition", "TSAbstractMethodDefinition", "PropertyDefinition", "TSAbstractPropertyDefinition", "AccessorProperty", "TSAbstractAccessorProperty", "ObjectExpression", "ArrayExpression"];
8
8
  var PLAIN_C_TYPES = ["AwaitExpression", "UnaryExpression", "UpdateExpression", "SpreadElement", "YieldExpression", "BinaryExpression", "AssignmentExpression", "TSAsExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TSNonNullExpression", "ChainExpression", "ParenthesizedExpression", "Decorator", "TSInstantiationExpression", "MemberExpression", "JSXMemberExpression", "TemplateLiteral", "TaggedTemplateExpression", "SequenceExpression", "JSXElement", "JSXFragment", "JSXOpeningElement", "JSXAttribute", "JSXExpressionContainer", "JSXSpreadChild", "JSXSpreadAttribute"];
9
9
 
10
- // effect-channel/cognitive-plain.ts
10
+ // readability/cognitive-plain.ts
11
11
  function isControl(node) {
12
12
  return CONTROL_TYPES.includes(node.type);
13
13
  }
@@ -142,7 +142,7 @@ function plainChildrenC(node) {
142
142
  }
143
143
  }
144
144
 
145
- // effect-channel/cognitive.ts
145
+ // readability/cognitive.ts
146
146
  function unreachable2(_value) {}
147
147
  function scoreList(state, nodes, nesting, parent) {
148
148
  for (const node of nodes) {
@@ -334,7 +334,7 @@ function cognitiveComplexity(root, names) {
334
334
  return state.recursive ? state.total + 1 : state.total;
335
335
  }
336
336
 
337
- // effect-channel/cognitive-complexity.ts
337
+ // readability/cognitive-complexity.ts
338
338
  var DEFAULT_MAX = 15;
339
339
  function maxOf(options) {
340
340
  const [first] = options;
@@ -416,126 +416,14 @@ var rule = {
416
416
  };
417
417
  var cognitive_complexity_default = rule;
418
418
 
419
- // effect-channel/no-error-channel-escape.ts
420
- var EFFECT_SOURCES = new Set(["effect", "effect/Effect"]);
421
- var INSTEAD = {
422
- drops: "it drops the failure and the success together, so no caller can tell one from the other. Handle the error by tag, or keep it as a value with Effect.result or Effect.exit",
423
- swallows: "a Cause is the typed error plus defects plus interruption, so this swallows the bugs and the cancellations along with it. Catch the errors you named, with Effect.catchTag or Effect.catchTags",
424
- blind: "the handler cannot see what it is recovering from, so every error in the channel collapses into one fallback. Name the errors with Effect.catchTag, or take the error and use it"
425
- };
426
- var REFUSED = new Map([
427
- ["ignore", INSTEAD.drops],
428
- ["ignoreCause", INSTEAD.drops],
429
- ["catchCause", INSTEAD.swallows],
430
- ["catchCauseIf", INSTEAD.swallows],
431
- ["catchCauseFilter", INSTEAD.swallows]
432
- ]);
433
- var blindToTheError = (handler) => {
434
- if (!handler)
435
- return false;
436
- if (handler.type !== "ArrowFunctionExpression" && handler.type !== "FunctionExpression")
437
- return false;
438
- return handler.params.every((param) => param.type === "Identifier" && /^_+$/.test(param.name));
439
- };
440
- var rule2 = {
441
- meta: {
442
- type: "problem",
443
- docs: { description: "Disallow the combinators that erase Effect's error channel" }
444
- },
445
- create(context) {
446
- const effect = new Set;
447
- const named = (node) => {
448
- if (!node || node.type !== "MemberExpression" || node.computed)
449
- return null;
450
- if (node.object.type !== "Identifier" || !effect.has(node.object.name))
451
- return null;
452
- return node.property.type === "Identifier" ? node.property.name : null;
453
- };
454
- const refuse = (node, combinator, instead) => {
455
- context.report({ node, message: `Effect.${combinator} erases the error channel: ${instead}` });
456
- };
457
- return {
458
- ImportDeclaration(node) {
459
- if (!EFFECT_SOURCES.has(node.source.value))
460
- return;
461
- const module = node.source.value === "effect/Effect";
462
- for (const specifier of node.specifiers) {
463
- if (specifier.type === "ImportSpecifier") {
464
- if (specifier.imported.type === "Identifier" && specifier.imported.name === "Effect")
465
- effect.add(specifier.local.name);
466
- } else if (module)
467
- effect.add(specifier.local.name);
468
- }
469
- },
470
- MemberExpression(node) {
471
- const combinator = named(node);
472
- if (combinator === null)
473
- return;
474
- const instead = REFUSED.get(combinator);
475
- if (instead !== undefined)
476
- refuse(node, combinator, instead);
477
- },
478
- CallExpression(node) {
479
- if (named(node.callee) !== "catch")
480
- return;
481
- if (!blindToTheError(node.arguments[node.arguments.length - 1]))
482
- return;
483
- refuse(node, "catch", INSTEAD.blind);
484
- }
485
- };
486
- }
487
- };
488
- var no_error_channel_escape_default = rule2;
489
-
490
- // effect-channel/no-throw.ts
491
- var rule3 = {
492
- meta: {
493
- type: "problem",
494
- docs: { description: "Disallow throw, which fails outside Effect's error channel" }
495
- },
496
- create(context) {
497
- return {
498
- ThrowStatement(node) {
499
- context.report({
500
- node,
501
- message: "throw escapes the error channel: no type records the failure, so no caller has to answer for it. Define the failure with Schema.TaggedError and fail with it through Effect.fail, so it stays in E for Effect.catchTag to handle"
502
- });
503
- }
504
- };
505
- }
506
- };
507
- var no_throw_default = rule3;
508
-
509
- // effect-channel/no-try-catch.ts
510
- var rule4 = {
511
- meta: {
512
- type: "problem",
513
- docs: { description: "Disallow a try statement with a catch clause, which recovers outside Effect's error channel" }
514
- },
515
- create(context) {
516
- return {
517
- CatchClause(node) {
518
- context.report({
519
- node,
520
- message: "catch recovers outside the error channel: it takes whatever was thrown as unknown, bugs included. Wrap the throwing call in Effect.try or Effect.tryPromise, whose catch maps the cause to a Schema.TaggedError, and recover by tag with Effect.catchTag"
521
- });
522
- }
523
- };
524
- }
525
- };
526
- var no_try_catch_default = rule4;
527
-
528
- // effect-channel/index.ts
419
+ // readability/index.ts
529
420
  var plugin = {
530
- meta: { name: "effect-channel" },
421
+ meta: { name: "readability" },
531
422
  rules: {
532
- "no-error-channel-escape": no_error_channel_escape_default,
533
- "no-throw": no_throw_default,
534
- "no-try-catch": no_try_catch_default,
535
423
  "cognitive-complexity": cognitive_complexity_default
536
424
  }
537
425
  };
538
- var effect_channel_default = plugin;
426
+ var readability_default = plugin;
539
427
  export {
540
- effect_channel_default as default
428
+ readability_default as default
541
429
  };
@@ -1,3 +1,7 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
1
5
  # The commit message lint
2
6
 
3
7
  The shared commitlint config holds each pull request title to conventional commits, and a reader looks it up to wire the lint into a repository's CI.
@@ -10,7 +14,7 @@ It arrives with the kit, since `@commitlint/cli` and `@commitlint/config-convent
10
14
  ## Workflow
11
15
 
12
16
  The lint runs in CI on pull requests, because `jj` never fires a git hook.
13
- `checks-quality generate` writes the workflow whole into `.github/workflows/commitlint.yml`, as [checks-quality](../gates/checks-quality.md) says.
17
+ Each repository owns `.github/workflows/commitlint.yml` and runs the installed `commitlint` binary in a pull request step.
14
18
  The workflow lints with the installed kit's `commitlint.config.js`, so every repository holds titles to the same rules.
15
19
 
16
20
  ## What it lints
@@ -1,3 +1,7 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
1
5
  # The dependency rules
2
6
 
3
7
  The shared dependency-cruiser base holds a repository's imports to a set of rules every repository shares, and a reader looks it up to add a boundary of its own.
@@ -35,7 +39,7 @@ module.exports = {
35
39
 
36
40
  A rule that restates a base name overrides it field by field.
37
41
  That is how an entry point stops being an orphan: redeclare `no-orphans` with the entry added to its `pathNot`.
38
- A repository that declares feature owners spreads the rules `quality.json` compiles to into the same `forbidden`, as [checks-feature-owners](../gates/checks-feature-owners.md#import-boundary) says.
42
+ A repository with an import boundary writes its rule directly under `forbidden`.
39
43
 
40
44
  ## Running it
41
45
 
@@ -59,5 +63,4 @@ jobs:
59
63
 
60
64
  ## Related topics
61
65
 
62
- - [checks-feature-owners](../gates/checks-feature-owners.md)
63
66
  - [Why it is shaped this way](../design.md)
@@ -1,49 +1,48 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
1
5
  # The Effect rules
2
6
 
3
- The oxlint base config and the tsconfig fragment hold a repository's Effect code to its error channel and to Effect-native IO, and a reader looks them up to learn what each rule refuses and how a path opts in.
7
+ The oxlint base and Effect language service check paths a repository writes with Effect.
4
8
 
5
- ## Base config
9
+ ## Oxlint override
6
10
 
7
- The base config, `oxlintrc.json`, loads the `effect-channel` plugin and turns on `effect-channel/no-error-channel-escape`.
8
- That rule refuses `Effect.ignore`, `Effect.ignoreCause`, the `Effect.catchCause` family, and an `Effect.catch` whose handler takes no error or names it `_`.
9
-
10
- Two more rules ship off, because a repository writes only some of its paths in Effect, and code a host loads without `node_modules`, such as a hook bundle or this oxlint plugin, cannot import it:
11
-
12
- - `effect-channel/no-throw` refuses a `throw` statement.
13
- - `effect-channel/no-try-catch` refuses a `try` statement with a `catch` clause, and `try`/`finally` stays allowed.
14
-
15
- Each refusal says what to write instead: a `Schema.TaggedError` failed through `Effect.fail`, a throwing call wrapped in `Effect.try` or `Effect.tryPromise`, and recovery by tag with `Effect.catchTag`.
16
-
17
- ## Effect paths
18
-
19
- A repository turns the rules on for the paths it writes in Effect by declaring those paths in `quality.json` and extending the fragments [checks-quality](../gates/checks-quality.md) generates:
11
+ The shared `oxlintrc.json` loads `effect-channel/no-error-channel-escape` across the tree.
12
+ The repo's `.oxlintrc.json` owns its Effect paths and exemptions in an override:
20
13
 
21
14
  ```json
22
- "sources": {
23
- "effect": { "paths": ["src/**/*.ts"], "exempt": ["src/host/*.ts"] }
15
+ {
16
+ "extends": ["./node_modules/@avi2dg/checks/oxlintrc.json"],
17
+ "plugins": ["typescript", "oxc", "eslint", "import"],
18
+ "overrides": [{
19
+ "files": ["src/**/*.ts"],
20
+ "excludeFiles": ["src/host/*.ts"],
21
+ "plugins": ["typescript", "oxc", "eslint", "import", "node", "promise", "unicorn"],
22
+ "rules": {
23
+ "node/no-sync": "error",
24
+ "oxc/no-async-await": "error",
25
+ "promise/avoid-new": "error",
26
+ "unicorn/no-process-exit": "error",
27
+ "effect-channel/no-throw": "error",
28
+ "effect-channel/no-try-catch": "error"
29
+ }
30
+ }]
24
31
  }
25
32
  ```
26
33
 
27
- The fragment's override carries the kit's Effect rule block, `presets/effect.oxlint.json`.
28
- It holds the two rules above, plus `node/no-sync`, `oxc/no-async-await`, `promise/avoid-new` and `unicorn/no-process-exit`.
29
- Files under `exempt` answer to none of them.
30
-
31
- oxlint resolves `files` against the directory of the config that holds the override, so a config passed with `-c` from outside the repository matches nothing and reports nothing.
32
-
33
- `unicorn/no-process-exit` passes over any file that opens with a shebang.
34
- A repository whose bins open with one bans `process.exit` itself with `no-restricted-properties` in its own `.oxlintrc.json`, as the kit's own repository does.
35
- The preset leaves that rule out because a repository's own `no-restricted-properties` list for the same files would replace it, or be replaced by it.
36
- Sites standing when the declaration lands go in oxlint's own baseline, `oxlint --suppress-all`, so their count can only fall, as [checks-suppressions-ratchet](../gates/checks-suppressions-ratchet.md) holds.
34
+ The kit ships the rule block in `presets/effect.oxlint.json` for copying into the override.
35
+ Each config in an oxlint `extends` chain sets `plugins` explicitly, because an omitted list enables defaults across the chain.
36
+ `unicorn/no-process-exit` does not check a shebang script, so a bin can use `eslint/no-restricted-properties` for `process.exit`.
37
37
 
38
38
  ## Language service
39
39
 
40
- The language service holds the same paths to Effect-native IO through the tsconfig fragment's override, whose severities are `presets/effect.language-service.json`.
41
- `nodeBuiltinImport`, `asyncFunction`, `newPromise` and `extendsNativeError` are all errors.
42
- effect-tsgo keeps the severities `tsconfig.effect.json` sets when a later config in `extends` restates the plugin with only its overrides.
40
+ The repository's `tsconfig.json` holds its Effect override under `compilerOptions.plugins`.
41
+ The override includes the same source paths and excludes the same exempt paths as oxlint.
42
+ The kit ships severity values in `presets/effect.language-service.json` for the override's `options`.
43
+ The kit's `tsconfig.effect.json` keeps the shared language service diagnostics.
43
44
 
44
45
  ## Related topics
45
46
 
46
47
  - [The TypeScript rules](typescript-rules.md)
47
- - [checks-quality](../gates/checks-quality.md)
48
- - [The quality file](quality-file.md)
49
- - [Why it is shaped this way](../design.md)
48
+ - [Native settings](native-settings.md)
@@ -0,0 +1,74 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
5
+ # Native settings
6
+
7
+ A consuming repository puts each setting in the file its tool reads.
8
+
9
+ ## Settings by file
10
+
11
+ | File | Setting | Reader |
12
+ | --- | --- | --- |
13
+ | `.github/workflows/*.yml` | Pull request commands, scheduled commands, runner labels | GitHub Actions and `checks-ci-wiring` |
14
+ | `.oxlintrc.json` | Effect paths, exemptions, file size, function size, statements, cognitive complexity and depth | oxlint |
15
+ | `oxlint-suppressions.json` | The existing violations oxlint suppresses, per file and rule | oxlint and `checks-suppressions-ratchet` |
16
+ | `.jscpd.json` | The `path` and `ignore` globs of the files repetition is measured in | jscpd and `checks-repetition` |
17
+ | `tsconfig.json` | Effect language service scope and severity | TypeScript and Effect language service |
18
+ | `package.json` | `scripts` with the `checks-vendor` arguments in `prepare`, `author` and `contributors` | Bun, `checks-commit-identity` and `checks-vendor` |
19
+ | `bunfig.toml` | Test discovery and quarantine exclusion | Bun and `checks-test-layout` |
20
+ | `.dependency-cruiser.cjs` | Import rules | dependency-cruiser |
21
+ | `stryker.conf.mjs` | Mutation settings | Stryker |
22
+ | Each page under `docs/` | Diátaxis mode in `kind` front matter, and `audience: consumers` on a page that speaks to a consuming repository | `checks-docs` |
23
+
24
+ Every page under `docs/` names its mode in `kind` front matter, whatever directory holds it.
25
+ A page whose front matter sets `audience: consumers` names commands a consuming repository runs, so `checks-docs` does not hold them to this `package.json`.
26
+ Every other living doc names commands this repository runs, and `checks-docs` holds each one to its `package.json`.
27
+
28
+ ## Consumer migration
29
+
30
+ The following table maps the former fields to their owners.
31
+
32
+ | Former field | New owner |
33
+ | --- | --- |
34
+ | `defaultBranch` | Git's `refs/remotes/origin/HEAD`, or the pull request base or event repository in CI |
35
+ | `runsOn`, `gates.ci`, `gates.scheduled` | `runs-on` and `run` steps in `.github/workflows/*.yml` |
36
+ | `gates.lint` | `checks-lint` runs every kit gate |
37
+ | `commitIdentity.authors` | `author` and `contributors` in `package.json` |
38
+ | `sources.production` | `path` and `ignore` in `.jscpd.json` |
39
+ | `size.production`, `size.tests` | File overrides and native size rules in `.oxlintrc.json` |
40
+ | `sources.effect.paths`, `sources.effect.exempt` | Overrides in `.oxlintrc.json` and `tsconfig.json` |
41
+ | `sources.libraries` | `checks-vendor` arguments in the `prepare` script of `package.json` |
42
+ | `docs.pages` | `kind` front matter on each page |
43
+ | `docs.forConsumers` | `audience: consumers` front matter on each page |
44
+ | `features`, `changeSignal`, `agentRules` | No active declarations used these fields |
45
+
46
+ The kit has no general configuration manifest or generated workflow.
47
+ `checks-ci-wiring` requires title lint on opened and synchronized pull requests.
48
+ It also requires lint, build, typecheck and test for each of those scripts that `package.json` defines, and a clean git diff after a build.
49
+ `checks-repetition` runs jscpd with the repository's own `.jscpd.json`, so its `path` and `ignore` globs decide which files are measured.
50
+
51
+ ## Size limits
52
+
53
+ The size rules are plain oxlint rules at `error` in `.oxlintrc.json`, and `bun run lint` enforces them on the whole tree.
54
+ A repository records its existing violations with `oxlint --suppress-all`, which writes them to `oxlint-suppressions.json`.
55
+ `checks-suppressions-ratchet` refuses any count in that file that rises, so the recorded debt only falls.
56
+ The kit's own `.oxlintrc.json` sets these limits for each size override, and a repository may copy them:
57
+
58
+ <!-- generated size-limits: bun run build writes it from .oxlintrc.json, SIZE_RULES in scripts/size-rules.ts and scripts/doc-blocks.ts -->
59
+
60
+ | Limits | oxlint rule | `effect-channel/**/*.ts`, `readability/**/*.ts`, `scripts/**/*.ts` | `tests/**/*.ts` |
61
+ | --- | --- | --- | --- |
62
+ | The most lines a file may hold, blank and comment lines counted | `max-lines` | 400 | 600 |
63
+ | The most lines a function may span, blank and comment lines counted | `max-lines-per-function` | 100 | off |
64
+ | The most statements a function may hold | `max-statements` | 30 | 50 |
65
+ | The highest cognitive complexity a function may reach, a switch counted once | `readability/cognitive-complexity` | 15 | 15 |
66
+ | The deepest a block may nest inside a function | `max-depth` | 4 | 4 |
67
+
68
+ <!-- end generated size-limits -->
69
+
70
+ ## Related topics
71
+
72
+ - [The Effect rules](effect-rules.md)
73
+ - [The CI wiring check](../gates/checks-ci-wiring.md)
74
+ - [The suppressions ratchet](../gates/checks-suppressions-ratchet.md)
@@ -1,3 +1,7 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
1
5
  # The TypeScript rules
2
6
 
3
7
  The oxlint base config holds a repository's TypeScript to a set of rules, and a reader looks it up to learn what each rule refuses and which rules need type information.
package/docs/design.md CHANGED
@@ -1,32 +1,30 @@
1
+ ---
2
+ kind: explanation
3
+ ---
1
4
  # Why it is shaped this way
2
5
 
3
6
  Each entry below is a choice in the kit's shape and the constraint that forced it.
4
7
 
5
8
  - Every config in an oxlint `extends` chain brings its own `plugins`, and one that sets none brings oxlint's default plugins, whose category rules the base's `categories` then turn on across the tree.
6
9
  `rules`, `categories` and `jsPlugins` inherit as expected.
7
- That is why the consumer snippet restates `plugins` and nothing else, and why the generated fragment always sets them.
10
+ That is why the consumer config and each override restate `plugins`.
8
11
  - `node_modules/` is excluded through the consumer's `.gitignore`, not `ignorePatterns`: oxlint still walks the installed package when only `ignorePatterns` names it.
9
12
  - `files` in package.json is the published surface: `tests/`, `AGENTS.md` and the `.ts` plugin source never reach an install.
10
13
  npm adds `package.json`, `README` and `LICENSE` to the tarball whatever `files` says.
11
14
  `bun pm pack` builds the same tarball the registry serves, which is what the packed-tarball consumer e2e test installs.
12
- - The plugin ships compiled as `dist/index.js`, built with `bun build effect-channel/index.ts --outdir dist --target node --format esm`.
15
+ - Each plugin ships compiled under `dist/`, built with `bun build <name>/index.ts --outdir dist/<name> --target node --format esm`.
13
16
  Node refuses to type-strip a `.ts` plugin under `node_modules`, so the `.ts` source would fail to load from an installed package.
14
- - `featureRules` ships compiled as `dist/feature-rules.js` for the same reason, with `effect` left out of the bundle so it resolves the consumer's own copy.
15
- dependency-cruiser uses a config's export as it is and never awaits it, so the declaration decodes synchronously, and `quality.json` exempts that one file from the Effect rules.
16
- - `checks-size-budget` writes the head commit's files to a temporary directory and runs oxlint there.
17
- Its configuration loads the kit's own plugin bundle for the complexity rule and turns every category off.
18
- The consumer's own `.oxlintrc.json`, its ignore files and its other rules never reach the count.
19
- - `checks-size-budget` ratchets against the base of the range rather than a committed baseline such as `oxlint-suppressions.json`.
20
- A suppression file stores a count of sites per file and rule, and `max-lines` reports a file once however long it grows, so the count stays at one while the file doubles.
21
- The gate sums how far each site runs over its limit instead, which grows with the file.
22
- The base commit already holds that sum, so nothing is generated, committed or pruned.
23
- - `checks-repetition` writes the production files of both ends of the range to temporary directories and runs jscpd in each, so the consumer's `.jscpd.json` and ignore files never reach the count.
17
+ - The size rules are plain oxlint rules at `error` in `.oxlintrc.json`, and `bun run lint` enforces them on the whole tree.
18
+ A repository records its existing violations with `oxlint --suppress-all`, and `checks-suppressions-ratchet` refuses any count that rises.
19
+ The kit runs no size script of its own, since restating how oxlint reads its config and compares sites left corners the native rules never had.
20
+ The suppression count lets a file or function already over its limit grow without a new site, which was accepted in exchange for dropping that script, and each repository is to work its counts down to zero.
21
+ - `checks-repetition` writes both ends of the range to temporary directories and runs jscpd in each with the head's `.jscpd.json`, so its `path` and `ignore` globs decide the files at both ends.
24
22
  It compares each file's count of repeated lines rather than using jscpd's `--baseline-from-ref`.
25
23
  That flag reports a repeated block as new once its text changes, so a change that shortens a grandfathered block would fail.
26
24
  - `dist/` is committed.
27
25
  No `prepack` or `prepublishOnly` builds it, so a publish ships whatever bundle the publishing worktree holds.
28
26
  Rebuild it after pulling with `bun run build`.
29
- `bun run build` also emits `quality.schema.json`, `templates/` and `CHANGELOG.md`, which are committed the same way.
27
+ `bun run build` also emits `templates/` and `CHANGELOG.md`, which are committed the same way.
30
28
  CI runs `git diff --exit-code` over the whole tree after the build, because a test that compares a generated file with its source passes on the copy the build just rewrote.
31
29
  - `CHANGELOG.md` is generated, so a release commit carries it and the tarball ships it.
32
30
  The commit that bumps package.json `version` closes its release, and the `v*` tag goes on that commit, so commits merged after it wait for the next release.
@@ -36,12 +34,13 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
36
34
  A section keeps the date it was written with, since the squash merge that lands the release commit may fall on another day.
37
35
  Entries come from commit subjects, the squash-merged pull request titles commitlint holds to the conventional format.
38
36
  The bodies are the branch's own messages, which nothing lints.
39
- The release path needs no `contents: write`: the changelog arrives in the release commit's pull request, not from a workflow that pushes.
40
- - `quality.json` is JSON, not TOML or a TypeScript module: a bun bin, a hook running without `node_modules`, a `.cjs` or `.mjs` config and `jq` all parse it with nothing installed, and nobody runs a repository's own code to learn its policy.
41
- It holds declarations only.
42
- The kit's bins read it directly.
43
- oxlint and tsc read nothing but their own JSON, so they extend generated fragments, which `checks-quality --check` holds to the declarations.
44
- - `quality.json` refuses a key its schema does not name, so a kit that cannot enforce a newer key refuses it rather than let the repository believe it enforced.
37
+ The changelog still arrives in the release commit's pull request, not from a workflow that pushes.
38
+ The release path writes to the repository only through the `github-release` job's `contents: write`, which creates or updates the GitHub release from the tag's `CHANGELOG.md` section.
39
+ - Workflow YAML owns CI execution, and the kit owns the required commands, but a repository is held only to the commands its own `package.json` can run.
40
+ `checks-ci-wiring` requires `bun run` with each of `lint`, `build`, `typecheck` and `test` that `package.json` defines as a script, `git diff --exit-code` when a `build` script exists, and commitlint always.
41
+ The target branch comes from git's own record in `refs/remotes/origin/HEAD`, or in CI from the pull request base or the default branch GitHub's event names, and never from a guess at the workflow files, and `checks-lint` starts its local range from the same branch.
42
+ Oxlint and TypeScript read their own overrides directly, so the consumer can change a rule where its tool reads it.
43
+ The Effect scopes occur in two native files, and the installed consumer test checks both independently.
45
44
  - The base parses with swc because typescript 7, which is tsgo, has no compiler API for dependency-cruiser to use.
46
45
  Without `@swc/core` installed the cruise silently skips every `.ts` file, so this repo's test asserts its own TypeScript is cruised.
47
46
  - `bunfig.toml` has no `extends` and no include: bun ignores an unknown top-level key in silence, so a preset cannot be inherited and the consumer's copy is compared key by key against the installed one instead.
@@ -52,11 +51,11 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
52
51
  `@effect/platform-node-shared` is a direct dependency at the same exact version only to pin it: `@effect/platform-bun` asks for it with a `^` range, and a newer rc peers on a newer `effect` than consumers install, so all three move together.
53
52
  - Each runnable script ships a `checks-` bin entry, so consumer `package.json` scripts call the short name, which the package manager puts on `PATH` only there.
54
53
  A shell runs it through `bun run`, which never falls back to the registry the way `bunx` does.
55
- The `.ts` checks keep a `bun` shebang, which needs no build step and no `dist/` entry, unlike the oxlint plugin that node loads.
54
+ The `.ts` checks keep a `bun` shebang, which needs no build step and no `dist/` entry, unlike the oxlint plugins that node loads.
56
55
  - `checks-lint` runs each gate as its own bin in a child process rather than importing it, so a gate behaves the same called alone or through the entry point, and `lint-coverage.sh` stays a shell script.
57
56
  The gates run one at a time with their output passed straight through, so each report reads whole and in the table's order.
58
- - A gate selection is checked against the repository's contents rather than trusted, so it cannot skip a gate that applies.
59
- ci-wiring does that check, which is why a selection without it, or without another gate that applies everywhere, is refused as `checks-lint` reads it: nothing would check the selection otherwise.
57
+ - `checks-lint` determines applicable gates from tracked files instead of accepting a repository selection.
58
+ A TypeScript gate starts running as soon as TypeScript source is tracked.
60
59
  - `checks-test` runs bun itself rather than reading a report some other run left: a skip taken only on CI is visible only in CI's own run, and an earlier run's report may be stale or narrowed.
61
60
  It reads the JUnit report bun writes to a temporary directory, since bun has no other per-test output meant for a program.
62
61
  - `checks-ci-wiring` runs inside `lint`, not in a workflow of its own: deleting the step that runs a check is the violation it catches, so the local `lint` is where it has to fail.
@@ -75,9 +74,9 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
75
74
  That is the entry-point and layer recipe under Boundaries in [The dependency rules](configs/dependency-rules.md).
76
75
  - The templates in `templates/` are rendered from `scripts/doc-templates.ts`, the spec `checks-docs` reads.
77
76
  A template written by hand beside the check agrees with it only until someone edits one of them.
78
- - A page's Diátaxis mode is declared in `quality.json` rather than read from the page.
79
- Whether a page teaches, walks a task, describes or explains is a judgment no program makes, so the repository states it once and the check holds the page to it.
80
- - `checks-docs` holds a doc file to its template when a change touches it, the way `checks-size-budget` holds a file to its budget.
77
+ - A page's Diátaxis mode comes from `kind` front matter on the page, whatever directory holds it.
78
+ The repository makes the judgment beside the page, and the check holds it to that template.
79
+ - `checks-docs` holds a doc file to its template when a change touches it, the way `checks-comment-gate` judges the comments a change adds.
81
80
  A repository adopts the templates as its files change, and an untouched file is listed as advisory rather than failing a change that never read it.
82
81
  - A task heading is verb first, and review holds it there rather than the check.
83
82
  No word list tells `Test layout` from `Test the layout`, and a check that passes the noun is worse than none.
@@ -95,8 +94,8 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
95
94
  A reference goes stale when the code it names moves far more often than when its own line is edited, so a gate on edited lines alone would miss the usual break.
96
95
  - A path under a top directory the repository lacks names a file in another repository, such as a consumer's, and no program tells that from a typo.
97
96
  A directory the range deletes still counts as this repository's, so a path under it reads as stale rather than foreign.
98
- - The command check passes over the docs a repository declares under `docs.forConsumers`.
99
- This kit's README and reference pages speak to a consuming repository, whose scripts are not this one's, and no program tells an example for a consumer from an instruction for a contributor.
97
+ - The command check passes over a page whose front matter sets `audience: consumers`.
98
+ Such a page speaks to a consuming repository, whose scripts are not this one's.
100
99
 
101
100
  ## Related topics
102
101
 
@@ -1,3 +1,7 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
1
5
  # checks-backtest
2
6
 
3
7
  `checks-backtest` is the report of what the comment check would have refused at each recent commit, and a reader looks it up to measure a repository's own history before adopting the check.