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,179 +0,0 @@
1
- import { detectEndpoints } from "../endpoints.js";
2
- import { detectHttpClientCalls } from "./client-call-detection.js";
3
- import { affectedForEndpoint, externalUseLiveness, handlerNodeEvidence, reverseRuntimeImports } from "./graph-context.js";
4
- import { matchHttpContract, methodMatches, pathSegments, routeShapeContains, routeShapeMatches, suffixShapeMatch } from "./matching.js";
5
- import {
6
- HTTP_CONTRACTS_V,
7
- HTTP_CONTRACT_METHODS,
8
- filesFromGraph,
9
- normalizeContractFile,
10
- normalizeHttpContractLimits,
11
- normalizeHttpContractPath,
12
- safeContractName,
13
- } from "./shared.js";
14
-
15
- const descriptorFiles = (descriptor) => descriptor.codeFiles || filesFromGraph(descriptor.graph);
16
-
17
- function endpointFilter(endpoint, backendId, options) {
18
- if (options.method && endpoint.method !== options.method && endpoint.method !== "ANY" && endpoint.method !== "ALL") return false;
19
- if (options.path) {
20
- const requested = normalizeHttpContractPath(options.path);
21
- const endpointSegments = pathSegments(normalizeHttpContractPath(endpoint.path));
22
- const requestedSegments = pathSegments(requested);
23
- if (!requested || (!routeShapeMatches(endpointSegments, requestedSegments) && !routeShapeContains(endpointSegments, requestedSegments))) return false;
24
- }
25
- if (options.changedFiles?.size) {
26
- const file = normalizeContractFile(endpoint.file);
27
- if (!options.changedFiles.has(file) && !options.changedFiles.has(`${backendId}::${file}`)) return false;
28
- }
29
- return true;
30
- }
31
-
32
- export function analyzeHttpContracts(input = {}) {
33
- const limits = normalizeHttpContractLimits(input);
34
- const method = input.method ? String(input.method).toUpperCase() : null;
35
- if (method && !HTTP_CONTRACT_METHODS.has(method)) throw new Error("method must be a concrete HTTP method");
36
- const changedFiles = new Set((Array.isArray(input.changedFiles) ? input.changedFiles : []).map((file) => normalizeContractFile(file)).filter(Boolean));
37
- const backendDescriptors = (Array.isArray(input.backends) ? input.backends : input.backend ? [input.backend] : []).slice(0, 20);
38
- const clientDescriptors = (Array.isArray(input.clients) ? input.clients : input.client ? [input.client] : []).slice(0, 20);
39
- const completeness = [], backends = [];
40
- let endpointBudget = limits.maxEndpoints;
41
- for (let index = 0; index < backendDescriptors.length; index++) {
42
- const descriptor = backendDescriptors[index] || {};
43
- const id = safeContractName(descriptor.id, `backend-${index + 1}`);
44
- const candidates = descriptorFiles(descriptor).slice().sort();
45
- if (candidates.length > limits.maxBackendFiles) completeness.push(`${id}: backend file cap reached`);
46
- const detected = Array.isArray(descriptor.endpoints)
47
- ? descriptor.endpoints
48
- : detectEndpoints(descriptor.repoRoot, candidates.slice(0, limits.maxBackendFiles));
49
- const filtered = detected
50
- .filter((endpoint) => normalizeHttpContractPath(endpoint?.path) && normalizeContractFile(endpoint?.file) && endpointFilter(endpoint, id, { method, path: input.path, changedFiles }))
51
- .sort((left, right) => left.path.localeCompare(right.path) || left.method.localeCompare(right.method) || String(left.file).localeCompare(String(right.file)) || Number(left.line) - Number(right.line));
52
- if (filtered.length > endpointBudget) completeness.push(`${id}: endpoint cap reached`);
53
- const accepted = filtered.slice(0, endpointBudget);
54
- endpointBudget -= accepted.length;
55
- backends.push({ id, endpoints: accepted, graph: descriptor.graph });
56
- }
57
-
58
- const clients = [];
59
- for (let index = 0; index < clientDescriptors.length; index++) {
60
- const descriptor = clientDescriptors[index] || {};
61
- const id = safeContractName(descriptor.id, `client-${index + 1}`);
62
- const detected = detectHttpClientCalls(descriptor.repoRoot, descriptorFiles(descriptor), {
63
- maxFiles: limits.maxClientFiles,
64
- maxCalls: limits.maxCallsPerClient,
65
- clientNames: descriptor.clientNames || input.clientNames,
66
- wrappers: descriptor.wrappers || input.wrappers,
67
- autoDiscoverWrappers: descriptor.autoDiscoverWrappers ?? input.autoDiscoverWrappers,
68
- graph: descriptor.graph,
69
- includeTests: descriptor.includeTests ?? input.includeTests,
70
- runtimeValues: descriptor.runtimeValues || input.runtimeValues,
71
- });
72
- if (detected.truncated) completeness.push(`${id}: client scan cap reached`);
73
- for (const reason of detected.reasons || []) completeness.push(`${id}: ${reason}`);
74
- clients.push({
75
- id,
76
- calls: detected.calls,
77
- reverse: reverseRuntimeImports(descriptor.graph),
78
- filesScanned: detected.filesScanned,
79
- wrapperDiscovery: detected.discovery,
80
- });
81
- }
82
-
83
- const results = [];
84
- let matches = 0, methodMismatches = 0, callsiteCapReached = false;
85
- for (const backend of backends) {
86
- for (const endpoint of backend.endpoints) {
87
- const callsites = [];
88
- let endpointMismatches = 0;
89
- const methodMismatchSites = [];
90
- for (const client of clients) {
91
- for (const call of client.calls) {
92
- if (call.path && !methodMatches(endpoint.method, call.method)) {
93
- const expected = pathSegments(normalizeHttpContractPath(endpoint.path));
94
- const actual = pathSegments(call.path);
95
- if (routeShapeMatches(expected, actual) || suffixShapeMatch(expected, actual)) {
96
- methodMismatches++;
97
- endpointMismatches++;
98
- if (methodMismatchSites.length < 3) methodMismatchSites.push({ clientRepo: client.id, file: call.file, line: call.line, method: call.method });
99
- }
100
- continue;
101
- }
102
- const match = matchHttpContract(endpoint, call);
103
- if (!match) continue;
104
- if (matches >= limits.maxMatches || callsites.length >= limits.maxCallsitesPerEndpoint) {
105
- callsiteCapReached = true;
106
- continue;
107
- }
108
- matches++;
109
- callsites.push({
110
- clientRepo: client.id,
111
- file: call.file,
112
- line: call.line,
113
- method: call.method,
114
- path: call.path,
115
- dynamic: call.dynamic,
116
- detector: call.detector,
117
- wrapper: call.wrapper,
118
- match,
119
- });
120
- }
121
- }
122
- callsites.sort((left, right) => right.match.score - left.match.score || left.clientRepo.localeCompare(right.clientRepo) || left.file.localeCompare(right.file) || left.line - right.line);
123
- const handlerEvidence = handlerNodeEvidence(endpoint, backend.graph);
124
- results.push({
125
- backend: backend.id,
126
- method: endpoint.method,
127
- path: endpoint.path,
128
- normalizedPath: normalizeHttpContractPath(endpoint.path),
129
- ...handlerEvidence,
130
- file: normalizeContractFile(endpoint.file) || null,
131
- line: Number(endpoint.line) || null,
132
- callsites,
133
- methodMismatches: endpointMismatches,
134
- methodMismatchSites,
135
- liveness: externalUseLiveness(callsites, handlerEvidence),
136
- affected: affectedForEndpoint(callsites, clients, limits),
137
- });
138
- }
139
- }
140
- if (callsiteCapReached) completeness.push("match or per-endpoint callsite cap reached");
141
-
142
- const uncertainAll = clients.flatMap((client) => client.calls
143
- .filter((call) => !call.path || call.unknownPrefix || call.partialDynamic || call.method === "UNKNOWN")
144
- .map((call) => ({
145
- clientRepo: client.id,
146
- file: call.file,
147
- line: call.line,
148
- method: call.method,
149
- reason: call.reason || "URL retains an unresolved dynamic component",
150
- })))
151
- .sort((left, right) => left.clientRepo.localeCompare(right.clientRepo) || left.file.localeCompare(right.file) || left.line - right.line);
152
- if (uncertainAll.length > limits.maxUncertain) completeness.push("uncertain callsite cap reached");
153
- if (uncertainAll.length) completeness.push(`${uncertainAll.length} dynamic HTTP callsite(s) remain UNKNOWN`);
154
- if (results.some((endpoint) => !endpoint.affected.complete)) completeness.push("affected-file traversal cap reached");
155
-
156
- return {
157
- httpContractsV: HTTP_CONTRACTS_V,
158
- status: completeness.length ? "partial" : "complete",
159
- filters: { method, path: input.path ? normalizeHttpContractPath(input.path) : null, changedFiles: [...changedFiles].sort() },
160
- limits,
161
- completeness: { complete: completeness.length === 0, reasons: [...new Set(completeness)] },
162
- totals: {
163
- backends: backends.length,
164
- clients: clients.length,
165
- endpoints: results.length,
166
- clientCalls: clients.reduce((sum, client) => sum + client.calls.length, 0),
167
- matches,
168
- methodMismatches,
169
- uncertainCalls: uncertainAll.length,
170
- notDeadExternalUse: results.filter((endpoint) => endpoint.liveness.status === "NOT_DEAD_EXTERNAL_USE").length,
171
- notDeadExternalHandlers: results.filter((endpoint) => endpoint.liveness.canSuppressDeadCandidate).length,
172
- possibleExternalUse: results.filter((endpoint) => endpoint.liveness.status === "POSSIBLE_EXTERNAL_USE").length,
173
- unknownLiveness: results.filter((endpoint) => endpoint.liveness.status === "UNKNOWN").length,
174
- },
175
- wrapperDiscovery: clients.map((client) => ({ clientRepo: client.id, ...client.wrapperDiscovery })),
176
- endpoints: results,
177
- uncertain: uncertainAll.slice(0, limits.maxUncertain),
178
- };
179
- }
@@ -1,82 +0,0 @@
1
- import { createPathClassifier, hasPathClass } from "../../path-classification.js";
2
- import { isWeavatrixIgnored, loadWeavatrixIgnore } from "../../path-ignore.js";
3
- import { createRepoBoundary } from "../../repo-path.js";
4
- import { safeRead } from "../../util.js";
5
- import {
6
- discoverHttpWrappers,
7
- loadHttpContractConfig,
8
- normalizeHttpClientNames,
9
- normalizeHttpWrapperDescriptors,
10
- } from "../http-contract-wrappers.js";
11
- import { wrapperScopeFiles } from "./graph-context.js";
12
- import { extractHttpClientCallsFromText } from "./client-call-parser.js";
13
- import {
14
- HTTP_CONTRACT_DEFAULTS,
15
- HTTP_CONTRACT_HARD_LIMITS,
16
- boundedInteger,
17
- normalizeContractFile,
18
- } from "./shared.js";
19
-
20
- export function detectHttpClientCalls(repoRoot, codeFiles, options = {}) {
21
- const boundary = createRepoBoundary(repoRoot);
22
- if (!boundary.root) return { calls: [], truncated: false, filesScanned: 0, discovery: { enabled: false, configured: 0, discovered: 0, ambiguous: [] }, reasons: [] };
23
- const maxFiles = boundedInteger(options.maxFiles, HTTP_CONTRACT_DEFAULTS.maxClientFiles, 1, HTTP_CONTRACT_HARD_LIMITS.maxClientFiles);
24
- const maxCalls = boundedInteger(options.maxCalls, HTTP_CONTRACT_DEFAULTS.maxCallsPerClient, 1, HTTP_CONTRACT_HARD_LIMITS.maxCallsPerClient);
25
- const ignoreRules = loadWeavatrixIgnore(boundary.root);
26
- const classifier = createPathClassifier(boundary.root);
27
- const candidates = [...new Set((codeFiles || []).map((entry) => normalizeContractFile(entry?.path || entry)).filter(Boolean))].sort();
28
- let truncated = candidates.length > maxFiles;
29
- let filesScanned = 0;
30
- const sources = [], calls = [];
31
- for (const file of candidates.slice(0, maxFiles)) {
32
- if (!/\.(?:[cm]?[jt]sx?|vue|svelte)$/i.test(file) || isWeavatrixIgnored(file, ignoreRules)) continue;
33
- const classification = classifier.explain(file, { content: "" });
34
- if (classification.excluded || (!options.includeTests && hasPathClass(classification, "test", "e2e"))) continue;
35
- const resolved = boundary.resolve(file);
36
- if (!resolved.ok) continue;
37
- const text = safeRead(resolved.path);
38
- if (!text) continue;
39
- filesScanned++;
40
- sources.push({ file, text });
41
- }
42
- const config = loadHttpContractConfig(boundary.root);
43
- const clientNames = [...new Set([...normalizeHttpClientNames(options.clientNames), ...config.clientNames])];
44
- const configured = [...normalizeHttpWrapperDescriptors(options.wrappers, "input"), ...config.wrappers];
45
- const discoveryEnabled = options.autoDiscoverWrappers !== false && config.autoDiscoverWrappers !== false;
46
- const discovered = discoveryEnabled ? discoverHttpWrappers(sources, clientNames) : { wrappers: [], ambiguous: [], truncated: false };
47
- const scopedDiscovered = discovered.wrappers.map((wrapper) => ({
48
- ...wrapper,
49
- allowedFiles: wrapperScopeFiles(wrapper.definitionFile, options.graph),
50
- }));
51
- for (const { file, text } of sources) {
52
- const remaining = maxCalls - calls.length;
53
- if (remaining <= 0) { truncated = true; break; }
54
- const extracted = extractHttpClientCallsFromText(text, file, {
55
- clientNames,
56
- normalizedWrappers: [...configured, ...scopedDiscovered],
57
- maxCalls: remaining,
58
- runtimeValues: options.runtimeValues,
59
- });
60
- calls.push(...extracted.calls);
61
- if (extracted.truncated) truncated = true;
62
- }
63
- calls.sort((left, right) => left.file.localeCompare(right.file) || left.line - right.line || left.method.localeCompare(right.method) || String(left.path).localeCompare(String(right.path)));
64
- const reasons = [];
65
- if (config.error) reasons.push(`HTTP contract config ${config.error}`);
66
- reasons.push(...(config.warnings || []));
67
- if (discovered.truncated) reasons.push("auto-discovered wrapper cap reached");
68
- if (discovered.ambiguous.length) reasons.push(`${discovered.ambiguous.length} ambiguous auto-discovered wrapper name(s) skipped`);
69
- return {
70
- calls,
71
- truncated,
72
- filesScanned,
73
- discovery: {
74
- enabled: discoveryEnabled,
75
- configured: configured.length,
76
- discovered: scopedDiscovered.length,
77
- ambiguous: discovered.ambiguous,
78
- truncated: discovered.truncated,
79
- },
80
- reasons,
81
- };
82
- }
@@ -1,273 +0,0 @@
1
- import { DEFAULT_HTTP_CLIENT_NAMES, normalizeHttpClientNames, normalizeHttpWrapperDescriptors } from "../http-contract-wrappers.js";
2
- import {
3
- HTTP_CONTRACT_DEFAULTS,
4
- HTTP_CONTRACT_HARD_LIMITS,
5
- boundedInteger,
6
- contractLineAt,
7
- normalizeContractFile,
8
- normalizeHttpContractPath,
9
- } from "./shared.js";
10
-
11
- function maskNonCode(text) {
12
- const chars = String(text || "").split("");
13
- let index = 0;
14
- while (index < chars.length) {
15
- const char = chars[index], next = chars[index + 1];
16
- if (char === "/" && next === "/") {
17
- chars[index++] = " "; chars[index++] = " ";
18
- while (index < chars.length && chars[index] !== "\n") chars[index++] = " ";
19
- continue;
20
- }
21
- if (char === "/" && next === "*") {
22
- chars[index++] = " "; chars[index++] = " ";
23
- while (index < chars.length) {
24
- if (chars[index] === "*" && chars[index + 1] === "/") { chars[index++] = " "; chars[index++] = " "; break; }
25
- if (chars[index] !== "\n") chars[index] = " ";
26
- index++;
27
- }
28
- continue;
29
- }
30
- if (char === "'" || char === '"' || char === "`") {
31
- const quote = char;
32
- chars[index++] = " ";
33
- while (index < chars.length) {
34
- if (chars[index] === "\\") {
35
- chars[index++] = " ";
36
- if (index < chars.length && chars[index] !== "\n") chars[index] = " ";
37
- index++;
38
- continue;
39
- }
40
- const closes = chars[index] === quote;
41
- if (chars[index] !== "\n") chars[index] = " ";
42
- index++;
43
- if (closes) break;
44
- }
45
- continue;
46
- }
47
- index++;
48
- }
49
- return chars.join("");
50
- }
51
-
52
- function quotedArgument(text, start, quote) {
53
- let value = "";
54
- for (let index = start + 1; index < text.length; index++) {
55
- const char = text[index];
56
- if (char === "\\") {
57
- if (index + 1 >= text.length) break;
58
- value += text[++index];
59
- continue;
60
- }
61
- if (char === quote) return { value, endIndex: index + 1 };
62
- if (char === "\n" || char === "\r") break;
63
- value += char;
64
- }
65
- return null;
66
- }
67
-
68
- function templateArgument(text, start, constants = null, requireStatic = false) {
69
- let value = "", dynamicSegments = 0, unknownPrefix = false, partialDynamic = false;
70
- for (let index = start + 1; index < text.length; index++) {
71
- const char = text[index];
72
- if (char === "\\") {
73
- if (index + 1 >= text.length) break;
74
- value += text[++index];
75
- continue;
76
- }
77
- if (char === "`") return { value, endIndex: index + 1, dynamicSegments, unknownPrefix, partialDynamic };
78
- if (char !== "$" || text[index + 1] !== "{") { value += char; continue; }
79
- let cursor = index + 2, depth = 1, quote = null;
80
- while (cursor < text.length && depth > 0) {
81
- const token = text[cursor];
82
- if (quote) {
83
- if (token === "\\") cursor += 2;
84
- else { if (token === quote) quote = null; cursor++; }
85
- continue;
86
- }
87
- if (token === "'" || token === '"' || token === "`") { quote = token; cursor++; continue; }
88
- if (token === "{") depth++;
89
- else if (token === "}") depth--;
90
- cursor++;
91
- }
92
- if (depth !== 0) return null;
93
- const expression = text.slice(index + 2, cursor - 1).trim();
94
- if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(expression) && constants?.has(expression)) {
95
- value += constants.get(expression);
96
- index = cursor - 1;
97
- continue;
98
- }
99
- if (requireStatic) return null;
100
- const next = text[cursor];
101
- const leftBoundary = value === "" || value.endsWith("/");
102
- const rightBoundary = !next || next === "/" || next === "?" || next === "#" || next === "`";
103
- dynamicSegments++;
104
- if (value === "" && next === "/") unknownPrefix = true;
105
- else if (leftBoundary && rightBoundary) value += ":param";
106
- else { value += ":dynamic"; partialDynamic = true; }
107
- index = cursor - 1;
108
- }
109
- return null;
110
- }
111
-
112
- function extractStaticStringConstants(text, runtimeValues = {}) {
113
- const source = String(text || "");
114
- const mask = maskNonCode(source);
115
- const declarations = [];
116
- const declaration = /\bconst\s+([A-Za-z_$][\w$]*)\s*=/g;
117
- let match;
118
- while ((match = declaration.exec(mask)) && declarations.length < 500) {
119
- let start = match.index + match[0].length;
120
- while (/\s/.test(source[start] || "")) start++;
121
- declarations.push({ name: match[1], start });
122
- }
123
- const constants = new Map(Object.entries(runtimeValues || {})
124
- .filter(([key, value]) => /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(key) && typeof value === "string" && value.length <= 2_048));
125
- for (const item of declarations) {
126
- const quote = source[item.start];
127
- if (quote !== "'" && quote !== '"') continue;
128
- const parsed = quotedArgument(source, item.start, quote);
129
- if (parsed && parsed.value.length <= 2_048) constants.set(item.name, parsed.value);
130
- }
131
- for (let pass = 0; pass < Math.min(8, declarations.length); pass++) {
132
- let changed = false;
133
- for (const item of declarations) {
134
- if (constants.has(item.name) || source[item.start] !== "`") continue;
135
- const parsed = templateArgument(source, item.start, constants, true);
136
- if (!parsed || parsed.value.length > 2_048) continue;
137
- constants.set(item.name, parsed.value);
138
- changed = true;
139
- }
140
- if (!changed) break;
141
- }
142
- for (const item of declarations) {
143
- if (constants.has(item.name)) continue;
144
- const rest = source.slice(item.start);
145
- const expression = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*/.exec(rest)?.[0];
146
- if (expression && constants.has(expression)) constants.set(item.name, constants.get(expression));
147
- if (constants.has(item.name)) continue;
148
- const fallback = /(?:\|\||\?\?)\s*(["'])/.exec(rest.slice(0, 500));
149
- if (fallback) {
150
- const start = item.start + fallback.index + fallback[0].lastIndexOf(fallback[1]);
151
- const parsed = quotedArgument(source, start, fallback[1]);
152
- if (parsed && parsed.value.length <= 2_048) constants.set(item.name, parsed.value);
153
- }
154
- }
155
- return constants;
156
- }
157
-
158
- function argumentStart(text, openParen, target) {
159
- let current = 0, round = 0, square = 0, curly = 0;
160
- for (let index = openParen + 1; index < text.length; index++) {
161
- const char = text[index];
162
- if (current === target && !/\s|,/.test(char)) return index;
163
- if (char === "'" || char === '"' || char === "`") {
164
- const quote = char;
165
- for (index++; index < text.length; index++) {
166
- if (text[index] === "\\") index++;
167
- else if (text[index] === quote) break;
168
- }
169
- continue;
170
- }
171
- if (char === "(") round++;
172
- else if (char === "[") square++;
173
- else if (char === "{") curly++;
174
- else if (char === ")") {
175
- if (round === 0 && square === 0 && curly === 0) return null;
176
- round--;
177
- } else if (char === "]") square--;
178
- else if (char === "}") curly--;
179
- else if (char === "," && round === 0 && square === 0 && curly === 0) current++;
180
- }
181
- return null;
182
- }
183
-
184
- function parseUrlArgument(text, openParen, constants, argument = 0) {
185
- let start = argumentStart(text, openParen, argument);
186
- if (start == null) return { path: null, endIndex: openParen, kind: "dynamic", dynamic: true, unknownPrefix: false, partialDynamic: true, reason: `URL argument ${argument} is missing` };
187
- while (/\s/.test(text[start] || "")) start++;
188
- const quote = text[start];
189
- if (quote === "'" || quote === '"') {
190
- const parsed = quotedArgument(text, start, quote);
191
- if (!parsed) return { path: null, endIndex: start, kind: "dynamic", dynamic: true, reason: "unterminated URL literal" };
192
- return { path: normalizeHttpContractPath(parsed.value), endIndex: parsed.endIndex, kind: "literal", dynamic: false, unknownPrefix: false, partialDynamic: false, reason: null };
193
- }
194
- if (quote === "`") {
195
- const parsed = templateArgument(text, start, constants);
196
- if (!parsed) return { path: null, endIndex: start, kind: "dynamic", dynamic: true, reason: "unterminated URL template" };
197
- return {
198
- path: normalizeHttpContractPath(parsed.value), endIndex: parsed.endIndex,
199
- kind: parsed.dynamicSegments ? "template" : "literal", dynamic: parsed.dynamicSegments > 0,
200
- unknownPrefix: parsed.unknownPrefix, partialDynamic: parsed.partialDynamic,
201
- reason: parsed.partialDynamic ? "URL template contains a partial dynamic segment" : null,
202
- };
203
- }
204
- return { path: null, endIndex: start, kind: "dynamic", dynamic: true, unknownPrefix: false, partialDynamic: true, reason: "URL argument is not a string or template literal" };
205
- }
206
-
207
- function fetchMethod(text, argumentEnd) {
208
- const tail = text.slice(argumentEnd, argumentEnd + 500);
209
- if (!/^\s*,/.test(tail)) return { method: "GET", uncertain: false };
210
- const config = tail.replace(/^\s*,\s*/, "");
211
- if (!config.startsWith("{") || /^\{\s*\.\.\./.test(config)) return { method: "UNKNOWN", uncertain: true };
212
- const literal = /\bmethod\s*:\s*(["'`])(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\1/i.exec(tail);
213
- if (literal) return { method: literal[2].toUpperCase(), uncertain: false };
214
- return /\bmethod\s*:/.test(tail) ? { method: "UNKNOWN", uncertain: true } : { method: "GET", uncertain: false };
215
- }
216
-
217
- function normalizedClientNames(values) {
218
- return new Set([...DEFAULT_HTTP_CLIENT_NAMES, ...normalizeHttpClientNames(values)]
219
- .map((value) => String(value || "").trim().toLowerCase())
220
- .filter((value) => /^[a-z_$][\w$]*$/i.test(value)));
221
- }
222
-
223
- const escapeRegex = (value) => String(value).replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
224
-
225
- export function extractHttpClientCallsFromText(text, file, options = {}) {
226
- const source = String(text || "");
227
- const mask = maskNonCode(source);
228
- const constants = extractStaticStringConstants(source, options.runtimeValues);
229
- const allowed = normalizedClientNames(options.clientNames);
230
- const wrappers = normalizeHttpWrapperDescriptors(options.wrappers, "input").concat(Array.isArray(options.normalizedWrappers) ? options.normalizedWrappers : []);
231
- const maxCalls = boundedInteger(options.maxCalls, HTTP_CONTRACT_DEFAULTS.maxCallsPerClient, 1, HTTP_CONTRACT_HARD_LIMITS.maxCallsPerClient);
232
- const calls = [], seen = new Set();
233
- let truncated = false;
234
- const add = (clientName, method, openParen, isFetch = false, urlArgument = 0, detector = "builtin", wrapper = null) => {
235
- const key = `${openParen}\0${method}\0${urlArgument}`;
236
- if (seen.has(key)) return;
237
- seen.add(key);
238
- if (calls.length >= maxCalls) { truncated = true; return; }
239
- const parsed = parseUrlArgument(source, openParen, constants, urlArgument);
240
- const fetchInfo = isFetch ? fetchMethod(source, parsed.endIndex) : { method: method.toUpperCase(), uncertain: false };
241
- calls.push({
242
- file: normalizeContractFile(file), line: contractLineAt(source, openParen), client: clientName, method: fetchInfo.method,
243
- path: parsed.path, kind: parsed.kind, dynamic: parsed.dynamic, unknownPrefix: Boolean(parsed.unknownPrefix),
244
- partialDynamic: Boolean(parsed.partialDynamic || fetchInfo.uncertain),
245
- reason: fetchInfo.uncertain ? "HTTP method is dynamic" : parsed.reason, detector, wrapper,
246
- });
247
- };
248
- const member = /(^|[^\w$])([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*(get|post|put|patch|delete|head|options)\s*(?:<[^>\n]{1,200}>)?\s*\(/gim;
249
- let match;
250
- while ((match = member.exec(mask))) if (allowed.has(match[2].toLowerCase())) add(match[2], match[3], member.lastIndex - 1);
251
- // Detect where the fetch Web API is invoked in the ANALYZED source. The token is assembled
252
- // at runtime so this static-analysis detector carries no literal call-shape of its own
253
- // (this module performs no network I/O itself; enforced by offline-artifact-boundary).
254
- const FETCH_CLIENT = "fetch";
255
- const fetchCall = new RegExp(`(^|[^\\w$])${FETCH_CLIENT}\\s*\\(`, "gim");
256
- while ((match = fetchCall.exec(mask))) add(FETCH_CLIENT, "GET", fetchCall.lastIndex - 1, true);
257
- for (const wrapper of wrappers) {
258
- if (wrapper.allowedFiles instanceof Set && !wrapper.allowedFiles.has(normalizeContractFile(file))) continue;
259
- if (wrapper.kind === "function") {
260
- const bare = new RegExp(`(^|[^\\w$.?])${escapeRegex(wrapper.call)}\\s*(?:<[^>\\n]{1,200}>)?\\s*\\(`, "gim");
261
- while ((match = bare.exec(mask))) {
262
- const nameAt = bare.lastIndex - match[0].length + match[1].length;
263
- if (/\bfunction\s*$/i.test(mask.slice(Math.max(0, nameAt - 30), nameAt))) continue;
264
- add(wrapper.call, wrapper.method, bare.lastIndex - 1, false, wrapper.urlArgument, `${wrapper.source}-wrapper`, { kind: wrapper.kind, call: wrapper.call, definitionFile: wrapper.definitionFile || null });
265
- }
266
- } else if (wrapper.kind === "member") {
267
- const memberCall = new RegExp(`(^|[^\\w$])${escapeRegex(wrapper.object)}\\s*(?:\\?\\.|\\.)\\s*${escapeRegex(wrapper.member)}\\s*(?:<[^>\\n]{1,200}>)?\\s*\\(`, "gim");
268
- while ((match = memberCall.exec(mask))) add(`${wrapper.object}.${wrapper.member}`, wrapper.method, memberCall.lastIndex - 1, false, wrapper.urlArgument, `${wrapper.source}-wrapper`, { kind: wrapper.kind, object: wrapper.object, member: wrapper.member, definitionFile: wrapper.definitionFile || null });
269
- }
270
- }
271
- calls.sort((left, right) => left.file.localeCompare(right.file) || left.line - right.line || left.method.localeCompare(right.method) || String(left.path).localeCompare(String(right.path)));
272
- return { calls, truncated };
273
- }