weavatrix 0.3.14 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +132 -200
  3. package/bin/native/darwin-arm64/weavatrix +0 -0
  4. package/bin/native/darwin-x64/weavatrix +0 -0
  5. package/bin/native/linux-arm64/weavatrix +0 -0
  6. package/bin/native/linux-x64/weavatrix +0 -0
  7. package/bin/native/win32-arm64/weavatrix.exe +0 -0
  8. package/bin/native/win32-x64/weavatrix.exe +0 -0
  9. package/bin/resolve-binary.mjs +73 -0
  10. package/bin/run-native.mjs +26 -0
  11. package/bin/weavatrix-mcp.mjs +7 -6
  12. package/bin/weavatrix.mjs +5 -0
  13. package/package.json +10 -67
  14. package/server.json +38 -0
  15. package/skill/SKILL.md +62 -374
  16. package/skill/agents/openai.yaml +4 -0
  17. package/skill/references/tool-routing.md +45 -0
  18. package/SECURITY.md +0 -101
  19. package/docs/adr/0001-v0.3-offline-online-split.md +0 -60
  20. package/docs/agent-task-benchmark.md +0 -58
  21. package/docs/releases/v0.2.19.md +0 -33
  22. package/docs/releases/v0.3.0.md +0 -43
  23. package/docs/releases/v0.3.1.md +0 -30
  24. package/docs/releases/v0.3.10.md +0 -76
  25. package/docs/releases/v0.3.11.md +0 -55
  26. package/docs/releases/v0.3.12.md +0 -44
  27. package/docs/releases/v0.3.13.md +0 -34
  28. package/docs/releases/v0.3.14.md +0 -37
  29. package/docs/releases/v0.3.2.md +0 -15
  30. package/docs/releases/v0.3.3.md +0 -25
  31. package/docs/releases/v0.3.4.md +0 -40
  32. package/docs/releases/v0.3.5.md +0 -50
  33. package/docs/releases/v0.3.6.md +0 -44
  34. package/docs/releases/v0.3.7.md +0 -37
  35. package/docs/releases/v0.3.8.md +0 -21
  36. package/docs/releases/v0.3.9.md +0 -36
  37. package/scripts/run-agent-task-benchmark.mjs +0 -118
  38. package/src/analysis/allowed-test-runner.js +0 -70
  39. package/src/analysis/architecture/contract-graph.js +0 -119
  40. package/src/analysis/architecture/contract-schema.js +0 -110
  41. package/src/analysis/architecture/contract-storage.js +0 -35
  42. package/src/analysis/architecture/contract-verification.js +0 -168
  43. package/src/analysis/architecture-contract.js +0 -16
  44. package/src/analysis/audit-debt.js +0 -94
  45. package/src/analysis/audit-extensions.js +0 -60
  46. package/src/analysis/cargo-dependency-evidence.js +0 -108
  47. package/src/analysis/cargo-manifests.js +0 -81
  48. package/src/analysis/change-classification/diff-parser.js +0 -103
  49. package/src/analysis/change-classification/options.js +0 -40
  50. package/src/analysis/change-classification/symbol-classifier.js +0 -177
  51. package/src/analysis/change-classification.js +0 -188
  52. package/src/analysis/coverage-reports-tarpaulin.js +0 -112
  53. package/src/analysis/coverage-reports.js +0 -246
  54. package/src/analysis/cycle-route.js +0 -14
  55. package/src/analysis/data-flow-evidence.js +0 -114
  56. package/src/analysis/dead-check.js +0 -300
  57. package/src/analysis/dead-code-review/candidates.js +0 -194
  58. package/src/analysis/dead-code-review/policy.js +0 -23
  59. package/src/analysis/dead-code-review.js +0 -119
  60. package/src/analysis/dep-check-ecosystems.js +0 -205
  61. package/src/analysis/dep-check.js +0 -290
  62. package/src/analysis/dep-rules.js +0 -10
  63. package/src/analysis/dependency/conventions.js +0 -16
  64. package/src/analysis/dependency/scoped-dependencies.js +0 -51
  65. package/src/analysis/dependency/source-references.js +0 -21
  66. package/src/analysis/duplicate-groups.js +0 -104
  67. package/src/analysis/duplicates-worker.js +0 -11
  68. package/src/analysis/duplicates.compute.js +0 -269
  69. package/src/analysis/duplicates.js +0 -15
  70. package/src/analysis/duplicates.run.js +0 -52
  71. package/src/analysis/duplicates.tokenize.js +0 -199
  72. package/src/analysis/endpoints/common.js +0 -79
  73. package/src/analysis/endpoints/extract.js +0 -120
  74. package/src/analysis/endpoints/inventory.js +0 -84
  75. package/src/analysis/endpoints/mounts.js +0 -129
  76. package/src/analysis/endpoints-java.js +0 -260
  77. package/src/analysis/endpoints-rust.js +0 -124
  78. package/src/analysis/endpoints.js +0 -3
  79. package/src/analysis/findings.js +0 -70
  80. package/src/analysis/git-history/analytics.js +0 -177
  81. package/src/analysis/git-history/collector.js +0 -67
  82. package/src/analysis/git-history/options.js +0 -66
  83. package/src/analysis/git-history.js +0 -143
  84. package/src/analysis/git-ref-graph.js +0 -76
  85. package/src/analysis/go-dependency-evidence.js +0 -62
  86. package/src/analysis/graph-analysis.aggregate.js +0 -296
  87. package/src/analysis/graph-analysis.edges.js +0 -24
  88. package/src/analysis/graph-analysis.js +0 -7
  89. package/src/analysis/graph-analysis.refs.js +0 -202
  90. package/src/analysis/graph-analysis.summaries.js +0 -65
  91. package/src/analysis/health-capabilities.js +0 -83
  92. package/src/analysis/hot-path-review.js +0 -266
  93. package/src/analysis/http-contract-wrappers.js +0 -241
  94. package/src/analysis/http-contracts/analysis.js +0 -179
  95. package/src/analysis/http-contracts/client-call-detection.js +0 -82
  96. package/src/analysis/http-contracts/client-call-parser.js +0 -273
  97. package/src/analysis/http-contracts/graph-context.js +0 -143
  98. package/src/analysis/http-contracts/matching.js +0 -61
  99. package/src/analysis/http-contracts/shared.js +0 -85
  100. package/src/analysis/http-contracts.js +0 -6
  101. package/src/analysis/internal-audit/dependency-health.js +0 -113
  102. package/src/analysis/internal-audit/python-manifests.js +0 -78
  103. package/src/analysis/internal-audit/repo-files.js +0 -50
  104. package/src/analysis/internal-audit/supply-chain.js +0 -149
  105. package/src/analysis/internal-audit.collect.js +0 -213
  106. package/src/analysis/internal-audit.js +0 -9
  107. package/src/analysis/internal-audit.reach.js +0 -201
  108. package/src/analysis/internal-audit.run.js +0 -256
  109. package/src/analysis/java-source.js +0 -36
  110. package/src/analysis/jvm-artifact-index.js +0 -87
  111. package/src/analysis/jvm-dependency-evidence.js +0 -138
  112. package/src/analysis/jvm-manifests.js +0 -114
  113. package/src/analysis/manifests.js +0 -123
  114. package/src/analysis/package-reachability.js +0 -39
  115. package/src/analysis/source-complexity.ast.js +0 -97
  116. package/src/analysis/source-complexity.constants.js +0 -55
  117. package/src/analysis/source-complexity.js +0 -14
  118. package/src/analysis/source-complexity.report.js +0 -81
  119. package/src/analysis/source-complexity.walk.js +0 -228
  120. package/src/analysis/source-correctness/retry-patterns.js +0 -93
  121. package/src/analysis/source-correctness.js +0 -222
  122. package/src/analysis/static-test-reachability.js +0 -150
  123. package/src/analysis/structure/dependency-graph.js +0 -151
  124. package/src/analysis/structure/findings.js +0 -162
  125. package/src/analysis/task-retrieval.js +0 -107
  126. package/src/analysis/transport-contracts.js +0 -224
  127. package/src/analysis/transport-runtime-evidence.js +0 -155
  128. package/src/analysis-kit.mjs +0 -17
  129. package/src/bounds.js +0 -4
  130. package/src/build-graph.js +0 -209
  131. package/src/child-env.js +0 -7
  132. package/src/config.js +0 -5
  133. package/src/extension/local-services.mjs +0 -75
  134. package/src/git-exec.js +0 -67
  135. package/src/graph/build-worker.js +0 -49
  136. package/src/graph/builder/go-receiver-resolution.js +0 -112
  137. package/src/graph/builder/js/dynamic-import.js +0 -26
  138. package/src/graph/builder/js/queries.js +0 -48
  139. package/src/graph/builder/lang-csharp.js +0 -40
  140. package/src/graph/builder/lang-css.js +0 -29
  141. package/src/graph/builder/lang-go.js +0 -140
  142. package/src/graph/builder/lang-html.js +0 -29
  143. package/src/graph/builder/lang-java-calls.js +0 -142
  144. package/src/graph/builder/lang-java.js +0 -215
  145. package/src/graph/builder/lang-js.js +0 -291
  146. package/src/graph/builder/lang-python.js +0 -235
  147. package/src/graph/builder/lang-rust-calls.js +0 -63
  148. package/src/graph/builder/lang-rust.js +0 -299
  149. package/src/graph/builder/lang-solidity.js +0 -126
  150. package/src/graph/builder/lang-sql.js +0 -263
  151. package/src/graph/builder/pass2-resolution.js +0 -81
  152. package/src/graph/builder/spec-pkg.js +0 -87
  153. package/src/graph/community.js +0 -27
  154. package/src/graph/edge-provenance.js +0 -31
  155. package/src/graph/file-lock.js +0 -69
  156. package/src/graph/freshness-probe.js +0 -142
  157. package/src/graph/graph-filter.js +0 -63
  158. package/src/graph/incremental-refresh.js +0 -235
  159. package/src/graph/internal-builder.barrels.js +0 -235
  160. package/src/graph/internal-builder.build.js +0 -300
  161. package/src/graph/internal-builder.java.js +0 -33
  162. package/src/graph/internal-builder.js +0 -12
  163. package/src/graph/internal-builder.langs.js +0 -157
  164. package/src/graph/internal-builder.pass2.js +0 -163
  165. package/src/graph/internal-builder.resolvers.js +0 -286
  166. package/src/graph/layout.js +0 -51
  167. package/src/graph/node-id.js +0 -7
  168. package/src/graph/parser-artifact-boundary.js +0 -55
  169. package/src/graph/relations.js +0 -4
  170. package/src/graph/repo-registry.js +0 -124
  171. package/src/graph/resolvers/rust.js +0 -117
  172. package/src/infra/infra-items.js +0 -255
  173. package/src/infra/infra-registry.js +0 -184
  174. package/src/infra/infra.detect.js +0 -119
  175. package/src/infra/infra.js +0 -38
  176. package/src/infra/infra.match.js +0 -102
  177. package/src/infra/infra.scan.js +0 -93
  178. package/src/mcp/actions/graph-lifecycle.mjs +0 -148
  179. package/src/mcp/architecture-bootstrap.mjs +0 -168
  180. package/src/mcp/architecture-starter.mjs +0 -234
  181. package/src/mcp/catalog.mjs +0 -233
  182. package/src/mcp/company-contract-page.mjs +0 -142
  183. package/src/mcp/company-contract-reconcile.mjs +0 -87
  184. package/src/mcp/company-contract-verdict.mjs +0 -42
  185. package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
  186. package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
  187. package/src/mcp/evidence/package-graph-common.mjs +0 -115
  188. package/src/mcp/evidence/package-lock-graph.mjs +0 -92
  189. package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
  190. package/src/mcp/evidence-snapshot.common.mjs +0 -158
  191. package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
  192. package/src/mcp/evidence-snapshot.health.mjs +0 -102
  193. package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
  194. package/src/mcp/evidence-snapshot.mjs +0 -50
  195. package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
  196. package/src/mcp/evidence-snapshot.structure.mjs +0 -84
  197. package/src/mcp/extension-api.mjs +0 -77
  198. package/src/mcp/git-output.mjs +0 -7
  199. package/src/mcp/graph/context-core.mjs +0 -111
  200. package/src/mcp/graph/context-seeds.mjs +0 -207
  201. package/src/mcp/graph/context-state.mjs +0 -85
  202. package/src/mcp/graph/reverse-reach.mjs +0 -42
  203. package/src/mcp/graph/tools-core.mjs +0 -143
  204. package/src/mcp/graph/tools-query.mjs +0 -276
  205. package/src/mcp/graph-context.mjs +0 -5
  206. package/src/mcp/graph-diff.mjs +0 -218
  207. package/src/mcp/health/audit-format.mjs +0 -199
  208. package/src/mcp/health/audit.mjs +0 -199
  209. package/src/mcp/health/dead-code.mjs +0 -110
  210. package/src/mcp/health/duplicates.mjs +0 -121
  211. package/src/mcp/health/endpoints.mjs +0 -71
  212. package/src/mcp/health/structure.mjs +0 -220
  213. package/src/mcp/runtime-version.mjs +0 -31
  214. package/src/mcp/server/auto-refresh.mjs +0 -66
  215. package/src/mcp/server/runtime-config.mjs +0 -43
  216. package/src/mcp/server/shutdown.mjs +0 -40
  217. package/src/mcp/staleness-notice.mjs +0 -20
  218. package/src/mcp/sync/evidence-architecture.mjs +0 -54
  219. package/src/mcp/sync/evidence-common.mjs +0 -88
  220. package/src/mcp/sync/evidence-duplicates.mjs +0 -97
  221. package/src/mcp/sync/evidence-health.mjs +0 -56
  222. package/src/mcp/sync/evidence-packages.mjs +0 -95
  223. package/src/mcp/sync/payload-common.mjs +0 -97
  224. package/src/mcp/sync/payload-v2.mjs +0 -54
  225. package/src/mcp/sync/payload-v3.mjs +0 -79
  226. package/src/mcp/sync-evidence.mjs +0 -23
  227. package/src/mcp/sync-payload.mjs +0 -70
  228. package/src/mcp/tool-result.mjs +0 -54
  229. package/src/mcp/tools-actions.mjs +0 -11
  230. package/src/mcp/tools-architecture.mjs +0 -177
  231. package/src/mcp/tools-company.mjs +0 -251
  232. package/src/mcp/tools-context.mjs +0 -222
  233. package/src/mcp/tools-endpoints.mjs +0 -207
  234. package/src/mcp/tools-graph-hubs.mjs +0 -78
  235. package/src/mcp/tools-graph.mjs +0 -17
  236. package/src/mcp/tools-health.mjs +0 -24
  237. package/src/mcp/tools-history.mjs +0 -28
  238. package/src/mcp/tools-impact-change.mjs +0 -220
  239. package/src/mcp/tools-impact-precision.mjs +0 -89
  240. package/src/mcp/tools-impact.mjs +0 -169
  241. package/src/mcp/tools-source.mjs +0 -238
  242. package/src/mcp/tools-verified-change.mjs +0 -200
  243. package/src/mcp-rg.mjs +0 -50
  244. package/src/mcp-runtime.mjs +0 -2
  245. package/src/mcp-server.mjs +0 -298
  246. package/src/mcp-source-tools.mjs +0 -163
  247. package/src/path-classification.js +0 -226
  248. package/src/path-ignore.js +0 -69
  249. package/src/precision/lsp-client/constants.js +0 -12
  250. package/src/precision/lsp-client/environment.js +0 -20
  251. package/src/precision/lsp-client/errors.js +0 -15
  252. package/src/precision/lsp-client/lifecycle.js +0 -114
  253. package/src/precision/lsp-client/message-parser.js +0 -81
  254. package/src/precision/lsp-client/protocol.js +0 -212
  255. package/src/precision/lsp-client/registry.js +0 -59
  256. package/src/precision/lsp-client/repo-uri.js +0 -60
  257. package/src/precision/lsp-client/stdio-client.js +0 -140
  258. package/src/precision/lsp-client.js +0 -10
  259. package/src/precision/lsp-overlay/budget-policy.js +0 -78
  260. package/src/precision/lsp-overlay/build.js +0 -261
  261. package/src/precision/lsp-overlay/contract.js +0 -61
  262. package/src/precision/lsp-overlay/reference-results.js +0 -108
  263. package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
  264. package/src/precision/lsp-overlay/source-session.js +0 -142
  265. package/src/precision/lsp-overlay/store.js +0 -150
  266. package/src/precision/lsp-overlay/target-index.js +0 -152
  267. package/src/precision/lsp-overlay/target-query.js +0 -55
  268. package/src/precision/lsp-overlay.js +0 -11
  269. package/src/precision/symbol-query.js +0 -224
  270. package/src/precision/typescript-lsp-provider.js +0 -12
  271. package/src/precision/typescript-provider/client.js +0 -89
  272. package/src/precision/typescript-provider/discovery.js +0 -124
  273. package/src/precision/typescript-provider/isolated-runtime.js +0 -39
  274. package/src/precision/typescript-provider/project-host.js +0 -254
  275. package/src/precision/typescript-provider/project-safety.js +0 -166
  276. package/src/precision/typescript-provider/reference-usage.js +0 -53
  277. package/src/process.js +0 -95
  278. package/src/repo-path.js +0 -53
  279. package/src/scan/discover.inventory.js +0 -78
  280. package/src/scan/discover.js +0 -5
  281. package/src/scan/discover.list.js +0 -79
  282. package/src/scan/discover.stack.js +0 -227
  283. package/src/scan/search.core.js +0 -24
  284. package/src/scan/search.git.js +0 -102
  285. package/src/scan/search.js +0 -9
  286. package/src/scan/search.node.js +0 -83
  287. package/src/scan/search.preview.js +0 -49
  288. package/src/scan/search.rg.js +0 -145
  289. package/src/security/advisory-store.js +0 -172
  290. package/src/security/installed-jvm-rust.js +0 -46
  291. package/src/security/installed.js +0 -280
  292. package/src/security/malware-file-heuristics.js +0 -121
  293. package/src/security/malware-heuristics.exclusions.js +0 -134
  294. package/src/security/malware-heuristics.js +0 -15
  295. package/src/security/malware-heuristics.roots.js +0 -145
  296. package/src/security/malware-heuristics.scan.js +0 -89
  297. package/src/security/malware-heuristics.sweep.js +0 -122
  298. package/src/security/malware-scoring.js +0 -105
  299. package/src/security/match.js +0 -85
  300. package/src/security/registry-sig.classify.js +0 -137
  301. package/src/security/registry-sig.js +0 -18
  302. package/src/security/registry-sig.rules.js +0 -192
  303. package/src/security/rust-advisory-report.js +0 -60
  304. package/src/security/typosquat.js +0 -81
  305. package/src/util.js +0 -40
  306. package/src/version.js +0 -7
