weavatrix 0.2.13 → 0.2.15
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/README.md +99 -39
- package/SECURITY.md +2 -2
- package/docs/releases/v0.2.15.md +37 -0
- package/package.json +2 -2
- package/skill/SKILL.md +57 -38
- package/src/analysis/architecture/contract-graph.js +119 -0
- package/src/analysis/architecture/contract-schema.js +110 -0
- package/src/analysis/architecture/contract-storage.js +35 -0
- package/src/analysis/architecture/contract-verification.js +168 -0
- package/src/analysis/architecture-contract.js +16 -343
- package/src/analysis/change-classification/diff-parser.js +103 -0
- package/src/analysis/change-classification/options.js +40 -0
- package/src/analysis/change-classification/symbol-classifier.js +177 -0
- package/src/analysis/change-classification.js +120 -519
- package/src/analysis/dead-code-review/policy.js +23 -0
- package/src/analysis/dead-code-review.js +9 -19
- package/src/analysis/dep-check.js +14 -71
- package/src/analysis/dep-rules.js +10 -303
- package/src/analysis/dependency/conventions.js +16 -0
- package/src/analysis/dependency/scoped-dependencies.js +45 -0
- package/src/analysis/dependency/source-references.js +21 -0
- package/src/analysis/duplicates.tokenize.js +12 -2
- package/src/analysis/endpoints/common.js +62 -0
- package/src/analysis/endpoints/extract.js +107 -0
- package/src/analysis/endpoints/inventory.js +84 -0
- package/src/analysis/endpoints/mounts.js +129 -0
- package/src/analysis/endpoints-java.js +80 -3
- package/src/analysis/endpoints.js +3 -448
- package/src/analysis/git-history/analytics.js +177 -0
- package/src/analysis/git-history/collector.js +109 -0
- package/src/analysis/git-history/options.js +68 -0
- package/src/analysis/git-history.js +128 -577
- package/src/analysis/health-capabilities.js +82 -0
- package/src/analysis/http-contracts/analysis.js +169 -0
- package/src/analysis/http-contracts/client-call-detection.js +81 -0
- package/src/analysis/http-contracts/client-call-parser.js +255 -0
- package/src/analysis/http-contracts/graph-context.js +143 -0
- package/src/analysis/http-contracts/matching.js +61 -0
- package/src/analysis/http-contracts/shared.js +86 -0
- package/src/analysis/http-contracts.js +6 -822
- package/src/analysis/internal-audit/dependency-health.js +111 -0
- package/src/analysis/internal-audit/python-manifests.js +65 -0
- package/src/analysis/internal-audit/repo-files.js +55 -0
- package/src/analysis/internal-audit/supply-chain.js +132 -0
- package/src/analysis/internal-audit.collect.js +5 -106
- package/src/analysis/internal-audit.reach.js +20 -0
- package/src/analysis/internal-audit.run.js +114 -200
- package/src/analysis/jvm-dependency-evidence.js +69 -0
- package/src/analysis/source-correctness/retry-patterns.js +93 -0
- package/src/analysis/source-correctness.js +225 -0
- package/src/analysis/structure/dependency-graph.js +156 -0
- package/src/analysis/structure/findings.js +142 -0
- package/src/analysis/task-retrieval.js +3 -14
- package/src/graph/builder/go-receiver-resolution.js +112 -0
- package/src/graph/builder/js/queries.js +48 -0
- package/src/graph/builder/lang-go.js +89 -4
- package/src/graph/builder/lang-js.js +4 -44
- package/src/graph/builder/pass2-resolution.js +68 -0
- package/src/graph/freshness-probe.js +2 -1
- package/src/graph/incremental-refresh.js +3 -2
- package/src/graph/internal-builder.build.js +22 -183
- package/src/graph/internal-builder.pass2.js +161 -0
- package/src/graph/internal-builder.resolvers.js +2 -105
- package/src/graph/resolvers/rust.js +117 -0
- package/src/mcp/actions/advisories.mjs +18 -0
- package/src/mcp/actions/graph-lifecycle.mjs +148 -0
- package/src/mcp/actions/graph-sync.mjs +195 -0
- package/src/mcp/actions/hosted-architecture.mjs +57 -0
- package/src/mcp/architecture-bootstrap.mjs +168 -0
- package/src/mcp/architecture-starter.mjs +234 -0
- package/src/mcp/catalog.mjs +20 -6
- package/src/mcp/evidence/bun-lock-graph.mjs +209 -0
- package/src/mcp/evidence/duplicate-member-order.mjs +8 -0
- package/src/mcp/evidence/package-graph-common.mjs +115 -0
- package/src/mcp/evidence/package-lock-graph.mjs +92 -0
- package/src/mcp/evidence-snapshot.duplicates.mjs +3 -7
- package/src/mcp/evidence-snapshot.package-graph.mjs +5 -474
- package/src/mcp/git-output.mjs +10 -0
- package/src/mcp/graph/context-core.mjs +111 -0
- package/src/mcp/graph/context-seeds.mjs +197 -0
- package/src/mcp/graph/context-state.mjs +86 -0
- package/src/mcp/graph/reverse-reach.mjs +42 -0
- package/src/mcp/graph/tools-core.mjs +143 -0
- package/src/mcp/graph/tools-query.mjs +223 -0
- package/src/mcp/graph-context.mjs +4 -496
- package/src/mcp/health/audit-format.mjs +172 -0
- package/src/mcp/health/audit.mjs +171 -0
- package/src/mcp/health/dead-code.mjs +110 -0
- package/src/mcp/health/duplicates.mjs +83 -0
- package/src/mcp/health/endpoints.mjs +42 -0
- package/src/mcp/health/structure.mjs +219 -0
- package/src/mcp/runtime-version.mjs +32 -0
- package/src/mcp/server/auto-refresh.mjs +66 -0
- package/src/mcp/server/runtime-config.mjs +43 -0
- package/src/mcp/server/shutdown.mjs +40 -0
- package/src/mcp/sync/evidence-architecture.mjs +54 -0
- package/src/mcp/sync/evidence-common.mjs +88 -0
- package/src/mcp/sync/evidence-duplicates.mjs +97 -0
- package/src/mcp/sync/evidence-health.mjs +56 -0
- package/src/mcp/sync/evidence-packages.mjs +95 -0
- package/src/mcp/sync/payload-common.mjs +97 -0
- package/src/mcp/sync/payload-v2.mjs +53 -0
- package/src/mcp/sync/payload-v3.mjs +79 -0
- package/src/mcp/sync-evidence.mjs +7 -402
- package/src/mcp/sync-payload.mjs +10 -244
- package/src/mcp/tools-actions.mjs +18 -414
- package/src/mcp/tools-architecture.mjs +18 -70
- package/src/mcp/tools-context.mjs +56 -15
- package/src/mcp/tools-endpoints.mjs +4 -1
- package/src/mcp/tools-graph.mjs +17 -331
- package/src/mcp/tools-health.mjs +24 -705
- package/src/mcp/tools-impact-change.mjs +2 -38
- package/src/mcp/tools-impact.mjs +2 -43
- package/src/mcp-server.mjs +35 -146
- package/src/path-classification.js +14 -0
- package/src/precision/lsp-client/constants.js +12 -0
- package/src/precision/lsp-client/environment.js +20 -0
- package/src/precision/lsp-client/errors.js +15 -0
- package/src/precision/lsp-client/lifecycle.js +127 -0
- package/src/precision/lsp-client/message-parser.js +81 -0
- package/src/precision/lsp-client/protocol.js +212 -0
- package/src/precision/lsp-client/registry.js +58 -0
- package/src/precision/lsp-client/repo-uri.js +60 -0
- package/src/precision/lsp-client/stdio-client.js +151 -0
- package/src/precision/lsp-client.js +10 -682
- package/src/precision/lsp-overlay/build.js +238 -0
- package/src/precision/lsp-overlay/contract.js +61 -0
- package/src/precision/lsp-overlay/reference-results.js +108 -0
- package/src/precision/lsp-overlay/semantic-inputs.js +62 -0
- package/src/precision/lsp-overlay/source-session.js +134 -0
- package/src/precision/lsp-overlay/store.js +150 -0
- package/src/precision/lsp-overlay/target-index.js +147 -0
- package/src/precision/lsp-overlay/target-query.js +55 -0
- package/src/precision/lsp-overlay.js +11 -868
- package/src/precision/typescript-lsp-provider.js +12 -682
- package/src/precision/typescript-provider/client.js +69 -0
- package/src/precision/typescript-provider/discovery.js +124 -0
- package/src/precision/typescript-provider/project-host.js +249 -0
- package/src/precision/typescript-provider/project-safety.js +161 -0
- package/src/precision/typescript-provider/reference-usage.js +53 -0
- package/src/security/malware-heuristics.sweep.js +1 -1
- package/src/security/registry-sig.classify.js +2 -1
- package/src/security/registry-sig.rules.js +3 -3
- package/src/version.js +7 -0
- package/docs/releases/v0.2.13.md +0 -48
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
}
|
|
@@ -3,6 +3,7 @@ import {computeDuplicates} from '../analysis/duplicates.js'
|
|
|
3
3
|
import {
|
|
4
4
|
CAPS, STATE, VERDICT, bounded, compareText, graphId, repoRelativePath,
|
|
5
5
|
} from './evidence-snapshot.common.mjs'
|
|
6
|
+
import {compareDuplicateMember} from './evidence/duplicate-member-order.mjs'
|
|
6
7
|
|
|
7
8
|
const CLONE_MIN_SIMILARITY = 80
|
|
8
9
|
const DIVERGENCE_MAX_SIMILARITY = 45
|
|
@@ -51,11 +52,6 @@ function memberIdentity(value) {
|
|
|
51
52
|
return `${value.file}\0${value.startLine}\0${value.endLine}\0${value.graphNodeId || ''}`
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
function compareMember(a, b) {
|
|
55
|
-
return compareText(a.file, b.file) || a.startLine - b.startLine || a.endLine - b.endLine ||
|
|
56
|
-
compareText(a.graphNodeId || '', b.graphNodeId || '')
|
|
57
|
-
}
|
|
58
|
-
|
|
59
55
|
function eligibleMembers(fragments) {
|
|
60
56
|
const out = new Map()
|
|
61
57
|
for (let index = 0; index < fragments.length; index++) {
|
|
@@ -107,7 +103,7 @@ function cloneGroups(data, membersByIndex) {
|
|
|
107
103
|
const value = membersByIndex.get(index)
|
|
108
104
|
unique.set(memberIdentity(value), value)
|
|
109
105
|
}
|
|
110
|
-
const members = [...unique.values()].sort(
|
|
106
|
+
const members = [...unique.values()].sort(compareDuplicateMember)
|
|
111
107
|
if (members.length < 2) continue
|
|
112
108
|
const indexSet = new Set(indices)
|
|
113
109
|
const similarities = links
|
|
@@ -141,7 +137,7 @@ function divergenceCandidates(data, membersByIndex) {
|
|
|
141
137
|
if (!Number.isInteger(left) || !Number.isInteger(right) || left === right || !leftMember || !rightMember ||
|
|
142
138
|
leftMember.file === rightMember.file || !Number.isFinite(similarity) ||
|
|
143
139
|
similarity < 0 || similarity > DIVERGENCE_MAX_SIMILARITY) continue
|
|
144
|
-
const members = [leftMember, rightMember].sort(
|
|
140
|
+
const members = [leftMember, rightMember].sort(compareDuplicateMember)
|
|
145
141
|
pairs.push({
|
|
146
142
|
similarity: Math.trunc(similarity),
|
|
147
143
|
totalTokens: members[0].tokens + members[1].tokens,
|