@dailephd/my-dev-kit-lab 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +17 -7
  2. package/dist/scripts/audits/runAudit.js +102 -0
  3. package/dist/src/audits/codeRot/codeRotDetectors.js +17 -0
  4. package/dist/src/audits/codeRot/detectors/architectureDriftDetector.js +271 -0
  5. package/dist/src/audits/codeRot/detectors/crossPlatformRotDetector.js +214 -0
  6. package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +377 -0
  7. package/dist/src/audits/codeRot/detectors/dependencyEnvironmentRotDetector.js +244 -0
  8. package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +219 -0
  9. package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +350 -0
  10. package/dist/src/audits/codeRot/detectors/packageReleaseRotDetector.js +325 -0
  11. package/dist/src/audits/codeRot/detectors/securityValidationAssumptionRotDetector.js +385 -0
  12. package/dist/src/audits/codeRot/detectors/staleCommandReferenceDetector.js +135 -0
  13. package/dist/src/audits/codeRot/detectors/testRotDetector.js +410 -0
  14. package/dist/src/audits/codeRot/utils/boundedRead.js +23 -0
  15. package/dist/src/audits/codeRot/utils/docClaimPatterns.js +62 -0
  16. package/dist/src/audits/codeRot/utils/docCommandReferences.js +64 -0
  17. package/dist/src/audits/codeRot/utils/filePatternUtils.js +79 -0
  18. package/dist/src/audits/codeRot/utils/issueFactories.js +38 -0
  19. package/dist/src/audits/codeRot/utils/sourceFactsLookup.js +53 -0
  20. package/dist/src/audits/codeRot/utils/textLines.js +12 -0
  21. package/dist/src/audits/core/auditConfig.js +125 -0
  22. package/dist/src/audits/core/auditExitCode.js +75 -0
  23. package/dist/src/audits/core/auditIssue.js +20 -0
  24. package/dist/src/audits/core/auditRegistry.js +40 -0
  25. package/dist/src/audits/core/auditRunner.js +74 -0
  26. package/dist/src/audits/core/auditTarget.js +19 -0
  27. package/dist/src/audits/core/auditTypes.js +30 -0
  28. package/dist/src/audits/core/collectSourceFacts.js +142 -0
  29. package/dist/src/audits/core/languageAnalyzerRegistry.js +33 -0
  30. package/dist/src/audits/core/projectInventory.js +458 -0
  31. package/dist/src/audits/core/sourceFacts.js +17 -0
  32. package/dist/src/audits/core/sourceOfTruth.js +339 -0
  33. package/dist/src/audits/core/textLines.js +24 -0
  34. package/dist/src/audits/core/typescriptJavaScriptAnalyzer.js +299 -0
  35. package/dist/src/audits/report/auditReportModel.js +253 -0
  36. package/dist/src/audits/report/auditReportPaths.js +13 -0
  37. package/dist/src/audits/report/renderAuditJsonReport.js +30 -0
  38. package/dist/src/audits/report/renderAuditTextReport.js +181 -0
  39. package/dist/src/audits/report/sanitizeAuditText.js +39 -0
  40. package/dist/src/audits/report/writeAuditReports.js +27 -0
  41. package/dist/src/core/resolveCommand.js +53 -2
  42. package/package.json +10 -6
