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

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 (29) hide show
  1. package/README.md +24 -18
  2. package/dist/scripts/audits/runAudit.js +8 -2
  3. package/dist/scripts/security/validate.js +16 -47
  4. package/dist/src/audits/codeRot/detectors/deadCodeCandidateDetector.js +191 -1
  5. package/dist/src/audits/codeRot/detectors/docsCodeMismatchDetector.js +202 -4
  6. package/dist/src/audits/codeRot/detectors/duplicateImplementationDetector.js +42 -9
  7. package/dist/src/audits/codeRot/detectors/testRotDetector.js +241 -1
  8. package/dist/src/audits/codeRot/utils/filePatternUtils.js +19 -1
  9. package/dist/src/audits/codeRot/utils/jvmSourceFactsUtils.js +153 -0
  10. package/dist/src/audits/core/auditConfig.js +2 -2
  11. package/dist/src/audits/core/auditRunner.js +19 -0
  12. package/dist/src/audits/core/auditTypes.js +14 -7
  13. package/dist/src/audits/core/javaAnalyzer.js +282 -0
  14. package/dist/src/audits/core/jvmProjectMetadata.js +106 -0
  15. package/dist/src/audits/core/kotlinAnalyzer.js +280 -0
  16. package/dist/src/audits/core/languageAnalyzerRegistry.js +12 -3
  17. package/dist/src/audits/core/projectInventory.js +76 -3
  18. package/dist/src/audits/core/pythonAnalyzer.js +328 -0
  19. package/dist/src/audits/core/pythonProjectMetadata.js +80 -0
  20. package/dist/src/audits/report/auditReportModel.js +3 -0
  21. package/dist/src/audits/report/renderAuditJsonReport.js +2 -0
  22. package/dist/src/audits/report/renderAuditTextReport.js +40 -0
  23. package/dist/src/audits/security/mapSecurityFindingToAuditIssue.js +70 -0
  24. package/dist/src/audits/security/securityAuditAdapter.js +70 -0
  25. package/dist/src/audits/security/securityAuditTypes.js +17 -0
  26. package/dist/src/securityValidation/report/buildSecurityReport.js +31 -0
  27. package/dist/src/securityValidation/report/writeSecurityReportFiles.js +28 -0
  28. package/dist/src/securityValidation/validate/resolveTarget.js +9 -0
  29. package/package.json +6 -8
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # my-dev-kit-lab
2
2
 
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.
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.2`. `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. 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 implementation is ahead of the published baseline: release-prepared, not-yet-published `v0.3.3` work adds Java/Kotlin source-facts support and JVM-aware code-rot detector integration on top of the published `v0.3.2` baseline. Package metadata is now `0.3.3` as part of release preparation. 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. Cross-language stability, Android validation, framework-aware profiles, and the `quality`/`project`/`all` audit types remain future roadmap work.
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
 
@@ -33,8 +33,10 @@ my-dev-kit is most useful when the repository is larger than the task. It helps
33
33
  - First experiment plugin: `context-strategy-comparison`
34
34
  - Target-aware experiment execution for local projects via `experiment:run -- --target <path>`
35
35
  - Plugin-aware JSON and HTML reports with plugin, target, variant, metric, artifact, warning, skip, and failure metadata
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.
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
+ - 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, 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 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
40
 
39
41
  ---
40
42
 
@@ -203,16 +205,18 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
203
205
 
204
206
  ---
