-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck-consistency.js
More file actions
214 lines (187 loc) · 7.03 KB
/
Copy pathcheck-consistency.js
File metadata and controls
214 lines (187 loc) · 7.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env node
/**
* check-consistency.js - Anti-drift consistency checker for markdown-formatter.
* Orchestrates focused validators and runs cross-document checks.
* Dev-only: not shipped in the runtime skill payload.
*/
"use strict";
const FORMAT_FILES = require("./format-files-list");
const RUNTIME_PAYLOAD_FILES = require("./runtime-payload");
const { read, extractFrontmatterVersion, extractBadgeVersion, hasDynamicBadge, findCliFlags, extractRuntimeNodeMinVersion } = require("./validators/common");
const { validateCi } = require("./validators/ci");
const { validateRepoShape } = require("./validators/repo-shape");
const { validateReleaseDrift } = require("./validators/release-drift");
const { validateReleaseLatest } = require("./validators/release-latest");
// ---------------------------------------------------------------------------
// Read all source files once
// ---------------------------------------------------------------------------
const files = {};
for (const f of [
"README.md",
".node-version",
"package.json",
".github/workflows/ci.yml",
"SKILL.md",
"src/index.js",
"src/format-content.mjs",
]) {
files[f] = read(f);
}
// ---------------------------------------------------------------------------
// Aggregate results from sub-validators
// ---------------------------------------------------------------------------
const errors = [];
const warnings = [];
function add(result) {
errors.push(...result.errors);
warnings.push(...result.warnings);
}
add(validateCi(files));
add(validateRepoShape());
add(validateReleaseDrift(files));
add(validateReleaseLatest());
// ---------------------------------------------------------------------------
// Cross-document consistency checks
// ---------------------------------------------------------------------------
const readme = files["README.md"];
const skillMd = files["SKILL.md"];
const indexJs = files["src/index.js"];
const formatContent = files["src/format-content.mjs"];
const pkgJson = files["package.json"];
// Node.js runtime min version
const runtimeMinNodeVersion = indexJs ? extractRuntimeNodeMinVersion(indexJs) : null;
if (!runtimeMinNodeVersion) {
errors.push("src/index.js: NODE_RUNTIME_MIN_VERSION is missing or unreadable");
}
// package.json: engines.node
if (pkgJson) {
try {
const pkg = JSON.parse(pkgJson);
// engines.node must match source
const nodeReq = pkg.engines && pkg.engines.node;
const runtimeMin = `>=${runtimeMinNodeVersion}`;
if (!nodeReq) {
errors.push("package.json engines.node is missing");
} else if (nodeReq !== runtimeMin) {
errors.push(`package.json engines.node is "${nodeReq}" — expected "${runtimeMin}" from NODE_RUNTIME_MIN_VERSION`);
}
} catch (e) {
errors.push(`package.json is not valid JSON: ${e.message}`);
}
}
// Stale reference checks across active docs
const staleChecks = [
{ pattern: /npx\s+markdownlint/, reason: "external markdown linter via npx" },
{ pattern: /npx\s+oxfmt|node_modules\/\.bin\/oxfmt/, reason: "external oxfmt invocation" },
{ pattern: /format-tables\.js.*format|primary.*formatter.*format-tables/i, reason: "format-tables is not the primary formatter" },
{ pattern: /name:\s*markdown-lint/, reason: "skill name should be 'markdown-formatter'" },
];
const ACTIVE_DRIFT_CHECK_PATTERNS = [
...FORMAT_FILES,
"src/index.js",
"src/format-content.mjs",
"guard/check-structure.js",
"guard/check-fences.js",
"guard/check-tables.js",
"guard/check-pipes.js",
];
for (const [file, content] of [
["README.md", readme],
["SKILL.md", skillMd],
["src/index.js", indexJs],
["src/format-content.mjs", formatContent],
]) {
if (!content) continue;
if (!ACTIVE_DRIFT_CHECK_PATTERNS.some((p) => file.startsWith(p))) continue;
for (const { pattern, reason } of staleChecks) {
if (pattern.test(content)) {
errors.push(`stale ref in ${file}: "${reason}"`);
}
}
}
// Version badge alignment: README vs SKILL.md frontmatter
if (readme && skillMd) {
if (hasDynamicBadge(readme)) {
// Dynamic GitHub Release badge — always current, skip comparison
} else {
const badgeVer = extractBadgeVersion(readme);
const frontVer = extractFrontmatterVersion(skillMd);
if (badgeVer && frontVer && badgeVer !== frontVer) {
errors.push(`README badge version "${badgeVer}" != SKILL.md frontmatter "${frontVer}"`);
} else if (!badgeVer && frontVer) {
warnings.push(`README: no version badge found (SKILL.md has "${frontVer}")`);
}
}
}
// package.json version vs SKILL.md frontmatter
if (pkgJson && skillMd) {
try {
const pkg = JSON.parse(pkgJson);
const pkgVer = pkg.version;
const frontVer = extractFrontmatterVersion(skillMd);
if (pkgVer && frontVer && pkgVer !== frontVer) {
warnings.push(`package.json version "${pkgVer}" != SKILL.md frontmatter "${frontVer}"`);
}
} catch { /* already handled above */ }
}
// Tap-installable skill payload staleness
const STAGED_DIR = "skills/markdown-formatter";
const { readFileSync } = require("fs");
const { join } = require("path");
const { ROOT } = require("./validators/common");
try {
const stalePayloadFiles = RUNTIME_PAYLOAD_FILES.filter((file) => {
const stagedContent = readFileSync(join(ROOT, STAGED_DIR, file), "utf8");
const sourceContent = readFileSync(join(ROOT, file), "utf8");
return stagedContent !== sourceContent;
});
if (stalePayloadFiles.length > 0) {
errors.push(
`skills/markdown-formatter/ is stale (${stalePayloadFiles.join(", ")}) — run bash scripts/staged-install-verify.sh to regenerate`
);
}
} catch (e) {
errors.push(
`skills/markdown-formatter/ payload is missing or unreadable — ` +
`run bash scripts/staged-install-verify.sh to regenerate (${e.message})`
);
}
// CLI flag documentation coverage
if (indexJs && skillMd) {
const flags = findCliFlags(indexJs);
for (const flag of flags) {
if (!skillMd.includes(flag)) {
errors.push(`CLI flag "${flag}" in index.js not documented in SKILL.md`);
}
if (readme && !readme.includes(flag)) {
warnings.push(`CLI flag "${flag}" in index.js not documented in README.md`);
}
}
}
if (indexJs && indexJs.includes("--doctor")) {
const doctorDocs = [
["README.md", readme],
["SKILL.md", skillMd],
];
for (const [file, content] of doctorDocs) {
if (!content || !content.includes("--doctor")) {
errors.push(`CLI flag "--doctor" in index.js not documented in ${file}`);
}
}
if (!/function\s+runDoctor\s*\(/.test(indexJs)) {
errors.push('CLI flag "--doctor" is listed but runDoctor() is missing');
}
}
// ---------------------------------------------------------------------------
// Reporting
// ---------------------------------------------------------------------------
if (errors.length > 0) {
console.error("check-consistency ERRORS:");
for (const e of errors) console.error(" ✗", e);
}
if (warnings.length > 0) {
console.warn("check-consistency WARNINGS:");
for (const w of warnings) console.warn(" ⚠", w);
}
if (errors.length === 0) console.log("check-consistency: OK");
process.exit(errors.length > 0 ? 1 : 0);