pasika 0.3.2 → 0.3.4
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 +72 -20
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +4999 -103
- package/dist/eslint/pasika/index.d.ts +165 -53
- package/dist/eslint/pasika/index.js +4424 -89
- package/package.json +5 -6
- package/dist/cli/output.d.ts +0 -3
- package/dist/cli/output.js +0 -11
- package/dist/enforcement/coverage.d.ts +0 -51
- package/dist/enforcement/coverage.js +0 -206
- package/dist/enforcement/doctor.d.ts +0 -16
- package/dist/enforcement/doctor.js +0 -249
- package/dist/enforcement/normalize.d.ts +0 -11
- package/dist/enforcement/normalize.js +0 -21
- package/dist/enforcement/parse-docs.d.ts +0 -58
- package/dist/enforcement/parse-docs.js +0 -94
- package/dist/enforcement/types.d.ts +0 -54
- package/dist/enforcement/types.js +0 -50
- package/dist/eslint/pasika/ast-types.d.ts +0 -122
- package/dist/eslint/pasika/ast-types.js +0 -1
- package/dist/eslint/pasika/project/ccf.d.ts +0 -48
- package/dist/eslint/pasika/project/ccf.js +0 -119
- package/dist/eslint/pasika/project/index.d.ts +0 -21
- package/dist/eslint/pasika/project/index.js +0 -139
- package/dist/eslint/pasika/project/parse-module.d.ts +0 -27
- package/dist/eslint/pasika/project/parse-module.js +0 -128
- package/dist/eslint/pasika/rules/application-structure.d.ts +0 -2
- package/dist/eslint/pasika/rules/application-structure.js +0 -152
- package/dist/eslint/pasika/rules/component-conventions.d.ts +0 -16
- package/dist/eslint/pasika/rules/component-conventions.js +0 -145
- package/dist/eslint/pasika/rules/component-nesting.d.ts +0 -12
- package/dist/eslint/pasika/rules/component-nesting.js +0 -69
- package/dist/eslint/pasika/rules/component-placement.d.ts +0 -11
- package/dist/eslint/pasika/rules/component-placement.js +0 -75
- package/dist/eslint/pasika/rules/config-extraction.d.ts +0 -14
- package/dist/eslint/pasika/rules/config-extraction.js +0 -74
- package/dist/eslint/pasika/rules/cross-feature-import.d.ts +0 -2
- package/dist/eslint/pasika/rules/cross-feature-import.js +0 -75
- package/dist/eslint/pasika/rules/css/apply-usage.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/apply-usage.js +0 -45
- package/dist/eslint/pasika/rules/css/base-layer-pair.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/base-layer-pair.js +0 -49
- package/dist/eslint/pasika/rules/css/css-variable-naming.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/css-variable-naming.js +0 -39
- package/dist/eslint/pasika/rules/css/custom-utility-apply.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/custom-utility-apply.js +0 -52
- package/dist/eslint/pasika/rules/css/global-css-location.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/global-css-location.js +0 -42
- package/dist/eslint/pasika/rules/css/helpers.d.ts +0 -29
- package/dist/eslint/pasika/rules/css/helpers.js +0 -93
- package/dist/eslint/pasika/rules/css/index.d.ts +0 -13
- package/dist/eslint/pasika/rules/css/index.js +0 -25
- package/dist/eslint/pasika/rules/css/root-variables.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/root-variables.js +0 -53
- package/dist/eslint/pasika/rules/css/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/rule-tester.js +0 -22
- package/dist/eslint/pasika/rules/css/stylesheet-ordering.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/stylesheet-ordering.js +0 -76
- package/dist/eslint/pasika/rules/css/surface-utility.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/surface-utility.js +0 -41
- package/dist/eslint/pasika/rules/css/theme-reset.d.ts +0 -9
- package/dist/eslint/pasika/rules/css/theme-reset.js +0 -36
- package/dist/eslint/pasika/rules/css/theme-variable-namespace.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/theme-variable-namespace.js +0 -53
- package/dist/eslint/pasika/rules/cva-appearance-props.d.ts +0 -10
- package/dist/eslint/pasika/rules/cva-appearance-props.js +0 -77
- package/dist/eslint/pasika/rules/cva-boolean-variants.d.ts +0 -11
- package/dist/eslint/pasika/rules/cva-boolean-variants.js +0 -97
- package/dist/eslint/pasika/rules/data-testid-case.d.ts +0 -2
- package/dist/eslint/pasika/rules/data-testid-case.js +0 -76
- package/dist/eslint/pasika/rules/enforce-barrel-exports.d.ts +0 -9
- package/dist/eslint/pasika/rules/enforce-barrel-exports.js +0 -78
- package/dist/eslint/pasika/rules/enforce-cn-merge.d.ts +0 -21
- package/dist/eslint/pasika/rules/enforce-cn-merge.js +0 -86
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.d.ts +0 -9
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.js +0 -86
- package/dist/eslint/pasika/rules/filename-case.d.ts +0 -2
- package/dist/eslint/pasika/rules/filename-case.js +0 -123
- package/dist/eslint/pasika/rules/hook-complexity.d.ts +0 -12
- package/dist/eslint/pasika/rules/hook-complexity.js +0 -114
- package/dist/eslint/pasika/rules/hook-extraction.d.ts +0 -11
- package/dist/eslint/pasika/rules/hook-extraction.js +0 -55
- package/dist/eslint/pasika/rules/import-boundaries.d.ts +0 -2
- package/dist/eslint/pasika/rules/import-boundaries.js +0 -167
- package/dist/eslint/pasika/rules/import-through-index.d.ts +0 -2
- package/dist/eslint/pasika/rules/import-through-index.js +0 -52
- package/dist/eslint/pasika/rules/interactive-component.d.ts +0 -10
- package/dist/eslint/pasika/rules/interactive-component.js +0 -76
- package/dist/eslint/pasika/rules/json/index.d.ts +0 -5
- package/dist/eslint/pasika/rules/json/index.js +0 -9
- package/dist/eslint/pasika/rules/json/no-cache-flag.d.ts +0 -10
- package/dist/eslint/pasika/rules/json/no-cache-flag.js +0 -45
- package/dist/eslint/pasika/rules/json/no-vulyk-dependency.d.ts +0 -10
- package/dist/eslint/pasika/rules/json/no-vulyk-dependency.js +0 -41
- package/dist/eslint/pasika/rules/json/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/json/rule-tester.js +0 -21
- package/dist/eslint/pasika/rules/jsx-hygiene.d.ts +0 -9
- package/dist/eslint/pasika/rules/jsx-hygiene.js +0 -108
- package/dist/eslint/pasika/rules/locale-dotted-path.d.ts +0 -10
- package/dist/eslint/pasika/rules/locale-dotted-path.js +0 -65
- package/dist/eslint/pasika/rules/locale-placement.d.ts +0 -13
- package/dist/eslint/pasika/rules/locale-placement.js +0 -163
- package/dist/eslint/pasika/rules/locales-location.d.ts +0 -11
- package/dist/eslint/pasika/rules/locales-location.js +0 -56
- package/dist/eslint/pasika/rules/md/doc-kind-suffix.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/doc-kind-suffix.js +0 -29
- package/dist/eslint/pasika/rules/md/example-heading-description.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/example-heading-description.js +0 -30
- package/dist/eslint/pasika/rules/md/glossary-term-linking.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/glossary-term-linking.js +0 -99
- package/dist/eslint/pasika/rules/md/guide-folder-entry-point.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/guide-folder-entry-point.js +0 -46
- package/dist/eslint/pasika/rules/md/guide-link-anchors.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-link-anchors.js +0 -50
- package/dist/eslint/pasika/rules/md/guide-overview-no-links.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-overview-no-links.js +0 -51
- package/dist/eslint/pasika/rules/md/guide-states-no-requirement.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-states-no-requirement.js +0 -26
- package/dist/eslint/pasika/rules/md/guide-step-single-link.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-step-single-link.js +0 -48
- package/dist/eslint/pasika/rules/md/guide-step-single-sentence.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-step-single-sentence.js +0 -42
- package/dist/eslint/pasika/rules/md/helpers.d.ts +0 -25
- package/dist/eslint/pasika/rules/md/helpers.js +0 -37
- package/dist/eslint/pasika/rules/md/index.d.ts +0 -27
- package/dist/eslint/pasika/rules/md/index.js +0 -53
- package/dist/eslint/pasika/rules/md/no-cross-document-link.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-cross-document-link.js +0 -36
- package/dist/eslint/pasika/rules/md/no-nested-how-to.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-nested-how-to.js +0 -39
- package/dist/eslint/pasika/rules/md/no-template-prompt.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-template-prompt.js +0 -22
- package/dist/eslint/pasika/rules/md/overview-length.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/overview-length.js +0 -47
- package/dist/eslint/pasika/rules/md/overview-present.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/overview-present.js +0 -41
- package/dist/eslint/pasika/rules/md/policy-no-examples.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-no-examples.js +0 -28
- package/dist/eslint/pasika/rules/md/policy-single-document.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-single-document.js +0 -34
- package/dist/eslint/pasika/rules/md/policy-subject-headings.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-subject-headings.js +0 -58
- package/dist/eslint/pasika/rules/md/project-index.d.ts +0 -17
- package/dist/eslint/pasika/rules/md/project-index.js +0 -67
- package/dist/eslint/pasika/rules/md/reference-block-headings.d.ts +0 -6
- package/dist/eslint/pasika/rules/md/reference-block-headings.js +0 -31
- package/dist/eslint/pasika/rules/md/reference-no-rfc-vocabulary.d.ts +0 -6
- package/dist/eslint/pasika/rules/md/reference-no-rfc-vocabulary.js +0 -26
- package/dist/eslint/pasika/rules/md/requirement-present.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/requirement-present.js +0 -41
- package/dist/eslint/pasika/rules/md/rfc-only-in-bullets.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/rfc-only-in-bullets.js +0 -46
- package/dist/eslint/pasika/rules/md/rule-paired-examples.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/rule-paired-examples.js +0 -36
- package/dist/eslint/pasika/rules/md/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/md/rule-tester.js +0 -21
- package/dist/eslint/pasika/rules/md/support-document-placement.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/support-document-placement.js +0 -36
- package/dist/eslint/pasika/rules/md/title-matches-file-name.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/title-matches-file-name.js +0 -46
- package/dist/eslint/pasika/rules/named-exports.d.ts +0 -2
- package/dist/eslint/pasika/rules/named-exports.js +0 -49
- package/dist/eslint/pasika/rules/no-arbitrary-tailwind.d.ts +0 -9
- package/dist/eslint/pasika/rules/no-arbitrary-tailwind.js +0 -97
- package/dist/eslint/pasika/rules/no-eslint-disable.d.ts +0 -10
- package/dist/eslint/pasika/rules/no-eslint-disable.js +0 -33
- package/dist/eslint/pasika/rules/no-mixed-concerns.d.ts +0 -9
- package/dist/eslint/pasika/rules/no-mixed-concerns.js +0 -71
- package/dist/eslint/pasika/rules/no-util-barrel.d.ts +0 -2
- package/dist/eslint/pasika/rules/no-util-barrel.js +0 -48
- package/dist/eslint/pasika/rules/pure-function-extract.d.ts +0 -9
- package/dist/eslint/pasika/rules/pure-function-extract.js +0 -92
- package/dist/eslint/pasika/rules/shared-style-dedup.d.ts +0 -13
- package/dist/eslint/pasika/rules/shared-style-dedup.js +0 -91
- package/dist/eslint/pasika/rules/stay-flat.d.ts +0 -12
- package/dist/eslint/pasika/rules/stay-flat.js +0 -81
- package/dist/eslint/pasika/rules/support-file-placement.d.ts +0 -15
- package/dist/eslint/pasika/rules/support-file-placement.js +0 -70
- package/dist/eslint/pasika/rules/support-folder-shape.d.ts +0 -2
- package/dist/eslint/pasika/rules/support-folder-shape.js +0 -59
- package/dist/eslint/pasika/rules/type-extraction.d.ts +0 -13
- package/dist/eslint/pasika/rules/type-extraction.js +0 -85
- package/dist/eslint/pasika/rules/ui-state.d.ts +0 -9
- package/dist/eslint/pasika/rules/ui-state.js +0 -99
- package/dist/eslint/pasika/rules/util-file-name.d.ts +0 -2
- package/dist/eslint/pasika/rules/util-file-name.js +0 -48
- package/dist/eslint/pasika/rules/value-extraction.d.ts +0 -13
- package/dist/eslint/pasika/rules/value-extraction.js +0 -48
- package/enforcement/registry.json +0 -1145
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pasika",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Reusable agent setup package",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,13 +19,11 @@
|
|
|
19
19
|
"pasika": "dist/cli/index.js"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
|
-
"dist"
|
|
23
|
-
"enforcement/registry.json"
|
|
22
|
+
"dist"
|
|
24
23
|
],
|
|
25
24
|
"scripts": {
|
|
26
|
-
"build": "
|
|
25
|
+
"build": "tsup",
|
|
27
26
|
"coverage": "tsx cli/index.ts coverage",
|
|
28
|
-
"docs": "eslint docs --config eslint.docs.config.ts",
|
|
29
27
|
"fix": "eslint . --fix",
|
|
30
28
|
"lint": "eslint .",
|
|
31
29
|
"prepack": "npm run build",
|
|
@@ -50,9 +48,10 @@
|
|
|
50
48
|
"eslint": "^10.9.1",
|
|
51
49
|
"jiti": "^2.7.0",
|
|
52
50
|
"prettier": "^3.8.1",
|
|
51
|
+
"tsup": "^8.5.1",
|
|
53
52
|
"tsx": "^4.23.12",
|
|
54
53
|
"typescript": "^5.9.2",
|
|
55
|
-
"zirka": "^0.0.
|
|
54
|
+
"zirka": "^0.0.42"
|
|
56
55
|
},
|
|
57
56
|
"peerDependencies": {
|
|
58
57
|
"typescript": ">=5.0.0"
|
package/dist/cli/output.d.ts
DELETED
package/dist/cli/output.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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,51 +0,0 @@
|
|
|
1
|
-
import { type EnforcementKind, type Registry, type Requirement } from "./types.js";
|
|
2
|
-
export interface CoverageIssue {
|
|
3
|
-
kind: "new" | "changed" | "removed" | "unknown-ref" | "missing-test";
|
|
4
|
-
doc: string;
|
|
5
|
-
line?: number;
|
|
6
|
-
text: string;
|
|
7
|
-
/** Hash of the requirement as it reads now, so a new entry can be recorded without recomputing it. */
|
|
8
|
-
hash?: string;
|
|
9
|
-
detail?: string;
|
|
10
|
-
}
|
|
11
|
-
export interface CoverageReport {
|
|
12
|
-
counts: Record<EnforcementKind, number>;
|
|
13
|
-
total: number;
|
|
14
|
-
mechanical: number;
|
|
15
|
-
issues: CoverageIssue[];
|
|
16
|
-
/** Registry updated for `--accept`: reworded requirements rehashed, removed ones dropped. */
|
|
17
|
-
nextRegistry: Registry;
|
|
18
|
-
}
|
|
19
|
-
export declare function buildCoverageReport(options: {
|
|
20
|
-
docsRoot: string;
|
|
21
|
-
registry: Registry;
|
|
22
|
-
rulesDir: string;
|
|
23
|
-
}): CoverageReport;
|
|
24
|
-
export interface ClassifyInput {
|
|
25
|
-
/** Hash of the requirement, as `coverage` prints it. */
|
|
26
|
-
hash: string;
|
|
27
|
-
kind: EnforcementKind;
|
|
28
|
-
ref?: string;
|
|
29
|
-
note?: string;
|
|
30
|
-
}
|
|
31
|
-
export interface ClassifyResult {
|
|
32
|
-
registry: Registry;
|
|
33
|
-
requirement: Requirement;
|
|
34
|
-
/** The kind this requirement carried before, when it was already classified. */
|
|
35
|
-
previousKind?: EnforcementKind;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Records how a requirement is checked.
|
|
39
|
-
*
|
|
40
|
-
* Validates that the requirement exists in the documentation as written, that a
|
|
41
|
-
* ref names a check that exists, and that the kinds which are meaningless
|
|
42
|
-
* without an explanation carry one. Throws with a readable message otherwise, so
|
|
43
|
-
* the caller can print it and exit.
|
|
44
|
-
*/
|
|
45
|
-
export declare function classifyRequirement(options: {
|
|
46
|
-
docsRoot: string;
|
|
47
|
-
registry: Registry;
|
|
48
|
-
input: ClassifyInput;
|
|
49
|
-
}): ClassifyResult;
|
|
50
|
-
export declare function readRegistry(registryPath: string): Registry;
|
|
51
|
-
export declare function writeRegistry(registryPath: string, registry: Registry): void;
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import { allPasikaRuleIds } from "../eslint/pasika/index.js";
|
|
5
|
-
import { parseDocs } from "./parse-docs.js";
|
|
6
|
-
import { MECHANICAL_KINDS, registrySchema } from "./types.js";
|
|
7
|
-
/** Ratio of shared words, used only to guess which stored requirement a reworded bullet came from. */
|
|
8
|
-
function similarity(left, right) {
|
|
9
|
-
const leftWords = new Set(left.toLowerCase().split(/\W+/).filter(Boolean));
|
|
10
|
-
const rightWords = new Set(right.toLowerCase().split(/\W+/).filter(Boolean));
|
|
11
|
-
const shared = [...leftWords].filter((word) => rightWords.has(word)).length;
|
|
12
|
-
const union = new Set([...leftWords, ...rightWords]).size;
|
|
13
|
-
return union === 0 ? 0 : shared / union;
|
|
14
|
-
}
|
|
15
|
-
/** Titles passed to `describe` or `test` in the rule test files, recursing into subfolders. */
|
|
16
|
-
function collectTestTitles(rulesDir) {
|
|
17
|
-
const titles = new Set();
|
|
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
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
visit(rulesDir);
|
|
36
|
-
return titles;
|
|
37
|
-
}
|
|
38
|
-
/** A requirement several checks cover lists them comma-separated. */
|
|
39
|
-
function refParts(ref) {
|
|
40
|
-
return (ref ?? "")
|
|
41
|
-
.split(",")
|
|
42
|
-
.map((part) => part.trim())
|
|
43
|
-
.filter(Boolean);
|
|
44
|
-
}
|
|
45
|
-
function isRefKnown(requirement) {
|
|
46
|
-
const parts = refParts(requirement.ref);
|
|
47
|
-
if (requirement.kind === "eslint")
|
|
48
|
-
return parts.length > 0 && parts.every((part) => allPasikaRuleIds.includes(part));
|
|
49
|
-
// Doctor checks do not exist yet, so a `doctor` entry is a forward reference.
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
export function buildCoverageReport(options) {
|
|
53
|
-
const { docsRoot, registry, rulesDir } = options;
|
|
54
|
-
const docs = parseDocs(docsRoot);
|
|
55
|
-
const testTitles = collectTestTitles(rulesDir);
|
|
56
|
-
const byHash = new Map(registry.requirements.map((requirement) => [requirement.hash, requirement]));
|
|
57
|
-
const matched = new Set();
|
|
58
|
-
const issues = [];
|
|
59
|
-
const counts = {
|
|
60
|
-
eslint: 0,
|
|
61
|
-
doctor: 0,
|
|
62
|
-
planned: 0,
|
|
63
|
-
judgment: 0,
|
|
64
|
-
permission: 0,
|
|
65
|
-
};
|
|
66
|
-
const nextRequirements = [];
|
|
67
|
-
const parsed = docs.flatMap((doc) => doc.requirements.map((requirement) => ({ doc: doc.doc, requirement })));
|
|
68
|
-
for (const { doc, requirement } of parsed) {
|
|
69
|
-
const recorded = byHash.get(requirement.hash);
|
|
70
|
-
if (recorded) {
|
|
71
|
-
matched.add(requirement.hash);
|
|
72
|
-
counts[recorded.kind] += 1;
|
|
73
|
-
nextRequirements.push({ ...recorded, doc });
|
|
74
|
-
if (!isRefKnown(recorded)) {
|
|
75
|
-
issues.push({
|
|
76
|
-
kind: "unknown-ref",
|
|
77
|
-
doc,
|
|
78
|
-
line: requirement.line,
|
|
79
|
-
text: requirement.text,
|
|
80
|
-
detail: `${recorded.kind} ref "${recorded.ref ?? "(none)"}" does not exist`,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
if (recorded.kind === "eslint" && !testTitles.has(requirement.text)) {
|
|
84
|
-
issues.push({
|
|
85
|
-
kind: "missing-test",
|
|
86
|
-
doc,
|
|
87
|
-
line: requirement.line,
|
|
88
|
-
text: requirement.text,
|
|
89
|
-
detail: `no rule test is titled with this requirement`,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
// Not recorded under this hash: either a reworded requirement or a new one.
|
|
95
|
-
const candidate = registry.requirements
|
|
96
|
-
.filter((entry) => entry.doc === doc && !matched.has(entry.hash))
|
|
97
|
-
.map((entry) => ({ entry, score: similarity(entry.text, requirement.text) }))
|
|
98
|
-
.sort((left, right) => right.score - left.score)
|
|
99
|
-
.find(({ score }) => score >= 0.5);
|
|
100
|
-
if (candidate) {
|
|
101
|
-
matched.add(candidate.entry.hash);
|
|
102
|
-
counts[candidate.entry.kind] += 1;
|
|
103
|
-
nextRequirements.push({ ...candidate.entry, doc, text: requirement.text, hash: requirement.hash });
|
|
104
|
-
issues.push({
|
|
105
|
-
kind: "changed",
|
|
106
|
-
doc,
|
|
107
|
-
line: requirement.line,
|
|
108
|
-
text: requirement.text,
|
|
109
|
-
hash: requirement.hash,
|
|
110
|
-
detail: `was "${candidate.entry.text}" — re-verify ${candidate.entry.kind}${candidate.entry.ref ? ` ${candidate.entry.ref}` : ""}`,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
issues.push({ kind: "new", doc, line: requirement.line, text: requirement.text, hash: requirement.hash });
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
for (const entry of registry.requirements) {
|
|
118
|
-
if (matched.has(entry.hash))
|
|
119
|
-
continue;
|
|
120
|
-
issues.push({
|
|
121
|
-
kind: "removed",
|
|
122
|
-
doc: entry.doc,
|
|
123
|
-
text: entry.text,
|
|
124
|
-
detail: `recorded as ${entry.kind}${entry.ref ? ` ${entry.ref}` : ""} but the bullet is gone`,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
const total = parsed.length;
|
|
128
|
-
let mechanical = 0;
|
|
129
|
-
for (const kind of MECHANICAL_KINDS)
|
|
130
|
-
mechanical += counts[kind];
|
|
131
|
-
return {
|
|
132
|
-
counts,
|
|
133
|
-
total,
|
|
134
|
-
mechanical,
|
|
135
|
-
issues,
|
|
136
|
-
nextRegistry: { requirements: nextRequirements },
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Records how a requirement is checked.
|
|
141
|
-
*
|
|
142
|
-
* Validates that the requirement exists in the documentation as written, that a
|
|
143
|
-
* ref names a check that exists, and that the kinds which are meaningless
|
|
144
|
-
* without an explanation carry one. Throws with a readable message otherwise, so
|
|
145
|
-
* the caller can print it and exit.
|
|
146
|
-
*/
|
|
147
|
-
export function classifyRequirement(options) {
|
|
148
|
-
const { docsRoot, registry, input } = options;
|
|
149
|
-
const parsed = parseDocs(docsRoot).flatMap((doc) => doc.requirements.map((requirement) => ({ doc: doc.doc, requirement })));
|
|
150
|
-
const match = parsed.find((entry) => entry.requirement.hash === input.hash);
|
|
151
|
-
if (!match) {
|
|
152
|
-
throw new Error(`No requirement in the documentation has hash "${input.hash}". Run coverage to list them.`);
|
|
153
|
-
}
|
|
154
|
-
const refs = refParts(input.ref);
|
|
155
|
-
if (input.kind === "eslint") {
|
|
156
|
-
if (refs.length === 0)
|
|
157
|
-
throw new Error('Kind "eslint" needs --ref naming the rule that reports it.');
|
|
158
|
-
const unknown = refs.filter((ref) => !allPasikaRuleIds.includes(ref));
|
|
159
|
-
if (unknown.length > 0) {
|
|
160
|
-
throw new Error(`--ref ${unknown.map((ref) => `"${ref}"`).join(", ")} is not a rule in the plugin.`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else if (input.kind !== "doctor" && refs.length > 0) {
|
|
164
|
-
throw new Error(`Kind "${input.kind}" takes no --ref, because nothing reports it.`);
|
|
165
|
-
}
|
|
166
|
-
if ((input.kind === "judgment" || input.kind === "planned") && (input.note ?? "").trim() === "") {
|
|
167
|
-
const reason = input.kind === "judgment" ? "why no check can decide it" : "the check that should cover it";
|
|
168
|
-
throw new Error(`Kind "${input.kind}" needs --note naming ${reason}.`);
|
|
169
|
-
}
|
|
170
|
-
const requirement = {
|
|
171
|
-
doc: match.doc,
|
|
172
|
-
text: match.requirement.text,
|
|
173
|
-
hash: match.requirement.hash,
|
|
174
|
-
kind: input.kind,
|
|
175
|
-
...(refs.length > 0 ? { ref: refs.join(", ") } : {}),
|
|
176
|
-
...((input.note ?? "").trim() === "" ? {} : { note: input.note?.trim() }),
|
|
177
|
-
};
|
|
178
|
-
const existing = registry.requirements.find((entry) => entry.hash === input.hash);
|
|
179
|
-
const requirements = registry.requirements.filter((entry) => entry.hash !== input.hash);
|
|
180
|
-
requirements.push(requirement);
|
|
181
|
-
return { registry: { requirements }, requirement, previousKind: existing?.kind };
|
|
182
|
-
}
|
|
183
|
-
export function readRegistry(registryPath) {
|
|
184
|
-
const parsed = JSON.parse(readFileSync(registryPath, "utf8"));
|
|
185
|
-
const result = registrySchema.safeParse(parsed);
|
|
186
|
-
if (!result.success) {
|
|
187
|
-
throw new Error(`${registryPath} is not a valid enforcement registry:\n${z.prettifyError(result.error)}`);
|
|
188
|
-
}
|
|
189
|
-
return result.data;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Code-point order on doc then text. Deliberately not `localeCompare`, whose
|
|
193
|
-
* result depends on the host's locale data — a generated file that is committed
|
|
194
|
-
* has to sort the same way everywhere.
|
|
195
|
-
*/
|
|
196
|
-
function compareRequirements(left, right) {
|
|
197
|
-
if (left.doc !== right.doc)
|
|
198
|
-
return left.doc < right.doc ? -1 : 1;
|
|
199
|
-
if (left.text !== right.text)
|
|
200
|
-
return left.text < right.text ? -1 : 1;
|
|
201
|
-
return 0;
|
|
202
|
-
}
|
|
203
|
-
export function writeRegistry(registryPath, registry) {
|
|
204
|
-
const sorted = { requirements: [...registry.requirements].sort(compareRequirements) };
|
|
205
|
-
writeFileSync(registryPath, `${JSON.stringify(sorted, null, 2)}\n`);
|
|
206
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
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[];
|
|
@@ -1,249 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reduces a requirement bullet to the text its meaning depends on.
|
|
3
|
-
*
|
|
4
|
-
* Markdown links collapse to their link text and code spans lose their
|
|
5
|
-
* backticks, so editing a URL or adding backticks does not read as a change in
|
|
6
|
-
* what the requirement demands. Whitespace collapses so re-wrapping a long
|
|
7
|
-
* bullet is not a change either.
|
|
8
|
-
*/
|
|
9
|
-
export declare function normalizeRequirement(bullet: string): string;
|
|
10
|
-
/** Short, stable fingerprint of a canonical requirement text. */
|
|
11
|
-
export declare function hashRequirement(canonicalText: string): string;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
/**
|
|
3
|
-
* Reduces a requirement bullet to the text its meaning depends on.
|
|
4
|
-
*
|
|
5
|
-
* Markdown links collapse to their link text and code spans lose their
|
|
6
|
-
* backticks, so editing a URL or adding backticks does not read as a change in
|
|
7
|
-
* what the requirement demands. Whitespace collapses so re-wrapping a long
|
|
8
|
-
* bullet is not a change either.
|
|
9
|
-
*/
|
|
10
|
-
export function normalizeRequirement(bullet) {
|
|
11
|
-
return bullet
|
|
12
|
-
.replace(/^\s*[-*]\s+/, "")
|
|
13
|
-
.replaceAll(/\[(?<text>[^\]]+)\]\([^)]*\)/g, "$<text>")
|
|
14
|
-
.replaceAll("`", "")
|
|
15
|
-
.replaceAll(/\s+/g, " ")
|
|
16
|
-
.trim();
|
|
17
|
-
}
|
|
18
|
-
/** Short, stable fingerprint of a canonical requirement text. */
|
|
19
|
-
export function hashRequirement(canonicalText) {
|
|
20
|
-
return createHash("sha256").update(canonicalText).digest("hex").slice(0, 10);
|
|
21
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/** The four document kinds the documentation guide defines. */
|
|
2
|
-
export type DocKind = "guide" | "rule" | "reference" | "policy";
|
|
3
|
-
/** Words that carry requirement strength, longest first so `MUST NOT` wins over `MUST`. */
|
|
4
|
-
declare const RFC_2119: RegExp;
|
|
5
|
-
export interface ParsedRequirement {
|
|
6
|
-
/** Canonical text used for identity. */
|
|
7
|
-
text: string;
|
|
8
|
-
hash: string;
|
|
9
|
-
/** The bullet as written, without its list marker. */
|
|
10
|
-
raw: string;
|
|
11
|
-
line: number;
|
|
12
|
-
}
|
|
13
|
-
export interface ParsedStep {
|
|
14
|
-
line: number;
|
|
15
|
-
text: string;
|
|
16
|
-
/** Documents this step links, as written. */
|
|
17
|
-
links: string[];
|
|
18
|
-
}
|
|
19
|
-
export interface ParsedDoc {
|
|
20
|
-
/** Absolute path on disk. */
|
|
21
|
-
filePath: string;
|
|
22
|
-
/** Path relative to the docs root, with forward slashes. */
|
|
23
|
-
doc: string;
|
|
24
|
-
fileName: string;
|
|
25
|
-
/** Undefined when no file-name suffix identifies the kind. */
|
|
26
|
-
kind?: DocKind;
|
|
27
|
-
title: string;
|
|
28
|
-
/** First non-empty prose line under the title. */
|
|
29
|
-
overview?: string;
|
|
30
|
-
/** Prose lines with fenced code blocks blanked and code spans unwrapped, indexed from 0. */
|
|
31
|
-
prose: string[];
|
|
32
|
-
/**
|
|
33
|
-
* Prose lines with code spans removed rather than unwrapped, so a backticked
|
|
34
|
-
* keyword counts as naming the keyword rather than using it.
|
|
35
|
-
*/
|
|
36
|
-
proseWithoutCode: string[];
|
|
37
|
-
requirements: ParsedRequirement[];
|
|
38
|
-
/** Numbered list items, which only a Guide is expected to have. */
|
|
39
|
-
steps: ParsedStep[];
|
|
40
|
-
/** `## Incorrect`/`## Correct` headings in document order. */
|
|
41
|
-
exampleHeadings: {
|
|
42
|
-
line: number;
|
|
43
|
-
text: string;
|
|
44
|
-
}[];
|
|
45
|
-
/** Every link to another Markdown document, outside fenced code. */
|
|
46
|
-
docLinks: {
|
|
47
|
-
line: number;
|
|
48
|
-
target: string;
|
|
49
|
-
}[];
|
|
50
|
-
/** `## ` section headings, excluding the example headings. */
|
|
51
|
-
sectionHeadings: {
|
|
52
|
-
line: number;
|
|
53
|
-
text: string;
|
|
54
|
-
}[];
|
|
55
|
-
}
|
|
56
|
-
export declare function parseDoc(filePath: string, docsRoot: string): ParsedDoc;
|
|
57
|
-
export declare function parseDocs(docsRoot: string): ParsedDoc[];
|
|
58
|
-
export { RFC_2119 };
|