@eduardbar/drift 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/actions/drift-review/README.md +4 -2
- package/.github/actions/drift-review/action.yml +22 -5
- package/.github/actions/drift-scan/README.md +3 -3
- package/.github/actions/drift-scan/action.yml +1 -1
- package/.github/workflows/publish-vscode.yml +1 -3
- package/.github/workflows/publish.yml +8 -0
- package/.github/workflows/quality.yml +15 -0
- package/.github/workflows/reusable-quality-checks.yml +95 -0
- package/.github/workflows/review-pr.yml +0 -1
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +14 -1
- package/README.md +30 -3
- package/benchmarks/fixtures/critical/drift.config.ts +21 -0
- package/benchmarks/fixtures/critical/src/app/user-service.ts +30 -0
- package/benchmarks/fixtures/critical/src/domain/entities.ts +19 -0
- package/benchmarks/fixtures/critical/src/domain/policies.ts +22 -0
- package/benchmarks/fixtures/critical/src/index.ts +10 -0
- package/benchmarks/fixtures/critical/src/infra/memory-user-repo.ts +14 -0
- package/benchmarks/perf-budget.v1.json +27 -0
- package/dist/benchmark.js +12 -0
- package/dist/cli.js +2 -2
- package/dist/doctor.d.ts +21 -0
- package/dist/doctor.js +10 -3
- package/dist/guard-baseline.d.ts +12 -0
- package/dist/guard-baseline.js +57 -0
- package/dist/guard-metrics.d.ts +6 -0
- package/dist/guard-metrics.js +39 -0
- package/dist/guard-types.d.ts +2 -1
- package/dist/guard.d.ts +3 -1
- package/dist/guard.js +9 -70
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/init.js +1 -1
- package/dist/output-metadata.d.ts +2 -0
- package/dist/output-metadata.js +2 -0
- package/dist/trust.d.ts +2 -1
- package/dist/trust.js +1 -1
- package/dist/types.d.ts +1 -1
- package/docs/AGENTS.md +1 -1
- package/package.json +10 -4
- package/schemas/drift-doctor.v1.json +57 -0
- package/schemas/drift-guard.v1.json +298 -0
- package/scripts/check-docs-drift.mjs +154 -0
- package/scripts/check-performance-budget.mjs +360 -0
- package/scripts/check-runtime-policy.mjs +66 -0
- package/src/benchmark.ts +17 -0
- package/src/cli.ts +2 -2
- package/src/doctor.ts +15 -3
- package/src/guard-baseline.ts +74 -0
- package/src/guard-metrics.ts +52 -0
- package/src/guard-types.ts +3 -1
- package/src/guard.ts +14 -90
- package/src/index.ts +1 -0
- package/src/init.ts +1 -1
- package/src/output-metadata.ts +2 -0
- package/src/trust.ts +1 -1
- package/src/types.ts +1 -0
- package/tests/ci-quality-matrix.test.ts +37 -0
- package/tests/ci-smoke-gate.test.ts +26 -0
- package/tests/ci-version-alignment.test.ts +93 -0
- package/tests/docs-drift-check.test.ts +115 -0
- package/tests/new-features.test.ts +2 -2
- package/tests/perf-budget-check.test.ts +146 -0
- package/tests/phase1-init-doctor-guard.test.ts +104 -2
- package/tests/runtime-policy-alignment.test.ts +46 -0
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "schemas/drift-guard.v1.json",
|
|
4
|
+
"title": "drift guard v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"$schema",
|
|
9
|
+
"toolVersion",
|
|
10
|
+
"scannedAt",
|
|
11
|
+
"projectPath",
|
|
12
|
+
"mode",
|
|
13
|
+
"passed",
|
|
14
|
+
"metrics",
|
|
15
|
+
"checks",
|
|
16
|
+
"current"
|
|
17
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"$schema": {
|
|
20
|
+
"const": "schemas/drift-guard.v1.json"
|
|
21
|
+
},
|
|
22
|
+
"toolVersion": {
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
"scannedAt": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"projectPath": {
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
"mode": {
|
|
32
|
+
"enum": ["diff", "baseline"]
|
|
33
|
+
},
|
|
34
|
+
"passed": {
|
|
35
|
+
"type": "boolean"
|
|
36
|
+
},
|
|
37
|
+
"baseRef": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"baselinePath": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
"metrics": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"required": ["scoreDelta", "totalIssuesDelta", "severityDelta"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"scoreDelta": { "type": "number" },
|
|
49
|
+
"totalIssuesDelta": { "type": "number" },
|
|
50
|
+
"severityDelta": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"required": ["error", "warning", "info"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"error": { "type": "number" },
|
|
56
|
+
"warning": { "type": "number" },
|
|
57
|
+
"info": { "type": "number" }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"checks": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"required": ["id", "passed", "actual", "limit", "message"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"id": { "type": "string" },
|
|
70
|
+
"passed": { "type": "boolean" },
|
|
71
|
+
"actual": { "type": "number" },
|
|
72
|
+
"limit": { "type": "number" },
|
|
73
|
+
"message": { "type": "string" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"current": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"required": [
|
|
81
|
+
"$schema",
|
|
82
|
+
"toolVersion",
|
|
83
|
+
"scannedAt",
|
|
84
|
+
"targetPath",
|
|
85
|
+
"files",
|
|
86
|
+
"totalIssues",
|
|
87
|
+
"totalScore",
|
|
88
|
+
"totalFiles",
|
|
89
|
+
"summary",
|
|
90
|
+
"quality",
|
|
91
|
+
"maintenanceRisk"
|
|
92
|
+
],
|
|
93
|
+
"properties": {
|
|
94
|
+
"$schema": {
|
|
95
|
+
"const": "schemas/drift-report.v1.json"
|
|
96
|
+
},
|
|
97
|
+
"toolVersion": {
|
|
98
|
+
"type": "string"
|
|
99
|
+
},
|
|
100
|
+
"scannedAt": {
|
|
101
|
+
"type": "string"
|
|
102
|
+
},
|
|
103
|
+
"targetPath": {
|
|
104
|
+
"type": "string"
|
|
105
|
+
},
|
|
106
|
+
"files": {
|
|
107
|
+
"type": "array",
|
|
108
|
+
"items": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"additionalProperties": false,
|
|
111
|
+
"required": ["path", "issues", "score"],
|
|
112
|
+
"properties": {
|
|
113
|
+
"path": { "type": "string" },
|
|
114
|
+
"score": { "type": "number" },
|
|
115
|
+
"issues": {
|
|
116
|
+
"type": "array",
|
|
117
|
+
"items": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"additionalProperties": false,
|
|
120
|
+
"required": ["rule", "severity", "message", "line", "column", "snippet"],
|
|
121
|
+
"properties": {
|
|
122
|
+
"rule": { "type": "string" },
|
|
123
|
+
"severity": { "enum": ["error", "warning", "info"] },
|
|
124
|
+
"message": { "type": "string" },
|
|
125
|
+
"line": { "type": "number" },
|
|
126
|
+
"column": { "type": "number" },
|
|
127
|
+
"snippet": { "type": "string" }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"totalIssues": { "type": "number" },
|
|
135
|
+
"totalScore": { "type": "number" },
|
|
136
|
+
"totalFiles": { "type": "number" },
|
|
137
|
+
"summary": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"additionalProperties": false,
|
|
140
|
+
"required": ["errors", "warnings", "infos", "byRule"],
|
|
141
|
+
"properties": {
|
|
142
|
+
"errors": { "type": "number" },
|
|
143
|
+
"warnings": { "type": "number" },
|
|
144
|
+
"infos": { "type": "number" },
|
|
145
|
+
"byRule": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"additionalProperties": { "type": "number" }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"quality": {
|
|
152
|
+
"type": "object",
|
|
153
|
+
"additionalProperties": false,
|
|
154
|
+
"required": ["overall", "dimensions"],
|
|
155
|
+
"properties": {
|
|
156
|
+
"overall": { "type": "number" },
|
|
157
|
+
"dimensions": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"additionalProperties": false,
|
|
160
|
+
"required": ["architecture", "complexity", "ai-patterns", "testing"],
|
|
161
|
+
"properties": {
|
|
162
|
+
"architecture": { "type": "number" },
|
|
163
|
+
"complexity": { "type": "number" },
|
|
164
|
+
"ai-patterns": { "type": "number" },
|
|
165
|
+
"testing": { "type": "number" }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"maintenanceRisk": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"additionalProperties": false,
|
|
173
|
+
"required": ["score", "level", "hotspots", "signals"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"score": { "type": "number" },
|
|
176
|
+
"level": { "enum": ["low", "medium", "high", "critical"] },
|
|
177
|
+
"hotspots": {
|
|
178
|
+
"type": "array",
|
|
179
|
+
"items": {
|
|
180
|
+
"type": "object",
|
|
181
|
+
"additionalProperties": false,
|
|
182
|
+
"required": [
|
|
183
|
+
"file",
|
|
184
|
+
"driftScore",
|
|
185
|
+
"complexityIssues",
|
|
186
|
+
"hasNearbyTests",
|
|
187
|
+
"changeFrequency",
|
|
188
|
+
"risk",
|
|
189
|
+
"reasons"
|
|
190
|
+
],
|
|
191
|
+
"properties": {
|
|
192
|
+
"file": { "type": "string" },
|
|
193
|
+
"driftScore": { "type": "number" },
|
|
194
|
+
"complexityIssues": { "type": "number" },
|
|
195
|
+
"hasNearbyTests": { "type": "boolean" },
|
|
196
|
+
"changeFrequency": { "type": "number" },
|
|
197
|
+
"risk": { "type": "number" },
|
|
198
|
+
"reasons": {
|
|
199
|
+
"type": "array",
|
|
200
|
+
"items": { "type": "string" }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"signals": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"additionalProperties": false,
|
|
208
|
+
"required": ["highComplexityFiles", "filesWithoutNearbyTests", "frequentChangeFiles"],
|
|
209
|
+
"properties": {
|
|
210
|
+
"highComplexityFiles": { "type": "number" },
|
|
211
|
+
"filesWithoutNearbyTests": { "type": "number" },
|
|
212
|
+
"frequentChangeFiles": { "type": "number" }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"diff": {
|
|
220
|
+
"type": "object",
|
|
221
|
+
"additionalProperties": false,
|
|
222
|
+
"required": [
|
|
223
|
+
"baseRef",
|
|
224
|
+
"projectPath",
|
|
225
|
+
"scannedAt",
|
|
226
|
+
"files",
|
|
227
|
+
"totalScoreBefore",
|
|
228
|
+
"totalScoreAfter",
|
|
229
|
+
"totalDelta",
|
|
230
|
+
"newIssuesCount",
|
|
231
|
+
"resolvedIssuesCount"
|
|
232
|
+
],
|
|
233
|
+
"properties": {
|
|
234
|
+
"baseRef": { "type": "string" },
|
|
235
|
+
"projectPath": { "type": "string" },
|
|
236
|
+
"scannedAt": { "type": "string" },
|
|
237
|
+
"files": {
|
|
238
|
+
"type": "array",
|
|
239
|
+
"items": {
|
|
240
|
+
"type": "object",
|
|
241
|
+
"additionalProperties": false,
|
|
242
|
+
"required": [
|
|
243
|
+
"path",
|
|
244
|
+
"scoreBefore",
|
|
245
|
+
"scoreAfter",
|
|
246
|
+
"scoreDelta",
|
|
247
|
+
"newIssues",
|
|
248
|
+
"resolvedIssues"
|
|
249
|
+
],
|
|
250
|
+
"properties": {
|
|
251
|
+
"path": { "type": "string" },
|
|
252
|
+
"scoreBefore": { "type": "number" },
|
|
253
|
+
"scoreAfter": { "type": "number" },
|
|
254
|
+
"scoreDelta": { "type": "number" },
|
|
255
|
+
"newIssues": {
|
|
256
|
+
"type": "array",
|
|
257
|
+
"items": {
|
|
258
|
+
"type": "object",
|
|
259
|
+
"additionalProperties": false,
|
|
260
|
+
"required": ["rule", "severity", "message", "line", "column", "snippet"],
|
|
261
|
+
"properties": {
|
|
262
|
+
"rule": { "type": "string" },
|
|
263
|
+
"severity": { "enum": ["error", "warning", "info"] },
|
|
264
|
+
"message": { "type": "string" },
|
|
265
|
+
"line": { "type": "number" },
|
|
266
|
+
"column": { "type": "number" },
|
|
267
|
+
"snippet": { "type": "string" }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
"resolvedIssues": {
|
|
272
|
+
"type": "array",
|
|
273
|
+
"items": {
|
|
274
|
+
"type": "object",
|
|
275
|
+
"additionalProperties": false,
|
|
276
|
+
"required": ["rule", "severity", "message", "line", "column", "snippet"],
|
|
277
|
+
"properties": {
|
|
278
|
+
"rule": { "type": "string" },
|
|
279
|
+
"severity": { "enum": ["error", "warning", "info"] },
|
|
280
|
+
"message": { "type": "string" },
|
|
281
|
+
"line": { "type": "number" },
|
|
282
|
+
"column": { "type": "number" },
|
|
283
|
+
"snippet": { "type": "string" }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"totalScoreBefore": { "type": "number" },
|
|
291
|
+
"totalScoreAfter": { "type": "number" },
|
|
292
|
+
"totalDelta": { "type": "number" },
|
|
293
|
+
"newIssuesCount": { "type": "number" },
|
|
294
|
+
"resolvedIssuesCount": { "type": "number" }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
function readRepoFile(rootDir, relativePath) {
|
|
6
|
+
return readFileSync(join(rootDir, relativePath), 'utf8')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readPackageVersion(rootDir) {
|
|
10
|
+
const packageJson = JSON.parse(readRepoFile(rootDir, 'package.json'))
|
|
11
|
+
const version = packageJson?.version
|
|
12
|
+
if (typeof version !== 'string' || version.length === 0) {
|
|
13
|
+
throw new Error('package.json is missing a valid version field')
|
|
14
|
+
}
|
|
15
|
+
return version
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function extractRuleIdsFromAnalyzer(analyzerContent) {
|
|
19
|
+
const blockMatch = analyzerContent.match(/export const RULE_WEIGHTS[\s\S]*?=\s*\{([\s\S]*?)\n\}/)
|
|
20
|
+
if (!blockMatch) {
|
|
21
|
+
throw new Error('Could not locate RULE_WEIGHTS block in src/analyzer.ts')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Array.from(blockMatch[1].matchAll(/'([^']+)'\s*:/g), (match) => match[1])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractRuleIdsFromCatalog(catalogContent) {
|
|
28
|
+
const ids = []
|
|
29
|
+
for (const match of catalogContent.matchAll(/^\|\s*`([^`]+)`\s*\|/gm)) {
|
|
30
|
+
const id = match[1]
|
|
31
|
+
if (id !== 'id') {
|
|
32
|
+
ids.push(id)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return ids
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractSingleNumber(content, pattern, errorMessage) {
|
|
39
|
+
const match = content.match(pattern)
|
|
40
|
+
if (!match) {
|
|
41
|
+
throw new Error(errorMessage)
|
|
42
|
+
}
|
|
43
|
+
return Number.parseInt(match[1], 10)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractAgentsVersion(agentsContent) {
|
|
47
|
+
const match = agentsContent.match(/Versi[oó]n del paquete:\s*`([^`]+)`/)
|
|
48
|
+
if (!match) {
|
|
49
|
+
throw new Error('AGENTS.md must include "Versión del paquete: `<version>`"')
|
|
50
|
+
}
|
|
51
|
+
return match[1]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function compareRuleSets(sourceRuleIds, catalogRuleIds) {
|
|
55
|
+
const sourceSet = new Set(sourceRuleIds)
|
|
56
|
+
const catalogSet = new Set(catalogRuleIds)
|
|
57
|
+
|
|
58
|
+
const missingInCatalog = [...sourceSet].filter((ruleId) => !catalogSet.has(ruleId)).sort()
|
|
59
|
+
const extraInCatalog = [...catalogSet].filter((ruleId) => !sourceSet.has(ruleId)).sort()
|
|
60
|
+
|
|
61
|
+
return { missingInCatalog, extraInCatalog }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function validateDocsDrift(rootDir = process.cwd()) {
|
|
65
|
+
const packageVersion = readPackageVersion(rootDir)
|
|
66
|
+
const analyzer = readRepoFile(rootDir, 'src/analyzer.ts')
|
|
67
|
+
const rulesCatalog = readRepoFile(rootDir, 'docs/rules-catalog.md')
|
|
68
|
+
const readme = readRepoFile(rootDir, 'README.md')
|
|
69
|
+
const agents = readRepoFile(rootDir, 'AGENTS.md')
|
|
70
|
+
|
|
71
|
+
const sourceRuleIds = extractRuleIdsFromAnalyzer(analyzer)
|
|
72
|
+
const catalogRuleIds = extractRuleIdsFromCatalog(rulesCatalog)
|
|
73
|
+
const sourceRuleCount = sourceRuleIds.length
|
|
74
|
+
|
|
75
|
+
const readmeRuleCount = extractSingleNumber(
|
|
76
|
+
readme,
|
|
77
|
+
/defines\s+\*\*(\d+)\s+rule IDs\*\*/,
|
|
78
|
+
'README.md must declare the current rule ID count as "defines **<n> rule IDs**"',
|
|
79
|
+
)
|
|
80
|
+
const agentsRuleCount = extractSingleNumber(
|
|
81
|
+
agents,
|
|
82
|
+
/Estado actual:\s+\*\*(\d+)\s+rule IDs\*\*/,
|
|
83
|
+
'AGENTS.md must declare the current rule ID count as "Estado actual: **<n> rule IDs**"',
|
|
84
|
+
)
|
|
85
|
+
const catalogRuleCount = extractSingleNumber(
|
|
86
|
+
rulesCatalog,
|
|
87
|
+
/Total rule IDs currently defined:\s+\*\*(\d+)\*\*/,
|
|
88
|
+
'docs/rules-catalog.md must declare the current rule count line',
|
|
89
|
+
)
|
|
90
|
+
const agentsVersion = extractAgentsVersion(agents)
|
|
91
|
+
const { missingInCatalog, extraInCatalog } = compareRuleSets(sourceRuleIds, catalogRuleIds)
|
|
92
|
+
|
|
93
|
+
const errors = []
|
|
94
|
+
|
|
95
|
+
if (agentsVersion !== packageVersion) {
|
|
96
|
+
errors.push(`AGENTS.md package version (${agentsVersion}) does not match package.json (${packageVersion})`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!rulesCatalog.includes('Source of truth: `RULE_WEIGHTS` in `src/analyzer.ts`.')) {
|
|
100
|
+
errors.push('docs/rules-catalog.md must explicitly declare RULE_WEIGHTS in src/analyzer.ts as source of truth')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (catalogRuleIds.length !== sourceRuleCount) {
|
|
104
|
+
errors.push(`docs/rules-catalog.md table has ${catalogRuleIds.length} rule IDs, but RULE_WEIGHTS defines ${sourceRuleCount}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (missingInCatalog.length > 0) {
|
|
108
|
+
errors.push(`rules missing in docs/rules-catalog.md: ${missingInCatalog.join(', ')}`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (extraInCatalog.length > 0) {
|
|
112
|
+
errors.push(`rules present in docs/rules-catalog.md but not in RULE_WEIGHTS: ${extraInCatalog.join(', ')}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (readmeRuleCount !== sourceRuleCount) {
|
|
116
|
+
errors.push(`README.md rule ID count (${readmeRuleCount}) does not match RULE_WEIGHTS (${sourceRuleCount})`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (agentsRuleCount !== sourceRuleCount) {
|
|
120
|
+
errors.push(`AGENTS.md rule ID count (${agentsRuleCount}) does not match RULE_WEIGHTS (${sourceRuleCount})`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (catalogRuleCount !== sourceRuleCount) {
|
|
124
|
+
errors.push(`docs/rules-catalog.md total rule ID count (${catalogRuleCount}) does not match RULE_WEIGHTS (${sourceRuleCount})`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
ok: errors.length === 0,
|
|
129
|
+
packageVersion,
|
|
130
|
+
sourceRuleCount,
|
|
131
|
+
errors,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function runDocsDriftCheck(rootDir = process.cwd()) {
|
|
136
|
+
const result = validateDocsDrift(rootDir)
|
|
137
|
+
|
|
138
|
+
if (!result.ok) {
|
|
139
|
+
process.stderr.write('Docs drift check failed:\n')
|
|
140
|
+
for (const error of result.errors) {
|
|
141
|
+
process.stderr.write(`- ${error}\n`)
|
|
142
|
+
}
|
|
143
|
+
process.exitCode = 1
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
process.stdout.write(
|
|
148
|
+
`Docs drift check passed: package version ${result.packageVersion}, rule IDs ${result.sourceRuleCount}, docs aligned.\n`,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
153
|
+
runDocsDriftCheck()
|
|
154
|
+
}
|