205
207
 
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
208
+ ## Current baseline release positioning
209
+
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.2`, which includes the generic audit framework, the code-rot detector family, the language-aware code-rot substrate with TypeScript/JavaScript and Python support, and a first security-validation audit adapter, on top of the earlier experiment and automated security-validation work.
211
+
212
+ The release-prepared, not-yet-published `v0.3.3` implementation extends that baseline with Java/Kotlin analyzers, JVM metadata detection, Java/Kotlin detector support, and Java/Kotlin/Gradle/Maven docs-code-mismatch checks. This work is documented here as checked-out behavior, not as a published package state; package metadata is now `0.3.3`, while `v0.3.2` remains the previous published baseline until publication completes.
213
+
214
+ Planned roadmap direction after the published baseline:
215
+
216
+ - `v0.3.3`: Java/Kotlin code-rot support (release-prepared, not yet published)
217
+ - `v0.3.4`: cross-language stability for the language-aware code-rot track
218
+ - `v0.4.0` through `v0.4.2`: Android automated security validation, including an Android-specific extension of the security audit adapter
219
+ - manual pentest: post-v1 / version TBD
216
220
 
217
221
  ---
218
222
 
@@ -220,6 +224,8 @@ Planned roadmap direction after the published baseline:
220
224
 
221
225
  my-dev-kit-lab owns a release-security validation track for **my-dev-kit**. This work is separate from the experiment pipeline and does not replace the generic experiment-plugin roadmap. Its purpose is to generate release-validation evidence for the local CLI/package before release preparation.
222
226
 
227
+ `security:validate` remains the standalone, focused command for this validation and is unaffected by the current checked-out state. `npm run audit -- --types security` additionally adapts the same `security:validate` internals into the shared audit/report surface (mapped issues, a `securitySummary` field, and links to the original `reports/security/` output) — it does not replace or duplicate `security:validate`'s own checks or reports.
228
+
223
229
  This is not a web application pentest framework. **my-dev-kit** is a local CLI/package, so the validation model is CLI/package adversarial testing focused on whether it remains:
224
230
 
225
231
  - local-first
@@ -250,7 +256,7 @@ Each security command can validate my-dev-kit-lab itself or another local projec
250
256
 
251
257
  `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.
252
258
 
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.
259
+ 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.
254
260
 
255
261
  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.
256
262
 
@@ -18,7 +18,7 @@ import { writeAuditReports } from "../../src/audits/report/writeAuditReports.js"
18
18
  // renderAuditJsonReport.ts, renderAuditTextReport.ts, writeAuditReports.ts.
19
19
  // Mirrors the structure of scripts/security/validate.ts.
20
20
  // ---------------------------------------------------------------------------
21
- const USAGE = "Usage: npm run audit -- [--target <path>] [--types code-rot] " +
21
+ const USAGE = "Usage: npm run audit -- [--target <path>] [--types code-rot,security] " +
22
22
  "[--include docs,tests,package,architecture,cli] [--format text,json] " +
23
23
  "[--fail-on blocker|high|medium|low|none] [--out <path>]";
24
24
  const rawArgs = process.argv.slice(2);
@@ -80,9 +80,15 @@ if (model.summary.detectorErrorCount > 0) {
80
80
  console.log(` [${e.id}] ${e.message}`);
81
81
  }
82
82
  }
