@untangle-code/cli 0.0.0-stage → 0.1.1

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 (571) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +90 -0
  3. package/README.md +451 -2
  4. package/THIRD_PARTY_LICENSES.md +152 -0
  5. package/dist/cli/args.d.ts +48 -0
  6. package/dist/cli/args.d.ts.map +1 -0
  7. package/dist/cli/args.js +167 -0
  8. package/dist/cli/args.js.map +1 -0
  9. package/dist/cli/command.d.ts +69 -0
  10. package/dist/cli/command.d.ts.map +1 -0
  11. package/dist/cli/command.js +82 -0
  12. package/dist/cli/command.js.map +1 -0
  13. package/dist/cli/deadline.d.ts +33 -0
  14. package/dist/cli/deadline.d.ts.map +1 -0
  15. package/dist/cli/deadline.js +49 -0
  16. package/dist/cli/deadline.js.map +1 -0
  17. package/dist/cli/embedder-deadline.d.ts +72 -0
  18. package/dist/cli/embedder-deadline.d.ts.map +1 -0
  19. package/dist/cli/embedder-deadline.js +117 -0
  20. package/dist/cli/embedder-deadline.js.map +1 -0
  21. package/dist/cli/exit.d.ts +71 -0
  22. package/dist/cli/exit.d.ts.map +1 -0
  23. package/dist/cli/exit.js +81 -0
  24. package/dist/cli/exit.js.map +1 -0
  25. package/dist/cli/graphs.d.ts +65 -0
  26. package/dist/cli/graphs.d.ts.map +1 -0
  27. package/dist/cli/graphs.js +108 -0
  28. package/dist/cli/graphs.js.map +1 -0
  29. package/dist/cli/index-freshness.d.ts +13 -0
  30. package/dist/cli/index-freshness.d.ts.map +1 -0
  31. package/dist/cli/index-freshness.js +226 -0
  32. package/dist/cli/index-freshness.js.map +1 -0
  33. package/dist/cli/index-refresh.d.ts +10 -0
  34. package/dist/cli/index-refresh.d.ts.map +1 -0
  35. package/dist/cli/index-refresh.js +76 -0
  36. package/dist/cli/index-refresh.js.map +1 -0
  37. package/dist/cli/index-state.d.ts +184 -0
  38. package/dist/cli/index-state.d.ts.map +1 -0
  39. package/dist/cli/index-state.js +852 -0
  40. package/dist/cli/index-state.js.map +1 -0
  41. package/dist/cli/io.d.ts +29 -0
  42. package/dist/cli/io.d.ts.map +1 -0
  43. package/dist/cli/io.js +42 -0
  44. package/dist/cli/io.js.map +1 -0
  45. package/dist/cli/language-support.d.ts +77 -0
  46. package/dist/cli/language-support.d.ts.map +1 -0
  47. package/dist/cli/language-support.js +152 -0
  48. package/dist/cli/language-support.js.map +1 -0
  49. package/dist/cli/renewing-lease.d.ts +26 -0
  50. package/dist/cli/renewing-lease.d.ts.map +1 -0
  51. package/dist/cli/renewing-lease.js +102 -0
  52. package/dist/cli/renewing-lease.js.map +1 -0
  53. package/dist/cli/search-graph-enrichment.d.ts +16 -0
  54. package/dist/cli/search-graph-enrichment.d.ts.map +1 -0
  55. package/dist/cli/search-graph-enrichment.js +428 -0
  56. package/dist/cli/search-graph-enrichment.js.map +1 -0
  57. package/dist/cli/system.d.ts +34 -0
  58. package/dist/cli/system.d.ts.map +1 -0
  59. package/dist/cli/system.js +89 -0
  60. package/dist/cli/system.js.map +1 -0
  61. package/dist/cli/vector-store-policy.d.ts +116 -0
  62. package/dist/cli/vector-store-policy.d.ts.map +1 -0
  63. package/dist/cli/vector-store-policy.js +160 -0
  64. package/dist/cli/vector-store-policy.js.map +1 -0
  65. package/dist/cli/version.d.ts +3 -0
  66. package/dist/cli/version.d.ts.map +1 -0
  67. package/dist/cli/version.js +3 -0
  68. package/dist/cli/version.js.map +1 -0
  69. package/dist/cli/workspace.d.ts +105 -0
  70. package/dist/cli/workspace.d.ts.map +1 -0
  71. package/dist/cli/workspace.js +215 -0
  72. package/dist/cli/workspace.js.map +1 -0
  73. package/dist/commands/complexity.d.ts +72 -0
  74. package/dist/commands/complexity.d.ts.map +1 -0
  75. package/dist/commands/complexity.js +580 -0
  76. package/dist/commands/complexity.js.map +1 -0
  77. package/dist/commands/doctor.d.ts +66 -0
  78. package/dist/commands/doctor.d.ts.map +1 -0
  79. package/dist/commands/doctor.js +764 -0
  80. package/dist/commands/doctor.js.map +1 -0
  81. package/dist/commands/doctor.languages.d.ts +37 -0
  82. package/dist/commands/doctor.languages.d.ts.map +1 -0
  83. package/dist/commands/doctor.languages.js +143 -0
  84. package/dist/commands/doctor.languages.js.map +1 -0
  85. package/dist/commands/doctor.storage.d.ts +17 -0
  86. package/dist/commands/doctor.storage.d.ts.map +1 -0
  87. package/dist/commands/doctor.storage.js +91 -0
  88. package/dist/commands/doctor.storage.js.map +1 -0
  89. package/dist/commands/extensions/add.d.ts +10 -0
  90. package/dist/commands/extensions/add.d.ts.map +1 -0
  91. package/dist/commands/extensions/add.js +213 -0
  92. package/dist/commands/extensions/add.js.map +1 -0
  93. package/dist/commands/extensions/configure.d.ts +5 -0
  94. package/dist/commands/extensions/configure.d.ts.map +1 -0
  95. package/dist/commands/extensions/configure.js +58 -0
  96. package/dist/commands/extensions/configure.js.map +1 -0
  97. package/dist/commands/extensions/container.d.ts +15 -0
  98. package/dist/commands/extensions/container.d.ts.map +1 -0
  99. package/dist/commands/extensions/container.js +135 -0
  100. package/dist/commands/extensions/container.js.map +1 -0
  101. package/dist/commands/extensions/context.d.ts +37 -0
  102. package/dist/commands/extensions/context.d.ts.map +1 -0
  103. package/dist/commands/extensions/context.js +72 -0
  104. package/dist/commands/extensions/context.js.map +1 -0
  105. package/dist/commands/extensions/enable.d.ts +6 -0
  106. package/dist/commands/extensions/enable.d.ts.map +1 -0
  107. package/dist/commands/extensions/enable.js +180 -0
  108. package/dist/commands/extensions/enable.js.map +1 -0
  109. package/dist/commands/extensions/environment.d.ts +74 -0
  110. package/dist/commands/extensions/environment.d.ts.map +1 -0
  111. package/dist/commands/extensions/environment.js +168 -0
  112. package/dist/commands/extensions/environment.js.map +1 -0
  113. package/dist/commands/extensions/feature-scan.d.ts +48 -0
  114. package/dist/commands/extensions/feature-scan.d.ts.map +1 -0
  115. package/dist/commands/extensions/feature-scan.js +156 -0
  116. package/dist/commands/extensions/feature-scan.js.map +1 -0
  117. package/dist/commands/extensions/invoke.d.ts +60 -0
  118. package/dist/commands/extensions/invoke.d.ts.map +1 -0
  119. package/dist/commands/extensions/invoke.js +275 -0
  120. package/dist/commands/extensions/invoke.js.map +1 -0
  121. package/dist/commands/extensions/list.d.ts +5 -0
  122. package/dist/commands/extensions/list.d.ts.map +1 -0
  123. package/dist/commands/extensions/list.js +125 -0
  124. package/dist/commands/extensions/list.js.map +1 -0
  125. package/dist/commands/extensions/logs.d.ts +5 -0
  126. package/dist/commands/extensions/logs.d.ts.map +1 -0
  127. package/dist/commands/extensions/logs.js +74 -0
  128. package/dist/commands/extensions/logs.js.map +1 -0
  129. package/dist/commands/extensions/provision.d.ts +36 -0
  130. package/dist/commands/extensions/provision.d.ts.map +1 -0
  131. package/dist/commands/extensions/provision.js +137 -0
  132. package/dist/commands/extensions/provision.js.map +1 -0
  133. package/dist/commands/extensions/remove.d.ts +5 -0
  134. package/dist/commands/extensions/remove.d.ts.map +1 -0
  135. package/dist/commands/extensions/remove.js +77 -0
  136. package/dist/commands/extensions/remove.js.map +1 -0
  137. package/dist/commands/extensions/render.d.ts +16 -0
  138. package/dist/commands/extensions/render.d.ts.map +1 -0
  139. package/dist/commands/extensions/render.js +25 -0
  140. package/dist/commands/extensions/render.js.map +1 -0
  141. package/dist/commands/extensions/run.d.ts +30 -0
  142. package/dist/commands/extensions/run.d.ts.map +1 -0
  143. package/dist/commands/extensions/run.js +292 -0
  144. package/dist/commands/extensions/run.js.map +1 -0
  145. package/dist/commands/extensions/secret-store.d.ts +15 -0
  146. package/dist/commands/extensions/secret-store.d.ts.map +1 -0
  147. package/dist/commands/extensions/secret-store.js +164 -0
  148. package/dist/commands/extensions/secret-store.js.map +1 -0
  149. package/dist/commands/extensions/setup.d.ts +17 -0
  150. package/dist/commands/extensions/setup.d.ts.map +1 -0
  151. package/dist/commands/extensions/setup.js +94 -0
  152. package/dist/commands/extensions/setup.js.map +1 -0
  153. package/dist/commands/extensions/source.d.ts +47 -0
  154. package/dist/commands/extensions/source.d.ts.map +1 -0
  155. package/dist/commands/extensions/source.js +142 -0
  156. package/dist/commands/extensions/source.js.map +1 -0
  157. package/dist/commands/extensions/state.d.ts +62 -0
  158. package/dist/commands/extensions/state.d.ts.map +1 -0
  159. package/dist/commands/extensions/state.js +177 -0
  160. package/dist/commands/extensions/state.js.map +1 -0
  161. package/dist/commands/extensions/status.d.ts +9 -0
  162. package/dist/commands/extensions/status.d.ts.map +1 -0
  163. package/dist/commands/extensions/status.js +239 -0
  164. package/dist/commands/extensions/status.js.map +1 -0
  165. package/dist/commands/extensions.d.ts +3 -0
  166. package/dist/commands/extensions.d.ts.map +1 -0
  167. package/dist/commands/extensions.js +223 -0
  168. package/dist/commands/extensions.js.map +1 -0
  169. package/dist/commands/flow/index.d.ts +3 -0
  170. package/dist/commands/flow/index.d.ts.map +1 -0
  171. package/dist/commands/flow/index.js +295 -0
  172. package/dist/commands/flow/index.js.map +1 -0
  173. package/dist/commands/flow/limits.d.ts +12 -0
  174. package/dist/commands/flow/limits.d.ts.map +1 -0
  175. package/dist/commands/flow/limits.js +13 -0
  176. package/dist/commands/flow/limits.js.map +1 -0
  177. package/dist/commands/flow/output.d.ts +8 -0
  178. package/dist/commands/flow/output.d.ts.map +1 -0
  179. package/dist/commands/flow/output.js +11 -0
  180. package/dist/commands/flow/output.js.map +1 -0
  181. package/dist/commands/flow/render.d.ts +51 -0
  182. package/dist/commands/flow/render.d.ts.map +1 -0
  183. package/dist/commands/flow/render.js +220 -0
  184. package/dist/commands/flow/render.js.map +1 -0
  185. package/dist/commands/flow/select.d.ts +31 -0
  186. package/dist/commands/flow/select.d.ts.map +1 -0
  187. package/dist/commands/flow/select.js +62 -0
  188. package/dist/commands/flow/select.js.map +1 -0
  189. package/dist/commands/graph/boundaries-input.d.ts +26 -0
  190. package/dist/commands/graph/boundaries-input.d.ts.map +1 -0
  191. package/dist/commands/graph/boundaries-input.js +54 -0
  192. package/dist/commands/graph/boundaries-input.js.map +1 -0
  193. package/dist/commands/graph/boundaries.d.ts +4 -0
  194. package/dist/commands/graph/boundaries.d.ts.map +1 -0
  195. package/dist/commands/graph/boundaries.js +141 -0
  196. package/dist/commands/graph/boundaries.js.map +1 -0
  197. package/dist/commands/graph/bounds.d.ts +16 -0
  198. package/dist/commands/graph/bounds.d.ts.map +1 -0
  199. package/dist/commands/graph/bounds.js +22 -0
  200. package/dist/commands/graph/bounds.js.map +1 -0
  201. package/dist/commands/graph/category-clusters.d.ts +20 -0
  202. package/dist/commands/graph/category-clusters.d.ts.map +1 -0
  203. package/dist/commands/graph/category-clusters.js +158 -0
  204. package/dist/commands/graph/category-clusters.js.map +1 -0
  205. package/dist/commands/graph/feature-3d.d.ts +22 -0
  206. package/dist/commands/graph/feature-3d.d.ts.map +1 -0
  207. package/dist/commands/graph/feature-3d.js +114 -0
  208. package/dist/commands/graph/feature-3d.js.map +1 -0
  209. package/dist/commands/graph/feature-clusters.d.ts +17 -0
  210. package/dist/commands/graph/feature-clusters.d.ts.map +1 -0
  211. package/dist/commands/graph/feature-clusters.js +94 -0
  212. package/dist/commands/graph/feature-clusters.js.map +1 -0
  213. package/dist/commands/graph/feature-html.d.ts +26 -0
  214. package/dist/commands/graph/feature-html.d.ts.map +1 -0
  215. package/dist/commands/graph/feature-html.js +314 -0
  216. package/dist/commands/graph/feature-html.js.map +1 -0
  217. package/dist/commands/graph/feature-map.d.ts +31 -0
  218. package/dist/commands/graph/feature-map.d.ts.map +1 -0
  219. package/dist/commands/graph/feature-map.js +217 -0
  220. package/dist/commands/graph/feature-map.js.map +1 -0
  221. package/dist/commands/graph/feature-model.d.ts +55 -0
  222. package/dist/commands/graph/feature-model.d.ts.map +1 -0
  223. package/dist/commands/graph/feature-model.js +167 -0
  224. package/dist/commands/graph/feature-model.js.map +1 -0
  225. package/dist/commands/graph/feature-untangle-motion.d.ts +33 -0
  226. package/dist/commands/graph/feature-untangle-motion.d.ts.map +1 -0
  227. package/dist/commands/graph/feature-untangle-motion.js +155 -0
  228. package/dist/commands/graph/feature-untangle-motion.js.map +1 -0
  229. package/dist/commands/graph/index.d.ts +3 -0
  230. package/dist/commands/graph/index.d.ts.map +1 -0
  231. package/dist/commands/graph/index.js +414 -0
  232. package/dist/commands/graph/index.js.map +1 -0
  233. package/dist/commands/graph/render.d.ts +63 -0
  234. package/dist/commands/graph/render.d.ts.map +1 -0
  235. package/dist/commands/graph/render.js +352 -0
  236. package/dist/commands/graph/render.js.map +1 -0
  237. package/dist/commands/graph/select.d.ts +34 -0
  238. package/dist/commands/graph/select.d.ts.map +1 -0
  239. package/dist/commands/graph/select.js +55 -0
  240. package/dist/commands/graph/select.js.map +1 -0
  241. package/dist/commands/index/index.d.ts +6 -0
  242. package/dist/commands/index/index.d.ts.map +1 -0
  243. package/dist/commands/index/index.js +461 -0
  244. package/dist/commands/index/index.js.map +1 -0
  245. package/dist/commands/index/render.d.ts +53 -0
  246. package/dist/commands/index/render.d.ts.map +1 -0
  247. package/dist/commands/index/render.js +205 -0
  248. package/dist/commands/index/render.js.map +1 -0
  249. package/dist/commands/index/target.d.ts +34 -0
  250. package/dist/commands/index/target.d.ts.map +1 -0
  251. package/dist/commands/index/target.js +97 -0
  252. package/dist/commands/index/target.js.map +1 -0
  253. package/dist/commands/index-watch.d.ts +30 -0
  254. package/dist/commands/index-watch.d.ts.map +1 -0
  255. package/dist/commands/index-watch.js +138 -0
  256. package/dist/commands/index-watch.js.map +1 -0
  257. package/dist/commands/init/command.d.ts +25 -0
  258. package/dist/commands/init/command.d.ts.map +1 -0
  259. package/dist/commands/init/command.js +198 -0
  260. package/dist/commands/init/command.js.map +1 -0
  261. package/dist/commands/init/config-draft.d.ts +48 -0
  262. package/dist/commands/init/config-draft.d.ts.map +1 -0
  263. package/dist/commands/init/config-draft.js +201 -0
  264. package/dist/commands/init/config-draft.js.map +1 -0
  265. package/dist/commands/init/diff.d.ts +33 -0
  266. package/dist/commands/init/diff.d.ts.map +1 -0
  267. package/dist/commands/init/diff.js +109 -0
  268. package/dist/commands/init/diff.js.map +1 -0
  269. package/dist/commands/init/http-probe.d.ts +26 -0
  270. package/dist/commands/init/http-probe.d.ts.map +1 -0
  271. package/dist/commands/init/http-probe.js +144 -0
  272. package/dist/commands/init/http-probe.js.map +1 -0
  273. package/dist/commands/init/ignore-rules.d.ts +70 -0
  274. package/dist/commands/init/ignore-rules.d.ts.map +1 -0
  275. package/dist/commands/init/ignore-rules.js +146 -0
  276. package/dist/commands/init/ignore-rules.js.map +1 -0
  277. package/dist/commands/init/index.d.ts +33 -0
  278. package/dist/commands/init/index.d.ts.map +1 -0
  279. package/dist/commands/init/index.js +32 -0
  280. package/dist/commands/init/index.js.map +1 -0
  281. package/dist/commands/init/integrations-apply.d.ts +50 -0
  282. package/dist/commands/init/integrations-apply.d.ts.map +1 -0
  283. package/dist/commands/init/integrations-apply.js +452 -0
  284. package/dist/commands/init/integrations-apply.js.map +1 -0
  285. package/dist/commands/init/integrations-plan.d.ts +68 -0
  286. package/dist/commands/init/integrations-plan.d.ts.map +1 -0
  287. package/dist/commands/init/integrations-plan.js +515 -0
  288. package/dist/commands/init/integrations-plan.js.map +1 -0
  289. package/dist/commands/init/integrations.d.ts +5 -0
  290. package/dist/commands/init/integrations.d.ts.map +1 -0
  291. package/dist/commands/init/integrations.js +4 -0
  292. package/dist/commands/init/integrations.js.map +1 -0
  293. package/dist/commands/init/languages.d.ts +55 -0
  294. package/dist/commands/init/languages.d.ts.map +1 -0
  295. package/dist/commands/init/languages.js +245 -0
  296. package/dist/commands/init/languages.js.map +1 -0
  297. package/dist/commands/init/product-context.d.ts +63 -0
  298. package/dist/commands/init/product-context.d.ts.map +1 -0
  299. package/dist/commands/init/product-context.js +390 -0
  300. package/dist/commands/init/product-context.js.map +1 -0
  301. package/dist/commands/init/render.d.ts +38 -0
  302. package/dist/commands/init/render.d.ts.map +1 -0
  303. package/dist/commands/init/render.js +501 -0
  304. package/dist/commands/init/render.js.map +1 -0
  305. package/dist/commands/init/report.d.ts +24 -0
  306. package/dist/commands/init/report.d.ts.map +1 -0
  307. package/dist/commands/init/report.js +163 -0
  308. package/dist/commands/init/report.js.map +1 -0
  309. package/dist/commands/init/run.d.ts +71 -0
  310. package/dist/commands/init/run.d.ts.map +1 -0
  311. package/dist/commands/init/run.js +253 -0
  312. package/dist/commands/init/run.js.map +1 -0
  313. package/dist/commands/init/services.d.ts +82 -0
  314. package/dist/commands/init/services.d.ts.map +1 -0
  315. package/dist/commands/init/services.js +209 -0
  316. package/dist/commands/init/services.js.map +1 -0
  317. package/dist/commands/init/storage.d.ts +8 -0
  318. package/dist/commands/init/storage.d.ts.map +1 -0
  319. package/dist/commands/init/storage.js +102 -0
  320. package/dist/commands/init/storage.js.map +1 -0
  321. package/dist/commands/init/toml.d.ts +65 -0
  322. package/dist/commands/init/toml.d.ts.map +1 -0
  323. package/dist/commands/init/toml.js +159 -0
  324. package/dist/commands/init/toml.js.map +1 -0
  325. package/dist/commands/init/topology.d.ts +54 -0
  326. package/dist/commands/init/topology.d.ts.map +1 -0
  327. package/dist/commands/init/topology.js +146 -0
  328. package/dist/commands/init/topology.js.map +1 -0
  329. package/dist/commands/init/types.d.ts +334 -0
  330. package/dist/commands/init/types.d.ts.map +1 -0
  331. package/dist/commands/init/types.js +31 -0
  332. package/dist/commands/init/types.js.map +1 -0
  333. package/dist/commands/init/wizard-terminal.d.ts +36 -0
  334. package/dist/commands/init/wizard-terminal.d.ts.map +1 -0
  335. package/dist/commands/init/wizard-terminal.js +163 -0
  336. package/dist/commands/init/wizard-terminal.js.map +1 -0
  337. package/dist/commands/init/wizard.d.ts +25 -0
  338. package/dist/commands/init/wizard.d.ts.map +1 -0
  339. package/dist/commands/init/wizard.js +358 -0
  340. package/dist/commands/init/wizard.js.map +1 -0
  341. package/dist/commands/mcp.d.ts +4 -0
  342. package/dist/commands/mcp.d.ts.map +1 -0
  343. package/dist/commands/mcp.js +111 -0
  344. package/dist/commands/mcp.js.map +1 -0
  345. package/dist/commands/product-test-runner.d.ts +4 -0
  346. package/dist/commands/product-test-runner.d.ts.map +1 -0
  347. package/dist/commands/product-test-runner.js +85 -0
  348. package/dist/commands/product-test-runner.js.map +1 -0
  349. package/dist/commands/product.d.ts +16 -0
  350. package/dist/commands/product.d.ts.map +1 -0
  351. package/dist/commands/product.js +262 -0
  352. package/dist/commands/product.js.map +1 -0
  353. package/dist/commands/query.d.ts +114 -0
  354. package/dist/commands/query.d.ts.map +1 -0
  355. package/dist/commands/query.js +783 -0
  356. package/dist/commands/query.js.map +1 -0
  357. package/dist/commands/scan.d.ts +10 -0
  358. package/dist/commands/scan.d.ts.map +1 -0
  359. package/dist/commands/scan.js +302 -0
  360. package/dist/commands/scan.js.map +1 -0
  361. package/dist/commands/sdd.d.ts +24 -0
  362. package/dist/commands/sdd.d.ts.map +1 -0
  363. package/dist/commands/sdd.js +195 -0
  364. package/dist/commands/sdd.js.map +1 -0
  365. package/dist/commands/unavailable.d.ts +40 -0
  366. package/dist/commands/unavailable.d.ts.map +1 -0
  367. package/dist/commands/unavailable.js +73 -0
  368. package/dist/commands/unavailable.js.map +1 -0
  369. package/dist/commands/watch/analyze.d.ts +69 -0
  370. package/dist/commands/watch/analyze.d.ts.map +1 -0
  371. package/dist/commands/watch/analyze.js +402 -0
  372. package/dist/commands/watch/analyze.js.map +1 -0
  373. package/dist/commands/watch/cli.d.ts +8 -0
  374. package/dist/commands/watch/cli.d.ts.map +1 -0
  375. package/dist/commands/watch/cli.js +97 -0
  376. package/dist/commands/watch/cli.js.map +1 -0
  377. package/dist/commands/watch/delta.d.ts +46 -0
  378. package/dist/commands/watch/delta.d.ts.map +1 -0
  379. package/dist/commands/watch/delta.js +170 -0
  380. package/dist/commands/watch/delta.js.map +1 -0
  381. package/dist/commands/watch/events.d.ts +214 -0
  382. package/dist/commands/watch/events.d.ts.map +1 -0
  383. package/dist/commands/watch/events.js +13 -0
  384. package/dist/commands/watch/events.js.map +1 -0
  385. package/dist/commands/watch/index.d.ts +19 -0
  386. package/dist/commands/watch/index.d.ts.map +1 -0
  387. package/dist/commands/watch/index.js +15 -0
  388. package/dist/commands/watch/index.js.map +1 -0
  389. package/dist/commands/watch/options.d.ts +43 -0
  390. package/dist/commands/watch/options.d.ts.map +1 -0
  391. package/dist/commands/watch/options.js +193 -0
  392. package/dist/commands/watch/options.js.map +1 -0
  393. package/dist/commands/watch/render.d.ts +26 -0
  394. package/dist/commands/watch/render.d.ts.map +1 -0
  395. package/dist/commands/watch/render.js +175 -0
  396. package/dist/commands/watch/render.js.map +1 -0
  397. package/dist/commands/watch/run.d.ts +14 -0
  398. package/dist/commands/watch/run.d.ts.map +1 -0
  399. package/dist/commands/watch/run.js +460 -0
  400. package/dist/commands/watch/run.js.map +1 -0
  401. package/dist/commands/watch/runtime.d.ts +67 -0
  402. package/dist/commands/watch/runtime.d.ts.map +1 -0
  403. package/dist/commands/watch/runtime.js +149 -0
  404. package/dist/commands/watch/runtime.js.map +1 -0
  405. package/dist/commands/watch-command.d.ts +3 -0
  406. package/dist/commands/watch-command.d.ts.map +1 -0
  407. package/dist/commands/watch-command.js +273 -0
  408. package/dist/commands/watch-command.js.map +1 -0
  409. package/dist/entry-point.d.ts +11 -0
  410. package/dist/entry-point.d.ts.map +1 -0
  411. package/dist/entry-point.js +65 -0
  412. package/dist/entry-point.js.map +1 -0
  413. package/dist/index.d.ts +38 -0
  414. package/dist/index.d.ts.map +1 -0
  415. package/dist/index.js +245 -0
  416. package/dist/index.js.map +1 -0
  417. package/dist/mcp/boundaries-schema.d.ts +262 -0
  418. package/dist/mcp/boundaries-schema.d.ts.map +1 -0
  419. package/dist/mcp/boundaries-schema.js +183 -0
  420. package/dist/mcp/boundaries-schema.js.map +1 -0
  421. package/dist/mcp/default-runtime.d.ts +16 -0
  422. package/dist/mcp/default-runtime.d.ts.map +1 -0
  423. package/dist/mcp/default-runtime.js +718 -0
  424. package/dist/mcp/default-runtime.js.map +1 -0
  425. package/dist/mcp/envelope.d.ts +13 -0
  426. package/dist/mcp/envelope.d.ts.map +1 -0
  427. package/dist/mcp/envelope.js +29 -0
  428. package/dist/mcp/envelope.js.map +1 -0
  429. package/dist/mcp/index.d.ts +8 -0
  430. package/dist/mcp/index.d.ts.map +1 -0
  431. package/dist/mcp/index.js +8 -0
  432. package/dist/mcp/index.js.map +1 -0
  433. package/dist/mcp/output-schemas.d.ts +5928 -0
  434. package/dist/mcp/output-schemas.d.ts.map +1 -0
  435. package/dist/mcp/output-schemas.js +2814 -0
  436. package/dist/mcp/output-schemas.js.map +1 -0
  437. package/dist/mcp/path-sandbox.d.ts +35 -0
  438. package/dist/mcp/path-sandbox.d.ts.map +1 -0
  439. package/dist/mcp/path-sandbox.js +128 -0
  440. package/dist/mcp/path-sandbox.js.map +1 -0
  441. package/dist/mcp/server.d.ts +155 -0
  442. package/dist/mcp/server.d.ts.map +1 -0
  443. package/dist/mcp/server.js +257 -0
  444. package/dist/mcp/server.js.map +1 -0
  445. package/dist/mcp/stdio.d.ts +20 -0
  446. package/dist/mcp/stdio.d.ts.map +1 -0
  447. package/dist/mcp/stdio.js +71 -0
  448. package/dist/mcp/stdio.js.map +1 -0
  449. package/dist/mcp/tools.d.ts +355 -0
  450. package/dist/mcp/tools.d.ts.map +1 -0
  451. package/dist/mcp/tools.js +319 -0
  452. package/dist/mcp/tools.js.map +1 -0
  453. package/dist/mcp/types.d.ts +15 -0
  454. package/dist/mcp/types.d.ts.map +1 -0
  455. package/dist/mcp/types.js +45 -0
  456. package/dist/mcp/types.js.map +1 -0
  457. package/package.json +99 -3
  458. package/src/cli/args.ts +231 -0
  459. package/src/cli/command.ts +146 -0
  460. package/src/cli/deadline.ts +56 -0
  461. package/src/cli/embedder-deadline.ts +187 -0
  462. package/src/cli/exit.ts +94 -0
  463. package/src/cli/graphs.ts +191 -0
  464. package/src/cli/index-freshness.ts +303 -0
  465. package/src/cli/index-refresh.ts +125 -0
  466. package/src/cli/index-state.ts +1217 -0
  467. package/src/cli/io.ts +56 -0
  468. package/src/cli/language-support.ts +203 -0
  469. package/src/cli/renewing-lease.ts +121 -0
  470. package/src/cli/search-graph-enrichment.ts +639 -0
  471. package/src/cli/system.ts +94 -0
  472. package/src/cli/vector-store-policy.ts +213 -0
  473. package/src/cli/version.ts +2 -0
  474. package/src/cli/workspace.ts +324 -0
  475. package/src/commands/complexity.ts +776 -0
  476. package/src/commands/doctor.languages.ts +187 -0
  477. package/src/commands/doctor.storage.ts +124 -0
  478. package/src/commands/doctor.ts +946 -0
  479. package/src/commands/extensions/add.ts +312 -0
  480. package/src/commands/extensions/configure.ts +82 -0
  481. package/src/commands/extensions/container.ts +209 -0
  482. package/src/commands/extensions/context.ts +109 -0
  483. package/src/commands/extensions/enable.ts +237 -0
  484. package/src/commands/extensions/environment.ts +218 -0
  485. package/src/commands/extensions/feature-scan.ts +232 -0
  486. package/src/commands/extensions/invoke.ts +368 -0
  487. package/src/commands/extensions/list.ts +158 -0
  488. package/src/commands/extensions/logs.ts +104 -0
  489. package/src/commands/extensions/provision.ts +206 -0
  490. package/src/commands/extensions/remove.ts +110 -0
  491. package/src/commands/extensions/render.ts +32 -0
  492. package/src/commands/extensions/run.ts +386 -0
  493. package/src/commands/extensions/secret-store.ts +223 -0
  494. package/src/commands/extensions/setup.ts +140 -0
  495. package/src/commands/extensions/source.ts +189 -0
  496. package/src/commands/extensions/state.ts +252 -0
  497. package/src/commands/extensions/status.ts +320 -0
  498. package/src/commands/extensions.ts +280 -0
  499. package/src/commands/flow/index.ts +402 -0
  500. package/src/commands/flow/limits.ts +14 -0
  501. package/src/commands/flow/output.ts +16 -0
  502. package/src/commands/flow/render.ts +318 -0
  503. package/src/commands/flow/select.ts +101 -0
  504. package/src/commands/graph/boundaries-input.ts +63 -0
  505. package/src/commands/graph/boundaries.ts +182 -0
  506. package/src/commands/graph/bounds.ts +25 -0
  507. package/src/commands/graph/category-clusters.ts +194 -0
  508. package/src/commands/graph/feature-3d.ts +141 -0
  509. package/src/commands/graph/feature-clusters.ts +125 -0
  510. package/src/commands/graph/feature-html.ts +336 -0
  511. package/src/commands/graph/feature-map.ts +270 -0
  512. package/src/commands/graph/feature-model.ts +234 -0
  513. package/src/commands/graph/feature-untangle-motion.ts +192 -0
  514. package/src/commands/graph/index.ts +562 -0
  515. package/src/commands/graph/render.ts +503 -0
  516. package/src/commands/graph/select.ts +77 -0
  517. package/src/commands/index/index.ts +595 -0
  518. package/src/commands/index/render.ts +325 -0
  519. package/src/commands/index/target.ts +136 -0
  520. package/src/commands/index-watch.ts +206 -0
  521. package/src/commands/init/command.ts +238 -0
  522. package/src/commands/init/config-draft.ts +235 -0
  523. package/src/commands/init/diff.ts +117 -0
  524. package/src/commands/init/http-probe.ts +150 -0
  525. package/src/commands/init/ignore-rules.ts +170 -0
  526. package/src/commands/init/index.ts +63 -0
  527. package/src/commands/init/integrations-apply.ts +618 -0
  528. package/src/commands/init/integrations-plan.ts +634 -0
  529. package/src/commands/init/integrations.ts +8 -0
  530. package/src/commands/init/languages.ts +320 -0
  531. package/src/commands/init/product-context.ts +576 -0
  532. package/src/commands/init/render.ts +598 -0
  533. package/src/commands/init/report.ts +204 -0
  534. package/src/commands/init/run.ts +349 -0
  535. package/src/commands/init/services.ts +271 -0
  536. package/src/commands/init/storage.ts +136 -0
  537. package/src/commands/init/toml.ts +174 -0
  538. package/src/commands/init/topology.ts +192 -0
  539. package/src/commands/init/types.ts +366 -0
  540. package/src/commands/init/wizard-terminal.ts +206 -0
  541. package/src/commands/init/wizard.ts +450 -0
  542. package/src/commands/mcp.ts +123 -0
  543. package/src/commands/product-test-runner.ts +80 -0
  544. package/src/commands/product.ts +331 -0
  545. package/src/commands/query.ts +1032 -0
  546. package/src/commands/scan.ts +360 -0
  547. package/src/commands/sdd.ts +243 -0
  548. package/src/commands/unavailable.ts +92 -0
  549. package/src/commands/watch/README.md +160 -0
  550. package/src/commands/watch/analyze.ts +600 -0
  551. package/src/commands/watch/cli.ts +108 -0
  552. package/src/commands/watch/delta.ts +186 -0
  553. package/src/commands/watch/events.ts +235 -0
  554. package/src/commands/watch/index.ts +53 -0
  555. package/src/commands/watch/options.ts +234 -0
  556. package/src/commands/watch/render.ts +240 -0
  557. package/src/commands/watch/run.ts +553 -0
  558. package/src/commands/watch/runtime.ts +167 -0
  559. package/src/commands/watch-command.ts +294 -0
  560. package/src/entry-point.ts +67 -0
  561. package/src/index.ts +291 -0
  562. package/src/mcp/boundaries-schema.ts +193 -0
  563. package/src/mcp/default-runtime.ts +1034 -0
  564. package/src/mcp/envelope.ts +57 -0
  565. package/src/mcp/index.ts +28 -0
  566. package/src/mcp/output-schemas.ts +3071 -0
  567. package/src/mcp/path-sandbox.ts +156 -0
  568. package/src/mcp/server.ts +358 -0
  569. package/src/mcp/stdio.ts +92 -0
  570. package/src/mcp/tools.ts +563 -0
  571. package/src/mcp/types.ts +66 -0
