weavatrix 0.3.13 → 1.0.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 +231 -199
- 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 +8 -65
- 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.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.js +0 -235
- 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 -62
- package/src/analysis/endpoints/extract.js +0 -107
- 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 -295
- 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 -154
- 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 -173
- 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 -174
- package/src/mcp/health/dead-code.mjs +0 -110
- package/src/mcp/health/duplicates.mjs +0 -85
- package/src/mcp/health/endpoints.mjs +0 -71
- package/src/mcp/health/structure.mjs +0 -219
- 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 -220
- 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,168 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
architectureHash,
|
|
3
|
-
architecturePathPrefix,
|
|
4
|
-
finiteArchitectureBudget,
|
|
5
|
-
normalizeArchitectureContract,
|
|
6
|
-
} from './contract-schema.js'
|
|
7
|
-
import {
|
|
8
|
-
architectureViolation as violation,
|
|
9
|
-
collectComponentEdges,
|
|
10
|
-
collectComponentFitness,
|
|
11
|
-
componentForFile,
|
|
12
|
-
fileOfNode,
|
|
13
|
-
isSymbolNode,
|
|
14
|
-
matchComponentSelector as matches,
|
|
15
|
-
runtimeFileGraph,
|
|
16
|
-
stronglyConnected,
|
|
17
|
-
} from './contract-graph.js'
|
|
18
|
-
|
|
19
|
-
function dependencyViolations(componentEdges, contract) {
|
|
20
|
-
const violations = []
|
|
21
|
-
for (const edge of componentEdges.values()) {
|
|
22
|
-
const applicable = contract.dependencyRules.filter((rule) =>
|
|
23
|
-
rule.kinds.some((kind) => kind === 'any' || kind === edge.kind) && matches(rule.from, edge.from))
|
|
24
|
-
for (const rule of applicable) if (rule.action === 'forbid' && matches(rule.to, edge.to)) {
|
|
25
|
-
violations.push(violation(rule.id, 'dependency', `${edge.from} -> ${edge.to} (${edge.kind})`, edge.count, 0))
|
|
26
|
-
}
|
|
27
|
-
const allowRules = applicable.filter((rule) => rule.action === 'allow')
|
|
28
|
-
if (allowRules.length && !allowRules.some((rule) => matches(rule.to, edge.to))) {
|
|
29
|
-
const ruleId = allowRules.map((rule) => rule.id).sort().join('+')
|
|
30
|
-
violations.push(violation(ruleId, 'dependency', `${edge.from} -> ${edge.to} (${edge.kind}; outside allow-list)`, edge.count, 0))
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return violations
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function componentBudgetViolations(componentEdges, stats, contract) {
|
|
37
|
-
const violations = []
|
|
38
|
-
const targetsByComponent = new Map()
|
|
39
|
-
for (const edge of componentEdges.values()) if (edge.kind === 'runtime') {
|
|
40
|
-
const targets = targetsByComponent.get(edge.from) || new Set()
|
|
41
|
-
targets.add(edge.to)
|
|
42
|
-
targetsByComponent.set(edge.from, targets)
|
|
43
|
-
}
|
|
44
|
-
if (contract.budgets.maxRuntimeDependenciesPerComponent != null) for (const [component, targets] of targetsByComponent) {
|
|
45
|
-
if (targets.size > contract.budgets.maxRuntimeDependenciesPerComponent) violations.push(violation(
|
|
46
|
-
'budget.maxRuntimeDependenciesPerComponent', 'budget', `${component} runtime dependencies`, targets.size,
|
|
47
|
-
contract.budgets.maxRuntimeDependenciesPerComponent,
|
|
48
|
-
))
|
|
49
|
-
}
|
|
50
|
-
for (const [component, item] of stats) {
|
|
51
|
-
const internal = item.internalPairs.size, boundary = item.boundaryPairs.size
|
|
52
|
-
const cohesion = internal + boundary ? internal / (internal + boundary) : 1
|
|
53
|
-
const boundaryRatio = internal + boundary ? boundary / (internal + boundary) : 0
|
|
54
|
-
if (contract.budgets.maxModuleFiles != null && item.files.size > contract.budgets.maxModuleFiles) {
|
|
55
|
-
violations.push(violation('budget.maxModuleFiles', 'budget', `${component} files`, item.files.size, contract.budgets.maxModuleFiles))
|
|
56
|
-
}
|
|
57
|
-
if (contract.budgets.minModuleCohesion != null && cohesion < contract.budgets.minModuleCohesion) {
|
|
58
|
-
violations.push(violation('budget.minModuleCohesion', 'budget', `${component} cohesion`, Number(cohesion.toFixed(4)), contract.budgets.minModuleCohesion))
|
|
59
|
-
}
|
|
60
|
-
if (contract.budgets.maxModuleBoundaryRatio != null && boundaryRatio > contract.budgets.maxModuleBoundaryRatio) {
|
|
61
|
-
violations.push(violation('budget.maxModuleBoundaryRatio', 'budget', `${component} boundary ratio`, Number(boundaryRatio.toFixed(4)), contract.budgets.maxModuleBoundaryRatio))
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return violations
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function codeBudgetViolations(nodes, byId, contract) {
|
|
68
|
-
const violations = []
|
|
69
|
-
for (const node of nodes) {
|
|
70
|
-
const loc = finiteArchitectureBudget(node?.complexity?.loc)
|
|
71
|
-
const cyclomatic = finiteArchitectureBudget(node?.complexity?.cyclomatic)
|
|
72
|
-
const params = finiteArchitectureBudget(node?.complexity?.params)
|
|
73
|
-
const evidence = `${node.source_file || fileOfNode(node.id, byId)}#${node.label || node.id}`
|
|
74
|
-
if (contract.budgets.maxFunctionLoc != null && loc != null && loc > contract.budgets.maxFunctionLoc) {
|
|
75
|
-
violations.push(violation('budget.maxFunctionLoc', 'budget', evidence, loc, contract.budgets.maxFunctionLoc))
|
|
76
|
-
}
|
|
77
|
-
if (contract.budgets.maxCyclomatic != null && cyclomatic != null && cyclomatic > contract.budgets.maxCyclomatic) {
|
|
78
|
-
violations.push(violation('budget.maxCyclomatic', 'budget', evidence, cyclomatic, contract.budgets.maxCyclomatic))
|
|
79
|
-
}
|
|
80
|
-
if (contract.budgets.maxParams != null && params != null && params > contract.budgets.maxParams) {
|
|
81
|
-
violations.push(violation('budget.maxParams', 'budget', evidence, params, contract.budgets.maxParams))
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (contract.budgets.maxFileLoc != null) for (const node of nodes.filter((item) => !isSymbolNode(item.id))) {
|
|
85
|
-
const file = String(node.source_file || node.id)
|
|
86
|
-
const physicalLoc = finiteArchitectureBudget(node.physical_loc)
|
|
87
|
-
if (physicalLoc != null && physicalLoc > contract.budgets.maxFileLoc) {
|
|
88
|
-
violations.push(violation('budget.maxFileLoc', 'budget', file, physicalLoc, contract.budgets.maxFileLoc))
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return violations
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function technologyViolations(contract, technologies) {
|
|
95
|
-
const techSet = new Set(technologies.map((item) => String(item ?? '').trim()).filter(Boolean))
|
|
96
|
-
const violations = []
|
|
97
|
-
for (const required of contract.technologies.required) if (!techSet.has(required)) {
|
|
98
|
-
violations.push(violation('technology.required', 'technology', `missing ${required}`))
|
|
99
|
-
}
|
|
100
|
-
for (const forbidden of contract.technologies.forbidden) if (techSet.has(forbidden)) {
|
|
101
|
-
violations.push(violation('technology.forbidden', 'technology', `forbidden ${forbidden}`))
|
|
102
|
-
}
|
|
103
|
-
return violations
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const componentFitnessMetrics = (stats) => Object.fromEntries([...stats].map(([component, item]) => {
|
|
107
|
-
const internal = item.internalPairs.size, boundary = item.boundaryPairs.size
|
|
108
|
-
return [component, {
|
|
109
|
-
files: item.files.size,
|
|
110
|
-
cohesion: internal + boundary ? Number((internal / (internal + boundary)).toFixed(4)) : 1,
|
|
111
|
-
boundaryRatio: internal + boundary ? Number((boundary / (internal + boundary)).toFixed(4)) : 0,
|
|
112
|
-
}]
|
|
113
|
-
}))
|
|
114
|
-
|
|
115
|
-
export function verifyArchitecture({graph, contract: rawContract, technologies = []}) {
|
|
116
|
-
const contract = rawContract?.contractHash ? rawContract : normalizeArchitectureContract(rawContract)
|
|
117
|
-
const {nodes, links, byId, componentEdges} = collectComponentEdges(graph, contract)
|
|
118
|
-
const stats = collectComponentFitness(nodes, links, byId, contract)
|
|
119
|
-
const runtimeCycles = stronglyConnected(runtimeFileGraph(graph))
|
|
120
|
-
const violations = [
|
|
121
|
-
...dependencyViolations(componentEdges, contract),
|
|
122
|
-
...componentBudgetViolations(componentEdges, stats, contract),
|
|
123
|
-
...codeBudgetViolations(nodes, byId, contract),
|
|
124
|
-
...technologyViolations(contract, technologies),
|
|
125
|
-
]
|
|
126
|
-
if (contract.budgets.runtimeCycles != null && runtimeCycles.length > contract.budgets.runtimeCycles) {
|
|
127
|
-
violations.push(violation('budget.runtimeCycles', 'budget', `runtime cycles: ${runtimeCycles.length}`, runtimeCycles.length, contract.budgets.runtimeCycles))
|
|
128
|
-
}
|
|
129
|
-
const exceptions = new Set(contract.exceptions
|
|
130
|
-
.filter((item) => !item.expires || item.expires >= new Date().toISOString().slice(0, 10))
|
|
131
|
-
.map((item) => item.fingerprint))
|
|
132
|
-
const active = violations.filter((item) => !exceptions.has(item.fingerprint))
|
|
133
|
-
const baseline = new Set(contract.ratchet.baseline.fingerprints)
|
|
134
|
-
const current = new Set(active.map((item) => item.fingerprint))
|
|
135
|
-
const fresh = active.filter((item) => !baseline.has(item.fingerprint))
|
|
136
|
-
const existing = active.filter((item) => baseline.has(item.fingerprint))
|
|
137
|
-
const fixed = [...baseline].filter((fingerprint) => !current.has(fingerprint))
|
|
138
|
-
const status = contract.enforcement === 'advisory'
|
|
139
|
-
? 'ADVISORY'
|
|
140
|
-
: contract.enforcement === 'strict' ? (active.length ? 'FAIL' : 'PASS') : (fresh.length ? 'FAIL' : 'PASS')
|
|
141
|
-
const result = {
|
|
142
|
-
architectureVerificationV: 1,
|
|
143
|
-
contractHash: contract.contractHash,
|
|
144
|
-
status,
|
|
145
|
-
enforcement: contract.enforcement,
|
|
146
|
-
metrics: {
|
|
147
|
-
runtimeCycles: runtimeCycles.length,
|
|
148
|
-
violations: active.length,
|
|
149
|
-
componentDependencies: componentEdges.size,
|
|
150
|
-
mappedComponents: contract.components.length,
|
|
151
|
-
componentFitness: componentFitnessMetrics(stats),
|
|
152
|
-
},
|
|
153
|
-
new: fresh,
|
|
154
|
-
existing,
|
|
155
|
-
fixed,
|
|
156
|
-
excepted: violations.filter((item) => exceptions.has(item.fingerprint)),
|
|
157
|
-
componentEdges: [...componentEdges.values()].sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || a.kind.localeCompare(b.kind)),
|
|
158
|
-
runtimeCycles: runtimeCycles.slice(0, 100),
|
|
159
|
-
}
|
|
160
|
-
return {...result, verificationHash: architectureHash(result)}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function contractForChange(contract, files = []) {
|
|
164
|
-
const normalized = (Array.isArray(files) ? files.slice(0, 200) : []).map(architecturePathPrefix).filter(Boolean)
|
|
165
|
-
const components = [...new Set(normalized.map((file) => componentForFile(file, contract.components)))]
|
|
166
|
-
const rules = contract.dependencyRules.filter((rule) => components.some((component) => matches(rule.from, component) || matches(rule.to, component)))
|
|
167
|
-
return {files: normalized, components, rules, budgets: contract.budgets, technologies: contract.technologies}
|
|
168
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// Stable public facade for target-architecture contracts.
|
|
2
|
-
// Owner-specific implementation lives under analysis/architecture so storage,
|
|
3
|
-
// normalization and graph verification can evolve independently.
|
|
4
|
-
export {
|
|
5
|
-
ARCHITECTURE_CONTRACT_V,
|
|
6
|
-
normalizeArchitectureContract,
|
|
7
|
-
} from './architecture/contract-schema.js'
|
|
8
|
-
export {
|
|
9
|
-
CONTRACT_PATHS,
|
|
10
|
-
loadArchitectureContract,
|
|
11
|
-
writeCachedArchitectureContract,
|
|
12
|
-
} from './architecture/contract-storage.js'
|
|
13
|
-
export {
|
|
14
|
-
contractForChange,
|
|
15
|
-
verifyArchitecture,
|
|
16
|
-
} from './architecture/contract-verification.js'
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// Deterministic health-debt comparison. Supply-chain findings are deliberately excluded: an
|
|
2
|
-
// immutable source checkout has no trustworthy per-repository OSV refresh stamp or installed-package
|
|
3
|
-
// malware state, so calling those findings new/fixed would manufacture certainty.
|
|
4
|
-
const OPTIONAL_CATEGORIES = new Set(["vulnerability", "malware"]);
|
|
5
|
-
|
|
6
|
-
const normalizePath = (value) => String(value || "").trim()
|
|
7
|
-
.replace(/\\/g, "/")
|
|
8
|
-
.replace(/^\.\//, "")
|
|
9
|
-
.replace(/\/{2,}/g, "/")
|
|
10
|
-
.replace(/\/$/, "");
|
|
11
|
-
|
|
12
|
-
export function normalizeAuditScopeFiles(input, maxFiles = 500) {
|
|
13
|
-
if (!Array.isArray(input)) return { ok: true, files: null };
|
|
14
|
-
const files = [];
|
|
15
|
-
const seen = new Set();
|
|
16
|
-
for (const raw of input) {
|
|
17
|
-
const file = normalizePath(raw);
|
|
18
|
-
if (!file || file.includes("\0") || file.startsWith("/") || /^[A-Za-z]:\//.test(file) || file === ".." || file.startsWith("../") || file.includes("/../") || file.startsWith("-")) {
|
|
19
|
-
return { ok: false, files: [], error: `changed_files contains an invalid repo-relative path: ${String(raw || "(empty)")}` };
|
|
20
|
-
}
|
|
21
|
-
if (!seen.has(file)) { seen.add(file); files.push(file); }
|
|
22
|
-
if (files.length > maxFiles) return { ok: false, files: [], error: `changed_files exceeds the ${maxFiles}-file safety bound` };
|
|
23
|
-
}
|
|
24
|
-
if (!files.length) return { ok: false, files: [], error: "changed_files was provided but empty" };
|
|
25
|
-
return { ok: true, files: files.sort((a, b) => a.localeCompare(b)) };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function auditFindingFiles(finding) {
|
|
29
|
-
const values = [finding?.file, finding?.manifest, finding?.graphNodeId];
|
|
30
|
-
for (const evidence of finding?.evidence || []) values.push(evidence?.file);
|
|
31
|
-
return [...new Set(values
|
|
32
|
-
.map((value) => normalizePath(String(value || "").split("#", 1)[0]))
|
|
33
|
-
.filter(Boolean))]
|
|
34
|
-
.sort((a, b) => a.localeCompare(b));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function findingTouchesAuditScope(finding, changedFiles) {
|
|
38
|
-
if (changedFiles == null) return true;
|
|
39
|
-
const scope = changedFiles instanceof Set ? changedFiles : new Set(changedFiles.map(normalizePath));
|
|
40
|
-
return auditFindingFiles(finding).some((file) => scope.has(file));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function scopeAuditFindings(findings, changedFiles) {
|
|
44
|
-
const scope = changedFiles == null ? null : new Set(changedFiles.map(normalizePath));
|
|
45
|
-
return (findings || []).filter((finding) => findingTouchesAuditScope(finding, scope));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const comparable = (findings) => (findings || []).filter((finding) => !OPTIONAL_CATEGORIES.has(finding.category));
|
|
49
|
-
const optional = (findings) => (findings || []).filter((finding) => OPTIONAL_CATEGORIES.has(finding.category));
|
|
50
|
-
|
|
51
|
-
// Findings already carry stable IDs from makeFinding(). Compare globally first, then apply the
|
|
52
|
-
// changed-file scope. This prevents pre-existing debt in an edited file from becoming "new".
|
|
53
|
-
export function compareAuditDebt(currentAudit, baselineAudit, changedFiles = null, { completeChangeSet = false } = {}) {
|
|
54
|
-
const current = comparable(currentAudit?.findings);
|
|
55
|
-
const baseline = comparable(baselineAudit?.findings);
|
|
56
|
-
const currentIds = new Set(current.map((finding) => String(finding.id)));
|
|
57
|
-
const baselineIds = new Set(baseline.map((finding) => String(finding.id)));
|
|
58
|
-
const scopedCurrent = scopeAuditFindings(current, changedFiles);
|
|
59
|
-
const scopedBaseline = scopeAuditFindings(baseline, changedFiles);
|
|
60
|
-
const globalFresh = current.filter((finding) => !baselineIds.has(String(finding.id)));
|
|
61
|
-
const globalFixedFindings = baseline.filter((finding) => !currentIds.has(String(finding.id)));
|
|
62
|
-
// A complete, automatically derived Git diff is the causal boundary: a new manifest-level finding
|
|
63
|
-
// can be caused by editing only its former importer, so path intersection would hide it. Explicit
|
|
64
|
-
// changed_files may be a subset of other working-tree changes and therefore stays path-scoped.
|
|
65
|
-
const fresh = completeChangeSet ? globalFresh : scopedCurrent.filter((finding) => !baselineIds.has(String(finding.id)));
|
|
66
|
-
const existing = scopedCurrent.filter((finding) => baselineIds.has(String(finding.id)));
|
|
67
|
-
const fixed = completeChangeSet ? globalFixedFindings : scopedBaseline.filter((finding) => !currentIds.has(String(finding.id)));
|
|
68
|
-
const all = [...fresh, ...existing].sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
69
|
-
const globalNew = globalFresh.length;
|
|
70
|
-
const globalExisting = current.length - globalNew;
|
|
71
|
-
const globalFixed = globalFixedFindings.length;
|
|
72
|
-
return {
|
|
73
|
-
scope: {strategy: completeChangeSet ? "complete-change-set-causality" : "path-intersection"},
|
|
74
|
-
new: fresh,
|
|
75
|
-
existing,
|
|
76
|
-
fixed,
|
|
77
|
-
all: all.map((finding) => ({ ...finding, debtState: baselineIds.has(String(finding.id)) ? "existing" : "new" })),
|
|
78
|
-
optional: {
|
|
79
|
-
current: optional(currentAudit?.findings),
|
|
80
|
-
baseline: optional(baselineAudit?.findings),
|
|
81
|
-
checks: ["osv", "malware"].map((name) => ({
|
|
82
|
-
name,
|
|
83
|
-
status: "UNCOMPARABLE",
|
|
84
|
-
current: currentAudit?.checks?.[name]?.status || "ERROR",
|
|
85
|
-
baseline: baselineAudit?.checks?.[name]?.status || "ERROR",
|
|
86
|
-
reason: "The immutable source checkout cannot reproduce the current advisory-refresh or installed-package scan state.",
|
|
87
|
-
})),
|
|
88
|
-
},
|
|
89
|
-
totals: {
|
|
90
|
-
scope: { new: fresh.length, existing: existing.length, fixed: fixed.length, active: fresh.length + existing.length },
|
|
91
|
-
repository: { new: globalNew, existing: globalExisting, fixed: globalFixed, active: current.length },
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import {sortFindings, summarizeFindings} from './findings.js'
|
|
2
|
-
|
|
3
|
-
const VALID_STATUS = new Set(['CHECKED', 'NOT_CHECKED', 'PARTIAL', 'ERROR', 'NOT_SUPPORTED'])
|
|
4
|
-
const VALID_COMPLETENESS = new Set(['COMPLETE', 'PARTIAL'])
|
|
5
|
-
|
|
6
|
-
const providerFailure = (provider, reason) => ({
|
|
7
|
-
id: provider.id,
|
|
8
|
-
extension: provider.extension || null,
|
|
9
|
-
status: 'ERROR',
|
|
10
|
-
completeness: 'PARTIAL',
|
|
11
|
-
detail: reason,
|
|
12
|
-
findings: [],
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
const normalizeResult = (provider, value) => {
|
|
16
|
-
if (!value || typeof value !== 'object') return providerFailure(provider, 'provider returned no result envelope')
|
|
17
|
-
const status = VALID_STATUS.has(value.status) ? value.status : 'ERROR'
|
|
18
|
-
const completeness = VALID_COMPLETENESS.has(value.completeness) ? value.completeness : 'PARTIAL'
|
|
19
|
-
const findings = Array.isArray(value.findings) ? value.findings.map((finding) => ({
|
|
20
|
-
...finding,
|
|
21
|
-
source: finding?.source || `extension:${provider.extension || provider.id}`,
|
|
22
|
-
})) : []
|
|
23
|
-
return {
|
|
24
|
-
id: provider.id,
|
|
25
|
-
extension: provider.extension || null,
|
|
26
|
-
status,
|
|
27
|
-
completeness,
|
|
28
|
-
detail: String(value.detail || (status === 'ERROR' ? 'provider returned an invalid status' : 'extension analyzer completed')),
|
|
29
|
-
findings,
|
|
30
|
-
evidence: value.evidence ?? null,
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Extension analyzers are deliberately local. A provider failure stays explicit ERROR/PARTIAL and
|
|
35
|
-
// never converts missing evidence into a clean result for the core audit.
|
|
36
|
-
export async function applyAuditExtensions(coreAudit, {providers = [], repoRoot, graph, args = {}} = {}) {
|
|
37
|
-
if (!coreAudit?.ok || !providers.length) return coreAudit
|
|
38
|
-
const results = []
|
|
39
|
-
for (const provider of providers) {
|
|
40
|
-
try {
|
|
41
|
-
const value = await provider.run({repoRoot, graph, args, coreAudit})
|
|
42
|
-
results.push(normalizeResult(provider, value))
|
|
43
|
-
} catch (error) {
|
|
44
|
-
results.push(providerFailure(provider, error instanceof Error ? error.message : String(error)))
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const findings = sortFindings([
|
|
48
|
-
...coreAudit.findings,
|
|
49
|
-
...results.flatMap((result) => result.findings),
|
|
50
|
-
])
|
|
51
|
-
return {
|
|
52
|
-
...coreAudit,
|
|
53
|
-
findings,
|
|
54
|
-
summary: summarizeFindings(findings),
|
|
55
|
-
extensionCapabilities: results.map(({findings: providerFindings, ...result}) => ({
|
|
56
|
-
...result,
|
|
57
|
-
findingCount: providerFindings.length,
|
|
58
|
-
})),
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { createRepoBoundary } from "../repo-path.js";
|
|
2
|
-
import { cargoName, parseCargoToml } from "./cargo-manifests.js";
|
|
3
|
-
import { dependencyVerification, makeFinding } from "./findings.js";
|
|
4
|
-
import { listRepoFiles, readRepoText } from "./internal-audit.collect.js";
|
|
5
|
-
import { dependencyScopeOwnsFile, dependencyScopeRoot } from "./dependency/scoped-dependencies.js";
|
|
6
|
-
|
|
7
|
-
export function collectCargoDependencyEvidence(repoRoot, { files = listRepoFiles(repoRoot), externalImports = [] } = {}) {
|
|
8
|
-
const boundary = createRepoBoundary(repoRoot);
|
|
9
|
-
const manifests = files.filter((file) => /(^|\/)Cargo\.toml$/i.test(file));
|
|
10
|
-
const scopes = manifests.map((file) => ({ file, root: dependencyScopeRoot(file), ...parseCargoToml(readRepoText(boundary, file)) }))
|
|
11
|
-
.sort((left, right) => right.root.length - left.root.length || left.root.localeCompare(right.root));
|
|
12
|
-
const workspaceDeps = new Map();
|
|
13
|
-
for (const scope of scopes) for (const dependency of scope.workspaceDependencies) workspaceDeps.set(cargoName(dependency.alias), dependency);
|
|
14
|
-
const issues = [];
|
|
15
|
-
for (const scope of scopes) {
|
|
16
|
-
scope.dependencies = scope.dependencies.map((dependency) => {
|
|
17
|
-
if (!dependency.inherited) return dependency;
|
|
18
|
-
const inherited = workspaceDeps.get(cargoName(dependency.alias));
|
|
19
|
-
if (!inherited) { issues.push(`${scope.file}: workspace dependency ${dependency.alias} is unresolved`); return dependency; }
|
|
20
|
-
return { ...dependency, name: inherited.name, version: inherited.version, inherited: true };
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
const importsByScope = new Map(scopes.map((scope) => [scope, []]));
|
|
24
|
-
for (const entry of externalImports) {
|
|
25
|
-
if (entry.ecosystem !== "crates.io" || !entry.pkg || entry.builtin || entry.unresolved) continue;
|
|
26
|
-
const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, entry.file));
|
|
27
|
-
if (owner) importsByScope.get(owner).push(entry);
|
|
28
|
-
}
|
|
29
|
-
const findings = [], declared = new Set();
|
|
30
|
-
let mappedImports = 0, unmappedImports = 0;
|
|
31
|
-
if (!scopes.length) {
|
|
32
|
-
const missing = new Map();
|
|
33
|
-
for (const entry of externalImports.filter((item) => item.ecosystem === "crates.io" && item.pkg && !item.builtin && !item.unresolved)) {
|
|
34
|
-
if (!missing.has(cargoName(entry.pkg))) missing.set(cargoName(entry.pkg), entry);
|
|
35
|
-
}
|
|
36
|
-
for (const [name, entry] of missing) findings.push(makeFinding({
|
|
37
|
-
category: "unused", rule: "missing-dep", severity: "low", confidence: "high",
|
|
38
|
-
title: `Rust crate path without Cargo.toml: ${name}`,
|
|
39
|
-
reason: "An indexed external Rust crate path exists, but no Cargo.toml was discovered.",
|
|
40
|
-
detail: `"${entry.spec || name}" is used by ${entry.file}, but no Cargo manifest owns the file.`,
|
|
41
|
-
package: name, file: entry.file, line: entry.line || 0, source: "internal",
|
|
42
|
-
verification: { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "ACTION_REQUIRED", manifestDeclaration: { status: "NOT_PRESENT" }, indexedSourceImports: { status: "FOUND", count: 1, files: [entry.file] }, mapping: "Cargo crate name normalization" },
|
|
43
|
-
fixHint: "initialize or restore the owning Cargo manifest",
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
46
|
-
for (const scope of scopes) {
|
|
47
|
-
const imports = importsByScope.get(scope);
|
|
48
|
-
const used = new Map(), missingSeen = new Set();
|
|
49
|
-
for (const entry of imports) {
|
|
50
|
-
const imported = cargoName(entry.pkg);
|
|
51
|
-
// A crate referencing its own package name (routine in examples/, tests/, benches/ and the
|
|
52
|
-
// [[bin]] that pairs with a [lib]) is a self-reference, never a missing dependency.
|
|
53
|
-
if (scope.packageName && imported === cargoName(scope.packageName)) { mappedImports++; continue; }
|
|
54
|
-
const dependency = scope.dependencies.find((item) => cargoName(item.alias) === imported || cargoName(item.name) === imported);
|
|
55
|
-
if (dependency) {
|
|
56
|
-
const evidence = used.get(dependency.alias) || [];
|
|
57
|
-
evidence.push(entry); used.set(dependency.alias, evidence); mappedImports++;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
unmappedImports++;
|
|
61
|
-
if (missingSeen.has(imported)) continue;
|
|
62
|
-
missingSeen.add(imported);
|
|
63
|
-
findings.push(makeFinding({
|
|
64
|
-
category: "unused", rule: "missing-dep", severity: "medium", confidence: "high",
|
|
65
|
-
title: `Missing Cargo dependency: ${entry.pkg}`,
|
|
66
|
-
reason: "An indexed external Rust crate path has no matching dependency alias or package name in the nearest Cargo.toml.",
|
|
67
|
-
detail: `"${entry.spec || entry.pkg}" is used by ${entry.file}, but ${scope.file} does not declare crate "${entry.pkg}" (including workspace inheritance and renamed packages).`,
|
|
68
|
-
package: entry.pkg, file: entry.file, line: entry.line || 0, manifest: scope.file,
|
|
69
|
-
evidence: [{ file: entry.file, line: entry.line || 0, snippet: entry.spec || "" }], source: "internal",
|
|
70
|
-
verification: { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "ACTION_REQUIRED", manifestDeclaration: { status: "NOT_FOUND", file: scope.file }, indexedSourceImports: { status: "FOUND", count: 1, files: [entry.file] }, mapping: "Cargo alias/package normalization" },
|
|
71
|
-
fixHint: `cargo add ${entry.pkg}`,
|
|
72
|
-
}));
|
|
73
|
-
}
|
|
74
|
-
for (const dependency of scope.dependencies) {
|
|
75
|
-
const identity = cargoName(dependency.name);
|
|
76
|
-
if (!identity) continue;
|
|
77
|
-
declared.add(`${scope.root || "."}:${identity}`);
|
|
78
|
-
const evidence = used.get(dependency.alias) || [];
|
|
79
|
-
if (evidence.length || dependency.optional || dependency.build) continue;
|
|
80
|
-
findings.push(makeFinding({
|
|
81
|
-
category: "unused", rule: "unused-dep", severity: dependency.dev ? "info" : "low", confidence: "low",
|
|
82
|
-
title: `Unused Cargo dependency: ${dependency.name}`,
|
|
83
|
-
reason: "No indexed Rust use/path/extern-crate evidence maps to this dependency; feature-only, proc-macro, generated and reflective registration remain possible.",
|
|
84
|
-
detail: `"${dependency.name}" (${dependency.alias}) is declared in ${scope.file}, but no indexed .rs file in that Cargo scope references its crate path. Confirm with cargo check/test and feature combinations before removal.`,
|
|
85
|
-
package: dependency.name, version: dependency.version, manifest: scope.file, source: "internal",
|
|
86
|
-
verification: dependencyVerification(scope.file, [], "REVIEW_REQUIRED", "Cargo alias/package normalization"),
|
|
87
|
-
fixHint: `cargo remove ${dependency.alias} after checking all features and targets`,
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const present = manifests.length > 0;
|
|
92
|
-
return {
|
|
93
|
-
present,
|
|
94
|
-
status: present ? "CHECKED" : "NOT_PRESENT",
|
|
95
|
-
completeness: present ? (issues.length ? "PARTIAL" : "COMPLETE") : "NOT_APPLICABLE",
|
|
96
|
-
manifests,
|
|
97
|
-
declared,
|
|
98
|
-
mappedImports,
|
|
99
|
-
unmappedImports,
|
|
100
|
-
findings,
|
|
101
|
-
reasons: issues,
|
|
102
|
-
reason: !present
|
|
103
|
-
? "No Cargo.toml was discovered."
|
|
104
|
-
: issues.length
|
|
105
|
-
? `Cargo declarations and indexed crate paths were checked, but ${issues.length} workspace inheritance reference(s) remain unresolved.`
|
|
106
|
-
: `Cargo declarations from ${manifests.length} scope(s), workspace inheritance, renamed packages and every indexed external crate path were compared.`,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
const clean = (value) => String(value || "").trim().replace(/^['"]|['"]$/g, "");
|
|
2
|
-
export const cargoName = (value) => clean(value).toLowerCase().replace(/_/g, "-");
|
|
3
|
-
|
|
4
|
-
function inlineTable(value) {
|
|
5
|
-
const fields = new Map();
|
|
6
|
-
for (const match of String(value || "").matchAll(/([A-Za-z][\w.-]*)\s*=\s*("(?:\\.|[^"])*"|'(?:\\.|[^'])*'|true|false)/g)) {
|
|
7
|
-
fields.set(match[1], clean(match[2]));
|
|
8
|
-
}
|
|
9
|
-
return fields;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Bounded Cargo.toml parser for package/workspace dependency tables. It intentionally owns
|
|
13
|
-
// dependency identity, aliases and concrete versions; feature activation is not treated as usage.
|
|
14
|
-
export function parseCargoToml(text) {
|
|
15
|
-
const source = String(text || "");
|
|
16
|
-
const dependencies = [];
|
|
17
|
-
const workspaceDependencies = [];
|
|
18
|
-
let section = "", packageName = "", workspace = false;
|
|
19
|
-
for (const raw of source.split(/\r?\n/)) {
|
|
20
|
-
const line = raw.replace(/(^|\s)#.*$/, "").trim();
|
|
21
|
-
if (!line) continue;
|
|
22
|
-
// Array-of-tables headers ([[bin]], [[example]], [[test]], [[bench]]) are neither the [package]
|
|
23
|
-
// table nor a dependency table. Recognise them so their `name =`/`path =` keys are not mis-read:
|
|
24
|
-
// a [[bin]] renamed to a crate ([[bin]] name = "foo") would otherwise overwrite the package name and
|
|
25
|
-
// make every foo:: import look like a self-reference, and a [[bin]] after [dependencies] would leak
|
|
26
|
-
// phantom "name"/"path" dependencies.
|
|
27
|
-
const arrayHeader = /^\[\[([^\]]+)]]$/.exec(line);
|
|
28
|
-
if (arrayHeader) { section = arrayHeader[1].trim(); continue; }
|
|
29
|
-
const header = /^\[([^\]]+)]$/.exec(line);
|
|
30
|
-
if (header) { section = header[1].trim(); workspace ||= section === "workspace"; continue; }
|
|
31
|
-
const kv = /^([A-Za-z0-9_-]+)\s*=\s*(.+)$/.exec(line);
|
|
32
|
-
if (!kv) continue;
|
|
33
|
-
if (section === "package" && kv[1] === "name") { packageName = clean(kv[2]); continue; }
|
|
34
|
-
const dependencyTable = /^(?:target\..+\.)?(dependencies|dev-dependencies|build-dependencies)$/.exec(section);
|
|
35
|
-
const workspaceTable = section === "workspace.dependencies";
|
|
36
|
-
if (!dependencyTable && !workspaceTable) continue;
|
|
37
|
-
const alias = kv[1], value = kv[2].trim();
|
|
38
|
-
const table = value.startsWith("{") ? inlineTable(value) : new Map();
|
|
39
|
-
const version = value.startsWith("{") ? clean(table.get("version")) : clean(value);
|
|
40
|
-
const name = clean(table.get("package")) || alias;
|
|
41
|
-
const record = {
|
|
42
|
-
alias,
|
|
43
|
-
name,
|
|
44
|
-
version: /^\d/.test(version) || /^=?\s*\d/.test(version) ? version.replace(/^=\s*/, "") : "",
|
|
45
|
-
dev: dependencyTable?.[1] === "dev-dependencies",
|
|
46
|
-
build: dependencyTable?.[1] === "build-dependencies",
|
|
47
|
-
optional: table.get("optional") === "true",
|
|
48
|
-
inherited: table.get("workspace") === "true",
|
|
49
|
-
};
|
|
50
|
-
(workspaceTable ? workspaceDependencies : dependencies).push(record);
|
|
51
|
-
}
|
|
52
|
-
return { packageName, workspace, dependencies, workspaceDependencies };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Cargo.lock v3/v4 [[package]] records. Registry packages are exact installed crates; workspace
|
|
56
|
-
// and git packages are excluded because OSV crates.io versions must not be inferred for them.
|
|
57
|
-
export function parseCargoLockPackages(text) {
|
|
58
|
-
const packages = [];
|
|
59
|
-
let current = null;
|
|
60
|
-
const flush = () => {
|
|
61
|
-
if (current?.name && current.version && /^registry\+https:\/\/github\.com\/rust-lang\/crates\.io-index|^registry\+https:\/\/index\.crates\.io\//.test(current.source)) {
|
|
62
|
-
packages.push({ ecosystem: "crates.io", name: current.name, version: current.version, dev: false, integrity: current.checksum || "", source: "cargo-lock" });
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
for (const raw of String(text || "").split(/\r?\n/)) {
|
|
66
|
-
const line = raw.trim();
|
|
67
|
-
if (line === "[[package]]") { flush(); current = { name: "", version: "", source: "", checksum: "" }; continue; }
|
|
68
|
-
if (!current) continue;
|
|
69
|
-
if (/^\[/.test(line)) { flush(); current = null; continue; }
|
|
70
|
-
const match = /^(name|version|source|checksum)\s*=\s*"([^"]+)"/.exec(line);
|
|
71
|
-
if (match) current[match[1]] = match[2];
|
|
72
|
-
}
|
|
73
|
-
flush();
|
|
74
|
-
const seen = new Set();
|
|
75
|
-
return packages.filter((pkg) => {
|
|
76
|
-
const key = `${pkg.name}@${pkg.version}`;
|
|
77
|
-
if (seen.has(key)) return false;
|
|
78
|
-
seen.add(key);
|
|
79
|
-
return true;
|
|
80
|
-
});
|
|
81
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import {changeLimits, normalizeChangePath} from './options.js'
|
|
2
|
-
|
|
3
|
-
function decodeGitQuoted(value) {
|
|
4
|
-
const input = String(value || '').trim()
|
|
5
|
-
if (!input.startsWith('"')) return input.split('\t', 1)[0]
|
|
6
|
-
try { return JSON.parse(input) } catch { /* Git octal escapes are not JSON. */ }
|
|
7
|
-
const bytes = []
|
|
8
|
-
for (let index = 1; index < input.length - 1; index++) {
|
|
9
|
-
const char = input[index]
|
|
10
|
-
if (char !== '\\') { bytes.push(...Buffer.from(char)); continue }
|
|
11
|
-
const next = input[++index] || ''
|
|
12
|
-
if (/[0-7]/.test(next)) {
|
|
13
|
-
let octal = next
|
|
14
|
-
while (octal.length < 3 && /[0-7]/.test(input[index + 1] || '')) octal += input[++index]
|
|
15
|
-
bytes.push(parseInt(octal, 8))
|
|
16
|
-
} else {
|
|
17
|
-
const escapes = {n: 10, r: 13, t: 9, b: 8, f: 12, v: 11, '\\': 92, '"': 34}
|
|
18
|
-
bytes.push(escapes[next] ?? next.charCodeAt(0))
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return Buffer.from(bytes).toString('utf8')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function diffPath(raw, prefix) {
|
|
25
|
-
const decoded = decodeGitQuoted(raw)
|
|
26
|
-
if (!decoded || decoded === '/dev/null') return null
|
|
27
|
-
return normalizeChangePath(decoded.startsWith(`${prefix}/`) ? decoded.slice(2) : decoded)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function headerPaths(line) {
|
|
31
|
-
const match = /^diff --git ("(?:\\.|[^"])*"|\S+) ("(?:\\.|[^"])*"|\S+)$/.exec(line)
|
|
32
|
-
return match
|
|
33
|
-
? {oldPath: diffPath(match[1], 'a'), newPath: diffPath(match[2], 'b')}
|
|
34
|
-
: {oldPath: null, newPath: null}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const emptyFile = (paths = {}) => ({
|
|
38
|
-
oldPath: paths.oldPath || null,
|
|
39
|
-
newPath: paths.newPath || null,
|
|
40
|
-
newFile: false,
|
|
41
|
-
deletedFile: false,
|
|
42
|
-
renamed: false,
|
|
43
|
-
binary: false,
|
|
44
|
-
hunks: [],
|
|
45
|
-
additions: [],
|
|
46
|
-
removals: [],
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
export function parseZeroContextDiff(diffText, options = {}) {
|
|
50
|
-
const limits = changeLimits(options)
|
|
51
|
-
const original = String(diffText ?? '')
|
|
52
|
-
const byteLength = Buffer.byteLength(original)
|
|
53
|
-
const oversized = byteLength > limits.maxDiffBytes
|
|
54
|
-
const text = oversized ? original.slice(0, limits.maxDiffBytes) : original
|
|
55
|
-
const files = []
|
|
56
|
-
let file = null, hunk = null, changedLines = 0, truncated = oversized
|
|
57
|
-
const finish = () => {
|
|
58
|
-
if (!file) return
|
|
59
|
-
if ((file.oldPath || file.newPath) && files.length < limits.maxFiles) files.push(file)
|
|
60
|
-
else if (files.length >= limits.maxFiles) truncated = true
|
|
61
|
-
file = null; hunk = null
|
|
62
|
-
}
|
|
63
|
-
const addChange = (kind, line, oldLine, newLine, mappedNewLine) => {
|
|
64
|
-
changedLines++
|
|
65
|
-
if (changedLines > limits.maxChangedLines) { truncated = true; return }
|
|
66
|
-
const change = {
|
|
67
|
-
kind,
|
|
68
|
-
text: String(line).slice(0, limits.maxLineLength),
|
|
69
|
-
...(oldLine != null ? {oldLine} : {}),
|
|
70
|
-
...(newLine != null ? {newLine} : {}),
|
|
71
|
-
mappedNewLine,
|
|
72
|
-
}
|
|
73
|
-
;(kind === 'added' ? file.additions : file.removals).push(change)
|
|
74
|
-
}
|
|
75
|
-
for (const line of text.split(/\r?\n/)) {
|
|
76
|
-
if (line.startsWith('diff --git ')) { finish(); file = emptyFile(headerPaths(line)); continue }
|
|
77
|
-
if (!file && line.startsWith('--- ')) file = emptyFile()
|
|
78
|
-
if (!file) continue
|
|
79
|
-
if (line.startsWith('new file mode ')) { file.newFile = true; continue }
|
|
80
|
-
if (line.startsWith('deleted file mode ')) { file.deletedFile = true; continue }
|
|
81
|
-
if (line.startsWith('rename from ')) { file.oldPath = normalizeChangePath(decodeGitQuoted(line.slice(12))); file.renamed = true; continue }
|
|
82
|
-
if (line.startsWith('rename to ')) { file.newPath = normalizeChangePath(decodeGitQuoted(line.slice(10))); file.renamed = true; continue }
|
|
83
|
-
if (line.startsWith('Binary files ') || line === 'GIT binary patch') { file.binary = true; continue }
|
|
84
|
-
if (!hunk && line.startsWith('--- ')) { file.oldPath = diffPath(line.slice(4), 'a'); if (!file.oldPath) file.newFile = true; continue }
|
|
85
|
-
if (!hunk && line.startsWith('+++ ')) { file.newPath = diffPath(line.slice(4), 'b'); if (!file.newPath) file.deletedFile = true; continue }
|
|
86
|
-
const match = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line)
|
|
87
|
-
if (match) {
|
|
88
|
-
hunk = {
|
|
89
|
-
oldStart: Number(match[1]), oldCount: match[2] == null ? 1 : Number(match[2]),
|
|
90
|
-
newStart: Number(match[3]), newCount: match[4] == null ? 1 : Number(match[4]),
|
|
91
|
-
oldCursor: Number(match[1]), newCursor: Number(match[3]),
|
|
92
|
-
}
|
|
93
|
-
file.hunks.push({oldStart: hunk.oldStart, oldCount: hunk.oldCount, newStart: hunk.newStart, newCount: hunk.newCount})
|
|
94
|
-
continue
|
|
95
|
-
}
|
|
96
|
-
if (!hunk || line.startsWith('\\ No newline')) continue
|
|
97
|
-
if (line.startsWith('+')) { addChange('added', line.slice(1), null, hunk.newCursor, hunk.newCursor); hunk.newCursor++ }
|
|
98
|
-
else if (line.startsWith('-')) { addChange('removed', line.slice(1), hunk.oldCursor, null, hunk.newCursor); hunk.oldCursor++ }
|
|
99
|
-
else { hunk.oldCursor++; hunk.newCursor++ }
|
|
100
|
-
}
|
|
101
|
-
finish()
|
|
102
|
-
return {files, byteLength, changedLines: Math.min(changedLines, limits.maxChangedLines), truncated, oversized, limits}
|
|
103
|
-
}
|