weavatrix 0.3.13 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +231 -199
  3. package/bin/native/darwin-arm64/weavatrix +0 -0
  4. package/bin/native/darwin-x64/weavatrix +0 -0
  5. package/bin/native/linux-arm64/weavatrix +0 -0
  6. package/bin/native/linux-x64/weavatrix +0 -0
  7. package/bin/native/win32-arm64/weavatrix.exe +0 -0
  8. package/bin/native/win32-x64/weavatrix.exe +0 -0
  9. package/bin/resolve-binary.mjs +73 -0
  10. package/bin/run-native.mjs +26 -0
  11. package/bin/weavatrix-mcp.mjs +7 -6
  12. package/bin/weavatrix.mjs +5 -0
  13. package/package.json +8 -65
  14. package/skill/SKILL.md +62 -374
  15. package/skill/agents/openai.yaml +4 -0
  16. package/skill/references/tool-routing.md +45 -0
  17. package/SECURITY.md +0 -101
  18. package/docs/adr/0001-v0.3-offline-online-split.md +0 -60
  19. package/docs/agent-task-benchmark.md +0 -58
  20. package/docs/releases/v0.2.19.md +0 -33
  21. package/docs/releases/v0.3.0.md +0 -43
  22. package/docs/releases/v0.3.1.md +0 -30
  23. package/docs/releases/v0.3.10.md +0 -76
  24. package/docs/releases/v0.3.11.md +0 -55
  25. package/docs/releases/v0.3.12.md +0 -44
  26. package/docs/releases/v0.3.13.md +0 -34
  27. package/docs/releases/v0.3.2.md +0 -15
  28. package/docs/releases/v0.3.3.md +0 -25
  29. package/docs/releases/v0.3.4.md +0 -40
  30. package/docs/releases/v0.3.5.md +0 -50
  31. package/docs/releases/v0.3.6.md +0 -44
  32. package/docs/releases/v0.3.7.md +0 -37
  33. package/docs/releases/v0.3.8.md +0 -21
  34. package/docs/releases/v0.3.9.md +0 -36
  35. package/scripts/run-agent-task-benchmark.mjs +0 -118
  36. package/src/analysis/allowed-test-runner.js +0 -70
  37. package/src/analysis/architecture/contract-graph.js +0 -119
  38. package/src/analysis/architecture/contract-schema.js +0 -110
  39. package/src/analysis/architecture/contract-storage.js +0 -35
  40. package/src/analysis/architecture/contract-verification.js +0 -168
  41. package/src/analysis/architecture-contract.js +0 -16
  42. package/src/analysis/audit-debt.js +0 -94
  43. package/src/analysis/audit-extensions.js +0 -60
  44. package/src/analysis/cargo-dependency-evidence.js +0 -108
  45. package/src/analysis/cargo-manifests.js +0 -81
  46. package/src/analysis/change-classification/diff-parser.js +0 -103
  47. package/src/analysis/change-classification/options.js +0 -40
  48. package/src/analysis/change-classification/symbol-classifier.js +0 -177
  49. package/src/analysis/change-classification.js +0 -188
  50. package/src/analysis/coverage-reports.js +0 -235
  51. package/src/analysis/cycle-route.js +0 -14
  52. package/src/analysis/data-flow-evidence.js +0 -114
  53. package/src/analysis/dead-check.js +0 -300
  54. package/src/analysis/dead-code-review/candidates.js +0 -194
  55. package/src/analysis/dead-code-review/policy.js +0 -23
  56. package/src/analysis/dead-code-review.js +0 -119
  57. package/src/analysis/dep-check-ecosystems.js +0 -205
  58. package/src/analysis/dep-check.js +0 -290
  59. package/src/analysis/dep-rules.js +0 -10
  60. package/src/analysis/dependency/conventions.js +0 -16
  61. package/src/analysis/dependency/scoped-dependencies.js +0 -51
  62. package/src/analysis/dependency/source-references.js +0 -21
  63. package/src/analysis/duplicate-groups.js +0 -104
  64. package/src/analysis/duplicates-worker.js +0 -11
  65. package/src/analysis/duplicates.compute.js +0 -269
  66. package/src/analysis/duplicates.js +0 -15
  67. package/src/analysis/duplicates.run.js +0 -52
  68. package/src/analysis/duplicates.tokenize.js +0 -199
  69. package/src/analysis/endpoints/common.js +0 -62
  70. package/src/analysis/endpoints/extract.js +0 -107
  71. package/src/analysis/endpoints/inventory.js +0 -84
  72. package/src/analysis/endpoints/mounts.js +0 -129
  73. package/src/analysis/endpoints-java.js +0 -260
  74. package/src/analysis/endpoints-rust.js +0 -124
  75. package/src/analysis/endpoints.js +0 -3
  76. package/src/analysis/findings.js +0 -70
  77. package/src/analysis/git-history/analytics.js +0 -177
  78. package/src/analysis/git-history/collector.js +0 -67
  79. package/src/analysis/git-history/options.js +0 -66
  80. package/src/analysis/git-history.js +0 -143
  81. package/src/analysis/git-ref-graph.js +0 -76
  82. package/src/analysis/go-dependency-evidence.js +0 -62
  83. package/src/analysis/graph-analysis.aggregate.js +0 -295
  84. package/src/analysis/graph-analysis.edges.js +0 -24
  85. package/src/analysis/graph-analysis.js +0 -7
  86. package/src/analysis/graph-analysis.refs.js +0 -154
  87. package/src/analysis/graph-analysis.summaries.js +0 -65
  88. package/src/analysis/health-capabilities.js +0 -83
  89. package/src/analysis/hot-path-review.js +0 -266
  90. package/src/analysis/http-contract-wrappers.js +0 -241
  91. package/src/analysis/http-contracts/analysis.js +0 -179
  92. package/src/analysis/http-contracts/client-call-detection.js +0 -82
  93. package/src/analysis/http-contracts/client-call-parser.js +0 -273
  94. package/src/analysis/http-contracts/graph-context.js +0 -143
  95. package/src/analysis/http-contracts/matching.js +0 -61
  96. package/src/analysis/http-contracts/shared.js +0 -85
  97. package/src/analysis/http-contracts.js +0 -6
  98. package/src/analysis/internal-audit/dependency-health.js +0 -113
  99. package/src/analysis/internal-audit/python-manifests.js +0 -78
  100. package/src/analysis/internal-audit/repo-files.js +0 -50
  101. package/src/analysis/internal-audit/supply-chain.js +0 -149
  102. package/src/analysis/internal-audit.collect.js +0 -213
  103. package/src/analysis/internal-audit.js +0 -9
  104. package/src/analysis/internal-audit.reach.js +0 -173
  105. package/src/analysis/internal-audit.run.js +0 -256
  106. package/src/analysis/java-source.js +0 -36
  107. package/src/analysis/jvm-artifact-index.js +0 -87
  108. package/src/analysis/jvm-dependency-evidence.js +0 -138
  109. package/src/analysis/jvm-manifests.js +0 -114
  110. package/src/analysis/manifests.js +0 -123
  111. package/src/analysis/package-reachability.js +0 -39
  112. package/src/analysis/source-complexity.ast.js +0 -97
  113. package/src/analysis/source-complexity.constants.js +0 -55
  114. package/src/analysis/source-complexity.js +0 -14
  115. package/src/analysis/source-complexity.report.js +0 -81
  116. package/src/analysis/source-complexity.walk.js +0 -228
  117. package/src/analysis/source-correctness/retry-patterns.js +0 -93
  118. package/src/analysis/source-correctness.js +0 -222
  119. package/src/analysis/static-test-reachability.js +0 -150
  120. package/src/analysis/structure/dependency-graph.js +0 -151
  121. package/src/analysis/structure/findings.js +0 -162
  122. package/src/analysis/task-retrieval.js +0 -107
  123. package/src/analysis/transport-contracts.js +0 -224
  124. package/src/analysis/transport-runtime-evidence.js +0 -155
  125. package/src/analysis-kit.mjs +0 -17
  126. package/src/bounds.js +0 -4
  127. package/src/build-graph.js +0 -209
  128. package/src/child-env.js +0 -7
  129. package/src/config.js +0 -5
  130. package/src/extension/local-services.mjs +0 -75
  131. package/src/git-exec.js +0 -67
  132. package/src/graph/build-worker.js +0 -49
  133. package/src/graph/builder/go-receiver-resolution.js +0 -112
  134. package/src/graph/builder/js/dynamic-import.js +0 -26
  135. package/src/graph/builder/js/queries.js +0 -48
  136. package/src/graph/builder/lang-csharp.js +0 -40
  137. package/src/graph/builder/lang-css.js +0 -29
  138. package/src/graph/builder/lang-go.js +0 -140
  139. package/src/graph/builder/lang-html.js +0 -29
  140. package/src/graph/builder/lang-java-calls.js +0 -142
  141. package/src/graph/builder/lang-java.js +0 -215
  142. package/src/graph/builder/lang-js.js +0 -291
  143. package/src/graph/builder/lang-python.js +0 -235
  144. package/src/graph/builder/lang-rust-calls.js +0 -63
  145. package/src/graph/builder/lang-rust.js +0 -299
  146. package/src/graph/builder/lang-solidity.js +0 -126
  147. package/src/graph/builder/lang-sql.js +0 -263
  148. package/src/graph/builder/pass2-resolution.js +0 -81
  149. package/src/graph/builder/spec-pkg.js +0 -87
  150. package/src/graph/community.js +0 -27
  151. package/src/graph/edge-provenance.js +0 -31
  152. package/src/graph/file-lock.js +0 -69
  153. package/src/graph/freshness-probe.js +0 -142
  154. package/src/graph/graph-filter.js +0 -63
  155. package/src/graph/incremental-refresh.js +0 -235
  156. package/src/graph/internal-builder.barrels.js +0 -235
  157. package/src/graph/internal-builder.build.js +0 -300
  158. package/src/graph/internal-builder.java.js +0 -33
  159. package/src/graph/internal-builder.js +0 -12
  160. package/src/graph/internal-builder.langs.js +0 -157
  161. package/src/graph/internal-builder.pass2.js +0 -163
  162. package/src/graph/internal-builder.resolvers.js +0 -286
  163. package/src/graph/layout.js +0 -51
  164. package/src/graph/node-id.js +0 -7
  165. package/src/graph/parser-artifact-boundary.js +0 -55
  166. package/src/graph/relations.js +0 -4
  167. package/src/graph/repo-registry.js +0 -124
  168. package/src/graph/resolvers/rust.js +0 -117
  169. package/src/infra/infra-items.js +0 -255
  170. package/src/infra/infra-registry.js +0 -184
  171. package/src/infra/infra.detect.js +0 -119
  172. package/src/infra/infra.js +0 -38
  173. package/src/infra/infra.match.js +0 -102
  174. package/src/infra/infra.scan.js +0 -93
  175. package/src/mcp/actions/graph-lifecycle.mjs +0 -148
  176. package/src/mcp/architecture-bootstrap.mjs +0 -168
  177. package/src/mcp/architecture-starter.mjs +0 -234
  178. package/src/mcp/catalog.mjs +0 -233
  179. package/src/mcp/company-contract-page.mjs +0 -142
  180. package/src/mcp/company-contract-reconcile.mjs +0 -87
  181. package/src/mcp/company-contract-verdict.mjs +0 -42
  182. package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
  183. package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
  184. package/src/mcp/evidence/package-graph-common.mjs +0 -115
  185. package/src/mcp/evidence/package-lock-graph.mjs +0 -92
  186. package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
  187. package/src/mcp/evidence-snapshot.common.mjs +0 -158
  188. package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
  189. package/src/mcp/evidence-snapshot.health.mjs +0 -102
  190. package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
  191. package/src/mcp/evidence-snapshot.mjs +0 -50
  192. package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
  193. package/src/mcp/evidence-snapshot.structure.mjs +0 -84
  194. package/src/mcp/extension-api.mjs +0 -77
  195. package/src/mcp/git-output.mjs +0 -7
  196. package/src/mcp/graph/context-core.mjs +0 -111
  197. package/src/mcp/graph/context-seeds.mjs +0 -207
  198. package/src/mcp/graph/context-state.mjs +0 -85
  199. package/src/mcp/graph/reverse-reach.mjs +0 -42
  200. package/src/mcp/graph/tools-core.mjs +0 -143
  201. package/src/mcp/graph/tools-query.mjs +0 -276
  202. package/src/mcp/graph-context.mjs +0 -5
  203. package/src/mcp/graph-diff.mjs +0 -218
  204. package/src/mcp/health/audit-format.mjs +0 -199
  205. package/src/mcp/health/audit.mjs +0 -174
  206. package/src/mcp/health/dead-code.mjs +0 -110
  207. package/src/mcp/health/duplicates.mjs +0 -85
  208. package/src/mcp/health/endpoints.mjs +0 -71
  209. package/src/mcp/health/structure.mjs +0 -219
  210. package/src/mcp/runtime-version.mjs +0 -31
  211. package/src/mcp/server/auto-refresh.mjs +0 -66
  212. package/src/mcp/server/runtime-config.mjs +0 -43
  213. package/src/mcp/server/shutdown.mjs +0 -40
  214. package/src/mcp/staleness-notice.mjs +0 -20
  215. package/src/mcp/sync/evidence-architecture.mjs +0 -54
  216. package/src/mcp/sync/evidence-common.mjs +0 -88
  217. package/src/mcp/sync/evidence-duplicates.mjs +0 -97
  218. package/src/mcp/sync/evidence-health.mjs +0 -56
  219. package/src/mcp/sync/evidence-packages.mjs +0 -95
  220. package/src/mcp/sync/payload-common.mjs +0 -97
  221. package/src/mcp/sync/payload-v2.mjs +0 -54
  222. package/src/mcp/sync/payload-v3.mjs +0 -79
  223. package/src/mcp/sync-evidence.mjs +0 -23
  224. package/src/mcp/sync-payload.mjs +0 -70
  225. package/src/mcp/tool-result.mjs +0 -54
  226. package/src/mcp/tools-actions.mjs +0 -11
  227. package/src/mcp/tools-architecture.mjs +0 -177
  228. package/src/mcp/tools-company.mjs +0 -251
  229. package/src/mcp/tools-context.mjs +0 -222
  230. package/src/mcp/tools-endpoints.mjs +0 -207
  231. package/src/mcp/tools-graph-hubs.mjs +0 -78
  232. package/src/mcp/tools-graph.mjs +0 -17
  233. package/src/mcp/tools-health.mjs +0 -24
  234. package/src/mcp/tools-history.mjs +0 -28
  235. package/src/mcp/tools-impact-change.mjs +0 -220
  236. package/src/mcp/tools-impact-precision.mjs +0 -89
  237. package/src/mcp/tools-impact.mjs +0 -169
  238. package/src/mcp/tools-source.mjs +0 -238
  239. package/src/mcp/tools-verified-change.mjs +0 -200
  240. package/src/mcp-rg.mjs +0 -50
  241. package/src/mcp-runtime.mjs +0 -2
  242. package/src/mcp-server.mjs +0 -298
  243. package/src/mcp-source-tools.mjs +0 -163
  244. package/src/path-classification.js +0 -220
  245. package/src/path-ignore.js +0 -69
  246. package/src/precision/lsp-client/constants.js +0 -12
  247. package/src/precision/lsp-client/environment.js +0 -20
  248. package/src/precision/lsp-client/errors.js +0 -15
  249. package/src/precision/lsp-client/lifecycle.js +0 -114
  250. package/src/precision/lsp-client/message-parser.js +0 -81
  251. package/src/precision/lsp-client/protocol.js +0 -212
  252. package/src/precision/lsp-client/registry.js +0 -59
  253. package/src/precision/lsp-client/repo-uri.js +0 -60
  254. package/src/precision/lsp-client/stdio-client.js +0 -140
  255. package/src/precision/lsp-client.js +0 -10
  256. package/src/precision/lsp-overlay/budget-policy.js +0 -78
  257. package/src/precision/lsp-overlay/build.js +0 -261
  258. package/src/precision/lsp-overlay/contract.js +0 -61
  259. package/src/precision/lsp-overlay/reference-results.js +0 -108
  260. package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
  261. package/src/precision/lsp-overlay/source-session.js +0 -142
  262. package/src/precision/lsp-overlay/store.js +0 -150
  263. package/src/precision/lsp-overlay/target-index.js +0 -152
  264. package/src/precision/lsp-overlay/target-query.js +0 -55
  265. package/src/precision/lsp-overlay.js +0 -11
  266. package/src/precision/symbol-query.js +0 -224
  267. package/src/precision/typescript-lsp-provider.js +0 -12
  268. package/src/precision/typescript-provider/client.js +0 -89
  269. package/src/precision/typescript-provider/discovery.js +0 -124
  270. package/src/precision/typescript-provider/isolated-runtime.js +0 -39
  271. package/src/precision/typescript-provider/project-host.js +0 -254
  272. package/src/precision/typescript-provider/project-safety.js +0 -166
  273. package/src/precision/typescript-provider/reference-usage.js +0 -53
  274. package/src/process.js +0 -95
  275. package/src/repo-path.js +0 -53
  276. package/src/scan/discover.inventory.js +0 -78
  277. package/src/scan/discover.js +0 -5
  278. package/src/scan/discover.list.js +0 -79
  279. package/src/scan/discover.stack.js +0 -227
  280. package/src/scan/search.core.js +0 -24
  281. package/src/scan/search.git.js +0 -102
  282. package/src/scan/search.js +0 -9
  283. package/src/scan/search.node.js +0 -83
  284. package/src/scan/search.preview.js +0 -49
  285. package/src/scan/search.rg.js +0 -145
  286. package/src/security/advisory-store.js +0 -172
  287. package/src/security/installed-jvm-rust.js +0 -46
  288. package/src/security/installed.js +0 -280
  289. package/src/security/malware-file-heuristics.js +0 -121
  290. package/src/security/malware-heuristics.exclusions.js +0 -134
  291. package/src/security/malware-heuristics.js +0 -15
  292. package/src/security/malware-heuristics.roots.js +0 -145
  293. package/src/security/malware-heuristics.scan.js +0 -89
  294. package/src/security/malware-heuristics.sweep.js +0 -122
  295. package/src/security/malware-scoring.js +0 -105
  296. package/src/security/match.js +0 -85
  297. package/src/security/registry-sig.classify.js +0 -137
  298. package/src/security/registry-sig.js +0 -18
  299. package/src/security/registry-sig.rules.js +0 -192
  300. package/src/security/rust-advisory-report.js +0 -60
  301. package/src/security/typosquat.js +0 -81
  302. package/src/util.js +0 -40
  303. package/src/version.js +0 -7
