@dailephd/my-dev-kit-lab 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 +11 -8
- package/dist/scripts/security/validate.js +8 -18
- package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +96 -0
- package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +202 -4
- package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +19 -1
- package/dist/src/audits/codeRot/detectors/testRotDetector.js +126 -0
- package/dist/src/audits/codeRot/utils/jvmSourceFactsUtils.js +153 -0
- package/dist/src/audits/core/collectSourceFacts.js +21 -10
- package/dist/src/audits/core/javaAnalyzer.js +282 -0
- package/dist/src/audits/core/jvmProjectMetadata.js +106 -0
- package/dist/src/audits/core/kotlinAnalyzer.js +280 -0
- package/dist/src/audits/core/languageAnalyzerRegistry.js +9 -3
- package/dist/src/audits/core/projectInventory.js +41 -0
- package/dist/src/audits/security/securityAuditAdapter.js +15 -12
- package/dist/src/securityValidation/report/writeSecurityReportFiles.js +28 -0
- package/package.json +97 -97
- package/tests/fixtures/audits/mixed-language/mixed-docs-claims/README.md +16 -0
- package/tests/fixtures/audits/mixed-language/mixed-docs-claims/build.gradle.kts +4 -0
- package/tests/fixtures/audits/mixed-language/mixed-docs-claims/src/main/java/com/example/Widget.java +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-docs-claims/src/main/kotlin/com/example/Gadget.kt +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/README.md +10 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/build/Main.class +1 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/dist/index.js +1 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/generated/schema.ts +1 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/src/index.ts +3 -0
- package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/vendor/lib.py +2 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/README.md +10 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/build.gradle.kts +8 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/settings.gradle.kts +1 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/main/java/com/example/FooService.java +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/main/kotlin/com/example/BarService.kt +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/test/java/com/example/FooServiceTest.java +8 -0
- package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/test/kotlin/com/example/BarServiceTest.kt +8 -0
- package/tests/fixtures/audits/mixed-language/mixed-python-ts/README.md +8 -0
- package/tests/fixtures/audits/mixed-language/mixed-python-ts/src/app.py +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-python-ts/src/index.ts +3 -0
- package/tests/fixtures/audits/mixed-language/mixed-python-ts/tests/test_app.py +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-js/README.md +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-js/src/helper.js +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-js/src/index.ts +3 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-js/tests/index.sample.ts +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/README.md +10 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/build.gradle.kts +4 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/pytests/test_app.py +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/app.py +2 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/helper.js +5 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/index.ts +3 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/main/java/com/example/Multiplier.java +7 -0
- package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/main/kotlin/com/example/Divider.kt +7 -0
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
-
The current published baseline is `v0.3.
|
|
5
|
+
The current published baseline is `v0.3.3`. `v0.3.0` introduced the generic audit framework and the first implemented code-rot detector family; `v0.3.1` added the language-aware code-rot substrate plus TypeScript/JavaScript support; `v0.3.2` added Python-aware code-rot support and a first security-validation audit adapter; `v0.3.3` adds Java/Kotlin source-facts support and JVM-aware code-rot detector integration. 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
6
|
|
|
7
|
-
|
|
7
|
+
`v0.3.3` is the previously published npm baseline. Java/Kotlin support is conservative static analysis only: dependency-free scanners, static Gradle/Maven metadata detection, and no compiler, classpath, Gradle, Maven, Android, or target-test execution. `v0.3.4` is release-prepared in this repository: package metadata has been bumped to `0.3.4`, adding mixed-language stability coverage, repeated-run report hardening, cross-platform/path normalization tests, CRLF/LF stability checks, and final documentation reconciliation. `v0.3.4` is not yet published to npm, not tagged, and no GitHub release has been created — local Windows validation has passed and a latest-Node cross-platform workflow is configured for ubuntu/macos/windows, but a fresh remote multi-OS run should be verified during publication.
|
|
8
8
|
|
|
9
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.
|
|
10
10
|
|
|
@@ -35,8 +35,9 @@ my-dev-kit is most useful when the repository is larger than the task. It helps
|
|
|
35
35
|
- Plugin-aware JSON and HTML reports with plugin, target, variant, metric, artifact, warning, skip, and failure metadata
|
|
36
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>`. `security:validate` remains the standalone, focused security-validation command; it is not replaced by the audit adapter below.
|
|
37
37
|
- Generic audit framework, current published baseline: `npm run audit` runs heuristic, conservative code-rot detectors and writes text/JSON reports under `reports/audits/code-rot/` by default. `code-rot` and `security` are the currently implemented audit types (`--types code-rot`, `--types security`, or combined `--types code-rot,security`); `quality`, `project`, and `all` audit types are planned and fail cleanly instead of running. `npm run audit` supports `--target`, `--types`, and other flags — see [docs/COMMANDS.md](docs/COMMANDS.md). Audit does not modify target files and does not auto-fix issues.
|
|
38
|
-
- Language-aware code-rot support, current
|
|
39
|
-
- Security-validation audit adapter, current
|
|
38
|
+
- Language-aware code-rot support, current published package state: normalized language/file-role inventory, a source-facts model and collector, TypeScript/JavaScript, Python, Java, and Kotlin analyzers, and conservative detector integrations for `dead-code-candidate`, `duplicate-implementation-candidate`, `test-rot`, and `docs-code-mismatch`. Python project metadata remains a top-level audit report field. Java/Kotlin support adds static JVM project metadata detection for Gradle/Maven/source-set shape and conservative source-facts evidence, but no compiler parsing, type resolution, classpath resolution, Gradle/Maven execution, Android validation, or target-project test execution. No `SourceFacts` schema change or new audit command was added for `v0.3.3`.
|
|
39
|
+
- Security-validation audit adapter, current published package state: `npm run audit -- --types security` runs security validation through the shared audit/report surface by calling the same `runSecurityValidation()` internals `security:validate` uses, maps security findings into audit-issue-shaped entries, and adds a `securitySummary` field (verdict, check counts, finding counts, and links to the original `reports/security/*.txt`/`*.json` report) to audit JSON/text reports. `npm run audit -- --types code-rot,security` runs both audit types together. The original `security:validate` command and its `reports/security/` output are unchanged.
|
|
40
|
+
- `v0.3.4` implementation state (release-prepared, not yet published): mixed-language fixture corpus, source-facts invariants, full-registry mixed-language detector stability tests, repeated-run audit report determinism checks, cross-platform/path normalization hardening, CRLF/LF parsing stability coverage, and final documentation reconciliation. Package metadata is bumped to `0.3.4`; it is not yet tagged or published.
|
|
40
41
|
|
|
41
42
|
---
|
|
42
43
|
|
|
@@ -207,12 +208,14 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
|
|
|
207
208
|
|
|
208
209
|
## Current baseline release positioning
|
|
209
210
|
|
|
210
|
-
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
|
|
211
|
+
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 previously published npm baseline is `v0.3.3`, which added Java/Kotlin analyzers, JVM metadata detection, Java/Kotlin detector support, and Java/Kotlin/Gradle/Maven docs-code-mismatch checks on top of the published `v0.3.2` baseline.
|
|
211
212
|
|
|
212
|
-
|
|
213
|
+
`v0.3.4` is release-prepared in this repository: package metadata has been bumped to `0.3.4`. It hardens the existing language-aware audit path with mixed-language fixture coverage, detector and report determinism checks, cross-platform/path normalization coverage, CRLF/LF stability coverage, and documentation reconciliation. It does not add a new command. It is not yet published, not tagged, and no GitHub release has been created.
|
|
213
214
|
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
Planned roadmap direction after the previously published baseline:
|
|
216
|
+
|
|
217
|
+
- `v0.3.3`: Java/Kotlin code-rot support (published)
|
|
218
|
+
- `v0.3.4`: cross-language stability for the language-aware code-rot track (release-prepared in this checkout; not yet published)
|
|
216
219
|
- `v0.4.0` through `v0.4.2`: Android automated security validation, including an Android-specific extension of the security audit adapter
|
|
217
220
|
- manual pentest: post-v1 / version TBD
|
|
218
221
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fs from "node:fs";
|
|
4
2
|
import { runSecurityValidation } from "../../src/securityValidation/validate/runSecurityValidation.js";
|
|
5
3
|
import { resolveValidationTarget, reportFilenamePrefix } from "../../src/securityValidation/validate/resolveTarget.js";
|
|
6
|
-
import { renderTextReport, renderJsonReport } from "../../src/securityValidation/report/renderSecurityReport.js";
|
|
7
4
|
import { buildSecurityReportFromSummary } from "../../src/securityValidation/report/buildSecurityReport.js";
|
|
5
|
+
import { renderTextReport } from "../../src/securityValidation/report/renderSecurityReport.js";
|
|
6
|
+
import { writeSecurityReportFiles } from "../../src/securityValidation/report/writeSecurityReportFiles.js";
|
|
8
7
|
import { parseSecurityValidateArgs, normalizeSecurityValidateConfig, applyProfileDefaultChecksIfApplicable, } from "../../src/securityValidation/validate/cliOptions.js";
|
|
9
8
|
import { resolveAttackProfile } from "../../src/securityValidation/attackScenarios/attackProfile.js";
|
|
10
9
|
import { findingsBreachFailOnThreshold } from "../../src/securityValidation/validate/verdict.js";
|
|
@@ -90,25 +89,16 @@ const report = buildSecurityReportFromSummary(summary, {
|
|
|
90
89
|
failOnBreached,
|
|
91
90
|
});
|
|
92
91
|
const textReport = renderTextReport(report);
|
|
93
|
-
const jsonReport = renderJsonReport(report);
|
|
94
92
|
// Determine output directory
|
|
95
93
|
const reportsDir = config.out;
|
|
96
|
-
if (!fs.existsSync(reportsDir)) {
|
|
97
|
-
fs.mkdirSync(reportsDir, { recursive: true });
|
|
98
|
-
}
|
|
99
94
|
// Determine report filename prefix
|
|
100
95
|
const prefix = args.reportPrefix ?? reportFilenamePrefix(target);
|
|
101
|
-
const txtPath =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
if (config.formats.includes("json")) {
|
|
109
|
-
fs.writeFileSync(jsonPath, jsonReport, "utf8");
|
|
110
|
-
writtenPaths.push(jsonPath);
|
|
111
|
-
}
|
|
96
|
+
const { textPath: txtPath, jsonPath, writtenPaths } = writeSecurityReportFiles({
|
|
97
|
+
outDir: reportsDir,
|
|
98
|
+
prefix,
|
|
99
|
+
report,
|
|
100
|
+
formats: config.formats,
|
|
101
|
+
});
|
|
112
102
|
// Print report to stdout
|
|
113
103
|
console.log(textReport);
|
|
114
104
|
console.log(`\nReports written:`);
|
|
@@ -3,6 +3,7 @@ import { readBoundedFileText } from "../utils/boundedRead.js";
|
|
|
3
3
|
import { baseNameNoExt, isGenericOrTestPrefixedDeclarationName } from "../utils/filePatternUtils.js";
|
|
4
4
|
import { deduplicateIssuesById, makeCodeRotIssue } from "../utils/issueFactories.js";
|
|
5
5
|
import { collectRelativeImportBasenames, getParsedSourceFacts, indexSourceFactsByPath } from "../utils/sourceFactsLookup.js";
|
|
6
|
+
import { isJvmLifecycleOrConventionalDeclarationName, jvmImportSimpleName } from "../utils/jvmSourceFactsUtils.js";
|
|
6
7
|
// ---------------------------------------------------------------------------
|
|
7
8
|
// v0.3.0 Batch 4 — dead-code candidate detector.
|
|
8
9
|
//
|
|
@@ -52,6 +53,7 @@ export const DEAD_CODE_CANDIDATE_DETECTOR = {
|
|
|
52
53
|
}
|
|
53
54
|
if (ctx.config.include.includes("cli") || ctx.config.include.includes("architecture")) {
|
|
54
55
|
issues.push(...findPossiblyUnreferencedPythonDeclarations(ctx));
|
|
56
|
+
issues.push(...findPossiblyUnreferencedJvmDeclarations(ctx));
|
|
55
57
|
}
|
|
56
58
|
return deduplicateIssuesById(issues);
|
|
57
59
|
},
|
|
@@ -344,6 +346,100 @@ function findPossiblyUnreferencedPythonDeclarations(ctx) {
|
|
|
344
346
|
}
|
|
345
347
|
return issues;
|
|
346
348
|
}
|
|
349
|
+
// v0.3.3 Batch 2 -- conservative, declaration-level Java/Kotlin dead-code
|
|
350
|
+
// candidate check. Same spirit and lowest severity/confidence tier as
|
|
351
|
+
// findPossiblyUnreferencedPythonDeclarations() above, adapted for the two
|
|
352
|
+
// key differences in what Java/Kotlin source facts can safely support:
|
|
353
|
+
//
|
|
354
|
+
// - Method/constructor declarations (DeclarationFactKind "method") are
|
|
355
|
+
// EXCLUDED ENTIRELY, for both languages. There is no call-tracking
|
|
356
|
+
// evidence in this project's source-facts model for any language
|
|
357
|
+
// (`references` stays empty everywhere -- see javaAnalyzer.ts's and
|
|
358
|
+
// kotlinAnalyzer.ts's own header comments), so a method-level check
|
|
359
|
+
// would have zero real evidence behind it and would flag nearly every
|
|
360
|
+
// method with equal (lack of) signal -- pure noise. This mirrors the
|
|
361
|
+
// existing Python check's own "methods are excluded entirely" rule
|
|
362
|
+
// exactly (see above), just extended to two more languages. This also
|
|
363
|
+
// conservatively covers Java constructors without needing a dedicated
|
|
364
|
+
// constructor detector, since javaAnalyzer.ts represents constructors
|
|
365
|
+
// as kind "method" too.
|
|
366
|
+
// - Kotlin top-level `fun` declarations (kind "function") ARE
|
|
367
|
+
// considered, the direct analogue of Python's module-level `def` --
|
|
368
|
+
// but Java has no equivalent (every Java method lives inside a
|
|
369
|
+
// class/interface/enum), so only class/interface/enum kinds are
|
|
370
|
+
// considered for Java.
|
|
371
|
+
//
|
|
372
|
+
// `exported` already folds Java's public/protected/private/package-private
|
|
373
|
+
// and Kotlin's public/internal/protected/private visibility into a single
|
|
374
|
+
// boolean (see both analyzers' "Visibility mapping" header comments) --
|
|
375
|
+
// exported (public) declarations are skipped entirely here, since a public
|
|
376
|
+
// API is expected to be used from outside the scanned file set and this
|
|
377
|
+
// scan has no way to see that.
|
|
378
|
+
function findPossiblyUnreferencedJvmDeclarations(ctx) {
|
|
379
|
+
if (!ctx.sourceFacts)
|
|
380
|
+
return [];
|
|
381
|
+
const index = indexSourceFactsByPath(ctx.sourceFacts);
|
|
382
|
+
// Cross-file "this simple name was imported somewhere" signal, combined
|
|
383
|
+
// across Java and Kotlin since either language can import the other in a
|
|
384
|
+
// mixed JVM project -- the strongest signal this conservative scan can
|
|
385
|
+
// offer, same role as Python's importedNamesAcrossProject above.
|
|
386
|
+
const importedSimpleNamesAcrossProject = new Set();
|
|
387
|
+
for (const facts of index.values()) {
|
|
388
|
+
if ((facts.language !== "java" && facts.language !== "kotlin") || facts.parseStatus !== "parsed")
|
|
389
|
+
continue;
|
|
390
|
+
for (const imp of facts.imports) {
|
|
391
|
+
importedSimpleNamesAcrossProject.add(jvmImportSimpleName(imp.source));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const issues = [];
|
|
395
|
+
for (const facts of index.values()) {
|
|
396
|
+
if (facts.language !== "java" && facts.language !== "kotlin")
|
|
397
|
+
continue;
|
|
398
|
+
if (facts.role !== "source" || facts.parseStatus !== "parsed")
|
|
399
|
+
continue;
|
|
400
|
+
for (const decl of facts.declarations) {
|
|
401
|
+
if (decl.kind === "method")
|
|
402
|
+
continue; // methods/constructors -- see header comment
|
|
403
|
+
if (facts.language === "java" && decl.kind === "function")
|
|
404
|
+
continue; // never produced by javaAnalyzer.ts, defensive
|
|
405
|
+
if (decl.exported)
|
|
406
|
+
continue; // public (Java) / public-by-default-or-explicit (Kotlin)
|
|
407
|
+
if (isJvmLifecycleOrConventionalDeclarationName(decl.name))
|
|
408
|
+
continue;
|
|
409
|
+
if (importedSimpleNamesAcrossProject.has(decl.name))
|
|
410
|
+
continue;
|
|
411
|
+
const languageLabel = facts.language === "java" ? "Java" : "Kotlin";
|
|
412
|
+
issues.push(makeCodeRotIssue({
|
|
413
|
+
auditType: "code-rot",
|
|
414
|
+
detectorId: DETECTOR_ID,
|
|
415
|
+
idCues: [`unreferenced-${facts.language}-declaration`, facts.relativePath, decl.kind, decl.name],
|
|
416
|
+
title: `${languageLabel} ${decl.kind} "${decl.name}" in "${facts.relativePath}" has no detected cross-file import (weak signal)`,
|
|
417
|
+
description: `"${decl.name}" (${decl.kind}) is declared in "${facts.relativePath}" and is not exported/public, and does not appear as an imported simple name in any other scanned Java/Kotlin file. This is a deterministic static source-facts signal from a conservative, non-semantic scan -- no compiler/classpath analysis was performed, and no Gradle/Maven execution was performed, so this cannot see reflection-based access, dependency-injection wiring, framework/plugin discovery, or usage from outside the scanned project. It may indicate a candidate for removal but is not proof. Confirm manually before removing.`,
|
|
418
|
+
severity: "info",
|
|
419
|
+
confidence: "low",
|
|
420
|
+
falsePositiveRisk: "high",
|
|
421
|
+
category: "dead-code-candidate",
|
|
422
|
+
recommendedAction: "Confirm the declaration is genuinely unused before removing it.",
|
|
423
|
+
suggestedFixStrategy: `Remove "${decl.name}" from "${facts.relativePath}" if confirmed unused, or reference it from another module.`,
|
|
424
|
+
validationCommands: ["npm run audit -- --types code-rot --include cli"],
|
|
425
|
+
releaseBlocking: false,
|
|
426
|
+
implementationBlocking: false,
|
|
427
|
+
autoFixEligible: false,
|
|
428
|
+
evidence: [
|
|
429
|
+
{
|
|
430
|
+
kind: "reference",
|
|
431
|
+
message: `Source facts: the ${languageLabel} analyzer parsed this file and found no other scanned Java/Kotlin file importing the simple name "${decl.name}". Static-analysis limitation -- confidence reduced because this scan cannot see reflection/DI usage, framework discovery, or usage outside the scanned file set, and no compiler/classpath analysis was performed.`,
|
|
432
|
+
filePath: facts.relativePath,
|
|
433
|
+
source: DETECTOR_ID,
|
|
434
|
+
confidence: "low",
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
affectedFiles: [facts.relativePath],
|
|
438
|
+
}));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return issues;
|
|
442
|
+
}
|
|
347
443
|
function findOldOrDeprecatedFilesNotInDocs(ctx, docsConcat) {
|
|
348
444
|
const issues = [];
|
|
349
445
|
for (const file of ctx.inventory.files) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { collectJvmProjectMetadata } from "../../core/jvmProjectMetadata.js";
|
|
3
4
|
import { currentClaimPatternFor, plannedClaimPatternFor, FULL_RELEASE_GATE_FOR_SCOPED_CHECKS_PATTERN, isNegatedNearby, } from "../utils/docClaimPatterns.js";
|
|
4
5
|
import { deduplicateIssuesById, makeCodeRotIssue } from "../utils/issueFactories.js";
|
|
5
6
|
import { splitLines } from "../utils/textLines.js";
|
|
@@ -27,20 +28,95 @@ const FEATURE_CHECKS = [
|
|
|
27
28
|
featureId: "security-validation",
|
|
28
29
|
label: "security validation",
|
|
29
30
|
subjectPattern: "security validation",
|
|
30
|
-
isImplemented: (
|
|
31
|
+
isImplemented: (ctx) => ctx.sourceOfTruth.security.hasSecurityValidationSourceDir && ctx.sourceOfTruth.security.hasSecurityValidateScript,
|
|
31
32
|
},
|
|
32
33
|
{
|
|
33
34
|
featureId: "experiment-framework",
|
|
34
35
|
label: "experiment framework",
|
|
35
36
|
subjectPattern: "experiment (?:framework|plugin)",
|
|
36
|
-
isImplemented: (
|
|
37
|
+
isImplemented: (ctx) => ctx.sourceOfTruth.experiment.hasExperimentsSourceDir && ctx.sourceOfTruth.experiment.hasExperimentListScript,
|
|
38
|
+
},
|
|
39
|
+
// v0.3.3 Batch 3 -- Gradle/Maven/Android feature-claim checks, using only
|
|
40
|
+
// static JVM metadata (see jvmProjectMetadata.ts). Never runs Gradle,
|
|
41
|
+
// Maven, or any Android tooling.
|
|
42
|
+
{
|
|
43
|
+
featureId: "gradle-support",
|
|
44
|
+
label: "Gradle",
|
|
45
|
+
subjectPattern: "Gradle",
|
|
46
|
+
isImplemented: (ctx) => ctx.jvmMetadata.hasGradleBuild || ctx.jvmMetadata.hasGradleSettings,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
featureId: "maven-support",
|
|
50
|
+
label: "Maven",
|
|
51
|
+
subjectPattern: "Maven",
|
|
52
|
+
isImplemented: (ctx) => ctx.jvmMetadata.hasMavenPom,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
featureId: "android-validation",
|
|
56
|
+
label: "Android validation",
|
|
57
|
+
// Negative lookbehind excludes "(non-Android)"-style negated mentions
|
|
58
|
+
// (this project's own docs legitimately describe things as
|
|
59
|
+
// "the general (non-Android) ... adapter", which is a claim about the
|
|
60
|
+
// *adapter*, not about Android -- without this guard, "is implemented"
|
|
61
|
+
// appearing later in the same sentence about the adapter would
|
|
62
|
+
// falsely match as an Android claim).
|
|
63
|
+
subjectPattern: "(?<!non-)Android(?: validation| support)?",
|
|
64
|
+
// Android automated validation is not implemented anywhere in this
|
|
65
|
+
// project (see the v0.3.x/v0.4.x roadmap) -- this is a permanently
|
|
66
|
+
// false signal, not a metadata lookup, so a claim that it is
|
|
67
|
+
// implemented/current is always a genuine mismatch until a future
|
|
68
|
+
// version actually builds it. This does not implement any Android
|
|
69
|
+
// detection itself (no AndroidManifest.xml/Gradle-Android-plugin/
|
|
70
|
+
// Compose/AGP/SDK/emulator awareness) -- it only reuses the existing
|
|
71
|
+
// current-vs-planned claim mechanism for a feature this project
|
|
72
|
+
// already knows, out of band, does not exist.
|
|
73
|
+
isImplemented: () => false,
|
|
37
74
|
},
|
|
38
75
|
];
|
|
76
|
+
// v0.3.3 Batch 3 -- backtick-quoted-only Java/Kotlin FQCN-shaped symbol
|
|
77
|
+
// scan (T1-T4). Requiring the mention to be inside an inline code span
|
|
78
|
+
// (`` `com.example.Foo` ``) is what keeps this conservative: ordinary prose
|
|
79
|
+
// mentioning a capitalized word is extremely common and would be far too
|
|
80
|
+
// noisy to scan directly, but a backtick-quoted, fully-dotted,
|
|
81
|
+
// lowercase-package-then-Capitalized-name token is specifically how a
|
|
82
|
+
// real symbol reference is conventionally written in markdown docs.
|
|
83
|
+
const BACKTICK_SPAN_PATTERN = /`([^`]+)`/g;
|
|
84
|
+
const JVM_FQCN_PATTERN = /^[a-z][\w]*(?:\.[a-z][\w]*)*\.[A-Z]\w*$/;
|
|
85
|
+
// Reused by both the symbol-claim check and the Gradle/Maven command-claim
|
|
86
|
+
// check below: a broader set of future/negative-boundary hedge phrases than
|
|
87
|
+
// docClaimPatterns.ts's own HEDGE_PATTERN (which is narrowly scoped to
|
|
88
|
+
// "not yet published"-style wording) -- this project's own docs
|
|
89
|
+
// legitimately say things like "Android support is planned" or "does not
|
|
90
|
+
// support Gradle yet", which must never be treated as a current-state claim.
|
|
91
|
+
const JVM_FUTURE_OR_NEGATIVE_HEDGE_PATTERN = /\b(planned|future|deferred|not yet|out of scope|does not support|doesn't support|will support|not implemented|not supported)\b/i;
|
|
92
|
+
const GRADLE_COMMAND_PATTERN = /\b(?:\.\/)?gradlew(?:\.bat)?\b|\bgradle\b/i;
|
|
93
|
+
const MAVEN_COMMAND_PATTERN = /\bmvnw(?:\.cmd)?\b|\bmvn\b/i;
|
|
94
|
+
// Declaration kinds treated as safe, implementation-weight symbol claims --
|
|
95
|
+
// same conservative kind allowlist Batch 2's duplicate/dead-code checks
|
|
96
|
+
// already use (methods/variables/constants excluded as too noisy/ambiguous
|
|
97
|
+
// for a name-only, no-package-tracking cross-check).
|
|
98
|
+
const JVM_SYMBOL_DECLARATION_KINDS = new Set(["class", "interface", "enum", "function"]);
|
|
99
|
+
function collectKnownJvmDeclarationNames(ctx) {
|
|
100
|
+
const names = new Set();
|
|
101
|
+
if (!ctx.sourceFacts)
|
|
102
|
+
return names;
|
|
103
|
+
for (const file of ctx.sourceFacts.files) {
|
|
104
|
+
if (file.language !== "java" && file.language !== "kotlin")
|
|
105
|
+
continue;
|
|
106
|
+
if (file.parseStatus !== "parsed")
|
|
107
|
+
continue;
|
|
108
|
+
for (const decl of file.declarations) {
|
|
109
|
+
if (JVM_SYMBOL_DECLARATION_KINDS.has(decl.kind))
|
|
110
|
+
names.add(decl.name);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return names;
|
|
114
|
+
}
|
|
39
115
|
export const DOCS_CODE_MISMATCH_DETECTOR = {
|
|
40
116
|
id: DETECTOR_ID,
|
|
41
117
|
auditType: "code-rot",
|
|
42
118
|
title: "Documentation/code mismatch",
|
|
43
|
-
description: "Detects deterministic mismatches between documentation claims and source-of-truth data: feature-implementation claims
|
|
119
|
+
description: "Detects deterministic mismatches between documentation claims and source-of-truth data: feature-implementation claims, full-release-gate-for-scoped-checks wording, and (v0.3.3) Java/Kotlin symbol claims plus Gradle/Maven feature/command claims against static JVM metadata.",
|
|
44
120
|
supportedIncludeAreas: ["docs", "architecture", "cli", "package"],
|
|
45
121
|
shouldSkip: (ctx) => {
|
|
46
122
|
if (!ctx.config.include.includes("docs")) {
|
|
@@ -50,6 +126,13 @@ export const DOCS_CODE_MISMATCH_DETECTOR = {
|
|
|
50
126
|
},
|
|
51
127
|
run: (ctx) => {
|
|
52
128
|
const issues = [];
|
|
129
|
+
// v0.3.3 Batch 3 -- computed once per run, same "cheap, static,
|
|
130
|
+
// presence-only" treatment as pythonMetadata/jvmMetadata elsewhere in
|
|
131
|
+
// this codebase (see testRotDetector.ts). Never runs Gradle/Maven/any
|
|
132
|
+
// JVM tooling.
|
|
133
|
+
const jvmMetadata = collectJvmProjectMetadata(ctx.target.rootPath, ctx.inventory);
|
|
134
|
+
const featureCtx = { sourceOfTruth: ctx.sourceOfTruth, jvmMetadata };
|
|
135
|
+
const knownJvmDeclarationNames = collectKnownJvmDeclarationNames(ctx);
|
|
53
136
|
for (const docFile of ctx.inventory.docsFiles) {
|
|
54
137
|
const content = readBoundedText(ctx.target.rootPath, docFile.relativePath, docFile.sizeBytes);
|
|
55
138
|
if (content === null)
|
|
@@ -58,9 +141,12 @@ export const DOCS_CODE_MISMATCH_DETECTOR = {
|
|
|
58
141
|
for (let i = 0; i < lines.length; i++) {
|
|
59
142
|
const line = lines[i];
|
|
60
143
|
for (const feature of FEATURE_CHECKS) {
|
|
144
|
+
if (feature.featureId === "security-validation" && isAndroidScopedSecurityValidationClaim(line)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
61
147
|
const currentMatch = currentClaimPatternFor(feature.subjectPattern).test(line);
|
|
62
148
|
const plannedMatch = plannedClaimPatternFor(feature.subjectPattern).test(line);
|
|
63
|
-
const implemented = feature.isImplemented(
|
|
149
|
+
const implemented = feature.isImplemented(featureCtx);
|
|
64
150
|
if (currentMatch && plannedMatch) {
|
|
65
151
|
// Same line claims both current and planned for the same
|
|
66
152
|
// subject -- genuinely ambiguous, reported as low-confidence
|
|
@@ -78,11 +164,120 @@ export const DOCS_CODE_MISMATCH_DETECTOR = {
|
|
|
78
164
|
if (FULL_RELEASE_GATE_FOR_SCOPED_CHECKS_PATTERN.test(line) && !isNegatedNearby(line)) {
|
|
79
165
|
issues.push(makeFullReleaseGateIssue(docFile.relativePath, i + 1, line));
|
|
80
166
|
}
|
|
167
|
+
if (!JVM_FUTURE_OR_NEGATIVE_HEDGE_PATTERN.test(line)) {
|
|
168
|
+
issues.push(...findJvmSymbolClaimMismatches(docFile.relativePath, i + 1, line, jvmMetadata, knownJvmDeclarationNames));
|
|
169
|
+
issues.push(...findJvmBuildCommandClaimMismatches(docFile.relativePath, i + 1, line, jvmMetadata));
|
|
170
|
+
}
|
|
81
171
|
}
|
|
82
172
|
}
|
|
83
173
|
return deduplicateIssuesById(issues);
|
|
84
174
|
},
|
|
85
175
|
};
|
|
176
|
+
// v0.3.3 Batch 3 -- T1/T2/T3/T4. Gated on the project having ANY real
|
|
177
|
+
// Java/Kotlin source at all (jvmMetadata.hasJavaSource/hasKotlinSource) --
|
|
178
|
+
// without this gate, a project with no JVM code whatsoever (e.g. this repo
|
|
179
|
+
// itself) would have every backtick-quoted FQCN-shaped mention trivially
|
|
180
|
+
// "fail" to match anything, which would be pure noise rather than a real
|
|
181
|
+
// signal. Matches by simple declaration name only (see
|
|
182
|
+
// collectKnownJvmDeclarationNames()'s header comment) -- this cannot verify
|
|
183
|
+
// the claimed package prefix actually matches, only that some declaration
|
|
184
|
+
// with that name exists somewhere in the scanned Java/Kotlin source, which
|
|
185
|
+
// is why this is always a "weak signal" candidate, never a proof.
|
|
186
|
+
function findJvmSymbolClaimMismatches(relativePath, lineNumber, line, jvmMetadata, knownJvmDeclarationNames) {
|
|
187
|
+
if (!jvmMetadata.hasJavaSource && !jvmMetadata.hasKotlinSource)
|
|
188
|
+
return [];
|
|
189
|
+
const issues = [];
|
|
190
|
+
BACKTICK_SPAN_PATTERN.lastIndex = 0;
|
|
191
|
+
for (const match of line.matchAll(BACKTICK_SPAN_PATTERN)) {
|
|
192
|
+
const candidate = match[1];
|
|
193
|
+
if (!JVM_FQCN_PATTERN.test(candidate))
|
|
194
|
+
continue;
|
|
195
|
+
const simpleName = candidate.slice(candidate.lastIndexOf(".") + 1);
|
|
196
|
+
if (knownJvmDeclarationNames.has(simpleName))
|
|
197
|
+
continue;
|
|
198
|
+
issues.push(makeCodeRotIssue({
|
|
199
|
+
auditType: "code-rot",
|
|
200
|
+
detectorId: DETECTOR_ID,
|
|
201
|
+
idCues: [relativePath, "jvm-symbol-claim-mismatch", String(lineNumber), candidate],
|
|
202
|
+
title: `Docs mention a Java/Kotlin symbol not found in static source facts: "${candidate}"`,
|
|
203
|
+
description: `${relativePath}:${lineNumber} mentions \`${candidate}\`, but no scanned Java/Kotlin declaration named "${simpleName}" was found. This is a static source-facts signal from a conservative, name-only scan -- it cannot verify the claimed package prefix, only that no declaration with this simple name exists anywhere in the scanned Java/Kotlin file set, so this is a candidate docs-code mismatch (weak signal), not proof.`,
|
|
204
|
+
severity: "info",
|
|
205
|
+
confidence: "low",
|
|
206
|
+
falsePositiveRisk: "high",
|
|
207
|
+
category: "docs-feature-mismatch",
|
|
208
|
+
recommendedAction: `Confirm "${candidate}" exists (it may be defined outside the scanned source set) before treating this as stale documentation.`,
|
|
209
|
+
suggestedFixStrategy: `Update ${relativePath} to reference an existing Java/Kotlin symbol, or add the missing declaration.`,
|
|
210
|
+
validationCommands: ["npm run audit -- --types code-rot --include docs"],
|
|
211
|
+
releaseBlocking: false,
|
|
212
|
+
implementationBlocking: false,
|
|
213
|
+
autoFixEligible: false,
|
|
214
|
+
evidence: [
|
|
215
|
+
{
|
|
216
|
+
kind: "file",
|
|
217
|
+
message: `Static source-facts check did not find a Java/Kotlin declaration named "${simpleName}" in the scanned file set.`,
|
|
218
|
+
filePath: relativePath,
|
|
219
|
+
line: lineNumber,
|
|
220
|
+
excerpt: line.trim().slice(0, 200),
|
|
221
|
+
source: DETECTOR_ID,
|
|
222
|
+
confidence: "low",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
affectedFiles: [relativePath],
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
return issues;
|
|
229
|
+
}
|
|
230
|
+
// v0.3.3 Batch 3 -- T6/T7/T8/T9. Only matches a Gradle/Maven command token
|
|
231
|
+
// inside a backtick-quoted inline code span, for the same "conventionally
|
|
232
|
+
// how a real command example is written" reasoning as the symbol check
|
|
233
|
+
// above. This never claims the command would actually succeed -- only that
|
|
234
|
+
// docs present a command example implying a build tool this project's
|
|
235
|
+
// static metadata does not show any trace of.
|
|
236
|
+
function findJvmBuildCommandClaimMismatches(relativePath, lineNumber, line, jvmMetadata) {
|
|
237
|
+
const issues = [];
|
|
238
|
+
BACKTICK_SPAN_PATTERN.lastIndex = 0;
|
|
239
|
+
for (const match of line.matchAll(BACKTICK_SPAN_PATTERN)) {
|
|
240
|
+
const candidate = match[1];
|
|
241
|
+
if (GRADLE_COMMAND_PATTERN.test(candidate) && !jvmMetadata.hasGradleBuild && !jvmMetadata.hasGradleSettings && !jvmMetadata.hasGradleWrapper) {
|
|
242
|
+
issues.push(makeBuildCommandIssue(relativePath, lineNumber, line, candidate, "Gradle", "no Gradle execution was performed"));
|
|
243
|
+
}
|
|
244
|
+
if (MAVEN_COMMAND_PATTERN.test(candidate) && !jvmMetadata.hasMavenPom) {
|
|
245
|
+
issues.push(makeBuildCommandIssue(relativePath, lineNumber, line, candidate, "Maven", "no Maven execution was performed"));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return issues;
|
|
249
|
+
}
|
|
250
|
+
function makeBuildCommandIssue(relativePath, lineNumber, line, commandExcerpt, toolLabel, executionNote) {
|
|
251
|
+
return makeCodeRotIssue({
|
|
252
|
+
auditType: "code-rot",
|
|
253
|
+
detectorId: DETECTOR_ID,
|
|
254
|
+
idCues: [relativePath, `${toolLabel.toLowerCase()}-command-claim-mismatch`, String(lineNumber), commandExcerpt],
|
|
255
|
+
title: `Docs include a ${toolLabel} command example, but no ${toolLabel} metadata was found: "${commandExcerpt}"`,
|
|
256
|
+
description: `${relativePath}:${lineNumber} shows the command \`${commandExcerpt}\`, but no ${toolLabel} build/config metadata was found in the project (see jvmProjectMetadata.ts's static presence checks). This is a candidate docs-code mismatch (weak signal), not proof -- ${executionNote}, so this scan cannot confirm the command would actually run.`,
|
|
257
|
+
severity: "info",
|
|
258
|
+
confidence: "low",
|
|
259
|
+
falsePositiveRisk: "high",
|
|
260
|
+
category: "docs-feature-mismatch",
|
|
261
|
+
recommendedAction: `Confirm ${toolLabel} metadata exists (it may live outside the scanned inventory) before treating this as stale documentation.`,
|
|
262
|
+
suggestedFixStrategy: `Update ${relativePath} to remove or correct the ${toolLabel} command example, or add the corresponding ${toolLabel} project files.`,
|
|
263
|
+
validationCommands: ["npm run audit -- --types code-rot --include docs"],
|
|
264
|
+
releaseBlocking: false,
|
|
265
|
+
implementationBlocking: false,
|
|
266
|
+
autoFixEligible: false,
|
|
267
|
+
evidence: [
|
|
268
|
+
{
|
|
269
|
+
kind: "file",
|
|
270
|
+
message: `Static source-facts/metadata check did not find ${toolLabel} build metadata; ${executionNote}.`,
|
|
271
|
+
filePath: relativePath,
|
|
272
|
+
line: lineNumber,
|
|
273
|
+
excerpt: line.trim().slice(0, 200),
|
|
274
|
+
source: DETECTOR_ID,
|
|
275
|
+
confidence: "low",
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
affectedFiles: [relativePath],
|
|
279
|
+
});
|
|
280
|
+
}
|
|
86
281
|
function makeCurrentButMissingIssue(relativePath, lineNumber, excerpt, feature) {
|
|
87
282
|
return makeCodeRotIssue({
|
|
88
283
|
auditType: "code-rot",
|
|
@@ -217,3 +412,6 @@ function readBoundedText(root, relativePath, sizeBytes) {
|
|
|
217
412
|
return null;
|
|
218
413
|
}
|
|
219
414
|
}
|
|
415
|
+
function isAndroidScopedSecurityValidationClaim(line) {
|
|
416
|
+
return /\bAndroid\b[^.\n]{0,60}\bsecurity validation\b/i.test(line) || /\bsecurity validation\b[^.\n]{0,30}\bfor Android\b/i.test(line);
|
|
417
|
+
}
|
|
@@ -2,6 +2,7 @@ import { readBoundedFileText } from "../utils/boundedRead.js";
|
|
|
2
2
|
import { firstPathSegment, GENERIC_INFRA_BASENAMES, baseNameNoExt, isGenericOrTestPrefixedDeclarationName, } from "../utils/filePatternUtils.js";
|
|
3
3
|
import { deduplicateIssuesById, makeCodeRotIssue } from "../utils/issueFactories.js";
|
|
4
4
|
import { indexSourceFactsByPath } from "../utils/sourceFactsLookup.js";
|
|
5
|
+
import { isJvmCommonLowSignalDeclarationName } from "../utils/jvmSourceFactsUtils.js";
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
6
7
|
// v0.3.0 Batch 4 — duplicate/parallel implementation candidate detector.
|
|
7
8
|
//
|
|
@@ -250,6 +251,16 @@ function findDuplicateDeclarationCandidates(ctx) {
|
|
|
250
251
|
const nameLower = decl.name.toLowerCase();
|
|
251
252
|
if (isGenericOrTestPrefixedDeclarationName(nameLower))
|
|
252
253
|
continue;
|
|
254
|
+
// v0.3.3 Batch 2 -- Java/Kotlin-specific common/low-signal name
|
|
255
|
+
// suppression (Main, App, Config, Service, Controller, test*, ...).
|
|
256
|
+
// Gated to java/kotlin so existing TS/JS/Python behavior is
|
|
257
|
+
// unaffected -- a TS/JS file exporting `class Service {}` (a very
|
|
258
|
+
// ordinary, non-generic name in that ecosystem) must keep being
|
|
259
|
+
// considered, unlike the JVM ecosystem where these names are
|
|
260
|
+
// conventionally near-meaningless boilerplate labels.
|
|
261
|
+
if ((facts.language === "java" || facts.language === "kotlin") && isJvmCommonLowSignalDeclarationName(nameLower)) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
253
264
|
// v0.3.2 Batch 2 -- the key is scoped by `facts.analyzerId` (not
|
|
254
265
|
// `facts.language`) so a Python declaration is only ever compared
|
|
255
266
|
// against other Python declarations, never against a same-named
|
|
@@ -264,7 +275,14 @@ function findDuplicateDeclarationCandidates(ctx) {
|
|
|
264
275
|
// TypeScript/JavaScript duplicate behavior" regression this batch
|
|
265
276
|
// must not introduce. Scoping by `analyzerId` fixes the real
|
|
266
277
|
// cross-language bug (Python vs TS/JS) without touching TS/JS's own
|
|
267
|
-
// existing behavior at all.
|
|
278
|
+
// existing behavior at all. v0.3.3 Batch 2 -- this same scoping is
|
|
279
|
+
// why Java ("java-analyzer") and Kotlin ("kotlin-analyzer") never
|
|
280
|
+
// group with each other, TS/JS, or Python: they are two distinct
|
|
281
|
+
// analyzers with two distinct ids, deliberately not unified into one
|
|
282
|
+
// "JVM" scope in this batch (a same-named Java class and Kotlin
|
|
283
|
+
// class are not the same declaration in the same language, and
|
|
284
|
+
// conflating them risks a false "duplicate" signal across languages
|
|
285
|
+
// that don't even share syntax).
|
|
268
286
|
const key = `${facts.analyzerId}:${decl.kind}:${nameLower}`;
|
|
269
287
|
const entry = byKey.get(key) ?? { name: decl.name, kind: decl.kind, paths: new Set() };
|
|
270
288
|
entry.paths.add(facts.relativePath);
|