@@ -1,233 +0,0 @@
1
- // Tool catalog + hot-reload loader. Every tool declares a capability GROUP; the stdio shell exposes
2
- // only the groups enabled for this registration (argv[4]). loadHotApi() re-imports the tool modules
3
- // with a cache-busting version so edits to src/mcp/tools-*.mjs (or this file) go live without an MCP
4
- // reconnect. graph-context.mjs is imported STATICALLY everywhere — it holds process-lifetime caches
5
- // and does not hot-reload (nor do the ../analysis engines; changing those needs a reconnect).
6
- import {dirname} from 'node:path'
7
- import {fileURLToPath} from 'node:url'
8
- import {resolveNode, isSymbol, stalenessLine, resetStalenessCache} from './graph-context.mjs'
9
- import {createRgResolver} from '../mcp-rg.mjs'
10
- import {readSource, searchCode} from '../mcp-source-tools.mjs'
11
- import {extensionRuntimeSummary, normalizeWeavatrixExtensions} from './extension-api.mjs'
12
-
13
- const SELF_DIR = dirname(fileURLToPath(import.meta.url))
14
- const resolveRg = createRgResolver(SELF_DIR)
15
- // The core artifact has only local profiles. Online packages compose their own profiles through the
16
- // public extension API; the MIT package never contains a capability alias that can enable HTTP.
17
- export const DEFAULT_CAPS = Object.freeze(['graph', 'search', 'source', 'health', 'build', 'retarget', 'crossrepo'])
18
- const PROFILE_CAPS = Object.freeze({
19
- offline: DEFAULT_CAPS,
20
- pinned: ['graph', 'search', 'source', 'health', 'build'],
21
- })
22
- const MOVED_PROFILES = new Set(['online', 'osv', 'hosted', 'full'])
23
-
24
- // The files whose mtime the stdio shell watches for hot reload — keep in sync with the imports in
25
- // loadHotApi below (catalog.mjs itself is last: a change here re-derives the whole table).
26
- const HOT_FACADES = [
27
- 'tools-graph.mjs', 'tools-impact.mjs', 'tools-health.mjs', 'tools-source.mjs',
28
- 'tools-context.mjs', 'tools-endpoints.mjs', 'tools-actions.mjs',
29
- 'tools-architecture.mjs', 'tools-history.mjs', 'tools-company.mjs',
30
- 'tools-verified-change.mjs',
31
- ]
32
- const HOT_OWNERS = [
33
- 'graph/tools-core.mjs', 'graph/tools-query.mjs', 'tools-graph-hubs.mjs',
34
- 'health/duplicates.mjs', 'health/dead-code.mjs', 'health/audit-format.mjs',
35
- 'health/audit.mjs', 'health/structure.mjs', 'health/endpoints.mjs',
36
- 'actions/graph-lifecycle.mjs',
37
- 'architecture-starter.mjs', 'architecture-bootstrap.mjs',
38
- 'company-contract-verdict.mjs',
39
- ]
40
- export const HOT_FILES = [...HOT_FACADES, ...HOT_OWNERS, 'catalog.mjs']
41
-
42
- function buildTools({tg, ti, th, ts, tb, te, ta, tar, thi, tc, tv, caps}) {
43
- const tools = [
44
- {cap: 'graph', name: 'graph_stats', description: 'Return summary statistics: node count, edge count, communities, versioned edge-provenance/legacy-confidence breakdowns, and graph build time vs repo HEAD (staleness).', inputSchema: {type: 'object', properties: {}}, run: (g, a, ctx) => tg.tGraphStats(g, ctx)},
45
- {cap: 'graph', name: 'get_node', description: 'Get full details for a specific node by label or ID.', inputSchema: {type: 'object', properties: {label: {type: 'string', description: 'Node label or ID to look up'}}, required: ['label']}, run: (g, a, ctx) => tg.tGetNode(g, a, ctx)},
46
- {cap: 'graph', name: 'get_neighbors', description: 'Get all direct neighbors of a node with edge details (1 hop, call sites deduped). For transitive impact use get_dependents; for the impact of your current branch changes use change_impact.', inputSchema: {type: 'object', properties: {label: {type: 'string'}, relation_filter: {type: 'string', description: 'Optional: filter by relation type'}}, required: ['label']}, run: (g, a, ctx) => tg.tGetNeighbors(g, a, ctx)},
47
- {cap: 'graph', name: 'query_graph', description: 'Explore a focused production-first graph around a concept or exact symbols (BFS/DFS). Exact seed files/symbols stay pinned; relation_filter and flow_direction support bounded event/data-flow views without a separate tool. Classified paths and unreferenced constant/field leaves stay suppressed unless explicitly requested.', inputSchema: {type: 'object', properties: {question: {type: 'string', description: 'Optional natural-language question or keyword search when exact seeds are not sufficient'}, mode: {type: 'string', enum: ['bfs', 'dfs'], default: 'bfs'}, depth: {type: 'integer', default: 3}, context_filter: {type: 'array', items: {type: 'string'}}, seed_files: {type: 'array', items: {type: 'string'}, maxItems: 12, description: 'Exact repo-relative file paths. Resolved exact seeds remain pinned unless augment_seeds is true'}, seed_symbols: {type: 'array', items: {type: 'string'}, maxItems: 12, description: 'Exact node IDs or unambiguous symbol labels; enables focused flows without fuzzy query seeds'}, relation_filter: {oneOf: [{type: 'array', items: {type: 'string'}}, {type: 'string'}], description: 'Optional relation allow-list, e.g. calls,references,imports'}, flow_direction: {type: 'string', enum: ['forward', 'backward', 'both'], default: 'both', description: 'Traverse outgoing, incoming, or both directions'}, augment_seeds: {type: 'boolean', default: false, description: 'With exact seeds, also add fuzzy question-derived seeds; false keeps traversal strictly pinned'}, include_classified: {type: 'boolean', default: false, description: 'Allow traversal through tests/e2e/generated/mocks/stories/docs/benchmarks/temp and explicitly excluded paths. An explicit class term in the question enables only that class.'}, include_low_signal: {type: 'boolean', default: false, description: 'Include unreferenced constant/field leaf symbols that do not match a query term'}, token_budget: {type: 'integer', description: 'Higher budget shows more nodes/edges', default: 2000}}, anyOf: [{required: ['question']}, {required: ['seed_files']}, {required: ['seed_symbols']}]}, run: (g, a, ctx) => tg.tQueryGraph(g, a, ctx)},
48
- {cap: 'graph', name: 'god_nodes', description: 'Rank production-code connectivity hubs by unique call/import/reference neighbors, with class/method ownership reported separately from runtime connectivity. Repeated call sites do not inflate the rank; classified tests, generated/build output and other non-product paths are excluded by default.', inputSchema: {type: 'object', properties: {top_n: {type: 'integer', default: 10}, include_classified: {type: 'boolean', default: false, description: 'Include tests/e2e/generated/build output/mocks/stories/docs/benchmarks/temp and paths explicitly excluded by repository classification'}}}, run: (g, a, ctx) => tg.tGodNodes(g, a, ctx)},
49
- {cap: 'graph', name: 'shortest_path', description: 'Find the shortest path between two concepts in the knowledge graph.', inputSchema: {type: 'object', properties: {source: {type: 'string'}, target: {type: 'string'}, max_hops: {type: 'integer', default: 8}}, required: ['source', 'target']}, run: (g, a) => tg.tShortestPath(g, a)},
50
- {cap: 'graph', name: 'get_dependents', description: 'Transitive blast-radius of ONE node. JavaScript/TypeScript symbols use a cached on-demand EXACT_LSP point query by default, then traverse exact direct callers through the wider graph; incomplete precision is labelled and never silently presented as exact. Set precision=graph to skip LSP or include_container_importers for a conservative module-wide radius.', inputSchema: {type: 'object', properties: {label: {type: 'string', description: 'Node label or ID'}, depth: {type: 'integer', description: 'Max reverse hops, default 3', default: 3}, max_nodes: {type: 'integer', description: 'Max dependents to list, default 40', default: 40}, precision: {type: 'string', enum: ['auto', 'graph', 'lsp'], default: 'auto', description: 'auto uses exact JS/TS point queries when precision is enabled; graph skips LSP; lsp forces an attempt'}, max_references: {type: 'integer', minimum: 1, maximum: 5000, default: 1000}, timeout_ms: {type: 'integer', minimum: 1000, maximum: 60000, default: 30000}, include_container_importers: {type: 'boolean', description: 'Also seed importers of the symbol containing file (broader, conservative; default false)', default: false}}, required: ['label']}, run: (g, a, ctx) => ti.tGetDependents(g, a, ctx)},
51
- {cap: 'graph', name: 'change_impact', description: 'Verdict-first, symbol-aware blast radius. Parses a zero-context git diff and uses one bounded EXACT_LSP batch query for direct references to changed JavaScript/TypeScript symbols; transitive hops stay explicitly graph-backed. Additive exports do not inherit legacy file importers. Measured coverage is used when present; otherwise static reachability is labelled, not treated as coverage.', inputSchema: {type: 'object', properties: {base: {type: 'string', description: 'Base ref, e.g. origin/main or HEAD~1 (default: first existing of origin/HEAD, origin/main, origin/master, main, master)'}, diff: {type: 'string', maxLength: 2097152, description: 'Optional unified diff (prefer --unified=0) for a PR/change that is not checked out; enables symbol-level classification'}, files: {type: 'array', items: {type: 'string'}, maxItems: 500, description: 'Optional repo-relative changed-file hints. Without diff evidence these are classified conservatively rather than guessed additive'}, depth: {type: 'integer', description: 'Max reverse hops, default 2'}, max_nodes: {type: 'integer', description: 'Max impacted nodes to list, default 40'}, precision: {type: 'string', enum: ['auto', 'graph', 'lsp'], default: 'auto'}, max_references: {type: 'integer', minimum: 1, maximum: 16384, default: 5000}, timeout_ms: {type: 'integer', minimum: 1000, maximum: 60000, default: 45000}}}, run: (g, a, ctx) => ti.tChangeImpact(g, a, ctx)},
52
- {cap: 'graph', name: 'git_history', description: 'Behavioral architecture evidence from bounded local git history: churn × connectivity hotspots, hidden co-change coupling, and expected test/source coupling. Reads numstat only — never commit messages, authors, or source bodies.', inputSchema: {type: 'object', properties: {months: {type: 'integer', enum: [3, 6, 12], default: 6}, max_commits: {type: 'integer', minimum: 1, maximum: 5000, default: 1000}, min_pair_count: {type: 'integer', minimum: 2, maximum: 100, default: 3}, max_pairs: {type: 'integer', minimum: 1, maximum: 500, default: 100}, top_n: {type: 'integer', minimum: 1, maximum: 50, default: 10}}}, run: (g, a, ctx) => thi.tGitHistory(g, a, ctx)},
53
- {
54
- cap: 'graph', refreshGraph: true, name: 'verified_change',
55
- description: 'Pre-commit, proof-carrying change safeguard. Given a natural-language task and current diff/files, returns compact edit contexts, bounded call-argument data-flow, blast radius, graph/architecture/duplicate/API ratchets, affected tests, and one PASS/BLOCKED/UNKNOWN verdict. Use this high-level workflow before manually composing lower-level checks. Package tests run only when explicitly requested and WEAVATRIX_ALLOW_TEST_RUNS=1.',
56
- inputSchema: {type: 'object', additionalProperties: false, properties: {
57
- task: {type: 'string', minLength: 1, maxLength: 4000}, phase: {type: 'string', enum: ['plan', 'verify'], default: 'plan'},
58
- base_ref: {type: 'string', maxLength: 200, default: 'HEAD'}, diff: {type: 'string', maxLength: 2097152}, files: {type: 'array', items: {type: 'string'}, maxItems: 500},
59
- precision: {type: 'string', enum: ['auto', 'graph', 'lsp'], default: 'auto'}, max_symbols: {type: 'integer', minimum: 1, maximum: 5, default: 3},
60
- impact_depth: {type: 'integer', minimum: 1, maximum: 4, default: 2}, max_impact_nodes: {type: 'integer', minimum: 5, maximum: 120, default: 40},
61
- data_flow_depth: {type: 'integer', minimum: 1, maximum: 3, default: 2}, max_data_flow_edges: {type: 'integer', minimum: 1, maximum: 60, default: 30},
62
- duplicate_ratchet: {type: 'boolean', default: true},
63
- api_contract: {type: 'object', additionalProperties: true, properties: {backend: {type: 'string'}, clients: {type: 'array', items: {type: 'string'}, minItems: 1, maxItems: 20}, method: {type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']}, path: {type: 'string', maxLength: 2048}, changed_files: {type: 'array', items: {type: 'string'}, maxItems: 500}}, required: ['backend', 'clients']},
64
- tests: {type: 'array', maxItems: 5, items: {type: 'object', additionalProperties: false, properties: {script: {type: 'string', maxLength: 120}, args: {type: 'array', maxItems: 40, items: {type: 'string', maxLength: 300}}}, required: ['script']}},
65
- run_tests: {type: 'boolean', default: false}, test_timeout_ms: {type: 'integer', minimum: 1000, maximum: 300000, default: 60000},
66
- }, required: ['task']},
67
- run: (g, a, ctx) => tv.tVerifiedChange(g, a, ctx, {
68
- impact: ti.tChangeImpact, context: tb.tContextBundle, inspect: ts.tInspectSymbol,
69
- prepareChange: tar.tPrepareChange, verifyArchitecture: tar.tVerifyArchitecture, traceApi: tc.tTraceApiContract,
70
- }, {source: caps.has('source'), health: caps.has('health'), crossrepo: caps.has('crossrepo')}),
71
- },
72
- {
73
- cap: 'crossrepo',
74
- name: 'trace_api_contract',
75
- description: 'Cross-repository HTTP, GraphQL, gRPC and event/topic contract, handler-liveness and blast-radius evidence. Joins static models with optional revision-bound runtime/OTLP evidence; unobserved dynamic URLs/topics/reflection remain explicit UNKNOWN. Medium/high-confidence external matches mark a handler/contract NOT_DEAD_EXTERNAL_USE. Repository paths stay local and runtime report paths are repository-contained.',
76
- inputSchema: {
77
- type: 'object',
78
- properties: {
79
- backend: {type: 'string', description: 'Backend repository UUID or exact unambiguous registry label'},
80
- clients: {type: 'array', items: {type: 'string'}, minItems: 1, maxItems: 20, description: 'Client repository UUIDs or exact unambiguous registry labels'},
81
- transport: {type: 'string', enum: ['all', 'http', 'graphql', 'grpc', 'event'], default: 'all', description: 'Contract family to trace; all runs static and revision-bound runtime evidence for every supported transport'},
82
- method: {type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']},
83
- path: {type: 'string', maxLength: 2048, description: 'Optional full route or segment-aligned route fragment; /query matches /edgeAnalytics/query/... and {id}, :id and concrete parameter values are normalized'},
84
- changed_files: {type: 'array', items: {type: 'string'}, maxItems: 500, description: 'Optional backend repo-relative changed files; only endpoints declared in those files are traced'},
85
- client_names: {type: 'array', items: {type: 'string', pattern: '^[A-Za-z_$][\\w$]{0,127}$'}, maxItems: 40, uniqueItems: true, description: 'Extra object-style clients whose .get/.post/... methods perform HTTP requests; persistent per-repo configuration belongs in .weavatrix.json httpContracts.clientNames'},
86
- client_wrappers: {
87
- type: 'array', maxItems: 100,
88
- description: 'Fixed-method wrapper calls. Use call+method for get(url), or object+member+method for transport.send(url). url_argument is zero-based.',
89
- items: {
90
- type: 'object', additionalProperties: false,
91
- properties: {
92
- call: {type: 'string', pattern: '^[A-Za-z_$][\\w$]{0,127}$'},
93
- object: {type: 'string', pattern: '^[A-Za-z_$][\\w$]{0,127}$'},
94
- member: {type: 'string', pattern: '^[A-Za-z_$][\\w$]{0,127}$'},
95
- method: {type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']},
96
- url_argument: {type: 'integer', minimum: 0, maximum: 5, default: 0},
97
- },
98
- anyOf: [
99
- {required: ['call', 'method'], not: {anyOf: [{required: ['object']}, {required: ['member']}]}},
100
- {required: ['object', 'member', 'method'], not: {required: ['call']}},
101
- ],
102
- },
103
- },
104
- auto_discover_wrappers: {type: 'boolean', default: true, description: 'Discover only simple unambiguous functions that forward a URL parameter directly to a known object-style HTTP client'},
105
- runtime_config: {type: 'object', maxProperties: 50, additionalProperties: {type: 'string', maxLength: 2048}, description: 'Optional non-secret static bindings for runtime URL prefixes, e.g. process.env.API_BASE. Values are used locally for this call and are not returned.'},
106
- runtime_evidence_files: {type: 'object', maxProperties: 21, additionalProperties: {type: 'string', maxLength: 512}, description: 'Optional repository-label/UUID to repository-relative weavatrix.transport-runtime.v1 JSON path. Defaults to .weavatrix/transport-runtime.json or .weavatrix/reports/transport-runtime.json in each repository.'},
107
- runtime_evidence_max_age_hours: {type: 'integer', minimum: 1, maximum: 8760, default: 168, description: 'Maximum accepted age for a revision-matched runtime evidence report'},
108
- include_tests: {type: 'boolean', default: false},
109
- max_impact_depth: {type: 'integer', minimum: 0, maximum: 5, default: 2},
110
- max_endpoints: {type: 'integer', minimum: 1, maximum: 500, default: 250},
111
- max_matches: {type: 'integer', minimum: 1, maximum: 5000, default: 1000},
112
- max_affected_files: {type: 'integer', minimum: 1, maximum: 500, default: 100},
113
- top_n: {type: 'integer', minimum: 1, maximum: 50, default: 10},
114
- response_detail: {type: 'string', enum: ['compact', 'full'], default: 'compact', description: 'Compact returns bounded samples and counts. Full is explicit opt-in and still paginated.'},
115
- page_size: {type: 'integer', minimum: 1, maximum: 50, default: 10, description: 'Maximum HTTP/transport/uncertain evidence items returned on this page.'},
116
- per_item_limit: {type: 'integer', minimum: 1, maximum: 25, default: 5, description: 'Compact-mode sample limit for callsites, affected files, screens and modules per item.'},
117
- cursor: {type: 'string', maxLength: 256, description: 'Opaque nextCursor from the previous page; bound to repository revisions and filters.'},
118
- },
119
- required: ['backend', 'clients'],
120
- },
121
- run: (g, a, ctx) => tc.tTraceApiContract(g, a, ctx),
122
- },
123
- {cap: 'graph', name: 'get_community', description: 'Get all nodes in a community by community ID (0-indexed by size).', inputSchema: {type: 'object', properties: {community_id: {type: 'integer', description: 'Community ID (0-indexed by size)'}}, required: ['community_id']}, run: (g, a) => tg.tGetCommunity(g, a)},
124
- {cap: 'search', name: 'search_code', description: 'Full-text or regex search across the repo source (ripgrep-backed, Node fallback). The graph only stores structure — use this to find literal text/patterns, then get_node/get_neighbors for structure.', inputSchema: {type: 'object', properties: {query: {type: 'string', description: 'text or regex to search for'}, is_regex: {type: 'boolean', default: false}, glob: {type: 'string', description: 'optional path glob, e.g. "*.js" or "src/**"'}, max_results: {type: 'integer', default: 40}}, required: ['query']}, run: (g, a, ctx) => searchCode({repoRoot: ctx.repoRoot, resolveRg}, a)},
125
- {cap: 'source', name: 'read_source', description: "Read the actual source of a node (by label/ID) or a repo-relative file path — the symbol's lines with context. The graph stores only locations, not source text. For a path read, pass start_line to anchor the window anywhere in the file (otherwise it shows the head).", inputSchema: {type: 'object', properties: {label: {type: 'string', description: 'node label or ID'}, path: {type: 'string', description: 'or a repo-relative file path'}, start_line: {type: 'integer', description: 'anchor line: window = start_line-before .. start_line+after'}, before: {type: 'integer', default: 3}, after: {type: 'integer', default: 40}}}, run: (g, a, ctx) => readSource({repoRoot: ctx.repoRoot, resolveNode, isSymbol}, g, a)},
126
- {cap: 'source', refreshGraph: true, name: 'inspect_symbol', description: 'Inspect one exact symbol with an on-demand TypeScript/JavaScript LSP reference query, grouped occurrence containers, graph blast radius, complexity facts and bounded local source context. Ambiguous labels fail closed; point queries never replace the broad precision overlay.', inputSchema: {type: 'object', properties: {label: {type: 'string', description: 'Exact node ID or unambiguous symbol label'}, precision: {type: 'string', enum: ['auto', 'graph', 'lsp'], default: 'auto'}, max_references: {type: 'integer', minimum: 1, maximum: 5000, default: 1000}, max_containers: {type: 'integer', minimum: 1, maximum: 50, default: 15}, context_lines: {type: 'integer', minimum: 0, maximum: 40, default: 8}, timeout_ms: {type: 'integer', minimum: 1000, maximum: 60000, default: 30000}}, required: ['label']}, run: (g, a, ctx) => ts.tInspectSymbol(g, a, ctx)},
127
- {cap: 'source', refreshGraph: true, name: 'context_bundle', description: 'Return one compact, bounded source bundle for an exact symbol: definition, production-first inbound/outbound containers, exact re-export sites, on-demand TS/JS reference evidence and diverse excerpts around call sites. Use before an edit when query_graph would be too broad.', inputSchema: {type: 'object', properties: {label: {type: 'string', description: 'Exact node ID or unambiguous symbol label'}, precision: {type: 'string', enum: ['auto', 'graph', 'lsp'], default: 'auto'}, max_references: {type: 'integer', minimum: 1, maximum: 5000, default: 1000}, max_related: {type: 'integer', minimum: 1, maximum: 30, default: 10}, max_reexports: {type: 'integer', minimum: 1, maximum: 100, default: 20}, max_source_files: {type: 'integer', minimum: 1, maximum: 8, default: 4}, context_lines: {type: 'integer', minimum: 0, maximum: 12, default: 4}, include_classified: {type: 'boolean', default: false, description: 'Include test/e2e/generated/vendored/mock/story/docs/benchmark/temp callers after production callers'}, timeout_ms: {type: 'integer', minimum: 1000, maximum: 60000, default: 30000}}, required: ['label']}, run: (g, a, ctx) => tb.tContextBundle(g, a, ctx, ts.tInspectSymbol)},
128
- {cap: 'health', name: 'find_duplicates', description: "Content-based clone detection over production code (MOSS winnowing over method bodies). Supports high-confidence small clones down to 12 tokens when min_tokens is lowered. Tests, classified non-product paths, all-router framework boilerplate and immutable declarative catalogs are excluded by default; opt them in explicitly.", inputSchema: {type: 'object', properties: {min_similarity: {type: 'integer', description: '50-100, default 80 (ignored in semantic mode)'}, min_tokens: {type: 'integer', minimum: 12, maximum: 400, description: 'min fragment size, 12-400; default 50'}, mode: {type: 'string', enum: ['renamed', 'strict', 'semantic'], default: 'renamed'}, include_tests: {type: 'boolean', default: false}, include_classified: {type: 'boolean', default: false, description: 'Include generated/vendored/mock/story/docs/benchmark/temp and paths explicitly classified as excluded; tests still require include_tests'}, include_boilerplate: {type: 'boolean', default: false, description: 'Include clone groups made entirely of conventional *.router.js/ts router symbols'}, include_declarative: {type: 'boolean', default: false, description: 'Include repeated immutable array/object catalogs that contain no executable control flow'}, include_strings: {type: 'boolean', description: 'Also clone-check large multi-line string literals', default: false}, top_n: {type: 'integer', default: 15}}}, run: (g, a, ctx) => th.tFindDuplicates(g, a, ctx)},
129
- {cap: 'health', name: 'find_dead_code', description: 'Conservative review queue for statically unreferenced files, functions, methods and symbols. Returns confidence, reason, bounded evidence and explicit framework/dynamic/reflection/public-API caveats; never an auto-delete verdict. Tests, generated/vendored code, mocks, stories, docs, benchmarks and temporary roots are excluded by default.', inputSchema: {type: 'object', properties: {path: {type: 'string', maxLength: 1024, description: 'Optional repo-relative path prefix'}, kinds: {type: 'array', items: {type: 'string', enum: ['file', 'function', 'method', 'symbol']}, maxItems: 4, uniqueItems: true, description: 'Optional candidate kinds; defaults to all'}, min_confidence: {type: 'string', enum: ['high', 'medium', 'low'], default: 'medium', description: 'Minimum confidence to include. low explicitly includes public/framework/dynamic review candidates'}, include_tests: {type: 'boolean', default: false}, include_classified: {type: 'boolean', default: false, description: 'Include generated/vendored/mock/story/docs/benchmark/temp and paths explicitly classified as excluded; tests still require include_tests'}, top_n: {type: 'integer', minimum: 1, maximum: 100, default: 30}}}, run: (g, a, ctx) => th.tFindDeadCode(g, a, ctx)},
130
- {cap: 'health', name: 'run_audit', description: 'Core production-first repository Health review with an explicit capability/completeness matrix for structure, dependencies, bounded runtime-correctness/concurrency patterns, advisories, malware and coverage. Unsupported Maven/Gradle import verification is NOT_SUPPORTED/PARTIAL, never a clean zero. Findings whose evidence is entirely test/e2e/generated/vendored/mock/story/docs/benchmark/temp or explicitly excluded are suppressed by default; opt them in with include_classified. category=dependencies is a dedicated dependency-health projection across missing, unused and duplicate declarations while preserving each finding\'s native category. With base_ref, builds and audits an immutable Git checkout and compares stable deterministic finding IDs; debt defaults to genuinely new findings. Supply-chain checks remain explicitly uncomparable across the source-only baseline. changed_files without base_ref is only changed-scope, never a new-debt claim.', inputSchema: {type: 'object', properties: {category: {type: 'string', enum: ['dependencies', 'unused', 'structure', 'vulnerability', 'malware'], description: 'Only findings of this category; dependencies selects dependency manifest/import findings across native categories'}, min_severity: {type: 'string', enum: ['critical', 'high', 'medium', 'low', 'info'], description: 'Minimum severity to include'}, max_findings: {type: 'integer', description: 'Max findings to list, default 30'}, include_classified: {type: 'boolean', default: false, description: 'Include findings whose evidence is entirely tests/e2e/generated/vendored/mocks/stories/docs/benchmarks/temp or explicitly excluded'}, include_malware_scan: {type: 'boolean', description: 'Also grep installed packages for malware heuristics (slow)', default: false}, base_ref: {type: 'string', maxLength: 200, description: 'Optional immutable Git baseline (for example HEAD~1 or origin/main). Enables honest new/existing/fixed debt comparison'}, changed_files: {type: 'array', items: {type: 'string'}, minItems: 1, maxItems: 500, description: 'Optional explicit repo-relative scope. Without base_ref this is changed-scope only; when omitted with base_ref, files are derived from the Git diff'}, debt: {type: 'string', enum: ['new', 'existing', 'all'], default: 'new', description: 'Baseline comparison view. Defaults to genuinely new deterministic findings when base_ref is present'}}}, run: (g, a, ctx) => th.tRunAudit(g, a, ctx)},
131
- {cap: 'health', name: 'coverage_map', description: 'Map a real existing coverage report onto the graph. If no report exists, return clearly labelled static test reachability (a test imports/reaches a source file) with actualCoverage=NOT_AVAILABLE; reachability is never presented as measured coverage.', inputSchema: {type: 'object', properties: {top_n: {type: 'integer', description: 'Max risk hotspots to list, default 15'}, path: {type: 'string', description: 'Optional repo-relative path prefix filter, e.g. src/query'}}}, run: (g, a, ctx) => th.tCoverageMap(g, a, ctx)},
132
- {cap: 'health', name: 'hot_path_review', description: 'Rank a focused production-symbol hot-path queue from parser-derived local complexity, inside-loop allocations/copies/scans/sorts/recursion, graph fan-in/fan-out, and measured coverage or clearly labelled static test reachability. The default score gate is 85 with a narrow strong-local fallback; set min_score=0 for the full diagnostic queue. This is not profiler data or interprocedural Big-O.', inputSchema: {type: 'object', properties: {path: {type: 'string', maxLength: 1024, description: 'Optional repository-relative path prefix'}, top_n: {type: 'integer', minimum: 1, maximum: 100, default: 20}, min_score: {type: 'integer', minimum: 0, maximum: 100, default: 85, description: 'Focused default is 85; lower explicitly to broaden, or use 0 for every threshold-matching diagnostic candidate'}, cyclomatic_threshold: {type: 'integer', minimum: 2, maximum: 1000, default: 8}, call_threshold: {type: 'integer', minimum: 1, maximum: 10000, default: 12}, loop_depth_threshold: {type: 'integer', minimum: 1, maximum: 10, default: 2}, time_rank_threshold: {type: 'integer', minimum: 0, maximum: 5, default: 2}, include_tests: {type: 'boolean', default: false}, include_classified: {type: 'boolean', default: false, description: 'Include generated/vendored/mock/story/docs/benchmark/temp and explicitly excluded paths; tests still require include_tests'}}}, run: (g, a, ctx) => th.tHotPathReview(g, a, ctx)},
133
- {cap: 'graph', name: 'list_communities', description: 'List graph communities named by their dominant folder (largest first) with sample files — a readable module overview; feed the list position into get_community.', inputSchema: {type: 'object', properties: {top_n: {type: 'integer', description: 'Max communities to list, default 20'}}}, run: (g, a, ctx) => th.tListCommunities(g, a, ctx)},
134
- {cap: 'graph', name: 'module_map', description: 'First orientation view for understanding an unfamiliar application with little context: a production-first folder architecture map with file/symbol counts and strongest module dependencies, separating runtime, TypeScript type-only and language compile-only coupling.', inputSchema: {type: 'object', properties: {top_n: {type: 'integer', description: 'Max modules to list, default 25'}, include_non_product: {type: 'boolean', default: false, description: 'Include tests, fixtures, benchmarks, generated output, docs and other classified non-product files; false by default'}}}, run: (g, a, ctx) => th.tModuleMap(g, a, ctx)},
135
- {cap: 'source', refreshGraph: true, name: 'list_endpoints', description: 'Inventory of HTTP endpoints defined in the repo (Express/Fastify/Nest/Flask/FastAPI/Go mux/Rust axum and actix-web/Spring MVC and WebFlux): declared and reachable composed paths, static mount provenance, confidence, handler, file:line, and Spring conditional/default-active state.', inputSchema: {type: 'object', properties: {method: {type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT', 'ALL', 'ANY']}, path: {type: 'string', maxLength: 2048, description: 'Optional exact composed path or segment-aligned suffix'}, max_results: {type: 'integer', description: 'Max endpoints to list, default 100'}, include_classified: {type: 'boolean', default: false, description: 'Include test/e2e/generated/mock/story/docs/benchmark/temp and explicitly excluded targets'}}}, run: (g, a, ctx) => th.tListEndpoints(g, a, ctx)},
136
- {cap: 'source', refreshGraph: true, name: 'trace_endpoint', description: 'Resolve one exact reachable HTTP endpoint, prove its router mount chain, bind its handler symbol, and return a bounded production-only multi-hop call graph with call-site excerpts. This is a focused projection of the repository graph, not text-search inference.', inputSchema: {type: 'object', properties: {path: {type: 'string', minLength: 1, maxLength: 2048, description: 'Exact composed path; a suffix is accepted only when unambiguous'}, method: {type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT', 'ALL', 'ANY']}, handler_file: {type: 'string', maxLength: 1024, description: 'Repo-relative file path (or unambiguous path suffix) declaring the handler; use after an AMBIGUOUS_HANDLER result.'}, max_depth: {type: 'integer', minimum: 1, maximum: 4, default: 3}, max_nodes: {type: 'integer', minimum: 2, maximum: 40, default: 20}, max_excerpts: {type: 'integer', minimum: 0, maximum: 12, default: 6}, context_lines: {type: 'integer', minimum: 0, maximum: 6, default: 2}, include_classified: {type: 'boolean', default: false, description: 'Include test/e2e/generated/mock/story/docs/benchmark/temp and explicitly excluded targets'}}, required: ['path']}, run: (g, a, ctx) => te.tTraceEndpoint(g, a, ctx)},
137
- {cap: 'build', name: 'rebuild_graph', description: "Rebuild the active full-repository graph and report a structural delta. Omitted mode/precision preserve the active graph; a first build uses full and the startup precision setting (lsp unless WEAVATRIX_PRECISION=off). The local TypeScript/JavaScript LSP overlay validates bounded ambiguous edges; precision:off is an explicit fallback. With scope, build an isolated diagnostic graph without replacing or diffing the full graph.", inputSchema: {type: 'object', properties: {mode: {type: 'string', enum: ['full', 'no-tests', 'tests-only'], description: 'Build mode; omit to preserve the active mode (or use full for a first build)'}, precision: {type: 'string', enum: ['lsp', 'off'], description: 'Semantic precision; omit to preserve the active mode (or use the startup setting for a first build)'}, scope: {type: 'string', description: 'Optional isolated diagnostic path prefix; never replaces the active full graph'}}}, run: (g, a, ctx) => ta.tRebuildGraph(g, a, ctx)},
138
- {cap: 'graph', name: 'graph_diff', description: 'Structural graph diff: compare the current graph with an immutable Git-ref baseline (base_ref such as HEAD~1 or main), or with graph.prev.json from the last rebuild when base_ref is omitted. Reports architecture drift, cycle changes and symbols that lost their last caller.', inputSchema: {type: 'object', properties: {base_ref: {type: 'string', maxLength: 256, description: 'Optional immutable Git baseline to build in isolation, e.g. HEAD~1, main or origin/main; never checks out or mutates the working tree'}, path: {type: 'string', description: 'Optional node-id/path prefix to scope the diff, e.g. src/query'}}}, run: (g, a, ctx) => ti.tGraphDiff(g, a, ctx)},
139
- {cap: 'graph', name: 'get_architecture_contract', description: 'Read the owner-approved architecture target or safely bootstrap one. With action=preview, returns an adaptive candidate, observed-but-not-enforced dependency directions, verification, exact file content/hash and a short-lived confirmation token. action=approve creates the local contract only after explicit token confirmation and never overwrites an active target.', inputSchema: {type: 'object', properties: {action: {type: 'string', enum: ['preview', 'approve'], description: 'Omit to read; preview is dry-run only; approve requires the preview token'}, candidate_contract: {type: 'object', description: 'Optional reviewed candidate to normalize and verify during preview'}, baseline_mode: {type: 'string', enum: ['none', 'accept-current'], default: 'none', description: 'Whether preview should materialize current violations as an explicit ratchet baseline'}, confirm_token: {type: 'string', description: 'One-time token returned by preview; required for approve'}}}, run: (g, a, ctx) => tar.tGetArchitectureContract(g, a, ctx)},
140
- {cap: 'graph', name: 'prepare_change', description: 'Select active target-architecture rules for an intended set of changed files. Run before a non-trivial edit.', inputSchema: {type: 'object', properties: {intent: {type: 'string'}, files: {type: 'array', items: {type: 'string'}, maxItems: 200}}, required: ['files']}, run: (g, a, ctx) => tar.tPrepareChange(g, a, ctx)},
141
- {cap: 'health', name: 'verify_architecture', description: 'Verify the fresh graph against the active target contract and ratchet; separates new, existing, fixed and excepted debt.', inputSchema: {type: 'object', properties: {}}, run: (g, a, ctx) => tar.tVerifyArchitecture(g, a, ctx)},
142
- {cap: 'health', name: 'explain_architecture_violation', description: 'Explain one active architecture violation and the governing rule.', inputSchema: {type: 'object', properties: {fingerprint: {type: 'string'}}, required: ['fingerprint']}, run: (g, a, ctx) => tar.tExplainArchitectureViolation(g, a, ctx)},
143
- {cap: 'health', name: 'propose_architecture_exception', description: 'Prepare, but never apply, a bounded exception proposal for human review.', inputSchema: {type: 'object', properties: {fingerprint: {type: 'string'}, reason: {type: 'string'}, expires: {type: 'string', description: 'Optional YYYY-MM-DD'}}, required: ['fingerprint', 'reason']}, run: (g, a, ctx) => tar.tProposeArchitectureException(g, a, ctx)},
144
- {cap: 'retarget', name: 'open_repo', description: 'OFFLINE RETARGET: switch this server to another local Git repository, building its graph when missing. This explicit tool call changes the active repository boundary; pass build:false to probe without building. Omitted mode/precision preserve an existing graph; a new graph uses full and the startup precision setting (lsp unless WEAVATRIX_PRECISION=off). Omit the retarget capability at registration to pin one repository.', inputSchema: {type: 'object', properties: {path: {type: 'string', description: 'Absolute path to a Git working tree'}, build: {type: 'boolean', description: 'Build the graph when missing (default true)', default: true}, mode: {type: 'string', enum: ['full', 'no-tests', 'tests-only'], description: 'Optional build mode override; omit to preserve an existing graph'}, precision: {type: 'string', enum: ['lsp', 'off'], description: 'Optional semantic precision override; omit to preserve an existing graph or use the startup setting for a new graph'}} , required: ['path']}, run: (g, a, ctx) => ta.tOpenRepo(g, a, ctx)},
145
- {cap: 'retarget', name: 'list_known_repos', description: 'OFFLINE RETARGET: list every registered local repository graph from the global per-user registry, regardless of parent folder.', inputSchema: {type: 'object', properties: {}}, run: (g, a, ctx) => ta.tListKnownRepos(g, a, ctx)},
146
- ]
147
- // Every tool supports the same machine-output switch. Text mode is TextContent-only so large
148
- // analysis payloads are not duplicated into an agent's context; JSON opts into structuredContent
149
- // and mirrors that envelope into TextContent for older workflow runners. Since MCP output schemas
150
- // apply to every invocation, tools do not advertise one conditionally here.
151
- return tools.map((tool) => ({
152
- ...tool,
153
- inputSchema: {
154
- ...(tool.inputSchema || {type: 'object'}),
155
- properties: {
156
- ...(tool.inputSchema?.properties || {}),
157
- output_format: {
158
- type: 'string',
159
- enum: ['text', 'json'],
160
- default: 'text',
161
- description: 'text returns only the concise TextContent summary; json also returns and mirrors the stable structuredContent envelope',
162
- },
163
- },
164
- },
165
- }))
166
- }
167
-
168
- // Import the tool modules (cache-busted when version > 0), build the catalog, apply the caps filter.
169
- // capsArg semantics: undefined/null = offline defaults; a
170
- // present string (even '') is an explicit selection, so "select nothing" really exposes nothing.
171
- export async function loadHotApi(version, capsArg, {extensions: extensionDefinitions = []} = {}) {
172
- const v = version ? `?v=${version}` : ''
173
- const [tg, ti, th, ts, tb, te, ta, tar, thi, tc, tv] = await Promise.all([
174
- import(new URL(`./tools-graph.mjs${v}`, import.meta.url).href),
175
- import(new URL(`./tools-impact.mjs${v}`, import.meta.url).href),
176
- import(new URL(`./tools-health.mjs${v}`, import.meta.url).href),
177
- import(new URL(`./tools-source.mjs${v}`, import.meta.url).href),
178
- import(new URL(`./tools-context.mjs${v}`, import.meta.url).href),
179
- import(new URL(`./tools-endpoints.mjs${v}`, import.meta.url).href),
180
- import(new URL(`./tools-actions.mjs${v}`, import.meta.url).href),
181
- import(new URL(`./tools-architecture.mjs${v}`, import.meta.url).href),
182
- import(new URL(`./tools-history.mjs${v}`, import.meta.url).href),
183
- import(new URL(`./tools-company.mjs${v}`, import.meta.url).href),
184
- import(new URL(`./tools-verified-change.mjs${v}`, import.meta.url).href),
185
- ])
186
- const extensions = normalizeWeavatrixExtensions(extensionDefinitions)
187
- const extensionProfiles = Object.assign({}, ...extensions.map((extension) => extension.profiles))
188
- for (const name of Object.keys(extensionProfiles)) {
189
- if (Object.hasOwn(PROFILE_CAPS, name)) throw new TypeError(`extension profile collides with core profile: ${name}`)
190
- }
191
- const profiles = {...PROFILE_CAPS, ...extensionProfiles}
192
- const raw = capsArg == null ? 'offline' : String(capsArg).trim()
193
- if (MOVED_PROFILES.has(raw) && !Object.hasOwn(extensionProfiles, raw)) {
194
- throw new Error(`MCP profile "${raw}" moved to weavatrix-online; the MIT core exposes only offline network-free profiles`)
195
- }
196
- const profile = Object.hasOwn(profiles, raw) ? raw : 'custom'
197
- const selected = profiles[raw] || raw.split(',').map((s) => s.trim()).filter(Boolean)
198
- const caps = new Set(selected)
199
- const coreTools = buildTools({tg, ti, th, ts, tb, te, ta, tar, thi, tc, tv, caps})
200
- const extensionTools = extensions.flatMap((extension) => extension.tools.map((tool) => ({...tool, extension: extension.name})))
201
- const all = [...coreTools, ...extensionTools.map((tool) => ({
202
- ...tool,
203
- inputSchema: {
204
- ...(tool.inputSchema || {type: 'object'}),
205
- properties: {
206
- ...(tool.inputSchema?.properties || {}),
207
- output_format: {
208
- type: 'string', enum: ['text', 'json'], default: 'text',
209
- description: 'text returns concise TextContent; json also returns the stable structuredContent envelope',
210
- },
211
- },
212
- },
213
- }))]
214
- const toolNames = new Set()
215
- for (const tool of all) {
216
- if (toolNames.has(tool.name)) throw new TypeError(`extension tool collides with an existing tool: ${tool.name}`)
217
- toolNames.add(tool.name)
218
- }
219
- const tools = all.filter((t) => caps.has(t.cap))
220
- return {
221
- tools,
222
- byName: new Map(tools.map((t) => [t.name, t])),
223
- caps,
224
- profile,
225
- extensions: {
226
- items: extensionRuntimeSummary(extensions),
227
- auditProviders: extensions.flatMap((extension) => extension.auditProviders.map((provider) => ({...provider, extension: extension.name}))),
228
- skills: extensions.flatMap((extension) => extension.skills.map((skill) => ({...skill, extension: extension.name}))),
229
- },
230
- stalenessLine,
231
- resetStalenessCache,
232
- }
233
- }
@@ -1,142 +0,0 @@
1
- import {createHash} from 'node:crypto'
2
-
3
- const DEFAULT_PAGE_SIZE = 10
4
- const MAX_PAGE_SIZE = 50
5
- const DEFAULT_RELATED_LIMIT = 5
6
- const MAX_RELATED_LIMIT = 25
7
-
8
- export class ContractCursorError extends Error {
9
- constructor(message) {
10
- super(message)
11
- this.name = 'ContractCursorError'
12
- this.code = 'INVALID_CURSOR'
13
- }
14
- }
15
-
16
- const boundedInteger = (value, fallback, minimum, maximum) => {
17
- const parsed = Number(value)
18
- return Number.isInteger(parsed) ? Math.max(minimum, Math.min(maximum, parsed)) : fallback
19
- }
20
-
21
- const pageFingerprint = (parts) => createHash('sha256')
22
- .update(JSON.stringify(parts))
23
- .digest('hex')
24
- .slice(0, 24)
25
-
26
- const encodeCursor = (fingerprint, offset) => Buffer
27
- .from(JSON.stringify({v: 1, fingerprint, offset}), 'utf8')
28
- .toString('base64url')
29
-
30
- function decodeCursor(raw, fingerprint, total) {
31
- if (!raw) return 0
32
- try {
33
- const parsed = JSON.parse(Buffer.from(String(raw), 'base64url').toString('utf8'))
34
- if (parsed?.v !== 1 || parsed.fingerprint !== fingerprint) {
35
- throw new ContractCursorError('cursor does not belong to this repository revision and filter set')
36
- }
37
- if (!Number.isInteger(parsed.offset) || parsed.offset < 0 || parsed.offset > total) {
38
- throw new ContractCursorError('cursor offset is outside the available evidence')
39
- }
40
- return parsed.offset
41
- } catch (error) {
42
- if (error instanceof ContractCursorError) throw error
43
- throw new ContractCursorError('cursor is malformed')
44
- }
45
- }
46
-
47
- const sample = (items, limit) => ({
48
- total: Array.isArray(items) ? items.length : 0,
49
- items: Array.isArray(items) ? items.slice(0, limit) : [],
50
- truncated: Array.isArray(items) && items.length > limit,
51
- })
52
-
53
- function compactEndpoint(endpoint, relatedLimit) {
54
- const affected = endpoint?.affected || {}
55
- return {
56
- kind: 'http-endpoint',
57
- backend: endpoint.backend,
58
- method: endpoint.method,
59
- path: endpoint.path,
60
- normalizedPath: endpoint.normalizedPath,
61
- handler: endpoint.handler,
62
- handlerNodeId: endpoint.handlerNodeId,
63
- handlerResolution: endpoint.handlerResolution,
64
- file: endpoint.file,
65
- line: endpoint.line,
66
- callsites: sample(endpoint.callsites, relatedLimit),
67
- methodMismatches: endpoint.methodMismatches,
68
- methodMismatchSites: sample(endpoint.methodMismatchSites, relatedLimit),
69
- liveness: endpoint.liveness,
70
- affected: {
71
- complete: affected.complete === true,
72
- files: sample(affected.files, relatedLimit),
73
- screens: sample(affected.screens, relatedLimit),
74
- modules: sample(affected.modules, relatedLimit),
75
- traversalTruncated: affected.truncated || {},
76
- },
77
- }
78
- }
79
-
80
- function compactTransport(contract, relatedLimit) {
81
- const affected = contract?.affected || {}
82
- return {
83
- kind: 'transport-contract',
84
- transport: contract.transport,
85
- side: contract.side,
86
- service: contract.service,
87
- operation: contract.operation,
88
- name: contract.name,
89
- file: contract.file,
90
- line: contract.line,
91
- detector: contract.detector,
92
- liveness: contract.liveness,
93
- runtimeObserved: contract.runtimeObserved === true,
94
- callsites: sample(contract.callsites, relatedLimit),
95
- affected: {
96
- complete: affected.complete === true,
97
- files: sample(affected.files, relatedLimit),
98
- screens: sample(affected.screens, relatedLimit),
99
- modules: sample(affected.modules, relatedLimit),
100
- traversalTruncated: affected.truncated || {},
101
- },
102
- }
103
- }
104
-
105
- function evidenceItems(analysis, transportAnalysis) {
106
- return [
107
- ...(analysis.endpoints || []).map((value) => ({kind: 'http-endpoint', value})),
108
- ...(transportAnalysis.contracts || []).map((value) => ({kind: 'transport-contract', value})),
109
- ...(analysis.uncertain || []).map((value) => ({kind: 'http-uncertain', value})),
110
- ...(transportAnalysis.uncertain || []).map((value) => ({kind: 'transport-uncertain', value})),
111
- ]
112
- }
113
-
114
- function projectItem(item, detail, relatedLimit) {
115
- if (detail === 'full') return {kind: item.kind, ...item.value}
116
- if (item.kind === 'http-endpoint') return compactEndpoint(item.value, relatedLimit)
117
- if (item.kind === 'transport-contract') return compactTransport(item.value, relatedLimit)
118
- return {kind: item.kind, ...item.value}
119
- }
120
-
121
- export function paginateContractEvidence({analysis, transportAnalysis, args = {}, fingerprintParts = []}) {
122
- const detail = args.response_detail === 'full' ? 'full' : 'compact'
123
- const pageSize = boundedInteger(args.page_size, DEFAULT_PAGE_SIZE, 1, MAX_PAGE_SIZE)
124
- const relatedLimit = boundedInteger(args.per_item_limit, DEFAULT_RELATED_LIMIT, 1, MAX_RELATED_LIMIT)
125
- const all = evidenceItems(analysis, transportAnalysis)
126
- const fingerprint = pageFingerprint(fingerprintParts)
127
- const offset = decodeCursor(args.cursor, fingerprint, all.length)
128
- const selected = all.slice(offset, offset + pageSize)
129
- const nextOffset = offset + selected.length
130
- const nextCursor = nextOffset < all.length ? encodeCursor(fingerprint, nextOffset) : null
131
- return {
132
- detail,
133
- offset,
134
- pageSize,
135
- perItemLimit: detail === 'compact' ? relatedLimit : null,
136
- totalItems: all.length,
137
- returnedItems: selected.length,
138
- hasMore: nextCursor !== null,
139
- nextCursor,
140
- items: selected.map((item) => projectItem(item, detail, relatedLimit)),
141
- }
142
- }
@@ -1,87 +0,0 @@
1
- import {join} from 'node:path'
2
- import {buildGraphForRepo} from '../build-graph.js'
3
- import {persistedFreshnessMatches, repositoryFreshnessProbe} from '../graph/freshness-probe.js'
4
- import {loadGraph} from './graph-context.mjs'
5
-
6
- export const publicRecord = (record, alias) => ({repositoryId: record.repositoryId, label: record.label, alias})
7
-
8
- function safeRefreshReason(record, error) {
9
- let message = String(error instanceof Error ? error.message : error || 'graph refresh failed')
10
- for (const path of [record.repoPath, record.graphDir]) {
11
- if (!path) continue
12
- message = message.split(String(path)).join(record.label || 'repository')
13
- message = message.split(String(path).replace(/\\/g, '/')).join(record.label || 'repository')
14
- }
15
- return message.replace(/[\r\n]+/g, ' ').trim().slice(0, 400) || 'graph refresh failed'
16
- }
17
-
18
- export async function reconcileGraph(record, alias, role, graphHome) {
19
- let registeredGraph = null
20
- try { registeredGraph = loadGraph(join(record.graphDir, 'graph.json'), {repoRoot: record.repoPath}) } catch { /* refresh may repair it */ }
21
- const buildMode = ['full', 'no-tests', 'tests-only'].includes(registeredGraph?.graphBuildMode)
22
- ? registeredGraph.graphBuildMode
23
- : 'full'
24
- const precision = registeredGraph?.graphPrecisionMode === 'off' ? 'off' : 'lsp'
25
- const freshnessProbe = repositoryFreshnessProbe(record.repoPath)
26
- if (registeredGraph && freshnessProbe && persistedFreshnessMatches(registeredGraph, freshnessProbe, buildMode)) {
27
- return {
28
- record,
29
- alias,
30
- graph: registeredGraph,
31
- publicStatus: {
32
- role,
33
- repository: publicRecord(record, alias),
34
- buildMode,
35
- status: 'CURRENT',
36
- refresh: {kind: 'none', reason: 'persisted-freshness-match', changedFileCount: 0},
37
- },
38
- }
39
- }
40
- let build
41
- try {
42
- build = await buildGraphForRepo(record.repoPath, {
43
- mode: buildMode,
44
- precision,
45
- scope: '',
46
- outDir: record.graphDir,
47
- graphHome,
48
- })
49
- } catch (error) {
50
- build = {ok: false, error: safeRefreshReason(record, error)}
51
- }
52
- const refreshKind = build?.refresh?.kind || null
53
- const changedFiles = Array.isArray(build?.refresh?.changedFiles) ? build.refresh.changedFiles : []
54
- const publicStatus = {
55
- role,
56
- repository: publicRecord(record, alias),
57
- buildMode,
58
- status: build?.ok ? (refreshKind === 'none' ? 'CURRENT' : 'REFRESHED') : 'STALE_FALLBACK',
59
- refresh: build?.ok ? {
60
- kind: refreshKind || 'full',
61
- reason: String(build?.refresh?.reason || 'graph-rebuilt'),
62
- changedFileCount: changedFiles.length,
63
- } : null,
64
- }
65
- if (build?.ok) {
66
- try {
67
- return {record, alias, graph: loadGraph(join(record.graphDir, 'graph.json'), {repoRoot: record.repoPath}), publicStatus}
68
- } catch (error) {
69
- const reason = `refreshed graph could not be loaded: ${safeRefreshReason(record, error)}`
70
- return {record, alias, graph: null, reason, publicStatus: {...publicStatus, status: 'FAILED', reason}}
71
- }
72
- }
73
-
74
- const reason = `graph refresh failed: ${safeRefreshReason(record, build?.error)}`
75
- try {
76
- return {
77
- record,
78
- alias,
79
- graph: loadGraph(join(record.graphDir, 'graph.json'), {repoRoot: record.repoPath}),
80
- reason: `${reason}; stale registered graph used`,
81
- publicStatus: {...publicStatus, reason: `${reason}; stale registered graph used`},
82
- }
83
- } catch (error) {
84
- const loadReason = `${reason}; registered graph could not be loaded: ${safeRefreshReason(record, error)}`
85
- return {record, alias, graph: null, reason: loadReason, publicStatus: {...publicStatus, status: 'FAILED', reason: loadReason}}
86
- }
87
- }
@@ -1,42 +0,0 @@
1
- export function contractVerdict(analysis, transportAnalysis) {
2
- const endpointsWithCallers = analysis.endpoints.filter((endpoint) => endpoint.callsites.length > 0)
3
- const transportWithCallers = (transportAnalysis.contracts || []).filter((contract) => contract.callsites.length > 0)
4
- const affectedFiles = new Set(), affectedScreens = new Set()
5
- for (const contract of [...endpointsWithCallers, ...transportWithCallers]) {
6
- for (const item of contract.affected.files || []) affectedFiles.add(`${item.client}\0${item.file}`)
7
- for (const item of contract.affected.screens || []) affectedScreens.add(`${item.client}\0${item.file}`)
8
- }
9
- const totalContracts = analysis.totals.endpoints + transportAnalysis.totals.contracts
10
- const totalMatches = analysis.totals.matches + transportAnalysis.totals.matches
11
- let code = 'NO_ENDPOINTS_MATCHED', risk = 'unknown'
12
- if (totalContracts > 0 && totalMatches === 0) {
13
- code = analysis.totals.methodMismatches > 0 ? 'HTTP_METHOD_MISMATCH' : 'NO_STATIC_CLIENT_CALLERS'
14
- risk = analysis.totals.methodMismatches > 0 ? 'high' : 'unknown'
15
- } else if (totalMatches > 0 && analysis.totals.methodMismatches > 0) {
16
- code = 'CLIENTS_AT_RISK_WITH_METHOD_MISMATCHES'; risk = 'high'
17
- } else if (totalMatches > 0) {
18
- code = 'CLIENTS_AT_RISK'; risk = 'medium'
19
- }
20
- return {
21
- code, risk,
22
- endpointsWithCallers: endpointsWithCallers.length + transportWithCallers.length,
23
- callsites: totalMatches,
24
- affectedFiles: affectedFiles.size,
25
- affectedScreens: affectedScreens.size,
26
- methodMismatches: analysis.totals.methodMismatches,
27
- uncertainCalls: analysis.totals.uncertainCalls + transportAnalysis.totals.uncertain,
28
- transportContracts: transportAnalysis.totals.contracts,
29
- transportMatches: transportAnalysis.totals.matches,
30
- notDeadExternalUse: analysis.totals.notDeadExternalUse,
31
- notDeadExternalHandlers: analysis.totals.notDeadExternalHandlers,
32
- possibleExternalUse: analysis.totals.possibleExternalUse,
33
- unknownLiveness: analysis.totals.unknownLiveness,
34
- }
35
- }
36
-
37
- export function contractVerdictLine(verdict, contracts) {
38
- if (verdict.code === 'NO_ENDPOINTS_MATCHED') return 'VERDICT NO_ENDPOINTS_MATCHED — no backend contract satisfied the requested transport/method/path/change filter.'
39
- if (verdict.code === 'NO_STATIC_CLIENT_CALLERS') return `VERDICT NO_STATIC_CLIENT_CALLERS — ${contracts} backend contract(s) matched, but no bounded static client call was proven; this is unknown, not proof of no consumers.`
40
- if (verdict.code === 'HTTP_METHOD_MISMATCH') return `VERDICT HTTP_METHOD_MISMATCH — ${verdict.methodMismatches} client call(s) match the route shape with a different method.`
41
- return `VERDICT ${verdict.code} — ${verdict.callsites} callsite(s) reach ${verdict.endpointsWithCallers} contract(s); ${verdict.affectedScreens} screen(s) and ${verdict.affectedFiles} file(s) are in the bounded blast radius.`
42
- }