weavatrix 0.3.14 → 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 -66
- 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,213 +0,0 @@
|
|
|
1
|
-
import {createHash} from 'node:crypto'
|
|
2
|
-
import {computeDuplicates} from '../analysis/duplicates.js'
|
|
3
|
-
import {
|
|
4
|
-
CAPS, STATE, VERDICT, bounded, compareText, graphId, repoRelativePath,
|
|
5
|
-
} from './evidence-snapshot.common.mjs'
|
|
6
|
-
import {compareDuplicateMember} from './evidence/duplicate-member-order.mjs'
|
|
7
|
-
|
|
8
|
-
const CLONE_MIN_SIMILARITY = 80
|
|
9
|
-
const DIVERGENCE_MAX_SIMILARITY = 45
|
|
10
|
-
const MIN_TOKENS = 50
|
|
11
|
-
const FILTERED_CLASSES = new Set(['test', 'e2e', 'generated', 'vendored', 'mock', 'story', 'docs', 'benchmark', 'temp'])
|
|
12
|
-
const SAFE_SYMBOL = /^[A-Za-z_$][A-Za-z0-9_$]*$/
|
|
13
|
-
|
|
14
|
-
const DUPLICATE_EVIDENCE_THRESHOLDS = Object.freeze({
|
|
15
|
-
clones: Object.freeze({mode: 'renamed', minSimilarityPercent: CLONE_MIN_SIMILARITY, minTokens: MIN_TOKENS}),
|
|
16
|
-
divergence: Object.freeze({
|
|
17
|
-
sameName: true,
|
|
18
|
-
maxSimilarityPercent: DIVERGENCE_MAX_SIMILARITY,
|
|
19
|
-
minTokens: MIN_TOKENS,
|
|
20
|
-
maxImplementationsPerName: 12,
|
|
21
|
-
}),
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const hash = (value) => createHash('sha256').update(value).digest('hex').slice(0, 24)
|
|
25
|
-
const positiveInteger = (value) => Number.isFinite(value) && value >= 1 ? Math.trunc(value) : undefined
|
|
26
|
-
|
|
27
|
-
function safeGraphNodeId(value, file) {
|
|
28
|
-
const id = graphId(value)
|
|
29
|
-
if (!id) return undefined
|
|
30
|
-
const hashIndex = id.indexOf('#')
|
|
31
|
-
if (hashIndex < 1 || id.slice(0, hashIndex) !== file) return undefined
|
|
32
|
-
const suffix = id.slice(hashIndex)
|
|
33
|
-
return /^#[^\s\\/\u0000-\u001f\u007f]{1,511}$/u.test(suffix) ? id : undefined
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function member(fragment) {
|
|
37
|
-
if (!fragment || typeof fragment !== 'object' || fragment.kind === 'string' || fragment.excluded === true) return null
|
|
38
|
-
const classes = Array.isArray(fragment.classes) ? fragment.classes : []
|
|
39
|
-
if (fragment.test === true || classes.some((value) => FILTERED_CLASSES.has(value))) return null
|
|
40
|
-
const file = repoRelativePath(fragment.file)
|
|
41
|
-
const startLine = positiveInteger(fragment.start)
|
|
42
|
-
const endLine = positiveInteger(fragment.end)
|
|
43
|
-
const tokens = positiveInteger(fragment.n)
|
|
44
|
-
if (!file || !startLine || !endLine || endLine < startLine || !tokens || tokens < MIN_TOKENS) return null
|
|
45
|
-
const out = {file, startLine, endLine, tokens}
|
|
46
|
-
const nodeId = safeGraphNodeId(fragment.id, file)
|
|
47
|
-
if (nodeId) out.graphNodeId = nodeId
|
|
48
|
-
return out
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function memberIdentity(value) {
|
|
52
|
-
return `${value.file}\0${value.startLine}\0${value.endLine}\0${value.graphNodeId || ''}`
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function eligibleMembers(fragments) {
|
|
56
|
-
const out = new Map()
|
|
57
|
-
for (let index = 0; index < fragments.length; index++) {
|
|
58
|
-
const value = member(fragments[index])
|
|
59
|
-
if (value) out.set(index, value)
|
|
60
|
-
}
|
|
61
|
-
return out
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function cloneGroups(data, membersByIndex) {
|
|
65
|
-
const parent = new Map([...membersByIndex.keys()].map((index) => [index, index]))
|
|
66
|
-
const find = (index) => {
|
|
67
|
-
let root = index
|
|
68
|
-
while (parent.get(root) !== root) root = parent.get(root)
|
|
69
|
-
while (parent.get(index) !== index) {
|
|
70
|
-
const next = parent.get(index)
|
|
71
|
-
parent.set(index, root)
|
|
72
|
-
index = next
|
|
73
|
-
}
|
|
74
|
-
return root
|
|
75
|
-
}
|
|
76
|
-
const union = (left, right) => {
|
|
77
|
-
const a = find(left), b = find(right)
|
|
78
|
-
if (a !== b) parent.set(a > b ? a : b, a > b ? b : a)
|
|
79
|
-
}
|
|
80
|
-
const links = []
|
|
81
|
-
for (const pair of Array.isArray(data?.modes?.renamed) ? data.modes.renamed : []) {
|
|
82
|
-
if (!Array.isArray(pair) || pair.length < 3) continue
|
|
83
|
-
const left = Number(pair[0]), right = Number(pair[1]), similarity = Number(pair[2])
|
|
84
|
-
if (!Number.isInteger(left) || !Number.isInteger(right) || left === right ||
|
|
85
|
-
!membersByIndex.has(left) || !membersByIndex.has(right) ||
|
|
86
|
-
!Number.isFinite(similarity) || similarity < CLONE_MIN_SIMILARITY || similarity > 100) continue
|
|
87
|
-
union(left, right)
|
|
88
|
-
links.push({left, right, similarity: Math.trunc(similarity)})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const components = new Map()
|
|
92
|
-
for (const index of membersByIndex.keys()) {
|
|
93
|
-
const root = find(index)
|
|
94
|
-
if (!components.has(root)) components.set(root, [])
|
|
95
|
-
components.get(root).push(index)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const groups = []
|
|
99
|
-
for (const indices of components.values()) {
|
|
100
|
-
if (indices.length < 2) continue
|
|
101
|
-
const unique = new Map()
|
|
102
|
-
for (const index of indices) {
|
|
103
|
-
const value = membersByIndex.get(index)
|
|
104
|
-
unique.set(memberIdentity(value), value)
|
|
105
|
-
}
|
|
106
|
-
const members = [...unique.values()].sort(compareDuplicateMember)
|
|
107
|
-
if (members.length < 2) continue
|
|
108
|
-
const indexSet = new Set(indices)
|
|
109
|
-
const similarities = links
|
|
110
|
-
.filter((link) => indexSet.has(link.left) && indexSet.has(link.right))
|
|
111
|
-
.map((link) => link.similarity)
|
|
112
|
-
if (!similarities.length) continue
|
|
113
|
-
const identity = members.map(memberIdentity).join('\0')
|
|
114
|
-
groups.push({
|
|
115
|
-
id: hash(`clone\0${identity}`),
|
|
116
|
-
memberCount: members.length,
|
|
117
|
-
totalTokens: members.reduce((sum, value) => sum + value.tokens, 0),
|
|
118
|
-
strongestSimilarity: Math.max(...similarities),
|
|
119
|
-
weakestLinkedSimilarity: Math.min(...similarities),
|
|
120
|
-
membersTruncated: members.length > CAPS.duplicateMembers,
|
|
121
|
-
members: members.slice(0, CAPS.duplicateMembers),
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
return groups.sort((a, b) => b.totalTokens - a.totalTokens || b.memberCount - a.memberCount ||
|
|
125
|
-
b.strongestSimilarity - a.strongestSimilarity || compareText(a.id, b.id))
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function divergenceCandidates(data, membersByIndex) {
|
|
129
|
-
const candidates = []
|
|
130
|
-
for (const twin of Array.isArray(data?.nameTwins) ? data.nameTwins : []) {
|
|
131
|
-
const symbol = typeof twin?.label === 'string' && SAFE_SYMBOL.test(twin.label) ? twin.label : undefined
|
|
132
|
-
if (!symbol) continue
|
|
133
|
-
const pairs = []
|
|
134
|
-
for (const pair of Array.isArray(twin.pairs) ? twin.pairs : []) {
|
|
135
|
-
const left = Number(pair?.a), right = Number(pair?.b), similarity = Number(pair?.similarity)
|
|
136
|
-
const leftMember = membersByIndex.get(left), rightMember = membersByIndex.get(right)
|
|
137
|
-
if (!Number.isInteger(left) || !Number.isInteger(right) || left === right || !leftMember || !rightMember ||
|
|
138
|
-
leftMember.file === rightMember.file || !Number.isFinite(similarity) ||
|
|
139
|
-
similarity < 0 || similarity > DIVERGENCE_MAX_SIMILARITY) continue
|
|
140
|
-
const members = [leftMember, rightMember].sort(compareDuplicateMember)
|
|
141
|
-
pairs.push({
|
|
142
|
-
similarity: Math.trunc(similarity),
|
|
143
|
-
totalTokens: members[0].tokens + members[1].tokens,
|
|
144
|
-
members,
|
|
145
|
-
identity: members.map(memberIdentity).join('\0'),
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
pairs.sort((a, b) => a.similarity - b.similarity || b.totalTokens - a.totalTokens || compareText(a.identity, b.identity))
|
|
149
|
-
const selected = pairs[0]
|
|
150
|
-
if (!selected) continue
|
|
151
|
-
candidates.push({
|
|
152
|
-
id: hash(`divergence\0${symbol.toLowerCase()}\0${selected.identity}`),
|
|
153
|
-
symbol,
|
|
154
|
-
similarity: selected.similarity,
|
|
155
|
-
totalTokens: selected.totalTokens,
|
|
156
|
-
members: selected.members,
|
|
157
|
-
})
|
|
158
|
-
}
|
|
159
|
-
return candidates.sort((a, b) => b.totalTokens - a.totalTokens || a.similarity - b.similarity ||
|
|
160
|
-
compareText(a.symbol, b.symbol) || compareText(a.id, b.id))
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function errorSection(reason = 'DUPLICATE_ANALYSIS_ERROR') {
|
|
164
|
-
return {
|
|
165
|
-
state: STATE.ERROR,
|
|
166
|
-
verdict: VERDICT.UNKNOWN,
|
|
167
|
-
thresholds: DUPLICATE_EVIDENCE_THRESHOLDS,
|
|
168
|
-
completeness: {
|
|
169
|
-
fragments: {total: 0, eligible: 0, filtered: 0},
|
|
170
|
-
cloneGroups: {total: 0, returned: 0, truncated: false},
|
|
171
|
-
divergenceCandidates: {total: 0, returned: 0, truncated: false},
|
|
172
|
-
reasons: [reason],
|
|
173
|
-
},
|
|
174
|
-
cloneGroups: [],
|
|
175
|
-
divergenceCandidates: [],
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function buildDuplicatesSection(repoRoot, graph) {
|
|
180
|
-
try {
|
|
181
|
-
const data = computeDuplicates(repoRoot, graph, {nameTwins: true, includeStrings: false})
|
|
182
|
-
if (!data?.ok || !Array.isArray(data.frags)) return errorSection()
|
|
183
|
-
const membersByIndex = eligibleMembers(data.frags)
|
|
184
|
-
const groups = bounded(cloneGroups(data, membersByIndex), CAPS.duplicateGroups)
|
|
185
|
-
const divergence = bounded(divergenceCandidates(data, membersByIndex), CAPS.divergenceCandidates)
|
|
186
|
-
const reasons = []
|
|
187
|
-
if (!Number.isFinite(data.graphSymbols) || data.graphSymbols < 1) reasons.push('NO_GRAPH_SYMBOLS')
|
|
188
|
-
if (data.completeness?.assetFiles?.truncated) reasons.push('ASSET_SCAN_TRUNCATED')
|
|
189
|
-
if (data.completeness?.nameTwinsTruncated) reasons.push('NAME_TWIN_SCAN_TRUNCATED')
|
|
190
|
-
if (groups.items.some((group) => group.membersTruncated)) reasons.push('CLONE_MEMBERS_TRUNCATED')
|
|
191
|
-
if (groups.completeness.truncated) reasons.push('CLONE_GROUPS_TRUNCATED')
|
|
192
|
-
if (divergence.completeness.truncated) reasons.push('DIVERGENCE_CANDIDATES_TRUNCATED')
|
|
193
|
-
return {
|
|
194
|
-
state: reasons.length ? STATE.PARTIAL : STATE.COMPLETE,
|
|
195
|
-
verdict: VERDICT.UNKNOWN,
|
|
196
|
-
thresholds: DUPLICATE_EVIDENCE_THRESHOLDS,
|
|
197
|
-
completeness: {
|
|
198
|
-
fragments: {
|
|
199
|
-
total: data.frags.length,
|
|
200
|
-
eligible: membersByIndex.size,
|
|
201
|
-
filtered: data.frags.length - membersByIndex.size,
|
|
202
|
-
},
|
|
203
|
-
cloneGroups: groups.completeness,
|
|
204
|
-
divergenceCandidates: divergence.completeness,
|
|
205
|
-
reasons,
|
|
206
|
-
},
|
|
207
|
-
cloneGroups: groups.items,
|
|
208
|
-
divergenceCandidates: divergence.items,
|
|
209
|
-
}
|
|
210
|
-
} catch {
|
|
211
|
-
return errorSection()
|
|
212
|
-
}
|
|
213
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CAPS, COMPLEXITY_THRESHOLDS, STATE, VERDICT, addIf, bounded, compareText, graphId,
|
|
3
|
-
normalizeCheckState, numericRecord, optionalNonNegativeInteger, privacySafeText, repoRelativePath,
|
|
4
|
-
sanitizeFinding,
|
|
5
|
-
} from './evidence-snapshot.common.mjs'
|
|
6
|
-
import {summarizeFindings} from '../analysis/findings.js'
|
|
7
|
-
import {auditFindingPathScope} from './health/audit-format.mjs'
|
|
8
|
-
|
|
9
|
-
function buildHotspots(graph) {
|
|
10
|
-
let analyzed = 0
|
|
11
|
-
const facts = []
|
|
12
|
-
for (const node of Array.isArray(graph?.nodes) ? graph.nodes : []) {
|
|
13
|
-
if (!node || typeof node !== 'object' || !node.complexity || typeof node.complexity !== 'object') continue
|
|
14
|
-
const id = graphId(node.id)
|
|
15
|
-
const file = repoRelativePath(node.source_file)
|
|
16
|
-
if (!id || !file) continue
|
|
17
|
-
analyzed++
|
|
18
|
-
const loc = optionalNonNegativeInteger(node.complexity.loc)
|
|
19
|
-
const cyclomatic = optionalNonNegativeInteger(node.complexity.cyclomatic)
|
|
20
|
-
const params = optionalNonNegativeInteger(node.complexity.params)
|
|
21
|
-
const breaches = []
|
|
22
|
-
let severity = 'medium'
|
|
23
|
-
if (loc !== undefined && loc >= COMPLEXITY_THRESHOLDS.loc.high) { breaches.push('LOC_HIGH'); severity = 'high' }
|
|
24
|
-
else if (loc !== undefined && loc >= COMPLEXITY_THRESHOLDS.loc.warning) breaches.push('LOC_WARNING')
|
|
25
|
-
if (cyclomatic !== undefined && cyclomatic >= COMPLEXITY_THRESHOLDS.cyclomatic.high) { breaches.push('CYCLOMATIC_HIGH'); severity = 'high' }
|
|
26
|
-
else if (cyclomatic !== undefined && cyclomatic >= COMPLEXITY_THRESHOLDS.cyclomatic.warning) breaches.push('CYCLOMATIC_WARNING')
|
|
27
|
-
if (params !== undefined && params >= COMPLEXITY_THRESHOLDS.params.high) { breaches.push('PARAMS_HIGH'); severity = 'high' }
|
|
28
|
-
else if (params !== undefined && params >= COMPLEXITY_THRESHOLDS.params.warning) breaches.push('PARAMS_WARNING')
|
|
29
|
-
if (!breaches.length) continue
|
|
30
|
-
const fact = {id, file, severity, breaches: breaches.sort(compareText)}
|
|
31
|
-
addIf(fact, 'symbol', privacySafeText(node.label, 256))
|
|
32
|
-
addIf(fact, 'startLine', optionalNonNegativeInteger(node.complexity.startLine))
|
|
33
|
-
addIf(fact, 'endLine', optionalNonNegativeInteger(node.complexity.endLine))
|
|
34
|
-
addIf(fact, 'loc', loc)
|
|
35
|
-
addIf(fact, 'cyclomatic', cyclomatic)
|
|
36
|
-
addIf(fact, 'params', params)
|
|
37
|
-
facts.push(fact)
|
|
38
|
-
}
|
|
39
|
-
facts.sort((a, b) => (a.severity === b.severity ? 0 : a.severity === 'high' ? -1 : 1) ||
|
|
40
|
-
(b.loc || 0) - (a.loc || 0) || (b.cyclomatic || 0) - (a.cyclomatic || 0) ||
|
|
41
|
-
(b.params || 0) - (a.params || 0) || compareText(a.id, b.id))
|
|
42
|
-
return {analyzed, ...bounded(facts, CAPS.hotspots)}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function buildHealthSection(graph, audit, repoRoot = null) {
|
|
46
|
-
if (!audit?.ok) {
|
|
47
|
-
return {
|
|
48
|
-
state: STATE.ERROR,
|
|
49
|
-
verdict: VERDICT.UNKNOWN,
|
|
50
|
-
completeness: {reasons: ['AUDIT_ERROR']},
|
|
51
|
-
summary: {bySeverity: {}, byCategory: {}},
|
|
52
|
-
checks: {osv: STATE.ERROR, malware: STATE.ERROR},
|
|
53
|
-
findings: [],
|
|
54
|
-
complexity: {thresholds: COMPLEXITY_THRESHOLDS, analyzed: 0, hotspots: []},
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// Source-free extension evidence follows the same production-first path policy as
|
|
58
|
-
// run_audit. Classified-only findings remain available locally through
|
|
59
|
-
// include_classified:true, but must not turn a production snapshot red.
|
|
60
|
-
const scopedFindings = auditFindingPathScope(audit.findings, {repoRoot}).findings
|
|
61
|
-
const scopedSummary = summarizeFindings(scopedFindings)
|
|
62
|
-
const findings = bounded(scopedFindings.map(sanitizeFinding).filter(Boolean)
|
|
63
|
-
.sort((a, b) => compareText(a.severity, b.severity) || compareText(a.category, b.category) ||
|
|
64
|
-
compareText(a.rule, b.rule) || compareText(a.file || a.package || '', b.file || b.package || '') || compareText(a.id, b.id)),
|
|
65
|
-
CAPS.findings)
|
|
66
|
-
const hotspots = buildHotspots(graph)
|
|
67
|
-
const checks = {
|
|
68
|
-
osv: normalizeCheckState(audit.checks?.osv?.status),
|
|
69
|
-
malware: normalizeCheckState(audit.checks?.malware?.status),
|
|
70
|
-
}
|
|
71
|
-
const state = Object.values(checks).every((value) => value === STATE.COMPLETE || value === STATE.NOT_APPLICABLE)
|
|
72
|
-
? STATE.COMPLETE
|
|
73
|
-
: STATE.PARTIAL
|
|
74
|
-
return {
|
|
75
|
-
state,
|
|
76
|
-
verdict: findings.completeness.total > 0 || hotspots.completeness.total > 0
|
|
77
|
-
? VERDICT.FAIL
|
|
78
|
-
: state === STATE.COMPLETE ? VERDICT.PASS : VERDICT.UNKNOWN,
|
|
79
|
-
completeness: {
|
|
80
|
-
findings: findings.completeness,
|
|
81
|
-
hotspots: hotspots.completeness,
|
|
82
|
-
complexity: {analyzed: hotspots.analyzed},
|
|
83
|
-
reasons: state === STATE.PARTIAL ? ['OPTIONAL_CHECKS_INCOMPLETE'] : [],
|
|
84
|
-
},
|
|
85
|
-
summary: {
|
|
86
|
-
bySeverity: numericRecord(scopedSummary.bySeverity, ['critical', 'high', 'medium', 'low', 'info']),
|
|
87
|
-
byCategory: numericRecord(scopedSummary.byCategory, ['unused', 'structure', 'vulnerability', 'malware']),
|
|
88
|
-
dead: numericRecord(audit.deadReport, ['deadSymbols', 'deadFiles', 'unusedExports']),
|
|
89
|
-
structure: numericRecord(audit.structureReport, [
|
|
90
|
-
'runtimeImportEdges', 'typeOnlyImportEdges', 'compileOnlyImportEdges', 'runtimeCycles',
|
|
91
|
-
'compileTimeCouplings', 'largestCycle', 'largestCompileTimeCoupling', 'orphans', 'boundaryViolations',
|
|
92
|
-
]),
|
|
93
|
-
},
|
|
94
|
-
checks,
|
|
95
|
-
findings: findings.items,
|
|
96
|
-
complexity: {
|
|
97
|
-
thresholds: COMPLEXITY_THRESHOLDS,
|
|
98
|
-
analyzed: hotspots.analyzed,
|
|
99
|
-
hotspots: hotspots.items,
|
|
100
|
-
},
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CAPS, STATE, VERDICT, addIf, bounded, compareText, metadataString, normalizeCheckState,
|
|
3
|
-
repoRelativePath, safeToken,
|
|
4
|
-
} from './evidence-snapshot.common.mjs'
|
|
5
|
-
import {buildPackageDependencyGraph} from './evidence-snapshot.package-graph.mjs'
|
|
6
|
-
|
|
7
|
-
const FIXED_PACKAGE_SOURCES = new Set([
|
|
8
|
-
'package-lock', 'yarn-lock', 'requirements', 'venv', 'poetry-lock', 'uv-lock',
|
|
9
|
-
'pipfile-lock', 'go-sum', 'go-mod', 'node_modules',
|
|
10
|
-
])
|
|
11
|
-
const PACKAGE_SOURCE_FILE = /(^|\/)(?:package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.ya?ml|requirements[\w.-]*\.(?:txt|in)|poetry\.lock|uv\.lock|Pipfile\.lock|go\.(?:mod|sum))$/i
|
|
12
|
-
|
|
13
|
-
function sanitizeBadge(category, value) {
|
|
14
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
15
|
-
const id = safeToken(value.id, 128)
|
|
16
|
-
if (!id) return null
|
|
17
|
-
const out = {category, id}
|
|
18
|
-
addIf(out, 'name', metadataString(value.name || value.label, 128))
|
|
19
|
-
addIf(out, 'kind', safeToken(value.kind, 64))
|
|
20
|
-
addIf(out, 'title', metadataString(value.title, 128))
|
|
21
|
-
addIf(out, 'version', safeToken(value.version, 64))
|
|
22
|
-
return out
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function buildTechnologiesSection(stack, stackError) {
|
|
26
|
-
if (stackError) {
|
|
27
|
-
return {
|
|
28
|
-
state: STATE.ERROR,
|
|
29
|
-
verdict: VERDICT.UNKNOWN,
|
|
30
|
-
completeness: {badges: {total: 0, returned: 0, truncated: false}, reasons: ['STACK_DETECTION_ERROR']},
|
|
31
|
-
badges: [],
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const categories = ['languages', 'runtimes', 'tests', 'infra', 'deploy']
|
|
35
|
-
const facts = categories.flatMap((category) => (Array.isArray(stack?.[category]) ? stack[category] : [])
|
|
36
|
-
.map((badge) => sanitizeBadge(category, badge)).filter(Boolean))
|
|
37
|
-
.sort((a, b) => compareText(a.category, b.category) || compareText(a.id, b.id))
|
|
38
|
-
const badges = bounded(facts, CAPS.stackBadges)
|
|
39
|
-
return {
|
|
40
|
-
state: STATE.PARTIAL,
|
|
41
|
-
verdict: VERDICT.UNKNOWN,
|
|
42
|
-
completeness: {
|
|
43
|
-
badges: badges.completeness,
|
|
44
|
-
reasons: ['MANIFEST_AND_FILE_HEURISTICS_ONLY', 'INFRA_IMPORT_DETECTION_DISABLED'],
|
|
45
|
-
},
|
|
46
|
-
badges: badges.items,
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function packageSource(value) {
|
|
51
|
-
const raw = metadataString(value, 512)
|
|
52
|
-
if (!raw) return undefined
|
|
53
|
-
const normalized = raw.replace(/\\/g, '/')
|
|
54
|
-
if (FIXED_PACKAGE_SOURCES.has(normalized)) return normalized
|
|
55
|
-
const relative = repoRelativePath(normalized, 512)
|
|
56
|
-
return relative && PACKAGE_SOURCE_FILE.test(relative) ? relative : undefined
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function sanitizePackage(value) {
|
|
60
|
-
const name = safeToken(value?.name)
|
|
61
|
-
const version = safeToken(value?.version, 128)
|
|
62
|
-
const ecosystem = safeToken(value?.ecosystem, 64)
|
|
63
|
-
const source = packageSource(value?.source)
|
|
64
|
-
if (!name || !version || !ecosystem || !source) return null
|
|
65
|
-
return {name, version, ecosystem, dev: value.dev === true, source}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function buildDirectUsage(externalImports) {
|
|
69
|
-
const usage = new Map()
|
|
70
|
-
for (const entry of Array.isArray(externalImports) ? externalImports : []) {
|
|
71
|
-
if (!entry || typeof entry !== 'object' || entry.builtin === true || entry.unresolved === true) continue
|
|
72
|
-
const name = safeToken(entry.pkg)
|
|
73
|
-
const ecosystem = safeToken(entry.ecosystem || 'npm', 64)
|
|
74
|
-
const file = repoRelativePath(entry.file)
|
|
75
|
-
if (!name || !ecosystem || !file) continue
|
|
76
|
-
const key = `${ecosystem}\0${name}`
|
|
77
|
-
let fact = usage.get(key)
|
|
78
|
-
if (!fact) usage.set(key, (fact = {name, ecosystem, importCount: 0, files: new Set(), kinds: new Set()}))
|
|
79
|
-
fact.importCount++
|
|
80
|
-
fact.files.add(file)
|
|
81
|
-
const kind = safeToken(entry.kind, 64)
|
|
82
|
-
if (kind) fact.kinds.add(kind)
|
|
83
|
-
}
|
|
84
|
-
return [...usage.values()].map((fact) => {
|
|
85
|
-
const files = [...fact.files].sort(compareText)
|
|
86
|
-
return {
|
|
87
|
-
name: fact.name,
|
|
88
|
-
ecosystem: fact.ecosystem,
|
|
89
|
-
importCount: fact.importCount,
|
|
90
|
-
fileCount: files.length,
|
|
91
|
-
files: files.slice(0, CAPS.usageFiles),
|
|
92
|
-
filesTruncated: files.length > CAPS.usageFiles,
|
|
93
|
-
kinds: [...fact.kinds].sort(compareText),
|
|
94
|
-
}
|
|
95
|
-
}).sort((a, b) => compareText(a.ecosystem, b.ecosystem) || compareText(a.name, b.name))
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function buildPackagesSection(installedResult, installedError, graph, audit, repoRoot) {
|
|
99
|
-
const dependencyGraph = buildPackageDependencyGraph(repoRoot)
|
|
100
|
-
const dependencyGraphCounts = {
|
|
101
|
-
dependencyGraphNodes: dependencyGraph.completeness.nodes,
|
|
102
|
-
dependencyGraphEdges: dependencyGraph.completeness.edges,
|
|
103
|
-
}
|
|
104
|
-
if (installedError) {
|
|
105
|
-
return {
|
|
106
|
-
state: STATE.ERROR,
|
|
107
|
-
verdict: VERDICT.UNKNOWN,
|
|
108
|
-
completeness: {...dependencyGraphCounts, reasons: ['PACKAGE_INVENTORY_ERROR']},
|
|
109
|
-
checks: {osv: normalizeCheckState(audit?.checks?.osv?.status), malware: normalizeCheckState(audit?.checks?.malware?.status)},
|
|
110
|
-
inventory: [],
|
|
111
|
-
directUsage: [],
|
|
112
|
-
dependencyGraph,
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const inventory = bounded((installedResult?.installed || []).map(sanitizePackage).filter(Boolean)
|
|
116
|
-
.sort((a, b) => compareText(a.ecosystem, b.ecosystem) || compareText(a.name, b.name) ||
|
|
117
|
-
compareText(a.version, b.version) || compareText(a.source, b.source) || Number(a.dev) - Number(b.dev)),
|
|
118
|
-
CAPS.packages)
|
|
119
|
-
const usage = bounded(buildDirectUsage(graph?.externalImports), CAPS.directUsage)
|
|
120
|
-
const packageRules = new Set(['unused-dep', 'missing-dep', 'duplicate-dep', 'lockfile-drift', 'known-vuln', 'malicious-package', 'typosquat'])
|
|
121
|
-
const packageFailure = (audit?.findings || []).some((finding) => packageRules.has(finding?.rule))
|
|
122
|
-
const checks = {
|
|
123
|
-
osv: normalizeCheckState(audit?.checks?.osv?.status),
|
|
124
|
-
malware: normalizeCheckState(audit?.checks?.malware?.status),
|
|
125
|
-
}
|
|
126
|
-
const reasons = []
|
|
127
|
-
if (inventory.completeness.truncated) reasons.push('PACKAGE_INVENTORY_TRUNCATED')
|
|
128
|
-
if (usage.completeness.truncated) reasons.push('DIRECT_USAGE_TRUNCATED')
|
|
129
|
-
if (Object.values(checks).some((value) => ![STATE.COMPLETE, STATE.NOT_APPLICABLE].includes(value))) {
|
|
130
|
-
reasons.push('OPTIONAL_CHECKS_INCOMPLETE')
|
|
131
|
-
}
|
|
132
|
-
if ([STATE.PARTIAL, STATE.ERROR].includes(dependencyGraph.state)) reasons.push('PACKAGE_DEPENDENCY_GRAPH_PARTIAL')
|
|
133
|
-
const state = reasons.length ? STATE.PARTIAL : STATE.COMPLETE
|
|
134
|
-
return {
|
|
135
|
-
state,
|
|
136
|
-
verdict: packageFailure ? VERDICT.FAIL : state === STATE.COMPLETE ? VERDICT.PASS : VERDICT.UNKNOWN,
|
|
137
|
-
completeness: {
|
|
138
|
-
inventory: inventory.completeness,
|
|
139
|
-
directUsage: usage.completeness,
|
|
140
|
-
...dependencyGraphCounts,
|
|
141
|
-
reasons,
|
|
142
|
-
},
|
|
143
|
-
checks,
|
|
144
|
-
inventory: inventory.items,
|
|
145
|
-
directUsage: usage.items,
|
|
146
|
-
dependencyGraph,
|
|
147
|
-
}
|
|
148
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// Bounded, deterministic and source-free evidence derived locally from a repository and its graph.
|
|
2
|
-
// The sync wire layer applies a second allowlist because cached graph/evidence data is untrusted.
|
|
3
|
-
import {aggregateGraph} from '../analysis/graph-analysis.js'
|
|
4
|
-
import {runInternalAudit} from '../analysis/internal-audit.js'
|
|
5
|
-
import {detectRepoStack} from '../scan/discover.js'
|
|
6
|
-
import {collectInstalled} from '../security/installed.js'
|
|
7
|
-
import {STATE, hashSnapshot} from './evidence-snapshot.common.mjs'
|
|
8
|
-
import {buildArchitectureSection} from './evidence-snapshot.architecture.mjs'
|
|
9
|
-
import {buildDuplicatesSection} from './evidence-snapshot.duplicates.mjs'
|
|
10
|
-
import {buildHealthSection} from './evidence-snapshot.health.mjs'
|
|
11
|
-
import {buildPackagesSection, buildTechnologiesSection} from './evidence-snapshot.inventory.mjs'
|
|
12
|
-
import {buildStructureEvidence} from './evidence-snapshot.structure.mjs'
|
|
13
|
-
|
|
14
|
-
export async function createEvidenceSnapshot({repoRoot, graph}) {
|
|
15
|
-
const inputGraph = graph && typeof graph === 'object' && !Array.isArray(graph)
|
|
16
|
-
? graph
|
|
17
|
-
: {nodes: [], links: [], externalImports: []}
|
|
18
|
-
|
|
19
|
-
let aggregate = null
|
|
20
|
-
try { aggregate = aggregateGraph(inputGraph) } catch { aggregate = null }
|
|
21
|
-
|
|
22
|
-
let audit = null
|
|
23
|
-
try { audit = await runInternalAudit(repoRoot, {graph: inputGraph, skipMalwareScan: true}) } catch { audit = null }
|
|
24
|
-
|
|
25
|
-
let stack = null
|
|
26
|
-
let stackError = false
|
|
27
|
-
try { stack = detectRepoStack(repoRoot) } catch { stackError = true }
|
|
28
|
-
|
|
29
|
-
let installedResult = null
|
|
30
|
-
let installedError = false
|
|
31
|
-
try { installedResult = collectInstalled(repoRoot) } catch { installedError = true }
|
|
32
|
-
|
|
33
|
-
const structure = buildStructureEvidence(inputGraph, repoRoot)
|
|
34
|
-
|
|
35
|
-
const sections = {
|
|
36
|
-
architecture: buildArchitectureSection(inputGraph, aggregate, audit, structure),
|
|
37
|
-
duplicates: buildDuplicatesSection(repoRoot, inputGraph),
|
|
38
|
-
health: buildHealthSection(inputGraph, audit, repoRoot),
|
|
39
|
-
technologies: buildTechnologiesSection(stack, stackError),
|
|
40
|
-
packages: buildPackagesSection(installedResult, installedError, inputGraph, audit, repoRoot),
|
|
41
|
-
}
|
|
42
|
-
const sectionStates = Object.values(sections).map((section) => section.state)
|
|
43
|
-
const state = sectionStates.every((value) => value === STATE.ERROR)
|
|
44
|
-
? STATE.ERROR
|
|
45
|
-
: sectionStates.every((value) => value === STATE.COMPLETE || value === STATE.NOT_APPLICABLE)
|
|
46
|
-
? STATE.COMPLETE
|
|
47
|
-
: STATE.PARTIAL
|
|
48
|
-
const snapshot = {evidenceSnapshotV: 1, state, sections}
|
|
49
|
-
return {...snapshot, snapshotHash: hashSnapshot(snapshot)}
|
|
50
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import {readFileSync, statSync} from 'node:fs'
|
|
2
|
-
import {createRepoBoundary} from '../repo-path.js'
|
|
3
|
-
import {STATE} from './evidence-snapshot.common.mjs'
|
|
4
|
-
import {parseBunLock, parseJsonc} from './evidence/bun-lock-graph.mjs'
|
|
5
|
-
import {MAX_LOCKFILE_BYTES, emptyGraph} from './evidence/package-graph-common.mjs'
|
|
6
|
-
import {parsePackageLock} from './evidence/package-lock-graph.mjs'
|
|
7
|
-
|
|
8
|
-
const LOCKFILES = ['npm-shrinkwrap.json', 'package-lock.json', 'bun.lock']
|
|
9
|
-
|
|
10
|
-
export function buildPackageDependencyGraph(repoRoot) {
|
|
11
|
-
const boundary = createRepoBoundary(repoRoot)
|
|
12
|
-
if (!boundary.root) return emptyGraph(STATE.ERROR, 'INVALID_REPOSITORY_ROOT')
|
|
13
|
-
|
|
14
|
-
let selected = null
|
|
15
|
-
for (const lockfile of LOCKFILES) {
|
|
16
|
-
const resolved = boundary.resolve(lockfile)
|
|
17
|
-
if (resolved.ok) {
|
|
18
|
-
selected = {lockfile, path: resolved.path}
|
|
19
|
-
break
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if (!selected) return emptyGraph(STATE.NOT_APPLICABLE, 'PACKAGE_LOCK_V2_V3_NOT_FOUND')
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
if (statSync(selected.path).size > MAX_LOCKFILE_BYTES) {
|
|
26
|
-
return emptyGraph(STATE.ERROR, 'PACKAGE_LOCK_SIZE_LIMIT_REACHED', {lockfile: selected.lockfile})
|
|
27
|
-
}
|
|
28
|
-
const raw = readFileSync(selected.path, 'utf8')
|
|
29
|
-
if (selected.lockfile === 'bun.lock') return parseBunLock(parseJsonc(raw), selected.lockfile)
|
|
30
|
-
return parsePackageLock(JSON.parse(raw), selected.lockfile)
|
|
31
|
-
} catch {
|
|
32
|
-
return emptyGraph(STATE.ERROR, 'PACKAGE_LOCK_READ_ERROR', {lockfile: selected.lockfile})
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import {createHash} from 'node:crypto'
|
|
2
|
-
import {readFileSync} from 'node:fs'
|
|
3
|
-
import {buildFileImportGraph, checkBoundaries, findSccs, isRustModuleTreeComponent, representativeCycle} from '../analysis/dep-rules.js'
|
|
4
|
-
import {createRepoBoundary} from '../repo-path.js'
|
|
5
|
-
import {CAPS, bounded, compareText, repoRelativePath, safeToken} from './evidence-snapshot.common.mjs'
|
|
6
|
-
|
|
7
|
-
const MAX_CYCLE_MEMBERS = 200
|
|
8
|
-
const SEVERITIES = new Set(['critical', 'high', 'medium', 'low', 'info'])
|
|
9
|
-
const hash = (value) => createHash('sha256').update(value).digest('hex').slice(0, 24)
|
|
10
|
-
|
|
11
|
-
function sortedSccs(adjacency) {
|
|
12
|
-
return findSccs(adjacency)
|
|
13
|
-
.map((members) => members.map((member) => repoRelativePath(member)).filter(Boolean).sort(compareText))
|
|
14
|
-
.filter((members) => members.length > 1)
|
|
15
|
-
.sort((a, b) => b.length - a.length || compareText(a.join('\0'), b.join('\0')))
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function cycleFact(kind, adjacency, members) {
|
|
19
|
-
const representative = representativeCycle(adjacency, members).map((member) => repoRelativePath(member)).filter(Boolean)
|
|
20
|
-
return {
|
|
21
|
-
id: hash(`${kind}\0${members.join('\0')}`),
|
|
22
|
-
kind,
|
|
23
|
-
size: members.length,
|
|
24
|
-
members: members.slice(0, MAX_CYCLE_MEMBERS),
|
|
25
|
-
membersTruncated: members.length > MAX_CYCLE_MEMBERS,
|
|
26
|
-
representativePath: representative.slice(0, MAX_CYCLE_MEMBERS + 1),
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function readRules(repoRoot) {
|
|
31
|
-
try {
|
|
32
|
-
const boundary = createRepoBoundary(repoRoot)
|
|
33
|
-
const resolved = boundary.resolve('.weavatrix-deps.json')
|
|
34
|
-
if (!resolved.ok) return {rules: {}, state: 'NOT_APPLICABLE'}
|
|
35
|
-
const parsed = JSON.parse(readFileSync(resolved.path, 'utf8'))
|
|
36
|
-
const hasRules = Array.isArray(parsed?.forbidden) && parsed.forbidden.length > 0 ||
|
|
37
|
-
Array.isArray(parsed?.allowedOnly) && parsed.allowedOnly.length > 0
|
|
38
|
-
return {rules: parsed && typeof parsed === 'object' ? parsed : {}, state: hasRules ? 'COMPLETE' : 'NOT_APPLICABLE'}
|
|
39
|
-
} catch {
|
|
40
|
-
return {rules: {}, state: 'ERROR'}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function boundaryFact(value) {
|
|
45
|
-
const from = repoRelativePath(value?.from), to = repoRelativePath(value?.to)
|
|
46
|
-
if (!from || !to || !['forbidden', 'allowedOnly'].includes(value?.kind)) return null
|
|
47
|
-
const name = safeToken(value?.name, 96)
|
|
48
|
-
return {
|
|
49
|
-
kind: value.kind,
|
|
50
|
-
ruleId: name || hash(String(value?.name || 'unnamed-rule')),
|
|
51
|
-
severity: SEVERITIES.has(value?.severity) ? value.severity : 'medium',
|
|
52
|
-
from,
|
|
53
|
-
to,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function buildStructureEvidence(graph, repoRoot) {
|
|
58
|
-
try {
|
|
59
|
-
const imports = buildFileImportGraph(graph)
|
|
60
|
-
const runtimeSccs = sortedSccs(imports.runtimeAdj)
|
|
61
|
-
const runtimeKeys = new Set(runtimeSccs.map((members) => members.join('\0')))
|
|
62
|
-
// Same suppression as computeStructureFindings: a synced snapshot must not contradict the
|
|
63
|
-
// local structure findings on idiomatic Rust module trees.
|
|
64
|
-
const compileSccs = sortedSccs(imports.allAdj)
|
|
65
|
-
.filter((members) => !runtimeKeys.has(members.join('\0')) && !isRustModuleTreeComponent(members))
|
|
66
|
-
const allCycles = [
|
|
67
|
-
...runtimeSccs.map((members) => cycleFact('runtime', imports.runtimeAdj, members)),
|
|
68
|
-
...compileSccs.map((members) => cycleFact('compile-time', imports.allAdj, members)),
|
|
69
|
-
].sort((a, b) => a.kind === b.kind ? b.size - a.size || compareText(a.id, b.id) : a.kind === 'runtime' ? -1 : 1)
|
|
70
|
-
const cycles = bounded(allCycles, CAPS.architectureFindings)
|
|
71
|
-
const rules = readRules(repoRoot)
|
|
72
|
-
const allBoundaries = checkBoundaries(imports.runtimeEdges, rules.rules)
|
|
73
|
-
.map(boundaryFact).filter(Boolean)
|
|
74
|
-
.sort((a, b) => compareText(a.ruleId, b.ruleId) || compareText(a.from, b.from) || compareText(a.to, b.to))
|
|
75
|
-
const boundaries = bounded(allBoundaries, CAPS.architectureFindings)
|
|
76
|
-
return {state: 'COMPLETE', rulesState: rules.state, cycles, boundaries}
|
|
77
|
-
} catch {
|
|
78
|
-
return {
|
|
79
|
-
state: 'ERROR', rulesState: 'ERROR',
|
|
80
|
-
cycles: {items: [], completeness: {total: 0, returned: 0, truncated: false}},
|
|
81
|
-
boundaries: {items: [], completeness: {total: 0, returned: 0, truncated: false}},
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|