pasika 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/cli/index.js +32 -38
- package/dist/cli/output.d.ts +3 -0
- package/dist/cli/output.js +11 -0
- package/dist/enforcement/coverage.js +25 -29
- package/dist/enforcement/doctor.d.ts +16 -0
- package/dist/enforcement/doctor.js +249 -0
- package/dist/enforcement/types.d.ts +0 -3
- package/dist/enforcement/types.js +4 -13
- package/dist/eslint/pasika/ast-types.d.ts +122 -0
- package/dist/eslint/pasika/ast-types.js +1 -0
- package/dist/eslint/pasika/index.d.ts +58 -3
- package/dist/eslint/pasika/index.js +61 -1
- package/dist/eslint/pasika/rules/application-structure.d.ts +2 -0
- package/dist/eslint/pasika/rules/application-structure.js +152 -0
- package/dist/eslint/pasika/rules/component-conventions.d.ts +16 -0
- package/dist/eslint/pasika/rules/component-conventions.js +145 -0
- package/dist/eslint/pasika/rules/component-nesting.d.ts +12 -0
- package/dist/eslint/pasika/rules/component-nesting.js +69 -0
- package/dist/eslint/pasika/rules/config-extraction.d.ts +14 -0
- package/dist/eslint/pasika/rules/config-extraction.js +74 -0
- package/dist/eslint/pasika/rules/cross-feature-import.d.ts +2 -0
- package/dist/eslint/pasika/rules/cross-feature-import.js +75 -0
- package/dist/eslint/pasika/rules/css/apply-usage.d.ts +10 -0
- package/dist/eslint/pasika/rules/css/apply-usage.js +45 -0
- package/dist/eslint/pasika/rules/css/base-layer-pair.d.ts +10 -0
- package/dist/eslint/pasika/rules/css/base-layer-pair.js +49 -0
- package/dist/eslint/pasika/rules/css/css-variable-naming.d.ts +10 -0
- package/dist/eslint/pasika/rules/css/css-variable-naming.js +39 -0
- package/dist/eslint/pasika/rules/css/custom-utility-apply.d.ts +11 -0
- package/dist/eslint/pasika/rules/css/custom-utility-apply.js +52 -0
- package/dist/eslint/pasika/rules/css/global-css-location.d.ts +10 -0
- package/dist/eslint/pasika/rules/css/global-css-location.js +42 -0
- package/dist/eslint/pasika/rules/css/helpers.d.ts +29 -0
- package/dist/eslint/pasika/rules/css/helpers.js +93 -0
- package/dist/eslint/pasika/rules/css/index.d.ts +13 -0
- package/dist/eslint/pasika/rules/css/index.js +25 -0
- package/dist/eslint/pasika/rules/css/root-variables.d.ts +11 -0
- package/dist/eslint/pasika/rules/css/root-variables.js +53 -0
- package/dist/eslint/pasika/rules/css/rule-tester.d.ts +11 -0
- package/dist/eslint/pasika/rules/css/rule-tester.js +22 -0
- package/dist/eslint/pasika/rules/css/stylesheet-ordering.d.ts +11 -0
- package/dist/eslint/pasika/rules/css/stylesheet-ordering.js +76 -0
- package/dist/eslint/pasika/rules/css/surface-utility.d.ts +10 -0
- package/dist/eslint/pasika/rules/css/surface-utility.js +41 -0
- package/dist/eslint/pasika/rules/css/theme-reset.d.ts +9 -0
- package/dist/eslint/pasika/rules/css/theme-reset.js +36 -0
- package/dist/eslint/pasika/rules/css/theme-variable-namespace.d.ts +11 -0
- package/dist/eslint/pasika/rules/css/theme-variable-namespace.js +53 -0
- package/dist/eslint/pasika/rules/cva-appearance-props.d.ts +10 -0
- package/dist/eslint/pasika/rules/cva-appearance-props.js +77 -0
- package/dist/eslint/pasika/rules/cva-boolean-variants.d.ts +11 -0
- package/dist/eslint/pasika/rules/cva-boolean-variants.js +97 -0
- package/dist/eslint/pasika/rules/data-testid-case.d.ts +2 -0
- package/dist/eslint/pasika/rules/data-testid-case.js +76 -0
- package/dist/eslint/pasika/rules/enforce-cn-merge.d.ts +13 -1
- package/dist/eslint/pasika/rules/enforce-cn-merge.js +46 -44
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.js +53 -48
- package/dist/eslint/pasika/rules/filename-case.js +28 -5
- package/dist/eslint/pasika/rules/hook-complexity.d.ts +12 -0
- package/dist/eslint/pasika/rules/hook-complexity.js +114 -0
- package/dist/eslint/pasika/rules/hook-extraction.d.ts +11 -0
- package/dist/eslint/pasika/rules/hook-extraction.js +55 -0
- package/dist/eslint/pasika/rules/import-through-index.d.ts +2 -0
- package/dist/eslint/pasika/rules/import-through-index.js +52 -0
- package/dist/eslint/pasika/rules/interactive-component.d.ts +10 -0
- package/dist/eslint/pasika/rules/interactive-component.js +76 -0
- package/dist/eslint/pasika/rules/json/index.d.ts +5 -0
- package/dist/eslint/pasika/rules/json/index.js +9 -0
- package/dist/eslint/pasika/rules/json/no-cache-flag.d.ts +10 -0
- package/dist/eslint/pasika/rules/json/no-cache-flag.js +45 -0
- package/dist/eslint/pasika/rules/json/no-vulyk-dependency.d.ts +10 -0
- package/dist/eslint/pasika/rules/json/no-vulyk-dependency.js +41 -0
- package/dist/eslint/pasika/rules/json/rule-tester.d.ts +11 -0
- package/dist/eslint/pasika/rules/json/rule-tester.js +21 -0
- package/dist/eslint/pasika/rules/jsx-hygiene.d.ts +9 -0
- package/dist/eslint/pasika/rules/jsx-hygiene.js +108 -0
- package/dist/eslint/pasika/rules/locale-dotted-path.d.ts +10 -0
- package/dist/eslint/pasika/rules/locale-dotted-path.js +65 -0
- package/dist/eslint/pasika/rules/locale-placement.d.ts +13 -0
- package/dist/eslint/pasika/rules/locale-placement.js +163 -0
- package/dist/eslint/pasika/rules/locales-location.d.ts +11 -0
- package/dist/eslint/pasika/rules/locales-location.js +56 -0
- package/dist/eslint/pasika/rules/md/doc-kind-suffix.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/doc-kind-suffix.js +29 -0
- package/dist/eslint/pasika/rules/md/example-heading-description.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/example-heading-description.js +30 -0
- package/dist/eslint/pasika/rules/md/glossary-term-linking.d.ts +2 -0
- package/dist/eslint/pasika/rules/md/glossary-term-linking.js +99 -0
- package/dist/eslint/pasika/rules/md/guide-folder-entry-point.d.ts +2 -0
- package/dist/eslint/pasika/rules/md/guide-folder-entry-point.js +46 -0
- package/dist/eslint/pasika/rules/md/guide-link-anchors.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/guide-link-anchors.js +50 -0
- package/dist/eslint/pasika/rules/md/guide-overview-no-links.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/guide-overview-no-links.js +51 -0
- package/dist/eslint/pasika/rules/md/guide-states-no-requirement.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/guide-states-no-requirement.js +26 -0
- package/dist/eslint/pasika/rules/md/guide-step-single-link.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/guide-step-single-link.js +48 -0
- package/dist/eslint/pasika/rules/md/guide-step-single-sentence.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/guide-step-single-sentence.js +42 -0
- package/dist/eslint/pasika/rules/md/helpers.d.ts +25 -0
- package/dist/eslint/pasika/rules/md/helpers.js +37 -0
- package/dist/eslint/pasika/rules/md/index.d.ts +27 -0
- package/dist/eslint/pasika/rules/md/index.js +53 -0
- package/dist/eslint/pasika/rules/md/no-cross-document-link.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/no-cross-document-link.js +36 -0
- package/dist/eslint/pasika/rules/md/no-nested-how-to.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/no-nested-how-to.js +39 -0
- package/dist/eslint/pasika/rules/md/no-template-prompt.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/no-template-prompt.js +22 -0
- package/dist/eslint/pasika/rules/md/overview-length.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/overview-length.js +47 -0
- package/dist/eslint/pasika/rules/md/overview-present.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/overview-present.js +41 -0
- package/dist/eslint/pasika/rules/md/policy-no-examples.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/policy-no-examples.js +28 -0
- package/dist/eslint/pasika/rules/md/policy-single-document.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/policy-single-document.js +34 -0
- package/dist/eslint/pasika/rules/md/policy-subject-headings.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/policy-subject-headings.js +58 -0
- package/dist/eslint/pasika/rules/md/project-index.d.ts +17 -0
- package/dist/eslint/pasika/rules/md/project-index.js +67 -0
- package/dist/eslint/pasika/rules/md/reference-block-headings.d.ts +6 -0
- package/dist/eslint/pasika/rules/md/reference-block-headings.js +31 -0
- package/dist/eslint/pasika/rules/md/reference-no-rfc-vocabulary.d.ts +6 -0
- package/dist/eslint/pasika/rules/md/reference-no-rfc-vocabulary.js +26 -0
- package/dist/eslint/pasika/rules/md/requirement-present.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/requirement-present.js +41 -0
- package/dist/eslint/pasika/rules/md/rfc-only-in-bullets.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/rfc-only-in-bullets.js +46 -0
- package/dist/eslint/pasika/rules/md/rule-paired-examples.d.ts +5 -0
- package/dist/eslint/pasika/rules/md/rule-paired-examples.js +36 -0
- package/dist/eslint/pasika/rules/md/rule-tester.d.ts +11 -0
- package/dist/eslint/pasika/rules/md/rule-tester.js +21 -0
- package/dist/eslint/pasika/rules/md/support-document-placement.d.ts +2 -0
- package/dist/eslint/pasika/rules/md/support-document-placement.js +36 -0
- package/dist/eslint/pasika/rules/md/title-matches-file-name.d.ts +2 -0
- package/dist/eslint/pasika/rules/md/title-matches-file-name.js +46 -0
- package/dist/eslint/pasika/rules/named-exports.d.ts +2 -0
- package/dist/eslint/pasika/rules/named-exports.js +49 -0
- package/dist/eslint/pasika/rules/no-eslint-disable.d.ts +10 -0
- package/dist/eslint/pasika/rules/no-eslint-disable.js +33 -0
- package/dist/eslint/pasika/rules/no-mixed-concerns.js +9 -22
- package/dist/eslint/pasika/rules/no-util-barrel.d.ts +2 -0
- package/dist/eslint/pasika/rules/no-util-barrel.js +48 -0
- package/dist/eslint/pasika/rules/pure-function-extract.d.ts +9 -0
- package/dist/eslint/pasika/rules/pure-function-extract.js +92 -0
- package/dist/eslint/pasika/rules/shared-style-dedup.d.ts +13 -0
- package/dist/eslint/pasika/rules/shared-style-dedup.js +91 -0
- package/dist/eslint/pasika/rules/stay-flat.d.ts +12 -0
- package/dist/eslint/pasika/rules/stay-flat.js +81 -0
- package/dist/eslint/pasika/rules/support-folder-shape.d.ts +2 -0
- package/dist/eslint/pasika/rules/support-folder-shape.js +59 -0
- package/dist/eslint/pasika/rules/type-extraction.d.ts +13 -0
- package/dist/eslint/pasika/rules/type-extraction.js +85 -0
- package/dist/eslint/pasika/rules/ui-state.d.ts +9 -0
- package/dist/eslint/pasika/rules/ui-state.js +99 -0
- package/dist/eslint/pasika/rules/util-file-name.d.ts +2 -0
- package/dist/eslint/pasika/rules/util-file-name.js +48 -0
- package/dist/eslint/pasika/rules/value-extraction.d.ts +13 -0
- package/dist/eslint/pasika/rules/value-extraction.js +48 -0
- package/enforcement/registry.json +196 -190
- package/package.json +13 -5
- package/dist/enforcement/docs-check.d.ts +0 -17
- package/dist/enforcement/docs-check.js +0 -162
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ docs/
|
|
|
16
16
|
enforcement/
|
|
17
17
|
registry.json # requirement → enforcement, keyed by content hash
|
|
18
18
|
coverage.ts # reconciles the docs against the registry
|
|
19
|
-
docs-check.ts # the documentation guide, mechanically
|
|
20
19
|
eslint/
|
|
21
20
|
pasika/rules/ # the lint rules, with fixture tests beside them
|
|
21
|
+
pasika/rules/md/ # the documentation-guide rules, linting docs/ itself
|
|
22
22
|
cli/
|
|
23
23
|
index.ts # the `pasika` command
|
|
24
24
|
```
|
|
@@ -48,7 +48,6 @@ The `note` field is where a check's known gap is recorded, so a partial check ne
|
|
|
48
48
|
| --- | --- |
|
|
49
49
|
| `eslint` | An ESLint rule reports it, and a fixture test titled with the requirement pins it |
|
|
50
50
|
| `doctor` | A `pasika doctor` check reports it |
|
|
51
|
-
| `docs-check` | A `pasika docs` check reports it |
|
|
52
51
|
| `planned` | Mechanically checkable, not written yet; `note` names the intended check |
|
|
53
52
|
| `judgment` | No mechanical check can decide it; `note` says why |
|
|
54
53
|
| `permission` | The requirement grants permission, so there is nothing to check |
|
|
@@ -57,9 +56,9 @@ The `note` field is where a check's known gap is recorded, so a partial check ne
|
|
|
57
56
|
|
|
58
57
|
## Commands
|
|
59
58
|
|
|
59
|
+
The documentation guide itself is linted: the `pasika/*` markdown rules run over `docs/**/*.md` and report title, overview, structure, example-pairing, and RFC 2119 violations at the exact node.
|
|
60
|
+
|
|
60
61
|
```bash
|
|
61
|
-
npx pasika docs # check a docs/ folder against the documentation guide
|
|
62
|
-
npx pasika docs --dir content # check another folder
|
|
63
62
|
npx pasika coverage # check that every requirement has recorded enforcement
|
|
64
63
|
npx pasika coverage --accept # record reworded and removed requirements
|
|
65
64
|
```
|
|
@@ -116,6 +115,10 @@ The ruleset applies to `src/**` only, so a repository without a `src/` tree pass
|
|
|
116
115
|
| `pasika/component-placement` † | The folder a component's consumers imply |
|
|
117
116
|
| `pasika/support-file-placement` † | The folder a hook, type, schema, constant, or utility belongs in |
|
|
118
117
|
|
|
118
|
+
### Documentation rules
|
|
119
|
+
|
|
120
|
+
The `pasika/*` markdown rules enforce the documentation guide over `docs/**/*.md`: file-name suffixes and titles, overview presence and length, guide step structure, Incorrect/Correct pairing, policy document shape, reference block headings, RFC 2119 placement, and template hygiene. They run through `@eslint/markdown` in `eslint.config.ts`; `pasika coverage` verifies each has a test and a registry entry.
|
|
121
|
+
|
|
119
122
|
Run `pasika coverage --json` for the exact requirement each rule covers.
|
|
120
123
|
|
|
121
124
|
### † Cross-file rules
|
|
@@ -133,7 +136,6 @@ Both are inert in a repository with no `src/` tree.
|
|
|
133
136
|
npm run lint
|
|
134
137
|
npm run typecheck
|
|
135
138
|
npm run test
|
|
136
|
-
npm run docs
|
|
137
139
|
npm run coverage
|
|
138
140
|
npm run build
|
|
139
141
|
```
|
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,8 @@ import path from "node:path";
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { buildCoverageReport, classifyRequirement, readRegistry, writeRegistry, } from "../enforcement/coverage.js";
|
|
6
6
|
import { enforcementKindSchema } from "../enforcement/types.js";
|
|
7
|
-
import {
|
|
7
|
+
import { runDoctor } from "../enforcement/doctor.js";
|
|
8
|
+
import { log, error, json } from "./output.js";
|
|
8
9
|
const REGISTRY_RELATIVE_PATH = path.join("enforcement", "registry.json");
|
|
9
10
|
/** Walks up from `startDir` to the directory that holds the enforcement registry. */
|
|
10
11
|
function findRegistryRoot(startDir) {
|
|
@@ -30,30 +31,6 @@ function truncate(text, width) {
|
|
|
30
31
|
}
|
|
31
32
|
const program = new Command();
|
|
32
33
|
program.name("pasika").description("Applies and diagnoses the pasika framework.");
|
|
33
|
-
program
|
|
34
|
-
.command("docs")
|
|
35
|
-
.description("Check documentation against the documentation guide.")
|
|
36
|
-
.option("--dir <path>", "documentation folder to check", "docs")
|
|
37
|
-
.option("--json", "print findings as JSON")
|
|
38
|
-
.action((options) => {
|
|
39
|
-
const docsRoot = path.resolve(options.dir);
|
|
40
|
-
if (!existsSync(docsRoot)) {
|
|
41
|
-
console.error(`No documentation folder at ${docsRoot}`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
const { docs, findings } = checkDocs(docsRoot);
|
|
45
|
-
if (options.json) {
|
|
46
|
-
console.log(JSON.stringify({ documents: docs.length, findings }, undefined, 2));
|
|
47
|
-
process.exit(findings.length > 0 ? 1 : 0);
|
|
48
|
-
}
|
|
49
|
-
for (const finding of findings) {
|
|
50
|
-
console.log(` ✗ ${finding.doc}:${String(finding.line)} ${finding.check} ${finding.message}`);
|
|
51
|
-
}
|
|
52
|
-
console.log(findings.length === 0
|
|
53
|
-
? `\n✓ ${String(docs.length)} documents pass`
|
|
54
|
-
: `\n${String(docs.length)} documents checked · ${String(findings.length)} findings`);
|
|
55
|
-
process.exit(findings.length > 0 ? 1 : 0);
|
|
56
|
-
});
|
|
57
34
|
program
|
|
58
35
|
.command("coverage")
|
|
59
36
|
.description("Check that every documented requirement has recorded enforcement.")
|
|
@@ -66,19 +43,19 @@ program
|
|
|
66
43
|
.action((options) => {
|
|
67
44
|
const root = findRegistryRoot(process.cwd());
|
|
68
45
|
if (!root) {
|
|
69
|
-
|
|
46
|
+
error(`No ${REGISTRY_RELATIVE_PATH} found in this directory or any parent.`);
|
|
70
47
|
process.exit(1);
|
|
71
48
|
}
|
|
72
49
|
const docsRoot = path.join(root, "docs");
|
|
73
50
|
if (!existsSync(docsRoot)) {
|
|
74
|
-
|
|
51
|
+
error(`No documentation folder at ${docsRoot}. Run coverage inside the pasika repository.`);
|
|
75
52
|
process.exit(1);
|
|
76
53
|
}
|
|
77
54
|
const registryPath = path.join(root, REGISTRY_RELATIVE_PATH);
|
|
78
55
|
if (options.classify !== undefined) {
|
|
79
56
|
const kind = enforcementKindSchema.safeParse(options.kind);
|
|
80
57
|
if (!kind.success) {
|
|
81
|
-
|
|
58
|
+
error(`✗ --kind must be one of ${enforcementKindSchema.options.join(", ")}.`);
|
|
82
59
|
process.exit(1);
|
|
83
60
|
}
|
|
84
61
|
try {
|
|
@@ -91,10 +68,10 @@ program
|
|
|
91
68
|
const change = result.previousKind === undefined
|
|
92
69
|
? `recorded as ${result.requirement.kind}`
|
|
93
70
|
: `reclassified from ${result.previousKind} to ${result.requirement.kind}`;
|
|
94
|
-
|
|
71
|
+
log(`✓ ${change}: ${result.requirement.text}`);
|
|
95
72
|
}
|
|
96
|
-
catch (
|
|
97
|
-
|
|
73
|
+
catch (err) {
|
|
74
|
+
error(`✗ ${err instanceof Error ? err.message : String(err)}`);
|
|
98
75
|
process.exit(1);
|
|
99
76
|
}
|
|
100
77
|
}
|
|
@@ -104,29 +81,46 @@ program
|
|
|
104
81
|
rulesDir: path.join(root, "eslint", "pasika", "rules"),
|
|
105
82
|
});
|
|
106
83
|
if (options.json) {
|
|
107
|
-
|
|
84
|
+
json(report);
|
|
108
85
|
process.exit(report.issues.length > 0 ? 1 : 0);
|
|
109
86
|
}
|
|
110
87
|
for (const issue of report.issues) {
|
|
111
88
|
const where = issue.line === undefined ? issue.doc : `${issue.doc}:${String(issue.line)}`;
|
|
112
|
-
|
|
113
|
-
|
|
89
|
+
log(` ✗ ${ISSUE_LABELS[issue.kind]} ${truncate(issue.text, 76)}`);
|
|
90
|
+
log(` ${where}${issue.detail ? `\n ${issue.detail}` : ""}`);
|
|
114
91
|
}
|
|
115
92
|
const { counts } = report;
|
|
116
|
-
|
|
93
|
+
log([
|
|
117
94
|
"",
|
|
118
95
|
`${String(report.total)} requirements · ${String(report.mechanical)} mechanically enforced`,
|
|
119
|
-
` eslint ${String(counts.eslint)} · doctor ${String(counts.doctor)}
|
|
96
|
+
` eslint ${String(counts.eslint)} · doctor ${String(counts.doctor)}`,
|
|
120
97
|
` planned ${String(counts.planned)} · judgment ${String(counts.judgment)} · permission ${String(counts.permission)}`,
|
|
121
98
|
` unclassified ${String(report.issues.filter((issue) => issue.kind === "new").length)}`,
|
|
122
99
|
].join("\n"));
|
|
123
100
|
if (options.accept) {
|
|
124
101
|
writeRegistry(registryPath, report.nextRegistry);
|
|
125
102
|
const accepted = report.issues.filter((issue) => issue.kind === "changed" || issue.kind === "removed");
|
|
126
|
-
|
|
127
|
-
|
|
103
|
+
log(`\nAccepted ${String(accepted.length)} change(s) into ${REGISTRY_RELATIVE_PATH}.`);
|
|
104
|
+
log("Requirements reported as new still need a classification.");
|
|
128
105
|
process.exit(report.issues.some((issue) => issue.kind === "new") ? 1 : 0);
|
|
129
106
|
}
|
|
130
107
|
process.exit(report.issues.length > 0 ? 1 : 0);
|
|
131
108
|
});
|
|
109
|
+
program
|
|
110
|
+
.command("doctor")
|
|
111
|
+
.description("Diagnose gaps between the repository and the pasika framework baseline.")
|
|
112
|
+
.option("--json", "print findings as JSON")
|
|
113
|
+
.action((options) => {
|
|
114
|
+
const findings = runDoctor(process.cwd());
|
|
115
|
+
if (options.json) {
|
|
116
|
+
json({ findings });
|
|
117
|
+
process.exit(findings.some((f) => f.severity === "error") ? 1 : 0);
|
|
118
|
+
}
|
|
119
|
+
for (const finding of findings) {
|
|
120
|
+
const icon = finding.severity === "error" ? "✗" : "⚠";
|
|
121
|
+
log(` ${icon} ${finding.check} ${finding.message}`);
|
|
122
|
+
}
|
|
123
|
+
log(findings.length === 0 ? "\n✓ No gaps found" : `\n${String(findings.length)} finding(s)`);
|
|
124
|
+
process.exit(findings.some((f) => f.severity === "error") ? 1 : 0);
|
|
125
|
+
});
|
|
132
126
|
program.parse();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* eslint-disable no-console -- cli-reports-to-terminal: stdout is the CLI's output channel */
|
|
2
|
+
export function log(message) {
|
|
3
|
+
console.log(message);
|
|
4
|
+
}
|
|
5
|
+
export function error(message) {
|
|
6
|
+
console.error(message);
|
|
7
|
+
}
|
|
8
|
+
export function json(data) {
|
|
9
|
+
console.log(JSON.stringify(data, undefined, 2));
|
|
10
|
+
}
|
|
11
|
+
/* eslint-enable no-console -- re-enable after CLI output block */
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
import { DOCS_CHECKS } from "./docs-check.js";
|
|
4
|
+
import { allPasikaRuleIds } from "../eslint/pasika/index.js";
|
|
6
5
|
import { parseDocs } from "./parse-docs.js";
|
|
7
6
|
import { MECHANICAL_KINDS, registrySchema } from "./types.js";
|
|
8
7
|
/** Ratio of shared words, used only to guess which stored requirement a reworded bullet came from. */
|
|
@@ -13,17 +12,27 @@ function similarity(left, right) {
|
|
|
13
12
|
const union = new Set([...leftWords, ...rightWords]).size;
|
|
14
13
|
return union === 0 ? 0 : shared / union;
|
|
15
14
|
}
|
|
16
|
-
/** Titles passed to `describe` or `test` in the rule test files. */
|
|
15
|
+
/** Titles passed to `describe` or `test` in the rule test files, recursing into subfolders. */
|
|
17
16
|
function collectTestTitles(rulesDir) {
|
|
18
17
|
const titles = new Set();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
const visit = (dir) => {
|
|
19
|
+
for (const entry of readdirSync(dir)) {
|
|
20
|
+
const entryPath = path.join(dir, entry);
|
|
21
|
+
if (statSync(entryPath).isDirectory()) {
|
|
22
|
+
visit(entryPath);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!entry.endsWith(".test.ts"))
|
|
26
|
+
continue;
|
|
27
|
+
const body = readFileSync(entryPath, "utf8");
|
|
28
|
+
const titlePattern = /\b(?:describe|test|it)\(\s*(?:"(?<double>(?:[^"\\]|\\.)*)"|'(?<single>(?:[^'\\]|\\.)*)')/g;
|
|
29
|
+
for (const match of body.matchAll(titlePattern)) {
|
|
30
|
+
const title = match.groups?.double ?? match.groups?.single ?? "";
|
|
31
|
+
titles.add(title.replaceAll('\\\\"', '"').replaceAll("\\\\'", "'"));
|
|
32
|
+
}
|
|
25
33
|
}
|
|
26
|
-
}
|
|
34
|
+
};
|
|
35
|
+
visit(rulesDir);
|
|
27
36
|
return titles;
|
|
28
37
|
}
|
|
29
38
|
/** A requirement several checks cover lists them comma-separated. */
|
|
@@ -33,19 +42,16 @@ function refParts(ref) {
|
|
|
33
42
|
.map((part) => part.trim())
|
|
34
43
|
.filter(Boolean);
|
|
35
44
|
}
|
|
36
|
-
function isRefKnown(requirement
|
|
45
|
+
function isRefKnown(requirement) {
|
|
37
46
|
const parts = refParts(requirement.ref);
|
|
38
47
|
if (requirement.kind === "eslint")
|
|
39
|
-
return parts.length > 0 && parts.every((part) =>
|
|
40
|
-
if (requirement.kind === "docs-check")
|
|
41
|
-
return parts.length > 0 && parts.every((part) => docsChecks.has(part));
|
|
48
|
+
return parts.length > 0 && parts.every((part) => allPasikaRuleIds.includes(part));
|
|
42
49
|
// Doctor checks do not exist yet, so a `doctor` entry is a forward reference.
|
|
43
50
|
return true;
|
|
44
51
|
}
|
|
45
52
|
export function buildCoverageReport(options) {
|
|
46
53
|
const { docsRoot, registry, rulesDir } = options;
|
|
47
54
|
const docs = parseDocs(docsRoot);
|
|
48
|
-
const docsChecks = new Set(DOCS_CHECKS);
|
|
49
55
|
const testTitles = collectTestTitles(rulesDir);
|
|
50
56
|
const byHash = new Map(registry.requirements.map((requirement) => [requirement.hash, requirement]));
|
|
51
57
|
const matched = new Set();
|
|
@@ -53,10 +59,9 @@ export function buildCoverageReport(options) {
|
|
|
53
59
|
const counts = {
|
|
54
60
|
eslint: 0,
|
|
55
61
|
doctor: 0,
|
|
56
|
-
|
|
62
|
+
planned: 0,
|
|
57
63
|
judgment: 0,
|
|
58
64
|
permission: 0,
|
|
59
|
-
planned: 0,
|
|
60
65
|
};
|
|
61
66
|
const nextRequirements = [];
|
|
62
67
|
const parsed = docs.flatMap((doc) => doc.requirements.map((requirement) => ({ doc: doc.doc, requirement })));
|
|
@@ -66,7 +71,7 @@ export function buildCoverageReport(options) {
|
|
|
66
71
|
matched.add(requirement.hash);
|
|
67
72
|
counts[recorded.kind] += 1;
|
|
68
73
|
nextRequirements.push({ ...recorded, doc });
|
|
69
|
-
if (!isRefKnown(recorded
|
|
74
|
+
if (!isRefKnown(recorded)) {
|
|
70
75
|
issues.push({
|
|
71
76
|
kind: "unknown-ref",
|
|
72
77
|
doc,
|
|
@@ -150,20 +155,11 @@ export function classifyRequirement(options) {
|
|
|
150
155
|
if (input.kind === "eslint") {
|
|
151
156
|
if (refs.length === 0)
|
|
152
157
|
throw new Error('Kind "eslint" needs --ref naming the rule that reports it.');
|
|
153
|
-
const unknown = refs.filter((ref) => !
|
|
158
|
+
const unknown = refs.filter((ref) => !allPasikaRuleIds.includes(ref));
|
|
154
159
|
if (unknown.length > 0) {
|
|
155
160
|
throw new Error(`--ref ${unknown.map((ref) => `"${ref}"`).join(", ")} is not a rule in the plugin.`);
|
|
156
161
|
}
|
|
157
162
|
}
|
|
158
|
-
else if (input.kind === "docs-check") {
|
|
159
|
-
if (refs.length === 0)
|
|
160
|
-
throw new Error('Kind "docs-check" needs --ref naming the check that reports it.');
|
|
161
|
-
const known = new Set(DOCS_CHECKS);
|
|
162
|
-
const unknown = refs.filter((ref) => !known.has(ref));
|
|
163
|
-
if (unknown.length > 0) {
|
|
164
|
-
throw new Error(`--ref ${unknown.map((ref) => `"${ref}"`).join(", ")} is not a documentation check.`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
163
|
else if (input.kind !== "doctor" && refs.length > 0) {
|
|
168
164
|
throw new Error(`Kind "${input.kind}" takes no --ref, because nothing reports it.`);
|
|
169
165
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pasika doctor` checks.
|
|
3
|
+
*
|
|
4
|
+
* Diagnoses gaps between a consumer repository and the pasika framework
|
|
5
|
+
* baseline. Scope: dependencies, configuration, managed files, and
|
|
6
|
+
* source structure. Code-level and path-level rules belong to ESLint.
|
|
7
|
+
*/
|
|
8
|
+
export interface DoctorFinding {
|
|
9
|
+
/** Which requirement this relates to. */
|
|
10
|
+
check: string;
|
|
11
|
+
/** Human-readable description. */
|
|
12
|
+
message: string;
|
|
13
|
+
/** Severity: error blocks adoption, warning is informational. */
|
|
14
|
+
severity: "error" | "warning";
|
|
15
|
+
}
|
|
16
|
+
export declare function runDoctor(cwd: string): DoctorFinding[];
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pasika doctor` checks.
|
|
3
|
+
*
|
|
4
|
+
* Diagnoses gaps between a consumer repository and the pasika framework
|
|
5
|
+
* baseline. Scope: dependencies, configuration, managed files, and
|
|
6
|
+
* source structure. Code-level and path-level rules belong to ESLint.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
const packageJsonSchema = z.object({
|
|
12
|
+
dependencies: z.record(z.string(), z.string()).optional(),
|
|
13
|
+
devDependencies: z.record(z.string(), z.string()).optional(),
|
|
14
|
+
scripts: z.record(z.string(), z.string()).optional(),
|
|
15
|
+
});
|
|
16
|
+
function readPackageJson(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
const result = packageJsonSchema.safeParse(JSON.parse(fs.readFileSync(filePath, "utf8")));
|
|
19
|
+
if (result.success)
|
|
20
|
+
return result.data;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// missing or unparseable
|
|
24
|
+
}
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check that pasika and zirka are installed at compatible versions.
|
|
29
|
+
*/
|
|
30
|
+
function checkFrameworkPackages(pkg) {
|
|
31
|
+
const findings = [];
|
|
32
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
33
|
+
if (!allDeps.pasika) {
|
|
34
|
+
findings.push({
|
|
35
|
+
check: "pasika-installed",
|
|
36
|
+
message: "pasika is not listed in package.json.",
|
|
37
|
+
severity: "error",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (!allDeps.zirka) {
|
|
41
|
+
findings.push({
|
|
42
|
+
check: "zirka-installed",
|
|
43
|
+
message: "zirka is not listed in package.json.",
|
|
44
|
+
severity: "error",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return findings;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check that application source lives under src/.
|
|
51
|
+
*/
|
|
52
|
+
function checkSourceRoot(cwd) {
|
|
53
|
+
const findings = [];
|
|
54
|
+
const srcDir = path.join(cwd, "src");
|
|
55
|
+
if (!fs.existsSync(srcDir)) {
|
|
56
|
+
findings.push({
|
|
57
|
+
check: "source-under-src",
|
|
58
|
+
message: "Application source must live under src/.",
|
|
59
|
+
severity: "error",
|
|
60
|
+
});
|
|
61
|
+
return findings;
|
|
62
|
+
}
|
|
63
|
+
// Check for common non-src source directories at the project root
|
|
64
|
+
const entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
65
|
+
const suspicious = entries.filter((e) => e.isDirectory() &&
|
|
66
|
+
!e.name.startsWith(".") &&
|
|
67
|
+
e.name !== "src" &&
|
|
68
|
+
e.name !== "node_modules" &&
|
|
69
|
+
e.name !== "dist" &&
|
|
70
|
+
e.name !== "build" &&
|
|
71
|
+
e.name !== ".vulyk" &&
|
|
72
|
+
e.name !== "docs" &&
|
|
73
|
+
e.name !== "enforcement" &&
|
|
74
|
+
e.name !== "eslint" &&
|
|
75
|
+
e.name !== "cli" &&
|
|
76
|
+
e.name !== "__tests__" &&
|
|
77
|
+
e.name !== "test" &&
|
|
78
|
+
e.name !== "tests" &&
|
|
79
|
+
e.name !== "coverage" &&
|
|
80
|
+
e.name !== ".github" &&
|
|
81
|
+
e.name !== ".agents" &&
|
|
82
|
+
e.name !== ".husky" &&
|
|
83
|
+
e.name !== ".next" &&
|
|
84
|
+
e.name !== ".turbo" &&
|
|
85
|
+
e.name !== ".vercel" &&
|
|
86
|
+
e.name !== ".cache" &&
|
|
87
|
+
e.name !== ".tmp" &&
|
|
88
|
+
e.name !== "tmp" &&
|
|
89
|
+
e.name !== "public" &&
|
|
90
|
+
e.name !== "static" &&
|
|
91
|
+
e.name !== "assets" &&
|
|
92
|
+
e.name !== "bin" &&
|
|
93
|
+
e.name !== "vendor");
|
|
94
|
+
if (suspicious.length > 0) {
|
|
95
|
+
const names = suspicious.map((e) => e.name).join(", ");
|
|
96
|
+
findings.push({
|
|
97
|
+
check: "source-under-src",
|
|
98
|
+
message: `Possible source directories outside src/: ${names}.`,
|
|
99
|
+
severity: "warning",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return findings;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Find the global CSS file that registers Tailwind.
|
|
106
|
+
*/
|
|
107
|
+
function findGlobalStylesheet(cwd) {
|
|
108
|
+
const candidates = ["src/app/globals.css", "src/styles/globals.css", "src/globals.css"];
|
|
109
|
+
for (const candidate of candidates) {
|
|
110
|
+
const full = path.join(cwd, candidate);
|
|
111
|
+
if (fs.existsSync(full))
|
|
112
|
+
return full;
|
|
113
|
+
}
|
|
114
|
+
// Search for any CSS file containing @import "tailwindcss"
|
|
115
|
+
const srcDir = path.join(cwd, "src");
|
|
116
|
+
if (!fs.existsSync(srcDir))
|
|
117
|
+
return undefined;
|
|
118
|
+
const walk = (dir) => {
|
|
119
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
120
|
+
const full = path.join(dir, entry.name);
|
|
121
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".next") {
|
|
122
|
+
const found = walk(full);
|
|
123
|
+
if (found)
|
|
124
|
+
return found;
|
|
125
|
+
}
|
|
126
|
+
if (entry.isFile() && entry.name.endsWith(".css")) {
|
|
127
|
+
const content = fs.readFileSync(full, "utf8");
|
|
128
|
+
if (content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'")) {
|
|
129
|
+
return full;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
};
|
|
135
|
+
return walk(srcDir);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check that the repository has a global stylesheet entry point that
|
|
139
|
+
* registers Tailwind. The stylesheet's contents are the ESLint CSS rules'
|
|
140
|
+
* concern, so doctor only checks that it exists.
|
|
141
|
+
*/
|
|
142
|
+
function checkGlobalStylesheet(cwd) {
|
|
143
|
+
const findings = [];
|
|
144
|
+
const stylesheet = findGlobalStylesheet(cwd);
|
|
145
|
+
if (!stylesheet) {
|
|
146
|
+
findings.push({
|
|
147
|
+
check: "global-stylesheet",
|
|
148
|
+
message: 'No global stylesheet found. Create src/app/globals.css with @import "tailwindcss".',
|
|
149
|
+
severity: "error",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return findings;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Check that configuration files follow the pasika baseline.
|
|
156
|
+
*
|
|
157
|
+
* Repositories must take their lint, format, and TypeScript configuration
|
|
158
|
+
* from pasika and zirka rather than restating it locally.
|
|
159
|
+
*/
|
|
160
|
+
function checkConfigBaseline(cwd) {
|
|
161
|
+
const findings = [];
|
|
162
|
+
// eslint.config.ts must exist and reference zirka
|
|
163
|
+
const eslintConfigs = ["eslint.config.ts", "eslint.config.mjs", "eslint.config.js"];
|
|
164
|
+
const eslintConfig = eslintConfigs.find((name) => fs.existsSync(path.join(cwd, name)));
|
|
165
|
+
if (!eslintConfig) {
|
|
166
|
+
findings.push({
|
|
167
|
+
check: "config-baseline",
|
|
168
|
+
message: "No eslint config found. Create eslint.config.ts extending the pasika/zirka baseline.",
|
|
169
|
+
severity: "error",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const content = fs.readFileSync(path.join(cwd, eslintConfig), "utf8");
|
|
174
|
+
if (!content.includes("zirka")) {
|
|
175
|
+
findings.push({
|
|
176
|
+
check: "config-baseline",
|
|
177
|
+
message: `eslint config does not reference zirka. Use the pasika/zirka baseline configuration.`,
|
|
178
|
+
severity: "warning",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// tsconfig.json must exist
|
|
183
|
+
if (!fs.existsSync(path.join(cwd, "tsconfig.json"))) {
|
|
184
|
+
findings.push({
|
|
185
|
+
check: "config-baseline",
|
|
186
|
+
message: "No tsconfig.json found. Create one extending the pasika baseline.",
|
|
187
|
+
severity: "error",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return findings;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check that vulyk-managed files have not been hand-edited.
|
|
194
|
+
*
|
|
195
|
+
* Files listed in the .vulyk manifest are generated by vulyk commands
|
|
196
|
+
* and must not be edited directly.
|
|
197
|
+
*/
|
|
198
|
+
function checkManagedFiles(cwd) {
|
|
199
|
+
const findings = [];
|
|
200
|
+
const vulykDir = path.join(cwd, ".vulyk");
|
|
201
|
+
if (!fs.existsSync(vulykDir))
|
|
202
|
+
return findings;
|
|
203
|
+
const manifestPath = path.join(vulykDir, "manifest.json");
|
|
204
|
+
if (!fs.existsSync(manifestPath))
|
|
205
|
+
return findings;
|
|
206
|
+
try {
|
|
207
|
+
const manifestEntrySchema = z.looseObject({
|
|
208
|
+
targets: z.array(z.string()).optional(),
|
|
209
|
+
});
|
|
210
|
+
const manifestSchema = z.record(z.string(), manifestEntrySchema);
|
|
211
|
+
const result = manifestSchema.safeParse(JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
212
|
+
if (!result.success)
|
|
213
|
+
return findings;
|
|
214
|
+
for (const [, entry] of Object.entries(result.data)) {
|
|
215
|
+
const targets = entry.targets ?? [];
|
|
216
|
+
for (const target of targets) {
|
|
217
|
+
const targetPath = path.join(cwd, target);
|
|
218
|
+
if (fs.existsSync(targetPath)) {
|
|
219
|
+
// File exists — check it hasn't been modified since vulyk last wrote it
|
|
220
|
+
const stat = fs.statSync(targetPath);
|
|
221
|
+
const vulykStat = fs.statSync(manifestPath);
|
|
222
|
+
// If target is newer than the manifest, it was edited after vulyk wrote it
|
|
223
|
+
if (stat.mtimeMs > vulykStat.mtimeMs + 1000) {
|
|
224
|
+
findings.push({
|
|
225
|
+
check: "managed-file-edit",
|
|
226
|
+
message: `File "${target}" appears to have been edited after vulyk wrote it. Use vulyk commands to update managed files.`,
|
|
227
|
+
severity: "warning",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// manifest unreadable
|
|
236
|
+
}
|
|
237
|
+
return findings;
|
|
238
|
+
}
|
|
239
|
+
export function runDoctor(cwd) {
|
|
240
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
241
|
+
const pkg = readPackageJson(pkgPath);
|
|
242
|
+
return [
|
|
243
|
+
...checkFrameworkPackages(pkg),
|
|
244
|
+
...checkConfigBaseline(cwd),
|
|
245
|
+
...checkManagedFiles(cwd),
|
|
246
|
+
...checkSourceRoot(cwd),
|
|
247
|
+
...checkGlobalStylesheet(cwd),
|
|
248
|
+
];
|
|
249
|
+
}
|
|
@@ -11,7 +11,6 @@ import { z } from "zod";
|
|
|
11
11
|
export declare const enforcementKindSchema: z.ZodEnum<{
|
|
12
12
|
eslint: "eslint";
|
|
13
13
|
doctor: "doctor";
|
|
14
|
-
"docs-check": "docs-check";
|
|
15
14
|
judgment: "judgment";
|
|
16
15
|
permission: "permission";
|
|
17
16
|
planned: "planned";
|
|
@@ -23,7 +22,6 @@ export declare const requirementSchema: z.ZodObject<{
|
|
|
23
22
|
kind: z.ZodEnum<{
|
|
24
23
|
eslint: "eslint";
|
|
25
24
|
doctor: "doctor";
|
|
26
|
-
"docs-check": "docs-check";
|
|
27
25
|
judgment: "judgment";
|
|
28
26
|
permission: "permission";
|
|
29
27
|
planned: "planned";
|
|
@@ -39,7 +37,6 @@ export declare const registrySchema: z.ZodObject<{
|
|
|
39
37
|
kind: z.ZodEnum<{
|
|
40
38
|
eslint: "eslint";
|
|
41
39
|
doctor: "doctor";
|
|
42
|
-
"docs-check": "docs-check";
|
|
43
40
|
judgment: "judgment";
|
|
44
41
|
permission: "permission";
|
|
45
42
|
planned: "planned";
|
|
@@ -13,8 +13,6 @@ export const enforcementKindSchema = z.enum([
|
|
|
13
13
|
"eslint",
|
|
14
14
|
/** A `pasika doctor` check reports it. */
|
|
15
15
|
"doctor",
|
|
16
|
-
/** A `pasika docs` check reports it. */
|
|
17
|
-
"docs-check",
|
|
18
16
|
/** No mechanical check can decide it; a reviewer or agent applies it. */
|
|
19
17
|
"judgment",
|
|
20
18
|
/** The requirement grants permission, so there is nothing to check. */
|
|
@@ -31,8 +29,8 @@ export const requirementSchema = z.object({
|
|
|
31
29
|
hash: z.string(),
|
|
32
30
|
kind: enforcementKindSchema,
|
|
33
31
|
/**
|
|
34
|
-
* Identifier of the check that covers this requirement: an ESLint rule id
|
|
35
|
-
* doctor check id
|
|
32
|
+
* Identifier of the check that covers this requirement: an ESLint rule id or
|
|
33
|
+
* a doctor check id. Several ids are comma-separated.
|
|
36
34
|
* Absent for judgment and permission.
|
|
37
35
|
*/
|
|
38
36
|
ref: z.string().optional(),
|
|
@@ -47,13 +45,6 @@ export const registrySchema = z.object({
|
|
|
47
45
|
requirements: z.array(requirementSchema),
|
|
48
46
|
});
|
|
49
47
|
/** Every kind, in the order the coverage summary prints them. */
|
|
50
|
-
export const ENFORCEMENT_KINDS = [
|
|
51
|
-
"eslint",
|
|
52
|
-
"doctor",
|
|
53
|
-
"docs-check",
|
|
54
|
-
"planned",
|
|
55
|
-
"judgment",
|
|
56
|
-
"permission",
|
|
57
|
-
];
|
|
48
|
+
export const ENFORCEMENT_KINDS = ["eslint", "doctor", "planned", "judgment", "permission"];
|
|
58
49
|
/** The kinds that count as mechanically enforced today. */
|
|
59
|
-
export const MECHANICAL_KINDS = ["eslint", "doctor"
|
|
50
|
+
export const MECHANICAL_KINDS = ["eslint", "doctor"];
|