@@ -0,0 +1,3071 @@
1
+ import {
2
+ MINIMUM_PRODUCT_COMPLEXITY_SESSIONS,
3
+ SEARCH_JOURNEY_ABANDON_REASON_CODES,
4
+ SEARCH_JOURNEY_EXECUTION_CONTEXT_DIMENSIONS,
5
+ SEARCH_JOURNEY_TARGET_CODE_COMPLEXITY_UNAVAILABLE_REASONS,
6
+ canonicalJson,
7
+ searchJourneyValidationIssue,
8
+ } from "@untangle-code/domain";
9
+ import * as z from "zod/v4";
10
+
11
+ import { frontendBoundaryReportSchema } from "./boundaries-schema.js";
12
+ import type { UntangleMcpToolName } from "./types.js";
13
+
14
+ const text = z.string();
15
+ const isoTimestamp = text.refine((value) => Number.isFinite(Date.parse(value)), {
16
+ message: "Expected an ISO timestamp",
17
+ });
18
+ // Validate semantic content without transforming wire values. In particular,
19
+ // raw import specifiers may intentionally carry leading/trailing whitespace
20
+ // and must round-trip byte-for-byte through the MCP response boundary.
21
+ const nonEmpty = text.refine((value) => value.trim().length > 0, {
22
+ message: "Expected a non-empty string",
23
+ });
24
+ const finite = z.number().finite();
25
+ const nonNegative = finite.nonnegative();
26
+ const ratio = finite.min(0).max(1);
27
+ const integer = z.number().int();
28
+ const nonNegativeInt = integer.nonnegative();
29
+ const positiveInt = integer.positive();
30
+ const sha256Fingerprint = text.regex(/^[0-9a-f]{64}$/u);
31
+ const stringArray = z.array(text);
32
+ const stringMap = z.record(z.string(), text);
33
+ const optionalStringMap = z.record(z.string(), text.nullable());
34
+ const numberMap = z.record(z.string(), finite);
35
+ const jsonMap = z.record(z.string(), z.json());
36
+
37
+ const sourceRangeSchema = z
38
+ .object({
39
+ startLine: positiveInt,
40
+ endLine: positiveInt,
41
+ startByte: nonNegativeInt,
42
+ endByte: nonNegativeInt,
43
+ })
44
+ .strict();
45
+
46
+ const skipLedgerEntrySchema = z
47
+ .object({
48
+ schemaVersion: z.literal(1),
49
+ stage: z.enum([
50
+ "scan",
51
+ "parse",
52
+ "chunk",
53
+ "embed",
54
+ "store",
55
+ "file_graph",
56
+ "symbol_graph",
57
+ "search",
58
+ "findability",
59
+ "extension",
60
+ ]),
61
+ subjectKind: z.enum([
62
+ "file",
63
+ "range",
64
+ "symbol",
65
+ "chunk",
66
+ "file_edge",
67
+ "symbol_edge",
68
+ "search_candidate",
69
+ "artifact",
70
+ "extension",
71
+ ]),
72
+ subjectId: text.optional(),
73
+ path: text.optional(),
74
+ range: sourceRangeSchema.optional(),
75
+ repoId: text.optional(),
76
+ refId: text.optional(),
77
+ reason: z.enum([
78
+ "unsupported_language",
79
+ "parse_failed",
80
+ "file_too_large",
81
+ "binary_content",
82
+ "excluded_by_configuration",
83
+ "token_budget_exceeded",
84
+ "time_budget_exceeded",
85
+ "resolution_failed",
86
+ "ambiguous_resolution",
87
+ "missing_index_entry",
88
+ "provider_unavailable",
89
+ "extension_unavailable",
90
+ "duplicate",
91
+ "no_metric_defined",
92
+ "generated_content",
93
+ "relevance_policy_rejected",
94
+ ]),
95
+ detail: nonEmpty,
96
+ recoverable: z.boolean(),
97
+ })
98
+ .strict();
99
+
100
+ const indexCountsSchema = z
101
+ .object({
102
+ filesSeen: nonNegativeInt,
103
+ filesPlanned: nonNegativeInt,
104
+ filesUnchanged: nonNegativeInt,
105
+ filesIgnored: nonNegativeInt,
106
+ filesAnalyzed: nonNegativeInt,
107
+ filesRemoved: nonNegativeInt,
108
+ chunks: nonNegativeInt,
109
+ symbols: nonNegativeInt,
110
+ points: nonNegativeInt,
111
+ embeddingCacheHits: nonNegativeInt,
112
+ embeddingsComputed: nonNegativeInt,
113
+ parseCacheHits: nonNegativeInt,
114
+ parseCacheMisses: nonNegativeInt,
115
+ chunkCacheHits: nonNegativeInt,
116
+ chunkCacheMisses: nonNegativeInt,
117
+ metricsCacheHits: nonNegativeInt,
118
+ metricsCacheMisses: nonNegativeInt,
119
+ truncated: nonNegativeInt,
120
+ fileGraphNodes: nonNegativeInt,
121
+ fileGraphEdges: nonNegativeInt,
122
+ symbolGraphNodes: nonNegativeInt,
123
+ symbolGraphEdges: nonNegativeInt,
124
+ })
125
+ .strict();
126
+
127
+ const indexReportSchema = z
128
+ .object({
129
+ schemaVersion: z.literal(1),
130
+ jobId: nonEmpty,
131
+ generationId: text.nullable(),
132
+ workspaceId: nonEmpty,
133
+ repoId: nonEmpty,
134
+ refId: nonEmpty,
135
+ embeddingProfileId: nonEmpty,
136
+ collection: nonEmpty,
137
+ outcome: z.enum(["completed", "failed", "cancelled"]),
138
+ counts: indexCountsSchema,
139
+ failure: z
140
+ .object({ code: nonEmpty, message: nonEmpty, retryable: z.boolean() })
141
+ .strict()
142
+ .optional(),
143
+ skipped: z.array(skipLedgerEntrySchema),
144
+ skippedByReason: z.record(z.string(), nonNegativeInt),
145
+ skippedByStage: z.record(z.string(), nonNegativeInt),
146
+ skippedTruncated: nonNegativeInt,
147
+ })
148
+ .strict();
149
+
150
+ const jobStatusSchema = z
151
+ .object({
152
+ jobId: nonEmpty,
153
+ operation: nonEmpty,
154
+ status: z.enum([
155
+ "queued",
156
+ "preparing_infrastructure",
157
+ "discovering_files",
158
+ "parsing_and_chunking",
159
+ "computing_metrics",
160
+ "embedding",
161
+ "storing",
162
+ "checkpointing",
163
+ "building_graphs",
164
+ "indexing_context",
165
+ "finalizing",
166
+ "completed",
167
+ "failed",
168
+ "cancel_requested",
169
+ "cancelled",
170
+ ]),
171
+ workspaceId: nonEmpty,
172
+ generationId: text.optional(),
173
+ updatedAt: text.optional(),
174
+ })
175
+ .strict();
176
+
177
+ const checkResultSchema = z
178
+ .object({
179
+ id: nonEmpty,
180
+ title: nonEmpty,
181
+ status: z.enum(["ok", "warn", "fail"]),
182
+ detail: nonEmpty,
183
+ nextStep: nonEmpty.optional(),
184
+ facts: z.record(z.string(), z.union([text, finite, z.boolean()])).optional(),
185
+ })
186
+ .strict();
187
+
188
+ const repositoryConfigSchema = z
189
+ .object({
190
+ id: nonEmpty,
191
+ path: nonEmpty,
192
+ label: text.optional(),
193
+ ref: text.optional(),
194
+ include: stringArray.optional(),
195
+ exclude: stringArray.optional(),
196
+ packageAliases: stringMap.optional(),
197
+ metadata: stringMap.optional(),
198
+ })
199
+ .strict();
200
+
201
+ const contextArtifactSchema = z
202
+ .object({
203
+ name: nonEmpty,
204
+ path: nonEmpty,
205
+ description: text,
206
+ type: z
207
+ .enum([
208
+ "auto",
209
+ "sql",
210
+ "openapi",
211
+ "asyncapi",
212
+ "protobuf",
213
+ "markdown",
214
+ "terraform",
215
+ "kubernetes",
216
+ "text",
217
+ ])
218
+ .optional(),
219
+ include: stringArray.optional(),
220
+ exclude: stringArray.optional(),
221
+ })
222
+ .strict();
223
+
224
+ const warningDataSchema = z
225
+ .object({
226
+ code: nonEmpty,
227
+ message: nonEmpty,
228
+ details: jsonMap.optional(),
229
+ })
230
+ .strict();
231
+
232
+ const scoreComponentSchema = z
233
+ .object({
234
+ kind: z.enum([
235
+ "base_hybrid",
236
+ "exact_symbol_boost",
237
+ "exact_path_boost",
238
+ "repo_priority_boost",
239
+ "graph_proximity_boost",
240
+ "recency_boost",
241
+ "duplicate_penalty",
242
+ "complexity_modifier",
243
+ ]),
244
+ rawValue: finite,
245
+ weight: finite,
246
+ contribution: finite,
247
+ evidence: text.optional(),
248
+ })
249
+ .strict();
250
+
251
+ const scoreBreakdownSchema = z
252
+ .object({
253
+ schemaVersion: z.literal(1),
254
+ final: finite,
255
+ components: z.array(scoreComponentSchema),
256
+ inputs: z
257
+ .object({
258
+ dense: finite.optional(),
259
+ sparseRank: positiveInt.optional(),
260
+ rrf: finite.optional(),
261
+ denseRank: positiveInt.optional(),
262
+ lexicalScore: finite.optional(),
263
+ lexicalRank: positiveInt.optional(),
264
+ symbolRank: positiveInt.optional(),
265
+ })
266
+ .strict(),
267
+ rankerVersion: nonEmpty,
268
+ clamped: z.boolean(),
269
+ clampReason: text.optional(),
270
+ })
271
+ .strict();
272
+
273
+ const complexitySummarySchema = z
274
+ .object({
275
+ metricsScope: z.enum(["symbol", "file", "chunk"]),
276
+ metricsScopeId: nonEmpty,
277
+ metricsVersion: nonEmpty,
278
+ cyclomaticClassic: nonNegativeInt.optional(),
279
+ cyclomaticExtended: nonNegativeInt.optional(),
280
+ cognitive: nonNegativeInt.optional(),
281
+ maxNesting: nonNegativeInt.optional(),
282
+ sloc: nonNegativeInt.optional(),
283
+ halsteadVolume: nonNegative.optional(),
284
+ maintainabilityIndex: finite.min(0).max(100).optional(),
285
+ riskScore: nonNegative.optional(),
286
+ })
287
+ .strict()
288
+ .refine(
289
+ (summary) =>
290
+ summary.cyclomaticClassic !== undefined ||
291
+ summary.cyclomaticExtended !== undefined ||
292
+ summary.cognitive !== undefined ||
293
+ summary.maxNesting !== undefined ||
294
+ summary.sloc !== undefined ||
295
+ summary.halsteadVolume !== undefined ||
296
+ summary.maintainabilityIndex !== undefined ||
297
+ summary.riskScore !== undefined,
298
+ { message: "complexity summary requires at least one metric" },
299
+ );
300
+
301
+ const graphRelationSchema = z
302
+ .object({
303
+ schemaVersion: z.literal(1),
304
+ source: z.enum(["file_graph", "symbol_graph"]),
305
+ edgeId: nonEmpty,
306
+ direction: z.enum(["incoming", "outgoing"]),
307
+ edgeKind: nonEmpty,
308
+ repoId: nonEmpty,
309
+ refId: nonEmpty,
310
+ fileId: nonEmpty,
311
+ path: text,
312
+ crossRepository: z.boolean(),
313
+ symbol: z
314
+ .object({ id: nonEmpty, name: text, qualifiedName: text, kind: text })
315
+ .strict()
316
+ .optional(),
317
+ })
318
+ .strict();
319
+
320
+ const graphRelationsSchema = z
321
+ .object({
322
+ schemaVersion: z.literal(1),
323
+ relations: z.array(graphRelationSchema),
324
+ relationLimit: nonNegativeInt,
325
+ truncated: z.boolean(),
326
+ omittedCount: nonNegativeInt,
327
+ omissions: z.array(
328
+ z
329
+ .object({
330
+ reason: z.enum([
331
+ "graph_unavailable",
332
+ "graph_partial",
333
+ "source_not_in_graph",
334
+ "symbol_id_missing",
335
+ "unresolved_endpoint",
336
+ "heuristic_endpoint",
337
+ "ambiguous_endpoint",
338
+ "endpoint_missing",
339
+ "provider_result_missing",
340
+ "relation_limit",
341
+ ]),
342
+ count: nonNegativeInt,
343
+ detail: nonEmpty,
344
+ })
345
+ .strict(),
346
+ ),
347
+ })
348
+ .strict();
349
+
350
+ const relatedFragmentSchema = z
351
+ .object({
352
+ chunkId: nonEmpty,
353
+ chunkKind: nonEmpty,
354
+ fragmentIndex: nonNegativeInt.optional(),
355
+ startLine: positiveInt,
356
+ endLine: positiveInt,
357
+ score: finite,
358
+ })
359
+ .strict();
360
+
361
+ const findabilitySummarySchema = z
362
+ .object({
363
+ score: ratio,
364
+ isHeuristic: z.literal(true),
365
+ algorithm: nonEmpty,
366
+ primaryCause: z
367
+ .enum(["scattered-responsibility", "weak-naming", "high-entanglement", "undocumented"])
368
+ .optional(),
369
+ primaryCauseConfidence: ratio.optional(),
370
+ })
371
+ .strict();
372
+
373
+ const searchHitSchema = z
374
+ .object({
375
+ schemaVersion: z.literal(1),
376
+ rank: positiveInt,
377
+ repoId: nonEmpty,
378
+ repoLabel: text,
379
+ refId: nonEmpty,
380
+ refName: text,
381
+ commitSha: text.optional(),
382
+ fileId: nonEmpty,
383
+ filePath: text,
384
+ range: sourceRangeSchema,
385
+ language: nonEmpty,
386
+ parseQuality: z.enum(["complete", "partial", "failed"]),
387
+ chunkId: nonEmpty,
388
+ chunkKind: nonEmpty,
389
+ fragmentIndex: nonNegativeInt.optional(),
390
+ fragmentCount: positiveInt.optional(),
391
+ symbolId: text.optional(),
392
+ symbolName: text.optional(),
393
+ symbolFqn: text.optional(),
394
+ symbolKind: text.optional(),
395
+ signature: text.optional(),
396
+ score: scoreBreakdownSchema,
397
+ dedupeKey: nonEmpty,
398
+ relatedFragments: z.array(relatedFragmentSchema),
399
+ embeddingProfileId: nonEmpty,
400
+ indexGeneration: nonEmpty,
401
+ complexity: complexitySummarySchema.optional(),
402
+ findability: findabilitySummarySchema.optional(),
403
+ content: text.optional(),
404
+ contentOmittedReason: text.optional(),
405
+ graphContext: graphRelationsSchema.optional(),
406
+ linkedSymbols: graphRelationsSchema.optional(),
407
+ incompletePayloadFields: stringArray.optional(),
408
+ })
409
+ .strict();
410
+
411
+ const searchQuerySchema = z
412
+ .object({
413
+ schemaVersion: z.literal(1),
414
+ text: nonEmpty,
415
+ workspaceId: nonEmpty,
416
+ filters: z
417
+ .object({
418
+ repoIds: stringArray,
419
+ refIds: stringArray,
420
+ commitSha: text.optional(),
421
+ languages: stringArray,
422
+ pathGlobs: stringArray,
423
+ symbolNames: stringArray,
424
+ symbolKinds: stringArray,
425
+ chunkKinds: stringArray,
426
+ complexity: z
427
+ .object({
428
+ cyclomaticMin: finite.optional(),
429
+ cyclomaticMax: finite.optional(),
430
+ cognitiveMin: finite.optional(),
431
+ cognitiveMax: finite.optional(),
432
+ riskMin: finite.optional(),
433
+ riskMax: finite.optional(),
434
+ })
435
+ .strict()
436
+ .optional(),
437
+ findability: z
438
+ .object({
439
+ maxScore: finite.optional(),
440
+ minScore: finite.optional(),
441
+ causes: z
442
+ .array(
443
+ z.enum([
444
+ "scattered-responsibility",
445
+ "weak-naming",
446
+ "high-entanglement",
447
+ "undocumented",
448
+ ]),
449
+ )
450
+ .optional(),
451
+ })
452
+ .strict()
453
+ .optional(),
454
+ changed: z.object({ since: nonEmpty, until: nonEmpty.optional() }).strict().optional(),
455
+ })
456
+ .strict(),
457
+ limit: positiveInt,
458
+ candidateLimit: positiveInt,
459
+ minScore: finite.optional(),
460
+ diversify: z.boolean(),
461
+ includeGraphContext: z.boolean(),
462
+ includeLinked: z.boolean(),
463
+ includeContent: z.boolean(),
464
+ rankerVersion: nonEmpty,
465
+ })
466
+ .strict();
467
+
468
+ const searchResultSetSchema = z
469
+ .object({
470
+ schemaVersion: z.literal(1),
471
+ queryFingerprint: nonEmpty,
472
+ indexFreshness: z
473
+ .object({
474
+ status: z.enum(["fresh", "stale", "unknown", "incompatible"]),
475
+ checkedAt: isoTimestamp,
476
+ repositories: z.array(
477
+ z
478
+ .object({
479
+ repoId: nonEmpty,
480
+ refId: nonEmpty,
481
+ status: z.enum(["fresh", "stale", "unknown", "incompatible"]),
482
+ reason: nonEmpty,
483
+ filesChecked: nonNegativeInt,
484
+ indexedAt: isoTimestamp.optional(),
485
+ indexedCorpusFingerprint: sha256Fingerprint.optional(),
486
+ currentCorpusFingerprint: sha256Fingerprint.optional(),
487
+ })
488
+ .strict(),
489
+ ),
490
+ })
491
+ .strict()
492
+ .optional(),
493
+ rankingContextFingerprint: nonEmpty.optional(),
494
+ query: searchQuerySchema,
495
+ groups: z.array(
496
+ z
497
+ .object({
498
+ embeddingProfileId: nonEmpty,
499
+ indexGenerations: z.array(nonEmpty).optional(),
500
+ corpusRevisionFingerprint: sha256Fingerprint.optional(),
501
+ repoIds: stringArray,
502
+ hits: z.array(searchHitSchema),
503
+ rankNormalized: z.boolean(),
504
+ caveats: stringArray,
505
+ notSearchedReason: text.optional(),
506
+ relevanceDecision: z
507
+ .object({
508
+ abstention: z.enum(["strict", "evidence"]),
509
+ status: z.enum(["accepted", "rejected", "not_evaluated"]),
510
+ basis: nonEmpty,
511
+ calibrationId: nonEmpty.optional(),
512
+ })
513
+ .strict()
514
+ .optional(),
515
+ retrieval: z
516
+ .object({
517
+ branchCandidateCounts: z
518
+ .object({
519
+ dense: nonNegativeInt,
520
+ sparse: nonNegativeInt,
521
+ symbol: nonNegativeInt,
522
+ })
523
+ .strict(),
524
+ uniqueRetrievedCandidateCount: nonNegativeInt,
525
+ eligibleCandidateCount: nonNegativeInt,
526
+ rankedCandidateCount: nonNegativeInt,
527
+ deduplicatedCandidateCount: nonNegativeInt,
528
+ qualifiedHitCount: nonNegativeInt,
529
+ returnedHitCount: nonNegativeInt,
530
+ uniqueEligibleFileCount: nonNegativeInt,
531
+ uniqueEligibleSymbolCount: nonNegativeInt,
532
+ eligibleFileDispersion: ratio,
533
+ topScoreGap: finite.nullable(),
534
+ candidateLimitReached: z.boolean(),
535
+ })
536
+ .strict()
537
+ .optional(),
538
+ })
539
+ .strict(),
540
+ ),
541
+ profileSeparation: z.boolean(),
542
+ totalHits: nonNegativeInt,
543
+ skipped: z.array(skipLedgerEntrySchema),
544
+ warnings: z.array(warningDataSchema),
545
+ timings: z
546
+ .object({
547
+ queryEmbeddingMs: nonNegative.optional(),
548
+ vectorSearchMs: nonNegative.optional(),
549
+ totalMs: nonNegative.optional(),
550
+ })
551
+ .strict()
552
+ .optional(),
553
+ searchObservation: z
554
+ .discriminatedUnion("status", [
555
+ z
556
+ .object({
557
+ sessionId: nonEmpty,
558
+ attemptId: nonEmpty,
559
+ status: z.literal("recorded"),
560
+ })
561
+ .strict(),
562
+ z
563
+ .object({
564
+ sessionId: nonEmpty,
565
+ status: z.literal("not_recorded"),
566
+ warningCode: nonEmpty,
567
+ })
568
+ .strict(),
569
+ ])
570
+ .optional(),
571
+ })
572
+ .strict();
573
+
574
+ const searchJourneyTargetSchema = z
575
+ .object({
576
+ repoId: nonEmpty.optional(),
577
+ refId: nonEmpty.optional(),
578
+ filePath: nonEmpty.optional(),
579
+ chunkId: nonEmpty.optional(),
580
+ symbolId: nonEmpty.optional(),
581
+ embeddingProfileId: nonEmpty.optional(),
582
+ indexGeneration: nonEmpty.optional(),
583
+ })
584
+ .strict()
585
+ .refine(
586
+ (target) =>
587
+ target.filePath !== undefined ||
588
+ target.chunkId !== undefined ||
589
+ target.symbolId !== undefined,
590
+ {
591
+ message: "search journey target requires filePath, chunkId or symbolId",
592
+ },
593
+ );
594
+
595
+ const searchJourneyCandidateSchema = z
596
+ .object({
597
+ repoId: nonEmpty,
598
+ refId: nonEmpty,
599
+ filePath: text,
600
+ chunkId: nonEmpty,
601
+ symbolId: nonEmpty.optional(),
602
+ symbolFqn: text.optional(),
603
+ embeddingProfileId: nonEmpty,
604
+ indexGeneration: nonEmpty,
605
+ rank: positiveInt,
606
+ score: finite,
607
+ complexity: complexitySummarySchema.optional(),
608
+ incompletePayloadFields: stringArray.optional(),
609
+ })
610
+ .strict();
611
+
612
+ const searchJourneyTargetCodeCandidateSchema = z
613
+ .object({
614
+ repoId: nonEmpty,
615
+ refId: nonEmpty,
616
+ filePath: text,
617
+ chunkId: nonEmpty,
618
+ symbolId: nonEmpty.optional(),
619
+ symbolFqn: text.optional(),
620
+ embeddingProfileId: nonEmpty,
621
+ indexGeneration: nonEmpty,
622
+ })
623
+ .strict();
624
+
625
+ const searchJourneyTargetCodeComplexitySchema = z.discriminatedUnion("status", [
626
+ z
627
+ .object({
628
+ status: z.literal("observed"),
629
+ source: z.literal("indexed-search-hit"),
630
+ candidate: searchJourneyTargetCodeCandidateSchema,
631
+ complexity: complexitySummarySchema,
632
+ payloadComplete: z.boolean(),
633
+ incompletePayloadFields: stringArray,
634
+ })
635
+ .strict()
636
+ .superRefine((evidence, context) => {
637
+ if (evidence.payloadComplete !== (evidence.incompletePayloadFields.length === 0)) {
638
+ context.addIssue({
639
+ code: "custom",
640
+ path: ["payloadComplete"],
641
+ message: "payload completeness must match incompletePayloadFields",
642
+ });
643
+ }
644
+ if (
645
+ new Set(evidence.incompletePayloadFields).size !== evidence.incompletePayloadFields.length
646
+ ) {
647
+ context.addIssue({
648
+ code: "custom",
649
+ path: ["incompletePayloadFields"],
650
+ message: "incomplete payload fields must be unique",
651
+ });
652
+ }
653
+ }),
654
+ z
655
+ .object({
656
+ status: z.literal("unavailable"),
657
+ reason: z.enum(SEARCH_JOURNEY_TARGET_CODE_COMPLEXITY_UNAVAILABLE_REASONS),
658
+ })
659
+ .strict(),
660
+ ]);
661
+
662
+ const searchRetrievalDiagnosticsSchema = z
663
+ .object({
664
+ branchCandidateCounts: z
665
+ .object({ dense: nonNegativeInt, sparse: nonNegativeInt, symbol: nonNegativeInt })
666
+ .strict(),
667
+ uniqueRetrievedCandidateCount: nonNegativeInt,
668
+ eligibleCandidateCount: nonNegativeInt,
669
+ rankedCandidateCount: nonNegativeInt,
670
+ deduplicatedCandidateCount: nonNegativeInt,
671
+ qualifiedHitCount: nonNegativeInt,
672
+ returnedHitCount: nonNegativeInt,
673
+ uniqueEligibleFileCount: nonNegativeInt,
674
+ uniqueEligibleSymbolCount: nonNegativeInt,
675
+ eligibleFileDispersion: ratio,
676
+ topScoreGap: finite.nullable(),
677
+ candidateLimitReached: z.boolean(),
678
+ })
679
+ .strict();
680
+
681
+ const searchJourneyAttemptSchema = z
682
+ .object({
683
+ attemptId: nonEmpty,
684
+ sequence: positiveInt,
685
+ clientStep: positiveInt.optional(),
686
+ observationFingerprint: nonEmpty,
687
+ measurementContextVersion: z.literal(2).optional(),
688
+ abstention: z.enum(["strict", "evidence"]).optional(),
689
+ queryFingerprint: nonEmpty,
690
+ queryText: text.optional(),
691
+ rankerVersion: nonEmpty,
692
+ rankingContextFingerprint: nonEmpty.optional(),
693
+ resultLimit: positiveInt.optional(),
694
+ candidateLimit: positiveInt.optional(),
695
+ observedAt: nonEmpty,
696
+ status: z.enum(["succeeded", "failed"]),
697
+ failureCode: nonEmpty.optional(),
698
+ failureReason: text.optional(),
699
+ warningCodes: stringArray,
700
+ retrievalHealthy: z.boolean(),
701
+ profiles: z.array(
702
+ z
703
+ .object({
704
+ embeddingProfileId: nonEmpty,
705
+ indexGenerations: stringArray,
706
+ corpusRevisionFingerprint: sha256Fingerprint.optional(),
707
+ retrievalPerformed: z.boolean(),
708
+ notSearchedReason: text.optional(),
709
+ retrieval: searchRetrievalDiagnosticsSchema.optional(),
710
+ candidates: z.array(searchJourneyCandidateSchema),
711
+ })
712
+ .strict(),
713
+ ),
714
+ })
715
+ .strict();
716
+
717
+ const dynamicSearchComplexitySchema = z
718
+ .object({
719
+ schemaVersion: z.literal(1),
720
+ algorithm: z.literal("untangle-dynamic-search-complexity-v1"),
721
+ isHeuristic: z.literal(true),
722
+ searchesToTarget: positiveInt,
723
+ successfulSearchesToTarget: nonNegativeInt,
724
+ failedSearchesToTarget: nonNegativeInt,
725
+ distinctQueryCount: positiveInt,
726
+ queryReformulations: nonNegativeInt,
727
+ acceptedTargetRank: positiveInt.nullable(),
728
+ reciprocalRank: ratio.nullable(),
729
+ targetWasReturned: z.boolean(),
730
+ targetFirstReturnedAtSearch: positiveInt.nullable(),
731
+ targetAcceptedAtSearch: positiveInt.nullable(),
732
+ selectionLagSearches: nonNegativeInt.nullable(),
733
+ directHit: z.boolean(),
734
+ eligibleCandidatesExamined: nonNegativeInt,
735
+ uniqueCandidatesExposed: nonNegativeInt,
736
+ uniqueFilesExposed: nonNegativeInt,
737
+ navigationEventsToTarget: nonNegativeInt,
738
+ observedToolAndFileHops: positiveInt,
739
+ meanCandidateFileDispersion: ratio.nullable(),
740
+ meanCandidateChurn: ratio.nullable(),
741
+ candidateLimitHitRate: ratio,
742
+ healthyRetrievalRate: ratio,
743
+ acceptedTargetScoreMargin: finite.nullable(),
744
+ acceptedTargetCodeComplexity: searchJourneyTargetCodeComplexitySchema.optional(),
745
+ measurementContextId: nonEmpty,
746
+ measurementContextComplete: z.boolean(),
747
+ caveats: stringArray,
748
+ })
749
+ .strict();
750
+
751
+ const journeyExecutionValueSchema = z
752
+ .object({ fingerprint: sha256Fingerprint, text: text.optional() })
753
+ .strict();
754
+ const journeyExecutionContextSchema = z
755
+ .object({
756
+ schemaVersion: z.literal(1),
757
+ source: z.literal("caller-declared"),
758
+ agentModel: journeyExecutionValueSchema,
759
+ harness: journeyExecutionValueSchema,
760
+ searchStrategy: journeyExecutionValueSchema,
761
+ taskClass: journeyExecutionValueSchema,
762
+ taskMix: journeyExecutionValueSchema.optional(),
763
+ priorContext: journeyExecutionValueSchema.optional(),
764
+ })
765
+ .strict();
766
+ const journeyExecutionEvidenceSchema = z
767
+ .object({
768
+ status: z.enum(["caller-declared", "not-recorded"]),
769
+ fingerprint: sha256Fingerprint.optional(),
770
+ uncontrolledDimensions: z.array(z.enum(SEARCH_JOURNEY_EXECUTION_CONTEXT_DIMENSIONS)),
771
+ })
772
+ .strict()
773
+ .superRefine((evidence, context) => {
774
+ if ((evidence.status === "caller-declared") !== (evidence.fingerprint !== undefined)) {
775
+ context.addIssue({
776
+ code: "custom",
777
+ message: "caller-declared conditions require a fingerprint",
778
+ });
779
+ }
780
+ if (new Set(evidence.uncontrolledDimensions).size !== evidence.uncontrolledDimensions.length) {
781
+ context.addIssue({ code: "custom", message: "uncontrolled dimensions must be unique" });
782
+ }
783
+ const allowedMissing =
784
+ evidence.status === "not-recorded"
785
+ ? SEARCH_JOURNEY_EXECUTION_CONTEXT_DIMENSIONS
786
+ : ["taskMix", "priorContext"];
787
+ if (
788
+ evidence.uncontrolledDimensions.some((dimension) => !allowedMissing.includes(dimension)) ||
789
+ (evidence.status === "not-recorded" &&
790
+ evidence.uncontrolledDimensions.length !==
791
+ SEARCH_JOURNEY_EXECUTION_CONTEXT_DIMENSIONS.length)
792
+ ) {
793
+ context.addIssue({
794
+ code: "custom",
795
+ message: "missing execution dimensions must agree with declaration status",
796
+ });
797
+ }
798
+ });
799
+
800
+ const searchJourneySchema = z
801
+ .object({
802
+ schemaVersion: z.literal(1),
803
+ revision: nonNegativeInt,
804
+ sessionId: nonEmpty,
805
+ workspaceId: nonEmpty,
806
+ status: z.enum(["active", "completed", "abandoned"]),
807
+ recordingConsent: z.literal("explicit"),
808
+ retainText: z.boolean(),
809
+ goal: z.object({ fingerprint: nonEmpty, text: text.optional() }).strict(),
810
+ feature: z
811
+ .object({ fingerprint: sha256Fingerprint, text: text.optional() })
812
+ .strict()
813
+ .optional(),
814
+ executionContext: journeyExecutionContextSchema.optional(),
815
+ createdAt: nonEmpty,
816
+ updatedAt: nonEmpty,
817
+ attempts: z.array(searchJourneyAttemptSchema),
818
+ navigation: z.array(
819
+ z
820
+ .object({
821
+ eventId: nonEmpty,
822
+ sequence: positiveInt,
823
+ kind: z.enum(["open-file", "open-symbol", "graph-query", "other-tool"]),
824
+ observedAt: nonEmpty,
825
+ toolName: nonEmpty.optional(),
826
+ target: searchJourneyTargetSchema.optional(),
827
+ })
828
+ .strict(),
829
+ ),
830
+ outcome: z
831
+ .discriminatedUnion("kind", [
832
+ z
833
+ .object({
834
+ kind: z.literal("completed"),
835
+ completedAt: nonEmpty,
836
+ target: searchJourneyTargetSchema,
837
+ acceptedAttemptId: nonEmpty.optional(),
838
+ assessment: dynamicSearchComplexitySchema,
839
+ })
840
+ .strict(),
841
+ z
842
+ .object({
843
+ kind: z.literal("abandoned"),
844
+ abandonedAt: nonEmpty,
845
+ reasonCode: z.enum(SEARCH_JOURNEY_ABANDON_REASON_CODES).optional(),
846
+ reason: text.optional(),
847
+ })
848
+ .strict(),
849
+ ])
850
+ .optional(),
851
+ })
852
+ .strict()
853
+ .superRefine((session, context) => {
854
+ const validationIssue = searchJourneyValidationIssue(session);
855
+ if (validationIssue !== null) {
856
+ context.addIssue({
857
+ code: "custom",
858
+ message: `invalid search journey: ${validationIssue}`,
859
+ });
860
+ }
861
+ const terminalKind = session.outcome?.kind;
862
+ if (
863
+ (session.status === "active" && terminalKind !== undefined) ||
864
+ (session.status === "completed" && terminalKind !== "completed") ||
865
+ (session.status === "abandoned" && terminalKind !== "abandoned")
866
+ ) {
867
+ context.addIssue({
868
+ code: "custom",
869
+ message: "search journey status and outcome are inconsistent",
870
+ });
871
+ }
872
+ if (
873
+ !session.retainText &&
874
+ (session.goal.text !== undefined ||
875
+ session.attempts.some(
876
+ (attempt) => attempt.queryText !== undefined || attempt.failureReason !== undefined,
877
+ ))
878
+ ) {
879
+ context.addIssue({
880
+ code: "custom",
881
+ message: "redacted search journey contains retained text",
882
+ });
883
+ }
884
+ if (session.attempts.some((attempt, index) => attempt.sequence !== index + 1)) {
885
+ context.addIssue({ code: "custom", message: "search journey attempts are not ordered" });
886
+ }
887
+ const numbered = session.attempts.filter((attempt) => attempt.clientStep !== undefined);
888
+ if (numbered.length !== 0 && numbered.length !== session.attempts.length) {
889
+ context.addIssue({
890
+ code: "custom",
891
+ message: "search journey mixes numbered and unnumbered attempts",
892
+ });
893
+ }
894
+ });
895
+
896
+ const dynamicProductComplexityCohortSchema = z
897
+ .object({
898
+ measurementContextId: sha256Fingerprint,
899
+ completedSessionCount: positiveInt,
900
+ measurementContextComplete: z.boolean(),
901
+ sufficientEvidence: z.boolean(),
902
+ minimumCompletedSessions: z.literal(MINIMUM_PRODUCT_COMPLEXITY_SESSIONS),
903
+ executionContext: journeyExecutionEvidenceSchema.optional(),
904
+ callerConditionsControlled: z.boolean().optional(),
905
+ medianSearchesToTarget: finite.positive(),
906
+ p90SearchesToTarget: finite.positive(),
907
+ medianQueryReformulations: nonNegative,
908
+ medianAcceptedTargetRank: finite.positive().nullable(),
909
+ meanReciprocalRank: ratio.nullable(),
910
+ targetReturnedRate: ratio,
911
+ directHitRate: ratio,
912
+ healthyRetrievalRate: ratio,
913
+ medianEligibleCandidatesExamined: nonNegative,
914
+ medianUniqueFilesExposed: nonNegative,
915
+ meanCandidateFileDispersion: ratio.nullable(),
916
+ meanCandidateChurn: ratio.nullable(),
917
+ candidateLimitHitRate: ratio,
918
+ caveats: stringArray,
919
+ })
920
+ .strict()
921
+ .superRefine((cohort, context) => {
922
+ if (
923
+ cohort.callerConditionsControlled !== undefined &&
924
+ cohort.callerConditionsControlled !==
925
+ (cohort.executionContext?.status === "caller-declared" &&
926
+ cohort.executionContext.uncontrolledDimensions.length === 0)
927
+ ) {
928
+ context.addIssue({
929
+ code: "custom",
930
+ message: "caller conditions require all declared dimensions",
931
+ });
932
+ }
933
+ const expectedSufficient =
934
+ cohort.measurementContextComplete &&
935
+ cohort.completedSessionCount >= cohort.minimumCompletedSessions;
936
+ if (cohort.sufficientEvidence !== expectedSufficient) {
937
+ context.addIssue({
938
+ code: "custom",
939
+ path: ["sufficientEvidence"],
940
+ message: "sufficient evidence requires a complete context and the minimum cohort size",
941
+ });
942
+ }
943
+ if (cohort.p90SearchesToTarget < cohort.medianSearchesToTarget) {
944
+ context.addIssue({
945
+ code: "custom",
946
+ path: ["p90SearchesToTarget"],
947
+ message: "p90 searches must not be below the median",
948
+ });
949
+ }
950
+ if (cohort.measurementContextComplete && cohort.healthyRetrievalRate !== 1) {
951
+ context.addIssue({
952
+ code: "custom",
953
+ path: ["healthyRetrievalRate"],
954
+ message: "a complete measurement context requires fully healthy retrieval",
955
+ });
956
+ }
957
+ if (cohort.directHitRate > cohort.targetReturnedRate) {
958
+ context.addIssue({
959
+ code: "custom",
960
+ path: ["directHitRate"],
961
+ message: "the direct-hit rate cannot exceed the target-returned rate",
962
+ });
963
+ }
964
+ });
965
+
966
+ const featureCodeComplexityVariantSchema = z
967
+ .object({
968
+ evidence: searchJourneyTargetCodeComplexitySchema,
969
+ completedSessionCount: positiveInt,
970
+ })
971
+ .strict();
972
+
973
+ const featureCodeTargetMappingSchema = z
974
+ .object({
975
+ target: searchJourneyTargetSchema,
976
+ completedSessionCount: positiveInt,
977
+ complexityVariants: z.array(featureCodeComplexityVariantSchema).min(1),
978
+ })
979
+ .strict()
980
+ .superRefine((mapping, context) => {
981
+ const variantKeys = mapping.complexityVariants.map((variant) =>
982
+ canonicalJson(variant.evidence),
983
+ );
984
+ if (new Set(variantKeys).size !== variantKeys.length) {
985
+ context.addIssue({
986
+ code: "custom",
987
+ path: ["complexityVariants"],
988
+ message: "complexity evidence variants must be unique",
989
+ });
990
+ }
991
+ for (const [index, variant] of mapping.complexityVariants.entries()) {
992
+ if (variant.evidence.status !== "observed") continue;
993
+ const candidate = variant.evidence.candidate;
994
+ const target = mapping.target;
995
+ const matches =
996
+ (target.repoId === undefined || target.repoId === candidate.repoId) &&
997
+ (target.refId === undefined || target.refId === candidate.refId) &&
998
+ (target.filePath === undefined || target.filePath === candidate.filePath) &&
999
+ (target.chunkId === undefined || target.chunkId === candidate.chunkId) &&
1000
+ (target.symbolId === undefined || target.symbolId === candidate.symbolId) &&
1001
+ (target.embeddingProfileId === undefined ||
1002
+ target.embeddingProfileId === candidate.embeddingProfileId) &&
1003
+ (target.indexGeneration === undefined ||
1004
+ target.indexGeneration === candidate.indexGeneration);
1005
+ if (!matches) {
1006
+ context.addIssue({
1007
+ code: "custom",
1008
+ path: ["complexityVariants", index, "evidence", "candidate"],
1009
+ message: "observed complexity candidate must match the confirmed target locator",
1010
+ });
1011
+ }
1012
+ }
1013
+ const variantSessions = mapping.complexityVariants.reduce(
1014
+ (sum, variant) => sum + variant.completedSessionCount,
1015
+ 0,
1016
+ );
1017
+ if (variantSessions !== mapping.completedSessionCount) {
1018
+ context.addIssue({
1019
+ code: "custom",
1020
+ path: ["complexityVariants"],
1021
+ message: "complexity variants must account for every completed target session",
1022
+ });
1023
+ }
1024
+ });
1025
+
1026
+ const dynamicProductComplexityFeatureMappingSchema = z
1027
+ .object({
1028
+ featureFingerprint: sha256Fingerprint,
1029
+ measurementContextId: sha256Fingerprint,
1030
+ distinctGoalCount: positiveInt,
1031
+ search: dynamicProductComplexityCohortSchema,
1032
+ code: z
1033
+ .object({
1034
+ confirmedTargetCount: positiveInt,
1035
+ observedComplexitySessionCount: nonNegativeInt,
1036
+ unavailableComplexitySessionCount: nonNegativeInt,
1037
+ targets: z.array(featureCodeTargetMappingSchema).min(1),
1038
+ })
1039
+ .strict(),
1040
+ caveats: stringArray,
1041
+ })
1042
+ .strict()
1043
+ .superRefine((mapping, context) => {
1044
+ if (mapping.search.measurementContextId !== mapping.measurementContextId) {
1045
+ context.addIssue({
1046
+ code: "custom",
1047
+ path: ["search", "measurementContextId"],
1048
+ message: "feature mapping and search cohort must share a measurement context",
1049
+ });
1050
+ }
1051
+ if (mapping.distinctGoalCount > mapping.search.completedSessionCount) {
1052
+ context.addIssue({
1053
+ code: "custom",
1054
+ path: ["distinctGoalCount"],
1055
+ message: "distinct goals cannot exceed completed feature sessions",
1056
+ });
1057
+ }
1058
+ if (mapping.code.confirmedTargetCount !== mapping.code.targets.length) {
1059
+ context.addIssue({
1060
+ code: "custom",
1061
+ path: ["code", "confirmedTargetCount"],
1062
+ message: "confirmed target count must equal the target mapping count",
1063
+ });
1064
+ }
1065
+ const targetKeys = mapping.code.targets.map((target) => canonicalJson(target.target));
1066
+ if (new Set(targetKeys).size !== targetKeys.length) {
1067
+ context.addIssue({
1068
+ code: "custom",
1069
+ path: ["code", "targets"],
1070
+ message: "confirmed feature targets must be unique",
1071
+ });
1072
+ }
1073
+ const targetSessions = mapping.code.targets.reduce(
1074
+ (sum, target) => sum + target.completedSessionCount,
1075
+ 0,
1076
+ );
1077
+ if (targetSessions !== mapping.search.completedSessionCount) {
1078
+ context.addIssue({
1079
+ code: "custom",
1080
+ path: ["code", "targets"],
1081
+ message: "target mappings must account for every completed feature session",
1082
+ });
1083
+ }
1084
+ const observedSessions = mapping.code.targets.reduce(
1085
+ (sum, target) =>
1086
+ sum +
1087
+ target.complexityVariants.reduce(
1088
+ (targetSum, variant) =>
1089
+ targetSum +
1090
+ (variant.evidence.status === "observed" ? variant.completedSessionCount : 0),
1091
+ 0,
1092
+ ),
1093
+ 0,
1094
+ );
1095
+ if (
1096
+ observedSessions !== mapping.code.observedComplexitySessionCount ||
1097
+ targetSessions - observedSessions !== mapping.code.unavailableComplexitySessionCount
1098
+ ) {
1099
+ context.addIssue({
1100
+ code: "custom",
1101
+ path: ["code"],
1102
+ message: "observed and unavailable complexity counts must partition feature sessions",
1103
+ });
1104
+ }
1105
+ });
1106
+
1107
+ const journeyOutcomeCohortSchema = z
1108
+ .object({
1109
+ measurementContextId: sha256Fingerprint,
1110
+ retrievalContextId: sha256Fingerprint,
1111
+ executionContext: journeyExecutionEvidenceSchema,
1112
+ featureFingerprint: sha256Fingerprint.optional(),
1113
+ distinctGoalCount: positiveInt,
1114
+ sessionCounts: z
1115
+ .object({
1116
+ total: positiveInt,
1117
+ active: nonNegativeInt,
1118
+ completed: nonNegativeInt,
1119
+ abandoned: nonNegativeInt,
1120
+ terminal: nonNegativeInt,
1121
+ })
1122
+ .strict(),
1123
+ terminalCompletionRate: ratio.nullable(),
1124
+ retrievalContextComplete: z.boolean(),
1125
+ statusEffort: z.array(
1126
+ z
1127
+ .object({
1128
+ status: z.enum(["active", "completed", "abandoned"]),
1129
+ sessionCount: nonNegativeInt,
1130
+ sessionsWithSearches: nonNegativeInt,
1131
+ totalSearches: nonNegativeInt,
1132
+ failedSearches: nonNegativeInt,
1133
+ totalNavigationEvents: nonNegativeInt,
1134
+ totalObservedHops: nonNegativeInt,
1135
+ medianSearches: nonNegative.nullable(),
1136
+ })
1137
+ .strict(),
1138
+ ),
1139
+ abandonmentReasons: z.array(
1140
+ z
1141
+ .object({
1142
+ reasonCode: z.enum([...SEARCH_JOURNEY_ABANDON_REASON_CODES, "not-recorded"]),
1143
+ count: positiveInt,
1144
+ })
1145
+ .strict(),
1146
+ ),
1147
+ caveats: stringArray,
1148
+ })
1149
+ .strict()
1150
+ .superRefine((cohort, context) => {
1151
+ const counts = cohort.sessionCounts;
1152
+ if (
1153
+ counts.total !== counts.active + counts.completed + counts.abandoned ||
1154
+ counts.terminal !== counts.completed + counts.abandoned ||
1155
+ cohort.distinctGoalCount > counts.total
1156
+ ) {
1157
+ context.addIssue({
1158
+ code: "custom",
1159
+ message: "outcome session counts must partition their cohort",
1160
+ });
1161
+ }
1162
+ const expectedRate = counts.terminal === 0 ? null : counts.completed / counts.terminal;
1163
+ if (cohort.terminalCompletionRate !== expectedRate) {
1164
+ context.addIssue({
1165
+ code: "custom",
1166
+ message: "completion rate must use completed plus abandoned sessions",
1167
+ });
1168
+ }
1169
+ if (
1170
+ cohort.statusEffort.length !== 3 ||
1171
+ new Set(cohort.statusEffort.map((effort) => effort.status)).size !== 3
1172
+ ) {
1173
+ context.addIssue({
1174
+ code: "custom",
1175
+ message: "outcome effort must include each session status once",
1176
+ });
1177
+ }
1178
+ for (const effort of cohort.statusEffort) {
1179
+ if (
1180
+ effort.sessionCount !== counts[effort.status] ||
1181
+ effort.sessionsWithSearches > effort.sessionCount ||
1182
+ effort.sessionsWithSearches > effort.totalSearches ||
1183
+ effort.failedSearches > effort.totalSearches ||
1184
+ effort.totalObservedHops !== effort.totalSearches + effort.totalNavigationEvents ||
1185
+ (effort.sessionCount === 0) !== (effort.medianSearches === null) ||
1186
+ (effort.sessionCount === 0 && effort.totalObservedHops !== 0) ||
1187
+ (effort.sessionsWithSearches === 0) !== (effort.totalSearches === 0) ||
1188
+ (effort.medianSearches !== null && effort.medianSearches > effort.totalSearches)
1189
+ ) {
1190
+ context.addIssue({
1191
+ code: "custom",
1192
+ message: "outcome effort must account for its status sessions and observations",
1193
+ });
1194
+ }
1195
+ }
1196
+ if (
1197
+ cohort.abandonmentReasons.reduce((sum, reason) => sum + reason.count, 0) !==
1198
+ counts.abandoned ||
1199
+ new Set(cohort.abandonmentReasons.map((reason) => reason.reasonCode)).size !==
1200
+ cohort.abandonmentReasons.length
1201
+ ) {
1202
+ context.addIssue({
1203
+ code: "custom",
1204
+ message: "abandonment reasons must partition abandoned sessions",
1205
+ });
1206
+ }
1207
+ });
1208
+
1209
+ const dynamicProductComplexityReportSchema = z
1210
+ .object({
1211
+ schemaVersion: z.literal(1),
1212
+ algorithm: z.literal("untangle-dynamic-search-complexity-v1"),
1213
+ isHeuristic: z.literal(true),
1214
+ workspaceId: nonEmpty,
1215
+ generatedAt: nonEmpty,
1216
+ sessionCounts: z
1217
+ .object({
1218
+ total: nonNegativeInt,
1219
+ active: nonNegativeInt,
1220
+ completed: nonNegativeInt,
1221
+ featureTaggedCompleted: nonNegativeInt,
1222
+ abandoned: nonNegativeInt,
1223
+ })
1224
+ .strict(),
1225
+ cohorts: z.array(dynamicProductComplexityCohortSchema),
1226
+ featureMappings: z.array(dynamicProductComplexityFeatureMappingSchema),
1227
+ outcomeCohorts: z.array(journeyOutcomeCohortSchema).optional(),
1228
+ caveats: stringArray,
1229
+ })
1230
+ .strict()
1231
+ .superRefine((report, context) => {
1232
+ const counts = report.sessionCounts;
1233
+ if (report.outcomeCohorts !== undefined) {
1234
+ const keys = report.outcomeCohorts.map(
1235
+ (cohort) => `${cohort.measurementContextId}:${cohort.featureFingerprint ?? ""}`,
1236
+ );
1237
+ for (const status of ["total", "active", "completed", "abandoned"] as const) {
1238
+ if (
1239
+ report.outcomeCohorts.reduce((sum, cohort) => sum + cohort.sessionCounts[status], 0) !==
1240
+ counts[status]
1241
+ ) {
1242
+ context.addIssue({
1243
+ code: "custom",
1244
+ message: "outcome cohorts must partition all recorded sessions",
1245
+ });
1246
+ }
1247
+ }
1248
+ if (new Set(keys).size !== keys.length) {
1249
+ context.addIssue({
1250
+ code: "custom",
1251
+ message: "outcome context and feature pairs must be unique",
1252
+ });
1253
+ }
1254
+ }
1255
+ if (counts.total !== counts.active + counts.completed + counts.abandoned) {
1256
+ context.addIssue({
1257
+ code: "custom",
1258
+ path: ["sessionCounts", "total"],
1259
+ message: "total sessions must equal active, completed and abandoned sessions",
1260
+ });
1261
+ }
1262
+ if (counts.featureTaggedCompleted > counts.completed) {
1263
+ context.addIssue({
1264
+ code: "custom",
1265
+ path: ["sessionCounts", "featureTaggedCompleted"],
1266
+ message: "feature-tagged completed sessions cannot exceed completed sessions",
1267
+ });
1268
+ }
1269
+ const contextIds = report.cohorts.map((cohort) => cohort.measurementContextId);
1270
+ if (new Set(contextIds).size !== contextIds.length) {
1271
+ context.addIssue({
1272
+ code: "custom",
1273
+ path: ["cohorts"],
1274
+ message: "measurement context IDs must be unique",
1275
+ });
1276
+ }
1277
+ const cohortSessions = report.cohorts.reduce(
1278
+ (sum, cohort) => sum + cohort.completedSessionCount,
1279
+ 0,
1280
+ );
1281
+ if (cohortSessions !== counts.completed) {
1282
+ context.addIssue({
1283
+ code: "custom",
1284
+ path: ["cohorts"],
1285
+ message: "every completed session must belong to exactly one cohort",
1286
+ });
1287
+ }
1288
+ const featureKeys = report.featureMappings.map(
1289
+ (mapping) => `${mapping.featureFingerprint}\u001f${mapping.measurementContextId}`,
1290
+ );
1291
+ if (new Set(featureKeys).size !== featureKeys.length) {
1292
+ context.addIssue({
1293
+ code: "custom",
1294
+ path: ["featureMappings"],
1295
+ message: "feature and measurement-context pairs must be unique",
1296
+ });
1297
+ }
1298
+ const featureSessions = report.featureMappings.reduce(
1299
+ (sum, mapping) => sum + mapping.search.completedSessionCount,
1300
+ 0,
1301
+ );
1302
+ if (featureSessions !== counts.featureTaggedCompleted) {
1303
+ context.addIssue({
1304
+ code: "custom",
1305
+ path: ["featureMappings"],
1306
+ message: "feature mappings must account for every feature-tagged completed session",
1307
+ });
1308
+ }
1309
+ const knownContexts = new Set(contextIds);
1310
+ for (const [index, mapping] of report.featureMappings.entries()) {
1311
+ if (!knownContexts.has(mapping.measurementContextId)) {
1312
+ context.addIssue({
1313
+ code: "custom",
1314
+ path: ["featureMappings", index, "measurementContextId"],
1315
+ message: "feature mapping must reference an existing workspace cohort",
1316
+ });
1317
+ }
1318
+ }
1319
+ for (const cohort of report.cohorts) {
1320
+ const taggedInContext = report.featureMappings
1321
+ .filter((mapping) => mapping.measurementContextId === cohort.measurementContextId)
1322
+ .reduce((sum, mapping) => sum + mapping.search.completedSessionCount, 0);
1323
+ if (taggedInContext > cohort.completedSessionCount) {
1324
+ context.addIssue({
1325
+ code: "custom",
1326
+ path: ["featureMappings"],
1327
+ message: "feature sessions in a context cannot exceed its completed cohort sessions",
1328
+ });
1329
+ }
1330
+ }
1331
+ });
1332
+
1333
+ const searchSessionResultSchema = z.discriminatedUnion("action", [
1334
+ z.object({ action: z.literal("start"), session: searchJourneySchema }).strict(),
1335
+ z.object({ action: z.literal("get"), session: searchJourneySchema }).strict(),
1336
+ z.object({ action: z.literal("observe"), session: searchJourneySchema }).strict(),
1337
+ z.object({ action: z.literal("complete"), session: searchJourneySchema }).strict(),
1338
+ z.object({ action: z.literal("abandon"), session: searchJourneySchema }).strict(),
1339
+ z.object({ action: z.literal("report"), report: dynamicProductComplexityReportSchema }).strict(),
1340
+ ]);
1341
+
1342
+ const graphConfidenceSchema = z
1343
+ .object({ value: ratio, basis: z.enum(["deterministic", "heuristic"]), signals: stringArray })
1344
+ .strict();
1345
+
1346
+ const symbolNodeSchema = z
1347
+ .object({
1348
+ id: nonEmpty,
1349
+ name: text,
1350
+ qualifiedName: text,
1351
+ kind: text,
1352
+ signature: text.optional(),
1353
+ repoId: nonEmpty,
1354
+ refId: nonEmpty,
1355
+ fileId: nonEmpty,
1356
+ filePath: text,
1357
+ language: text,
1358
+ startLine: positiveInt,
1359
+ endLine: positiveInt,
1360
+ startByte: nonNegativeInt,
1361
+ endByte: nonNegativeInt,
1362
+ exportedAs: stringArray.optional(),
1363
+ visibility: text.optional(),
1364
+ contentHash: text,
1365
+ })
1366
+ .strict();
1367
+
1368
+ const symbolEdgeTargetSchema = z
1369
+ .object({
1370
+ symbolId: nonEmpty,
1371
+ qualifiedName: text,
1372
+ repoId: nonEmpty,
1373
+ refId: nonEmpty,
1374
+ filePath: text,
1375
+ })
1376
+ .strict();
1377
+
1378
+ const symbolEdgeSchema = z
1379
+ .object({
1380
+ schemaVersion: z.literal(1),
1381
+ edgeId: nonEmpty,
1382
+ workspaceId: nonEmpty,
1383
+ repoId: nonEmpty,
1384
+ refId: nonEmpty,
1385
+ fromSymbolId: nonEmpty,
1386
+ kind: z.enum(["calls", "references", "extends", "implements", "overrides", "unresolved"]),
1387
+ toName: text,
1388
+ toQualifier: text.optional(),
1389
+ resolution: z.enum(["exact", "heuristic", "ambiguous", "unresolved"]),
1390
+ target: symbolEdgeTargetSchema.optional(),
1391
+ candidates: z.array(symbolEdgeTargetSchema.extend({ weight: ratio }).strict()).optional(),
1392
+ unresolvedReason: z
1393
+ .enum([
1394
+ "no_candidate_found",
1395
+ "target_outside_index",
1396
+ "target_in_unparsed_file",
1397
+ "dynamic_dispatch_not_statically_analyzable",
1398
+ "import_unresolved",
1399
+ "budget_exceeded",
1400
+ ])
1401
+ .optional(),
1402
+ unresolvedDetail: text.optional(),
1403
+ confidence: graphConfidenceSchema,
1404
+ crossRepo: z.boolean(),
1405
+ sites: z.array(
1406
+ z
1407
+ .object({
1408
+ repoId: nonEmpty,
1409
+ refId: nonEmpty,
1410
+ filePath: text,
1411
+ range: sourceRangeSchema,
1412
+ column: positiveInt.optional(),
1413
+ })
1414
+ .strict(),
1415
+ ),
1416
+ sitesMissingReason: text.optional(),
1417
+ occurrenceOrdinal: nonNegativeInt,
1418
+ graphGeneration: nonEmpty,
1419
+ graphVersion: nonEmpty,
1420
+ })
1421
+ .strict();
1422
+
1423
+ const symbolBindingSchema = z
1424
+ .object({
1425
+ symbolId: nonEmpty,
1426
+ edgeId: nonEmpty,
1427
+ resolution: z.enum(["exact", "heuristic", "ambiguous", "unresolved"]),
1428
+ weight: ratio,
1429
+ ambiguous: z.boolean(),
1430
+ })
1431
+ .strict();
1432
+
1433
+ const traversalLimitsSchema = z
1434
+ .object({
1435
+ maxDepth: nonNegativeInt,
1436
+ maxNodes: positiveInt,
1437
+ maxEdges: positiveInt,
1438
+ maxWallTimeMs: positiveInt,
1439
+ })
1440
+ .strict();
1441
+
1442
+ const traversalResultSchema = z
1443
+ .object({
1444
+ schemaVersion: z.literal(1),
1445
+ direction: z.enum(["forward", "reverse"]),
1446
+ rootSymbolIds: stringArray,
1447
+ visitedSymbolIds: stringArray,
1448
+ edgeIds: stringArray,
1449
+ depthReached: nonNegativeInt,
1450
+ truncated: z.boolean(),
1451
+ truncationReason: z
1452
+ .enum(["max_depth", "max_nodes", "max_edges", "max_wall_time", "cancelled"])
1453
+ .optional(),
1454
+ revisitSkippedSymbolIds: stringArray,
1455
+ limits: traversalLimitsSchema,
1456
+ })
1457
+ .strict();
1458
+
1459
+ const callPathSchema = z
1460
+ .object({
1461
+ schemaVersion: z.literal(1),
1462
+ symbolIds: stringArray,
1463
+ edgeIds: stringArray,
1464
+ depth: nonNegativeInt,
1465
+ confidence: ratio,
1466
+ weakestResolution: z.enum(["exact", "heuristic", "ambiguous", "unresolved"]),
1467
+ containsAmbiguous: z.boolean(),
1468
+ })
1469
+ .strict();
1470
+
1471
+ const shortestPathSchema = z
1472
+ .object({
1473
+ schemaVersion: z.literal(1),
1474
+ path: callPathSchema.nullable(),
1475
+ missReason: z
1476
+ .enum(["unknown_source_symbol", "unknown_target_symbol", "no_path_within_limits"])
1477
+ .optional(),
1478
+ missDetail: text.optional(),
1479
+ truncated: z.boolean(),
1480
+ truncationReason: z
1481
+ .enum(["max_depth", "max_nodes", "max_edges", "max_wall_time", "cancelled"])
1482
+ .optional(),
1483
+ limits: traversalLimitsSchema,
1484
+ })
1485
+ .strict();
1486
+
1487
+ const allPathsSchema = z
1488
+ .object({
1489
+ schemaVersion: z.literal(1),
1490
+ paths: z.array(callPathSchema),
1491
+ missReason: z
1492
+ .enum(["unknown_source_symbol", "unknown_target_symbol", "no_path_within_limits"])
1493
+ .optional(),
1494
+ missDetail: text.optional(),
1495
+ truncated: z.boolean(),
1496
+ truncationReason: z
1497
+ .enum(["max_depth", "max_nodes", "max_edges", "max_wall_time", "max_paths", "cancelled"])
1498
+ .optional(),
1499
+ droppedPathCount: nonNegativeInt,
1500
+ droppedPathCountExact: z.boolean(),
1501
+ pathLimitReached: z.boolean(),
1502
+ skipped: z.array(skipLedgerEntrySchema),
1503
+ maxDepth: nonNegativeInt,
1504
+ })
1505
+ .strict();
1506
+
1507
+ const impactResultSchema = z
1508
+ .object({
1509
+ schemaVersion: z.literal(1),
1510
+ rootSymbolIds: stringArray,
1511
+ affected: z.array(
1512
+ z
1513
+ .object({
1514
+ symbolId: nonEmpty,
1515
+ qualifiedName: text,
1516
+ filePath: text,
1517
+ repoId: nonEmpty,
1518
+ depth: nonNegativeInt,
1519
+ minDepth: nonNegativeInt,
1520
+ confidence: ratio,
1521
+ weakestResolution: z.enum(["exact", "heuristic", "ambiguous", "unresolved"]),
1522
+ viaEdgeIds: stringArray,
1523
+ throughAmbiguous: z.boolean(),
1524
+ })
1525
+ .strict(),
1526
+ ),
1527
+ containsAmbiguousEdges: z.boolean(),
1528
+ ambiguousEdgeIds: stringArray,
1529
+ minConfidence: ratio,
1530
+ traversal: traversalResultSchema,
1531
+ skipped: z.array(skipLedgerEntrySchema),
1532
+ })
1533
+ .strict();
1534
+
1535
+ const fileNodeSchema = z
1536
+ .object({
1537
+ schemaVersion: z.literal(1),
1538
+ workspaceId: nonEmpty,
1539
+ repoId: nonEmpty,
1540
+ refId: nonEmpty,
1541
+ fileId: nonEmpty,
1542
+ filePath: text,
1543
+ language: text,
1544
+ contentHash: text,
1545
+ imports: stringArray,
1546
+ dependencies: stringArray,
1547
+ dependents: stringArray,
1548
+ graphGeneration: nonEmpty,
1549
+ packageName: text.optional(),
1550
+ containerService: text.optional(),
1551
+ unresolvedImportCount: nonNegativeInt,
1552
+ })
1553
+ .strict();
1554
+
1555
+ const fileEdgeResolutionSchema = z.discriminatedUnion("status", [
1556
+ z
1557
+ .object({
1558
+ status: z.literal("resolved"),
1559
+ mechanism: nonEmpty,
1560
+ targetFileId: nonEmpty,
1561
+ targetRepoId: nonEmpty,
1562
+ targetRefId: nonEmpty,
1563
+ targetPath: text,
1564
+ viaPackageName: text.optional(),
1565
+ })
1566
+ .strict(),
1567
+ z
1568
+ .object({
1569
+ status: z.literal("external"),
1570
+ mechanism: nonEmpty,
1571
+ packageName: nonEmpty,
1572
+ declaredVersion: text.optional(),
1573
+ })
1574
+ .strict(),
1575
+ z
1576
+ .object({
1577
+ status: z.literal("unresolved"),
1578
+ reason: nonEmpty,
1579
+ detail: nonEmpty,
1580
+ rejectedCandidates: stringArray.optional(),
1581
+ })
1582
+ .strict(),
1583
+ ]);
1584
+
1585
+ const fileEdgeSchema = z
1586
+ .object({
1587
+ schemaVersion: z.literal(1),
1588
+ edgeId: nonEmpty,
1589
+ workspaceId: nonEmpty,
1590
+ repoId: nonEmpty,
1591
+ refId: nonEmpty,
1592
+ fromFileId: nonEmpty,
1593
+ fromPath: text,
1594
+ kind: z.enum([
1595
+ "import",
1596
+ "re-export",
1597
+ "dynamic-import",
1598
+ "asset-reference",
1599
+ "config-reference",
1600
+ "unresolved",
1601
+ ]),
1602
+ rawSpecifier: nonEmpty,
1603
+ resolution: fileEdgeResolutionSchema,
1604
+ boundary: z
1605
+ .object({
1606
+ crossRepo: z.boolean(),
1607
+ crossPackage: z.boolean(),
1608
+ crossContainer: z.boolean(),
1609
+ fromPackageName: text.optional(),
1610
+ toPackageName: text.optional(),
1611
+ fromContainerService: text.optional(),
1612
+ toContainerService: text.optional(),
1613
+ })
1614
+ .strict(),
1615
+ sites: z.array(
1616
+ z
1617
+ .object({
1618
+ range: sourceRangeSchema,
1619
+ importedNames: stringArray.optional(),
1620
+ typeOnly: z.boolean().optional(),
1621
+ })
1622
+ .strict(),
1623
+ ),
1624
+ sitesMissingReason: text.optional(),
1625
+ occurrenceOrdinal: nonNegativeInt,
1626
+ graphGeneration: nonEmpty,
1627
+ graphVersion: nonEmpty,
1628
+ })
1629
+ .strict();
1630
+
1631
+ const centralitySchema = z
1632
+ .object({
1633
+ fileId: nonEmpty,
1634
+ filePath: text,
1635
+ inDegree: nonNegativeInt,
1636
+ outDegree: nonNegativeInt,
1637
+ centrality: ratio,
1638
+ })
1639
+ .strict();
1640
+
1641
+ const fileGraphMetricsSchema = z
1642
+ .object({
1643
+ schemaVersion: z.literal(1),
1644
+ workspaceId: nonEmpty,
1645
+ graphGeneration: nonEmpty,
1646
+ graphVersion: nonEmpty,
1647
+ nodeCount: nonNegativeInt,
1648
+ edgeCount: nonNegativeInt,
1649
+ orphanFileIds: stringArray,
1650
+ stronglyConnectedComponents: z.array(stringArray),
1651
+ cycles: z.array(stringArray),
1652
+ importResolution: z
1653
+ .object({
1654
+ resolvedCount: nonNegativeInt,
1655
+ externalCount: nonNegativeInt,
1656
+ unresolvedCount: nonNegativeInt,
1657
+ rate: ratio,
1658
+ })
1659
+ .strict(),
1660
+ repositoryDistribution: numberMap,
1661
+ languageDistribution: numberMap,
1662
+ topCentralFiles: z.array(centralitySchema),
1663
+ centralityFormula: nonEmpty,
1664
+ truncationReason: text.optional(),
1665
+ })
1666
+ .strict();
1667
+
1668
+ const snapshotReadIssueSchema = z
1669
+ .object({
1670
+ kind: z.enum([
1671
+ "missing_meta",
1672
+ "missing_path_index",
1673
+ "missing_file_shard",
1674
+ "malformed_payload",
1675
+ "count_mismatch",
1676
+ "workspace_scope_required",
1677
+ "scope_mismatch",
1678
+ ]),
1679
+ message: nonEmpty,
1680
+ })
1681
+ .strict();
1682
+
1683
+ const fileTraversalSchema = z
1684
+ .object({
1685
+ schemaVersion: z.literal(1),
1686
+ direction: z.enum(["forward", "reverse"]),
1687
+ rootFileIds: stringArray,
1688
+ visitedFileIds: stringArray,
1689
+ edgeIds: stringArray,
1690
+ depthReached: nonNegativeInt,
1691
+ truncated: z.boolean(),
1692
+ truncationReason: z
1693
+ .enum(["max_depth", "max_nodes", "max_edges", "max_wall_time", "cancelled"])
1694
+ .optional(),
1695
+ revisitSkippedFileIds: stringArray,
1696
+ cycles: z.array(stringArray),
1697
+ unvisitedRootFileIds: stringArray.optional(),
1698
+ unknownRootFileIds: stringArray.optional(),
1699
+ limits: traversalLimitsSchema,
1700
+ visitedPaths: stringArray,
1701
+ cyclePaths: z.array(stringArray),
1702
+ })
1703
+ .strict();
1704
+
1705
+ const boundaryCountsSchema = z
1706
+ .object({
1707
+ crossRepo: nonNegativeInt,
1708
+ crossPackage: nonNegativeInt,
1709
+ crossContainer: nonNegativeInt,
1710
+ })
1711
+ .strict();
1712
+
1713
+ const graphFileViewSchema = z
1714
+ .object({
1715
+ generationId: nonEmpty,
1716
+ workspaceId: nonEmpty,
1717
+ node: fileNodeSchema,
1718
+ edges: z.array(fileEdgeSchema),
1719
+ dependencies: fileTraversalSchema.optional(),
1720
+ dependents: fileTraversalSchema.optional(),
1721
+ issues: z.array(snapshotReadIssueSchema),
1722
+ })
1723
+ .strict();
1724
+
1725
+ const boundedGraphFileViewSchema = graphFileViewSchema.extend({
1726
+ truncated: z.boolean(),
1727
+ omittedCount: nonNegativeInt,
1728
+ });
1729
+
1730
+ const graphViewStatsSchema = z
1731
+ .object({
1732
+ inputNodeCount: nonNegativeInt,
1733
+ shownNodeCount: nonNegativeInt,
1734
+ nodeLimitDroppedCount: nonNegativeInt,
1735
+ sourceEdgeCount: nonNegativeInt,
1736
+ candidateLinkCount: nonNegativeInt,
1737
+ renderableLinkCount: nonNegativeInt,
1738
+ shownLinkCount: nonNegativeInt,
1739
+ unrenderableSourceEdgeCount: nonNegativeInt,
1740
+ missingEndpointLinkCount: nonNegativeInt,
1741
+ nodeLimitDroppedLinkCount: nonNegativeInt,
1742
+ edgeLimitDroppedLinkCount: nonNegativeInt,
1743
+ })
1744
+ .strict();
1745
+
1746
+ const graphVisualizationArtifactShape = {
1747
+ path: nonEmpty,
1748
+ bytes: positiveInt,
1749
+ sha256: text.regex(/^[0-9a-f]{64}$/u),
1750
+ limits: z.object({ maxNodes: positiveInt, maxEdges: positiveInt }).strict(),
1751
+ };
1752
+
1753
+ const graphVisualizationShape = {
1754
+ generationId: nonEmpty,
1755
+ workspaceId: nonEmpty,
1756
+ issues: z.array(snapshotReadIssueSchema),
1757
+ };
1758
+
1759
+ const graphVisualizationSchema = z.union([
1760
+ z
1761
+ .object({
1762
+ ...graphVisualizationShape,
1763
+ artifact: z
1764
+ .object({
1765
+ ...graphVisualizationArtifactShape,
1766
+ views: z.object({ file: graphViewStatsSchema, symbol: graphViewStatsSchema }).strict(),
1767
+ })
1768
+ .strict(),
1769
+ symbolGraph: z.object({ available: z.literal(true) }).strict(),
1770
+ })
1771
+ .strict(),
1772
+ z
1773
+ .object({
1774
+ ...graphVisualizationShape,
1775
+ artifact: z
1776
+ .object({
1777
+ ...graphVisualizationArtifactShape,
1778
+ views: z.object({ file: graphViewStatsSchema, symbol: z.null() }).strict(),
1779
+ })
1780
+ .strict(),
1781
+ symbolGraph: z.object({ available: z.literal(false), missingReason: nonEmpty }).strict(),
1782
+ })
1783
+ .strict(),
1784
+ ]);
1785
+
1786
+ const symbolListSchema = z
1787
+ .object({
1788
+ generationId: nonEmpty,
1789
+ symbols: z.array(symbolNodeSchema),
1790
+ total: nonNegativeInt,
1791
+ truncated: z.boolean(),
1792
+ })
1793
+ .strict();
1794
+
1795
+ const symbolDetailSchema = z
1796
+ .object({
1797
+ generationId: nonEmpty,
1798
+ matchedBy: z.enum(["symbol_id", "qualified_name", "file_and_name", "name"]).optional(),
1799
+ symbol: symbolNodeSchema,
1800
+ outgoing: z.array(symbolEdgeSchema),
1801
+ callers: z.array(symbolBindingSchema),
1802
+ })
1803
+ .strict();
1804
+
1805
+ const boundedFlowSymbolDetailSchema = symbolDetailSchema.extend({
1806
+ truncated: z.boolean(),
1807
+ omittedCount: nonNegativeInt,
1808
+ });
1809
+
1810
+ const flowSchema = z.union([
1811
+ boundedFlowSymbolDetailSchema,
1812
+ z
1813
+ .object({
1814
+ generationId: nonEmpty,
1815
+ from: symbolNodeSchema,
1816
+ to: symbolNodeSchema,
1817
+ shortest: shortestPathSchema,
1818
+ all: allPathsSchema,
1819
+ })
1820
+ .strict(),
1821
+ ]);
1822
+
1823
+ const impactSchema = z
1824
+ .object({
1825
+ generationId: nonEmpty,
1826
+ root: symbolNodeSchema,
1827
+ impact: impactResultSchema,
1828
+ truncated: z.boolean(),
1829
+ omittedCount: nonNegativeInt,
1830
+ })
1831
+ .strict();
1832
+
1833
+ const proseNumberSchema = z
1834
+ .object({
1835
+ proseMeasurement: z.literal(true),
1836
+ value: finite,
1837
+ unit: z.enum(["levels", "lines", "per_100_lines", "ratio", "count"]),
1838
+ })
1839
+ .strict();
1840
+
1841
+ const complexitySymbolSchema = z
1842
+ .object({
1843
+ filePath: text,
1844
+ name: text,
1845
+ kind: text,
1846
+ line: positiveInt,
1847
+ executionUnit: z.boolean(),
1848
+ cognitive: nonNegative,
1849
+ cyclomatic: nonNegative,
1850
+ sloc: nonNegative,
1851
+ })
1852
+ .strict();
1853
+
1854
+ const complexityViolationSchema = z
1855
+ .object({
1856
+ filePath: text,
1857
+ line: positiveInt,
1858
+ name: text,
1859
+ metric: z.enum(["cognitive", "cyclomatic"]),
1860
+ value: nonNegative,
1861
+ threshold: finite,
1862
+ })
1863
+ .strict();
1864
+
1865
+ const proseSummarySchema = z
1866
+ .object({
1867
+ path: text,
1868
+ metricsVersion: nonEmpty,
1869
+ structureLoad: proseNumberSchema,
1870
+ sectionCount: proseNumberSchema,
1871
+ sectionDepth: proseNumberSchema,
1872
+ maxSectionLines: proseNumberSchema,
1873
+ linkDensity: proseNumberSchema,
1874
+ unresolvedLinkRatio: proseNumberSchema.nullable(),
1875
+ repetitionRatio: proseNumberSchema,
1876
+ contentLines: proseNumberSchema,
1877
+ })
1878
+ .strict();
1879
+
1880
+ const complexityReportSchema = z
1881
+ .object({
1882
+ schemaVersion: z.literal(1),
1883
+ root: nonEmpty,
1884
+ filesDiscovered: nonNegativeInt,
1885
+ filesAnalyzed: nonNegativeInt,
1886
+ filesProse: nonNegativeInt,
1887
+ totalsReading: z.enum(["execution_unit"]).optional(),
1888
+ totals: z
1889
+ .object({
1890
+ executionUnits: nonNegativeInt,
1891
+ executionUnitRoots: nonNegativeInt.optional(),
1892
+ cognitive: nonNegative,
1893
+ cyclomatic: nonNegative,
1894
+ })
1895
+ .strict(),
1896
+ hotspots: z.array(complexitySymbolSchema),
1897
+ violations: z.array(complexityViolationSchema),
1898
+ prose: z.array(proseSummarySchema),
1899
+ skipped: z.array(skipLedgerEntrySchema),
1900
+ })
1901
+ .strict();
1902
+
1903
+ const metricContributionSchema = z
1904
+ .object({
1905
+ line: positiveInt,
1906
+ column: positiveInt.optional(),
1907
+ kind: nonEmpty,
1908
+ value: finite,
1909
+ note: text.optional(),
1910
+ })
1911
+ .strict();
1912
+
1913
+ const cognitiveContributionSchema = z
1914
+ .object({
1915
+ line: positiveInt,
1916
+ column: positiveInt.optional(),
1917
+ kind: nonEmpty,
1918
+ base: finite,
1919
+ nestingPenalty: finite,
1920
+ total: finite,
1921
+ note: text.optional(),
1922
+ })
1923
+ .strict();
1924
+
1925
+ const complexitySnapshotSchema = z
1926
+ .object({
1927
+ snapshotId: nonEmpty,
1928
+ scope: z.enum(["symbol", "file", "repository", "workspace"]),
1929
+ scopeId: nonEmpty,
1930
+ repoId: nonEmpty,
1931
+ refId: nonEmpty,
1932
+ commitSha: text.optional(),
1933
+ metricsVersion: nonEmpty,
1934
+ measuredAt: nonEmpty,
1935
+ parseQuality: z.enum(["complete", "partial", "failed"]),
1936
+ cyclomatic: z
1937
+ .object({
1938
+ classic: finite,
1939
+ extended: finite,
1940
+ contributors: z.array(metricContributionSchema),
1941
+ })
1942
+ .strict(),
1943
+ cognitive: z
1944
+ .object({
1945
+ value: finite,
1946
+ algorithm: nonEmpty,
1947
+ contributors: z.array(cognitiveContributionSchema),
1948
+ })
1949
+ .strict(),
1950
+ structural: numberMap,
1951
+ textual: numberMap,
1952
+ syntactic: numberMap,
1953
+ halstead: numberMap,
1954
+ maintainabilityIndex: finite,
1955
+ maintainabilityFormula: nonEmpty,
1956
+ })
1957
+ .strict();
1958
+
1959
+ const metricDeltaSchema = z
1960
+ .object({
1961
+ metric: nonEmpty,
1962
+ baseValue: finite.nullable(),
1963
+ headValue: finite.nullable(),
1964
+ delta: finite.nullable(),
1965
+ relativeDelta: finite.nullable(),
1966
+ })
1967
+ .strict();
1968
+
1969
+ const changeEventSchema = z
1970
+ .object({
1971
+ schemaVersion: z.literal(1),
1972
+ changeId: nonEmpty,
1973
+ workspaceId: nonEmpty,
1974
+ repoId: nonEmpty,
1975
+ baseRef: nonEmpty,
1976
+ headRef: nonEmpty,
1977
+ baseCommit: text.optional(),
1978
+ headCommit: text.optional(),
1979
+ changedAt: nonEmpty,
1980
+ changeKind: z.enum(["added", "modified", "deleted", "renamed", "moved", "moved_and_renamed"]),
1981
+ oldSymbolId: text.optional(),
1982
+ newSymbolId: text.optional(),
1983
+ oldFilePath: text.optional(),
1984
+ newFilePath: text.optional(),
1985
+ textDiffSummary: z
1986
+ .object({
1987
+ linesAdded: nonNegativeInt,
1988
+ linesRemoved: nonNegativeInt,
1989
+ hunkCount: nonNegativeInt,
1990
+ binary: z.boolean(),
1991
+ bytesAdded: nonNegativeInt.optional(),
1992
+ bytesRemoved: nonNegativeInt.optional(),
1993
+ })
1994
+ .strict(),
1995
+ astDiffSummary: z
1996
+ .object({
1997
+ nodesAdded: nonNegativeInt,
1998
+ nodesRemoved: nonNegativeInt,
1999
+ nodesMoved: nonNegativeInt,
2000
+ nodesUpdated: nonNegativeInt,
2001
+ affectedNodeKinds: stringArray,
2002
+ fingerprintChanged: z.boolean(),
2003
+ })
2004
+ .strict()
2005
+ .optional(),
2006
+ complexityDelta: z
2007
+ .object({
2008
+ metricsVersion: nonEmpty,
2009
+ epsilon: nonNegative,
2010
+ entries: z.array(metricDeltaSchema),
2011
+ })
2012
+ .strict()
2013
+ .optional(),
2014
+ graphDelta: z
2015
+ .object({
2016
+ addedFileImports: z.array(
2017
+ z
2018
+ .object({
2019
+ fromId: nonEmpty,
2020
+ toId: nonEmpty,
2021
+ edgeKind: nonEmpty,
2022
+ crossRepo: z.boolean(),
2023
+ })
2024
+ .strict(),
2025
+ ),
2026
+ removedFileImports: z.array(
2027
+ z
2028
+ .object({
2029
+ fromId: nonEmpty,
2030
+ toId: nonEmpty,
2031
+ edgeKind: nonEmpty,
2032
+ crossRepo: z.boolean(),
2033
+ })
2034
+ .strict(),
2035
+ ),
2036
+ addedSymbolCalls: z.array(
2037
+ z
2038
+ .object({
2039
+ fromId: nonEmpty,
2040
+ toId: nonEmpty,
2041
+ edgeKind: nonEmpty,
2042
+ crossRepo: z.boolean(),
2043
+ })
2044
+ .strict(),
2045
+ ),
2046
+ removedSymbolCalls: z.array(
2047
+ z
2048
+ .object({
2049
+ fromId: nonEmpty,
2050
+ toId: nonEmpty,
2051
+ edgeKind: nonEmpty,
2052
+ crossRepo: z.boolean(),
2053
+ })
2054
+ .strict(),
2055
+ ),
2056
+ addedCrossRepoEdges: z.array(
2057
+ z
2058
+ .object({
2059
+ fromId: nonEmpty,
2060
+ toId: nonEmpty,
2061
+ edgeKind: nonEmpty,
2062
+ crossRepo: z.boolean(),
2063
+ })
2064
+ .strict(),
2065
+ ),
2066
+ removedCrossRepoEdges: z.array(
2067
+ z
2068
+ .object({
2069
+ fromId: nonEmpty,
2070
+ toId: nonEmpty,
2071
+ edgeKind: nonEmpty,
2072
+ crossRepo: z.boolean(),
2073
+ })
2074
+ .strict(),
2075
+ ),
2076
+ addedCycles: z.array(stringArray),
2077
+ removedCycles: z.array(stringArray),
2078
+ fanChanges: z.array(
2079
+ z
2080
+ .object({
2081
+ nodeId: nonEmpty,
2082
+ fanInBefore: nonNegativeInt,
2083
+ fanInAfter: nonNegativeInt,
2084
+ fanOutBefore: nonNegativeInt,
2085
+ fanOutAfter: nonNegativeInt,
2086
+ })
2087
+ .strict(),
2088
+ ),
2089
+ impactSizeBefore: nonNegativeInt,
2090
+ impactSizeAfter: nonNegativeInt,
2091
+ })
2092
+ .strict()
2093
+ .optional(),
2094
+ blastRadius: z
2095
+ .object({
2096
+ impactedFileCount: nonNegativeInt,
2097
+ impactedSymbolCount: nonNegativeInt,
2098
+ impactedRepoIds: stringArray,
2099
+ maxDepthReached: nonNegativeInt,
2100
+ truncated: z.boolean(),
2101
+ truncationReason: text.optional(),
2102
+ sampleImpactedSymbolIds: stringArray,
2103
+ })
2104
+ .strict()
2105
+ .optional(),
2106
+ risk: z
2107
+ .object({
2108
+ value: ratio,
2109
+ level: z.enum(["low", "medium", "high", "critical"]),
2110
+ algorithm: nonEmpty,
2111
+ components: z.record(
2112
+ z.string(),
2113
+ z.object({ value: finite, weight: finite, contribution: finite }).strict(),
2114
+ ),
2115
+ caveats: stringArray,
2116
+ })
2117
+ .strict()
2118
+ .optional(),
2119
+ })
2120
+ .strict();
2121
+
2122
+ const qualityGateSchema = z
2123
+ .object({
2124
+ status: z.enum(["passed", "warning", "failed", "incomplete"]),
2125
+ base: nonEmpty,
2126
+ head: nonEmpty,
2127
+ violations: z.array(
2128
+ z
2129
+ .object({
2130
+ rule: nonEmpty,
2131
+ severity: z.enum(["warning", "error"]),
2132
+ scope: z.enum(["symbol", "file", "workspace"]),
2133
+ scopeId: nonEmpty,
2134
+ filePath: text.optional(),
2135
+ symbolId: text.optional(),
2136
+ symbolFqn: text.optional(),
2137
+ metric: text.optional(),
2138
+ baseValue: finite.optional(),
2139
+ headValue: finite.optional(),
2140
+ threshold: finite.optional(),
2141
+ newViolation: z.boolean(),
2142
+ worsened: z.boolean(),
2143
+ message: nonEmpty,
2144
+ })
2145
+ .strict(),
2146
+ ),
2147
+ improvements: z.array(
2148
+ z
2149
+ .object({
2150
+ rule: nonEmpty,
2151
+ scope: z.enum(["symbol", "file", "workspace"]),
2152
+ scopeId: nonEmpty,
2153
+ filePath: text.optional(),
2154
+ symbolId: text.optional(),
2155
+ symbolFqn: text.optional(),
2156
+ metric: text.optional(),
2157
+ baseValue: finite.optional(),
2158
+ headValue: finite.optional(),
2159
+ message: nonEmpty,
2160
+ })
2161
+ .strict(),
2162
+ ),
2163
+ analysisCoverage: z
2164
+ .object({
2165
+ filesChanged: nonNegativeInt,
2166
+ filesAnalyzed: nonNegativeInt,
2167
+ symbolsChanged: nonNegativeInt,
2168
+ parseFailures: nonNegativeInt,
2169
+ })
2170
+ .strict(),
2171
+ })
2172
+ .strict();
2173
+
2174
+ // Portfolio contracts: portfolio.md sections 7 and 14.2.
2175
+ const portfolioEvidenceKindSchema = z.enum([
2176
+ "manifest",
2177
+ "lockfile",
2178
+ "config",
2179
+ "import_graph",
2180
+ "symbol_graph",
2181
+ "ci",
2182
+ "container",
2183
+ "iac",
2184
+ "change_history",
2185
+ "heuristic",
2186
+ ]);
2187
+
2188
+ const portfolioProvenanceSchema = z
2189
+ .object({
2190
+ workspaceId: nonEmpty,
2191
+ repoId: nonEmpty,
2192
+ refId: nonEmpty,
2193
+ commitSha: text.optional(),
2194
+ sourceFile: text,
2195
+ startLine: positiveInt.optional(),
2196
+ endLine: positiveInt.optional(),
2197
+ detectorId: nonEmpty,
2198
+ detectorVersion: nonEmpty,
2199
+ evidenceKind: portfolioEvidenceKindSchema,
2200
+ })
2201
+ .strict();
2202
+
2203
+ const portfolioConfidenceSchema = z
2204
+ .object({ value: ratio, basis: z.enum(["deterministic", "heuristic"]), signals: stringArray })
2205
+ .strict();
2206
+
2207
+ const repositoryHealthSchema = z
2208
+ .object({
2209
+ metricsVersion: nonEmpty,
2210
+ measuredAt: nonEmpty,
2211
+ maintainabilityIndexMedian: finite.optional(),
2212
+ cognitiveP90: finite.optional(),
2213
+ cyclomaticP90: finite.optional(),
2214
+ dependencyCycleCount: nonNegativeInt.optional(),
2215
+ hotspotShare: ratio.optional(),
2216
+ unresolvedImportRatio: ratio.optional(),
2217
+ parseFailureRatio: ratio.optional(),
2218
+ caveats: stringArray,
2219
+ })
2220
+ .strict();
2221
+
2222
+ const stackMatrixEntrySchema = z
2223
+ .object({
2224
+ technologyId: nonEmpty,
2225
+ name: nonEmpty,
2226
+ category: nonEmpty,
2227
+ ecosystem: text.optional(),
2228
+ problemDomain: text.optional(),
2229
+ adoption: z
2230
+ .object({ repoCount: nonNegativeInt, repoShare: ratio, repoIds: stringArray })
2231
+ .strict(),
2232
+ versionSpread: z
2233
+ .object({
2234
+ distinctVersions: stringArray,
2235
+ byRepo: optionalStringMap,
2236
+ majorSpread: nonNegativeInt,
2237
+ comparable: z.boolean(),
2238
+ })
2239
+ .strict(),
2240
+ usageDepth: z
2241
+ .object({
2242
+ importingFilesByRepo: z.record(z.string(), nonNegativeInt),
2243
+ declaredOnlyRepos: stringArray,
2244
+ importOnlyRepos: stringArray,
2245
+ })
2246
+ .strict(),
2247
+ firstSeenAt: text.optional(),
2248
+ lastSeenAt: text.optional(),
2249
+ confidence: portfolioConfidenceSchema,
2250
+ })
2251
+ .strict();
2252
+
2253
+ export const stackMatrixResultSchema = z
2254
+ .object({
2255
+ schemaVersion: z.literal(1),
2256
+ matrixId: nonEmpty,
2257
+ workspaceId: nonEmpty,
2258
+ generationId: nonEmpty,
2259
+ generatedAt: nonEmpty,
2260
+ analyzerVersion: nonEmpty,
2261
+ repositories: z.array(
2262
+ z.object({ repoId: nonEmpty, refId: nonEmpty, profileId: nonEmpty }).strict(),
2263
+ ),
2264
+ entries: z.array(stackMatrixEntrySchema),
2265
+ warnings: stringArray,
2266
+ })
2267
+ .strict();
2268
+
2269
+ const conventionRuleSchema = z
2270
+ .object({
2271
+ ruleKind: z.enum([
2272
+ "path_exists",
2273
+ "path_layout",
2274
+ "naming_pattern",
2275
+ "dependency_present",
2276
+ "layering",
2277
+ "symbol_pattern",
2278
+ "config_pattern",
2279
+ ]),
2280
+ expression: jsonMap,
2281
+ humanReadable: nonEmpty,
2282
+ })
2283
+ .strict();
2284
+
2285
+ const conventionSchema = z
2286
+ .object({
2287
+ schemaVersion: z.literal(1),
2288
+ conventionId: nonEmpty,
2289
+ workspaceId: nonEmpty,
2290
+ generationId: nonEmpty,
2291
+ kind: z.enum([
2292
+ "directory_layout",
2293
+ "layering",
2294
+ "naming",
2295
+ "test_setup",
2296
+ "error_handling",
2297
+ "config_handling",
2298
+ "logging",
2299
+ ]),
2300
+ title: nonEmpty,
2301
+ description: text,
2302
+ rule: conventionRuleSchema,
2303
+ evidence: z
2304
+ .object({
2305
+ applicableRepos: stringArray,
2306
+ supportingRepos: stringArray,
2307
+ matchCount: nonNegativeInt,
2308
+ counterexamples: z.array(
2309
+ z
2310
+ .object({
2311
+ repoId: nonEmpty,
2312
+ provenance: portfolioProvenanceSchema,
2313
+ note: text.optional(),
2314
+ })
2315
+ .strict(),
2316
+ ),
2317
+ sampleProvenance: z.array(portfolioProvenanceSchema),
2318
+ })
2319
+ .strict(),
2320
+ confidence: portfolioConfidenceSchema,
2321
+ detectorId: nonEmpty,
2322
+ detectorVersion: nonEmpty,
2323
+ heuristic: z.boolean(),
2324
+ })
2325
+ .strict();
2326
+
2327
+ const archetypeNameSchema = z.enum([
2328
+ "layered_service",
2329
+ "hexagonal_service",
2330
+ "modular_monolith",
2331
+ "api_service",
2332
+ "worker",
2333
+ "cli_tool",
2334
+ "library",
2335
+ "frontend_app",
2336
+ "mcp_server",
2337
+ "infra_repo",
2338
+ "monorepo_root",
2339
+ "unclassified",
2340
+ ]);
2341
+
2342
+ const archetypeSchema = z
2343
+ .object({
2344
+ schemaVersion: z.literal(1),
2345
+ archetypeId: nonEmpty,
2346
+ workspaceId: nonEmpty,
2347
+ repoId: nonEmpty,
2348
+ refId: nonEmpty,
2349
+ generationId: nonEmpty,
2350
+ classifierVersion: nonEmpty,
2351
+ archetype: archetypeNameSchema,
2352
+ score: ratio,
2353
+ alternatives: z.array(z.object({ archetype: archetypeNameSchema, score: ratio }).strict()),
2354
+ signals: z.array(
2355
+ z
2356
+ .object({
2357
+ signalId: nonEmpty,
2358
+ description: nonEmpty,
2359
+ weight: finite,
2360
+ matched: z.boolean(),
2361
+ contribution: finite,
2362
+ provenance: portfolioProvenanceSchema.optional(),
2363
+ heuristic: z.boolean(),
2364
+ })
2365
+ .strict(),
2366
+ ),
2367
+ confidence: portfolioConfidenceSchema,
2368
+ })
2369
+ .strict();
2370
+
2371
+ const radarEntrySchema = z
2372
+ .object({
2373
+ schemaVersion: z.literal(1),
2374
+ entryId: nonEmpty,
2375
+ workspaceId: nonEmpty,
2376
+ generationId: nonEmpty,
2377
+ radarVersion: nonEmpty,
2378
+ technologyId: nonEmpty,
2379
+ name: nonEmpty,
2380
+ category: nonEmpty,
2381
+ problemDomain: text.optional(),
2382
+ ring: z.enum(["adopt", "maintain", "consolidate", "retire"]),
2383
+ ringReason: stringArray,
2384
+ versionDrift: z
2385
+ .object({
2386
+ distinctVersions: stringArray,
2387
+ majorSpread: nonNegativeInt,
2388
+ byRepo: optionalStringMap,
2389
+ comparable: z.boolean(),
2390
+ })
2391
+ .strict(),
2392
+ duplicates: z.array(
2393
+ z.object({ problemDomain: nonEmpty, competingTechnologyIds: stringArray }).strict(),
2394
+ ),
2395
+ trend: z
2396
+ .object({
2397
+ direction: z.enum(["converging", "diverging", "stable", "declining", "new", "unknown"]),
2398
+ points: z.array(
2399
+ z
2400
+ .object({
2401
+ window: nonEmpty,
2402
+ repoCount: nonNegativeInt,
2403
+ distinctVersionCount: nonNegativeInt,
2404
+ })
2405
+ .strict(),
2406
+ ),
2407
+ })
2408
+ .strict(),
2409
+ evidence: z.array(portfolioProvenanceSchema),
2410
+ confidence: portfolioConfidenceSchema,
2411
+ })
2412
+ .strict();
2413
+
2414
+ const blueprintKindSchema = z.enum([
2415
+ "api-service",
2416
+ "worker",
2417
+ "cli",
2418
+ "library",
2419
+ "web-app",
2420
+ "mcp-server",
2421
+ ]);
2422
+
2423
+ const blueprintFileSchema = z
2424
+ .object({
2425
+ path: text,
2426
+ templateId: nonEmpty,
2427
+ contentPreviewHash: nonEmpty,
2428
+ sizeBytes: nonNegativeInt,
2429
+ })
2430
+ .strict();
2431
+
2432
+ const blueprintDependencySchema = z
2433
+ .object({
2434
+ technologyId: nonEmpty,
2435
+ name: nonEmpty,
2436
+ ecosystem: nonEmpty,
2437
+ version: nonEmpty,
2438
+ usage: z.enum(["runtime", "dev"]),
2439
+ recommendationId: nonEmpty,
2440
+ })
2441
+ .strict();
2442
+
2443
+ const recommendationComponentSchema = z
2444
+ .object({
2445
+ name: z.enum(["adoption", "recency", "health", "version_consistency"]),
2446
+ value: ratio,
2447
+ weight: finite,
2448
+ contribution: finite,
2449
+ inputs: jsonMap,
2450
+ })
2451
+ .strict();
2452
+
2453
+ const blueprintSchema = z
2454
+ .object({
2455
+ schemaVersion: z.literal(1),
2456
+ blueprintId: nonEmpty,
2457
+ workspaceId: nonEmpty,
2458
+ generationId: nonEmpty,
2459
+ advisorVersion: nonEmpty,
2460
+ generatedAt: nonEmpty,
2461
+ kind: blueprintKindSchema,
2462
+ language: nonEmpty,
2463
+ runtime: z.object({ name: nonEmpty, versionRange: nonEmpty }).strict(),
2464
+ directoryLayout: z.array(
2465
+ z
2466
+ .object({
2467
+ path: text,
2468
+ kind: z.enum(["directory", "file"]),
2469
+ purpose: nonEmpty,
2470
+ sourceConventionId: text.optional(),
2471
+ })
2472
+ .strict(),
2473
+ ),
2474
+ dependencies: z.array(blueprintDependencySchema),
2475
+ toolchain: z.array(blueprintDependencySchema),
2476
+ testSetup: z
2477
+ .object({ framework: nonEmpty, layout: nonEmpty, recommendationId: nonEmpty })
2478
+ .strict(),
2479
+ ciSkeleton: z
2480
+ .object({ system: nonEmpty, workflowFiles: stringArray, recommendationId: nonEmpty })
2481
+ .strict(),
2482
+ appliedConventionIds: stringArray,
2483
+ recommendationIds: stringArray,
2484
+ files: z.array(blueprintFileSchema).optional(),
2485
+ evidenceSummary: z
2486
+ .object({
2487
+ reposConsidered: nonNegativeInt,
2488
+ reposMatchingKind: stringArray,
2489
+ portfolioGenerationId: nonEmpty,
2490
+ })
2491
+ .strict(),
2492
+ warnings: stringArray,
2493
+ })
2494
+ .strict();
2495
+
2496
+ const blueprintRecommendationSchema = z
2497
+ .object({
2498
+ schemaVersion: z.literal(1),
2499
+ recommendationId: nonEmpty,
2500
+ blueprintId: nonEmpty,
2501
+ workspaceId: nonEmpty,
2502
+ generationId: nonEmpty,
2503
+ advisorVersion: nonEmpty,
2504
+ topic: nonEmpty,
2505
+ recommended: z
2506
+ .object({ technologyId: nonEmpty, name: nonEmpty, version: text.optional() })
2507
+ .strict(),
2508
+ fitScore: ratio,
2509
+ components: z.array(recommendationComponentSchema),
2510
+ evidence: z
2511
+ .object({
2512
+ supportingRepoIds: stringArray,
2513
+ supportingRecordIds: stringArray,
2514
+ healthByRepo: z.record(z.string(), repositoryHealthSchema.nullable()),
2515
+ })
2516
+ .strict(),
2517
+ alternatives: z.array(
2518
+ z
2519
+ .object({
2520
+ technologyId: nonEmpty,
2521
+ name: nonEmpty,
2522
+ version: text.optional(),
2523
+ fitScore: ratio,
2524
+ components: z.array(recommendationComponentSchema),
2525
+ note: text.optional(),
2526
+ })
2527
+ .strict(),
2528
+ ),
2529
+ risks: z.array(
2530
+ z
2531
+ .object({
2532
+ kind: z.enum([
2533
+ "outdated_version",
2534
+ "low_adoption",
2535
+ "high_hotspot_share",
2536
+ "version_divergence",
2537
+ "single_repo_evidence",
2538
+ "declared_only_evidence",
2539
+ "heuristic_evidence",
2540
+ ]),
2541
+ severity: z.enum(["info", "warning", "critical"]),
2542
+ message: nonEmpty,
2543
+ evidence: z.array(portfolioProvenanceSchema),
2544
+ })
2545
+ .strict(),
2546
+ ),
2547
+ confidence: portfolioConfidenceSchema,
2548
+ })
2549
+ .strict();
2550
+
2551
+ export const portfolioScanResultSchema = z
2552
+ .object({
2553
+ schemaVersion: z.literal(1),
2554
+ jobId: nonEmpty,
2555
+ generationId: nonEmpty,
2556
+ status: z.enum(["queued", "running", "completed", "failed", "cancelled"]),
2557
+ repositoriesPlanned: nonNegativeInt,
2558
+ repositoriesCompleted: nonNegativeInt,
2559
+ technologiesDetected: nonNegativeInt.optional(),
2560
+ conventionsDetected: nonNegativeInt.optional(),
2561
+ warnings: stringArray,
2562
+ })
2563
+ .strict();
2564
+
2565
+ export const whoUsesResultSchema = z
2566
+ .object({
2567
+ schemaVersion: z.literal(1),
2568
+ technology: z
2569
+ .object({
2570
+ technologyId: nonEmpty,
2571
+ name: nonEmpty,
2572
+ category: nonEmpty,
2573
+ ecosystem: text.optional(),
2574
+ })
2575
+ .strict(),
2576
+ generationId: nonEmpty,
2577
+ usages: z.array(
2578
+ z
2579
+ .object({
2580
+ repoId: nonEmpty,
2581
+ repoLabel: text,
2582
+ refId: nonEmpty,
2583
+ versionRaw: text.optional(),
2584
+ versionResolved: text.optional(),
2585
+ usage: nonEmpty,
2586
+ declaredOnly: z.boolean(),
2587
+ importOnly: z.boolean(),
2588
+ importingFileCount: nonNegativeInt.optional(),
2589
+ firstSeenAt: text.optional(),
2590
+ lastSeenAt: text.optional(),
2591
+ provenance: z.array(portfolioProvenanceSchema),
2592
+ })
2593
+ .strict(),
2594
+ ),
2595
+ notUsingRepoIds: stringArray,
2596
+ warnings: stringArray,
2597
+ })
2598
+ .strict();
2599
+
2600
+ export const conventionsResultSchema = z
2601
+ .object({
2602
+ schemaVersion: z.literal(1),
2603
+ generationId: nonEmpty,
2604
+ conventions: z.array(conventionSchema),
2605
+ suppressedCount: nonNegativeInt,
2606
+ warnings: stringArray,
2607
+ })
2608
+ .strict();
2609
+
2610
+ export const archetypesResultSchema = z
2611
+ .object({
2612
+ schemaVersion: z.literal(1),
2613
+ generationId: nonEmpty,
2614
+ classifierVersion: nonEmpty,
2615
+ assignments: z.array(archetypeSchema),
2616
+ warnings: stringArray,
2617
+ })
2618
+ .strict();
2619
+
2620
+ export const techRadarResultSchema = z
2621
+ .object({
2622
+ schemaVersion: z.literal(1),
2623
+ generationId: nonEmpty,
2624
+ radarVersion: nonEmpty,
2625
+ entries: z.array(radarEntrySchema),
2626
+ historyAvailable: z.boolean(),
2627
+ warnings: stringArray,
2628
+ })
2629
+ .strict();
2630
+
2631
+ export const blueprintResultSchema = z
2632
+ .object({
2633
+ schemaVersion: z.literal(1),
2634
+ blueprint: blueprintSchema,
2635
+ recommendations: z.array(blueprintRecommendationSchema),
2636
+ scaffold: z
2637
+ .object({
2638
+ mode: z.enum(["dry_run", "applied"]),
2639
+ targetDir: nonEmpty,
2640
+ files: z.array(blueprintFileSchema),
2641
+ manifestPath: text.optional(),
2642
+ })
2643
+ .strict()
2644
+ .optional(),
2645
+ warnings: stringArray,
2646
+ })
2647
+ .strict();
2648
+
2649
+ const graphStatusSchema = z
2650
+ .object({
2651
+ generationId: nonEmpty,
2652
+ workspaceId: nonEmpty,
2653
+ metrics: fileGraphMetricsSchema,
2654
+ issues: z.array(snapshotReadIssueSchema),
2655
+ })
2656
+ .strict();
2657
+
2658
+ const graphStatsSchema = z
2659
+ .object({
2660
+ generationId: nonEmpty,
2661
+ metrics: fileGraphMetricsSchema,
2662
+ boundaries: boundaryCountsSchema,
2663
+ issues: z.array(snapshotReadIssueSchema),
2664
+ })
2665
+ .strict();
2666
+
2667
+ const graphCyclesSchema = z
2668
+ .object({
2669
+ generationId: nonEmpty,
2670
+ cycles: z.array(stringArray),
2671
+ total: nonNegativeInt,
2672
+ issues: z.array(snapshotReadIssueSchema),
2673
+ })
2674
+ .strict();
2675
+
2676
+ const aboutSchema = z
2677
+ .object({
2678
+ name: z.literal("untangle"),
2679
+ version: nonEmpty,
2680
+ license: z.literal("AGPL-3.0-only"),
2681
+ source: nonEmpty,
2682
+ commit: nonEmpty,
2683
+ availableTools: z.array(nonEmpty),
2684
+ unavailableToolsReturn: z.literal("FEATURE_UNAVAILABLE"),
2685
+ })
2686
+ .strict();
2687
+
2688
+ /** Tool-specific success payload contracts; none falls back to an arbitrary object. */
2689
+ const productSourceReferenceSchema = z.object({ path: text, heading: text }).strict();
2690
+ const productFileSelectionSchema = z
2691
+ .object({ include: stringArray, exclude: stringArray.optional() })
2692
+ .strict();
2693
+ const productManifestSchema = z
2694
+ .object({
2695
+ schemaVersion: z.literal(1),
2696
+ owners: z.array(
2697
+ z
2698
+ .object({
2699
+ id: text,
2700
+ label: text,
2701
+ kind: z.enum(["person", "team", "role"]),
2702
+ status: z.enum(["declared", "confirmed"]),
2703
+ confirmedBy: text.optional(),
2704
+ sources: z.array(productSourceReferenceSchema),
2705
+ })
2706
+ .strict(),
2707
+ ),
2708
+ features: z.array(
2709
+ z
2710
+ .object({
2711
+ id: text,
2712
+ title: text,
2713
+ stage: z.enum(["implemented", "planned"]),
2714
+ review: z.enum(["proposed", "confirmed"]),
2715
+ confirmedBy: text.optional(),
2716
+ intent: z.array(
2717
+ productSourceReferenceSchema.extend({ role: z.enum(["vision", "specification"]) }),
2718
+ ),
2719
+ owners: stringArray,
2720
+ code: productFileSelectionSchema,
2721
+ tests: productFileSelectionSchema,
2722
+ criteria: z.array(
2723
+ z
2724
+ .object({
2725
+ id: text,
2726
+ description: text,
2727
+ tests: stringArray,
2728
+ testNames: stringArray.optional(),
2729
+ })
2730
+ .strict(),
2731
+ ),
2732
+ dependsOn: stringArray,
2733
+ budgets: z
2734
+ .object({
2735
+ maxCognitivePerUnit: nonNegative.optional(),
2736
+ maxCognitiveTotal: nonNegative.optional(),
2737
+ maxCodeFiles: nonNegativeInt.optional(),
2738
+ maxOwners: nonNegativeInt.optional(),
2739
+ })
2740
+ .strict()
2741
+ .optional(),
2742
+ })
2743
+ .strict(),
2744
+ ),
2745
+ })
2746
+ .strict();
2747
+ const productSourceEvidenceSchema = productSourceReferenceSchema.extend({
2748
+ status: z.enum(["resolved", "missing", "ambiguous", "not_in_scan", "changed", "unreadable"]),
2749
+ contentHash: sha256Fingerprint.optional(),
2750
+ startLine: positiveInt.optional(),
2751
+ endLine: positiveInt.optional(),
2752
+ });
2753
+ const productFileEvidenceSchema = z
2754
+ .object({
2755
+ path: nonEmpty,
2756
+ status: z.enum([
2757
+ "measured",
2758
+ "not_in_scan",
2759
+ "changed",
2760
+ "unreadable",
2761
+ "unsupported",
2762
+ "partial",
2763
+ "invalid_metrics",
2764
+ ]),
2765
+ contentHash: sha256Fingerprint.optional(),
2766
+ scannedContentHash: sha256Fingerprint.optional(),
2767
+ })
2768
+ .strict();
2769
+ const productMetricReadingsSchema = z
2770
+ .object({ top_level: nonNegative, execution_unit: nonNegative, self: nonNegative })
2771
+ .strict();
2772
+ const productCodeMetricsSchema = z
2773
+ .object({
2774
+ metricsVersion: nonEmpty,
2775
+ cognitiveAlgorithm: nonEmpty,
2776
+ cognitive: productMetricReadingsSchema,
2777
+ cyclomaticClassic: productMetricReadingsSchema,
2778
+ cyclomaticExtended: productMetricReadingsSchema,
2779
+ files: nonNegativeInt,
2780
+ scopes: nonNegativeInt,
2781
+ executionUnitRoots: nonNegativeInt,
2782
+ sloc: nonNegativeInt,
2783
+ cyclomaticPerScopeBase: z.literal(1),
2784
+ })
2785
+ .strict();
2786
+ const productMeasuredSelectionSchema = z
2787
+ .object({
2788
+ complete: z.boolean(),
2789
+ files: z.array(productFileEvidenceSchema),
2790
+ metrics: productCodeMetricsSchema.nullable(),
2791
+ })
2792
+ .strict();
2793
+ const productHotspotSchema = z
2794
+ .object({
2795
+ path: nonEmpty,
2796
+ contentHash: sha256Fingerprint,
2797
+ symbol: nonEmpty,
2798
+ scopeId: nonEmpty,
2799
+ startLine: positiveInt,
2800
+ endLine: positiveInt,
2801
+ cognitive: nonNegative,
2802
+ cyclomaticClassic: nonNegative,
2803
+ cyclomaticExtended: nonNegative,
2804
+ })
2805
+ .strict();
2806
+ const productAgentHintSchema = z
2807
+ .object({
2808
+ code: nonEmpty,
2809
+ severity: z.enum(["info", "warning", "error"]),
2810
+ message: text,
2811
+ paths: stringArray,
2812
+ suggestedAction: text,
2813
+ search: z
2814
+ .object({ symbol: nonEmpty, path: nonEmpty, requireFresh: z.literal(true) })
2815
+ .strict()
2816
+ .optional(),
2817
+ })
2818
+ .strict();
2819
+ const productFeatureAlignmentSchema = z
2820
+ .object({
2821
+ id: nonEmpty,
2822
+ title: text,
2823
+ stage: z.enum(["implemented", "planned"]),
2824
+ review: z.enum(["proposed", "confirmed"]),
2825
+ status: z.enum(["evidence_consistent", "needs_review", "incomplete", "violated"]),
2826
+ intent: z.array(
2827
+ productSourceEvidenceSchema.extend({ role: z.enum(["vision", "specification"]) }),
2828
+ ),
2829
+ ownership: z.array(
2830
+ z
2831
+ .object({
2832
+ id: nonEmpty,
2833
+ label: text.optional(),
2834
+ status: z.enum(["declared", "confirmed", "missing"]),
2835
+ sources: z.array(productSourceEvidenceSchema),
2836
+ })
2837
+ .strict(),
2838
+ ),
2839
+ code: productMeasuredSelectionSchema.extend({ hotspots: z.array(productHotspotSchema) }),
2840
+ tests: productMeasuredSelectionSchema,
2841
+ testEvidence: z
2842
+ .object({
2843
+ status: z.enum(["bound_passed", "bound_failed", "not_run", "stale", "invalid"]),
2844
+ passed: nonNegativeInt,
2845
+ failed: nonNegativeInt,
2846
+ skipped: nonNegativeInt,
2847
+ proof: z.enum(["supplied_source_bound_report", "none"]),
2848
+ })
2849
+ .strict(),
2850
+ criteria: z.array(
2851
+ z
2852
+ .object({
2853
+ id: nonEmpty,
2854
+ description: text,
2855
+ matchedFiles: stringArray,
2856
+ status: z.enum(["passed", "failed", "not_run", "missing_tests", "unverified"]),
2857
+ })
2858
+ .strict(),
2859
+ ),
2860
+ dimensions: z
2861
+ .object({
2862
+ requirements: nonNegativeInt,
2863
+ owners: nonNegativeInt,
2864
+ codeFiles: nonNegativeInt,
2865
+ testFiles: nonNegativeInt,
2866
+ sharedCodeFiles: nonNegativeInt,
2867
+ declaredDependencies: nonNegativeInt,
2868
+ dependentFeatures: nonNegativeInt,
2869
+ })
2870
+ .strict(),
2871
+ dependsOn: stringArray,
2872
+ agentHints: z.array(productAgentHintSchema),
2873
+ issues: stringArray,
2874
+ })
2875
+ .strict();
2876
+ const productAlignmentReportSchema = z
2877
+ .object({
2878
+ schemaVersion: z.literal(1),
2879
+ root: nonEmpty,
2880
+ status: z.enum([
2881
+ "needs_manifest",
2882
+ "invalid_manifest",
2883
+ "evidence_consistent",
2884
+ "needs_review",
2885
+ "incomplete",
2886
+ "violated",
2887
+ ]),
2888
+ manifest: z
2889
+ .object({
2890
+ path: nonEmpty,
2891
+ status: z.enum(["missing", "loaded", "invalid", "not_in_scan", "changed", "unreadable"]),
2892
+ contentHash: sha256Fingerprint.optional(),
2893
+ })
2894
+ .strict(),
2895
+ sourceTreeDigest: sha256Fingerprint,
2896
+ inventory: z
2897
+ .object({ files: nonNegativeInt, truncated: z.boolean(), excluded: nonNegativeInt })
2898
+ .strict(),
2899
+ semanticConformance: z.literal("not_independently_proven"),
2900
+ ownershipAuthority: z.literal("manifest_declarations"),
2901
+ selection: z
2902
+ .object({
2903
+ path: nonEmpty,
2904
+ status: z.enum(["matched", "unmapped", "missing", "not_in_scan", "changed", "unreadable"]),
2905
+ matches: z.array(
2906
+ z
2907
+ .object({
2908
+ featureId: nonEmpty,
2909
+ roles: z.array(z.enum(["code", "test", "intent", "ownership"])),
2910
+ })
2911
+ .strict(),
2912
+ ),
2913
+ })
2914
+ .strict()
2915
+ .optional(),
2916
+ testRun: z
2917
+ .object({
2918
+ status: z.enum([
2919
+ "completed",
2920
+ "stale",
2921
+ "invalid_report",
2922
+ "spawn_failed",
2923
+ "timed_out",
2924
+ "output_limit",
2925
+ "cancelled",
2926
+ ]),
2927
+ directory: nonEmpty,
2928
+ receiptPath: nonEmpty,
2929
+ exitCode: integer.nullable(),
2930
+ issue: text.optional(),
2931
+ })
2932
+ .strict()
2933
+ .optional(),
2934
+ features: z.array(productFeatureAlignmentSchema),
2935
+ coverage: z
2936
+ .object({
2937
+ measuredCodeFiles: nonNegativeInt,
2938
+ mappedCodeFiles: nonNegativeInt,
2939
+ unmappedCodeFiles: stringArray,
2940
+ unmappedCodeFileCount: nonNegativeInt,
2941
+ sharedFiles: z.array(z.object({ path: nonEmpty, featureIds: stringArray }).strict()),
2942
+ mappedCodeUnion: productCodeMetricsSchema.nullable(),
2943
+ })
2944
+ .strict(),
2945
+ dependencyCycles: z.array(stringArray),
2946
+ issues: stringArray,
2947
+ limitations: stringArray,
2948
+ draft: productManifestSchema.nullable(),
2949
+ })
2950
+ .strict();
2951
+
2952
+ export const UNTANGLE_MCP_SUCCESS_DATA_SCHEMAS = {
2953
+ untangle_index: indexReportSchema,
2954
+ untangle_update: indexReportSchema,
2955
+ untangle_stop: z.object({ job: jobStatusSchema, cancellationRequested: z.boolean() }).strict(),
2956
+ untangle_watch: z.object({ job: jobStatusSchema, watching: z.boolean() }).strict(),
2957
+ untangle_status: z.object({ jobs: z.array(jobStatusSchema) }).strict(),
2958
+ untangle_health: z
2959
+ .object({ schemaVersion: z.literal(1), ok: z.boolean(), checks: z.array(checkResultSchema) })
2960
+ .strict(),
2961
+ untangle_projects: z
2962
+ .object({
2963
+ root: nonEmpty,
2964
+ repositories: z.array(repositoryConfigSchema),
2965
+ projectId: text.optional(),
2966
+ })
2967
+ .strict(),
2968
+ untangle_search: searchResultSetSchema,
2969
+ untangle_search_session: searchSessionResultSchema,
2970
+ untangle_context_list: z
2971
+ .object({ artifacts: z.array(contextArtifactSchema), configured: nonNegativeInt })
2972
+ .strict(),
2973
+ untangle_context_index: z
2974
+ .object({
2975
+ generationId: nonEmpty,
2976
+ artifacts: z.array(nonEmpty),
2977
+ indexedDocuments: nonNegativeInt,
2978
+ })
2979
+ .strict(),
2980
+ untangle_context_search: z
2981
+ .object({
2982
+ query: nonEmpty,
2983
+ results: z.array(
2984
+ z
2985
+ .object({
2986
+ artifactName: nonEmpty,
2987
+ chunkId: nonEmpty,
2988
+ path: text,
2989
+ kind: nonEmpty,
2990
+ title: text.optional(),
2991
+ score: finite,
2992
+ content: text.optional(),
2993
+ })
2994
+ .strict(),
2995
+ ),
2996
+ total: nonNegativeInt,
2997
+ })
2998
+ .strict(),
2999
+ untangle_context_remove: z
3000
+ .object({ target: nonEmpty, removed: z.boolean(), removedDocumentCount: nonNegativeInt })
3001
+ .strict(),
3002
+ untangle_graph_build: z
3003
+ .object({
3004
+ generationId: nonEmpty,
3005
+ workspaceId: nonEmpty,
3006
+ fileGraph: z.object({ nodeCount: nonNegativeInt, edgeCount: nonNegativeInt }).strict(),
3007
+ symbolGraph: z.object({ nodeCount: nonNegativeInt, edgeCount: nonNegativeInt }).strict(),
3008
+ skipped: z.array(skipLedgerEntrySchema),
3009
+ })
3010
+ .strict(),
3011
+ untangle_graph_status: graphStatusSchema,
3012
+ untangle_graph_query: boundedGraphFileViewSchema,
3013
+ untangle_graph_stats: graphStatsSchema,
3014
+ untangle_graph_cycles: graphCyclesSchema,
3015
+ untangle_graph_visualize: graphVisualizationSchema,
3016
+ untangle_graph_remove: z
3017
+ .object({ target: nonEmpty, generationId: nonEmpty, removed: z.boolean() })
3018
+ .strict(),
3019
+ untangle_symbols: symbolListSchema,
3020
+ untangle_symbol: symbolDetailSchema,
3021
+ untangle_flow: flowSchema,
3022
+ untangle_impact: impactSchema,
3023
+ untangle_cross_repo_impact: z
3024
+ .object({
3025
+ generationId: nonEmpty,
3026
+ root: symbolNodeSchema,
3027
+ impact: impactResultSchema,
3028
+ repoIds: stringArray,
3029
+ })
3030
+ .strict(),
3031
+ untangle_complexity: complexityReportSchema,
3032
+ untangle_complexity_explain: z
3033
+ .object({ symbol: symbolNodeSchema, snapshot: complexitySnapshotSchema })
3034
+ .strict(),
3035
+ untangle_hotspots: complexityReportSchema,
3036
+ untangle_complexity_diff: z
3037
+ .object({
3038
+ base: nonEmpty,
3039
+ head: nonEmpty,
3040
+ metricsVersion: nonEmpty,
3041
+ entries: z.array(metricDeltaSchema),
3042
+ warnings: stringArray,
3043
+ })
3044
+ .strict(),
3045
+ untangle_changes: z
3046
+ .object({
3047
+ base: nonEmpty,
3048
+ head: nonEmpty,
3049
+ changes: z.array(changeEventSchema),
3050
+ total: nonNegativeInt,
3051
+ })
3052
+ .strict(),
3053
+ untangle_change_search: z
3054
+ .object({ query: nonEmpty, changes: z.array(changeEventSchema), total: nonNegativeInt })
3055
+ .strict(),
3056
+ untangle_quality_gate: qualityGateSchema,
3057
+ untangle_about: aboutSchema,
3058
+ untangle_portfolio_scan: portfolioScanResultSchema,
3059
+ untangle_stack_matrix: stackMatrixResultSchema,
3060
+ untangle_who_uses: whoUsesResultSchema,
3061
+ untangle_conventions: conventionsResultSchema,
3062
+ untangle_archetypes: archetypesResultSchema,
3063
+ untangle_tech_radar: techRadarResultSchema,
3064
+ untangle_blueprint: blueprintResultSchema,
3065
+ untangle_product: productAlignmentReportSchema,
3066
+ untangle_boundaries: frontendBoundaryReportSchema,
3067
+ } as const satisfies Record<UntangleMcpToolName, z.ZodType>;
3068
+
3069
+ export type UntangleMcpSuccessDataByOperation = {
3070
+ [Name in UntangleMcpToolName]: z.output<(typeof UNTANGLE_MCP_SUCCESS_DATA_SCHEMAS)[Name]>;
3071
+ };