weavatrix 0.3.14 → 1.1.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 (306) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +132 -200
  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 +10 -67
  14. package/server.json +38 -0
  15. package/skill/SKILL.md +62 -374
  16. package/skill/agents/openai.yaml +4 -0
  17. package/skill/references/tool-routing.md +45 -0
  18. package/SECURITY.md +0 -101
  19. package/docs/adr/0001-v0.3-offline-online-split.md +0 -60
  20. package/docs/agent-task-benchmark.md +0 -58
  21. package/docs/releases/v0.2.19.md +0 -33
  22. package/docs/releases/v0.3.0.md +0 -43
  23. package/docs/releases/v0.3.1.md +0 -30
  24. package/docs/releases/v0.3.10.md +0 -76
  25. package/docs/releases/v0.3.11.md +0 -55
  26. package/docs/releases/v0.3.12.md +0 -44
  27. package/docs/releases/v0.3.13.md +0 -34
  28. package/docs/releases/v0.3.14.md +0 -37
  29. package/docs/releases/v0.3.2.md +0 -15
  30. package/docs/releases/v0.3.3.md +0 -25
  31. package/docs/releases/v0.3.4.md +0 -40
  32. package/docs/releases/v0.3.5.md +0 -50
  33. package/docs/releases/v0.3.6.md +0 -44
  34. package/docs/releases/v0.3.7.md +0 -37
  35. package/docs/releases/v0.3.8.md +0 -21
  36. package/docs/releases/v0.3.9.md +0 -36
  37. package/scripts/run-agent-task-benchmark.mjs +0 -118
  38. package/src/analysis/allowed-test-runner.js +0 -70
  39. package/src/analysis/architecture/contract-graph.js +0 -119
  40. package/src/analysis/architecture/contract-schema.js +0 -110
  41. package/src/analysis/architecture/contract-storage.js +0 -35
  42. package/src/analysis/architecture/contract-verification.js +0 -168
  43. package/src/analysis/architecture-contract.js +0 -16
  44. package/src/analysis/audit-debt.js +0 -94
  45. package/src/analysis/audit-extensions.js +0 -60
  46. package/src/analysis/cargo-dependency-evidence.js +0 -108
  47. package/src/analysis/cargo-manifests.js +0 -81
  48. package/src/analysis/change-classification/diff-parser.js +0 -103
  49. package/src/analysis/change-classification/options.js +0 -40
  50. package/src/analysis/change-classification/symbol-classifier.js +0 -177
  51. package/src/analysis/change-classification.js +0 -188
  52. package/src/analysis/coverage-reports-tarpaulin.js +0 -112
  53. package/src/analysis/coverage-reports.js +0 -246
  54. package/src/analysis/cycle-route.js +0 -14
  55. package/src/analysis/data-flow-evidence.js +0 -114
  56. package/src/analysis/dead-check.js +0 -300
  57. package/src/analysis/dead-code-review/candidates.js +0 -194
  58. package/src/analysis/dead-code-review/policy.js +0 -23
  59. package/src/analysis/dead-code-review.js +0 -119
  60. package/src/analysis/dep-check-ecosystems.js +0 -205
  61. package/src/analysis/dep-check.js +0 -290
  62. package/src/analysis/dep-rules.js +0 -10
  63. package/src/analysis/dependency/conventions.js +0 -16
  64. package/src/analysis/dependency/scoped-dependencies.js +0 -51
  65. package/src/analysis/dependency/source-references.js +0 -21
  66. package/src/analysis/duplicate-groups.js +0 -104
  67. package/src/analysis/duplicates-worker.js +0 -11
  68. package/src/analysis/duplicates.compute.js +0 -269
  69. package/src/analysis/duplicates.js +0 -15
  70. package/src/analysis/duplicates.run.js +0 -52
  71. package/src/analysis/duplicates.tokenize.js +0 -199
  72. package/src/analysis/endpoints/common.js +0 -79
  73. package/src/analysis/endpoints/extract.js +0 -120
  74. package/src/analysis/endpoints/inventory.js +0 -84
  75. package/src/analysis/endpoints/mounts.js +0 -129
  76. package/src/analysis/endpoints-java.js +0 -260
  77. package/src/analysis/endpoints-rust.js +0 -124
  78. package/src/analysis/endpoints.js +0 -3
  79. package/src/analysis/findings.js +0 -70
  80. package/src/analysis/git-history/analytics.js +0 -177
  81. package/src/analysis/git-history/collector.js +0 -67
  82. package/src/analysis/git-history/options.js +0 -66
  83. package/src/analysis/git-history.js +0 -143
  84. package/src/analysis/git-ref-graph.js +0 -76
  85. package/src/analysis/go-dependency-evidence.js +0 -62
  86. package/src/analysis/graph-analysis.aggregate.js +0 -296
  87. package/src/analysis/graph-analysis.edges.js +0 -24
  88. package/src/analysis/graph-analysis.js +0 -7
  89. package/src/analysis/graph-analysis.refs.js +0 -202
  90. package/src/analysis/graph-analysis.summaries.js +0 -65
  91. package/src/analysis/health-capabilities.js +0 -83
  92. package/src/analysis/hot-path-review.js +0 -266
  93. package/src/analysis/http-contract-wrappers.js +0 -241
  94. package/src/analysis/http-contracts/analysis.js +0 -179
  95. package/src/analysis/http-contracts/client-call-detection.js +0 -82
  96. package/src/analysis/http-contracts/client-call-parser.js +0 -273
  97. package/src/analysis/http-contracts/graph-context.js +0 -143
  98. package/src/analysis/http-contracts/matching.js +0 -61
  99. package/src/analysis/http-contracts/shared.js +0 -85
  100. package/src/analysis/http-contracts.js +0 -6
  101. package/src/analysis/internal-audit/dependency-health.js +0 -113
  102. package/src/analysis/internal-audit/python-manifests.js +0 -78
  103. package/src/analysis/internal-audit/repo-files.js +0 -50
  104. package/src/analysis/internal-audit/supply-chain.js +0 -149
  105. package/src/analysis/internal-audit.collect.js +0 -213
  106. package/src/analysis/internal-audit.js +0 -9
  107. package/src/analysis/internal-audit.reach.js +0 -201
  108. package/src/analysis/internal-audit.run.js +0 -256
  109. package/src/analysis/java-source.js +0 -36
  110. package/src/analysis/jvm-artifact-index.js +0 -87
  111. package/src/analysis/jvm-dependency-evidence.js +0 -138
  112. package/src/analysis/jvm-manifests.js +0 -114
  113. package/src/analysis/manifests.js +0 -123
  114. package/src/analysis/package-reachability.js +0 -39
  115. package/src/analysis/source-complexity.ast.js +0 -97
  116. package/src/analysis/source-complexity.constants.js +0 -55
  117. package/src/analysis/source-complexity.js +0 -14
  118. package/src/analysis/source-complexity.report.js +0 -81
  119. package/src/analysis/source-complexity.walk.js +0 -228
  120. package/src/analysis/source-correctness/retry-patterns.js +0 -93
  121. package/src/analysis/source-correctness.js +0 -222
  122. package/src/analysis/static-test-reachability.js +0 -150
  123. package/src/analysis/structure/dependency-graph.js +0 -151
  124. package/src/analysis/structure/findings.js +0 -162
  125. package/src/analysis/task-retrieval.js +0 -107
  126. package/src/analysis/transport-contracts.js +0 -224
  127. package/src/analysis/transport-runtime-evidence.js +0 -155
  128. package/src/analysis-kit.mjs +0 -17
  129. package/src/bounds.js +0 -4
  130. package/src/build-graph.js +0 -209
  131. package/src/child-env.js +0 -7
  132. package/src/config.js +0 -5
  133. package/src/extension/local-services.mjs +0 -75
  134. package/src/git-exec.js +0 -67
  135. package/src/graph/build-worker.js +0 -49
  136. package/src/graph/builder/go-receiver-resolution.js +0 -112
  137. package/src/graph/builder/js/dynamic-import.js +0 -26
  138. package/src/graph/builder/js/queries.js +0 -48
  139. package/src/graph/builder/lang-csharp.js +0 -40
  140. package/src/graph/builder/lang-css.js +0 -29
  141. package/src/graph/builder/lang-go.js +0 -140
  142. package/src/graph/builder/lang-html.js +0 -29
  143. package/src/graph/builder/lang-java-calls.js +0 -142
  144. package/src/graph/builder/lang-java.js +0 -215
  145. package/src/graph/builder/lang-js.js +0 -291
  146. package/src/graph/builder/lang-python.js +0 -235
  147. package/src/graph/builder/lang-rust-calls.js +0 -63
  148. package/src/graph/builder/lang-rust.js +0 -299
  149. package/src/graph/builder/lang-solidity.js +0 -126
  150. package/src/graph/builder/lang-sql.js +0 -263
  151. package/src/graph/builder/pass2-resolution.js +0 -81
  152. package/src/graph/builder/spec-pkg.js +0 -87
  153. package/src/graph/community.js +0 -27
  154. package/src/graph/edge-provenance.js +0 -31
  155. package/src/graph/file-lock.js +0 -69
  156. package/src/graph/freshness-probe.js +0 -142
  157. package/src/graph/graph-filter.js +0 -63
  158. package/src/graph/incremental-refresh.js +0 -235
  159. package/src/graph/internal-builder.barrels.js +0 -235
  160. package/src/graph/internal-builder.build.js +0 -300
  161. package/src/graph/internal-builder.java.js +0 -33
  162. package/src/graph/internal-builder.js +0 -12
  163. package/src/graph/internal-builder.langs.js +0 -157
  164. package/src/graph/internal-builder.pass2.js +0 -163
  165. package/src/graph/internal-builder.resolvers.js +0 -286
  166. package/src/graph/layout.js +0 -51
  167. package/src/graph/node-id.js +0 -7
  168. package/src/graph/parser-artifact-boundary.js +0 -55
  169. package/src/graph/relations.js +0 -4
  170. package/src/graph/repo-registry.js +0 -124
  171. package/src/graph/resolvers/rust.js +0 -117
  172. package/src/infra/infra-items.js +0 -255
  173. package/src/infra/infra-registry.js +0 -184
  174. package/src/infra/infra.detect.js +0 -119
  175. package/src/infra/infra.js +0 -38
  176. package/src/infra/infra.match.js +0 -102
  177. package/src/infra/infra.scan.js +0 -93
  178. package/src/mcp/actions/graph-lifecycle.mjs +0 -148
  179. package/src/mcp/architecture-bootstrap.mjs +0 -168
  180. package/src/mcp/architecture-starter.mjs +0 -234
  181. package/src/mcp/catalog.mjs +0 -233
  182. package/src/mcp/company-contract-page.mjs +0 -142
  183. package/src/mcp/company-contract-reconcile.mjs +0 -87
  184. package/src/mcp/company-contract-verdict.mjs +0 -42
  185. package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
  186. package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
  187. package/src/mcp/evidence/package-graph-common.mjs +0 -115
  188. package/src/mcp/evidence/package-lock-graph.mjs +0 -92
  189. package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
  190. package/src/mcp/evidence-snapshot.common.mjs +0 -158
  191. package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
  192. package/src/mcp/evidence-snapshot.health.mjs +0 -102
  193. package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
  194. package/src/mcp/evidence-snapshot.mjs +0 -50
  195. package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
  196. package/src/mcp/evidence-snapshot.structure.mjs +0 -84
  197. package/src/mcp/extension-api.mjs +0 -77
  198. package/src/mcp/git-output.mjs +0 -7
  199. package/src/mcp/graph/context-core.mjs +0 -111
  200. package/src/mcp/graph/context-seeds.mjs +0 -207
  201. package/src/mcp/graph/context-state.mjs +0 -85
  202. package/src/mcp/graph/reverse-reach.mjs +0 -42
  203. package/src/mcp/graph/tools-core.mjs +0 -143
  204. package/src/mcp/graph/tools-query.mjs +0 -276
  205. package/src/mcp/graph-context.mjs +0 -5
  206. package/src/mcp/graph-diff.mjs +0 -218
  207. package/src/mcp/health/audit-format.mjs +0 -199
  208. package/src/mcp/health/audit.mjs +0 -199
  209. package/src/mcp/health/dead-code.mjs +0 -110
  210. package/src/mcp/health/duplicates.mjs +0 -121
  211. package/src/mcp/health/endpoints.mjs +0 -71
  212. package/src/mcp/health/structure.mjs +0 -220
  213. package/src/mcp/runtime-version.mjs +0 -31
  214. package/src/mcp/server/auto-refresh.mjs +0 -66
  215. package/src/mcp/server/runtime-config.mjs +0 -43
  216. package/src/mcp/server/shutdown.mjs +0 -40
  217. package/src/mcp/staleness-notice.mjs +0 -20
  218. package/src/mcp/sync/evidence-architecture.mjs +0 -54
  219. package/src/mcp/sync/evidence-common.mjs +0 -88
  220. package/src/mcp/sync/evidence-duplicates.mjs +0 -97
  221. package/src/mcp/sync/evidence-health.mjs +0 -56
  222. package/src/mcp/sync/evidence-packages.mjs +0 -95
  223. package/src/mcp/sync/payload-common.mjs +0 -97
  224. package/src/mcp/sync/payload-v2.mjs +0 -54
  225. package/src/mcp/sync/payload-v3.mjs +0 -79
  226. package/src/mcp/sync-evidence.mjs +0 -23
  227. package/src/mcp/sync-payload.mjs +0 -70
  228. package/src/mcp/tool-result.mjs +0 -54
  229. package/src/mcp/tools-actions.mjs +0 -11
  230. package/src/mcp/tools-architecture.mjs +0 -177
  231. package/src/mcp/tools-company.mjs +0 -251
  232. package/src/mcp/tools-context.mjs +0 -222
  233. package/src/mcp/tools-endpoints.mjs +0 -207
  234. package/src/mcp/tools-graph-hubs.mjs +0 -78
  235. package/src/mcp/tools-graph.mjs +0 -17
  236. package/src/mcp/tools-health.mjs +0 -24
  237. package/src/mcp/tools-history.mjs +0 -28
  238. package/src/mcp/tools-impact-change.mjs +0 -220
  239. package/src/mcp/tools-impact-precision.mjs +0 -89
  240. package/src/mcp/tools-impact.mjs +0 -169
  241. package/src/mcp/tools-source.mjs +0 -238
  242. package/src/mcp/tools-verified-change.mjs +0 -200
  243. package/src/mcp-rg.mjs +0 -50
  244. package/src/mcp-runtime.mjs +0 -2
  245. package/src/mcp-server.mjs +0 -298
  246. package/src/mcp-source-tools.mjs +0 -163
  247. package/src/path-classification.js +0 -226
  248. package/src/path-ignore.js +0 -69
  249. package/src/precision/lsp-client/constants.js +0 -12
  250. package/src/precision/lsp-client/environment.js +0 -20
  251. package/src/precision/lsp-client/errors.js +0 -15
  252. package/src/precision/lsp-client/lifecycle.js +0 -114
  253. package/src/precision/lsp-client/message-parser.js +0 -81
  254. package/src/precision/lsp-client/protocol.js +0 -212
  255. package/src/precision/lsp-client/registry.js +0 -59
  256. package/src/precision/lsp-client/repo-uri.js +0 -60
  257. package/src/precision/lsp-client/stdio-client.js +0 -140
  258. package/src/precision/lsp-client.js +0 -10
  259. package/src/precision/lsp-overlay/budget-policy.js +0 -78
  260. package/src/precision/lsp-overlay/build.js +0 -261
  261. package/src/precision/lsp-overlay/contract.js +0 -61
  262. package/src/precision/lsp-overlay/reference-results.js +0 -108
  263. package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
  264. package/src/precision/lsp-overlay/source-session.js +0 -142
  265. package/src/precision/lsp-overlay/store.js +0 -150
  266. package/src/precision/lsp-overlay/target-index.js +0 -152
  267. package/src/precision/lsp-overlay/target-query.js +0 -55
  268. package/src/precision/lsp-overlay.js +0 -11
  269. package/src/precision/symbol-query.js +0 -224
  270. package/src/precision/typescript-lsp-provider.js +0 -12
  271. package/src/precision/typescript-provider/client.js +0 -89
  272. package/src/precision/typescript-provider/discovery.js +0 -124
  273. package/src/precision/typescript-provider/isolated-runtime.js +0 -39
  274. package/src/precision/typescript-provider/project-host.js +0 -254
  275. package/src/precision/typescript-provider/project-safety.js +0 -166
  276. package/src/precision/typescript-provider/reference-usage.js +0 -53
  277. package/src/process.js +0 -95
  278. package/src/repo-path.js +0 -53
  279. package/src/scan/discover.inventory.js +0 -78
  280. package/src/scan/discover.js +0 -5
  281. package/src/scan/discover.list.js +0 -79
  282. package/src/scan/discover.stack.js +0 -227
  283. package/src/scan/search.core.js +0 -24
  284. package/src/scan/search.git.js +0 -102
  285. package/src/scan/search.js +0 -9
  286. package/src/scan/search.node.js +0 -83
  287. package/src/scan/search.preview.js +0 -49
  288. package/src/scan/search.rg.js +0 -145
  289. package/src/security/advisory-store.js +0 -172
  290. package/src/security/installed-jvm-rust.js +0 -46
  291. package/src/security/installed.js +0 -280
  292. package/src/security/malware-file-heuristics.js +0 -121
  293. package/src/security/malware-heuristics.exclusions.js +0 -134
  294. package/src/security/malware-heuristics.js +0 -15
  295. package/src/security/malware-heuristics.roots.js +0 -145
  296. package/src/security/malware-heuristics.scan.js +0 -89
  297. package/src/security/malware-heuristics.sweep.js +0 -122
  298. package/src/security/malware-scoring.js +0 -105
  299. package/src/security/match.js +0 -85
  300. package/src/security/registry-sig.classify.js +0 -137
  301. package/src/security/registry-sig.js +0 -18
  302. package/src/security/registry-sig.rules.js +0 -192
  303. package/src/security/rust-advisory-report.js +0 -60
  304. package/src/security/typosquat.js +0 -81
  305. package/src/util.js +0 -40
  306. 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
- }