weavatrix 0.3.14 → 1.0.0

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