83
- if (model.summary.noDetectorsRegistered) {
83
+ if (model.summary.noDetectorsRegistered && !model.securitySummary.ran) {
84
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
85
  }
86
+ if (model.securitySummary.ran) {
87
+ console.log(`\nSecurity validation: verdict=${model.securitySummary.verdictLabel} checks=${model.securitySummary.totalChecks} findings(blocker=${model.securitySummary.findingCounts.blocker} major=${model.securitySummary.findingCounts.major} minor=${model.securitySummary.findingCounts.minor} info=${model.securitySummary.findingCounts.informational})`);
88
+ if (model.securitySummary.reportPaths.text) {
89
+ console.log(` Full report: ${model.securitySummary.reportPaths.text}`);
90
+ }
91
+ }
86
92
  console.log(`\nInventory: ${model.inventory.totalScannedFileCount} file(s) scanned, ${model.inventory.skippedFileCount} skipped`);
87
93
  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
94
  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}`);
@@ -1,9 +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";
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";
7
7
  import { parseSecurityValidateArgs, normalizeSecurityValidateConfig, applyProfileDefaultChecksIfApplicable, } from "../../src/securityValidation/validate/cliOptions.js";
8
8
  import { resolveAttackProfile } from "../../src/securityValidation/attackScenarios/attackProfile.js";
9
9
  import { findingsBreachFailOnThreshold } from "../../src/securityValidation/validate/verdict.js";
@@ -81,55 +81,24 @@ const summary = await runSecurityValidation({
81
81
  // downgrade an existing blocker.
82
82
  const failOnBreached = findingsBreachFailOnThreshold(summary.findings, config.failOnThreshold);
83
83
  // Build report object
84
- const report = {
85
- metadata: {
86
- toolRoot: summary.toolRoot,
87
- toolPackageName: summary.toolPackageName,
88
- toolPackageVersion: summary.toolPackageVersion,
89
- targetRoot: summary.targetRoot,
90
- targetDescription: summary.targetDescription,
91
- packageName: summary.packageName,
92
- packageVersion: summary.packageVersion,
93
- branch: summary.auditedBranch,
94
- commit: summary.auditedCommit,
95
- isSelf: summary.isSelf,
96
- generatedAt: summary.finishedAt,
97
- totalDurationMs: new Date(summary.finishedAt).getTime() - new Date(summary.startedAt).getTime(),
98
- profile: config.profile,
99
- selectedChecks: config.checks,
100
- failOnThreshold: config.failOnThreshold,
101
- formats: config.formats,
102
- failOnBreached,
103
- isFullReleaseGate: summary.isFullReleaseGate,
104
- },
105
- sections: [],
106
- allChecks: summary.checks,
107
- allFindings: summary.findings,
108
- verdict: summary.verdict,
109
- recommendedNextStep: summary.recommendedNextStep,
110
- attackResults: summary.attackResults,
111
- verdictReasonSummary: summary.verdictReasonSummary,
112
- };
84
+ const report = buildSecurityReportFromSummary(summary, {
85
+ profile: config.profile,
86
+ selectedChecks: config.checks,
87
+ failOnThreshold: config.failOnThreshold,
88
+ formats: config.formats,
89
+ failOnBreached,
90
+ });
113
91
  const textReport = renderTextReport(report);
114
- const jsonReport = renderJsonReport(report);
115
92
  // Determine output directory
116
93
  const reportsDir = config.out;
117
- if (!fs.existsSync(reportsDir)) {
118
- fs.mkdirSync(reportsDir, { recursive: true });
119
- }
120
94
  // Determine report filename prefix
121
95
  const prefix = args.reportPrefix ?? reportFilenamePrefix(target);
122
- const txtPath = path.join(reportsDir, `${prefix}-security-validation.txt`);
123
- const jsonPath = path.join(reportsDir, `${prefix}-security-validation.json`);
124
- const writtenPaths = [];
125
- if (config.formats.includes("text")) {
126
- fs.writeFileSync(txtPath, textReport, "utf8");
127
- writtenPaths.push(txtPath);
128
- }
129
- if (config.formats.includes("json")) {
130
- fs.writeFileSync(jsonPath, jsonReport, "utf8");
131
- writtenPaths.push(jsonPath);
132
- }
96
+ const { textPath: txtPath, jsonPath, writtenPaths } = writeSecurityReportFiles({
97
+ outDir: reportsDir,
98
+ prefix,
99
+ report,
100
+ formats: config.formats,
101
+ });
133
102
  // Print report to stdout
134
103
  console.log(textReport);
135
104
  console.log(`\nReports written:`);
@@ -1,8 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { readBoundedFileText } from "../utils/boundedRead.js";
3
- import { baseNameNoExt } from "../utils/filePatternUtils.js";
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
  //
@@ -50,6 +51,10 @@ export const DEAD_CODE_CANDIDATE_DETECTOR = {
50
51
  if (ctx.config.include.includes("tests")) {
51
52
  issues.push(...findUnreferencedFixtures(ctx));
52
53
  }
54
+ if (ctx.config.include.includes("cli") || ctx.config.include.includes("architecture")) {
55
+ issues.push(...findPossiblyUnreferencedPythonDeclarations(ctx));
56
+ issues.push(...findPossiblyUnreferencedJvmDeclarations(ctx));
57
+ }
53
58
  return deduplicateIssuesById(issues);
54
59
  },
55
60
  };
@@ -250,6 +255,191 @@ function buildSourceFactsEvidence(facts) {
250
255
  confidence: "medium",
251
256
  };
252
257
  }
258
+ // v0.3.2 Batch 2 -- conservative, declaration-level Python dead-code
259
+ // candidate check. TS/JS module-level "unused exported symbol" detection is
260
+ // explicitly SKIPPED in this file's own header comment (no safe/cheap way
261
+ // without a real type-checker) -- this Python-only check is narrower and
262
+ // safer than that skipped TS/JS idea would be, because Python source facts
263
+ // already carry declarations *and* a repo-wide cross-file "imported name"
264
+ // index (unlike anything comparable for TS/JS bare specifiers), so the
265
+ // false-positive surface is much smaller. Every one of the skip conditions
266
+ // below is a deliberate, spec-required conservatism -- see the requirements
267
+ // list in this batch's task description. Findings stay at the lowest
268
+ // severity/confidence tier in this detector's own schema (matches
269
+ // findUnreferencedFixtures()'s tier) and use "candidate"/"may indicate"
270
+ // wording throughout, never "unused" or "dead".
271
+ function findPossiblyUnreferencedPythonDeclarations(ctx) {
272
+ if (!ctx.sourceFacts)
273
+ return [];
274
+ const index = indexSourceFactsByPath(ctx.sourceFacts);
275
+ // Cross-file "this name was imported somewhere" signal -- a declaration
276
+ // named here is referenced by at least one `from module import name`
277
+ // elsewhere in the scanned Python file set, which is the strongest signal
278
+ // this conservative scan can offer (Python source facts carry no
279
+ // call/identifier reference tracking -- see pythonAnalyzer.ts's header
280
+ // comment on why `references` stays empty by design).
281
+ const importedNamesAcrossProject = new Set();
282
+ for (const facts of index.values()) {
283
+ if (facts.language !== "python" || facts.parseStatus !== "parsed")
284
+ continue;
285
+ for (const imp of facts.imports) {
286
+ for (const name of imp.importedNames ?? [])
287
+ importedNamesAcrossProject.add(name);
288
+ }
289
+ }
290
+ const issues = [];
291
+ for (const facts of index.values()) {
292
+ if (facts.language !== "python" || facts.role !== "source" || facts.parseStatus !== "parsed")
293
+ continue;
294
+ // __init__.py commonly exists only to re-export a package's public
295
+ // surface or to mark a directory as a package -- its declarations (if
296
+ // any) are conservatively excluded entirely rather than guessed at.
297
+ if (path.basename(facts.relativePath) === "__init__.py")
298
+ continue;
299
+ for (const decl of facts.declarations) {
300
+ // Methods are excluded entirely -- this detector has no existing
301
+ // method-level dead-code evidence model for any language (TS/JS
302
+ // methods are never flagged either), and Python method visibility is
303
+ // even harder to infer safely (inherited/overridden/dispatched via
304
+ // duck typing).
305
+ if (decl.kind === "method")
306
+ continue;
307
+ if (!decl.exported)
308
+ continue; // leading-underscore convention already applied by pythonAnalyzer.ts
309
+ if (isGenericOrTestPrefixedDeclarationName(decl.name))
310
+ continue; // main/run/setup/teardown/__init__/__call__/test_*
311
+ // A name explicitly listed in a real `__all__` is the file's own
312
+ // strongest "this is public API" declaration -- skip rather than
313
+ // second-guess it.
314
+ if (facts.exports.some((e) => e.name === decl.name))
315
+ continue;
316
+ if (importedNamesAcrossProject.has(decl.name))
317
+ continue;
318
+ issues.push(makeCodeRotIssue({
319
+ auditType: "code-rot",
320
+ detectorId: DETECTOR_ID,
321
+ idCues: ["unreferenced-python-declaration", facts.relativePath, decl.kind, decl.name],
322
+ title: `Python ${decl.kind} "${decl.name}" in "${facts.relativePath}" has no detected cross-file import (weak signal)`,
323
+ description: `"${decl.name}" (${decl.kind}) is declared in "${facts.relativePath}" and does not appear as an imported name in any other scanned Python file. This is a deterministic source-facts signal from a conservative, non-semantic scan -- it does not resolve dynamic imports, framework/plugin discovery, string-based dispatch, or usage from outside the scanned project, so it may indicate dead code but is not proof. Confirm manually before removing.`,
324
+ severity: "info",
325
+ confidence: "low",
326
+ falsePositiveRisk: "high",
327
+ category: "dead-code-candidate",
328
+ recommendedAction: "Confirm the declaration is genuinely unused before removing it.",
329
+ suggestedFixStrategy: `Remove "${decl.name}" from "${facts.relativePath}" if confirmed unused, or reference it from another module.`,
330
+ validationCommands: ["npm run audit -- --types code-rot --include cli"],
331
+ releaseBlocking: false,
332
+ implementationBlocking: false,
333
+ autoFixEligible: false,
334
+ evidence: [
335
+ {
336
+ kind: "reference",
337
+ message: `Source facts: the Python analyzer parsed this file and found no other scanned Python file importing the name "${decl.name}". Static-analysis limitation -- confidence reduced because this scan cannot see dynamic imports or usage outside the scanned file set.`,
338
+ filePath: facts.relativePath,
339
+ source: DETECTOR_ID,
340
+ confidence: "low",
341
+ },
342
+ ],
343
+ affectedFiles: [facts.relativePath],
344
+ }));
345
+ }
346
+ }
347
+ return issues;
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
+ }
253
443
  function findOldOrDeprecatedFilesNotInDocs(ctx, docsConcat) {
254
444
  const issues = [];
255
445
  for (const file of ctx.inventory.files) {