@@ -1,142 +0,0 @@
1
- // Fast repository freshness probe. Full graph snapshots hash every source file and are still the
2
- // authority when something changed; this exact Git/worktree token is safe to cache in-process and in
3
- // versioned graph metadata when HEAD + dirty/untracked/control-file content remain unchanged.
4
- import {createHash} from 'node:crypto'
5
- import {readFileSync, statSync} from 'node:fs'
6
- import {createRequire} from 'node:module'
7
- import {runGit} from '../git-exec.js'
8
- import {createRepoBoundary} from '../repo-path.js'
9
-
10
- const REPOSITORY_FRESHNESS_PROBE_V = 1
11
- const GRAPH_BUILDER_SCHEMA_V = 6
12
- export const GRAPH_BUILDER_VERSION = (() => {
13
- try { return String(createRequire(import.meta.url)('../../package.json').version) }
14
- catch { return '0.0.0' }
15
- })()
16
-
17
- // These are the graph capabilities whose meaning is owned by the current builder. The package
18
- // version invalidates stamps across releases; the explicit schema requirements also fail closed when
19
- // a saved graph is hand-edited or a development build bumps a structural schema without a version bump.
20
- const CURRENT_GRAPH_SCHEMA = Object.freeze({
21
- extImportsV: 3,
22
- edgeTypesV: 2,
23
- edgeProvenanceV: 1,
24
- complexityV: 2,
25
- physicalFileLocV: 1,
26
- repoBoundaryV: 1,
27
- barrelResolutionV: 1,
28
- reExportOccurrencesV: 1,
29
- symbolSpacesV: 1,
30
- extractorSchemaV: 7,
31
- })
32
- const CONTROL_FILES = ['.gitignore', '.weavatrixignore', '.weavatrix.json']
33
- const MAX_CONTROL_BYTES = 1_000_000
34
-
35
- function git(repoRoot, args) {
36
- const result = runGit(repoRoot, args, {encoding: 'buffer', maxBuffer: 16 * 1024 * 1024})
37
- return result.status === 0 ? Buffer.from(result.stdout || '') : null
38
- }
39
-
40
- function statusPaths(status) {
41
- const records = status.toString('utf8').split('\0').filter(Boolean)
42
- const paths = []
43
- for (let index = 0; index < records.length; index++) {
44
- const record = records[index]
45
- if (record.length < 4) continue
46
- paths.push(record.slice(3))
47
- // In porcelain -z, rename/copy destinations are followed by the original path as a bare item.
48
- if (/^[RC]/.test(record) || /^[RC]/.test(record.slice(1, 2))) {
49
- if (records[index + 1]) paths.push(records[++index])
50
- }
51
- }
52
- return [...new Set(paths)]
53
- }
54
-
55
- export function repositoryFreshnessProbe(repoRoot) {
56
- const head = git(repoRoot, ['rev-parse', '--verify', 'HEAD'])
57
- const status = git(repoRoot, ['status', '--porcelain=v1', '-z', '--untracked-files=all', '--ignored=no'])
58
- if (!head || !status) return null
59
- const digest = createHash('sha256').update(head).update(status)
60
- const boundary = createRepoBoundary(repoRoot)
61
- for (const rel of statusPaths(status).sort()) {
62
- digest.update(rel)
63
- const resolved = boundary.resolve(rel)
64
- if (!resolved.ok) { digest.update(`!${resolved.reason}`); continue }
65
- try {
66
- const stats = statSync(resolved.path)
67
- if (!stats.isFile()) { digest.update('!not-file'); continue }
68
- // Dirty sets are normally tiny. Hashing their content makes same-size rapid edits exact while
69
- // still avoiding reads of every clean file in the repository.
70
- digest.update(readFileSync(resolved.path))
71
- } catch { digest.update('!unreadable') }
72
- }
73
- // Control files can intentionally be gitignored. Always include their bounded contents so a
74
- // matching Git status can never hide a changed graph universe/classification policy.
75
- for (const rel of CONTROL_FILES) {
76
- digest.update(`control:${rel}\0`)
77
- const resolved = boundary.resolve(rel)
78
- if (!resolved.ok) { digest.update(`!${resolved.reason}`); continue }
79
- try {
80
- const stats = statSync(resolved.path)
81
- if (!stats.isFile()) { digest.update('!not-file'); continue }
82
- if (stats.size > MAX_CONTROL_BYTES) { digest.update('!oversized'); continue }
83
- digest.update(readFileSync(resolved.path))
84
- } catch { digest.update('!unreadable') }
85
- }
86
- return digest.digest('hex')
87
- }
88
-
89
- const isHash = (value) => /^[a-f0-9]{64}$/.test(String(value || ''))
90
-
91
- export function graphSchemaIsCurrent(graph) {
92
- return Boolean(graph) && Object.entries(CURRENT_GRAPH_SCHEMA)
93
- .every(([key, expected]) => Number(graph[key]) === expected)
94
- }
95
-
96
- // Returns true only when a graph stamp was produced by this exact builder contract for the active
97
- // complete build mode. Legacy, scoped, non-Git, malformed and schema-old graphs all fall through to
98
- // the authoritative repository snapshot path.
99
- export function persistedFreshnessMatches(graph, probe, mode = graph?.graphBuildMode || 'full') {
100
- if (!graph || !isHash(probe) || !graphSchemaIsCurrent(graph)) return false
101
- if (Number(graph.repositoryFreshnessProbeV) !== REPOSITORY_FRESHNESS_PROBE_V) return false
102
- if (Number(graph.repositoryFreshnessBuilderSchemaV) !== GRAPH_BUILDER_SCHEMA_V) return false
103
- if (String(graph.repositoryFreshnessBuilderVersion || '') !== GRAPH_BUILDER_VERSION) return false
104
- if (String(graph.repositoryFreshnessProbe || '') !== probe) return false
105
- if (String(graph.repositoryFreshnessMode || '') !== String(mode || 'full')) return false
106
- if (String(graph.graphBuildMode || '') !== String(mode || 'full')) return false
107
- if (String(graph.graphBuildScope || '') !== '') return false
108
- return true
109
- }
110
-
111
- // Mutates the graph metadata and reports whether serialization is required. A null probe deliberately
112
- // removes an older stamp: non-Git repos and repositories that changed while parsing must never inherit
113
- // a stale fast-path token.
114
- export function stampRepositoryFreshness(graph, probe, mode = graph?.graphBuildMode || 'full') {
115
- if (!graph || typeof graph !== 'object') return false
116
- const next = isHash(probe) && graphSchemaIsCurrent(graph) && !graph.graphBuildScope
117
- ? {
118
- repositoryFreshnessProbeV: REPOSITORY_FRESHNESS_PROBE_V,
119
- repositoryFreshnessBuilderSchemaV: GRAPH_BUILDER_SCHEMA_V,
120
- repositoryFreshnessBuilderVersion: GRAPH_BUILDER_VERSION,
121
- repositoryFreshnessProbe: probe,
122
- repositoryFreshnessMode: String(mode || 'full'),
123
- }
124
- : null
125
- const keys = [
126
- 'repositoryFreshnessProbeV',
127
- 'repositoryFreshnessBuilderSchemaV',
128
- 'repositoryFreshnessBuilderVersion',
129
- 'repositoryFreshnessProbe',
130
- 'repositoryFreshnessMode',
131
- ]
132
- let changed = false
133
- for (const key of keys) {
134
- if (next && Object.hasOwn(next, key)) {
135
- if (graph[key] !== next[key]) { graph[key] = next[key]; changed = true }
136
- } else if (Object.hasOwn(graph, key)) {
137
- delete graph[key]
138
- changed = true
139
- }
140
- }
141
- return changed
142
- }
@@ -1,63 +0,0 @@
1
- // Graph filters applied to a built graph.json. Test semantics come from the shared repository path
2
- // classifier, so no-tests agrees with duplicate/audit/coverage tools (including repo config).
3
- import { createPathClassifier, hasPathClass } from "../path-classification.js";
4
-
5
- export function isTestPath(path) {
6
- return hasPathClass(createPathClassifier(null).explain(path), "test", "e2e");
7
- }
8
-
9
- const normalizedPath = (value) => String(value || "").replace(/\\/g, "/");
10
-
11
- function externalImportsForNodes(graph, nodes) {
12
- if (!Array.isArray(graph.externalImports)) return undefined;
13
- const files = new Set(nodes.map((node) => normalizedPath(node.source_file)).filter(Boolean));
14
- return graph.externalImports.filter((item) => files.has(normalizedPath(item?.file)));
15
- }
16
-
17
- // Filter a built graph.json by test-mode: drop test nodes ("no-tests") or keep only tests + the
18
- // nodes they depend on ("tests-only"). graph-builder update has no test filter, so we do it post-build.
19
- export function filterGraphForMode(graph, mode, { repoRoot = null } = {}) {
20
- if (mode !== "no-tests" && mode !== "tests-only") return graph;
21
- const nodes = graph.nodes || [];
22
- const links = graph.links || [];
23
- const endpoint = (value) => (value && typeof value === "object" ? value.id : value);
24
- const classifier = createPathClassifier(repoRoot);
25
- // A node is a test surface when its file is test-classified OR the extractor proved the symbol
26
- // itself is compiled only under test (Rust #[cfg(test)] inline modules live in production files).
27
- const isTest = (node) => node.test_surface === true || hasPathClass(classifier.explain(node.source_file), "test", "e2e");
28
- let keep;
29
- if (mode === "no-tests") {
30
- keep = new Set(nodes.filter((node) => !isTest(node)).map((node) => node.id));
31
- } else {
32
- const testIds = new Set(nodes.filter((node) => isTest(node)).map((node) => node.id));
33
- keep = new Set(testIds);
34
- for (const link of links) {
35
- if (testIds.has(endpoint(link.source))) keep.add(endpoint(link.target)); // a test's dependency
36
- }
37
- }
38
- const keptNodes = nodes.filter((node) => keep.has(node.id));
39
- const externalImports = externalImportsForNodes(graph, keptNodes);
40
- return {
41
- ...graph,
42
- nodes: keptNodes,
43
- links: links.filter((link) => keep.has(endpoint(link.source)) && keep.has(endpoint(link.target))),
44
- ...(externalImports ? { externalImports } : {})
45
- };
46
- }
47
-
48
- // Keep only nodes under a subpath (path-scope), drop links touching removed nodes.
49
- export function filterGraphByScope(graph, scope) {
50
- if (!scope) return graph;
51
- const norm = normalizedPath;
52
- const prefix = norm(scope).replace(/\/+$/, "") + "/";
53
- const endpoint = (value) => (value && typeof value === "object" ? value.id : value);
54
- const keep = new Set((graph.nodes || []).filter((node) => (norm(node.source_file) + "/").startsWith(prefix)).map((node) => node.id));
55
- const keptNodes = (graph.nodes || []).filter((node) => keep.has(node.id));
56
- const externalImports = externalImportsForNodes(graph, keptNodes);
57
- return {
58
- ...graph,
59
- nodes: keptNodes,
60
- links: (graph.links || []).filter((link) => keep.has(endpoint(link.source)) && keep.has(endpoint(link.target))),
61
- ...(externalImports ? { externalImports } : {})
62
- };
63
- }
@@ -1,235 +0,0 @@
1
- // Conservative incremental graph refresh. This module never writes graph.json: it either returns the
2
- // untouched complete graph, a fully rebuilt graph, or a complete merge of a bounded scoped parse into
3
- // the previous graph. Callers own serialization/locking.
4
- import { createHash } from "node:crypto";
5
- import { readFileSync, statSync } from "node:fs";
6
- import { extname, relative } from "node:path";
7
- import { walk } from "./internal-builder.langs.js";
8
- import { createRepoBoundary } from "../repo-path.js";
9
- import { assignDeterministicCommunities } from "./community.js";
10
-
11
- const JS_EXT = new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
12
- const CONFIG_RISK = /(^|\/)(package(?:-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|tsconfig(?:\.[^/]*)?\.json|jsconfig(?:\.[^/]*)?\.json|go\.(?:mod|sum)|Cargo\.(?:toml|lock)|pom\.xml|build\.gradle(?:\.kts)?|vite\.config\.[^/]+|webpack\.config\.[^/]+|babel\.config\.[^/]+)$/i;
13
- const CONTROL_FILES = [".gitignore", ".weavatrixignore", ".weavatrix.json"];
14
- const MAX_CONTROL_BYTES = 1_000_000;
15
-
16
- const norm = (value) => String(value || "").replace(/\\/g, "/").replace(/^\.\//, "");
17
- const hash = (value) => createHash("sha256").update(value).digest("hex");
18
- const endpoint = (value) => String(value && typeof value === "object" ? value.id : value);
19
- const fileOfEndpoint = (value, nodesById) => {
20
- const id = endpoint(value);
21
- const source = nodesById.get(id)?.source_file;
22
- if (source) return norm(source);
23
- const marker = id.indexOf("#");
24
- return norm(marker < 0 ? id : id.slice(0, marker));
25
- };
26
-
27
- // We only need to know whether a module's public surface changed, not understand its implementation.
28
- // The full tree-sitter pass remains authoritative; this signature is deliberately conservative and
29
- // whitespace-insensitive. Any changed export statement forces a full rebuild.
30
- export function jsExportSignature(text, file = "") {
31
- if (!JS_EXT.has(extname(file))) return "";
32
- const source = String(text || "");
33
- const exports = [];
34
- const pattern = /\bexport\s+(?:declare\s+)?(?:type\s+)?(?:\*\s+(?:as\s+[A-Za-z_$][\w$]*\s+)?from\s*["'][^"']+["']|\{[\s\S]*?\}(?:\s+from\s*["'][^"']+["'])?|default\s+(?:(?:async\s+)?function\s*\*?|class)?\s*[A-Za-z_$]?[\w$]*|(?:async\s+)?function\s*\*?\s+[A-Za-z_$][\w$]*|class\s+[A-Za-z_$][\w$]*|(?:const|let|var|interface|type|enum)\s+[A-Za-z_$][\w$]*)/g;
35
- for (const match of source.matchAll(pattern)) exports.push(match[0].replace(/\s+/g, " ").trim());
36
- // The general matcher intentionally stops before implementation bodies. Preserve every binding name
37
- // in multi-declarator exports (`export const a = 1, b = 2`) so adding/removing one cannot slip through.
38
- for (const match of source.matchAll(/\bexport\s+(?:declare\s+)?(?:const|let|var)\s+([^;\n]+)/g)) {
39
- const names = [];
40
- for (const part of match[1].split(",")) {
41
- const declared = part.trim().match(/^([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=/);
42
- if (declared) names.push(declared[1]);
43
- }
44
- exports.push(`bindings:${names.join(",")}`);
45
- }
46
- return hash(exports.join("\n"));
47
- }
48
-
49
- export function snapshotRepository(repoDir, files = walk(repoDir)) {
50
- const root = String(repoDir);
51
- const fileHashes = {};
52
- const fileExportSignatures = {};
53
- const relativeFiles = [];
54
- for (const abs of files) {
55
- const rel = norm(relative(root, abs));
56
- let body;
57
- try { body = readFileSync(abs); } catch { continue; }
58
- relativeFiles.push(rel);
59
- fileHashes[rel] = hash(body);
60
- if (JS_EXT.has(extname(rel))) fileExportSignatures[rel] = jsExportSignature(body.toString("utf8"), rel);
61
- }
62
- relativeFiles.sort();
63
- const controlHashes = {};
64
- const boundary = createRepoBoundary(root);
65
- for (const rel of CONTROL_FILES) {
66
- const resolved = boundary.resolve(rel);
67
- if (!resolved.ok) {
68
- controlHashes[rel] = resolved.reason === "not-found" ? null : `UNREADABLE:${resolved.reason}`;
69
- continue;
70
- }
71
- try {
72
- const stats = statSync(resolved.path);
73
- controlHashes[rel] = stats.isFile() && stats.size <= MAX_CONTROL_BYTES
74
- ? hash(readFileSync(resolved.path))
75
- : stats.isFile() ? "UNREADABLE:oversized" : "UNREADABLE:not-file";
76
- } catch {
77
- controlHashes[rel] = "UNREADABLE:unreadable";
78
- }
79
- }
80
- const revision = hash([
81
- ...relativeFiles.map((file) => `${file}:${fileHashes[file]}`),
82
- ...Object.keys(controlHashes).sort().map((file) => `${file}:${controlHashes[file] ?? "missing"}`),
83
- ].join("\n"));
84
- return { files, relativeFiles, fileHashes, fileExportSignatures, controlHashes, revision };
85
- }
86
-
87
- function sameRecord(left, right) {
88
- const a = left && typeof left === "object" ? left : {};
89
- const b = right && typeof right === "object" ? right : {};
90
- const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
91
- for (const key of keys) if (a[key] !== b[key]) return false;
92
- return true;
93
- }
94
-
95
- function changedPaths(previous, current) {
96
- const keys = new Set([...Object.keys(previous || {}), ...Object.keys(current || {})]);
97
- return [...keys].filter((file) => previous?.[file] !== current?.[file]).sort();
98
- }
99
-
100
- function isBarrelFile(graph, file) {
101
- const nodesById = new Map((graph.nodes || []).map((node) => [String(node.id), node]));
102
- return (graph.links || []).some((link) => {
103
- const sourceFile = fileOfEndpoint(link.source, nodesById);
104
- return sourceFile === file && (link.relation === "re_exports" || link.barrelProxy === true);
105
- });
106
- }
107
-
108
- function reverseImporters(graph, changed) {
109
- const changedSet = new Set(changed);
110
- const nodesById = new Map((graph.nodes || []).map((node) => [String(node.id), node]));
111
- const result = new Set();
112
- for (const link of graph.links || []) {
113
- const targetFile = fileOfEndpoint(link.target, nodesById);
114
- if (!changedSet.has(targetFile)) continue;
115
- const sourceFile = fileOfEndpoint(link.source, nodesById);
116
- if (sourceFile && sourceFile !== targetFile) result.add(sourceFile);
117
- }
118
- return result;
119
- }
120
-
121
- function mergeScopedGraph(base, scoped, affected, snapshot) {
122
- const affectedSet = new Set(affected);
123
- const baseNodes = Array.isArray(base.nodes) ? base.nodes : [];
124
- const scopedNodes = Array.isArray(scoped.nodes) ? scoped.nodes : [];
125
- const baseNodesById = new Map(baseNodes.map((node) => [String(node.id), node]));
126
- const nodeFile = (node) => norm(node?.source_file || fileOfEndpoint(node?.id, baseNodesById));
127
- const nodes = [...baseNodes.filter((node) => !affectedSet.has(nodeFile(node))), ...scopedNodes];
128
- assignDeterministicCommunities(nodes);
129
- const nodesById = new Map(nodes.map((node) => [String(node.id), node]));
130
- if (nodesById.size !== nodes.length) throw new Error("incremental merge produced duplicate node ids");
131
-
132
- const baseLinkKept = (link) => {
133
- const sourceFile = fileOfEndpoint(link.source, baseNodesById);
134
- // A scoped rebuild owns every outgoing edge of an affected source. Incoming edges from an
135
- // unaffected source still describe that source and must survive (A -> B -> changed C reparses
136
- // B+C, but A -> B is not regenerated by the scoped builder). The node-id filter below safely
137
- // removes an incoming edge when its affected target symbol no longer exists.
138
- return !affectedSet.has(sourceFile);
139
- };
140
- // Base and scoped links are disjoint by source file: every scoped source was removed above. Keep
141
- // repeated call/reference occurrences intact because occurrence hotspots intentionally use them.
142
- const links = [...(base.links || []).filter(baseLinkKept), ...(scoped.links || [])]
143
- .filter((link) => nodesById.has(endpoint(link.source)) && nodesById.has(endpoint(link.target)));
144
-
145
- const externalImports = [
146
- ...(base.externalImports || []).filter((record) => !affectedSet.has(norm(record?.file))),
147
- ...(scoped.externalImports || []),
148
- ];
149
- const fileNodes = new Set(nodes.filter((node) => !String(node.id).includes("#")).map((node) => norm(node.source_file || node.id)));
150
- if (fileNodes.size !== snapshot.relativeFiles.length || snapshot.relativeFiles.some((file) => !fileNodes.has(file))) {
151
- throw new Error("incremental merge did not preserve the complete file universe");
152
- }
153
-
154
- const merged = {
155
- ...base,
156
- nodes,
157
- links,
158
- externalImports,
159
- jsExportRecords: scoped.jsExportRecords || base.jsExportRecords || {},
160
- reExportOccurrences: scoped.reExportOccurrences || base.reExportOccurrences || [],
161
- fileHashes: snapshot.fileHashes,
162
- fileExportSignatures: snapshot.fileExportSignatures,
163
- controlHashes: snapshot.controlHashes,
164
- graphRevision: snapshot.revision,
165
- };
166
- for (const key of ["extImportsV", "edgeTypesV", "edgeProvenanceV", "complexityV", "physicalFileLocV", "repoBoundaryV", "barrelResolutionV", "reExportOccurrencesV", "symbolSpacesV", "extractorSchemaV"]) {
167
- merged[key] = Math.max(Number(base[key]) || 0, Number(scoped[key]) || 0);
168
- }
169
- return merged;
170
- }
171
-
172
- export async function refreshGraphIncrementally(repoDir, existingGraph, {
173
- buildGraph,
174
- maxChangedFiles = 24,
175
- maxParsedFiles = 80,
176
- builderOptions = {},
177
- } = {}) {
178
- const builder = buildGraph || (await import("./internal-builder.js")).buildInternalGraph;
179
- const snapshot = snapshotRepository(repoDir);
180
- const full = async (reason, changedFiles = []) => {
181
- const graph = await builder(repoDir, builderOptions);
182
- return { graph, kind: "full", changedFiles, reason, revision: graph.graphRevision || snapshot.revision, parsedFiles: snapshot.relativeFiles };
183
- };
184
-
185
- if (!existingGraph || !existingGraph.fileHashes || !existingGraph.fileExportSignatures
186
- || !existingGraph.controlHashes || !existingGraph.jsExportRecords || Number(existingGraph.barrelResolutionV) < 1
187
- || Number(existingGraph.extractorSchemaV) < 7 || Number(existingGraph.physicalFileLocV) < 1
188
- || Number(existingGraph.reExportOccurrencesV) < 1
189
- || Number(existingGraph.symbolSpacesV) < 1 || Number(existingGraph.edgeProvenanceV) < 1) {
190
- return full("incremental-baseline-unavailable");
191
- }
192
- if (!sameRecord(existingGraph.controlHashes, snapshot.controlHashes)) return full("ignore-or-control-config-changed");
193
-
194
- const oldFiles = Object.keys(existingGraph.fileHashes).sort();
195
- if (oldFiles.length !== snapshot.relativeFiles.length || oldFiles.some((file, index) => file !== snapshot.relativeFiles[index])) {
196
- const changedFiles = changedPaths(existingGraph.fileHashes, snapshot.fileHashes);
197
- return full("file-universe-changed", changedFiles);
198
- }
199
- const changedFiles = changedPaths(existingGraph.fileHashes, snapshot.fileHashes);
200
- if (!changedFiles.length) {
201
- return { graph: existingGraph, kind: "none", changedFiles: [], reason: "content-unchanged", revision: snapshot.revision, parsedFiles: [] };
202
- }
203
- if (changedFiles.length > maxChangedFiles) return full("changed-set-too-large", changedFiles);
204
- if (changedFiles.some((file) => CONFIG_RISK.test(file))) return full("config-manifest-or-alias-changed", changedFiles);
205
- if (changedFiles.some((file) => !JS_EXT.has(extname(file)))) return full("language-requires-full-context", changedFiles);
206
- for (const file of changedFiles) {
207
- if (isBarrelFile(existingGraph, file)) return full(`barrel-file-changed:${file}`, changedFiles);
208
- if (existingGraph.fileExportSignatures[file] !== snapshot.fileExportSignatures[file]) {
209
- return full(`export-surface-changed:${file}`, changedFiles);
210
- }
211
- }
212
-
213
- const importers = reverseImporters(existingGraph, changedFiles);
214
- const affected = [...new Set([...changedFiles, ...importers])].filter((file) => snapshot.fileHashes[file]).sort();
215
- if (affected.length > maxParsedFiles) return full("reverse-importer-set-too-large", changedFiles);
216
- try {
217
- const scoped = await builder(repoDir, {
218
- ...builderOptions,
219
- includeFiles: affected,
220
- baseGraph: existingGraph,
221
- });
222
- if (scoped.incrementalScope !== true) return full("builder-did-not-honor-incremental-scope", changedFiles);
223
- const graph = mergeScopedGraph(existingGraph, scoped, affected, snapshot);
224
- return {
225
- graph,
226
- kind: "incremental",
227
- changedFiles,
228
- reason: importers.size ? "changed-files-and-reverse-importers" : "changed-files-only",
229
- revision: snapshot.revision,
230
- parsedFiles: affected,
231
- };
232
- } catch {
233
- return full("incremental-merge-safety-fallback", changedFiles);
234
- }
235
- }
@@ -1,235 +0,0 @@
1
- // TypeScript/JavaScript barrel transparency. The parser keeps physical import/re-export edges for
2
- // execution-order and cycle analysis; this post-pass resolves public export names to their declaring
3
- // files, marks the physical facade hops as proxies, and adds direct semantic importer -> origin edges.
4
- // Resolution follows ESM rules that matter for a static graph: explicit exports shadow `export *`,
5
- // `default` never flows through a star, cycles terminate, and conflicting star origins stay ambiguous.
6
-
7
- const resolved = (file, name, typeOnly = false) => ({ status: "resolved", origin: { file, name, typeOnly } });
8
- const MISSING = Object.freeze({ status: "missing" });
9
- const AMBIGUOUS = Object.freeze({ status: "ambiguous" });
10
-
11
- function mergeCandidates(candidates) {
12
- if (candidates.some((candidate) => candidate.status === "ambiguous")) return AMBIGUOUS;
13
- const origins = new Map();
14
- for (const candidate of candidates) {
15
- if (candidate.status !== "resolved") continue;
16
- const key = `${candidate.origin.file}\0${candidate.origin.name}`;
17
- const current = origins.get(key);
18
- if (!current) origins.set(key, { ...candidate.origin });
19
- else current.typeOnly = current.typeOnly && candidate.origin.typeOnly;
20
- }
21
- if (!origins.size) return MISSING;
22
- if (origins.size > 1) return AMBIGUOUS;
23
- return { status: "resolved", origin: [...origins.values()][0] };
24
- }
25
-
26
- function withTypeOnly(result, typeOnly) {
27
- if (result.status !== "resolved" || !typeOnly) return result;
28
- return resolved(result.origin.file, result.origin.name, true);
29
- }
30
-
31
- export function resolveJsBarrels({ jsExports, importedLocals, links, resolveSymbol = () => null }) {
32
- const tables = new Map();
33
- for (const [file, records] of jsExports) {
34
- const table = { explicit: new Map(), stars: [], facadeMembers: new Map() };
35
- for (const record of records || []) {
36
- if (record.kind === "star") {
37
- table.stars.push(record);
38
- continue;
39
- }
40
- if (record.kind === "facade-member") {
41
- if (!record.member) continue;
42
- const list = table.facadeMembers.get(record.member) || [];
43
- list.push(record);
44
- table.facadeMembers.set(record.member, list);
45
- continue;
46
- }
47
- if (!record.exported) continue;
48
- const list = table.explicit.get(record.exported) || [];
49
- list.push(record);
50
- table.explicit.set(record.exported, list);
51
- }
52
- tables.set(file, table);
53
- }
54
-
55
- const cache = new Map();
56
- const resolveExport = (file, name, trail = new Set()) => {
57
- const key = `${file}\0${name}`;
58
- const rootResolution = trail.size === 0;
59
- if (cache.has(key)) return cache.get(key);
60
- if (trail.has(key)) return MISSING;
61
- const table = tables.get(file);
62
- if (!table) return MISSING;
63
- const nextTrail = new Set(trail);
64
- nextTrail.add(key);
65
-
66
- const explicit = table.explicit.get(name) || [];
67
- if (explicit.length) {
68
- const candidates = explicit.map((record) => {
69
- if (record.kind === "named") {
70
- return withTypeOnly(resolveExport(record.targetFile, record.imported, nextTrail), record.typeOnly);
71
- }
72
- if (record.kind === "namespace") return resolved(record.targetFile, "*", record.typeOnly);
73
- const local = record.local || name;
74
- const binding = importedLocals.get(file)?.get(local);
75
- if (binding?.targetFile && binding.imported && binding.imported !== "*") {
76
- return withTypeOnly(resolveExport(binding.targetFile, binding.imported, nextTrail), record.typeOnly || binding.typeOnly);
77
- }
78
- // Type/interface declarations are intentionally not symbol nodes yet, but their declaring file is
79
- // still the correct semantic origin for file/module dependency analysis.
80
- return resolved(file, local, record.typeOnly);
81
- });
82
- const result = mergeCandidates(candidates);
83
- if (rootResolution) cache.set(key, result);
84
- return result;
85
- }
86
-
87
- if (name === "default") {
88
- if (rootResolution) cache.set(key, MISSING);
89
- return MISSING;
90
- }
91
- const candidates = table.stars.map((star) => withTypeOnly(resolveExport(star.targetFile, name, nextTrail), star.typeOnly));
92
- const result = mergeCandidates(candidates);
93
- if (rootResolution) cache.set(key, result);
94
- return result;
95
- };
96
-
97
- const resolveFacadeMember = (file, member, trail = new Set()) => {
98
- const key = `${file}\0default.${member}`;
99
- if (trail.has(key)) return MISSING;
100
- const records = tables.get(file)?.facadeMembers.get(member) || [];
101
- if (!records.length) return MISSING;
102
- const nextTrail = new Set(trail);
103
- nextTrail.add(key);
104
- return mergeCandidates(records.map((record) => {
105
- const local = record.local || member;
106
- const binding = importedLocals.get(file)?.get(local);
107
- if (binding?.targetFile && binding.imported && binding.imported !== "*") {
108
- return withTypeOnly(resolveExport(binding.targetFile, binding.imported, nextTrail), record.typeOnly || binding.typeOnly);
109
- }
110
- return resolved(file, local, record.typeOnly);
111
- }));
112
- };
113
-
114
- // Materialize source-free, occurrence-level re-export evidence. Named/local alias records retain
115
- // their exact public name and final declaration origin; wildcard/namespace records retain the
116
- // concrete facade statement and target for on-demand propagation in context tools.
117
- const reExportOccurrences = [];
118
- for (const [file, records] of jsExports) for (const record of records || []) {
119
- if (["facade-root", "facade-member"].includes(record.kind)) continue;
120
- const resolution = record.kind === "star" || !record.exported
121
- ? MISSING
122
- : resolveExport(file, record.exported);
123
- if (resolution.status === "resolved") {
124
- record.originFile = resolution.origin.file;
125
- record.originName = resolution.origin.name;
126
- record.originTypeOnly = resolution.origin.typeOnly === true;
127
- record.originId = resolveSymbol(resolution.origin.file, resolution.origin.name, resolution.origin.typeOnly === true) || null;
128
- }
129
- const reExportedLocal = record.kind === "local" && resolution.status === "resolved" && resolution.origin.file !== file;
130
- if (!record.targetFile && !reExportedLocal) continue;
131
- reExportOccurrences.push({
132
- file,
133
- line: Number(record.line) || 1,
134
- kind: record.kind,
135
- exported: record.exported || record.member || "*",
136
- imported: record.imported || record.local || "*",
137
- targetFile: record.targetFile || resolution.origin?.file || file,
138
- typeOnly: record.typeOnly === true || resolution.origin?.typeOnly === true,
139
- ...(record.specifier ? {specifier: record.specifier} : {}),
140
- status: record.kind === "star" ? "wildcard" : resolution.status,
141
- ...(resolution.status === "resolved" ? {
142
- originFile: resolution.origin.file,
143
- originName: resolution.origin.name,
144
- ...(record.originId ? {originId: record.originId} : {}),
145
- } : {}),
146
- });
147
- }
148
- reExportOccurrences.sort((left, right) => left.file.localeCompare(right.file)
149
- || left.line - right.line || left.exported.localeCompare(right.exported));
150
-
151
- // Every internal re-export is a physical facade hop. It remains in the graph for runtime cycle truth,
152
- // but semantic consumers ignore it in favor of importer -> declaration edges below.
153
- for (const link of links) {
154
- if (link.relation !== "re_exports") continue;
155
- const records = jsExports.get(String(link.source)) || [];
156
- if (records.some((record) => record.targetFile === String(link.target))) link.barrelProxy = true;
157
- }
158
-
159
- const semanticEdges = new Map();
160
- const addSemanticEdge = (source, imp, origin, usage = null, force = false) => {
161
- if (!origin?.file || (!force && origin.file === imp.targetFile)) return;
162
- const key = `${source}\0${origin.file}\0${imp.line || 0}`;
163
- const effectiveTypeOnly = imp.typeOnly === true || origin.typeOnly === true;
164
- const current = semanticEdges.get(key);
165
- if (current) {
166
- if (!effectiveTypeOnly) delete current.typeOnly;
167
- return;
168
- }
169
- semanticEdges.set(key, {
170
- source,
171
- target: origin.file,
172
- relation: "imports",
173
- confidence: "EXTRACTED",
174
- semanticOrigin: true,
175
- viaBarrel: imp.targetFile,
176
- ...(effectiveTypeOnly ? { typeOnly: true } : {}),
177
- ...(imp.line ? { line: imp.line } : {}),
178
- ...(imp.specifier ? { specifier: imp.specifier } : {}),
179
- ...(usage ? { usage } : {}),
180
- });
181
- };
182
-
183
- const proxiedImportGroups = new Set();
184
- for (const [source, imports] of importedLocals) {
185
- for (const imp of imports.values()) {
186
- if (!imp?.targetFile || imp.imported === "*") continue;
187
- const result = resolveExport(imp.targetFile, imp.imported);
188
- if (result.status !== "resolved") continue;
189
- imp.originFile = result.origin.file;
190
- imp.originName = result.origin.name;
191
- imp.originTypeOnly = result.origin.typeOnly === true;
192
- if (result.origin.file === imp.targetFile) continue;
193
- proxiedImportGroups.add(`${source}\0${imp.targetFile}\0${imp.line || 0}`);
194
- for (const link of links) {
195
- if (String(link.source) === source && String(link.target) === imp.targetFile && link.relation === "imports"
196
- && (!imp.line || !link.line || link.line === imp.line)) link.barrelProxy = true;
197
- }
198
- addSemanticEdge(source, imp, result.origin);
199
- }
200
- }
201
- // One import statement can mix a barrel-local export with re-exported bindings. Once its physical
202
- // file edge becomes a proxy, preserve the local binding as its own semantic edge to the barrel file.
203
- for (const [source, imports] of importedLocals) for (const imp of imports.values()) {
204
- if (!imp?.originFile || imp.originFile !== imp.targetFile) continue;
205
- if (!proxiedImportGroups.has(`${source}\0${imp.targetFile}\0${imp.line || 0}`)) continue;
206
- addSemanticEdge(source, imp, { file: imp.originFile, name: imp.originName, typeOnly: imp.originTypeOnly }, null, true);
207
- }
208
- links.push(...semanticEdges.values());
209
-
210
- // Namespace member usage is only knowable in pass 2 (`ui.Button`, `ui.run`). Expose a bounded helper
211
- // that uses the same cache and emits the corresponding semantic file edge exactly once.
212
- const resolveNamespaceMember = (source, imp, member, usage = null) => {
213
- if (!imp?.targetFile) return MISSING;
214
- const defaultFacade = imp.imported === "default" || imp.originName === "default";
215
- const result = imp.imported === "*"
216
- ? resolveExport(imp.targetFile, member)
217
- : defaultFacade
218
- ? resolveFacadeMember(imp.originFile || imp.targetFile, member)
219
- : MISSING;
220
- if (result.status !== "resolved") return result;
221
- if (result.origin.file !== imp.targetFile) {
222
- for (const link of links) {
223
- if (String(link.source) === source && String(link.target) === imp.targetFile && link.relation === "imports"
224
- && (!imp.line || !link.line || link.line === imp.line)) link.barrelProxy = true;
225
- }
226
- addSemanticEdge(source, imp, result.origin, usage);
227
- // addSemanticEdge may have created a new entry after the initial push.
228
- const edge = semanticEdges.get(`${source}\0${result.origin.file}\0${imp.line || 0}`);
229
- if (edge && !links.includes(edge)) links.push(edge);
230
- }
231
- return result;
232
- };
233
-
234
- return { resolveExport, resolveNamespaceMember, resolveFacadeMember, reExportOccurrences };
235
- }