@@ -0,0 +1,244 @@
1
+ import { readBoundedFileText } from "../utils/boundedRead.js";
2
+ import { deduplicateIssuesById, makeCodeRotIssue } from "../utils/issueFactories.js";
3
+ // ---------------------------------------------------------------------------
4
+ // v0.3.0 Batch 4 — dependency/environment rot detector.
5
+ //
6
+ // Purely local: reads already-collected sourceOfTruth plus a bounded direct
7
+ // read of docs content. No npm install/audit/registry calls and no
8
+ // subprocess execution anywhere in this file (enforced by a structural
9
+ // test, see dependencyEnvironmentRotDetector.test.ts).
10
+ // ---------------------------------------------------------------------------
11
+ const DETECTOR_ID = "dependency-environment-rot";
12
+ const MAX_READ_BYTES = 200_000;
13
+ const OTHER_PACKAGE_MANAGER_INSTALL_PATTERN = /\b(yarn add|pnpm add|pnpm install)\b/gi;
14
+ const NPM_INSTALL_NEARBY_PATTERN = /\bnpm (install|ci)\b/i;
15
+ const NODE_POLICY_CLAIM_PATTERN = /latest[- ]two|node (?:lts )?policy/i;
16
+ const OPTIONAL_TOOL_PATTERN = /\b(codeql|semgrep|osv-scanner)\b/gi;
17
+ export const DEPENDENCY_ENVIRONMENT_ROT_DETECTOR = {
18
+ id: DETECTOR_ID,
19
+ auditType: "code-rot",
20
+ title: "Dependency/environment rot",
21
+ description: "Detects package-manager instruction mismatches against the lockfile, Node engine/CI version mismatches, unverifiable Node-policy claims, and scripts invoking optional external tools that are undocumented.",
22
+ supportedIncludeAreas: ["package", "docs", "cli"],
23
+ shouldSkip: (ctx) => {
24
+ if (!ctx.config.include.includes("package")) {
25
+ return { skip: true, reason: "--include does not select package; this detector's primary signal is package.json/lockfile/CI metadata." };
26
+ }
27
+ return { skip: false };
28
+ },
29
+ run: (ctx) => {
30
+ const issues = [];
31
+ const pkg = ctx.sourceOfTruth.package;
32
+ if (!pkg)
33
+ return issues;
34
+ if (ctx.config.include.includes("docs")) {
35
+ const docsConcat = concatDocsContent(ctx);
36
+ issues.push(...findPackageManagerMismatch(ctx, docsConcat));
37
+ issues.push(...findNodePolicyClaimMismatch(ctx, docsConcat));
38
+ issues.push(...findUndocumentedOptionalTools(ctx, docsConcat));
39
+ }
40
+ issues.push(...findNodeEngineCiMismatch(ctx));
41
+ return deduplicateIssuesById(issues);
42
+ },
43
+ };
44
+ function concatDocsContent(ctx) {
45
+ const parts = [];
46
+ for (const docFile of ctx.inventory.docsFiles) {
47
+ const content = readBoundedFileText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes, MAX_READ_BYTES);
48
+ if (content !== null)
49
+ parts.push({ relativePath: docFile.relativePath, content });
50
+ }
51
+ return parts;
52
+ }
53
+ function findPackageManagerMismatch(ctx, docs) {
54
+ if (!ctx.sourceOfTruth.lockfile.present)
55
+ return [];
56
+ const issues = [];
57
+ for (const doc of docs) {
58
+ OTHER_PACKAGE_MANAGER_INSTALL_PATTERN.lastIndex = 0;
59
+ for (const match of doc.content.matchAll(OTHER_PACKAGE_MANAGER_INSTALL_PATTERN)) {
60
+ const matchIndex = match.index ?? 0;
61
+ const windowStart = Math.max(0, matchIndex - 150);
62
+ const windowEnd = Math.min(doc.content.length, matchIndex + match[0].length + 150);
63
+ const window = doc.content.slice(windowStart, windowEnd);
64
+ if (NPM_INSTALL_NEARBY_PATTERN.test(window))
65
+ continue;
66
+ issues.push(makeCodeRotIssue({
67
+ auditType: "code-rot",
68
+ detectorId: DETECTOR_ID,
69
+ idCues: [doc.relativePath, "package-manager-mismatch", match[0].toLowerCase()],
70
+ title: `${doc.relativePath} instructs "${match[0]}" as install method, but package-lock.json (npm) is present`,
71
+ description: `${doc.relativePath} mentions "${match[0]}" as an install instruction with no nearby "npm install"/"npm ci" mention, but this project has an npm package-lock.json as its lockfile.`,
72
+ severity: "medium",
73
+ confidence: "medium",
74
+ falsePositiveRisk: "medium",
75
+ category: "dependency-environment-rot",
76
+ recommendedAction: `Update ${doc.relativePath} to instruct "npm install" or "npm ci" instead.`,
77
+ suggestedFixStrategy: `Replace "${match[0]}" with the npm equivalent in ${doc.relativePath}.`,
78
+ validationCommands: ["npm run audit -- --types code-rot --include package,docs"],
79
+ releaseBlocking: false,
80
+ implementationBlocking: false,
81
+ autoFixEligible: false,
82
+ evidence: [
83
+ {
84
+ kind: "file",
85
+ message: `Doc references "${match[0]}" without a nearby npm install/ci mention, while package-lock.json (npm) is present.`,
86
+ filePath: doc.relativePath,
87
+ source: DETECTOR_ID,
88
+ confidence: "medium",
89
+ },
90
+ ],
91
+ affectedFiles: [doc.relativePath, "package-lock.json"],
92
+ }));
93
+ }
94
+ }
95
+ return issues;
96
+ }
97
+ function findNodeEngineCiMismatch(ctx) {
98
+ const engines = ctx.sourceOfTruth.package?.engines;
99
+ const nodeEngine = engines?.node;
100
+ if (!nodeEngine)
101
+ return [];
102
+ const minMatch = nodeEngine.match(/>=?\s*(\d+)/);
103
+ if (!minMatch)
104
+ return [];
105
+ const minVersion = Number(minMatch[1]);
106
+ const issues = [];
107
+ for (const workflow of ctx.sourceOfTruth.ci.workflows) {
108
+ for (const versionStr of workflow.nodeVersionsReferenced) {
109
+ const versionNum = Number(versionStr);
110
+ if (Number.isNaN(versionNum))
111
+ continue;
112
+ if (versionNum >= minVersion)
113
+ continue;
114
+ issues.push(makeCodeRotIssue({
115
+ auditType: "code-rot",
116
+ detectorId: DETECTOR_ID,
117
+ idCues: [workflow.relativePath, "node-engine-ci-mismatch", versionStr],
118
+ title: `CI workflow "${workflow.relativePath}" uses Node ${versionStr}, below package.json's engines.node minimum (${nodeEngine})`,
119
+ description: `package.json declares "engines.node": "${nodeEngine}" (minimum ${minVersion}), but ${workflow.relativePath} references Node ${versionStr}.`,
120
+ severity: "medium",
121
+ confidence: "high",
122
+ falsePositiveRisk: "low",
123
+ category: "dependency-environment-rot",
124
+ recommendedAction: `Update ${workflow.relativePath}'s Node version matrix or relax package.json's engines.node constraint.`,
125
+ suggestedFixStrategy: `Align the Node version in ${workflow.relativePath} with package.json's "engines.node" field.`,
126
+ validationCommands: ["npm run audit -- --types code-rot --include package"],
127
+ releaseBlocking: false,
128
+ implementationBlocking: false,
129
+ autoFixEligible: false,
130
+ evidence: [
131
+ {
132
+ kind: "file",
133
+ message: `CI Node version (${versionStr}) is below package.json's engines.node minimum (${minVersion}).`,
134
+ filePath: workflow.relativePath,
135
+ expected: nodeEngine,
136
+ observed: versionStr,
137
+ source: DETECTOR_ID,
138
+ confidence: "high",
139
+ },
140
+ ],
141
+ affectedFiles: [workflow.relativePath, "package.json"],
142
+ }));
143
+ }
144
+ }
145
+ return issues;
146
+ }
147
+ function findNodePolicyClaimMismatch(ctx, docs) {
148
+ const allWorkflowVersions = new Set();
149
+ for (const workflow of ctx.sourceOfTruth.ci.workflows) {
150
+ for (const v of workflow.nodeVersionsReferenced)
151
+ allWorkflowVersions.add(v);
152
+ }
153
+ // Only flag when the claim is checkable in a low-noise way: CI has exactly
154
+ // one pinned version total, yet docs explicitly claim a multi-version
155
+ // policy.
156
+ if (allWorkflowVersions.size !== 1)
157
+ return [];
158
+ const issues = [];
159
+ for (const doc of docs) {
160
+ if (!NODE_POLICY_CLAIM_PATTERN.test(doc.content))
161
+ continue;
162
+ issues.push(makeCodeRotIssue({
163
+ auditType: "code-rot",
164
+ detectorId: DETECTOR_ID,
165
+ idCues: [doc.relativePath, "node-policy-claim-mismatch"],
166
+ title: `${doc.relativePath} claims a multi-version Node policy, but CI pins exactly one Node version`,
167
+ description: `${doc.relativePath} mentions a "latest two"/Node LTS policy claim, but CI workflows collectively reference exactly one Node version (${[...allWorkflowVersions][0]}).`,
168
+ severity: "medium",
169
+ confidence: "medium",
170
+ falsePositiveRisk: "medium",
171
+ category: "dependency-environment-rot",
172
+ recommendedAction: `Update ${doc.relativePath} or expand the CI Node version matrix to match the claim.`,
173
+ suggestedFixStrategy: `Align the Node-version policy claim in ${doc.relativePath} with the actual CI matrix.`,
174
+ validationCommands: ["npm run audit -- --types code-rot --include package,docs"],
175
+ releaseBlocking: false,
176
+ implementationBlocking: false,
177
+ autoFixEligible: false,
178
+ evidence: [
179
+ {
180
+ kind: "observation",
181
+ message: "Doc claims a multi-version Node policy while CI references exactly one Node version.",
182
+ filePath: doc.relativePath,
183
+ source: DETECTOR_ID,
184
+ confidence: "medium",
185
+ },
186
+ ],
187
+ affectedFiles: [doc.relativePath],
188
+ }));
189
+ }
190
+ return issues;
191
+ }
192
+ function findUndocumentedOptionalTools(ctx, docs) {
193
+ const pkg = ctx.sourceOfTruth.package;
194
+ if (!pkg)
195
+ return [];
196
+ const mentionedTools = new Set();
197
+ for (const [scriptName, command] of Object.entries(pkg.scripts)) {
198
+ OPTIONAL_TOOL_PATTERN.lastIndex = 0;
199
+ for (const match of command.matchAll(OPTIONAL_TOOL_PATTERN)) {
200
+ mentionedTools.add(`${match[1].toLowerCase()}::${scriptName}`);
201
+ }
202
+ }
203
+ if (mentionedTools.size === 0)
204
+ return [];
205
+ const combinedDocsLower = docs.map((d) => d.content).join("\n").toLowerCase();
206
+ const issues = [];
207
+ const seenTools = new Set();
208
+ for (const entry of mentionedTools) {
209
+ const [tool, scriptName] = entry.split("::");
210
+ if (seenTools.has(tool))
211
+ continue;
212
+ if (combinedDocsLower.includes(tool))
213
+ continue;
214
+ seenTools.add(tool);
215
+ issues.push(makeCodeRotIssue({
216
+ auditType: "code-rot",
217
+ detectorId: DETECTOR_ID,
218
+ idCues: ["undocumented-optional-tool", tool],
219
+ title: `Script "${scriptName}" invokes external tool "${tool}", which is not mentioned in any doc`,
220
+ description: `package.json script "${scriptName}" invokes "${tool}", but no doc file mentions it as optional or required.`,
221
+ severity: "low",
222
+ confidence: "low",
223
+ falsePositiveRisk: "medium",
224
+ category: "dependency-environment-rot",
225
+ recommendedAction: `Document "${tool}" as an optional/required tool in the relevant docs.`,
226
+ suggestedFixStrategy: `Add a short mention of "${tool}" (optional/required, and how it's skipped when unavailable) to docs/COMMANDS.md.`,
227
+ validationCommands: ["npm run audit -- --types code-rot --include package,docs"],
228
+ releaseBlocking: false,
229
+ implementationBlocking: false,
230
+ autoFixEligible: false,
231
+ evidence: [
232
+ {
233
+ kind: "observation",
234
+ message: `"${tool}" appears in a package.json script command but not in any doc file.`,
235
+ filePath: "package.json",
236
+ source: DETECTOR_ID,
237
+ confidence: "low",
238
+ },
239
+ ],
240
+ affectedFiles: ["package.json"],
241
+ }));
242
+ }
243
+ return issues;
244
+ }
@@ -0,0 +1,219 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { currentClaimPatternFor, plannedClaimPatternFor, FULL_RELEASE_GATE_FOR_SCOPED_CHECKS_PATTERN, isNegatedNearby, } from "../utils/docClaimPatterns.js";
4
+ import { deduplicateIssuesById, makeCodeRotIssue } from "../utils/issueFactories.js";
5
+ import { splitLines } from "../utils/textLines.js";
6
+ // ---------------------------------------------------------------------------
7
+ // v0.3.0 Batch 3 — documentation/code mismatch detector.
8
+ //
9
+ // Deterministic, regex/structured-cue only (no LLM, no broad semantic
10
+ // interpretation, per Batch 3 scope). Publish/release claims and doc-vs-
11
+ // package version mismatches are deliberately owned by
12
+ // packageReleaseRotDetector.ts instead of here (both detectors' Batch 3
13
+ // spec sections list overlapping bullets for those two checks; this
14
+ // detector focuses on feature-implementation claims and the
15
+ // full-release-gate-for-scoped-checks wording pattern, so the same
16
+ // underlying signal is never reported twice by two different detectors).
17
+ // ---------------------------------------------------------------------------
18
+ const DETECTOR_ID = "docs-code-mismatch";
19
+ const MAX_DOC_READ_BYTES = 200_000;
20
+ // Deliberately small and conservative -- only features with an unambiguous,
21
+ // directly-observable source-of-truth signal are checked. Audit's own
22
+ // implementation status is intentionally excluded to avoid a
23
+ // self-referential "is the audit tool auditing claims about itself"
24
+ // complication in this batch.
25
+ const FEATURE_CHECKS = [
26
+ {
27
+ featureId: "security-validation",
28
+ label: "security validation",
29
+ subjectPattern: "security validation",
30
+ isImplemented: (sot) => sot.security.hasSecurityValidationSourceDir && sot.security.hasSecurityValidateScript,
31
+ },
32
+ {
33
+ featureId: "experiment-framework",
34
+ label: "experiment framework",
35
+ subjectPattern: "experiment (?:framework|plugin)",
36
+ isImplemented: (sot) => sot.experiment.hasExperimentsSourceDir && sot.experiment.hasExperimentListScript,
37
+ },
38
+ ];
39
+ export const DOCS_CODE_MISMATCH_DETECTOR = {
40
+ id: DETECTOR_ID,
41
+ auditType: "code-rot",
42
+ title: "Documentation/code mismatch",
43
+ description: "Detects deterministic mismatches between documentation claims and source-of-truth data: feature-implementation claims and full-release-gate-for-scoped-checks wording.",
44
+ supportedIncludeAreas: ["docs", "architecture", "cli", "package"],
45
+ shouldSkip: (ctx) => {
46
+ if (!ctx.config.include.includes("docs")) {
47
+ return { skip: true, reason: "--include does not select docs; this detector's checks are docs-based." };
48
+ }
49
+ return { skip: false };
50
+ },
51
+ run: (ctx) => {
52
+ const issues = [];
53
+ for (const docFile of ctx.inventory.docsFiles) {
54
+ const content = readBoundedText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes);
55
+ if (content === null)
56
+ continue;
57
+ const lines = splitLines(content);
58
+ for (let i = 0; i < lines.length; i++) {
59
+ const line = lines[i];
60
+ for (const feature of FEATURE_CHECKS) {
61
+ const currentMatch = currentClaimPatternFor(feature.subjectPattern).test(line);
62
+ const plannedMatch = plannedClaimPatternFor(feature.subjectPattern).test(line);
63
+ const implemented = feature.isImplemented(ctx.sourceOfTruth);
64
+ if (currentMatch && plannedMatch) {
65
+ // Same line claims both current and planned for the same
66
+ // subject -- genuinely ambiguous, reported as low-confidence
67
+ // info rather than skipped silently.
68
+ issues.push(makeAmbiguousIssue(docFile.relativePath, i + 1, line, feature.label));
69
+ continue;
70
+ }
71
+ if (currentMatch && !implemented) {
72
+ issues.push(makeCurrentButMissingIssue(docFile.relativePath, i + 1, line, feature));
73
+ }
74
+ else if (plannedMatch && implemented) {
75
+ issues.push(makePlannedButImplementedIssue(docFile.relativePath, i + 1, line, feature));
76
+ }
77
+ }
78
+ if (FULL_RELEASE_GATE_FOR_SCOPED_CHECKS_PATTERN.test(line) && !isNegatedNearby(line)) {
79
+ issues.push(makeFullReleaseGateIssue(docFile.relativePath, i + 1, line));
80
+ }
81
+ }
82
+ }
83
+ return deduplicateIssuesById(issues);
84
+ },
85
+ };
86
+ function makeCurrentButMissingIssue(relativePath, lineNumber, excerpt, feature) {
87
+ return makeCodeRotIssue({
88
+ auditType: "code-rot",
89
+ detectorId: DETECTOR_ID,
90
+ idCues: [relativePath, "current-but-missing", feature.featureId],
91
+ title: `Docs claim "${feature.label}" is implemented/current, but source-of-truth shows it is missing`,
92
+ description: `${relativePath}:${lineNumber} claims "${feature.label}" is implemented/current, but the expected source directory/script is not present.`,
93
+ severity: "high",
94
+ confidence: "medium",
95
+ falsePositiveRisk: "medium",
96
+ category: "docs-feature-mismatch",
97
+ recommendedAction: `Either implement the missing "${feature.label}" pieces, or update ${relativePath} to describe it as planned.`,
98
+ suggestedFixStrategy: `Correct the "${feature.label}" claim in ${relativePath} to match its actual implementation state.`,
99
+ validationCommands: ["npm run audit -- --types code-rot --include docs"],
100
+ releaseBlocking: false,
101
+ implementationBlocking: false,
102
+ autoFixEligible: false,
103
+ evidence: [
104
+ {
105
+ kind: "file",
106
+ message: `Doc claims "${feature.label}" is current/implemented; source-of-truth signal indicates it is not.`,
107
+ filePath: relativePath,
108
+ line: lineNumber,
109
+ excerpt: excerpt.trim().slice(0, 200),
110
+ source: DETECTOR_ID,
111
+ confidence: "medium",
112
+ },
113
+ ],
114
+ affectedFiles: [relativePath],
115
+ });
116
+ }
117
+ function makePlannedButImplementedIssue(relativePath, lineNumber, excerpt, feature) {
118
+ return makeCodeRotIssue({
119
+ auditType: "code-rot",
120
+ detectorId: DETECTOR_ID,
121
+ idCues: [relativePath, "planned-but-implemented", feature.featureId],
122
+ title: `Docs claim "${feature.label}" is planned/future, but it is already implemented`,
123
+ description: `${relativePath}:${lineNumber} describes "${feature.label}" as planned/future, but source-of-truth shows it is already present.`,
124
+ severity: "medium",
125
+ confidence: "medium",
126
+ falsePositiveRisk: "medium",
127
+ category: "docs-feature-mismatch",
128
+ recommendedAction: `Update ${relativePath} to describe "${feature.label}" as implemented/current.`,
129
+ suggestedFixStrategy: `Move the "${feature.label}" description out of the planned/roadmap section in ${relativePath}.`,
130
+ validationCommands: ["npm run audit -- --types code-rot --include docs"],
131
+ releaseBlocking: false,
132
+ implementationBlocking: false,
133
+ autoFixEligible: false,
134
+ evidence: [
135
+ {
136
+ kind: "file",
137
+ message: `Doc claims "${feature.label}" is planned/future; source-of-truth signal indicates it is already implemented.`,
138
+ filePath: relativePath,
139
+ line: lineNumber,
140
+ excerpt: excerpt.trim().slice(0, 200),
141
+ source: DETECTOR_ID,
142
+ confidence: "medium",
143
+ },
144
+ ],
145
+ affectedFiles: [relativePath],
146
+ });
147
+ }
148
+ function makeAmbiguousIssue(relativePath, lineNumber, excerpt, featureLabel) {
149
+ return makeCodeRotIssue({
150
+ auditType: "code-rot",
151
+ detectorId: DETECTOR_ID,
152
+ idCues: [relativePath, "ambiguous", featureLabel, String(lineNumber)],
153
+ title: `Ambiguous implementation-status claim for "${featureLabel}"`,
154
+ description: `${relativePath}:${lineNumber} appears to describe "${featureLabel}" as both current/implemented and planned/future in the same line -- not automatically resolvable.`,
155
+ severity: "info",
156
+ confidence: "low",
157
+ falsePositiveRisk: "high",
158
+ category: "docs-feature-mismatch-ambiguous",
159
+ recommendedAction: "Manually review this line for clarity.",
160
+ suggestedFixStrategy: "Rewrite the sentence so the implementation status of the feature is unambiguous.",
161
+ validationCommands: ["npm run audit -- --types code-rot --include docs"],
162
+ releaseBlocking: false,
163
+ implementationBlocking: false,
164
+ autoFixEligible: false,
165
+ evidence: [
166
+ {
167
+ kind: "file",
168
+ message: "Both a current/implemented cue and a planned/future cue matched the same line for the same subject.",
169
+ filePath: relativePath,
170
+ line: lineNumber,
171
+ excerpt: excerpt.trim().slice(0, 200),
172
+ source: DETECTOR_ID,
173
+ confidence: "low",
174
+ },
175
+ ],
176
+ affectedFiles: [relativePath],
177
+ });
178
+ }
179
+ function makeFullReleaseGateIssue(relativePath, lineNumber, excerpt) {
180
+ return makeCodeRotIssue({
181
+ auditType: "code-rot",
182
+ detectorId: DETECTOR_ID,
183
+ idCues: [relativePath, "full-release-gate-scoped-claim", String(lineNumber)],
184
+ title: `Docs describe a scoped run as a full release gate`,
185
+ description: `${relativePath}:${lineNumber} appears to describe a scoped (non-full) run as if it were a complete release gate.`,
186
+ severity: "medium",
187
+ confidence: "medium",
188
+ falsePositiveRisk: "medium",
189
+ category: "docs-feature-mismatch",
190
+ recommendedAction: `Clarify in ${relativePath} that a scoped run is not a full release gate.`,
191
+ suggestedFixStrategy: `Add explicit "scoped run, not a full release gate" wording near this claim in ${relativePath}.`,
192
+ validationCommands: ["npm run audit -- --types code-rot --include docs"],
193
+ releaseBlocking: false,
194
+ implementationBlocking: false,
195
+ autoFixEligible: false,
196
+ evidence: [
197
+ {
198
+ kind: "file",
199
+ message: "Matched a scoped-run-described-as-full-release-gate phrasing pattern.",
200
+ filePath: relativePath,
201
+ line: lineNumber,
202
+ excerpt: excerpt.trim().slice(0, 200),
203
+ source: DETECTOR_ID,
204
+ confidence: "medium",
205
+ },
206
+ ],
207
+ affectedFiles: [relativePath],
208
+ });
209
+ }
210
+ function readBoundedText(root, relativePath, sizeBytes) {
211
+ if (sizeBytes > MAX_DOC_READ_BYTES)
212
+ return null;
213
+ try {
214
+ return fs.readFileSync(path.join(root, relativePath), "utf8");
215
+ }
216
+ catch {
217
+ return null;
218
+ }
219
+ }