@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.
Files changed (49) hide show
  1. package/README.md +11 -8
  2. package/dist/scripts/security/validate.js +8 -18
  3. package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +96 -0
  4. package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +202 -4
  5. package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +19 -1
  6. package/dist/src/audits/codeRot/detectors/testRotDetector.js +126 -0
  7. package/dist/src/audits/codeRot/utils/jvmSourceFactsUtils.js +153 -0
  8. package/dist/src/audits/core/collectSourceFacts.js +21 -10
  9. package/dist/src/audits/core/javaAnalyzer.js +282 -0
  10. package/dist/src/audits/core/jvmProjectMetadata.js +106 -0
  11. package/dist/src/audits/core/kotlinAnalyzer.js +280 -0
  12. package/dist/src/audits/core/languageAnalyzerRegistry.js +9 -3
  13. package/dist/src/audits/core/projectInventory.js +41 -0
  14. package/dist/src/audits/security/securityAuditAdapter.js +15 -12
  15. package/dist/src/securityValidation/report/writeSecurityReportFiles.js +28 -0
  16. package/package.json +97 -97
  17. package/tests/fixtures/audits/mixed-language/mixed-docs-claims/README.md +16 -0
  18. package/tests/fixtures/audits/mixed-language/mixed-docs-claims/build.gradle.kts +4 -0
  19. package/tests/fixtures/audits/mixed-language/mixed-docs-claims/src/main/java/com/example/Widget.java +7 -0
  20. package/tests/fixtures/audits/mixed-language/mixed-docs-claims/src/main/kotlin/com/example/Gadget.kt +7 -0
  21. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/README.md +10 -0
  22. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/build/Main.class +1 -0
  23. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/dist/index.js +1 -0
  24. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/generated/schema.ts +1 -0
  25. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/src/index.ts +3 -0
  26. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/vendor/lib.py +2 -0
  27. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/README.md +10 -0
  28. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/build.gradle.kts +8 -0
  29. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/settings.gradle.kts +1 -0
  30. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/main/java/com/example/FooService.java +7 -0
  31. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/main/kotlin/com/example/BarService.kt +7 -0
  32. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/test/java/com/example/FooServiceTest.java +8 -0
  33. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/test/kotlin/com/example/BarServiceTest.kt +8 -0
  34. package/tests/fixtures/audits/mixed-language/mixed-python-ts/README.md +8 -0
  35. package/tests/fixtures/audits/mixed-language/mixed-python-ts/src/app.py +7 -0
  36. package/tests/fixtures/audits/mixed-language/mixed-python-ts/src/index.ts +3 -0
  37. package/tests/fixtures/audits/mixed-language/mixed-python-ts/tests/test_app.py +5 -0
  38. package/tests/fixtures/audits/mixed-language/mixed-ts-js/README.md +7 -0
  39. package/tests/fixtures/audits/mixed-language/mixed-ts-js/src/helper.js +5 -0
  40. package/tests/fixtures/audits/mixed-language/mixed-ts-js/src/index.ts +3 -0
  41. package/tests/fixtures/audits/mixed-language/mixed-ts-js/tests/index.sample.ts +5 -0
  42. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/README.md +10 -0
  43. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/build.gradle.kts +4 -0
  44. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/pytests/test_app.py +5 -0
  45. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/app.py +2 -0
  46. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/helper.js +5 -0
  47. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/index.ts +3 -0
  48. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/main/java/com/example/Multiplier.java +7 -0
  49. 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.1`. `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. 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.
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
- The checked-out package state is `v0.3.2` release-prepared on top of the published `v0.3.1` baseline: Python-aware code-rot support (a Python source-facts analyzer, Python project metadata, and Python-aware dead-code/duplicate-implementation/test-rot detector signals) and a security-validation audit adapter (`npm run audit -- --types security`, mapped security findings, and a `securitySummary` audit report field). Package metadata is bumped for release preparation, but `v0.3.2` is not published yet; Java, Kotlin, cross-language stability, Android validation, framework-aware profiles, and the `quality`/`project`/`all` audit types remain future roadmap work.
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 checked-out package state: normalized language/file-role inventory, a source-facts model and collector, a TypeScript/JavaScript analyzer, and a Python analyzer. Source facts feed the `dead-code-candidate`, `duplicate-implementation-candidate`, and `test-rot` detectors with parsed-import/export/declaration evidence for both languages. Python project metadata (presence of `pyproject.toml`, `requirements.txt`, `setup.py`/`setup.cfg`, `tox.ini`, `pytest.ini`, and a best-effort project name) is collected and reported as its own audit report field. Java and Kotlin remain inventory-classified but fallback-only; no analyzer is registered for them yet.
39
- - Security-validation audit adapter, current checked-out 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.
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 current published npm baseline is `v0.3.1`, which includes the generic audit framework, the code-rot detector family, and the language-aware code-rot substrate with TypeScript/JavaScript support, on top of the earlier experiment and automated security-validation work.
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
- Planned roadmap direction after the published baseline:
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
- - `v0.3.2`: Python code-rot support plus a first security-validation audit adapter, release-prepared and not yet published
215
- - `v0.3.3` through `v0.3.4`: Java/Kotlin and cross-language stability for the language-aware code-rot track
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 = path.join(reportsDir, `${prefix}-security-validation.txt`);
102
- const jsonPath = path.join(reportsDir, `${prefix}-security-validation.json`);
103
- const writtenPaths = [];
104
- if (config.formats.includes("text")) {
105
- fs.writeFileSync(txtPath, textReport, "utf8");
106
- writtenPaths.push(txtPath);
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: (sot) => sot.security.hasSecurityValidationSourceDir && sot.security.hasSecurityValidateScript,
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: (sot) => sot.experiment.hasExperimentsSourceDir && sot.experiment.hasExperimentListScript,
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 and full-release-gate-for-scoped-checks wording.",
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(ctx.sourceOfTruth);
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);