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.
Files changed (145) hide show
  1. package/README.md +99 -39
  2. package/SECURITY.md +2 -2
  3. package/docs/releases/v0.2.15.md +37 -0
  4. package/package.json +2 -2
  5. package/skill/SKILL.md +57 -38
  6. package/src/analysis/architecture/contract-graph.js +119 -0
  7. package/src/analysis/architecture/contract-schema.js +110 -0
  8. package/src/analysis/architecture/contract-storage.js +35 -0
  9. package/src/analysis/architecture/contract-verification.js +168 -0
  10. package/src/analysis/architecture-contract.js +16 -343
  11. package/src/analysis/change-classification/diff-parser.js +103 -0
  12. package/src/analysis/change-classification/options.js +40 -0
  13. package/src/analysis/change-classification/symbol-classifier.js +177 -0
  14. package/src/analysis/change-classification.js +120 -519
  15. package/src/analysis/dead-code-review/policy.js +23 -0
  16. package/src/analysis/dead-code-review.js +9 -19
  17. package/src/analysis/dep-check.js +14 -71
  18. package/src/analysis/dep-rules.js +10 -303
  19. package/src/analysis/dependency/conventions.js +16 -0
  20. package/src/analysis/dependency/scoped-dependencies.js +45 -0
  21. package/src/analysis/dependency/source-references.js +21 -0
  22. package/src/analysis/duplicates.tokenize.js +12 -2
  23. package/src/analysis/endpoints/common.js +62 -0
  24. package/src/analysis/endpoints/extract.js +107 -0
  25. package/src/analysis/endpoints/inventory.js +84 -0
  26. package/src/analysis/endpoints/mounts.js +129 -0
  27. package/src/analysis/endpoints-java.js +80 -3
  28. package/src/analysis/endpoints.js +3 -448
  29. package/src/analysis/git-history/analytics.js +177 -0
  30. package/src/analysis/git-history/collector.js +109 -0
  31. package/src/analysis/git-history/options.js +68 -0
  32. package/src/analysis/git-history.js +128 -577
  33. package/src/analysis/health-capabilities.js +82 -0
  34. package/src/analysis/http-contracts/analysis.js +169 -0
  35. package/src/analysis/http-contracts/client-call-detection.js +81 -0
  36. package/src/analysis/http-contracts/client-call-parser.js +255 -0
  37. package/src/analysis/http-contracts/graph-context.js +143 -0
  38. package/src/analysis/http-contracts/matching.js +61 -0
  39. package/src/analysis/http-contracts/shared.js +86 -0
  40. package/src/analysis/http-contracts.js +6 -822
  41. package/src/analysis/internal-audit/dependency-health.js +111 -0
  42. package/src/analysis/internal-audit/python-manifests.js +65 -0
  43. package/src/analysis/internal-audit/repo-files.js +55 -0
  44. package/src/analysis/internal-audit/supply-chain.js +132 -0
  45. package/src/analysis/internal-audit.collect.js +5 -106
  46. package/src/analysis/internal-audit.reach.js +20 -0
  47. package/src/analysis/internal-audit.run.js +114 -200
  48. package/src/analysis/jvm-dependency-evidence.js +69 -0
  49. package/src/analysis/source-correctness/retry-patterns.js +93 -0
  50. package/src/analysis/source-correctness.js +225 -0
  51. package/src/analysis/structure/dependency-graph.js +156 -0
  52. package/src/analysis/structure/findings.js +142 -0
  53. package/src/analysis/task-retrieval.js +3 -14
  54. package/src/graph/builder/go-receiver-resolution.js +112 -0
  55. package/src/graph/builder/js/queries.js +48 -0
  56. package/src/graph/builder/lang-go.js +89 -4
  57. package/src/graph/builder/lang-js.js +4 -44
  58. package/src/graph/builder/pass2-resolution.js +68 -0
  59. package/src/graph/freshness-probe.js +2 -1
  60. package/src/graph/incremental-refresh.js +3 -2
  61. package/src/graph/internal-builder.build.js +22 -183
  62. package/src/graph/internal-builder.pass2.js +161 -0
  63. package/src/graph/internal-builder.resolvers.js +2 -105
  64. package/src/graph/resolvers/rust.js +117 -0
  65. package/src/mcp/actions/advisories.mjs +18 -0
  66. package/src/mcp/actions/graph-lifecycle.mjs +148 -0
  67. package/src/mcp/actions/graph-sync.mjs +195 -0
  68. package/src/mcp/actions/hosted-architecture.mjs +57 -0
  69. package/src/mcp/architecture-bootstrap.mjs +168 -0
  70. package/src/mcp/architecture-starter.mjs +234 -0
  71. package/src/mcp/catalog.mjs +20 -6
  72. package/src/mcp/evidence/bun-lock-graph.mjs +209 -0
  73. package/src/mcp/evidence/duplicate-member-order.mjs +8 -0
  74. package/src/mcp/evidence/package-graph-common.mjs +115 -0
  75. package/src/mcp/evidence/package-lock-graph.mjs +92 -0
  76. package/src/mcp/evidence-snapshot.duplicates.mjs +3 -7
  77. package/src/mcp/evidence-snapshot.package-graph.mjs +5 -474
  78. package/src/mcp/git-output.mjs +10 -0
  79. package/src/mcp/graph/context-core.mjs +111 -0
  80. package/src/mcp/graph/context-seeds.mjs +197 -0
  81. package/src/mcp/graph/context-state.mjs +86 -0
  82. package/src/mcp/graph/reverse-reach.mjs +42 -0
  83. package/src/mcp/graph/tools-core.mjs +143 -0
  84. package/src/mcp/graph/tools-query.mjs +223 -0
  85. package/src/mcp/graph-context.mjs +4 -496
  86. package/src/mcp/health/audit-format.mjs +172 -0
  87. package/src/mcp/health/audit.mjs +171 -0
  88. package/src/mcp/health/dead-code.mjs +110 -0
  89. package/src/mcp/health/duplicates.mjs +83 -0
  90. package/src/mcp/health/endpoints.mjs +42 -0
  91. package/src/mcp/health/structure.mjs +219 -0
  92. package/src/mcp/runtime-version.mjs +32 -0
  93. package/src/mcp/server/auto-refresh.mjs +66 -0
  94. package/src/mcp/server/runtime-config.mjs +43 -0
  95. package/src/mcp/server/shutdown.mjs +40 -0
  96. package/src/mcp/sync/evidence-architecture.mjs +54 -0
  97. package/src/mcp/sync/evidence-common.mjs +88 -0
  98. package/src/mcp/sync/evidence-duplicates.mjs +97 -0
  99. package/src/mcp/sync/evidence-health.mjs +56 -0
  100. package/src/mcp/sync/evidence-packages.mjs +95 -0
  101. package/src/mcp/sync/payload-common.mjs +97 -0
  102. package/src/mcp/sync/payload-v2.mjs +53 -0
  103. package/src/mcp/sync/payload-v3.mjs +79 -0
  104. package/src/mcp/sync-evidence.mjs +7 -402
  105. package/src/mcp/sync-payload.mjs +10 -244
  106. package/src/mcp/tools-actions.mjs +18 -414
  107. package/src/mcp/tools-architecture.mjs +18 -70
  108. package/src/mcp/tools-context.mjs +56 -15
  109. package/src/mcp/tools-endpoints.mjs +4 -1
  110. package/src/mcp/tools-graph.mjs +17 -331
  111. package/src/mcp/tools-health.mjs +24 -705
  112. package/src/mcp/tools-impact-change.mjs +2 -38
  113. package/src/mcp/tools-impact.mjs +2 -43
  114. package/src/mcp-server.mjs +35 -146
  115. package/src/path-classification.js +14 -0
  116. package/src/precision/lsp-client/constants.js +12 -0
  117. package/src/precision/lsp-client/environment.js +20 -0
  118. package/src/precision/lsp-client/errors.js +15 -0
  119. package/src/precision/lsp-client/lifecycle.js +127 -0
  120. package/src/precision/lsp-client/message-parser.js +81 -0
  121. package/src/precision/lsp-client/protocol.js +212 -0
  122. package/src/precision/lsp-client/registry.js +58 -0
  123. package/src/precision/lsp-client/repo-uri.js +60 -0
  124. package/src/precision/lsp-client/stdio-client.js +151 -0
  125. package/src/precision/lsp-client.js +10 -682
  126. package/src/precision/lsp-overlay/build.js +238 -0
  127. package/src/precision/lsp-overlay/contract.js +61 -0
  128. package/src/precision/lsp-overlay/reference-results.js +108 -0
  129. package/src/precision/lsp-overlay/semantic-inputs.js +62 -0
  130. package/src/precision/lsp-overlay/source-session.js +134 -0
  131. package/src/precision/lsp-overlay/store.js +150 -0
  132. package/src/precision/lsp-overlay/target-index.js +147 -0
  133. package/src/precision/lsp-overlay/target-query.js +55 -0
  134. package/src/precision/lsp-overlay.js +11 -868
  135. package/src/precision/typescript-lsp-provider.js +12 -682
  136. package/src/precision/typescript-provider/client.js +69 -0
  137. package/src/precision/typescript-provider/discovery.js +124 -0
  138. package/src/precision/typescript-provider/project-host.js +249 -0
  139. package/src/precision/typescript-provider/project-safety.js +161 -0
  140. package/src/precision/typescript-provider/reference-usage.js +53 -0
  141. package/src/security/malware-heuristics.sweep.js +1 -1
  142. package/src/security/registry-sig.classify.js +2 -1
  143. package/src/security/registry-sig.rules.js +3 -3
  144. package/src/version.js +7 -0
  145. 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(compareMember)
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(compareMember)
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,