@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.
- package/README.md +17 -7
- package/dist/scripts/audits/runAudit.js +102 -0
- package/dist/src/audits/codeRot/codeRotDetectors.js +17 -0
- package/dist/src/audits/codeRot/detectors/architectureDriftDetector.js +271 -0
- package/dist/src/audits/codeRot/detectors/crossPlatformRotDetector.js +214 -0
- package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +377 -0
- package/dist/src/audits/codeRot/detectors/dependencyEnvironmentRotDetector.js +244 -0
- package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +219 -0
- package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +350 -0
- package/dist/src/audits/codeRot/detectors/packageReleaseRotDetector.js +325 -0
- package/dist/src/audits/codeRot/detectors/securityValidationAssumptionRotDetector.js +385 -0
- package/dist/src/audits/codeRot/detectors/staleCommandReferenceDetector.js +135 -0
- package/dist/src/audits/codeRot/detectors/testRotDetector.js +410 -0
- package/dist/src/audits/codeRot/utils/boundedRead.js +23 -0
- package/dist/src/audits/codeRot/utils/docClaimPatterns.js +62 -0
- package/dist/src/audits/codeRot/utils/docCommandReferences.js +64 -0
- package/dist/src/audits/codeRot/utils/filePatternUtils.js +79 -0
- package/dist/src/audits/codeRot/utils/issueFactories.js +38 -0
- package/dist/src/audits/codeRot/utils/sourceFactsLookup.js +53 -0
- package/dist/src/audits/codeRot/utils/textLines.js +12 -0
- package/dist/src/audits/core/auditConfig.js +125 -0
- package/dist/src/audits/core/auditExitCode.js +75 -0
- package/dist/src/audits/core/auditIssue.js +20 -0
- package/dist/src/audits/core/auditRegistry.js +40 -0
- package/dist/src/audits/core/auditRunner.js +74 -0
- package/dist/src/audits/core/auditTarget.js +19 -0
- package/dist/src/audits/core/auditTypes.js +30 -0
- package/dist/src/audits/core/collectSourceFacts.js +142 -0
- package/dist/src/audits/core/languageAnalyzerRegistry.js +33 -0
- package/dist/src/audits/core/projectInventory.js +458 -0
- package/dist/src/audits/core/sourceFacts.js +17 -0
- package/dist/src/audits/core/sourceOfTruth.js +339 -0
- package/dist/src/audits/core/textLines.js +24 -0
- package/dist/src/audits/core/typescriptJavaScriptAnalyzer.js +299 -0
- package/dist/src/audits/report/auditReportModel.js +253 -0
- package/dist/src/audits/report/auditReportPaths.js +13 -0
- package/dist/src/audits/report/renderAuditJsonReport.js +30 -0
- package/dist/src/audits/report/renderAuditTextReport.js +181 -0
- package/dist/src/audits/report/sanitizeAuditText.js +39 -0
- package/dist/src/audits/report/writeAuditReports.js +27 -0
- package/dist/src/core/resolveCommand.js +53 -2
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# my-dev-kit-lab
|
|
2
2
|
|
|
3
|
-
my-dev-kit-lab is the experiment, evidence, reporting, security-validation, and
|
|
4
|
-
|
|
5
|
-
The current
|
|
3
|
+
my-dev-kit-lab is the experiment, evidence, reporting, security-validation, and audit companion for my-dev-kit. It runs reproducible experiments that test whether my-dev-kit's graph-guided retrieval helps coding-agent workflows, collects metrics, renders reports, generates plots, captures screenshots, builds gallery outputs, performs automated CLI/package security validation, and runs the generic audit framework.
|
|
4
|
+
|
|
5
|
+
The current published baseline is `v0.3.0`. `v0.3.0` introduced the generic audit framework and the first implemented code-rot detector family. The generic experiment-plugin framework was introduced in `v0.2.0`. The first plugin is `context-strategy-comparison`, which preserves the existing raw-full-file vs my-dev-kit-guided workflow through the plugin runner.
|
|
6
|
+
|
|
7
|
+
The checked-out package is release-prepared for `v0.3.1` on top of the published baseline: normalized language/file-role inventory, source-facts collection, a TypeScript/JavaScript analyzer, source-facts-aware code-rot signals, and source-facts summaries in text/JSON audit reports. `v0.3.1` is not published yet; Python, Java, Kotlin, Android validation, framework-aware profiles, and broader audit types remain future roadmap work.
|
|
6
8
|
|
|
7
9
|
my-dev-kit is most useful when the repository is larger than the task. It helps coding agents work with large codebases through reusable structural indexing, graph-guided retrieval, targeted source slices, and auditable context selection. Results are scoped evidence; the project does not claim that my-dev-kit always saves tokens.
|
|
8
10
|
|
|
@@ -32,6 +34,7 @@ my-dev-kit is most useful when the repository is larger than the task. It helps
|
|
|
32
34
|
- Target-aware experiment execution for local projects via `experiment:run -- --target <path>`
|
|
33
35
|
- Plugin-aware JSON and HTML reports with plugin, target, variant, metric, artifact, warning, skip, and failure metadata
|
|
34
36
|
- Security validation framework: dependency audit, package tarball inspection, CLI adversarial tests, static scans (CodeQL/Semgrep), bounded fuzz smoke, attack-scenario checks, and structured verdict/report output — runnable against any local project via `security:validate --target <path>`
|
|
37
|
+
- Generic audit framework in the current published `v0.3.0` baseline: `npm run audit` runs heuristic, conservative code-rot detectors and writes text/JSON reports under `reports/audits/code-rot/` by default. Only the `code-rot` audit type is currently implemented; `quality`, `security`, `project`, and `all` audit types are planned and fail cleanly instead of running. `npm run audit` supports `--target`, `--types code-rot`, and other flags — see [docs/COMMANDS.md](docs/COMMANDS.md). Audit does not modify target files, does not auto-fix issues, does not run `security:validate`, and does not perform release-readiness determination.
|
|
35
38
|
|
|
36
39
|
---
|
|
37
40
|
|
|
@@ -200,9 +203,16 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
|
|
|
200
203
|
|
|
201
204
|
---
|
|
202
205
|
|
|
203
|
-
## Current baseline release positioning
|
|
204
|
-
|
|
205
|
-
my-dev-kit-lab is at a working baseline. The raw-vs-indexed experiment pipeline is fully implemented and produces reproducible artifacts. Real-agent campaign support exists for Codex and Claude. The current
|
|
206
|
+
## Current baseline release positioning
|
|
207
|
+
|
|
208
|
+
my-dev-kit-lab is at a working baseline. The raw-vs-indexed experiment pipeline is fully implemented and produces reproducible artifacts. Real-agent campaign support exists for Codex and Claude. The current published npm baseline is `v0.3.0`, which includes the generic audit framework and the first implemented code-rot detector family on top of the earlier experiment and automated security-validation work.
|
|
209
|
+
|
|
210
|
+
Planned roadmap direction after the published baseline:
|
|
211
|
+
|
|
212
|
+
- `v0.3.1`: language-aware code-rot substrate plus TypeScript/JavaScript support, release-prepared and not yet published
|
|
213
|
+
- `v0.3.2` through `v0.3.4`: Python, Java/Kotlin, and cross-language stability for the language-aware code-rot track
|
|
214
|
+
- `v0.4.0` through `v0.4.2`: Android automated security validation
|
|
215
|
+
- manual pentest: post-v1 / version TBD
|
|
206
216
|
|
|
207
217
|
---
|
|
208
218
|
|
|
@@ -240,7 +250,7 @@ Each security command can validate my-dev-kit-lab itself or another local projec
|
|
|
240
250
|
|
|
241
251
|
`security:validate` supports `--checks`, `--profile`, `--format`, `--fail-on`, `--out`, and `--report-prefix`. The no-flag path remains backward compatible and still runs the classic implemented check groups: `deps`, `package`, `static`, `cli-adversarial`, and `fuzz`. Supplying `--profile` without `--checks` swaps in that profile's default checks; supplying explicit `--checks` always wins over profile defaults.
|
|
242
252
|
|
|
243
|
-
The current attack-scenario checks cover boundary, subprocess, secrets, and network assumptions. They are automated adversarial checks, not a manual pentest. `verdictImpact` metadata from each registered scenario drives blocker categorization in report reasoning, and narrowed `--checks` runs are labeled as scoped rather than described as a full release gate.
|
|
253
|
+
The current attack-scenario checks cover boundary, subprocess, secrets, and network assumptions. They are automated adversarial checks, not a manual pentest. Manual pentest is deferred until after `v1.0.0`. `verdictImpact` metadata from each registered scenario drives blocker categorization in report reasoning, and narrowed `--checks` runs are labeled as scoped rather than described as a full release gate.
|
|
244
254
|
|
|
245
255
|
JSON report poisoning/config-injection checks use a baseline-diff schema guard so legitimate additive JSON fields do not fail the guard while payload-created trusted top-level fields still do. Text reports are sanitized to strip ANSI/control-byte payloads before rendering.
|
|
246
256
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseAuditArgs, normalizeAuditConfig } from "../../src/audits/core/auditConfig.js";
|
|
3
|
+
import { resolveAuditTarget } from "../../src/audits/core/auditTarget.js";
|
|
4
|
+
import { runAudit } from "../../src/audits/core/auditRunner.js";
|
|
5
|
+
import { AUDIT_EXIT_CODES } from "../../src/audits/core/auditExitCode.js";
|
|
6
|
+
import { resolveToolRoot } from "../security/resolveToolRoot.js";
|
|
7
|
+
import { buildAuditReportModel } from "../../src/audits/report/auditReportModel.js";
|
|
8
|
+
import { writeAuditReports } from "../../src/audits/report/writeAuditReports.js";
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// v0.3.0 Batch 5 -- npm run audit entrypoint.
|
|
11
|
+
//
|
|
12
|
+
// Thin wiring only: parse args -> normalize config -> resolve target ->
|
|
13
|
+
// runAudit() -> buildAuditReportModel() -> writeAuditReports() -> print a
|
|
14
|
+
// concise console summary -> set process.exitCode. All report-shaping logic
|
|
15
|
+
// (the former inline buildJsonReport()/renderMinimalTextSummary()/
|
|
16
|
+
// summarizeInventory()/summarizeSourceOfTruth() functions from Batch 1/2)
|
|
17
|
+
// now lives in src/audits/report/ -- see auditReportModel.ts,
|
|
18
|
+
// renderAuditJsonReport.ts, renderAuditTextReport.ts, writeAuditReports.ts.
|
|
19
|
+
// Mirrors the structure of scripts/security/validate.ts.
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const USAGE = "Usage: npm run audit -- [--target <path>] [--types code-rot] " +
|
|
22
|
+
"[--include docs,tests,package,architecture,cli] [--format text,json] " +
|
|
23
|
+
"[--fail-on blocker|high|medium|low|none] [--out <path>]";
|
|
24
|
+
const rawArgs = process.argv.slice(2);
|
|
25
|
+
const toolRoot = resolveToolRoot(import.meta.url);
|
|
26
|
+
let config;
|
|
27
|
+
try {
|
|
28
|
+
const args = parseAuditArgs(rawArgs);
|
|
29
|
+
config = normalizeAuditConfig(args, toolRoot);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33
|
+
console.error(`\nERROR: ${msg}`);
|
|
34
|
+
console.error(USAGE);
|
|
35
|
+
process.exitCode = AUDIT_EXIT_CODES.FATAL_ERROR;
|
|
36
|
+
process.exit(AUDIT_EXIT_CODES.FATAL_ERROR);
|
|
37
|
+
}
|
|
38
|
+
let target;
|
|
39
|
+
try {
|
|
40
|
+
target = resolveAuditTarget(config.targetPathArg, toolRoot);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
44
|
+
console.error(`\nERROR: ${msg}`);
|
|
45
|
+
console.error(USAGE);
|
|
46
|
+
process.exitCode = AUDIT_EXIT_CODES.FATAL_ERROR;
|
|
47
|
+
process.exit(AUDIT_EXIT_CODES.FATAL_ERROR);
|
|
48
|
+
}
|
|
49
|
+
console.log("=".repeat(60));
|
|
50
|
+
console.log("my-dev-kit-lab audit");
|
|
51
|
+
console.log("=".repeat(60));
|
|
52
|
+
console.log(`Tool root : ${toolRoot}`);
|
|
53
|
+
if (!target.isSelf) {
|
|
54
|
+
console.log(`Target : ${target.rootPath}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(`Mode : self-audit`);
|
|
58
|
+
}
|
|
59
|
+
console.log(`Types : ${config.types.join(", ")}`);
|
|
60
|
+
console.log(`Include : ${config.include.join(", ")}`);
|
|
61
|
+
console.log("");
|
|
62
|
+
let result;
|
|
63
|
+
try {
|
|
64
|
+
result = await runAudit({ config, toolRoot, target });
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
68
|
+
console.error(`\nERROR: audit runtime failure: ${msg}`);
|
|
69
|
+
process.exitCode = AUDIT_EXIT_CODES.FATAL_ERROR;
|
|
70
|
+
process.exit(AUDIT_EXIT_CODES.FATAL_ERROR);
|
|
71
|
+
}
|
|
72
|
+
const model = buildAuditReportModel(result, { target });
|
|
73
|
+
const { writtenPaths } = writeAuditReports({ model, config, outDir: config.out });
|
|
74
|
+
console.log(`Issues found : ${model.summary.totalIssues}`);
|
|
75
|
+
console.log(` blocker=${model.summary.issuesBySeverity.blocker} high=${model.summary.issuesBySeverity.high} medium=${model.summary.issuesBySeverity.medium} low=${model.summary.issuesBySeverity.low} info=${model.summary.issuesBySeverity.info}`);
|
|
76
|
+
console.log(`Skipped detectors : ${model.summary.skippedDetectorCount}`);
|
|
77
|
+
if (model.summary.detectorErrorCount > 0) {
|
|
78
|
+
console.log(`Detector errors : ${model.summary.detectorErrorCount}`);
|
|
79
|
+
for (const e of model.detectorErrors) {
|
|
80
|
+
console.log(` [${e.id}] ${e.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (model.summary.noDetectorsRegistered) {
|
|
84
|
+
console.log(`Note: no code-rot detectors are registered for this run's --types/--include selection -- this run collected inventory/source-of-truth data only.`);
|
|
85
|
+
}
|
|
86
|
+
console.log(`\nInventory: ${model.inventory.totalScannedFileCount} file(s) scanned, ${model.inventory.skippedFileCount} skipped`);
|
|
87
|
+
console.log(` source=${model.inventory.filesByCategory.source} tests=${model.inventory.filesByCategory.tests} docs=${model.inventory.filesByCategory.docs} package=${model.inventory.filesByCategory.package} config=${model.inventory.filesByCategory.config} scripts=${model.inventory.filesByCategory.scripts} ci=${model.inventory.filesByCategory.ci}`);
|
|
88
|
+
console.log(`Source of truth: package=${model.sourceOfTruth.packageName ?? "(none)"}@${model.sourceOfTruth.packageVersion ?? "?"} readme=${model.sourceOfTruth.hasReadme} changelog=${model.sourceOfTruth.hasChangelog} ciWorkflows=${model.sourceOfTruth.ciWorkflowCount}`);
|
|
89
|
+
console.log(`Source facts: ${model.sourceFacts.totalFilesAnalyzed} file(s) analyzed (parsed=${model.sourceFacts.filesByParseStatus.parsed} file-level-only=${model.sourceFacts.filesByParseStatus["file-level-only"]} unsupported=${model.sourceFacts.filesByParseStatus.unsupported})`);
|
|
90
|
+
if (writtenPaths.length > 0) {
|
|
91
|
+
console.log(`\nReports written:`);
|
|
92
|
+
for (const p of writtenPaths) {
|
|
93
|
+
console.log(` ${p}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
console.log(`\nHighest severity: ${model.summary.highestSeverity ?? "(none)"}`);
|
|
97
|
+
console.log(`Verdict: ${model.summary.finalVerdictLabel}`);
|
|
98
|
+
console.log(`Exit reason: ${model.exit.reason}`);
|
|
99
|
+
console.log(model.exit.code === AUDIT_EXIT_CODES.SUCCESS
|
|
100
|
+
? `Exit 0 -- audit completed.`
|
|
101
|
+
: `Exit 1 -- fail-on threshold breached.`);
|
|
102
|
+
process.exitCode = model.exit.code;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// v0.3.0 Batch 3 — code-rot detector aggregator.
|
|
3
|
+
//
|
|
4
|
+
// Single import surface for src/audits/core/auditRegistry.ts's
|
|
5
|
+
// DEFAULT_AUDIT_REGISTRY. Keeps the registry file from needing to know each
|
|
6
|
+
// detector's individual file path.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export { STALE_COMMAND_REFERENCE_DETECTOR } from "./detectors/staleCommandReferenceDetector.js";
|
|
9
|
+
export { DOCS_CODE_MISMATCH_DETECTOR } from "./detectors/docsCodeMismatchDetector.js";
|
|
10
|
+
export { PACKAGE_RELEASE_ROT_DETECTOR } from "./detectors/packageReleaseRotDetector.js";
|
|
11
|
+
export { DUPLICATE_IMPLEMENTATION_DETECTOR } from "./detectors/duplicateImplementationDetector.js";
|
|
12
|
+
export { DEAD_CODE_CANDIDATE_DETECTOR } from "./detectors/deadCodeCandidateDetector.js";
|
|
13
|
+
export { TEST_ROT_DETECTOR } from "./detectors/testRotDetector.js";
|
|
14
|
+
export { ARCHITECTURE_DRIFT_DETECTOR } from "./detectors/architectureDriftDetector.js";
|
|
15
|
+
export { DEPENDENCY_ENVIRONMENT_ROT_DETECTOR } from "./detectors/dependencyEnvironmentRotDetector.js";
|
|
16
|
+
export { CROSS_PLATFORM_ROT_DETECTOR } from "./detectors/crossPlatformRotDetector.js";
|
|
17
|
+
export { SECURITY_VALIDATION_ASSUMPTION_ROT_DETECTOR } from "./detectors/securityValidationAssumptionRotDetector.js";
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { currentClaimPatternFor } from "../utils/docClaimPatterns.js";
|
|
4
|
+
import { readBoundedFileText } from "../utils/boundedRead.js";
|
|
5
|
+
import { deduplicateIssuesById, makeCodeRotIssue } from "../utils/issueFactories.js";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// v0.3.0 Batch 4 — architecture drift detector.
|
|
8
|
+
//
|
|
9
|
+
// Deterministic path/claim checks only -- no semantic judgment. Two of the
|
|
10
|
+
// checks below (stale/missing backtick path mentions, and the hardcoded
|
|
11
|
+
// "audit framework described as planned while implemented" check) are
|
|
12
|
+
// deliberately scoped to docs/ARCHITECTURE.md and docs/CURRENT_STATE.md
|
|
13
|
+
// only, not every doc file. Both files' own headers state their purpose is
|
|
14
|
+
// describing CURRENT implemented state ("This file is the concise source of
|
|
15
|
+
// truth for the checked-in implementation" / "Current implemented
|
|
16
|
+
// architecture"). docs/ROADMAP.md, by contrast, is full of legitimate
|
|
17
|
+
// backtick-quoted "Suggested architecture" paths for genuinely
|
|
18
|
+
// not-yet-built future work items -- scanning it with the same "does this
|
|
19
|
+
// path exist" check would produce many false positives against accurate,
|
|
20
|
+
// correctly-labeled-as-future roadmap content. Restricting scope keeps this
|
|
21
|
+
// detector's signal high-confidence.
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const DETECTOR_ID = "architecture-drift";
|
|
24
|
+
const MAX_READ_BYTES = 200_000;
|
|
25
|
+
const CURRENT_STATE_DOC_BASENAMES = new Set(["architecture.md", "current_state.md"]);
|
|
26
|
+
const BACKTICK_SRC_PATH_PATTERN = /`(src\/[\w./-]+|scripts\/[\w./-]+)`/g;
|
|
27
|
+
const HISTORICAL_FRAMING_PATTERN = /\b(historical|previously|used to|no longer|removed|deprecated)\b/i;
|
|
28
|
+
const PLANNED_FRAMING_PATTERN = /\b(planned|future|roadmap|not (?:yet )?implemented)\b/i;
|
|
29
|
+
const AUDIT_FRAMEWORK_SUBJECT_PATTERN = /\baudit (?:framework|contracts)\b|\bcode rot detector\b/i;
|
|
30
|
+
const MIN_SOURCE_FILES_FOR_MAJOR_DIR = 3;
|
|
31
|
+
export const ARCHITECTURE_DRIFT_DETECTOR = {
|
|
32
|
+
id: DETECTOR_ID,
|
|
33
|
+
auditType: "code-rot",
|
|
34
|
+
title: "Architecture drift",
|
|
35
|
+
description: "Detects deterministic drift between documentation and actual source layout: doc-claimed paths that don't exist, the audit framework described as planned while implemented, major source directories missing from ARCHITECTURE.md, and unhedged manual-pentest/Android implementation claims.",
|
|
36
|
+
supportedIncludeAreas: ["architecture", "docs", "package", "cli"],
|
|
37
|
+
shouldSkip: (ctx) => {
|
|
38
|
+
if (!ctx.config.include.includes("docs") && !ctx.config.include.includes("architecture")) {
|
|
39
|
+
return { skip: true, reason: "--include selects neither docs nor architecture; this detector's checks are docs-based." };
|
|
40
|
+
}
|
|
41
|
+
return { skip: false };
|
|
42
|
+
},
|
|
43
|
+
run: (ctx) => {
|
|
44
|
+
const issues = [];
|
|
45
|
+
const currentStateDocs = ctx.inventory.docsFiles.filter((f) => CURRENT_STATE_DOC_BASENAMES.has(path.basename(f.relativePath).toLowerCase()));
|
|
46
|
+
for (const docFile of currentStateDocs) {
|
|
47
|
+
const content = readBoundedFileText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes, MAX_READ_BYTES);
|
|
48
|
+
if (content === null)
|
|
49
|
+
continue;
|
|
50
|
+
issues.push(...findStaleOrMissingBacktickPaths(ctx, docFile.relativePath, content));
|
|
51
|
+
}
|
|
52
|
+
issues.push(...findAuditFrameworkDescribedAsPlanned(ctx, currentStateDocs));
|
|
53
|
+
const architectureDoc = ctx.inventory.docsFiles.find((f) => path.basename(f.relativePath).toLowerCase() === "architecture.md");
|
|
54
|
+
if (architectureDoc) {
|
|
55
|
+
const content = readBoundedFileText(ctx.target.rootPath, architectureDoc.relativePath, architectureDoc.sizeBytes, MAX_READ_BYTES);
|
|
56
|
+
if (content !== null) {
|
|
57
|
+
issues.push(...findMajorDirsAbsentFromArchitectureDoc(ctx, architectureDoc.relativePath, content));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const docFile of ctx.inventory.docsFiles) {
|
|
61
|
+
const content = readBoundedFileText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes, MAX_READ_BYTES);
|
|
62
|
+
if (content === null)
|
|
63
|
+
continue;
|
|
64
|
+
issues.push(...findUnsupportedCurrentClaims(docFile.relativePath, content));
|
|
65
|
+
}
|
|
66
|
+
return deduplicateIssuesById(issues);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
function pathExistsInInventoryOrDisk(ctx, claimedPath) {
|
|
70
|
+
const trimmed = claimedPath.replace(/\/$/, "");
|
|
71
|
+
const inventoryHit = ctx.inventory.files.some((f) => f.relativePath === trimmed || f.relativePath.startsWith(`${trimmed}/`));
|
|
72
|
+
if (inventoryHit)
|
|
73
|
+
return true;
|
|
74
|
+
try {
|
|
75
|
+
return fs.existsSync(path.join(ctx.target.rootPath, trimmed));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function findStaleOrMissingBacktickPaths(ctx, docRelativePath, content) {
|
|
82
|
+
const issues = [];
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
BACKTICK_SRC_PATH_PATTERN.lastIndex = 0;
|
|
85
|
+
for (const match of content.matchAll(BACKTICK_SRC_PATH_PATTERN)) {
|
|
86
|
+
const claimedPath = match[1];
|
|
87
|
+
if (seen.has(claimedPath))
|
|
88
|
+
continue;
|
|
89
|
+
seen.add(claimedPath);
|
|
90
|
+
if (pathExistsInInventoryOrDisk(ctx, claimedPath))
|
|
91
|
+
continue;
|
|
92
|
+
const matchIndex = match.index ?? 0;
|
|
93
|
+
const windowStart = Math.max(0, matchIndex - 200);
|
|
94
|
+
const windowEnd = Math.min(content.length, matchIndex + claimedPath.length + 200);
|
|
95
|
+
const window = content.slice(windowStart, windowEnd);
|
|
96
|
+
const isHistorical = HISTORICAL_FRAMING_PATTERN.test(window);
|
|
97
|
+
issues.push(makeCodeRotIssue({
|
|
98
|
+
auditType: "code-rot",
|
|
99
|
+
detectorId: DETECTOR_ID,
|
|
100
|
+
idCues: [docRelativePath, isHistorical ? "stale-path-mention" : "claimed-path-missing", claimedPath],
|
|
101
|
+
title: isHistorical
|
|
102
|
+
? `${docRelativePath} mentions a stale (historically-framed) path: "${claimedPath}"`
|
|
103
|
+
: `${docRelativePath} claims path "${claimedPath}" exists, but it does not`,
|
|
104
|
+
description: isHistorical
|
|
105
|
+
? `${docRelativePath} mentions "${claimedPath}" with historical framing, and the path no longer exists on disk. Low-severity because it is explicitly framed as historical.`
|
|
106
|
+
: `${docRelativePath} references "${claimedPath}" as a current path (backtick code span), but no such file or directory exists in the project.`,
|
|
107
|
+
severity: isHistorical ? "low" : "medium",
|
|
108
|
+
confidence: isHistorical ? "medium" : "medium",
|
|
109
|
+
falsePositiveRisk: "medium",
|
|
110
|
+
category: "architecture-drift",
|
|
111
|
+
recommendedAction: isHistorical
|
|
112
|
+
? `Confirm whether the historical reference to "${claimedPath}" is still useful context.`
|
|
113
|
+
: `Update ${docRelativePath} to reference an existing path, or restore/implement "${claimedPath}".`,
|
|
114
|
+
suggestedFixStrategy: `Correct or remove the "${claimedPath}" reference in ${docRelativePath}.`,
|
|
115
|
+
validationCommands: ["npm run audit -- --types code-rot --include architecture,docs"],
|
|
116
|
+
releaseBlocking: false,
|
|
117
|
+
implementationBlocking: false,
|
|
118
|
+
autoFixEligible: false,
|
|
119
|
+
evidence: [
|
|
120
|
+
{
|
|
121
|
+
kind: "file",
|
|
122
|
+
message: "Backtick-quoted path does not exist on disk.",
|
|
123
|
+
filePath: docRelativePath,
|
|
124
|
+
excerpt: claimedPath,
|
|
125
|
+
source: DETECTOR_ID,
|
|
126
|
+
confidence: "medium",
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
affectedFiles: [docRelativePath],
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
return issues;
|
|
133
|
+
}
|
|
134
|
+
function findAuditFrameworkDescribedAsPlanned(ctx, currentStateDocs) {
|
|
135
|
+
const auditImplemented = fs.existsSync(path.join(ctx.target.rootPath, "src", "audits")) && "audit" in (ctx.sourceOfTruth.package?.scripts ?? {});
|
|
136
|
+
if (!auditImplemented)
|
|
137
|
+
return [];
|
|
138
|
+
const issues = [];
|
|
139
|
+
for (const docFile of currentStateDocs) {
|
|
140
|
+
const content = readBoundedFileText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes, MAX_READ_BYTES);
|
|
141
|
+
if (content === null)
|
|
142
|
+
continue;
|
|
143
|
+
AUDIT_FRAMEWORK_SUBJECT_PATTERN.lastIndex = 0;
|
|
144
|
+
const subjectMatch = AUDIT_FRAMEWORK_SUBJECT_PATTERN.exec(content);
|
|
145
|
+
if (!subjectMatch)
|
|
146
|
+
continue;
|
|
147
|
+
const matchIndex = subjectMatch.index;
|
|
148
|
+
const windowStart = Math.max(0, matchIndex - 400);
|
|
149
|
+
const windowEnd = Math.min(content.length, matchIndex + subjectMatch[0].length + 400);
|
|
150
|
+
const window = content.slice(windowStart, windowEnd);
|
|
151
|
+
if (!PLANNED_FRAMING_PATTERN.test(window))
|
|
152
|
+
continue;
|
|
153
|
+
issues.push(makeCodeRotIssue({
|
|
154
|
+
auditType: "code-rot",
|
|
155
|
+
detectorId: DETECTOR_ID,
|
|
156
|
+
idCues: [docFile.relativePath, "audit-framework-planned-but-implemented"],
|
|
157
|
+
title: `${docFile.relativePath} describes the audit framework as planned/future, but it is already implemented`,
|
|
158
|
+
description: `${docFile.relativePath} mentions "${subjectMatch[0]}" near planned/future/not-implemented wording, but src/audits exists and package.json defines an "audit" script -- the audit framework is already implemented, not merely planned.`,
|
|
159
|
+
severity: "high",
|
|
160
|
+
confidence: "medium",
|
|
161
|
+
falsePositiveRisk: "medium",
|
|
162
|
+
category: "architecture-drift",
|
|
163
|
+
recommendedAction: `Update ${docFile.relativePath} to describe the audit framework's actual current implementation status.`,
|
|
164
|
+
suggestedFixStrategy: `Move the audit-framework description in ${docFile.relativePath} out of the planned/future section.`,
|
|
165
|
+
validationCommands: ["npm run audit -- --types code-rot --include architecture,docs"],
|
|
166
|
+
releaseBlocking: false,
|
|
167
|
+
implementationBlocking: false,
|
|
168
|
+
autoFixEligible: false,
|
|
169
|
+
evidence: [
|
|
170
|
+
{
|
|
171
|
+
kind: "file",
|
|
172
|
+
message: "Doc describes the audit framework as planned/future near this mention, but src/audits exists and an \"audit\" npm script is defined.",
|
|
173
|
+
filePath: docFile.relativePath,
|
|
174
|
+
excerpt: subjectMatch[0],
|
|
175
|
+
source: DETECTOR_ID,
|
|
176
|
+
confidence: "medium",
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
affectedFiles: [docFile.relativePath],
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
return issues;
|
|
183
|
+
}
|
|
184
|
+
function findMajorDirsAbsentFromArchitectureDoc(ctx, docRelativePath, content) {
|
|
185
|
+
const countsByDir = new Map();
|
|
186
|
+
for (const file of ctx.inventory.sourceFiles) {
|
|
187
|
+
const parts = file.relativePath.split("/");
|
|
188
|
+
if (parts[0] !== "src" || parts.length < 3)
|
|
189
|
+
continue;
|
|
190
|
+
const dirName = parts[1];
|
|
191
|
+
countsByDir.set(dirName, (countsByDir.get(dirName) ?? 0) + 1);
|
|
192
|
+
}
|
|
193
|
+
const issues = [];
|
|
194
|
+
const sortedDirs = [...countsByDir.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
195
|
+
for (const [dirName, count] of sortedDirs) {
|
|
196
|
+
if (count < MIN_SOURCE_FILES_FOR_MAJOR_DIR)
|
|
197
|
+
continue;
|
|
198
|
+
if (content.includes(dirName))
|
|
199
|
+
continue;
|
|
200
|
+
issues.push(makeCodeRotIssue({
|
|
201
|
+
auditType: "code-rot",
|
|
202
|
+
detectorId: DETECTOR_ID,
|
|
203
|
+
idCues: [docRelativePath, "major-dir-absent-from-architecture-doc", dirName],
|
|
204
|
+
title: `Major implemented directory "src/${dirName}" is not mentioned in ${docRelativePath}`,
|
|
205
|
+
description: `"src/${dirName}" contains ${count} source files but is never mentioned in ${docRelativePath}.`,
|
|
206
|
+
severity: "medium",
|
|
207
|
+
confidence: "medium",
|
|
208
|
+
falsePositiveRisk: "medium",
|
|
209
|
+
category: "architecture-drift",
|
|
210
|
+
recommendedAction: `Add "src/${dirName}" to ${docRelativePath}'s module map.`,
|
|
211
|
+
suggestedFixStrategy: `Document the responsibility of "src/${dirName}" in ${docRelativePath}.`,
|
|
212
|
+
validationCommands: ["npm run audit -- --types code-rot --include architecture,docs"],
|
|
213
|
+
releaseBlocking: false,
|
|
214
|
+
implementationBlocking: false,
|
|
215
|
+
autoFixEligible: false,
|
|
216
|
+
evidence: [
|
|
217
|
+
{
|
|
218
|
+
kind: "observation",
|
|
219
|
+
message: `"src/${dirName}" has ${count} source files and is not mentioned anywhere in ${docRelativePath}.`,
|
|
220
|
+
filePath: docRelativePath,
|
|
221
|
+
source: DETECTOR_ID,
|
|
222
|
+
confidence: "medium",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
affectedFiles: [docRelativePath],
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
return issues;
|
|
229
|
+
}
|
|
230
|
+
function findUnsupportedCurrentClaims(docRelativePath, content) {
|
|
231
|
+
const issues = [];
|
|
232
|
+
const subjects = [
|
|
233
|
+
{ id: "manual-pentest", label: "manual pentest", pattern: "manual pentest" },
|
|
234
|
+
{ id: "android-validation", label: "Android/mobile validation", pattern: "(?:android (?:validation|support))" },
|
|
235
|
+
];
|
|
236
|
+
for (const subject of subjects) {
|
|
237
|
+
const pattern = currentClaimPatternFor(subject.pattern);
|
|
238
|
+
const match = pattern.exec(content);
|
|
239
|
+
if (!match)
|
|
240
|
+
continue;
|
|
241
|
+
issues.push(makeCodeRotIssue({
|
|
242
|
+
auditType: "code-rot",
|
|
243
|
+
detectorId: DETECTOR_ID,
|
|
244
|
+
idCues: [docRelativePath, "unsupported-current-claim", subject.id],
|
|
245
|
+
title: `${docRelativePath} claims "${subject.label}" is implemented/current`,
|
|
246
|
+
description: `${docRelativePath} contains an unhedged "${subject.label} is implemented/current" claim, but there is no corresponding source signal for this repo.`,
|
|
247
|
+
severity: "high",
|
|
248
|
+
confidence: "medium",
|
|
249
|
+
falsePositiveRisk: "medium",
|
|
250
|
+
category: "architecture-drift",
|
|
251
|
+
recommendedAction: `Confirm whether "${subject.label}" is actually implemented; if not, correct ${docRelativePath}.`,
|
|
252
|
+
suggestedFixStrategy: `Rephrase the "${subject.label}" claim in ${docRelativePath} to accurately reflect its planned status.`,
|
|
253
|
+
validationCommands: ["npm run audit -- --types code-rot --include architecture,docs"],
|
|
254
|
+
releaseBlocking: false,
|
|
255
|
+
implementationBlocking: false,
|
|
256
|
+
autoFixEligible: false,
|
|
257
|
+
evidence: [
|
|
258
|
+
{
|
|
259
|
+
kind: "file",
|
|
260
|
+
message: `Matched an unhedged "${subject.label} is implemented/current" claim pattern.`,
|
|
261
|
+
filePath: docRelativePath,
|
|
262
|
+
excerpt: match[0].slice(0, 200),
|
|
263
|
+
source: DETECTOR_ID,
|
|
264
|
+
confidence: "medium",
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
affectedFiles: [docRelativePath],
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
return issues;
|
|
271
|
+
}
|