@avi2dg/checks 0.12.0 → 0.14.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 (41) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/CONTRIBUTING.md +89 -0
  3. package/README.md +137 -1230
  4. package/dist/feature-rules.js +14 -1
  5. package/docs/configs/commit-messages.md +43 -0
  6. package/docs/configs/dependency-rules.md +62 -0
  7. package/docs/configs/effect-rules.md +48 -0
  8. package/docs/configs/quality-file.md +99 -0
  9. package/docs/design.md +77 -0
  10. package/docs/gates/checks-backtest.md +50 -0
  11. package/docs/gates/checks-ci-wiring.md +122 -0
  12. package/docs/gates/checks-comment-gate.md +58 -0
  13. package/docs/gates/checks-commit-identity.md +63 -0
  14. package/docs/gates/checks-docs.md +98 -0
  15. package/docs/gates/checks-feature-owners.md +113 -0
  16. package/docs/gates/checks-flake.md +88 -0
  17. package/docs/gates/checks-lint-coverage.md +49 -0
  18. package/docs/gates/checks-lint.md +136 -0
  19. package/docs/gates/checks-mutation-compare.md +84 -0
  20. package/docs/gates/checks-quality.md +94 -0
  21. package/docs/gates/checks-size-budget.md +64 -0
  22. package/docs/gates/checks-suppressions-ratchet.md +51 -0
  23. package/docs/gates/checks-test-layout.md +77 -0
  24. package/docs/gates/checks-test.md +75 -0
  25. package/package.json +24 -3
  26. package/quality.schema.json +48 -0
  27. package/scripts/doc-outline.ts +215 -0
  28. package/scripts/doc-rules.ts +177 -0
  29. package/scripts/doc-templates.ts +208 -0
  30. package/scripts/docs.ts +90 -0
  31. package/scripts/gates.ts +1 -0
  32. package/scripts/quality-file.ts +22 -1
  33. package/templates/adr.md +29 -0
  34. package/templates/agents.md +17 -0
  35. package/templates/changelog.md +37 -0
  36. package/templates/claude.md +2 -0
  37. package/templates/explanation.md +15 -0
  38. package/templates/how-to.md +33 -0
  39. package/templates/readme.md +45 -0
  40. package/templates/reference.md +15 -0
  41. package/templates/tutorial.md +31 -0
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bun
2
+ import { Console, Effect } from "effect";
3
+ import { ADR_DIRECTORY, judge, placementOf, placementProblem, type Placement } from "./doc-rules.ts";
4
+ import { changedPaths, git, pathsAt, rangeEnds } from "./git.ts";
5
+ import { runMain, Usage } from "./main.ts";
6
+ import { readQuality } from "./quality-file.ts";
7
+
8
+ type Finding = {
9
+ readonly path: string;
10
+ readonly line: number | undefined;
11
+ readonly message: string;
12
+ };
13
+
14
+ type Judged = {
15
+ readonly held: readonly string[];
16
+ readonly findings: readonly Finding[];
17
+ readonly advisory: ReadonlyMap<string, number>;
18
+ };
19
+
20
+ const NAME = "docs";
21
+ const USAGE = "usage: docs.ts <ref> | <base-ref> <head-ref>";
22
+ const MARKDOWN = [":(glob)**/*.md"];
23
+
24
+ const judgeFile = Effect.fn("judgeFile")(function* (
25
+ root: string,
26
+ head: string,
27
+ path: string,
28
+ placement: Placement,
29
+ records: readonly string[],
30
+ ) {
31
+ const misplaced = placementProblem(placement);
32
+ if (misplaced !== undefined) return [{ path, line: undefined, message: misplaced }];
33
+ if (placement.type !== "judged") return [];
34
+ const text = yield* git(["show", `${head}:${path}`], root);
35
+ return judge(placement.kind, { path, text }, records).map(({ line, message }) => ({ path, line, message }));
36
+ });
37
+
38
+ const runDocs = Effect.fn("runDocs")(function* (root: string, base: string, head: string) {
39
+ const { quality } = yield* readQuality(root);
40
+ const touched = new Set(
41
+ (yield* changedPaths(base, head, MARKDOWN, root)).flatMap((change) => (change.kind === "deleted" ? [] : [change.path])),
42
+ );
43
+ const present = yield* pathsAt(head, MARKDOWN, root);
44
+ const records = present.filter((path) => path.startsWith(ADR_DIRECTORY));
45
+ const placed = present.map((path) => ({ path, placement: placementOf(path, quality.docs) }));
46
+ const judged = placed.filter(({ placement }) => placement.type !== "unjudged");
47
+ const findings = (yield* Effect.forEach(judged, ({ path, placement }) => judgeFile(root, head, path, placement, records), {
48
+ concurrency: 8,
49
+ })).flat();
50
+ const advisory = new Map<string, number>();
51
+ for (const { path } of findings.filter((finding) => !touched.has(finding.path))) advisory.set(path, (advisory.get(path) ?? 0) + 1);
52
+ return {
53
+ held: judged.map(({ path }) => path).filter((path) => touched.has(path)),
54
+ findings: findings.filter((finding) => touched.has(finding.path)),
55
+ advisory,
56
+ } satisfies Judged;
57
+ });
58
+
59
+ function describe({ path, line, message }: Finding): string {
60
+ return ` ${path}${line === undefined ? "" : `:${line}`}: ${message}`;
61
+ }
62
+
63
+ export function report({ held, findings, advisory }: Judged): string {
64
+ const verdict =
65
+ findings.length === 0
66
+ ? [`${NAME}: ${held.length} doc file(s) the range touches hold to their templates`]
67
+ : [`${NAME}: ${findings.length} violation(s) in the doc files the range touches:`, ...findings.map(describe)];
68
+ const notice =
69
+ advisory.size === 0
70
+ ? []
71
+ : [
72
+ `${NAME}: advisory, ${advisory.size} doc file(s) the range leaves alone do not hold to their templates yet:`,
73
+ ...[...advisory].map(([path, count]) => ` ${path}: ${count} violation(s)`),
74
+ ];
75
+ return [...verdict, ...notice].join("\n");
76
+ }
77
+
78
+ const docs = Effect.gen(function* () {
79
+ const [first, second, ...extra] = process.argv.slice(2);
80
+ if (first === undefined || extra.length > 0) return yield* new Usage({ message: USAGE });
81
+
82
+ const root = (yield* git(["rev-parse", "--show-toplevel"])).trim();
83
+ const { base, head } = yield* rangeEnds(first, second, root);
84
+ const judged = yield* runDocs(root, base, head);
85
+
86
+ yield* Console.log(report(judged));
87
+ return judged.findings.length === 0;
88
+ });
89
+
90
+ if (import.meta.main) runMain(NAME, docs);
package/scripts/gates.ts CHANGED
@@ -37,6 +37,7 @@ export const KIT_GATES = [
37
37
  { bin: "checks-comment-gate", script: "comment-gate.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
38
38
  { bin: "checks-suppressions-ratchet", script: "suppressions-ratchet.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
39
39
  { bin: "checks-ci-wiring", script: "ci-wiring.ts", reads: "tree", appliesTo: EVERY_REPOSITORY },
40
+ { bin: "checks-docs", script: "docs.ts", reads: "range", appliesTo: EVERY_REPOSITORY },
40
41
  { bin: "checks-quality", script: "quality.ts", reads: "tree", args: ["--check"], appliesTo: QUALITY_DECLARATION },
41
42
  { bin: "checks-size-budget", script: "size-budget.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
42
43
  { bin: "checks-feature-owners", script: "feature-owners.ts", reads: "range", appliesTo: TYPESCRIPT_SOURCE },
@@ -149,6 +149,26 @@ const AgentRules = Schema.Struct({
149
149
  }),
150
150
  );
151
151
 
152
+ export const MODES = ["tutorial", "how-to", "reference", "explanation"] as const;
153
+ export type Mode = (typeof MODES)[number];
154
+
155
+ const pagesIn = (mode: string) =>
156
+ Schema.optionalKey(Schema.Array(PathGlob).annotate({ description: `The pages written as ${mode}` }));
157
+
158
+ const Docs = Schema.Struct({
159
+ pages: Schema.optionalKey(
160
+ Schema.Struct({
161
+ tutorial: pagesIn("a tutorial, which teaches by building one thing"),
162
+ "how-to": pagesIn("a how-to, which walks one task"),
163
+ reference: pagesIn("reference, which describes a thing to be looked up"),
164
+ explanation: pagesIn("an explanation, which says why"),
165
+ } satisfies Record<Mode, unknown>).annotate({
166
+ description: "The Diátaxis mode of each page, whose template checks-docs holds the page to; a page under docs/ needs one",
167
+ }),
168
+ ),
169
+ });
170
+ export type Docs = typeof Docs.Type;
171
+
152
172
  export const Quality = Schema.Struct({
153
173
  $schema: Schema.optionalKey(Schema.String),
154
174
  defaultBranch: Schema.optionalKey(
@@ -171,6 +191,7 @@ export const Quality = Schema.Struct({
171
191
  }),
172
192
  ),
173
193
  agentRules: Schema.optionalKey(AgentRules),
194
+ docs: Schema.optionalKey(Docs.annotate({ description: "What checks-docs reads to map a doc file to its template" })),
174
195
  })
175
196
  .annotate({
176
197
  title: QUALITY_FILE,
@@ -200,7 +221,7 @@ export const Quality = Schema.Struct({
200
221
  );
201
222
  export type Quality = typeof Quality.Type;
202
223
 
203
- const LegacyManifest = Schema.Struct({
224
+ export const LegacyManifest = Schema.Struct({
204
225
  ciWiring: Schema.optionalKey(
205
226
  Schema.Struct({
206
227
  gates: Schema.optionalKey(Schema.NonEmptyArray(Command)),
@@ -0,0 +1,29 @@
1
+ # <number>. <The decision, as a sentence>
2
+
3
+ Date: <YYYY-MM-DD, the day the record was written>
4
+
5
+ ## Status
6
+
7
+ <Proposed, Accepted, Rejected, Deprecated, Superseded or Retired as its first word, then what it amends or what replaced it.>
8
+
9
+ ## Context
10
+
11
+ <What forced a decision, and what was true when it was made.>
12
+
13
+ ## <Another part of the record, such as What was considered and rejected>
14
+
15
+ <Leave this section out when the record needs no more.>
16
+
17
+ <A section like this may also follow any section below it.>
18
+
19
+ <Its text.>
20
+
21
+ ## Decision
22
+
23
+ <What was decided, stated as what now holds.>
24
+
25
+ ## Consequences
26
+
27
+ <Leave this section out when nothing follows from the decision but the decision.>
28
+
29
+ <What follows from the decision, its cost included.>
@@ -0,0 +1,17 @@
1
+ # Project agent memory
2
+
3
+ <What this repository is, in one sentence, and that README.md holds what a person reads.>
4
+
5
+ ## <A topic an agent needs>
6
+
7
+ <Leave this section out when the lead holds every constraint.>
8
+
9
+ - <A constraint an agent cannot infer from the code, and the file that holds its detail.>
10
+
11
+ ## Maintaining this file
12
+
13
+ Keep this file for knowledge useful to almost every future agent session in this project.
14
+ Do not repeat what the codebase already shows.
15
+ Point to the authoritative file or command instead.
16
+ Prefer rewriting or pruning existing entries over appending new ones.
17
+ When updating this file, preserve this bar for all agents and keep entries concise.
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ Every release of <package>, newest first, written by the release from its conventional commits.
4
+
5
+ ## <version>
6
+
7
+ Released <YYYY-MM-DD>.
8
+
9
+ ### Breaking changes
10
+
11
+ <Leave this section out when the release holds no such commit.>
12
+
13
+ - **<the commit's scope, when it has one>:** <its description>
14
+
15
+ ### Features
16
+
17
+ <Leave this section out when the release holds no such commit.>
18
+
19
+ - **<the commit's scope, when it has one>:** <its description>
20
+
21
+ ### Fixes
22
+
23
+ <Leave this section out when the release holds no such commit.>
24
+
25
+ - **<the commit's scope, when it has one>:** <its description>
26
+
27
+ ### Performance
28
+
29
+ <Leave this section out when the release holds no such commit.>
30
+
31
+ - **<the commit's scope, when it has one>:** <its description>
32
+
33
+ ### Reverts
34
+
35
+ <Leave this section out when the release holds no such commit.>
36
+
37
+ - **<the commit's scope, when it has one>:** <its description>
@@ -0,0 +1,2 @@
1
+ <!-- Points Claude at AGENTS.md via import; edit AGENTS.md, not this file. -->
2
+ @AGENTS.md
@@ -0,0 +1,15 @@
1
+ # <The idea this page explains, as a noun>
2
+
3
+ <The question this page answers, and the short answer.>
4
+
5
+ ## <One strand of the answer>
6
+
7
+ <Leave this section out when the lead holds the whole answer.>
8
+
9
+ <The reasoning, the alternatives, and why they lost.>
10
+
11
+ ## Related topics
12
+
13
+ <Leave this section out when there is no other page to send the reader to.>
14
+
15
+ - [<page title>](<path to the page>)
@@ -0,0 +1,33 @@
1
+ # <Task, verb first>
2
+
3
+ <Who does this, and when, in one or two sentences.>
4
+
5
+ ## Before you begin
6
+
7
+ <Leave this section out when the task needs nothing set up first.>
8
+
9
+ - <each prerequisite, with the version it is tested on>
10
+
11
+ ## <Part of the task, verb first>
12
+
13
+ <Leave this section out when the page is one task, whose steps then follow the lead.>
14
+
15
+ To <do the task>:
16
+
17
+ 1. <step>
18
+ 1. <step>
19
+
20
+ ## Troubleshooting
21
+
22
+ <Leave this section out when no reader has met a failure worth naming yet.>
23
+
24
+ ### <The symptom, or the error text>
25
+
26
+ <The cause.>
27
+ <The resolution.>
28
+
29
+ ## Related topics
30
+
31
+ <Leave this section out when there is no other page to send the reader to.>
32
+
33
+ - [<page title>](<path to the page>)
@@ -0,0 +1,45 @@
1
+ # <name>
2
+
3
+ <The concept in two to four sentences: what this is, who it is for, why you would use it.>
4
+
5
+ ## Before you begin
6
+
7
+ - <each prerequisite, with the version it is tested on>
8
+
9
+ ## Install
10
+
11
+ To install <name>:
12
+
13
+ 1. <step>
14
+ 1. <step>
15
+
16
+ <What you see when it worked.>
17
+
18
+ ## <Everyday task, verb first, or what the reader looks up>
19
+
20
+ To <do the task>:
21
+
22
+ 1. <step>
23
+ 1. <step>
24
+
25
+ <Or, for what the reader looks up, a table or a list with no steps.>
26
+
27
+ ## Where things are
28
+
29
+ | Path | What it holds |
30
+ | --- | --- |
31
+
32
+ ## Troubleshooting
33
+
34
+ <Leave this section out when no reader has met a failure worth naming yet.>
35
+
36
+ ### <The symptom, or the error text>
37
+
38
+ <The cause.>
39
+ <The resolution.>
40
+
41
+ ## Related topics
42
+
43
+ <Leave this section out when there is no other page to send the reader to.>
44
+
45
+ - [<page title>](<path to the page>)
@@ -0,0 +1,15 @@
1
+ # <The thing this page describes, as a noun>
2
+
3
+ <What the thing is, in one sentence, and when a reader looks it up.>
4
+
5
+ ## <One part of it, as a noun>
6
+
7
+ <Leave this section out when the lead and one table describe all of it.>
8
+
9
+ <A table, a list or a short description, with no steps and no opinion.>
10
+
11
+ ## Related topics
12
+
13
+ <Leave this section out when there is no other page to send the reader to.>
14
+
15
+ - [<page title>](<path to the page>)
@@ -0,0 +1,31 @@
1
+ # Tutorial: <Verb and what the reader builds>
2
+
3
+ <What the reader builds, and what they learn on the way.>
4
+
5
+ ## Before you begin
6
+
7
+ - <each prerequisite, with the version it is tested on>
8
+
9
+ ## <Step, verb first>
10
+
11
+ To <do the task>:
12
+
13
+ 1. <step>
14
+ 1. <step>
15
+
16
+ <What the reader sees now.>
17
+
18
+ ## Troubleshooting
19
+
20
+ <Leave this section out when no reader has met a failure worth naming yet.>
21
+
22
+ ### <The symptom, or the error text>
23
+
24
+ <The cause.>
25
+ <The resolution.>
26
+
27
+ ## Related topics
28
+
29
+ <Leave this section out when there is no other page to send the reader to.>
30
+
31
+ - [<page title>](<path to the page>)