weavatrix 0.3.14 → 1.1.0
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/LICENSE +1 -0
- package/README.md +132 -200
- package/bin/native/darwin-arm64/weavatrix +0 -0
- package/bin/native/darwin-x64/weavatrix +0 -0
- package/bin/native/linux-arm64/weavatrix +0 -0
- package/bin/native/linux-x64/weavatrix +0 -0
- package/bin/native/win32-arm64/weavatrix.exe +0 -0
- package/bin/native/win32-x64/weavatrix.exe +0 -0
- package/bin/resolve-binary.mjs +73 -0
- package/bin/run-native.mjs +26 -0
- package/bin/weavatrix-mcp.mjs +7 -6
- package/bin/weavatrix.mjs +5 -0
- package/package.json +10 -67
- package/server.json +38 -0
- package/skill/SKILL.md +62 -374
- package/skill/agents/openai.yaml +4 -0
- package/skill/references/tool-routing.md +45 -0
- package/SECURITY.md +0 -101
- package/docs/adr/0001-v0.3-offline-online-split.md +0 -60
- package/docs/agent-task-benchmark.md +0 -58
- package/docs/releases/v0.2.19.md +0 -33
- package/docs/releases/v0.3.0.md +0 -43
- package/docs/releases/v0.3.1.md +0 -30
- package/docs/releases/v0.3.10.md +0 -76
- package/docs/releases/v0.3.11.md +0 -55
- package/docs/releases/v0.3.12.md +0 -44
- package/docs/releases/v0.3.13.md +0 -34
- package/docs/releases/v0.3.14.md +0 -37
- package/docs/releases/v0.3.2.md +0 -15
- package/docs/releases/v0.3.3.md +0 -25
- package/docs/releases/v0.3.4.md +0 -40
- package/docs/releases/v0.3.5.md +0 -50
- package/docs/releases/v0.3.6.md +0 -44
- package/docs/releases/v0.3.7.md +0 -37
- package/docs/releases/v0.3.8.md +0 -21
- package/docs/releases/v0.3.9.md +0 -36
- package/scripts/run-agent-task-benchmark.mjs +0 -118
- package/src/analysis/allowed-test-runner.js +0 -70
- package/src/analysis/architecture/contract-graph.js +0 -119
- package/src/analysis/architecture/contract-schema.js +0 -110
- package/src/analysis/architecture/contract-storage.js +0 -35
- package/src/analysis/architecture/contract-verification.js +0 -168
- package/src/analysis/architecture-contract.js +0 -16
- package/src/analysis/audit-debt.js +0 -94
- package/src/analysis/audit-extensions.js +0 -60
- package/src/analysis/cargo-dependency-evidence.js +0 -108
- package/src/analysis/cargo-manifests.js +0 -81
- package/src/analysis/change-classification/diff-parser.js +0 -103
- package/src/analysis/change-classification/options.js +0 -40
- package/src/analysis/change-classification/symbol-classifier.js +0 -177
- package/src/analysis/change-classification.js +0 -188
- package/src/analysis/coverage-reports-tarpaulin.js +0 -112
- package/src/analysis/coverage-reports.js +0 -246
- package/src/analysis/cycle-route.js +0 -14
- package/src/analysis/data-flow-evidence.js +0 -114
- package/src/analysis/dead-check.js +0 -300
- package/src/analysis/dead-code-review/candidates.js +0 -194
- package/src/analysis/dead-code-review/policy.js +0 -23
- package/src/analysis/dead-code-review.js +0 -119
- package/src/analysis/dep-check-ecosystems.js +0 -205
- package/src/analysis/dep-check.js +0 -290
- package/src/analysis/dep-rules.js +0 -10
- package/src/analysis/dependency/conventions.js +0 -16
- package/src/analysis/dependency/scoped-dependencies.js +0 -51
- package/src/analysis/dependency/source-references.js +0 -21
- package/src/analysis/duplicate-groups.js +0 -104
- package/src/analysis/duplicates-worker.js +0 -11
- package/src/analysis/duplicates.compute.js +0 -269
- package/src/analysis/duplicates.js +0 -15
- package/src/analysis/duplicates.run.js +0 -52
- package/src/analysis/duplicates.tokenize.js +0 -199
- package/src/analysis/endpoints/common.js +0 -79
- package/src/analysis/endpoints/extract.js +0 -120
- package/src/analysis/endpoints/inventory.js +0 -84
- package/src/analysis/endpoints/mounts.js +0 -129
- package/src/analysis/endpoints-java.js +0 -260
- package/src/analysis/endpoints-rust.js +0 -124
- package/src/analysis/endpoints.js +0 -3
- package/src/analysis/findings.js +0 -70
- package/src/analysis/git-history/analytics.js +0 -177
- package/src/analysis/git-history/collector.js +0 -67
- package/src/analysis/git-history/options.js +0 -66
- package/src/analysis/git-history.js +0 -143
- package/src/analysis/git-ref-graph.js +0 -76
- package/src/analysis/go-dependency-evidence.js +0 -62
- package/src/analysis/graph-analysis.aggregate.js +0 -296
- package/src/analysis/graph-analysis.edges.js +0 -24
- package/src/analysis/graph-analysis.js +0 -7
- package/src/analysis/graph-analysis.refs.js +0 -202
- package/src/analysis/graph-analysis.summaries.js +0 -65
- package/src/analysis/health-capabilities.js +0 -83
- package/src/analysis/hot-path-review.js +0 -266
- package/src/analysis/http-contract-wrappers.js +0 -241
- package/src/analysis/http-contracts/analysis.js +0 -179
- package/src/analysis/http-contracts/client-call-detection.js +0 -82
- package/src/analysis/http-contracts/client-call-parser.js +0 -273
- package/src/analysis/http-contracts/graph-context.js +0 -143
- package/src/analysis/http-contracts/matching.js +0 -61
- package/src/analysis/http-contracts/shared.js +0 -85
- package/src/analysis/http-contracts.js +0 -6
- package/src/analysis/internal-audit/dependency-health.js +0 -113
- package/src/analysis/internal-audit/python-manifests.js +0 -78
- package/src/analysis/internal-audit/repo-files.js +0 -50
- package/src/analysis/internal-audit/supply-chain.js +0 -149
- package/src/analysis/internal-audit.collect.js +0 -213
- package/src/analysis/internal-audit.js +0 -9
- package/src/analysis/internal-audit.reach.js +0 -201
- package/src/analysis/internal-audit.run.js +0 -256
- package/src/analysis/java-source.js +0 -36
- package/src/analysis/jvm-artifact-index.js +0 -87
- package/src/analysis/jvm-dependency-evidence.js +0 -138
- package/src/analysis/jvm-manifests.js +0 -114
- package/src/analysis/manifests.js +0 -123
- package/src/analysis/package-reachability.js +0 -39
- package/src/analysis/source-complexity.ast.js +0 -97
- package/src/analysis/source-complexity.constants.js +0 -55
- package/src/analysis/source-complexity.js +0 -14
- package/src/analysis/source-complexity.report.js +0 -81
- package/src/analysis/source-complexity.walk.js +0 -228
- package/src/analysis/source-correctness/retry-patterns.js +0 -93
- package/src/analysis/source-correctness.js +0 -222
- package/src/analysis/static-test-reachability.js +0 -150
- package/src/analysis/structure/dependency-graph.js +0 -151
- package/src/analysis/structure/findings.js +0 -162
- package/src/analysis/task-retrieval.js +0 -107
- package/src/analysis/transport-contracts.js +0 -224
- package/src/analysis/transport-runtime-evidence.js +0 -155
- package/src/analysis-kit.mjs +0 -17
- package/src/bounds.js +0 -4
- package/src/build-graph.js +0 -209
- package/src/child-env.js +0 -7
- package/src/config.js +0 -5
- package/src/extension/local-services.mjs +0 -75
- package/src/git-exec.js +0 -67
- package/src/graph/build-worker.js +0 -49
- package/src/graph/builder/go-receiver-resolution.js +0 -112
- package/src/graph/builder/js/dynamic-import.js +0 -26
- package/src/graph/builder/js/queries.js +0 -48
- package/src/graph/builder/lang-csharp.js +0 -40
- package/src/graph/builder/lang-css.js +0 -29
- package/src/graph/builder/lang-go.js +0 -140
- package/src/graph/builder/lang-html.js +0 -29
- package/src/graph/builder/lang-java-calls.js +0 -142
- package/src/graph/builder/lang-java.js +0 -215
- package/src/graph/builder/lang-js.js +0 -291
- package/src/graph/builder/lang-python.js +0 -235
- package/src/graph/builder/lang-rust-calls.js +0 -63
- package/src/graph/builder/lang-rust.js +0 -299
- package/src/graph/builder/lang-solidity.js +0 -126
- package/src/graph/builder/lang-sql.js +0 -263
- package/src/graph/builder/pass2-resolution.js +0 -81
- package/src/graph/builder/spec-pkg.js +0 -87
- package/src/graph/community.js +0 -27
- package/src/graph/edge-provenance.js +0 -31
- package/src/graph/file-lock.js +0 -69
- package/src/graph/freshness-probe.js +0 -142
- package/src/graph/graph-filter.js +0 -63
- package/src/graph/incremental-refresh.js +0 -235
- package/src/graph/internal-builder.barrels.js +0 -235
- package/src/graph/internal-builder.build.js +0 -300
- package/src/graph/internal-builder.java.js +0 -33
- package/src/graph/internal-builder.js +0 -12
- package/src/graph/internal-builder.langs.js +0 -157
- package/src/graph/internal-builder.pass2.js +0 -163
- package/src/graph/internal-builder.resolvers.js +0 -286
- package/src/graph/layout.js +0 -51
- package/src/graph/node-id.js +0 -7
- package/src/graph/parser-artifact-boundary.js +0 -55
- package/src/graph/relations.js +0 -4
- package/src/graph/repo-registry.js +0 -124
- package/src/graph/resolvers/rust.js +0 -117
- package/src/infra/infra-items.js +0 -255
- package/src/infra/infra-registry.js +0 -184
- package/src/infra/infra.detect.js +0 -119
- package/src/infra/infra.js +0 -38
- package/src/infra/infra.match.js +0 -102
- package/src/infra/infra.scan.js +0 -93
- package/src/mcp/actions/graph-lifecycle.mjs +0 -148
- package/src/mcp/architecture-bootstrap.mjs +0 -168
- package/src/mcp/architecture-starter.mjs +0 -234
- package/src/mcp/catalog.mjs +0 -233
- package/src/mcp/company-contract-page.mjs +0 -142
- package/src/mcp/company-contract-reconcile.mjs +0 -87
- package/src/mcp/company-contract-verdict.mjs +0 -42
- package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
- package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
- package/src/mcp/evidence/package-graph-common.mjs +0 -115
- package/src/mcp/evidence/package-lock-graph.mjs +0 -92
- package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
- package/src/mcp/evidence-snapshot.common.mjs +0 -158
- package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
- package/src/mcp/evidence-snapshot.health.mjs +0 -102
- package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
- package/src/mcp/evidence-snapshot.mjs +0 -50
- package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
- package/src/mcp/evidence-snapshot.structure.mjs +0 -84
- package/src/mcp/extension-api.mjs +0 -77
- package/src/mcp/git-output.mjs +0 -7
- package/src/mcp/graph/context-core.mjs +0 -111
- package/src/mcp/graph/context-seeds.mjs +0 -207
- package/src/mcp/graph/context-state.mjs +0 -85
- package/src/mcp/graph/reverse-reach.mjs +0 -42
- package/src/mcp/graph/tools-core.mjs +0 -143
- package/src/mcp/graph/tools-query.mjs +0 -276
- package/src/mcp/graph-context.mjs +0 -5
- package/src/mcp/graph-diff.mjs +0 -218
- package/src/mcp/health/audit-format.mjs +0 -199
- package/src/mcp/health/audit.mjs +0 -199
- package/src/mcp/health/dead-code.mjs +0 -110
- package/src/mcp/health/duplicates.mjs +0 -121
- package/src/mcp/health/endpoints.mjs +0 -71
- package/src/mcp/health/structure.mjs +0 -220
- package/src/mcp/runtime-version.mjs +0 -31
- package/src/mcp/server/auto-refresh.mjs +0 -66
- package/src/mcp/server/runtime-config.mjs +0 -43
- package/src/mcp/server/shutdown.mjs +0 -40
- package/src/mcp/staleness-notice.mjs +0 -20
- package/src/mcp/sync/evidence-architecture.mjs +0 -54
- package/src/mcp/sync/evidence-common.mjs +0 -88
- package/src/mcp/sync/evidence-duplicates.mjs +0 -97
- package/src/mcp/sync/evidence-health.mjs +0 -56
- package/src/mcp/sync/evidence-packages.mjs +0 -95
- package/src/mcp/sync/payload-common.mjs +0 -97
- package/src/mcp/sync/payload-v2.mjs +0 -54
- package/src/mcp/sync/payload-v3.mjs +0 -79
- package/src/mcp/sync-evidence.mjs +0 -23
- package/src/mcp/sync-payload.mjs +0 -70
- package/src/mcp/tool-result.mjs +0 -54
- package/src/mcp/tools-actions.mjs +0 -11
- package/src/mcp/tools-architecture.mjs +0 -177
- package/src/mcp/tools-company.mjs +0 -251
- package/src/mcp/tools-context.mjs +0 -222
- package/src/mcp/tools-endpoints.mjs +0 -207
- package/src/mcp/tools-graph-hubs.mjs +0 -78
- package/src/mcp/tools-graph.mjs +0 -17
- package/src/mcp/tools-health.mjs +0 -24
- package/src/mcp/tools-history.mjs +0 -28
- package/src/mcp/tools-impact-change.mjs +0 -220
- package/src/mcp/tools-impact-precision.mjs +0 -89
- package/src/mcp/tools-impact.mjs +0 -169
- package/src/mcp/tools-source.mjs +0 -238
- package/src/mcp/tools-verified-change.mjs +0 -200
- package/src/mcp-rg.mjs +0 -50
- package/src/mcp-runtime.mjs +0 -2
- package/src/mcp-server.mjs +0 -298
- package/src/mcp-source-tools.mjs +0 -163
- package/src/path-classification.js +0 -226
- package/src/path-ignore.js +0 -69
- package/src/precision/lsp-client/constants.js +0 -12
- package/src/precision/lsp-client/environment.js +0 -20
- package/src/precision/lsp-client/errors.js +0 -15
- package/src/precision/lsp-client/lifecycle.js +0 -114
- package/src/precision/lsp-client/message-parser.js +0 -81
- package/src/precision/lsp-client/protocol.js +0 -212
- package/src/precision/lsp-client/registry.js +0 -59
- package/src/precision/lsp-client/repo-uri.js +0 -60
- package/src/precision/lsp-client/stdio-client.js +0 -140
- package/src/precision/lsp-client.js +0 -10
- package/src/precision/lsp-overlay/budget-policy.js +0 -78
- package/src/precision/lsp-overlay/build.js +0 -261
- package/src/precision/lsp-overlay/contract.js +0 -61
- package/src/precision/lsp-overlay/reference-results.js +0 -108
- package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
- package/src/precision/lsp-overlay/source-session.js +0 -142
- package/src/precision/lsp-overlay/store.js +0 -150
- package/src/precision/lsp-overlay/target-index.js +0 -152
- package/src/precision/lsp-overlay/target-query.js +0 -55
- package/src/precision/lsp-overlay.js +0 -11
- package/src/precision/symbol-query.js +0 -224
- package/src/precision/typescript-lsp-provider.js +0 -12
- package/src/precision/typescript-provider/client.js +0 -89
- package/src/precision/typescript-provider/discovery.js +0 -124
- package/src/precision/typescript-provider/isolated-runtime.js +0 -39
- package/src/precision/typescript-provider/project-host.js +0 -254
- package/src/precision/typescript-provider/project-safety.js +0 -166
- package/src/precision/typescript-provider/reference-usage.js +0 -53
- package/src/process.js +0 -95
- package/src/repo-path.js +0 -53
- package/src/scan/discover.inventory.js +0 -78
- package/src/scan/discover.js +0 -5
- package/src/scan/discover.list.js +0 -79
- package/src/scan/discover.stack.js +0 -227
- package/src/scan/search.core.js +0 -24
- package/src/scan/search.git.js +0 -102
- package/src/scan/search.js +0 -9
- package/src/scan/search.node.js +0 -83
- package/src/scan/search.preview.js +0 -49
- package/src/scan/search.rg.js +0 -145
- package/src/security/advisory-store.js +0 -172
- package/src/security/installed-jvm-rust.js +0 -46
- package/src/security/installed.js +0 -280
- package/src/security/malware-file-heuristics.js +0 -121
- package/src/security/malware-heuristics.exclusions.js +0 -134
- package/src/security/malware-heuristics.js +0 -15
- package/src/security/malware-heuristics.roots.js +0 -145
- package/src/security/malware-heuristics.scan.js +0 -89
- package/src/security/malware-heuristics.sweep.js +0 -122
- package/src/security/malware-scoring.js +0 -105
- package/src/security/match.js +0 -85
- package/src/security/registry-sig.classify.js +0 -137
- package/src/security/registry-sig.js +0 -18
- package/src/security/registry-sig.rules.js +0 -192
- package/src/security/rust-advisory-report.js +0 -60
- package/src/security/typosquat.js +0 -81
- package/src/util.js +0 -40
- package/src/version.js +0 -7
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { basename, join } from "node:path";
|
|
3
|
-
import { computeDead, computeUnusedExports } from "./dead-check.js";
|
|
4
|
-
import { computePyDepFindings, computeScopedDepFindings } from "./dep-check.js";
|
|
5
|
-
import { computeStructureFindings } from "./dep-rules.js";
|
|
6
|
-
import { makeFinding, sortFindings, summarizeFindings } from "./findings.js";
|
|
7
|
-
import { graphOutDirForRepo } from "../graph/layout.js";
|
|
8
|
-
import {
|
|
9
|
-
TEST_FILE_RE,
|
|
10
|
-
collectConfigTexts,
|
|
11
|
-
collectNonRuntimeRoots,
|
|
12
|
-
collectPackageScopes,
|
|
13
|
-
collectPyManifest,
|
|
14
|
-
collectSourceTexts,
|
|
15
|
-
listRepoFiles,
|
|
16
|
-
readJson,
|
|
17
|
-
readRepoJson,
|
|
18
|
-
workspacePkgNames,
|
|
19
|
-
} from "./internal-audit.collect.js";
|
|
20
|
-
import { computeReachability, entryFiles } from "./internal-audit.reach.js";
|
|
21
|
-
import { PATH_CLASS_NAMES, createPathClassifier, hasPathClass } from "../path-classification.js";
|
|
22
|
-
import { createRepoBoundary } from "../repo-path.js";
|
|
23
|
-
import { analyzeSourceCorrectness } from "./source-correctness.js";
|
|
24
|
-
import { collectJvmDependencyEvidence } from "./jvm-dependency-evidence.js";
|
|
25
|
-
import { buildDependencyHealth } from "./internal-audit/dependency-health.js";
|
|
26
|
-
import { runSupplyChainChecks } from "./internal-audit/supply-chain.js";
|
|
27
|
-
import { collectGoDependencyEvidence } from "./go-dependency-evidence.js";
|
|
28
|
-
import { collectCargoDependencyEvidence } from "./cargo-dependency-evidence.js";
|
|
29
|
-
|
|
30
|
-
export async function runInternalAudit(repoPath, {
|
|
31
|
-
graph,
|
|
32
|
-
advisoryStorePath,
|
|
33
|
-
skipMalwareScan = false,
|
|
34
|
-
malwareExclusions = {},
|
|
35
|
-
rgPath = "",
|
|
36
|
-
} = {}) {
|
|
37
|
-
if (!existsSync(repoPath)) return { ok: false, error: "Repo path not found" };
|
|
38
|
-
const boundary = createRepoBoundary(repoPath);
|
|
39
|
-
if (!boundary.root) return { ok: false, error: "Repository path is unreadable" };
|
|
40
|
-
if (!graph) {
|
|
41
|
-
graph = readJson(join(graphOutDirForRepo(repoPath), "graph.json"));
|
|
42
|
-
if (!graph) return { ok: false, error: "Build the graph first (no graph.json)" };
|
|
43
|
-
}
|
|
44
|
-
const pkg = readRepoJson(boundary, "package.json") || {};
|
|
45
|
-
const packageScopes = collectPackageScopes(repoPath, pkg);
|
|
46
|
-
const externalImports = graph.externalImports || [];
|
|
47
|
-
const dynamicTargets = new Set(externalImports.filter((entry) => entry.dynamic && entry.target).map((entry) => entry.target));
|
|
48
|
-
const rules = readRepoJson(boundary, ".weavatrix-deps.json") || {};
|
|
49
|
-
const repoFiles = listRepoFiles(repoPath);
|
|
50
|
-
const sources = collectSourceTexts(repoPath, graph);
|
|
51
|
-
const nonRuntimeRoots = collectNonRuntimeRoots(repoPath, rules);
|
|
52
|
-
|
|
53
|
-
const pathClassifier = createPathClassifier(repoPath);
|
|
54
|
-
const pathClassifications = new Map();
|
|
55
|
-
const classifyPath = (file) => {
|
|
56
|
-
const normalized = String(file || "").replace(/\\/g, "/");
|
|
57
|
-
if (!pathClassifications.has(normalized)) {
|
|
58
|
-
pathClassifications.set(normalized, pathClassifier.explain(normalized, { content: sources.get(normalized) }));
|
|
59
|
-
}
|
|
60
|
-
return pathClassifications.get(normalized);
|
|
61
|
-
};
|
|
62
|
-
const isNonProductPath = (file) => {
|
|
63
|
-
const info = classifyPath(file);
|
|
64
|
-
return info.excluded || hasPathClass(info, "test", "e2e", "generated", "vendored", "mock", "story", "docs", "benchmark", "temp");
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const conventionEvidence = [];
|
|
68
|
-
const entries = entryFiles(graph, packageScopes, dynamicTargets, {
|
|
69
|
-
declaredEntries: rules.entrypoints || rules.entries || [],
|
|
70
|
-
sources,
|
|
71
|
-
conventionEvidence,
|
|
72
|
-
});
|
|
73
|
-
for (const file of sources.keys()) {
|
|
74
|
-
if (nonRuntimeRoots.some((root) => file === root || file.startsWith(`${root}/`))) entries.add(file);
|
|
75
|
-
if (isNonProductPath(file)) entries.add(file);
|
|
76
|
-
}
|
|
77
|
-
const dead = computeDead(graph, sources, { entrySet: entries });
|
|
78
|
-
const unusedExports = computeUnusedExports(graph, sources, { dynamicTargets, entrySet: entries });
|
|
79
|
-
const reachable = computeReachability(graph, entries);
|
|
80
|
-
const configTexts = collectConfigTexts(repoPath);
|
|
81
|
-
const npmDependencyImports = externalImports.filter((entry) => entry.ecosystem
|
|
82
|
-
? entry.ecosystem === "npm"
|
|
83
|
-
: !/\.(?:java|go|py)$/i.test(entry.file || ""));
|
|
84
|
-
const dep = computeScopedDepFindings({
|
|
85
|
-
externalImports: npmDependencyImports,
|
|
86
|
-
packageScopes,
|
|
87
|
-
workspacePkgNames: workspacePkgNames(repoPath, pkg),
|
|
88
|
-
configTexts,
|
|
89
|
-
sourceTexts: sources,
|
|
90
|
-
nonRuntimeRoots,
|
|
91
|
-
sourceFiles: [...sources.keys()],
|
|
92
|
-
});
|
|
93
|
-
const goDep = collectGoDependencyEvidence(repoPath, { files: repoFiles, externalImports, nonRuntimeRoots });
|
|
94
|
-
const asList = (value) => Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
95
|
-
const pyRules = rules.python || {}, depRules = rules.dependencies || {};
|
|
96
|
-
const managedPython = [...new Set([
|
|
97
|
-
...asList(rules.managedPythonDependencies), ...asList(pyRules.managed), ...asList(pyRules.managedDependencies), ...asList(depRules.managedPython),
|
|
98
|
-
])];
|
|
99
|
-
const ignoredPython = [...new Set([
|
|
100
|
-
...asList(rules.ignorePythonDependencies), ...asList(pyRules.ignore), ...asList(pyRules.ignoreDependencies), ...asList(depRules.ignorePython),
|
|
101
|
-
])];
|
|
102
|
-
const pyManifest = collectPyManifest(repoPath);
|
|
103
|
-
const pyDep = computePyDepFindings({
|
|
104
|
-
externalImports,
|
|
105
|
-
pyManifest,
|
|
106
|
-
configTexts,
|
|
107
|
-
managedDependencies: managedPython,
|
|
108
|
-
ignoredDependencies: ignoredPython,
|
|
109
|
-
nonRuntimeRoots,
|
|
110
|
-
});
|
|
111
|
-
const jvmDependencies = collectJvmDependencyEvidence(repoPath, { files: repoFiles, externalImports });
|
|
112
|
-
const cargoDep = collectCargoDependencyEvidence(repoPath, { files: repoFiles, externalImports });
|
|
113
|
-
|
|
114
|
-
const externalImportFiles = new Set(externalImports.filter((entry) => entry.pkg && !entry.builtin).map((entry) => entry.file));
|
|
115
|
-
const structure = computeStructureFindings(graph, { rules, entrySet: entries, externalImportFiles });
|
|
116
|
-
const correctness = analyzeSourceCorrectness(sources, { isNonProductPath });
|
|
117
|
-
const findings = [
|
|
118
|
-
...dep.findings, ...goDep.findings, ...pyDep.findings,
|
|
119
|
-
...jvmDependencies.maven.findings, ...jvmDependencies.gradle.findings, ...cargoDep.findings,
|
|
120
|
-
...correctness.findings,
|
|
121
|
-
];
|
|
122
|
-
const deadFileSet = new Set(dead.deadFiles.map((finding) => finding.file));
|
|
123
|
-
for (const finding of structure.findings) {
|
|
124
|
-
if (!(finding.rule === "orphan-file" && (deadFileSet.has(finding.file) || entries.has(finding.file)))) findings.push(finding);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const actionableDeadFiles = dead.deadFiles.filter((finding) => !isNonProductPath(finding.file));
|
|
128
|
-
for (const finding of actionableDeadFiles) {
|
|
129
|
-
if (entries.has(finding.file) || dynamicTargets.has(finding.file) || TEST_FILE_RE.test(finding.file)) continue;
|
|
130
|
-
findings.push(makeFinding({
|
|
131
|
-
category: "unused",
|
|
132
|
-
rule: "unused-file",
|
|
133
|
-
severity: "low",
|
|
134
|
-
confidence: reachable.has(finding.file) ? "medium" : "high",
|
|
135
|
-
title: `Unused file: ${finding.file}`,
|
|
136
|
-
detail: `${finding.reason}${reachable.has(finding.file) ? "" : "; also unreachable from every entry point"}. Dynamic loading and framework conventions can't be fully ruled out — review before deleting.`,
|
|
137
|
-
file: finding.file,
|
|
138
|
-
graphNodeId: finding.file,
|
|
139
|
-
source: "internal",
|
|
140
|
-
fixHint: "review, then delete the file",
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
143
|
-
let unusedExportCount = 0;
|
|
144
|
-
for (const symbol of unusedExports) {
|
|
145
|
-
if (symbol.test || isNonProductPath(symbol.file) || entries.has(symbol.file) || dynamicTargets.has(symbol.file)) continue;
|
|
146
|
-
if (/(^|\/)[^/]*\.config\.[a-z0-9]+$|(^|\/)\.[^/]+rc(\.[a-z]+)?$/i.test(symbol.file)) continue;
|
|
147
|
-
findings.push(makeFinding({
|
|
148
|
-
category: "unused",
|
|
149
|
-
rule: "unused-export",
|
|
150
|
-
severity: "info",
|
|
151
|
-
confidence: "low",
|
|
152
|
-
title: `Unused export: ${symbol.label.replace(/\(\)$/, "")} — ${symbol.file}`,
|
|
153
|
-
detail: `${symbol.reason}. This is unused module-surface evidence, not proof that the implementation is dead: downstream packages and runtime registration can be invisible to the repository graph. Review external consumers first; remove only the export keyword when the symbol is still used internally, and delete the implementation only after separate dead-code verification.`,
|
|
154
|
-
file: symbol.file,
|
|
155
|
-
symbol: symbol.label,
|
|
156
|
-
graphNodeId: symbol.id,
|
|
157
|
-
source: "internal",
|
|
158
|
-
classification: "unused-export-surface",
|
|
159
|
-
deadCodeCandidate: false,
|
|
160
|
-
fixHint: "check downstream/runtime consumers, then remove only the export surface or run a separate exact dead-code review before deleting the implementation",
|
|
161
|
-
}));
|
|
162
|
-
unusedExportCount++;
|
|
163
|
-
}
|
|
164
|
-
for (const symbol of dead.testOnlySymbols || []) {
|
|
165
|
-
if (isNonProductPath(symbol.file) || entries.has(symbol.file) || dynamicTargets.has(symbol.file)) continue;
|
|
166
|
-
findings.push(makeFinding({
|
|
167
|
-
category: "unused",
|
|
168
|
-
rule: "test-only-symbol",
|
|
169
|
-
severity: "low",
|
|
170
|
-
confidence: symbol.publicApi ? "low" : "medium",
|
|
171
|
-
title: `Production symbol used only by tests: ${symbol.label}`,
|
|
172
|
-
detail: `${symbol.reason}. Static analysis cannot rule out external or reflective consumers, so review before removal.`,
|
|
173
|
-
file: symbol.file,
|
|
174
|
-
graphNodeId: symbol.id,
|
|
175
|
-
source: "internal",
|
|
176
|
-
fixHint: "verify production/config consumers, then keep as intentional test support or remove it together with obsolete tests",
|
|
177
|
-
}));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const supplyChain = await runSupplyChainChecks(repoPath, {
|
|
181
|
-
externalImports,
|
|
182
|
-
packageScopes,
|
|
183
|
-
isNonProductPath,
|
|
184
|
-
advisoryStorePath,
|
|
185
|
-
skipMalwareScan,
|
|
186
|
-
malwareExclusions,
|
|
187
|
-
rgPath,
|
|
188
|
-
});
|
|
189
|
-
findings.push(...supplyChain.findings);
|
|
190
|
-
const sorted = sortFindings(findings);
|
|
191
|
-
const dependencyHealth = buildDependencyHealth({
|
|
192
|
-
repoPath,
|
|
193
|
-
graph,
|
|
194
|
-
repoFiles,
|
|
195
|
-
pyManifest,
|
|
196
|
-
dep,
|
|
197
|
-
goDep,
|
|
198
|
-
pyDep,
|
|
199
|
-
jvmDependencies,
|
|
200
|
-
cargoDep,
|
|
201
|
-
externalImports,
|
|
202
|
-
findings: sorted,
|
|
203
|
-
packageScopes,
|
|
204
|
-
sourceFiles: [...sources.keys()],
|
|
205
|
-
correctnessCoverage: correctness.coverage,
|
|
206
|
-
checks: supplyChain.checks,
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
ok: true,
|
|
211
|
-
engine: "internal",
|
|
212
|
-
repo: basename(repoPath),
|
|
213
|
-
path: repoPath,
|
|
214
|
-
savedAt: new Date().toISOString(),
|
|
215
|
-
scanned: {
|
|
216
|
-
files: dead.stats.files,
|
|
217
|
-
symbols: dead.stats.symbols,
|
|
218
|
-
manifestDeps: dependencyHealth.manifestDeps,
|
|
219
|
-
externalImports: externalImports.length,
|
|
220
|
-
nodeModulesPresent: boundary.resolve("node_modules").ok,
|
|
221
|
-
installedPackages: supplyChain.installedCount,
|
|
222
|
-
advisoryDbDate: supplyChain.advisoryDbDate,
|
|
223
|
-
advisoryStatus: supplyChain.checks.osv.status,
|
|
224
|
-
malwareScanMode: supplyChain.malwareScan?.scanMode || "skipped",
|
|
225
|
-
malwareStatus: supplyChain.checks.malware.status,
|
|
226
|
-
packageScopes: packageScopes.length,
|
|
227
|
-
managedPythonDependencies: managedPython.length,
|
|
228
|
-
nonRuntimeRoots,
|
|
229
|
-
pathClassifications: Object.fromEntries(PATH_CLASS_NAMES.map((name) => [
|
|
230
|
-
name,
|
|
231
|
-
[...pathClassifications.values()].filter((info) => info.classes.includes(name)).length,
|
|
232
|
-
])),
|
|
233
|
-
pathClassificationExcluded: [...pathClassifications.values()].filter((info) => info.excluded).length,
|
|
234
|
-
conventionEntrypoints: conventionEvidence.length,
|
|
235
|
-
},
|
|
236
|
-
summary: summarizeFindings(sorted),
|
|
237
|
-
findings: sorted,
|
|
238
|
-
dependencyReport: dependencyHealth.dependencyReport,
|
|
239
|
-
deadReport: {
|
|
240
|
-
deadSymbols: dead.deadSymbols.filter((symbol) => !isNonProductPath(symbol.file)).length,
|
|
241
|
-
deadFiles: actionableDeadFiles.length,
|
|
242
|
-
unusedExports: unusedExportCount,
|
|
243
|
-
testOnlySymbols: (dead.testOnlySymbols || []).filter((symbol) => !isNonProductPath(symbol.file)).length,
|
|
244
|
-
},
|
|
245
|
-
conventionReachability: {
|
|
246
|
-
count: conventionEvidence.length,
|
|
247
|
-
entries: conventionEvidence.slice(0, 100),
|
|
248
|
-
truncated: conventionEvidence.length > 100,
|
|
249
|
-
},
|
|
250
|
-
structureReport: structure.stats,
|
|
251
|
-
sourceCorrectnessReport: correctness.coverage,
|
|
252
|
-
healthCapabilities: dependencyHealth.healthCapabilities,
|
|
253
|
-
checks: supplyChain.checks,
|
|
254
|
-
malwareScan: supplyChain.malwareScan,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// Replace Java comments and string/char literal contents with spaces while preserving length and
|
|
2
|
-
// newlines. Regex-based signal extractors can then trust match positions without accepting examples
|
|
3
|
-
// from comments, docs, or string constants as executable annotations.
|
|
4
|
-
export function maskJavaNonCode(source) {
|
|
5
|
-
const text = String(source || "");
|
|
6
|
-
const chars = [...text];
|
|
7
|
-
let state = "code";
|
|
8
|
-
let escaped = false;
|
|
9
|
-
for (let i = 0; i < text.length; i++) {
|
|
10
|
-
const ch = text[i];
|
|
11
|
-
const next = text[i + 1];
|
|
12
|
-
if (state === "code") {
|
|
13
|
-
if (ch === "/" && next === "/") { chars[i] = chars[i + 1] = " "; i++; state = "line"; }
|
|
14
|
-
else if (ch === "/" && next === "*") { chars[i] = chars[i + 1] = " "; i++; state = "block"; }
|
|
15
|
-
else if (ch === '"') { chars[i] = " "; state = "string"; escaped = false; }
|
|
16
|
-
else if (ch === "'") { chars[i] = " "; state = "char"; escaped = false; }
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
if (state === "line") {
|
|
20
|
-
if (ch === "\n" || ch === "\r") state = "code";
|
|
21
|
-
else chars[i] = " ";
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (state === "block") {
|
|
25
|
-
if (ch === "*" && next === "/") { chars[i] = chars[i + 1] = " "; i++; state = "code"; }
|
|
26
|
-
else if (ch !== "\n" && ch !== "\r") chars[i] = " ";
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (ch === "\n" || ch === "\r") { escaped = false; continue; }
|
|
30
|
-
chars[i] = " ";
|
|
31
|
-
if (escaped) escaped = false;
|
|
32
|
-
else if (ch === "\\") escaped = true;
|
|
33
|
-
else if ((state === "string" && ch === '"') || (state === "char" && ch === "'")) state = "code";
|
|
34
|
-
}
|
|
35
|
-
return chars.join("");
|
|
36
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
const concreteVersion = (value) => !!value && !/[\[\](),${}*+]/.test(String(value));
|
|
6
|
-
const safeEntries = (path) => { try { return readdirSync(path, { withFileTypes: true }); } catch { return []; } };
|
|
7
|
-
|
|
8
|
-
function findEndOfCentralDirectory(buffer) {
|
|
9
|
-
const minimum = Math.max(0, buffer.length - 65_557);
|
|
10
|
-
for (let offset = buffer.length - 22; offset >= minimum; offset--) {
|
|
11
|
-
if (buffer.readUInt32LE(offset) === 0x06054b50) return offset;
|
|
12
|
-
}
|
|
13
|
-
return -1;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// JARs are ZIP files. Reading only the central directory avoids extraction and code execution.
|
|
17
|
-
export function readJarClassNames(path, { maxClasses = 250_000 } = {}) {
|
|
18
|
-
const buffer = readFileSync(path);
|
|
19
|
-
const end = findEndOfCentralDirectory(buffer);
|
|
20
|
-
if (end < 0) throw new Error("ZIP central directory is missing");
|
|
21
|
-
const entries = buffer.readUInt16LE(end + 10);
|
|
22
|
-
let offset = buffer.readUInt32LE(end + 16);
|
|
23
|
-
const classes = new Set(), packages = new Set();
|
|
24
|
-
let truncated = false;
|
|
25
|
-
for (let index = 0; index < entries && offset + 46 <= buffer.length; index++) {
|
|
26
|
-
if (buffer.readUInt32LE(offset) !== 0x02014b50) throw new Error("invalid ZIP central directory entry");
|
|
27
|
-
const nameLength = buffer.readUInt16LE(offset + 28);
|
|
28
|
-
const extraLength = buffer.readUInt16LE(offset + 30);
|
|
29
|
-
const commentLength = buffer.readUInt16LE(offset + 32);
|
|
30
|
-
let name = buffer.subarray(offset + 46, offset + 46 + nameLength).toString("utf8");
|
|
31
|
-
offset += 46 + nameLength + extraLength + commentLength;
|
|
32
|
-
name = name.replace(/^META-INF\/versions\/\d+\//, "");
|
|
33
|
-
if (!name.endsWith(".class") || /(?:^|\/)(?:module-info|package-info)\.class$/.test(name)) continue;
|
|
34
|
-
const identity = name.slice(0, -6).replace(/\$/g, ".").replace(/\//g, ".");
|
|
35
|
-
classes.add(identity);
|
|
36
|
-
packages.add(identity.includes(".") ? identity.slice(0, identity.lastIndexOf(".")) : "");
|
|
37
|
-
if (classes.size >= maxClasses) { truncated = true; break; }
|
|
38
|
-
}
|
|
39
|
-
return { classes, packages, truncated };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function artifactCandidates(dependency, home) {
|
|
43
|
-
if (!dependency.group || !dependency.artifact || !concreteVersion(dependency.version)) return [];
|
|
44
|
-
const file = `${dependency.artifact}-${dependency.version}.jar`;
|
|
45
|
-
const maven = join(home, ".m2", "repository", ...dependency.group.split("."), dependency.artifact, dependency.version, file);
|
|
46
|
-
const gradleRoot = join(home, ".gradle", "caches", "modules-2", "files-2.1", dependency.group, dependency.artifact, dependency.version);
|
|
47
|
-
const gradle = safeEntries(gradleRoot).filter((entry) => entry.isDirectory()).flatMap((entry) =>
|
|
48
|
-
safeEntries(join(gradleRoot, entry.name)).filter((child) => child.isFile() && child.name.endsWith(".jar"))
|
|
49
|
-
.map((child) => join(gradleRoot, entry.name, child.name)));
|
|
50
|
-
return [maven, ...gradle].filter((candidate, index, all) => existsSync(candidate) && all.indexOf(candidate) === index);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const addOwner = (map, key, owner) => {
|
|
54
|
-
const owners = map.get(key) || new Set();
|
|
55
|
-
owners.add(owner); map.set(key, owners);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Exact import -> artifact evidence from already-installed Maven/Gradle JARs. This is offline and
|
|
59
|
-
// read-only: no build tool, plugin, class loader or repository code is executed.
|
|
60
|
-
export function collectJvmArtifactIndex(dependencies, { home = homedir(), maxArtifacts = 256, maxClasses = 500_000 } = {}) {
|
|
61
|
-
const unique = [...new Map(dependencies.map((dependency) => [dependency.name, dependency])).values()];
|
|
62
|
-
const classes = new Map(), packages = new Map(), errors = [];
|
|
63
|
-
let artifactsIndexed = 0, artifactsMissing = 0, classCount = 0, truncated = false;
|
|
64
|
-
for (const dependency of unique) {
|
|
65
|
-
if (artifactsIndexed >= maxArtifacts || classCount >= maxClasses) { truncated = true; break; }
|
|
66
|
-
const candidates = artifactCandidates(dependency, home);
|
|
67
|
-
if (!candidates.length) { artifactsMissing++; continue; }
|
|
68
|
-
try {
|
|
69
|
-
const remaining = Math.max(1, maxClasses - classCount);
|
|
70
|
-
const content = readJarClassNames(candidates[0], { maxClasses: remaining });
|
|
71
|
-
for (const identity of content.classes) addOwner(classes, identity, dependency.name);
|
|
72
|
-
for (const identity of content.packages) addOwner(packages, identity, dependency.name);
|
|
73
|
-
classCount += content.classes.size; artifactsIndexed++; truncated ||= content.truncated;
|
|
74
|
-
} catch (error) {
|
|
75
|
-
errors.push(`${dependency.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const resolve = (spec) => {
|
|
79
|
-
const imported = String(spec || "").replace(/\.\*$/, "");
|
|
80
|
-
const owners = String(spec || "").endsWith(".*") ? packages.get(imported) : classes.get(imported);
|
|
81
|
-
return [...(owners || [])].sort();
|
|
82
|
-
};
|
|
83
|
-
return {
|
|
84
|
-
resolve, artifactsRequired: unique.length, artifactsIndexed, artifactsMissing,
|
|
85
|
-
classCount, errors, truncated,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { createRepoBoundary } from "../repo-path.js";
|
|
2
|
-
import { dependencyVerification, makeFinding } from "./findings.js";
|
|
3
|
-
import { parseGradleDependencies, parseGradleVersionCatalog, parseMavenPom } from "./jvm-manifests.js";
|
|
4
|
-
import { collectJvmArtifactIndex } from "./jvm-artifact-index.js";
|
|
5
|
-
import { listRepoFiles, readRepoText } from "./internal-audit.collect.js";
|
|
6
|
-
|
|
7
|
-
const normalize = (value) => String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
8
|
-
const javaBuiltin = (name) => /^(?:java|jdk|sun)\.|^(?:org\.w3c\.dom|org\.xml\.sax)(?:\.|$)/.test(name);
|
|
9
|
-
|
|
10
|
-
function mappingScore(spec, dependency) {
|
|
11
|
-
const imported = String(spec || "").replace(/\.\*$/, "");
|
|
12
|
-
if (!imported || !dependency.group) return 0;
|
|
13
|
-
if (imported === dependency.group || imported.startsWith(`${dependency.group}.`)) return 1_000 + dependency.group.length;
|
|
14
|
-
const compactImport = normalize(imported), compactArtifact = normalize(dependency.artifact);
|
|
15
|
-
if (compactArtifact.length >= 4 && compactImport.includes(compactArtifact)) return 500 + compactArtifact.length;
|
|
16
|
-
const artifactTokens = String(dependency.artifact).toLowerCase().split(/[-_.]+/).filter((part) => part.length >= 4 && !["core", "java", "client", "common", "api"].includes(part));
|
|
17
|
-
const hits = artifactTokens.filter((part) => imported.toLowerCase().split(".").some((segment) => normalize(segment) === normalize(part))).length;
|
|
18
|
-
return hits ? 100 + hits : 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function bestDependency(spec, dependencies) {
|
|
22
|
-
const ranked = dependencies.map((dependency) => ({ dependency, score: mappingScore(spec, dependency) }))
|
|
23
|
-
.filter((item) => item.score > 0).sort((left, right) => right.score - left.score || left.dependency.name.localeCompare(right.dependency.name));
|
|
24
|
-
if (!ranked.length || (ranked[1] && ranked[1].score === ranked[0].score)) return null;
|
|
25
|
-
return ranked[0].dependency;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function analyze(ecosystem, manifests, dependencies, imports, unresolvedDeclarations, mappingDependencies = dependencies, includeMissing = true, artifactEvidence) {
|
|
29
|
-
const findings = [], used = new Map(), missing = new Map();
|
|
30
|
-
const owned = new Set(dependencies.map((dependency) => dependency.name));
|
|
31
|
-
let exactMappedImports = 0, heuristicMappedImports = 0, ambiguousImports = 0;
|
|
32
|
-
for (const item of imports) {
|
|
33
|
-
const exactOwners = artifactEvidence.resolve(item.spec || item.pkg);
|
|
34
|
-
if (exactOwners.length > 1) ambiguousImports++;
|
|
35
|
-
const dependency = exactOwners.length === 1
|
|
36
|
-
? mappingDependencies.find((candidate) => candidate.name === exactOwners[0])
|
|
37
|
-
: bestDependency(item.spec || item.pkg, mappingDependencies);
|
|
38
|
-
if (dependency) {
|
|
39
|
-
if (exactOwners.length === 1) exactMappedImports++; else heuristicMappedImports++;
|
|
40
|
-
if (owned.has(dependency.name)) {
|
|
41
|
-
const list = used.get(dependency.name) || [];
|
|
42
|
-
list.push(item); used.set(dependency.name, list);
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
const key = item.spec || item.pkg;
|
|
46
|
-
const list = missing.get(key) || [];
|
|
47
|
-
list.push(item); missing.set(key, list);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const runtimeOnly = /runtime|provided|classpath|annotationProcessor|kapt/i;
|
|
51
|
-
for (const dependency of dependencies) {
|
|
52
|
-
const evidence = used.get(dependency.name) || [];
|
|
53
|
-
if (evidence.length || dependency.optional || runtimeOnly.test(dependency.scope || "")) continue;
|
|
54
|
-
findings.push(makeFinding({
|
|
55
|
-
category: "unused", rule: "unused-dep", severity: "low", confidence: "low",
|
|
56
|
-
title: `Unused ${ecosystem} dependency: ${dependency.name}`,
|
|
57
|
-
reason: "No indexed Java import mapped to this declared artifact; reflection, service loading, generated code and runtime-only use remain possible.",
|
|
58
|
-
detail: `"${dependency.name}" is declared in ${dependency.file}, but no indexed Java import maps to it. Review framework, reflection, ServiceLoader and generated-source use before removal.`,
|
|
59
|
-
package: dependency.name, version: dependency.version, manifest: dependency.file, source: "internal",
|
|
60
|
-
verification: dependencyVerification(dependency.file, [], "REVIEW_REQUIRED", "group-prefix/artifact-token"),
|
|
61
|
-
fixHint: `remove ${dependency.name} only after the ${ecosystem} build and tests confirm it is unused`,
|
|
62
|
-
}));
|
|
63
|
-
}
|
|
64
|
-
for (const [spec, evidence] of includeMissing ? missing : []) {
|
|
65
|
-
if (javaBuiltin(spec)) continue;
|
|
66
|
-
findings.push(makeFinding({
|
|
67
|
-
category: "unused", rule: "missing-dep", severity: "medium", confidence: "medium",
|
|
68
|
-
title: `Unmapped Java import: ${spec}`,
|
|
69
|
-
reason: `The indexed Java import did not map to any declared ${ecosystem} artifact.`,
|
|
70
|
-
detail: `"${spec}" is imported by ${evidence.length} file(s), but no ${ecosystem} declaration has a matching group prefix or artifact token. Add the owning artifact or configure/build the source that supplies it.`,
|
|
71
|
-
package: spec, file: evidence[0].file, line: evidence[0].line || 0,
|
|
72
|
-
evidence: evidence.slice(0, 5).map((item) => ({ file: item.file, line: item.line || 0, snippet: item.spec || "" })),
|
|
73
|
-
source: "internal",
|
|
74
|
-
verification: { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "ACTION_REQUIRED", manifestDeclaration: { status: "NOT_FOUND", files: manifests }, indexedSourceImports: { status: "FOUND", count: evidence.length, files: evidence.map((item) => item.file).slice(0, 10) }, mapping: "group-prefix/artifact-token" },
|
|
75
|
-
fixHint: `identify the artifact that owns ${spec} and declare it in the nearest ${ecosystem} manifest`,
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
const present = manifests.length > 0;
|
|
79
|
-
const exactComplete = present && unresolvedDeclarations === 0 && !artifactEvidence.truncated
|
|
80
|
-
&& artifactEvidence.errors.length === 0 && artifactEvidence.artifactsMissing === 0
|
|
81
|
-
&& artifactEvidence.artifactsIndexed === artifactEvidence.artifactsRequired
|
|
82
|
-
&& exactMappedImports === imports.length && ambiguousImports === 0;
|
|
83
|
-
return {
|
|
84
|
-
present,
|
|
85
|
-
status: present ? "CHECKED" : "NOT_PRESENT",
|
|
86
|
-
completeness: present ? (exactComplete ? "COMPLETE" : "PARTIAL") : "NOT_APPLICABLE",
|
|
87
|
-
manifests,
|
|
88
|
-
declared: dependencies.length,
|
|
89
|
-
mappedImports: [...used.values()].reduce((sum, list) => sum + list.length, 0),
|
|
90
|
-
unmappedImports: [...missing.values()].reduce((sum, list) => sum + list.length, 0),
|
|
91
|
-
unresolvedDeclarations,
|
|
92
|
-
exactArtifactEvidence: {
|
|
93
|
-
artifactsRequired: artifactEvidence.artifactsRequired,
|
|
94
|
-
artifactsIndexed: artifactEvidence.artifactsIndexed,
|
|
95
|
-
artifactsMissing: artifactEvidence.artifactsMissing,
|
|
96
|
-
classesIndexed: artifactEvidence.classCount,
|
|
97
|
-
exactMappedImports,
|
|
98
|
-
heuristicMappedImports,
|
|
99
|
-
ambiguousImports,
|
|
100
|
-
truncated: artifactEvidence.truncated,
|
|
101
|
-
errors: artifactEvidence.errors.slice(0, 10),
|
|
102
|
-
},
|
|
103
|
-
sample: dependencies.slice(0, 20).map(({ file, name, version }) => ({ file, identity: name, version })),
|
|
104
|
-
reason: !present
|
|
105
|
-
? `No ${ecosystem === "maven" ? "pom.xml" : "Gradle build file"} was discovered.`
|
|
106
|
-
: exactComplete
|
|
107
|
-
? `${dependencies.length} declarations were compared with every indexed non-JDK Java import using exact class ownership from ${artifactEvidence.artifactsIndexed} installed JAR(s).`
|
|
108
|
-
: `${dependencies.length} declarations and every indexed non-JDK Java import were checked, but exact artifact evidence is partial: ${unresolvedDeclarations} unresolved declaration(s), ${artifactEvidence.artifactsMissing} installed JAR(s) missing, ${heuristicMappedImports} heuristic mapping(s), ${ambiguousImports} ambiguous mapping(s). Heuristic missing/unused findings remain review evidence, never compiler proof.`,
|
|
109
|
-
findings,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function collectJvmDependencyEvidence(repoRoot, { files = listRepoFiles(repoRoot), externalImports = [] } = {}) {
|
|
114
|
-
const boundary = createRepoBoundary(repoRoot);
|
|
115
|
-
const mavenFiles = files.filter((file) => /(^|\/)pom\.xml$/i.test(file));
|
|
116
|
-
const gradleFiles = files.filter((file) => /(^|\/)(?:build|settings|[^/]+)\.gradle(?:\.kts)?$/i.test(file));
|
|
117
|
-
const catalogFiles = files.filter((file) => /(^|\/)libs\.versions\.toml$/i.test(file));
|
|
118
|
-
const catalog = new Map();
|
|
119
|
-
for (const file of catalogFiles) for (const [alias, entry] of parseGradleVersionCatalog(readRepoText(boundary, file))) catalog.set(alias, entry);
|
|
120
|
-
let mavenUnresolved = 0, gradleUnresolved = 0;
|
|
121
|
-
const mavenDependencies = mavenFiles.flatMap((file) => {
|
|
122
|
-
const parsed = parseMavenPom(readRepoText(boundary, file));
|
|
123
|
-
mavenUnresolved += parsed.unresolvedDeclarations;
|
|
124
|
-
return parsed.dependencies.map((dependency) => ({ ...dependency, file }));
|
|
125
|
-
});
|
|
126
|
-
const gradleDependencies = gradleFiles.flatMap((file) => {
|
|
127
|
-
const parsed = parseGradleDependencies(readRepoText(boundary, file), catalog);
|
|
128
|
-
gradleUnresolved += parsed.unresolvedDeclarations || 0;
|
|
129
|
-
return parsed.map((dependency) => ({ ...dependency, file }));
|
|
130
|
-
});
|
|
131
|
-
const javaImports = externalImports.filter((entry) => entry.ecosystem === "Maven" && entry.pkg && !entry.builtin && !entry.unresolved);
|
|
132
|
-
const allDependencies = [...mavenDependencies, ...gradleDependencies];
|
|
133
|
-
const artifactEvidence = collectJvmArtifactIndex(allDependencies);
|
|
134
|
-
return {
|
|
135
|
-
maven: analyze("maven", mavenFiles, mavenDependencies, javaImports, mavenUnresolved, allDependencies, true, artifactEvidence),
|
|
136
|
-
gradle: analyze("gradle", [...gradleFiles, ...catalogFiles], gradleDependencies, javaImports, gradleUnresolved, allDependencies, mavenFiles.length === 0, artifactEvidence),
|
|
137
|
-
};
|
|
138
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const stripComments = (value) => String(value || "").replace(/<!--[\s\S]*?-->/g, " ");
|
|
2
|
-
const clean = (value) => String(value || "").trim().replace(/^['"]|['"]$/g, "");
|
|
3
|
-
|
|
4
|
-
function xmlValue(body, name) {
|
|
5
|
-
return new RegExp(`<${name}>\\s*([^<]+?)\\s*</${name}>`, "i").exec(body)?.[1]?.trim() || "";
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function resolveMavenValue(value, properties) {
|
|
9
|
-
let output = String(value || "");
|
|
10
|
-
for (let pass = 0; pass < 8; pass++) {
|
|
11
|
-
let changed = false;
|
|
12
|
-
output = output.replace(/\$\{([^}]+)}/g, (whole, key) => {
|
|
13
|
-
if (!properties.has(key)) return whole;
|
|
14
|
-
changed = true;
|
|
15
|
-
return properties.get(key);
|
|
16
|
-
});
|
|
17
|
-
if (!changed) break;
|
|
18
|
-
}
|
|
19
|
-
return /\$\{/.test(output) ? "" : output.trim();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function parseMavenPom(text) {
|
|
23
|
-
const source = stripComments(text);
|
|
24
|
-
const properties = new Map();
|
|
25
|
-
const propertyBlock = /<properties\b[^>]*>([\s\S]*?)<\/properties>/i.exec(source)?.[1] || "";
|
|
26
|
-
for (const match of propertyBlock.matchAll(/<([A-Za-z0-9_.-]+)>\s*([^<]+?)\s*<\/\1>/g)) properties.set(match[1], match[2].trim());
|
|
27
|
-
const parent = /<parent\b[^>]*>([\s\S]*?)<\/parent>/i.exec(source)?.[1] || "";
|
|
28
|
-
const beforeDependencies = source.split(/<dependencies\b/i)[0];
|
|
29
|
-
const projectVersion = xmlValue(beforeDependencies, "version") || xmlValue(parent, "version");
|
|
30
|
-
const projectGroup = xmlValue(beforeDependencies, "groupId") || xmlValue(parent, "groupId");
|
|
31
|
-
properties.set("project.version", projectVersion); properties.set("pom.version", projectVersion);
|
|
32
|
-
properties.set("project.groupId", projectGroup); properties.set("pom.groupId", projectGroup);
|
|
33
|
-
const managed = new Map();
|
|
34
|
-
for (const block of source.matchAll(/<dependencyManagement\b[^>]*>([\s\S]*?)<\/dependencyManagement>/gi)) {
|
|
35
|
-
for (const dep of block[1].matchAll(/<dependency\b[^>]*>([\s\S]*?)<\/dependency>/gi)) {
|
|
36
|
-
const group = resolveMavenValue(xmlValue(dep[1], "groupId"), properties);
|
|
37
|
-
const artifact = resolveMavenValue(xmlValue(dep[1], "artifactId"), properties);
|
|
38
|
-
const version = resolveMavenValue(xmlValue(dep[1], "version"), properties);
|
|
39
|
-
if (group && artifact && version) managed.set(`${group}:${artifact}`, version);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const directSource = source.replace(/<dependencyManagement\b[^>]*>[\s\S]*?<\/dependencyManagement>/gi, " ");
|
|
43
|
-
const dependencies = [];
|
|
44
|
-
const directBlocks = [...directSource.matchAll(/<dependency\b[^>]*>([\s\S]*?)<\/dependency>/gi)];
|
|
45
|
-
for (const match of directBlocks) {
|
|
46
|
-
const group = resolveMavenValue(xmlValue(match[1], "groupId"), properties);
|
|
47
|
-
const artifact = resolveMavenValue(xmlValue(match[1], "artifactId"), properties);
|
|
48
|
-
if (!group || !artifact) continue;
|
|
49
|
-
dependencies.push({
|
|
50
|
-
group, artifact, name: `${group}:${artifact}`,
|
|
51
|
-
version: resolveMavenValue(xmlValue(match[1], "version"), properties) || managed.get(`${group}:${artifact}`) || "",
|
|
52
|
-
scope: xmlValue(match[1], "scope") || "compile",
|
|
53
|
-
optional: xmlValue(match[1], "optional") === "true",
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return { projectGroup, projectVersion, dependencies, unresolvedDeclarations: directBlocks.length - dependencies.length };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function parseGradleVersionCatalog(text) {
|
|
60
|
-
const versions = new Map(), libraries = new Map();
|
|
61
|
-
let section = "";
|
|
62
|
-
for (const raw of String(text || "").split(/\r?\n/)) {
|
|
63
|
-
const line = raw.replace(/(^|\s)#.*$/, "").trim();
|
|
64
|
-
const header = /^\[([^\]]+)]$/.exec(line);
|
|
65
|
-
if (header) { section = header[1]; continue; }
|
|
66
|
-
const kv = /^([A-Za-z0-9_.-]+)\s*=\s*(.+)$/.exec(line);
|
|
67
|
-
if (!kv) continue;
|
|
68
|
-
if (section === "versions") { versions.set(kv[1], clean(kv[2])); continue; }
|
|
69
|
-
if (section !== "libraries") continue;
|
|
70
|
-
const alias = kv[1], value = kv[2];
|
|
71
|
-
const module = /\bmodule\s*=\s*["']([^"']+)["']/.exec(value)?.[1]
|
|
72
|
-
|| /\bgroup\s*=\s*["']([^"']+)["'][\s\S]*?\bname\s*=\s*["']([^"']+)["']/.exec(value)?.slice(1, 3).join(":")
|
|
73
|
-
|| (/^["'][^"']+:[^"']+["']$/.test(value) ? clean(value) : "");
|
|
74
|
-
if (!module) continue;
|
|
75
|
-
const explicit = /\bversion\s*=\s*["']([^"']+)["']/.exec(value)?.[1] || "";
|
|
76
|
-
const reference = /\bversion\.ref\s*=\s*["']([^"']+)["']/.exec(value)?.[1] || "";
|
|
77
|
-
libraries.set(alias.replace(/[_.]/g, "-"), { module, version: explicit || versions.get(reference) || "" });
|
|
78
|
-
}
|
|
79
|
-
return libraries;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function parseGradleDependencies(text, catalog = new Map()) {
|
|
83
|
-
const source = String(text || "").replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|\s)\/\/.*$/gm, "$1");
|
|
84
|
-
const dependencies = [];
|
|
85
|
-
const configurations = "api|implementation|compile|compileOnly|runtimeOnly|annotationProcessor|kapt|classpath|testImplementation|testCompileOnly|testRuntimeOnly|androidTestImplementation";
|
|
86
|
-
const matcher = new RegExp(`^\\s*(${configurations})\\s*(?:\\(\\s*)?([^\\r\\n]+)`, "gmi");
|
|
87
|
-
const declarations = [...source.matchAll(matcher)];
|
|
88
|
-
for (const match of declarations) {
|
|
89
|
-
const expression = match[2].trim().replace(/[),;]+\s*$/, "");
|
|
90
|
-
const coordinate = /["']([^"']+:[^"']+)["']/.exec(expression)?.[1] || "";
|
|
91
|
-
if (coordinate) {
|
|
92
|
-
const [group, artifact, version = ""] = coordinate.split(":");
|
|
93
|
-
if (group && artifact) dependencies.push({ group, artifact, name: `${group}:${artifact}`, version, scope: match[1] });
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const alias = /\blibs((?:\.[A-Za-z_]\w*)+)/.exec(expression)?.[1]?.slice(1).replace(/\./g, "-") || "";
|
|
97
|
-
const entry = catalog.get(alias);
|
|
98
|
-
if (entry) {
|
|
99
|
-
const [group, artifact] = entry.module.split(":");
|
|
100
|
-
dependencies.push({ group, artifact, name: entry.module, version: entry.version, scope: match[1], catalogAlias: alias });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
dependencies.unresolvedDeclarations = declarations.length - dependencies.length;
|
|
104
|
-
return dependencies;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function parseGradleLockPackages(text) {
|
|
108
|
-
const packages = [];
|
|
109
|
-
for (const raw of String(text || "").split(/\r?\n/)) {
|
|
110
|
-
const match = /^([^#=\s:]+(?:\.[^#=\s:]+)*):([^#=\s:]+):([^#=\s=]+)=/.exec(raw.trim());
|
|
111
|
-
if (match) packages.push({ ecosystem: "Maven", name: `${match[1]}:${match[2]}`, version: match[3], dev: false, integrity: "", source: "gradle-lock" });
|
|
112
|
-
}
|
|
113
|
-
return packages;
|
|
114
|
-
}
|