@avi2dg/checks 0.14.0 → 0.16.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/CHANGELOG.md +17 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +3 -1
- package/dist/feature-rules.js +145 -69
- package/docs/configs/quality-file.md +4 -3
- package/docs/design.md +25 -1
- package/docs/gates/checks-docs.md +83 -6
- package/docs/gates/checks-lint.md +4 -3
- package/docs/gates/checks-quality.md +1 -1
- package/docs/gates/checks-repetition.md +72 -0
- package/docs/gates/checks-size-budget.md +57 -14
- package/package.json +11 -1
- package/quality.schema.json +75 -18
- package/scripts/comment-gate.ts +5 -48
- package/scripts/doc-references.ts +190 -0
- package/scripts/doc-rules.ts +2 -2
- package/scripts/doc-snapshot.ts +70 -0
- package/scripts/docs.ts +100 -24
- package/scripts/gates.ts +1 -0
- package/scripts/git.ts +83 -1
- package/scripts/prose-matchers.ts +380 -0
- package/scripts/quality-file.ts +18 -17
- package/scripts/quality.ts +2 -2
- package/scripts/repetition.ts +161 -0
- package/scripts/size-budget.ts +187 -87
- package/scripts/size-rules.ts +117 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
|
|
4
4
|
|
|
5
|
+
## 0.16.0
|
|
6
|
+
|
|
7
|
+
Released 2026-09-25.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **scripts:** add checks-repetition to hold new repetition in production code (#46)
|
|
12
|
+
- **scripts:** add the recommended size limits, a tests budget and an overrun ratchet (#45)
|
|
13
|
+
|
|
14
|
+
## 0.15.0
|
|
15
|
+
|
|
16
|
+
Released 2026-09-25.
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
- **scripts:** hold living docs to prose rules and resolvable references in checks-docs (#42)
|
|
21
|
+
|
|
5
22
|
## 0.14.0
|
|
6
23
|
|
|
7
24
|
Released 2026-09-25.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -81,7 +81,7 @@ To place a change:
|
|
|
81
81
|
|
|
82
82
|
This repository holds itself to the kit, with two exceptions of its own.
|
|
83
83
|
Its `.dependency-cruiser.cjs` redeclares `no-orphans` with the plugin entry added to its `pathNot`.
|
|
84
|
-
Its `.oxlintrc.json` lifts `effect-channel/no-throw` from `scripts/comment-matchers.ts`,
|
|
84
|
+
Its `.oxlintrc.json` lifts `effect-channel/no-throw` from `scripts/comment-matchers.ts`, whose synchronous `refused()` a host loads without `node_modules`.
|
|
85
85
|
|
|
86
86
|
## Related topics
|
|
87
87
|
|
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ A repository declares what it opts into once, in `quality.json`, and each check
|
|
|
16
16
|
- `@swc/core` 1.16.2
|
|
17
17
|
- `dependency-cruiser` 18.4.0
|
|
18
18
|
- `effect` 4.0.0-rc.115
|
|
19
|
+
- `jscpd` 5.3.2
|
|
19
20
|
- `oxlint` 1.83.0
|
|
20
21
|
- `oxlint-tsgolint` 7.0.2002
|
|
21
22
|
|
|
@@ -30,7 +31,7 @@ To consume the kit from a repository:
|
|
|
30
31
|
<!-- generated install: bun run build writes it from package.json and scripts/doc-blocks.ts -->
|
|
31
32
|
|
|
32
33
|
```sh
|
|
33
|
-
bun add -d @avi2dg/checks @effect/tsgo@0.45.0 @swc/core@1.16.2 dependency-cruiser@18.4.0 effect@4.0.0-rc.115 oxlint@1.83.0 oxlint-tsgolint@7.0.2002 typescript@7.0.2
|
|
34
|
+
bun add -d @avi2dg/checks @effect/tsgo@0.45.0 @swc/core@1.16.2 dependency-cruiser@18.4.0 effect@4.0.0-rc.115 jscpd@5.3.2 oxlint@1.83.0 oxlint-tsgolint@7.0.2002 typescript@7.0.2
|
|
34
35
|
```
|
|
35
36
|
|
|
36
37
|
<!-- end generated install -->
|
|
@@ -120,6 +121,7 @@ A repository leaves out a gate that does not apply to it through `gates.lint`, a
|
|
|
120
121
|
| [`checks-docs`](docs/gates/checks-docs.md) | the range | every repository |
|
|
121
122
|
| [`checks-quality`](docs/gates/checks-quality.md) | the working tree | a repository tracking `quality.json` |
|
|
122
123
|
| [`checks-size-budget`](docs/gates/checks-size-budget.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
124
|
+
| [`checks-repetition`](docs/gates/checks-repetition.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
123
125
|
| [`checks-feature-owners`](docs/gates/checks-feature-owners.md) | the range | a repository tracking `*.ts` or `*.tsx` |
|
|
124
126
|
|
|
125
127
|
<!-- end generated gates -->
|
package/dist/feature-rules.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// scripts/feature-rules.ts
|
|
2
|
-
import { Schema as
|
|
2
|
+
import { Schema as Schema4 } from "effect";
|
|
3
3
|
|
|
4
4
|
// scripts/quality-file.ts
|
|
5
|
-
import { Console, Effect, FileSystem, JsonSchema, Path, Schema as
|
|
5
|
+
import { Console, Effect, FileSystem, JsonSchema, Path, Schema as Schema3 } from "effect";
|
|
6
6
|
|
|
7
7
|
// scripts/gates.ts
|
|
8
8
|
import { Schema } from "effect";
|
|
@@ -20,6 +20,7 @@ var KIT_GATES = [
|
|
|
20
20
|
{ bin: "checks-docs", script: "docs.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
|
|
21
21
|
{ bin: "checks-quality", script: "quality.ts", reads: "tree", args: ["--check"], appliesTo: QUALITY_DECLARATION },
|
|
22
22
|
{ bin: "checks-size-budget", script: "size-budget.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
23
|
+
{ bin: "checks-repetition", script: "repetition.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
|
|
23
24
|
{ bin: "checks-feature-owners", script: "feature-owners.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE }
|
|
24
25
|
];
|
|
25
26
|
var UNCONDITIONAL = KIT_GATES.filter((gate) => gate.appliesTo === EVERY_REPOSITORY).map((gate) => gate.bin);
|
|
@@ -31,81 +32,153 @@ var LintGates = Schema.Array(Schema.Literals(KIT_GATES.map((gate) => gate.bin)))
|
|
|
31
32
|
return `checks-lint must run ${missing.join(", ")}, which ${verb} to ${EVERY_REPOSITORY}`;
|
|
32
33
|
}, { toJsonSchema: () => ({ allOf: UNCONDITIONAL.map((bin) => ({ contains: { const: bin } })) }) }));
|
|
33
34
|
|
|
35
|
+
// scripts/size-rules.ts
|
|
36
|
+
import { Schema as Schema2 } from "effect";
|
|
37
|
+
var TESTS_DIRECTORY = "tests";
|
|
38
|
+
var COUNTED = { skipBlankLines: false, skipComments: false };
|
|
39
|
+
var FILE_LINES = {
|
|
40
|
+
key: "fileLines",
|
|
41
|
+
rule: "max-lines",
|
|
42
|
+
options: COUNTED,
|
|
43
|
+
measured: /has too many lines \((\d+)\)/,
|
|
44
|
+
limits: "The most lines a file may hold, blank and comment lines counted"
|
|
45
|
+
};
|
|
46
|
+
var FUNCTION_LINES = {
|
|
47
|
+
key: "functionLines",
|
|
48
|
+
rule: "max-lines-per-function",
|
|
49
|
+
options: COUNTED,
|
|
50
|
+
measured: /has too many lines \((\d+)\)/,
|
|
51
|
+
limits: "The most lines a function may span, blank and comment lines counted"
|
|
52
|
+
};
|
|
53
|
+
var STATEMENTS = {
|
|
54
|
+
key: "statements",
|
|
55
|
+
rule: "max-statements",
|
|
56
|
+
options: {},
|
|
57
|
+
measured: /has too many statements \((\d+)\)/,
|
|
58
|
+
limits: "The most statements a function may hold"
|
|
59
|
+
};
|
|
60
|
+
var COMPLEXITY = {
|
|
61
|
+
key: "complexity",
|
|
62
|
+
rule: "complexity",
|
|
63
|
+
options: { variant: "modified" },
|
|
64
|
+
measured: /has a complexity of (\d+)/,
|
|
65
|
+
limits: "The highest cyclomatic complexity a function may reach, a switch counted once"
|
|
66
|
+
};
|
|
67
|
+
var DEPTH = {
|
|
68
|
+
key: "depth",
|
|
69
|
+
rule: "max-depth",
|
|
70
|
+
options: {},
|
|
71
|
+
measured: /nested too deeply \((\d+)\)/,
|
|
72
|
+
limits: "The deepest a block may nest inside a function"
|
|
73
|
+
};
|
|
74
|
+
var APPLIES = ["ratchet", "all"];
|
|
75
|
+
var SIZE_DEFAULTS = {
|
|
76
|
+
applies: "ratchet",
|
|
77
|
+
production: { fileLines: 400, functionLines: 100, statements: 30, complexity: 15, depth: 4 },
|
|
78
|
+
tests: { fileLines: 600, statements: 50, complexity: 15, depth: 4 }
|
|
79
|
+
};
|
|
80
|
+
var Limit = Schema2.Int.check(Schema2.isGreaterThan(0));
|
|
81
|
+
function limit({ limits }, fallback) {
|
|
82
|
+
return Schema2.optionalKey(Limit.annotate({ description: `${limits}; ${fallback} when absent` }));
|
|
83
|
+
}
|
|
84
|
+
var ProductionBudget = Schema2.Struct({
|
|
85
|
+
fileLines: limit(FILE_LINES, SIZE_DEFAULTS.production.fileLines),
|
|
86
|
+
functionLines: limit(FUNCTION_LINES, SIZE_DEFAULTS.production.functionLines),
|
|
87
|
+
statements: limit(STATEMENTS, SIZE_DEFAULTS.production.statements),
|
|
88
|
+
complexity: limit(COMPLEXITY, SIZE_DEFAULTS.production.complexity),
|
|
89
|
+
depth: limit(DEPTH, SIZE_DEFAULTS.production.depth)
|
|
90
|
+
});
|
|
91
|
+
var TestBudget = Schema2.Struct({
|
|
92
|
+
fileLines: limit(FILE_LINES, SIZE_DEFAULTS.tests.fileLines),
|
|
93
|
+
statements: limit(STATEMENTS, SIZE_DEFAULTS.tests.statements),
|
|
94
|
+
complexity: limit(COMPLEXITY, SIZE_DEFAULTS.tests.complexity),
|
|
95
|
+
depth: limit(DEPTH, SIZE_DEFAULTS.tests.depth)
|
|
96
|
+
});
|
|
97
|
+
var Size = Schema2.Struct({
|
|
98
|
+
applies: Schema2.optionalKey(Schema2.Literals(APPLIES).annotate({
|
|
99
|
+
description: `Which production and test files the budget holds: ratchet, the ones a range adds or changes, to no more overrun per rule than at the range's base; all, every one. ${SIZE_DEFAULTS.applies} when absent. The rest are listed as advisory`,
|
|
100
|
+
message: "Expected ratchet or all, and ratchet replaces changed"
|
|
101
|
+
})),
|
|
102
|
+
production: Schema2.optionalKey(ProductionBudget.annotate({
|
|
103
|
+
description: `The budget of the files under sources.production, and of the files listed as advisory outside ${TESTS_DIRECTORY}/`
|
|
104
|
+
})),
|
|
105
|
+
tests: Schema2.optionalKey(TestBudget.annotate({ description: `The budget of the files under ${TESTS_DIRECTORY}/, which sets no limit on a function's lines` }))
|
|
106
|
+
}).annotate({
|
|
107
|
+
description: "The size budget oxlint holds production and test files to, read by checks-size-budget",
|
|
108
|
+
messageUnexpectedKey: "Expected only applies, production and tests, since each limit is set inside production or tests"
|
|
109
|
+
});
|
|
110
|
+
|
|
34
111
|
// scripts/quality-file.ts
|
|
35
112
|
var SEGMENT = String.raw`(?!\.\.?(?:/|$))(?:\*\*|(?:[\w.@+-]|\*(?!\*))+)`;
|
|
36
113
|
var FILE = String.raw`(?:[\w.@+-]|\*(?!\*))*\.\w+`;
|
|
37
|
-
var PathGlob =
|
|
114
|
+
var PathGlob = Schema3.String.check(Schema3.isPattern(new RegExp(`^${SEGMENT}(?:/${SEGMENT})*/${FILE}$`), {
|
|
38
115
|
expected: "a glob from the repository root such as src/**/*.ts: a directory first, * within a segment, ** as a whole one, a file name with an extension last"
|
|
39
116
|
})).annotate({
|
|
40
117
|
identifier: "PathGlob",
|
|
41
118
|
description: "A glob from the repository root that oxlint, the Effect language service and git read alike: a directory first, * within a segment, ** as a whole one, a file name with an extension last, and no braces, ?, [ or leading ./"
|
|
42
119
|
});
|
|
120
|
+
var DocGlob = Schema3.String.check(Schema3.isPattern(new RegExp(`^(?:${SEGMENT}/)*${FILE}$`), {
|
|
121
|
+
expected: "a glob from the repository root such as README.md or docs/**/*.md: * within a segment, ** as a whole one, a file name with an extension last"
|
|
122
|
+
})).annotate({
|
|
123
|
+
identifier: "DocGlob",
|
|
124
|
+
description: "A glob from the repository root that checks-docs reads: * within a segment, ** as a whole one, a file name with an extension last"
|
|
125
|
+
});
|
|
43
126
|
var LITERAL_SEGMENT = String.raw`(?!\.\.?(?:/|$))[\w.@+-]+`;
|
|
44
|
-
var DirectoryPath =
|
|
127
|
+
var DirectoryPath = Schema3.String.check(Schema3.isPattern(new RegExp(`^${LITERAL_SEGMENT}(?:/${LITERAL_SEGMENT})*$`), {
|
|
45
128
|
expected: "a directory from the repository root such as src/billing, with no glob and no trailing slash"
|
|
46
129
|
})).annotate({ identifier: "DirectoryPath" });
|
|
47
|
-
var FilePath =
|
|
130
|
+
var FilePath = Schema3.String.check(Schema3.isPattern(new RegExp(`^(?:${LITERAL_SEGMENT}/)*[\\w.@+-]*\\.\\w+$`), {
|
|
48
131
|
expected: "a file from the repository root such as src/billing/index.ts, with no glob"
|
|
49
132
|
})).annotate({ identifier: "FilePath" });
|
|
50
133
|
var PROOF_DIRECTORY = "tests/e2e/";
|
|
51
|
-
var ProofPath =
|
|
134
|
+
var ProofPath = Schema3.String.check(Schema3.isPattern(new RegExp(`^${PROOF_DIRECTORY}(?:${LITERAL_SEGMENT}/)*[\\w.@+-]+\\.test\\.tsx?$`), {
|
|
52
135
|
expected: `a test file under ${PROOF_DIRECTORY} such as ${PROOF_DIRECTORY}billing.test.ts`
|
|
53
136
|
})).annotate({ identifier: "ProofPath" });
|
|
54
|
-
var Command =
|
|
55
|
-
var RuleName =
|
|
56
|
-
var Identity =
|
|
137
|
+
var Command = Schema3.NonEmptyString.annotate({ identifier: "Command" });
|
|
138
|
+
var RuleName = Schema3.String.check(Schema3.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { expected: "a Rule name in kebab case" })).annotate({ identifier: "RuleName" });
|
|
139
|
+
var Identity = Schema3.Struct({ name: Schema3.NonEmptyString, email: Schema3.NonEmptyString }).annotate({
|
|
57
140
|
identifier: "Identity"
|
|
58
141
|
});
|
|
59
|
-
var CommitIdentity =
|
|
60
|
-
authors:
|
|
142
|
+
var CommitIdentity = Schema3.Struct({
|
|
143
|
+
authors: Schema3.NonEmptyArray(Identity).annotate({
|
|
61
144
|
description: "The identities allowed to author and commit, in place of the kit's default owner"
|
|
62
145
|
})
|
|
63
146
|
});
|
|
64
|
-
var Gates =
|
|
65
|
-
ci:
|
|
147
|
+
var Gates = Schema3.Struct({
|
|
148
|
+
ci: Schema3.optionalKey(Schema3.NonEmptyArray(Command).annotate({
|
|
66
149
|
description: "The commands CI runs on every pull request to the default branch, each one plain command"
|
|
67
150
|
})),
|
|
68
|
-
scheduled:
|
|
69
|
-
lint:
|
|
151
|
+
scheduled: Schema3.optionalKey(Schema3.Array(Command).annotate({ description: "The commands a cron-scheduled workflow runs" })),
|
|
152
|
+
lint: Schema3.optionalKey(LintGates)
|
|
70
153
|
});
|
|
71
|
-
var EffectSources =
|
|
72
|
-
paths:
|
|
154
|
+
var EffectSources = Schema3.Struct({
|
|
155
|
+
paths: Schema3.NonEmptyArray(PathGlob).annotate({
|
|
73
156
|
description: "Where source is written in Effect, held to the Effect rules of oxlint and the language service"
|
|
74
157
|
}),
|
|
75
|
-
exempt:
|
|
158
|
+
exempt: Schema3.optionalKey(Schema3.Array(PathGlob).annotate({ description: "Files under paths the Effect rules pass over" }))
|
|
76
159
|
});
|
|
77
|
-
var Sources =
|
|
78
|
-
production:
|
|
79
|
-
effect:
|
|
160
|
+
var Sources = Schema3.Struct({
|
|
161
|
+
production: Schema3.optionalKey(Schema3.Array(PathGlob).annotate({ description: "The source the repository ships, as against tests and tooling" })),
|
|
162
|
+
effect: Schema3.optionalKey(EffectSources)
|
|
80
163
|
});
|
|
81
|
-
var
|
|
82
|
-
|
|
83
|
-
fileLines: LineBudget.annotate({ description: "The most lines a file may hold, blank and comment lines counted" }),
|
|
84
|
-
functionLines: LineBudget.annotate({
|
|
85
|
-
description: "The most lines a function may span, blank and comment lines counted"
|
|
86
|
-
}),
|
|
87
|
-
applies: Schema2.Literals(["changed", "all"]).annotate({
|
|
88
|
-
description: "Which production files the budget holds: changed, the ones a range adds or changes; all, every one. The rest are reported as advisory"
|
|
89
|
-
})
|
|
90
|
-
});
|
|
91
|
-
var Feature = Schema2.Struct({
|
|
92
|
-
name: Schema2.String.check(Schema2.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { expected: "a feature name in kebab case" })).annotate({ description: "The owner the dependency rule and the change signal name" }),
|
|
164
|
+
var Feature = Schema3.Struct({
|
|
165
|
+
name: Schema3.String.check(Schema3.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { expected: "a feature name in kebab case" })).annotate({ description: "The owner the dependency rule and the change signal name" }),
|
|
93
166
|
root: DirectoryPath.annotate({ description: "The directory the feature owns" }),
|
|
94
|
-
entries:
|
|
167
|
+
entries: Schema3.NonEmptyArray(FilePath).annotate({
|
|
95
168
|
description: "The files under root that code outside it imports the feature through"
|
|
96
169
|
}),
|
|
97
|
-
allowFrom:
|
|
170
|
+
allowFrom: Schema3.optionalKey(Schema3.Array(PathGlob).annotate({
|
|
98
171
|
description: "Files outside root that may import past its entries, such as a CLI or a harness; tests/ always may"
|
|
99
172
|
})),
|
|
100
173
|
proof: ProofPath.annotate({ description: "The end-to-end test that imports one of entries" })
|
|
101
|
-
}).check(
|
|
174
|
+
}).check(Schema3.makeFilter(({ root, entries }) => {
|
|
102
175
|
const outside = entries.filter((entry) => !entry.startsWith(`${root}/`));
|
|
103
176
|
return outside.length === 0 || `lists ${outside.join(", ")} among its entries, outside its root ${root}`;
|
|
104
177
|
}));
|
|
105
178
|
function nests(outer, inner) {
|
|
106
179
|
return outer === inner || inner.startsWith(`${outer}/`);
|
|
107
180
|
}
|
|
108
|
-
var Features =
|
|
181
|
+
var Features = Schema3.Array(Feature).check(Schema3.makeFilter((features) => {
|
|
109
182
|
const names = features.map((feature) => feature.name);
|
|
110
183
|
const repeated = names.filter((name, index) => names.indexOf(name) !== index);
|
|
111
184
|
if (repeated.length > 0)
|
|
@@ -117,71 +190,74 @@ var Features = Schema2.Array(Feature).check(Schema2.makeFilter((features) => {
|
|
|
117
190
|
}
|
|
118
191
|
return true;
|
|
119
192
|
}));
|
|
120
|
-
var AgentRules =
|
|
121
|
-
on:
|
|
122
|
-
off:
|
|
123
|
-
}).check(
|
|
193
|
+
var AgentRules = Schema3.Struct({
|
|
194
|
+
on: Schema3.optionalKey(Schema3.Array(RuleName).annotate({ description: "Catalogued Rules switched on here" })),
|
|
195
|
+
off: Schema3.optionalKey(Schema3.Array(RuleName).annotate({ description: "Catalogued Rules switched off here" }))
|
|
196
|
+
}).check(Schema3.makeFilter(({ on = [], off = [] }) => {
|
|
124
197
|
const both = on.filter((rule) => off.includes(rule));
|
|
125
198
|
return both.length === 0 || `switches ${both.join(", ")} both on and off`;
|
|
126
199
|
}));
|
|
127
|
-
var pagesIn = (mode) =>
|
|
128
|
-
var Docs =
|
|
129
|
-
pages:
|
|
200
|
+
var pagesIn = (mode) => Schema3.optionalKey(Schema3.Array(PathGlob).annotate({ description: `The pages written as ${mode}` }));
|
|
201
|
+
var Docs = Schema3.Struct({
|
|
202
|
+
pages: Schema3.optionalKey(Schema3.Struct({
|
|
130
203
|
tutorial: pagesIn("a tutorial, which teaches by building one thing"),
|
|
131
204
|
"how-to": pagesIn("a how-to, which walks one task"),
|
|
132
205
|
reference: pagesIn("reference, which describes a thing to be looked up"),
|
|
133
206
|
explanation: pagesIn("an explanation, which says why")
|
|
134
207
|
}).annotate({
|
|
135
208
|
description: "The Diátaxis mode of each page, whose template checks-docs holds the page to; a page under docs/ needs one"
|
|
209
|
+
})),
|
|
210
|
+
forConsumers: Schema3.optionalKey(Schema3.Array(DocGlob).annotate({
|
|
211
|
+
description: "The living docs that speak to a repository installing this one, whose bun run commands checks-docs does not hold to this package.json"
|
|
136
212
|
}))
|
|
137
213
|
});
|
|
138
|
-
var Quality =
|
|
139
|
-
$schema:
|
|
140
|
-
defaultBranch:
|
|
141
|
-
gates:
|
|
142
|
-
commitIdentity:
|
|
143
|
-
sources:
|
|
144
|
-
size:
|
|
145
|
-
features:
|
|
214
|
+
var Quality = Schema3.Struct({
|
|
215
|
+
$schema: Schema3.optionalKey(Schema3.String),
|
|
216
|
+
defaultBranch: Schema3.optionalKey(Schema3.NonEmptyString.annotate({ description: "The branch pull requests merge into; main when absent" })),
|
|
217
|
+
gates: Schema3.optionalKey(Gates),
|
|
218
|
+
commitIdentity: Schema3.optionalKey(CommitIdentity),
|
|
219
|
+
sources: Schema3.optionalKey(Sources),
|
|
220
|
+
size: Schema3.optionalKey(Size),
|
|
221
|
+
features: Schema3.optionalKey(Features.annotate({
|
|
146
222
|
description: "The feature owners dependency-cruiser holds to their entries and checks-feature-owners maps a change to"
|
|
147
223
|
})),
|
|
148
|
-
changeSignal:
|
|
224
|
+
changeSignal: Schema3.optionalKey(Schema3.Literal("advisory").annotate({
|
|
149
225
|
description: "Report which feature owners a change touches, without failing on it"
|
|
150
226
|
})),
|
|
151
|
-
agentRules:
|
|
152
|
-
docs:
|
|
227
|
+
agentRules: Schema3.optionalKey(AgentRules),
|
|
228
|
+
docs: Schema3.optionalKey(Docs.annotate({ description: "What checks-docs reads to map a doc file to its template" }))
|
|
153
229
|
}).annotate({
|
|
154
230
|
title: QUALITY_FILE,
|
|
155
231
|
description: "What a repository has opted into from @avi2dg/checks, read by its bins and agent Rule selection"
|
|
156
|
-
}).check(
|
|
232
|
+
}).check(Schema3.makeFilter(({ size, sources }) => size === undefined || (sources?.production ?? []).length > 0 || "declares size, which holds no production file without sources.production", {
|
|
157
233
|
toJsonSchema: () => ({
|
|
158
234
|
if: { required: ["size"] },
|
|
159
235
|
then: { required: ["sources"], properties: { sources: { required: ["production"], properties: { production: { minItems: 1 } } } } }
|
|
160
236
|
})
|
|
161
|
-
}),
|
|
237
|
+
}), Schema3.makeFilter(({ changeSignal, features = [] }) => changeSignal === undefined || features.length > 0 || "declares changeSignal, which maps a change to no owner without features", {
|
|
162
238
|
toJsonSchema: () => ({
|
|
163
239
|
if: { required: ["changeSignal"] },
|
|
164
240
|
then: { required: ["features"], properties: { features: { minItems: 1 } } }
|
|
165
241
|
})
|
|
166
242
|
}));
|
|
167
|
-
var LegacyManifest =
|
|
168
|
-
ciWiring:
|
|
169
|
-
gates:
|
|
170
|
-
scheduled:
|
|
171
|
-
lintGates:
|
|
172
|
-
defaultBranch:
|
|
243
|
+
var LegacyManifest = Schema3.Struct({
|
|
244
|
+
ciWiring: Schema3.optionalKey(Schema3.Struct({
|
|
245
|
+
gates: Schema3.optionalKey(Schema3.NonEmptyArray(Command)),
|
|
246
|
+
scheduled: Schema3.optionalKey(Schema3.Array(Command)),
|
|
247
|
+
lintGates: Schema3.optionalKey(LintGates),
|
|
248
|
+
defaultBranch: Schema3.optionalKey(Schema3.NonEmptyString)
|
|
173
249
|
})),
|
|
174
|
-
commitIdentity:
|
|
250
|
+
commitIdentity: Schema3.optionalKey(CommitIdentity)
|
|
175
251
|
});
|
|
176
252
|
var LEGACY_KEYS = ["ciWiring", "commitIdentity"];
|
|
177
253
|
|
|
178
|
-
class QualityUnreadable extends
|
|
179
|
-
message:
|
|
254
|
+
class QualityUnreadable extends Schema3.TaggedError()("QualityUnreadable", {
|
|
255
|
+
message: Schema3.String
|
|
180
256
|
}) {
|
|
181
257
|
}
|
|
182
258
|
var MANIFEST = "package.json";
|
|
183
|
-
var decodeQualityJson =
|
|
184
|
-
var decodeManifestJson =
|
|
259
|
+
var decodeQualityJson = Schema3.decodeUnknownEffect(Schema3.fromJsonString(Quality), { onExcessProperty: "error" });
|
|
260
|
+
var decodeManifestJson = Schema3.decodeUnknownEffect(Schema3.fromJsonString(LegacyManifest));
|
|
185
261
|
var decodeQuality = (text, source) => decodeQualityJson(text).pipe(Effect.mapError((cause) => new QualityUnreadable({ message: `${source}: ${cause.message}` })));
|
|
186
262
|
function fromLegacy({ ciWiring, commitIdentity }) {
|
|
187
263
|
const gates = {
|
|
@@ -245,7 +321,7 @@ function rulesFor(features) {
|
|
|
245
321
|
to: { path: `^${escaped(root)}/`, pathNot: entries.map((entry) => `^${escaped(entry)}$`) }
|
|
246
322
|
}));
|
|
247
323
|
}
|
|
248
|
-
var decode =
|
|
324
|
+
var decode = Schema4.decodeUnknownSync(Quality);
|
|
249
325
|
function featureRules(quality) {
|
|
250
326
|
return rulesFor(decode(quality, { onExcessProperty: "error" }).features ?? []);
|
|
251
327
|
}
|
|
@@ -19,7 +19,7 @@ The kit's bins find the file at the git root and read it there:
|
|
|
19
19
|
"production": ["src/**/*.ts"],
|
|
20
20
|
"effect": { "paths": ["src/**/*.ts"], "exempt": ["src/host/*.ts"] }
|
|
21
21
|
},
|
|
22
|
-
"size": { "
|
|
22
|
+
"size": { "applies": "ratchet", "tests": { "fileLines": 800 } },
|
|
23
23
|
"features": [
|
|
24
24
|
{
|
|
25
25
|
"name": "billing",
|
|
@@ -44,14 +44,15 @@ The kit's bins find the file at the git root and read it there:
|
|
|
44
44
|
| `gates.scheduled` | `checks-ci-wiring` | the commands a schedule runs |
|
|
45
45
|
| `gates.lint` | `checks-lint`, `checks-ci-wiring` | the gates `checks-lint` runs when not all apply, as [Gate selection](../gates/checks-lint.md#gate-selection) says |
|
|
46
46
|
| `commitIdentity.authors` | `checks-commit-identity` | the identities allowed to author and commit, as [checks-commit-identity](../gates/checks-commit-identity.md) says |
|
|
47
|
-
| `sources.production` | `checks-size-budget`, `checks-quality` | the source the repository ships, as [checks-size-budget](../gates/checks-size-budget.md)
|
|
47
|
+
| `sources.production` | `checks-size-budget`, `checks-repetition`, `checks-quality` | the source the repository ships, as [checks-size-budget](../gates/checks-size-budget.md) and [checks-repetition](../gates/checks-repetition.md) say |
|
|
48
48
|
| `sources.effect` | `checks-quality` | the paths held to the Effect rules, and the files under them that are not, as [The Effect rules](effect-rules.md) says |
|
|
49
|
-
| `size` | `checks-size-budget` | the
|
|
49
|
+
| `size` | `checks-size-budget` | the size budget of production and test files, and how a change is held to it, as [checks-size-budget](../gates/checks-size-budget.md) says |
|
|
50
50
|
| `features` | `featureRules`, `checks-feature-owners` | each feature's root, entries, exempt importers and proof, as [checks-feature-owners](../gates/checks-feature-owners.md) says |
|
|
51
51
|
| `changeSignal` | `checks-feature-owners` | `advisory` to list the feature owners a change touches |
|
|
52
52
|
| `agentRules.on` | agent Rule selection, not the kit | catalogued Rules switched on for this repository |
|
|
53
53
|
| `agentRules.off` | agent Rule selection, not the kit | catalogued Rules switched off for this repository |
|
|
54
54
|
| `docs.pages` | `checks-docs` | the Diátaxis mode of each page, by glob, as [checks-docs](../gates/checks-docs.md) says |
|
|
55
|
+
| `docs.forConsumers` | `checks-docs` | the living docs that speak to a repository installing this one, by glob, whose `bun run` commands name that repository's scripts |
|
|
55
56
|
|
|
56
57
|
<!-- end generated quality-keys -->
|
|
57
58
|
|
package/docs/design.md
CHANGED
|
@@ -14,6 +14,13 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
14
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
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
16
|
- `checks-size-budget` writes the head commit's files to a temporary directory and runs oxlint there, with a configuration that sets no plugin and turns every category off, so the consumer's own `.oxlintrc.json`, its ignore files and its other rules never reach the count.
|
|
17
|
+
- `checks-size-budget` ratchets against the base of the range rather than a committed baseline such as `oxlint-suppressions.json`.
|
|
18
|
+
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.
|
|
19
|
+
The gate sums how far each site runs over its limit instead, which grows with the file.
|
|
20
|
+
The base commit already holds that sum, so nothing is generated, committed or pruned.
|
|
21
|
+
- `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.
|
|
22
|
+
It compares each file's count of repeated lines rather than using jscpd's `--baseline-from-ref`.
|
|
23
|
+
That flag reports a repeated block as new once its text changes, so a change that shortens a grandfathered block would fail.
|
|
17
24
|
- `dist/` is committed.
|
|
18
25
|
No `prepack` or `prepublishOnly` builds it, so a publish ships whatever bundle the publishing worktree holds.
|
|
19
26
|
Rebuild it after pulling with `bun run build`.
|
|
@@ -25,7 +32,8 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
25
32
|
A bump not newer than the release before it is a revert: it cancels every release above the version it returns to, and a tag only keeps a reverted release that was already published.
|
|
26
33
|
The committed changelog is the record of what was released: a version older than the newest it lists and absent from it was never published, and its commits go into the next release.
|
|
27
34
|
A section keeps the date it was written with, since the squash merge that lands the release commit may fall on another day.
|
|
28
|
-
Entries come from commit subjects, the squash-merged pull request titles commitlint holds to the conventional format
|
|
35
|
+
Entries come from commit subjects, the squash-merged pull request titles commitlint holds to the conventional format.
|
|
36
|
+
The bodies are the branch's own messages, which nothing lints.
|
|
29
37
|
The release path needs no `contents: write`: the changelog arrives in the release commit's pull request, not from a workflow that pushes.
|
|
30
38
|
- `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.
|
|
31
39
|
It holds declarations only.
|
|
@@ -71,6 +79,22 @@ Each entry below is a choice in the kit's shape and the constraint that forced i
|
|
|
71
79
|
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.
|
|
72
80
|
- A task heading is verb first, and review holds it there rather than the check.
|
|
73
81
|
No word list tells `Test layout` from `Test the layout`, and a check that passes the noun is worse than none.
|
|
82
|
+
- The prose rules judge only the lines a change adds or edits, the way `checks-comment-gate` judges comments.
|
|
83
|
+
Text nobody touched never turns a change red, a record keeps the words it was written in, and a repository needs no cleanup pass before the gate runs.
|
|
84
|
+
- A living doc takes one sentence per line, so a changed line is a changed sentence.
|
|
85
|
+
Under a hard wrap a one-word edit reflows a paragraph, and the gate would then demand fixes to sentences the edit never touched.
|
|
86
|
+
- An agent file such as `AGENTS.md` takes the separator rules and no other prose rule.
|
|
87
|
+
One sentence per line serves the people who review a doc's diffs, and an agent file keeps each entry to one line however many sentences it holds.
|
|
88
|
+
- `scripts/prose-matchers.ts` imports nothing, so the gate and a write-time hook run one matcher and refuse in the same words.
|
|
89
|
+
A hook bundle ships without `node_modules`, so a matcher that needed Vale or a package could not refuse at write time.
|
|
90
|
+
- Readability grades and words such as easy stay out of the prose rules.
|
|
91
|
+
A score cannot fail a change without failing correct prose, and a suggestion nobody runs an editor for is never seen.
|
|
92
|
+
- A path, link or command on a line the range leaves alone fails when the range broke it, as by deleting the file it names.
|
|
93
|
+
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.
|
|
94
|
+
- 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.
|
|
95
|
+
A directory the range deletes still counts as this repository's, so a path under it reads as stale rather than foreign.
|
|
96
|
+
- The command check passes over the docs a repository declares under `docs.forConsumers`.
|
|
97
|
+
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.
|
|
74
98
|
|
|
75
99
|
## Related topics
|
|
76
100
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# checks-docs
|
|
2
2
|
|
|
3
|
-
`checks-docs` is the gate that holds each doc file a change touches to the template for its kind, and a reader looks it up to learn
|
|
3
|
+
`checks-docs` is the gate that holds each doc file a change touches to the template for its kind, each line a change adds to a living doc or an agent file to the prose rules, and each path, link and command a living doc names to what the repository holds, and a reader looks it up to learn what a doc file answers to.
|
|
4
4
|
|
|
5
5
|
## What it checks
|
|
6
6
|
|
|
7
7
|
It 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.
|
|
8
|
+
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.
|
|
9
|
+
It fails when a living doc names a path, link or command that does not resolve, and the range added it or broke it, as [Paths, links and commands](#paths-links-and-commands) says.
|
|
8
10
|
The package ships one template per kind under `templates/`, and a repository starts a new doc file by copying one:
|
|
9
11
|
|
|
10
12
|
```sh
|
|
@@ -51,12 +53,81 @@ A template decides a file's structure, and the template file itself is the refer
|
|
|
51
53
|
- A changelog lists its releases newest first, each opening with a `Released YYYY-MM-DD.` line.
|
|
52
54
|
- A how-to or tutorial page numbers its steps.
|
|
53
55
|
- `CLAUDE.md` is its template word for word.
|
|
54
|
-
It is a fixed agent pointer rather than a
|
|
56
|
+
It is a fixed agent pointer rather than a living doc, so it takes only the prose rules for agent files.
|
|
57
|
+
|
|
58
|
+
## The prose rules
|
|
59
|
+
|
|
60
|
+
A line a change adds or edits in a living doc or an agent file is held to the prose rules, and a line the change leaves alone is not, so a repository needs no cleanup pass before it runs them.
|
|
61
|
+
|
|
62
|
+
<!-- generated living-docs: bun run build writes it from scripts/prose-matchers.ts and scripts/doc-blocks.ts -->
|
|
63
|
+
|
|
64
|
+
A living doc is one of these:
|
|
65
|
+
|
|
66
|
+
- a `README.md` or `CONTRIBUTING.md` in any directory
|
|
67
|
+
- a Markdown page under `docs/`
|
|
68
|
+
|
|
69
|
+
An agent file is a `AGENTS.md` or `CLAUDE.md` in any directory, and takes only the rules the table below marks for agent files.
|
|
70
|
+
|
|
71
|
+
These are records, and take no prose rule:
|
|
72
|
+
|
|
73
|
+
- a file in `docs/adr/`
|
|
74
|
+
- a file whose name opens with four digits, as in `0001-` or `2026-05-08-`
|
|
75
|
+
- a `CHANGELOG.md`
|
|
76
|
+
|
|
77
|
+
<!-- end generated living-docs -->
|
|
78
|
+
|
|
79
|
+
<!-- generated prose-rules: bun run build writes it from PROSE_RULES in scripts/prose-matchers.ts and scripts/doc-blocks.ts -->
|
|
80
|
+
|
|
81
|
+
| Refused | For example | Write instead | In agent files |
|
|
82
|
+
| --- | --- | --- | --- |
|
|
83
|
+
| an em dash | `—` | End the sentence, or use a comma | yes |
|
|
84
|
+
| an en dash | `–` | End the sentence, or use a comma | yes |
|
|
85
|
+
| a parenthesis other than the plural `(s)` | `(` | Make the aside its own sentence, or set it off with commas | yes |
|
|
86
|
+
| a hyphen used as a dash | `a - b` or `a -- b` | End the sentence, or use a comma | yes |
|
|
87
|
+
| a semicolon | `;` | Use two sentences | yes |
|
|
88
|
+
| a promise about the future | `until #11`, `is planned`, `will soon`, `coming soon`, `in a future release` | Say what is true now | no |
|
|
89
|
+
| a sentence that opens by talking about the page | `This page explains` | Talk directly about the subject | no |
|
|
90
|
+
| a second sentence on one line | `It builds. It ships.` | Start it on its own line | no |
|
|
91
|
+
| a sentence that runs across lines | `It builds` with `and ships.` on the next line | Join the sentence onto one line | no |
|
|
92
|
+
|
|
93
|
+
<!-- end generated prose-rules -->
|
|
94
|
+
|
|
95
|
+
A line holds one sentence, so a changed line is a changed sentence.
|
|
96
|
+
A bold label that opens a line, as in `**Status.**`, heads the sentence after it rather than counting as one.
|
|
97
|
+
Fenced code, inline code, link destinations, URLs, HTML comments and front matter are not prose, so no rule reads them.
|
|
98
|
+
Readability scores and word choice, such as easy, are not checked.
|
|
99
|
+
|
|
100
|
+
`scripts/prose-matchers.ts` holds the rules and a synchronous `proseRefused()`, and imports nothing.
|
|
101
|
+
A host such as a hook bundle can therefore copy it alone into a directory with no `node_modules` and import it as `@avi2dg/checks/scripts/prose-matchers.ts`, to refuse the same lines at write time.
|
|
102
|
+
|
|
103
|
+
## Paths, links and commands
|
|
104
|
+
|
|
105
|
+
Each reference a living doc names has to resolve at the head commit:
|
|
106
|
+
|
|
107
|
+
- A path in inline code that ends in a file extension, such as `scripts/lint.ts`, names a file from the root or from the doc's directory.
|
|
108
|
+
- A relative Markdown link names a file or a directory, and its anchor names a heading in the file it links, as GitHub derives the anchor, or an explicit `id`.
|
|
109
|
+
- A `bun run` command in code names a script in the nearest `package.json`, a bin in `node_modules/.bin`, or a file that exists.
|
|
110
|
+
|
|
111
|
+
A reference that does not resolve fails when it sits on a line the range adds or edits.
|
|
112
|
+
It fails on any other line when the range broke it, as by deleting the file it names or renaming the heading it links, and is listed as advisory when it was broken before the range.
|
|
113
|
+
A path under a top directory the repository lacks at both ends of the range names another repository's file, such as a consumer's, and is passed over.
|
|
114
|
+
So is a path git ignores, since a clean checkout lacks a generated file by design.
|
|
115
|
+
|
|
116
|
+
A doc that speaks to a repository installing this one is declared under `docs.forConsumers` in `quality.json`, and its commands are not held to this repository's `package.json`:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
"docs": {
|
|
120
|
+
"forConsumers": ["README.md", "docs/gates/*.md"]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
55
123
|
|
|
56
124
|
## What it reads
|
|
57
125
|
|
|
58
126
|
It reads each Markdown file from the head commit, and `docs.pages` from `quality.json`.
|
|
59
127
|
A file the range adds, changes or renames is held to its template, and a file it deletes is not.
|
|
128
|
+
It reads the lines the range adds or edits from the diff, with renames detected, so a renamed doc is judged only on the lines the rename changed.
|
|
129
|
+
It reads the files tracked at both ends of the range, the `scripts` of each `package.json` a living doc sits under, and `docs.forConsumers` from `quality.json`.
|
|
130
|
+
From the working tree it reads the ignore files git reads, and `node_modules/.bin`.
|
|
60
131
|
|
|
61
132
|
## Arguments
|
|
62
133
|
|
|
@@ -72,24 +143,30 @@ With one it is that commit against its parent, or against the empty tree for a r
|
|
|
72
143
|
|
|
73
144
|
| Code | When |
|
|
74
145
|
| --- | --- |
|
|
75
|
-
| 0 | every doc file the range touches holds to its template |
|
|
76
|
-
| 1 | a doc file the range touches does not |
|
|
77
|
-
| 2 | `quality.json` does not decode, or a ref does not resolve |
|
|
146
|
+
| 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 it adds or breaks no reference that does not resolve |
|
|
147
|
+
| 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 the range adds or breaks a reference that does not resolve |
|
|
148
|
+
| 2 | `quality.json` or a `package.json` does not decode, or a ref does not resolve |
|
|
78
149
|
|
|
79
150
|
## Sample output
|
|
80
151
|
|
|
81
152
|
```
|
|
82
|
-
docs:
|
|
153
|
+
docs: 4 violation(s):
|
|
83
154
|
README.md:1: lacks `## Where things are`
|
|
155
|
+
README.md:12: carries `;`, a semicolon. Use two sentences
|
|
156
|
+
README.md:20: names `scripts/bild.ts`, which is not in the repository
|
|
84
157
|
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
|
|
85
158
|
docs: advisory, 1 doc file(s) the range leaves alone do not hold to their templates yet:
|
|
86
159
|
docs/adr/0001-quality-gates.md: 5 violation(s)
|
|
160
|
+
docs: advisory, 1 path(s), link(s) or command(s) the living docs name were broken before the range:
|
|
161
|
+
docs/parts.md:9: links to `suppliers.md#prices`, and `docs/suppliers.md` has no heading with that anchor
|
|
87
162
|
```
|
|
88
163
|
|
|
89
164
|
## Opting out
|
|
90
165
|
|
|
91
166
|
It applies to every repository, so no selection leaves it out.
|
|
92
167
|
A file the range leaves alone is only listed as advisory, so a repository adopts the templates as its files change.
|
|
168
|
+
A line the range leaves alone takes no prose rule, so a repository adopts the prose rules as its lines change.
|
|
169
|
+
A doc listed under `docs.forConsumers` holds no `bun run` command to this repository's `package.json`.
|
|
93
170
|
`checks-lint` runs it over each pull request's range, as [checks-lint](checks-lint.md) says.
|
|
94
171
|
|
|
95
172
|
## Related topics
|
|
@@ -64,7 +64,7 @@ It prints the range, the declared selection if there is one, each gate's own rep
|
|
|
64
64
|
```
|
|
65
65
|
checks-lint: range 2504acf098d120e73a8ece3c96f22b934f35c6a8..10ba7d8935b73ed72624120a1542e51bd21ca7c7 from HEAD against origin/main
|
|
66
66
|
...
|
|
67
|
-
checks-lint: 3 of
|
|
67
|
+
checks-lint: 3 of 11 gate(s) failed: checks-commit-identity, checks-comment-gate, checks-suppressions-ratchet
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
## Opting out
|
|
@@ -74,7 +74,7 @@ A repository that runs its gates without `checks-lint` calls each gate's bin in
|
|
|
74
74
|
|
|
75
75
|
## Gate selection
|
|
76
76
|
|
|
77
|
-
A repository with no TypeScript source gives `checks-lint-coverage`, `checks-test-layout`, `checks-size-budget` and `checks-feature-owners` nothing to check, and test-layout would still refuse its missing `bun test` script and `bunfig.toml`.
|
|
77
|
+
A repository with no TypeScript source gives `checks-lint-coverage`, `checks-test-layout`, `checks-size-budget`, `checks-repetition` and `checks-feature-owners` nothing to check, and test-layout would still refuse its missing `bun test` script and `bunfig.toml`.
|
|
78
78
|
It declares the gates `checks-lint` runs as `gates.lint`:
|
|
79
79
|
|
|
80
80
|
```json
|
|
@@ -100,10 +100,11 @@ Both `checks-lint` and `checks-ci-wiring` exit 2 on a `gates.lint` that names an
|
|
|
100
100
|
`checks-ci-wiring` exits 1 when the selection leaves out a gate the repository's tracked files make applicable, and names the gate and the files:
|
|
101
101
|
|
|
102
102
|
```
|
|
103
|
-
ci-wiring: quality.json gates.lint leaves out
|
|
103
|
+
ci-wiring: quality.json gates.lint leaves out 5 gate(s) this repository's contents make applicable:
|
|
104
104
|
checks-lint-coverage: the repository tracks TypeScript source (src/widget.ts)
|
|
105
105
|
checks-test-layout: the repository tracks TypeScript source (src/widget.ts)
|
|
106
106
|
checks-size-budget: the repository tracks TypeScript source (src/widget.ts)
|
|
107
|
+
checks-repetition: the repository tracks TypeScript source (src/widget.ts)
|
|
107
108
|
checks-feature-owners: the repository tracks TypeScript source (src/widget.ts)
|
|
108
109
|
```
|
|
109
110
|
|
|
@@ -34,7 +34,7 @@ A rule only this repository needs stays in its own `.oxlintrc.json`, whose overr
|
|
|
34
34
|
- A fragment is missing, or differs from what `generate` would write from `quality.json` and the installed kit's presets.
|
|
35
35
|
- A fragment is left over once `quality.json` stops declaring `sources.effect`.
|
|
36
36
|
- `.oxlintrc.json` or `tsconfig.json` does not list its fragment in `extends`, so the tool never reads it.
|
|
37
|
-
- A `sources.effect.paths`
|
|
37
|
+
- A `sources.effect.paths` or `sources.production` glob matches no tracked or untracked file, so it holds nothing.
|
|
38
38
|
|
|
39
39
|
Two details of the fragments are easy to get wrong, so the kit's tests pin both:
|
|
40
40
|
|