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,209 +0,0 @@
|
|
|
1
|
-
import {compareText, safeToken, STATE} from '../evidence-snapshot.common.mjs'
|
|
2
|
-
import {
|
|
3
|
-
MAX_DEPENDENCY_DECLARATIONS, MAX_PACKAGE_RECORDS, PACKAGE_NAME,
|
|
4
|
-
dependencyDeclarations, emptyDeclarations, emptyGraph, finalizeGraph,
|
|
5
|
-
packageId, packageVersion,
|
|
6
|
-
} from './package-graph-common.mjs'
|
|
7
|
-
|
|
8
|
-
// Bun >= 1.2 writes JSONC: JSON plus comments and trailing commas.
|
|
9
|
-
export function parseJsonc(text) {
|
|
10
|
-
const source = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text
|
|
11
|
-
const out = []
|
|
12
|
-
let i = 0
|
|
13
|
-
const length = source.length
|
|
14
|
-
const skipComment = (index) => {
|
|
15
|
-
if (source[index] !== '/') return index
|
|
16
|
-
if (source[index + 1] === '/') {
|
|
17
|
-
let cursor = index + 2
|
|
18
|
-
while (cursor < length && source[cursor] !== '\n') cursor++
|
|
19
|
-
return cursor
|
|
20
|
-
}
|
|
21
|
-
if (source[index + 1] === '*') {
|
|
22
|
-
let cursor = index + 2
|
|
23
|
-
while (cursor < length && !(source[cursor] === '*' && source[cursor + 1] === '/')) cursor++
|
|
24
|
-
return Math.min(cursor + 2, length)
|
|
25
|
-
}
|
|
26
|
-
return index
|
|
27
|
-
}
|
|
28
|
-
while (i < length) {
|
|
29
|
-
const char = source[i]
|
|
30
|
-
if (char === '"') {
|
|
31
|
-
const start = i
|
|
32
|
-
i++
|
|
33
|
-
while (i < length && source[i] !== '"') i += source[i] === '\\' ? 2 : 1
|
|
34
|
-
i = Math.min(i + 1, length)
|
|
35
|
-
out.push(source.slice(start, i))
|
|
36
|
-
continue
|
|
37
|
-
}
|
|
38
|
-
if (char === '/') {
|
|
39
|
-
const next = skipComment(i)
|
|
40
|
-
if (next !== i) { i = next; continue }
|
|
41
|
-
}
|
|
42
|
-
if (char === ',') {
|
|
43
|
-
let ahead = i + 1
|
|
44
|
-
while (ahead < length) {
|
|
45
|
-
if (/\s/.test(source[ahead])) { ahead++; continue }
|
|
46
|
-
const next = skipComment(ahead)
|
|
47
|
-
if (next !== ahead) { ahead = next; continue }
|
|
48
|
-
break
|
|
49
|
-
}
|
|
50
|
-
if (ahead < length && (source[ahead] === '}' || source[ahead] === ']')) { i++; continue }
|
|
51
|
-
}
|
|
52
|
-
out.push(char)
|
|
53
|
-
i++
|
|
54
|
-
}
|
|
55
|
-
return JSON.parse(out.join(''))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function bunKeySegments(key) {
|
|
59
|
-
if (typeof key !== 'string' || key.length === 0 || key.length > 4096 || /[\u0000-\u001f\u007f]/.test(key)) return null
|
|
60
|
-
const parts = key.split('/')
|
|
61
|
-
const segments = []
|
|
62
|
-
for (let index = 0; index < parts.length; index++) {
|
|
63
|
-
let segment = parts[index]
|
|
64
|
-
if (segment.startsWith('@')) {
|
|
65
|
-
if (index + 1 >= parts.length) return null
|
|
66
|
-
segment = `${segment}/${parts[++index]}`
|
|
67
|
-
}
|
|
68
|
-
if (segment.length > 256 || !PACKAGE_NAME.test(segment)) return null
|
|
69
|
-
segments.push(segment)
|
|
70
|
-
}
|
|
71
|
-
return segments
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function bunPackageRecord(entry) {
|
|
75
|
-
if (!Array.isArray(entry) || typeof entry[0] !== 'string' || entry[0].length > 1024) return null
|
|
76
|
-
const at = entry[0].lastIndexOf('@')
|
|
77
|
-
if (at <= 0) return null
|
|
78
|
-
const name = entry[0].slice(0, at)
|
|
79
|
-
if (name.length > 256 || !PACKAGE_NAME.test(name)) return null
|
|
80
|
-
const rawVersion = entry[0].slice(at + 1)
|
|
81
|
-
const meta = entry.slice(1).find((value) => value && typeof value === 'object' && !Array.isArray(value)) || {}
|
|
82
|
-
return {name, rawVersion, workspace: rawVersion.startsWith('workspace:'), meta}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function bunDeclarationRecord(meta) {
|
|
86
|
-
if (!meta || typeof meta !== 'object' || Array.isArray(meta)) return {}
|
|
87
|
-
const peerDependenciesMeta = {}
|
|
88
|
-
if (Array.isArray(meta.optionalPeers)) {
|
|
89
|
-
for (const name of meta.optionalPeers) if (typeof name === 'string') peerDependenciesMeta[name] = {optional: true}
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
dependencies: meta.dependencies,
|
|
93
|
-
devDependencies: meta.devDependencies,
|
|
94
|
-
optionalDependencies: meta.optionalDependencies,
|
|
95
|
-
peerDependencies: meta.peerDependencies,
|
|
96
|
-
peerDependenciesMeta,
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function resolveBunDependency(sourceSegments, name, records) {
|
|
101
|
-
for (let depth = sourceSegments.length; depth >= 0; depth--) {
|
|
102
|
-
const key = [...sourceSegments.slice(0, depth), name].join('/')
|
|
103
|
-
const record = records.get(key)
|
|
104
|
-
if (record) return {key, record}
|
|
105
|
-
}
|
|
106
|
-
return null
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function applyBunReachabilityFlags(nodes, edges) {
|
|
110
|
-
const adjacency = new Map()
|
|
111
|
-
for (const edge of edges) {
|
|
112
|
-
if (!adjacency.has(edge.from)) adjacency.set(edge.from, [])
|
|
113
|
-
adjacency.get(edge.from).push(edge)
|
|
114
|
-
}
|
|
115
|
-
const reach = (excluded) => {
|
|
116
|
-
const seen = new Set(['(root)'])
|
|
117
|
-
const queue = ['(root)']
|
|
118
|
-
while (queue.length) {
|
|
119
|
-
for (const edge of adjacency.get(queue.pop()) || []) {
|
|
120
|
-
if (excluded.has(edge.kind) || seen.has(edge.to)) continue
|
|
121
|
-
seen.add(edge.to)
|
|
122
|
-
queue.push(edge.to)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return seen
|
|
126
|
-
}
|
|
127
|
-
const withoutDev = reach(new Set(['dev']))
|
|
128
|
-
const withoutOptional = reach(new Set(['optional', 'optional-peer']))
|
|
129
|
-
const withoutPeer = reach(new Set(['peer', 'optional-peer']))
|
|
130
|
-
for (const node of nodes) {
|
|
131
|
-
node.dev = !withoutDev.has(node.id)
|
|
132
|
-
node.optional = !withoutOptional.has(node.id)
|
|
133
|
-
node.peer = !withoutPeer.has(node.id)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function parseBunLock(lock, lockfile) {
|
|
138
|
-
const lockfileVersion = Number(lock?.lockfileVersion)
|
|
139
|
-
const extras = {lockfile, lockfileVersion: Number.isFinite(lockfileVersion) ? Math.trunc(lockfileVersion) : 0}
|
|
140
|
-
if (!lock?.packages || typeof lock.packages !== 'object' || Array.isArray(lock.packages)) {
|
|
141
|
-
return emptyGraph(STATE.PARTIAL, 'BUN_LOCK_PACKAGES_REQUIRED', extras)
|
|
142
|
-
}
|
|
143
|
-
const allKeys = Object.keys(lock.packages).sort(compareText)
|
|
144
|
-
const selectedKeys = allKeys.slice(0, MAX_PACKAGE_RECORDS)
|
|
145
|
-
const reasons = []
|
|
146
|
-
if (selectedKeys.length < allKeys.length) reasons.push('LOCKFILE_PACKAGE_RECORD_LIMIT_REACHED')
|
|
147
|
-
const records = new Map()
|
|
148
|
-
let invalidRecords = 0
|
|
149
|
-
for (const key of selectedKeys) {
|
|
150
|
-
const segments = bunKeySegments(key)
|
|
151
|
-
const record = segments ? bunPackageRecord(lock.packages[key]) : null
|
|
152
|
-
if (!record) { invalidRecords++; continue }
|
|
153
|
-
records.set(key, {...record, segments})
|
|
154
|
-
}
|
|
155
|
-
if (invalidRecords > 0) reasons.push('INVALID_LOCKFILE_PACKAGE_RECORDS')
|
|
156
|
-
const externalNodes = new Map()
|
|
157
|
-
let invalidPackageVersions = 0
|
|
158
|
-
for (const [key, record] of records) {
|
|
159
|
-
if (record.workspace) continue
|
|
160
|
-
const version = packageVersion(record.rawVersion)
|
|
161
|
-
if (!version) { invalidPackageVersions++; continue }
|
|
162
|
-
externalNodes.set(key, {
|
|
163
|
-
id: packageId(record.name, version, key), name: record.name, version,
|
|
164
|
-
direct: false, dev: false, optional: false, peer: false,
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
if (invalidPackageVersions > 0) reasons.push('INVALID_LOCKFILE_PACKAGE_VERSIONS')
|
|
168
|
-
const workspaces = lock?.workspaces && typeof lock.workspaces === 'object' && !Array.isArray(lock.workspaces)
|
|
169
|
-
? lock.workspaces : null
|
|
170
|
-
if (!workspaces) reasons.push('BUN_LOCK_WORKSPACES_MISSING')
|
|
171
|
-
const sources = []
|
|
172
|
-
for (const path of Object.keys(workspaces || {}).sort(compareText)) {
|
|
173
|
-
const meta = workspaces[path]
|
|
174
|
-
if (!meta || typeof meta !== 'object' || Array.isArray(meta)) continue
|
|
175
|
-
const workspaceName = path === '' ? null : safeToken(meta.name)
|
|
176
|
-
const segments = workspaceName && records.has(workspaceName) ? [workspaceName] : []
|
|
177
|
-
sources.push({sourceId: '(root)', segments, declarationRecord: bunDeclarationRecord(meta)})
|
|
178
|
-
}
|
|
179
|
-
for (const [key, record] of [...records].sort(([a], [b]) => compareText(a, b))) {
|
|
180
|
-
const node = externalNodes.get(key)
|
|
181
|
-
if (node) sources.push({sourceId: node.id, segments: record.segments, declarationRecord: bunDeclarationRecord(record.meta)})
|
|
182
|
-
}
|
|
183
|
-
const edgeMap = new Map()
|
|
184
|
-
const declarations = emptyDeclarations()
|
|
185
|
-
let declarationLimitReached = false
|
|
186
|
-
outer: for (const source of sources) {
|
|
187
|
-
for (const declaration of dependencyDeclarations(source.declarationRecord)) {
|
|
188
|
-
if (declarations.total >= MAX_DEPENDENCY_DECLARATIONS) { declarationLimitReached = true; break outer }
|
|
189
|
-
declarations.total++
|
|
190
|
-
const resolved = resolveBunDependency(source.segments, declaration.name, records)
|
|
191
|
-
if (resolved?.record.workspace) { declarations.local++; continue }
|
|
192
|
-
const node = resolved ? externalNodes.get(resolved.key) : null
|
|
193
|
-
if (!node) {
|
|
194
|
-
if (declaration.kind === 'optional' || declaration.kind === 'optional-peer') declarations.optionalMissing++
|
|
195
|
-
else declarations.unresolved++
|
|
196
|
-
continue
|
|
197
|
-
}
|
|
198
|
-
declarations.resolved++
|
|
199
|
-
if (source.sourceId === '(root)') node.direct = true
|
|
200
|
-
if (source.sourceId === node.id) continue
|
|
201
|
-
const edge = {from: source.sourceId, to: node.id, kind: declaration.kind}
|
|
202
|
-
edgeMap.set(`${edge.from}\0${edge.to}\0${edge.kind}`, edge)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (declarationLimitReached) reasons.push('LOCKFILE_DEPENDENCY_DECLARATION_LIMIT_REACHED')
|
|
206
|
-
if (declarations.unresolved > 0) reasons.push('UNRESOLVED_LOCKFILE_DEPENDENCIES')
|
|
207
|
-
applyBunReachabilityFlags(externalNodes.values(), edgeMap.values())
|
|
208
|
-
return finalizeGraph({externalNodes, edgeMap, declarations, reasons, lockfile, lockfileVersion: extras.lockfileVersion})
|
|
209
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
const compareText = (left, right) => String(left).localeCompare(String(right), 'en')
|
|
2
|
-
|
|
3
|
-
// Snapshot construction and source-free payload validation must use the same stable
|
|
4
|
-
// member order or equivalent evidence can hash differently across the boundary.
|
|
5
|
-
export function compareDuplicateMember(left, right) {
|
|
6
|
-
return compareText(left.file, right.file) || left.startLine - right.startLine ||
|
|
7
|
-
left.endLine - right.endLine || compareText(left.graphNodeId || '', right.graphNodeId || '')
|
|
8
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import {createHash} from 'node:crypto'
|
|
2
|
-
import {CAPS, STATE, compareText, safeToken} from '../evidence-snapshot.common.mjs'
|
|
3
|
-
|
|
4
|
-
export const MAX_LOCKFILE_BYTES = 64 * 1024 * 1024
|
|
5
|
-
export const MAX_PACKAGE_RECORDS = 50_000
|
|
6
|
-
export const MAX_DEPENDENCY_DECLARATIONS = 200_000
|
|
7
|
-
export const PACKAGE_NAME = /^(?:@[a-z0-9._-]+\/)?[a-z0-9][a-z0-9._-]*$/i
|
|
8
|
-
const PACKAGE_VERSION = /^[A-Za-z0-9][A-Za-z0-9._+~-]*$/
|
|
9
|
-
|
|
10
|
-
export const packageVersion = (value) => {
|
|
11
|
-
const version = safeToken(value, 128)
|
|
12
|
-
return version && PACKAGE_VERSION.test(version) ? version : null
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const emptyDeclarations = () => ({total: 0, resolved: 0, unresolved: 0, local: 0, optionalMissing: 0})
|
|
16
|
-
|
|
17
|
-
export function emptyGraph(state, reason, extras = {}) {
|
|
18
|
-
const emptyCount = () => ({total: 0, returned: 0, truncated: false})
|
|
19
|
-
return {
|
|
20
|
-
state,
|
|
21
|
-
ecosystem: 'npm',
|
|
22
|
-
root: '(root)',
|
|
23
|
-
...extras,
|
|
24
|
-
completeness: {
|
|
25
|
-
nodes: emptyCount(),
|
|
26
|
-
edges: emptyCount(),
|
|
27
|
-
declarations: emptyDeclarations(),
|
|
28
|
-
reasons: reason ? [reason] : [],
|
|
29
|
-
},
|
|
30
|
-
nodes: [],
|
|
31
|
-
edges: [],
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function normalizeLockPath(value) {
|
|
36
|
-
if (typeof value !== 'string' || value.length > 4096 || /[\u0000-\u001f\u007f]/.test(value)) return null
|
|
37
|
-
const raw = value.replace(/\\/g, '/')
|
|
38
|
-
if (/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(raw)) return null
|
|
39
|
-
const normalized = raw.replace(/\/$/, '')
|
|
40
|
-
if (normalized === '') return ''
|
|
41
|
-
const parts = normalized.split('/')
|
|
42
|
-
return parts.every((part) => part && part !== '.' && part !== '..') ? normalized : null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function packageNameFromPath(packagePath) {
|
|
46
|
-
const match = packagePath.match(/(?:^|\/)node_modules\/((?:@[^/]+\/)?[^/]+)$/)
|
|
47
|
-
const name = match?.[1]
|
|
48
|
-
return name && name.length <= 256 && PACKAGE_NAME.test(name) ? name : null
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function packageId(name, version, packagePath) {
|
|
52
|
-
const location = createHash('sha256').update(packagePath).digest('hex').slice(0, 12)
|
|
53
|
-
return `npm:${name}@${version}:${location}`
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function parentInstallPath(packagePath) {
|
|
57
|
-
const marker = packagePath.lastIndexOf('/node_modules/')
|
|
58
|
-
return marker >= 0 ? packagePath.slice(0, marker) : ''
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function dependencyDeclarations(record) {
|
|
62
|
-
const declarations = new Map()
|
|
63
|
-
const add = (values, kind, replace = false) => {
|
|
64
|
-
if (!values || typeof values !== 'object' || Array.isArray(values)) return
|
|
65
|
-
for (const name of Object.keys(values)) {
|
|
66
|
-
if (name.length > 256 || !PACKAGE_NAME.test(name)) continue
|
|
67
|
-
if (replace || !declarations.has(name)) declarations.set(name, kind)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
add(record?.dependencies, 'runtime')
|
|
71
|
-
add(record?.devDependencies, 'dev')
|
|
72
|
-
add(record?.optionalDependencies, 'optional', true)
|
|
73
|
-
if (record?.peerDependencies && typeof record.peerDependencies === 'object' && !Array.isArray(record.peerDependencies)) {
|
|
74
|
-
for (const name of Object.keys(record.peerDependencies)) {
|
|
75
|
-
if (name.length > 256 || !PACKAGE_NAME.test(name) || declarations.has(name)) continue
|
|
76
|
-
const optional = record?.peerDependenciesMeta?.[name]?.optional === true
|
|
77
|
-
declarations.set(name, optional ? 'optional-peer' : 'peer')
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return [...declarations].map(([name, kind]) => ({name, kind}))
|
|
81
|
-
.sort((a, b) => compareText(a.name, b.name) || compareText(a.kind, b.kind))
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function finalizeGraph({externalNodes, edgeMap, declarations, reasons, lockfile, lockfileVersion}) {
|
|
85
|
-
const allNodes = [...externalNodes.values()].sort((a, b) =>
|
|
86
|
-
Number(b.direct) - Number(a.direct) || compareText(a.name, b.name) ||
|
|
87
|
-
compareText(a.version, b.version) || compareText(a.id, b.id))
|
|
88
|
-
const nodes = allNodes.slice(0, CAPS.packageGraphNodes)
|
|
89
|
-
const nodeIds = new Set(nodes.map((node) => node.id))
|
|
90
|
-
const allEdges = [...edgeMap.values()].sort((a, b) =>
|
|
91
|
-
compareText(a.from, b.from) || compareText(a.to, b.to) || compareText(a.kind, b.kind))
|
|
92
|
-
const eligibleEdges = allEdges.filter((edge) =>
|
|
93
|
-
(edge.from === '(root)' || nodeIds.has(edge.from)) && nodeIds.has(edge.to))
|
|
94
|
-
const edges = eligibleEdges.slice(0, CAPS.packageGraphEdges)
|
|
95
|
-
const nodesTruncated = nodes.length < allNodes.length
|
|
96
|
-
const edgesTruncated = edges.length < allEdges.length
|
|
97
|
-
if (nodesTruncated) reasons.push('PACKAGE_NODE_LIMIT_REACHED')
|
|
98
|
-
if (edgesTruncated) reasons.push('PACKAGE_EDGE_LIMIT_REACHED')
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
state: reasons.length ? STATE.PARTIAL : STATE.COMPLETE,
|
|
102
|
-
ecosystem: 'npm',
|
|
103
|
-
lockfile,
|
|
104
|
-
lockfileVersion,
|
|
105
|
-
root: '(root)',
|
|
106
|
-
completeness: {
|
|
107
|
-
nodes: {total: allNodes.length, returned: nodes.length, truncated: nodesTruncated},
|
|
108
|
-
edges: {total: allEdges.length, returned: edges.length, truncated: edgesTruncated},
|
|
109
|
-
declarations,
|
|
110
|
-
reasons: [...new Set(reasons)].sort(compareText),
|
|
111
|
-
},
|
|
112
|
-
nodes,
|
|
113
|
-
edges,
|
|
114
|
-
}
|
|
115
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import {compareText, STATE} from '../evidence-snapshot.common.mjs'
|
|
2
|
-
import {
|
|
3
|
-
MAX_DEPENDENCY_DECLARATIONS, MAX_PACKAGE_RECORDS, dependencyDeclarations,
|
|
4
|
-
emptyDeclarations, emptyGraph, finalizeGraph, normalizeLockPath, packageId,
|
|
5
|
-
packageNameFromPath, packageVersion, parentInstallPath,
|
|
6
|
-
} from './package-graph-common.mjs'
|
|
7
|
-
|
|
8
|
-
function resolveDependency(sourcePath, name, records, externalNodes) {
|
|
9
|
-
let cursor = sourcePath
|
|
10
|
-
const seen = new Set()
|
|
11
|
-
while (!seen.has(cursor)) {
|
|
12
|
-
seen.add(cursor)
|
|
13
|
-
const candidate = cursor ? `${cursor}/node_modules/${name}` : `node_modules/${name}`
|
|
14
|
-
const record = records.get(candidate)
|
|
15
|
-
if (record) {
|
|
16
|
-
if (record.link === true) return {kind: 'local'}
|
|
17
|
-
const node = externalNodes.get(candidate)
|
|
18
|
-
return node ? {kind: 'external', node} : {kind: 'unresolved'}
|
|
19
|
-
}
|
|
20
|
-
if (!cursor) break
|
|
21
|
-
cursor = parentInstallPath(cursor)
|
|
22
|
-
}
|
|
23
|
-
return {kind: 'unresolved'}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function parsePackageLock(lock, lockfile) {
|
|
27
|
-
const lockfileVersion = Number(lock?.lockfileVersion || 0)
|
|
28
|
-
const extras = {lockfile, lockfileVersion: Number.isFinite(lockfileVersion) ? Math.trunc(lockfileVersion) : 0}
|
|
29
|
-
if (![2, 3].includes(lockfileVersion) || !lock?.packages || typeof lock.packages !== 'object' || Array.isArray(lock.packages)) {
|
|
30
|
-
return emptyGraph(STATE.PARTIAL, 'PACKAGE_LOCK_V2_V3_REQUIRED', extras)
|
|
31
|
-
}
|
|
32
|
-
const allKeys = Object.keys(lock.packages).sort(compareText)
|
|
33
|
-
const selectedKeys = allKeys.slice(0, MAX_PACKAGE_RECORDS)
|
|
34
|
-
const reasons = []
|
|
35
|
-
if (selectedKeys.length < allKeys.length) reasons.push('LOCKFILE_PACKAGE_RECORD_LIMIT_REACHED')
|
|
36
|
-
const records = new Map()
|
|
37
|
-
let invalidRecords = 0
|
|
38
|
-
for (const rawPath of selectedKeys) {
|
|
39
|
-
const packagePath = normalizeLockPath(rawPath)
|
|
40
|
-
const record = lock.packages[rawPath]
|
|
41
|
-
if (packagePath == null || !record || typeof record !== 'object' || Array.isArray(record) ||
|
|
42
|
-
((packagePath.startsWith('node_modules/') || packagePath.includes('/node_modules/')) && !packageNameFromPath(packagePath)) || records.has(packagePath)) {
|
|
43
|
-
invalidRecords++
|
|
44
|
-
continue
|
|
45
|
-
}
|
|
46
|
-
records.set(packagePath, record)
|
|
47
|
-
}
|
|
48
|
-
if (invalidRecords > 0) reasons.push('INVALID_LOCKFILE_PACKAGE_RECORDS')
|
|
49
|
-
const externalNodes = new Map()
|
|
50
|
-
let invalidPackageVersions = 0
|
|
51
|
-
for (const [packagePath, record] of records) {
|
|
52
|
-
const name = packageNameFromPath(packagePath)
|
|
53
|
-
const version = packageVersion(record.version)
|
|
54
|
-
if (!name || record.link === true) continue
|
|
55
|
-
if (!version) { invalidPackageVersions++; continue }
|
|
56
|
-
externalNodes.set(packagePath, {
|
|
57
|
-
id: packageId(name, version, packagePath), name, version, direct: false,
|
|
58
|
-
dev: record.dev === true || record.devOptional === true,
|
|
59
|
-
optional: record.optional === true || record.devOptional === true,
|
|
60
|
-
peer: record.peer === true,
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
if (invalidPackageVersions > 0) reasons.push('INVALID_LOCKFILE_PACKAGE_VERSIONS')
|
|
64
|
-
const edgeMap = new Map()
|
|
65
|
-
const declarations = emptyDeclarations()
|
|
66
|
-
let declarationLimitReached = false
|
|
67
|
-
const sources = [...records].filter(([packagePath]) => packageNameFromPath(packagePath) == null || externalNodes.has(packagePath))
|
|
68
|
-
.sort(([a], [b]) => compareText(a, b))
|
|
69
|
-
outer: for (const [sourcePath, record] of sources) {
|
|
70
|
-
const sourceNode = externalNodes.get(sourcePath)
|
|
71
|
-
const sourceId = sourceNode?.id || '(root)'
|
|
72
|
-
for (const declaration of dependencyDeclarations(record)) {
|
|
73
|
-
if (declarations.total >= MAX_DEPENDENCY_DECLARATIONS) { declarationLimitReached = true; break outer }
|
|
74
|
-
declarations.total++
|
|
75
|
-
const resolved = resolveDependency(sourcePath, declaration.name, records, externalNodes)
|
|
76
|
-
if (resolved.kind === 'local') { declarations.local++; continue }
|
|
77
|
-
if (resolved.kind !== 'external') {
|
|
78
|
-
if (declaration.kind === 'optional' || declaration.kind === 'optional-peer') declarations.optionalMissing++
|
|
79
|
-
else declarations.unresolved++
|
|
80
|
-
continue
|
|
81
|
-
}
|
|
82
|
-
declarations.resolved++
|
|
83
|
-
if (sourceId === '(root)') resolved.node.direct = true
|
|
84
|
-
if (sourceId === resolved.node.id) continue
|
|
85
|
-
const edge = {from: sourceId, to: resolved.node.id, kind: declaration.kind}
|
|
86
|
-
edgeMap.set(`${edge.from}\0${edge.to}\0${edge.kind}`, edge)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (declarationLimitReached) reasons.push('LOCKFILE_DEPENDENCY_DECLARATION_LIMIT_REACHED')
|
|
90
|
-
if (declarations.unresolved > 0) reasons.push('UNRESOLVED_LOCKFILE_DEPENDENCIES')
|
|
91
|
-
return finalizeGraph({externalNodes, edgeMap, declarations, reasons, lockfile, lockfileVersion})
|
|
92
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CAPS, STATE, VERDICT, bounded, compareText, moduleId, nonNegativeInteger,
|
|
3
|
-
} from './evidence-snapshot.common.mjs'
|
|
4
|
-
|
|
5
|
-
function sanitizeModule(value) {
|
|
6
|
-
const id = moduleId(value?.name)
|
|
7
|
-
if (!id) return null
|
|
8
|
-
return {
|
|
9
|
-
id,
|
|
10
|
-
fileCount: nonNegativeInteger(value.fileCount),
|
|
11
|
-
nodeCount: nonNegativeInteger(value.nodeCount),
|
|
12
|
-
symbolCount: nonNegativeInteger(value.symbolCount),
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function sanitizeModuleDependency(value) {
|
|
17
|
-
const from = moduleId(value?.from)
|
|
18
|
-
const to = moduleId(value?.to)
|
|
19
|
-
if (!from || !to || from === to) return null
|
|
20
|
-
return {from, to, count: nonNegativeInteger(value.count)}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function sortedModuleDependencies(values) {
|
|
24
|
-
return (Array.isArray(values) ? values : [])
|
|
25
|
-
.map(sanitizeModuleDependency)
|
|
26
|
-
.filter(Boolean)
|
|
27
|
-
.sort((a, b) => compareText(a.from, b.from) || compareText(a.to, b.to) || a.count - b.count)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function buildArchitectureSection(graph, aggregate, audit, structure) {
|
|
31
|
-
if (!aggregate) {
|
|
32
|
-
return {
|
|
33
|
-
state: STATE.ERROR,
|
|
34
|
-
verdict: VERDICT.UNKNOWN,
|
|
35
|
-
completeness: {reasons: ['GRAPH_AGGREGATION_ERROR']},
|
|
36
|
-
modules: [],
|
|
37
|
-
dependencies: {runtime: [], typeOnly: [], compileOnly: []},
|
|
38
|
-
cycles: [],
|
|
39
|
-
boundaryViolations: [],
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const modules = bounded(
|
|
44
|
-
(aggregate.modules || []).map(sanitizeModule).filter(Boolean).sort((a, b) => compareText(a.id, b.id)),
|
|
45
|
-
CAPS.modules,
|
|
46
|
-
)
|
|
47
|
-
const runtime = bounded(sortedModuleDependencies(aggregate.moduleEdges), CAPS.moduleDependencies)
|
|
48
|
-
const typeOnly = bounded(sortedModuleDependencies(aggregate.typeOnlyModuleEdges), CAPS.moduleDependencies)
|
|
49
|
-
const compileOnly = bounded(sortedModuleDependencies(aggregate.compileOnlyModuleEdges), CAPS.moduleDependencies)
|
|
50
|
-
const cycles = structure?.cycles || bounded([], CAPS.architectureFindings)
|
|
51
|
-
const boundaries = structure?.boundaries || bounded([], CAPS.architectureFindings)
|
|
52
|
-
|
|
53
|
-
const edgeTypesComplete = Number.isInteger(graph?.edgeTypesV) && graph.edgeTypesV >= 2
|
|
54
|
-
const auditComplete = audit?.ok === true
|
|
55
|
-
const structureComplete = structure?.state === STATE.COMPLETE
|
|
56
|
-
const state = edgeTypesComplete && auditComplete && structureComplete ? STATE.COMPLETE : STATE.PARTIAL
|
|
57
|
-
const fails = cycles.items.some((cycle) => cycle.kind === 'runtime') || boundaries.items.length > 0
|
|
58
|
-
return {
|
|
59
|
-
state,
|
|
60
|
-
verdict: fails ? VERDICT.FAIL : state === STATE.COMPLETE ? VERDICT.PASS : VERDICT.UNKNOWN,
|
|
61
|
-
completeness: {
|
|
62
|
-
modules: modules.completeness,
|
|
63
|
-
runtimeDependencies: runtime.completeness,
|
|
64
|
-
typeOnlyDependencies: typeOnly.completeness,
|
|
65
|
-
compileOnlyDependencies: compileOnly.completeness,
|
|
66
|
-
cycles: cycles.completeness,
|
|
67
|
-
boundaryViolations: boundaries.completeness,
|
|
68
|
-
reasons: [
|
|
69
|
-
...(!edgeTypesComplete ? ['EDGE_TYPES_V2_REQUIRED'] : []),
|
|
70
|
-
...(!auditComplete ? ['AUDIT_UNAVAILABLE'] : []),
|
|
71
|
-
...(!structureComplete ? ['STRUCTURE_EVIDENCE_UNAVAILABLE'] : []),
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
modules: modules.items,
|
|
75
|
-
dependencies: {runtime: runtime.items, typeOnly: typeOnly.items, compileOnly: compileOnly.items},
|
|
76
|
-
cycles: cycles.items,
|
|
77
|
-
boundaryViolations: boundaries.items,
|
|
78
|
-
boundaryRulesState: structure?.rulesState || STATE.ERROR,
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import {createHash} from 'node:crypto'
|
|
2
|
-
|
|
3
|
-
export const STATE = Object.freeze({
|
|
4
|
-
COMPLETE: 'COMPLETE',
|
|
5
|
-
PARTIAL: 'PARTIAL',
|
|
6
|
-
NOT_CHECKED: 'NOT_CHECKED',
|
|
7
|
-
NOT_APPLICABLE: 'NOT_APPLICABLE',
|
|
8
|
-
ERROR: 'ERROR',
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
export const VERDICT = Object.freeze({PASS: 'PASS', FAIL: 'FAIL', UNKNOWN: 'UNKNOWN'})
|
|
12
|
-
|
|
13
|
-
export const CAPS = Object.freeze({
|
|
14
|
-
modules: 500,
|
|
15
|
-
moduleDependencies: 2_000,
|
|
16
|
-
architectureFindings: 150,
|
|
17
|
-
findings: 500,
|
|
18
|
-
hotspots: 250,
|
|
19
|
-
stackBadges: 100,
|
|
20
|
-
packages: 5_000,
|
|
21
|
-
directUsage: 1_000,
|
|
22
|
-
usageFiles: 20,
|
|
23
|
-
packageGraphNodes: 5_000,
|
|
24
|
-
packageGraphEdges: 20_000,
|
|
25
|
-
duplicateGroups: 100,
|
|
26
|
-
duplicateMembers: 12,
|
|
27
|
-
divergenceCandidates: 100,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
export const COMPLEXITY_THRESHOLDS = Object.freeze({
|
|
31
|
-
loc: Object.freeze({warning: 120, high: 300}),
|
|
32
|
-
cyclomatic: Object.freeze({warning: 15, high: 30}),
|
|
33
|
-
params: Object.freeze({warning: 6, high: 10}),
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const CONTROL_CHARS = /[\u0000-\u001f\u007f]/
|
|
37
|
-
const ABSOLUTE_PATH_FRAGMENT = /(?:^|[\/\s"'`(=])[a-z]:[\\/]|(?:^|[\s"'`(=])(?:\\\\[^\\/\s]+(?:[\\/]|$)|file:(?:\/\/)?[\\/]|\/(?!\/)[^\s])/i
|
|
38
|
-
const SAFE_TOKEN = /^[A-Za-z0-9@][A-Za-z0-9._:+/@-]*$/
|
|
39
|
-
const SAFE_RULE = /^[a-z0-9][a-z0-9._-]*$/
|
|
40
|
-
const SAFE_FINDING_ID = /^[a-f0-9]{8,64}$/i
|
|
41
|
-
const SAFE_SEVERITY = new Set(['critical', 'high', 'medium', 'low', 'info'])
|
|
42
|
-
const SAFE_CONFIDENCE = new Set(['high', 'medium', 'low'])
|
|
43
|
-
const SAFE_CATEGORY = new Set(['unused', 'structure', 'vulnerability', 'malware'])
|
|
44
|
-
const SAFE_CHECK_STATE = new Set(Object.values(STATE))
|
|
45
|
-
|
|
46
|
-
export function metadataString(value, max = 512) {
|
|
47
|
-
return typeof value === 'string' && value.length > 0 && value.length <= max && !CONTROL_CHARS.test(value)
|
|
48
|
-
? value
|
|
49
|
-
: undefined
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function privacySafeText(value, max = 512) {
|
|
53
|
-
const text = metadataString(value, max)
|
|
54
|
-
return text && !ABSOLUTE_PATH_FRAGMENT.test(text) ? text : undefined
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function safeToken(value, max = 256) {
|
|
58
|
-
const text = metadataString(value, max)
|
|
59
|
-
return text && SAFE_TOKEN.test(text) ? text : undefined
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function repoRelativePath(value, max = 4096) {
|
|
63
|
-
const raw = metadataString(value, max)
|
|
64
|
-
if (!raw) return undefined
|
|
65
|
-
const path = raw.replace(/\\/g, '/')
|
|
66
|
-
if (/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(path)) return undefined
|
|
67
|
-
const segments = path.split('/')
|
|
68
|
-
if (!segments.length || segments.some((segment) => !segment || segment === '.' || segment === '..')) return undefined
|
|
69
|
-
return path
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function graphId(value) {
|
|
73
|
-
const id = metadataString(value, 4096)
|
|
74
|
-
if (!id) return undefined
|
|
75
|
-
const hash = id.indexOf('#')
|
|
76
|
-
const file = hash < 0 ? id : id.slice(0, hash)
|
|
77
|
-
return repoRelativePath(file) ? id.replace(/\\/g, '/') : undefined
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function moduleId(value) {
|
|
81
|
-
if (value === '(root)') return '(root)'
|
|
82
|
-
return repoRelativePath(value)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function nonNegativeInteger(value) {
|
|
86
|
-
return optionalNonNegativeInteger(value) ?? 0
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function optionalNonNegativeInteger(value) {
|
|
90
|
-
return typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
91
|
-
? Math.trunc(value)
|
|
92
|
-
: undefined
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function addIf(out, key, value) {
|
|
96
|
-
if (value !== undefined) out[key] = value
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function compareText(a, b) {
|
|
100
|
-
return String(a).localeCompare(String(b), 'en')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function bounded(sortedItems, cap) {
|
|
104
|
-
const items = sortedItems.slice(0, cap)
|
|
105
|
-
return {
|
|
106
|
-
items,
|
|
107
|
-
completeness: {
|
|
108
|
-
total: sortedItems.length,
|
|
109
|
-
returned: items.length,
|
|
110
|
-
truncated: sortedItems.length > items.length,
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function stableStringify(value) {
|
|
116
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`
|
|
117
|
-
if (value && typeof value === 'object') {
|
|
118
|
-
return `{${Object.keys(value).sort(compareText).map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`
|
|
119
|
-
}
|
|
120
|
-
return JSON.stringify(value)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function hashSnapshot(snapshot) {
|
|
124
|
-
return createHash('sha256').update(stableStringify(snapshot)).digest('hex')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function normalizeCheckState(value) {
|
|
128
|
-
if (value === 'OK') return STATE.COMPLETE
|
|
129
|
-
return SAFE_CHECK_STATE.has(value) ? value : STATE.ERROR
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function sanitizeFinding(value) {
|
|
133
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
134
|
-
const id = metadataString(value.id, 64)
|
|
135
|
-
const category = metadataString(value.category, 32)
|
|
136
|
-
const rule = metadataString(value.rule, 64)
|
|
137
|
-
const severity = metadataString(value.severity, 16)
|
|
138
|
-
if (!id || !SAFE_FINDING_ID.test(id) || !category || !SAFE_CATEGORY.has(category) ||
|
|
139
|
-
!rule || !SAFE_RULE.test(rule) || !severity || !SAFE_SEVERITY.has(severity)) return null
|
|
140
|
-
|
|
141
|
-
const out = {id, category, rule, severity}
|
|
142
|
-
const confidence = metadataString(value.confidence, 16)
|
|
143
|
-
if (confidence && SAFE_CONFIDENCE.has(confidence)) out.confidence = confidence
|
|
144
|
-
addIf(out, 'file', repoRelativePath(value.file))
|
|
145
|
-
const line = optionalNonNegativeInteger(value.line)
|
|
146
|
-
if (line !== undefined) out.line = line
|
|
147
|
-
addIf(out, 'symbol', privacySafeText(value.symbol, 256))
|
|
148
|
-
addIf(out, 'package', safeToken(value.package))
|
|
149
|
-
addIf(out, 'version', safeToken(value.version, 128))
|
|
150
|
-
addIf(out, 'graphNodeId', graphId(value.graphNodeId))
|
|
151
|
-
return out
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function numericRecord(value, keys) {
|
|
155
|
-
const out = {}
|
|
156
|
-
for (const key of keys) out[key] = nonNegativeInteger(value?.[key])
|
|
157
|
-
return out
|
|
158
|
-
}
|