@untangle-code/cli 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (571) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +90 -0
  3. package/README.md +424 -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,946 @@
1
+ /**
2
+ * `untangle doctor` — die Antwort auf „warum geht das nicht?".
3
+ *
4
+ * REGEL DIESES BEFEHLS: Jeder fehlgeschlagene Test nennt GENAU EINEN
5
+ * naechsten Schritt. Nicht drei Moeglichkeiten, nicht „pruefe deine
6
+ * Konfiguration" — einen Schritt, den man tippen kann. Ein Diagnosewerkzeug,
7
+ * das eine Auswahl praesentiert, hat die Diagnose nicht zu Ende gefuehrt.
8
+ *
9
+ * `doctor` ist AUSSCHLIESSLICH LESEND. Er legt keine Collection an, laedt
10
+ * kein Modell nach und schreibt keine Datei. Das ist wichtig, weil auf einem
11
+ * Entwicklungsrechner fremde Dienste auf denselben Vorgabeports liegen
12
+ * koennen (spec.md §3.2): ein Diagnoselauf darf nichts anfassen, was ihm
13
+ * nicht gehoert.
14
+ */
15
+
16
+ import path from "node:path";
17
+
18
+ import { compareCodepoints, type EmbeddingReadinessV1 } from "@untangle-code/domain";
19
+ import { DEFAULT_MANAGED_URL, DEFAULT_NATIVE_PROBE_URL } from "@untangle-code/embeddings-ollama";
20
+
21
+ /** Nur fuer den Hinweistext im Fehlschlag — nie fuer eine Entscheidung. */
22
+ const DEFAULT_PROBE_HINT = DEFAULT_NATIVE_PROBE_URL;
23
+
24
+ /**
25
+ * Warum `doctor` eine EIGENE Frist braucht und die der Adapter nicht genuegt.
26
+ *
27
+ * Gemessen an einem ausgelasteten Ollama: die Bereitschaftsfolge des
28
+ * Embedding-Adapters wiederholt einen Zeitueberlauf mit Backoff und meldet
29
+ * den Fehlschlag erst nach ueber drei Minuten. Fuer den Indexlauf ist das
30
+ * richtig — dort ist Durchhalten mehr wert als Tempo. Fuer ein
31
+ * Diagnosewerkzeug ist es falsch: wer fragt „warum geht das nicht?", wartet
32
+ * nicht drei Minuten auf die Antwort „Ollama antwortet nicht".
33
+ *
34
+ * Also bricht `doctor` jeden Einzeltest nach dieser Frist ab — und sagt
35
+ * ausdruecklich, DASS er abgebrochen hat. Ein abgebrochener Test ist keine
36
+ * bestandene Pruefung; er wird als Fehlschlag gewertet.
37
+ */
38
+ export const DEFAULT_CHECK_TIMEOUT_MS = 15_000;
39
+
40
+ /**
41
+ * WARUM DIESE FRIST ALLEIN NICHT REICHT — und was `doctor` deshalb tut.
42
+ *
43
+ * Gemessen auf dem Abnahmerechner: ein Ollama, das ein Einbettungsmodell zum
44
+ * ERSTEN Mal laedt, braucht 31-37 s bis zur ersten Antwort; danach liegt
45
+ * dieselbe Anfrage unter einer Sekunde. Mit einer starren Frist von 15 s
46
+ * meldete `doctor` fuer einen vollkommen gesunden Dienst „Keine Antwort" —
47
+ * eine falsche Aussage ueber die Umgebung, also E4 an der Oberflaeche.
48
+ *
49
+ * Die Frist wird deshalb NICHT einfach hochgesetzt (dann wartete auch der,
50
+ * dessen Ollama gar nicht laeuft, eine Minute auf eine Auskunft, die nach
51
+ * drei Sekunden feststeht). Stattdessen prueft `doctor` nach dem Fristablauf
52
+ * nach, ob der Dienst ueberhaupt eine HTTP-Verbindung annimmt:
53
+ *
54
+ * nimmt er keine an -> „antwortet nicht", sofortiger Fehlschlag;
55
+ * antwortet er -> er lebt, also wird bis zu dieser Zusatzfrist
56
+ * weitergewartet, und der Unterschied wird BENANNT.
57
+ *
58
+ * Das Weiterwarten kostet nichts Zusaetzliches: die Bereitschaftsfolge des
59
+ * Adapters laeuft nach einem abgelaufenen Aufruferzeitlimit im Hintergrund
60
+ * weiter (`OllamaEmbeddingProvider.ensureReadyOnce` startet sie ohne das
61
+ * Signal des Aufrufers), der zweite Aufruf haengt sich an denselben Lauf.
62
+ */
63
+ export const DEFAULT_COLD_START_TIMEOUT_MS = 90_000;
64
+
65
+ /**
66
+ * Frist der Lebenszeichen-Probe. Sie beantwortet eine einzige Frage — nimmt
67
+ * der Dienst eine Verbindung an? —, und diese Antwort steht sofort fest oder
68
+ * gar nicht.
69
+ */
70
+ const LIVENESS_TIMEOUT_MS = 3_000;
71
+
72
+ /**
73
+ * Ab dieser Dauer wird der Kaltstart als Erklaerung ANGEBOTEN. Darunter steht
74
+ * nur die gemessene Zahl — eine Frist, die jemand mit `--timeout=5` selbst zu
75
+ * eng gesetzt hat, ist kein Modellladen.
76
+ */
77
+ const COLD_START_HINT_MS = 5_000;
78
+
79
+ import {
80
+ boolFlag,
81
+ enumFlag,
82
+ integerFlag,
83
+ stringFlag,
84
+ type FlagSpecs,
85
+ type ParsedArgs,
86
+ } from "../cli/args.js";
87
+ import type { Command, CommandContext } from "../cli/command.js";
88
+ import { withDeadline } from "../cli/deadline.js";
89
+ import { CliError, EXIT } from "../cli/exit.js";
90
+ import { inspectIndexFreshness } from "../cli/index-freshness.js";
91
+ import { resolveIndexState } from "../cli/index-state.js";
92
+ import { line, note } from "../cli/io.js";
93
+ import {
94
+ describeConfigIssues,
95
+ errorMessage,
96
+ loadWorkspace,
97
+ matchesStoredBackend,
98
+ storageIndexScope,
99
+ wireEmbedder,
100
+ wireStorage,
101
+ type WorkspaceView,
102
+ } from "../cli/workspace.js";
103
+
104
+ import { checkLanguages } from "./doctor.languages.js";
105
+ import { inspectLanceDirectory, selectLanceIndexProfile } from "./doctor.storage.js";
106
+
107
+ export type CheckStatus = "ok" | "warn" | "fail";
108
+
109
+ export interface CheckResult {
110
+ readonly id: string;
111
+ readonly title: string;
112
+ readonly status: CheckStatus;
113
+ readonly detail: string;
114
+ /** Bei "fail" und "warn" Pflicht: GENAU EIN naechster Schritt. */
115
+ readonly nextStep?: string;
116
+ readonly facts?: Readonly<Record<string, string | number | boolean>>;
117
+ }
118
+
119
+ const FLAGS: FlagSpecs = {
120
+ root: {
121
+ kind: "string",
122
+ placeholder: "PATH",
123
+ describe: "Project directory; configuration is discovered from here.",
124
+ defaultLabel: "current directory",
125
+ },
126
+ format: {
127
+ kind: "string",
128
+ placeholder: "text|json",
129
+ describe: "Output format.",
130
+ defaultLabel: "text",
131
+ },
132
+ "skip-embeddings": {
133
+ kind: "boolean",
134
+ describe: "Skip the Ollama check (it costs one request).",
135
+ },
136
+ timeout: {
137
+ kind: "number",
138
+ placeholder: "MS",
139
+ describe:
140
+ "Deadline per check. Expiry fails the check; for Ollama, only when the service also gives no sign of life.",
141
+ defaultLabel: String(DEFAULT_CHECK_TIMEOUT_MS),
142
+ },
143
+ "cold-start-timeout": {
144
+ kind: "number",
145
+ placeholder: "MS",
146
+ describe:
147
+ "Additional Ollama deadline when the service responds but is still loading. 0 disables the additional wait.",
148
+ defaultLabel: String(DEFAULT_COLD_START_TIMEOUT_MS),
149
+ },
150
+ };
151
+
152
+ export const doctorCommand: Command = {
153
+ spec: {
154
+ name: "doctor",
155
+ implementedBy: "commands/doctor.js",
156
+ summary: "Check configuration, embeddings, selected search storage, index state and languages",
157
+ usage: "untangle doctor [--root=<PATH>] [--format=text|json]",
158
+ details: [
159
+ "Checks configuration, Ollama, the selected search backend, this embedding profile's index, analyzer extensions and languages. LanceDB inspects the local directory and existing index without creating either; it contacts no Qdrant or Docker service. Explicit Qdrant configurations retain their service and collection checks.",
160
+ 'Language availability is measured, not assumed: each adapter parses a word to load its actual grammar. This verifies which registered languages work on this machine. ast-grep language prebuilds support five platforms; a failed fallback build can still exit 0 during installation. Languages without a loadable grammar are reported as tier "text" with a reason, never as a claimed "deep" tier.',
161
+ "The Ollama check exercises the endpoint indexing actually uses, ending with a probe embedding on /api/embed. An available /api/tags endpoint alone does not count as a pass.",
162
+ "If Ollama misses the deadline, a follow-up checks whether it accepts HTTP connections. A responding service may still be loading its model (measured cold starts took 31–37 s); the check then waits up to --cold-start-timeout. A service accepting no connection fails immediately. This distinguishes an unresponsive service from a model still loading.",
163
+ "Every failed check gives one next step.",
164
+ "The run is read-only: it creates no collection, downloads no model and writes no file. Language checks load grammars from the existing node_modules tree without fetching or building anything.",
165
+ ],
166
+ flags: FLAGS,
167
+ examples: ["untangle doctor", "untangle doctor --format=json"],
168
+ exitCodes: [
169
+ [EXIT.ok, "All checks passed (warnings allowed)."],
170
+ [EXIT.operational, "The diagnostic run itself failed."],
171
+ [EXIT.environment, "At least one check failed."],
172
+ ],
173
+ },
174
+
175
+ async run(ctx: CommandContext, args: ParsedArgs): Promise<number> {
176
+ ctx.signal.throwIfAborted();
177
+ const format = enumFlag(args, "format", ["text", "json"] as const, "text");
178
+ const cwd = stringFlag(args, "root") ?? ctx.cwd;
179
+ const timeoutMs = integerFlag(args, "timeout", DEFAULT_CHECK_TIMEOUT_MS);
180
+ const coldStartTimeoutMs = integerFlag(
181
+ args,
182
+ "cold-start-timeout",
183
+ DEFAULT_COLD_START_TIMEOUT_MS,
184
+ );
185
+ const checks: CheckResult[] = [];
186
+
187
+ // --- 1. Konfiguration ---------------------------------------------------
188
+ let view: WorkspaceView | undefined;
189
+ try {
190
+ view = loadWorkspace({ cwd, env: ctx.env, strict: false });
191
+ const errors = describeConfigIssues(view.load.errors);
192
+ const warnings = describeConfigIssues(view.load.warnings);
193
+ // WP-222: WELCHE Datei geladen wurde, statt der Datei, die `doctor`
194
+ // erwartet hat. `untangle init` schreibt `untangle.toml`; solange
195
+ // `doctor` nur nach `.untangle.json` sah, meldete es „keine
196
+ // Konfiguration" ueber eine Konfiguration, die es gerade gab, und riet
197
+ // zu `untangle init` — was soeben gelaufen war. Der Nutzer lief im
198
+ // Kreis. Jetzt nennt der Befund die Datei beim Namen.
199
+ const configFile = primaryConfigFile(view);
200
+ if (errors.length > 0) {
201
+ checks.push({
202
+ id: "config",
203
+ title: "Configuration",
204
+ status: "fail",
205
+ detail: `${String(errors.length)} errors: ${errors.join("; ")}`,
206
+ nextStep:
207
+ configFile === undefined
208
+ ? "Run `untangle init` to write a valid untangle.toml."
209
+ : `Fix the first reported error in ${configFile} and run \`untangle doctor\` again.`,
210
+ facts: configFacts(view, configFile),
211
+ });
212
+ } else if (configFile === undefined) {
213
+ // KEIN Fehler und trotzdem kein Rat zum Kreislauf: es gibt schlicht
214
+ // keine Projektkonfiguration, also gelten die Produktvorgaben. Das
215
+ // wird gesagt, nicht als „alles in Ordnung" verkauft.
216
+ checks.push({
217
+ id: "config",
218
+ title: "Configuration",
219
+ status: "warn",
220
+ detail:
221
+ "No project configuration found (neither untangle.toml nor .untangle.json). " +
222
+ "Only product defaults apply.",
223
+ nextStep: "Run `untangle init` to write a commented untangle.toml.",
224
+ facts: configFacts(view, configFile),
225
+ });
226
+ } else {
227
+ checks.push({
228
+ id: "config",
229
+ title: "Configuration",
230
+ status: warnings.length > 0 ? "warn" : "ok",
231
+ detail:
232
+ warnings.length > 0
233
+ ? `${configFile} loaded with ${String(warnings.length)} Warnung(en): ${warnings.join("; ")}`
234
+ : `${configFile} loaded and valid.`,
235
+ ...(warnings.length > 0
236
+ ? {
237
+ nextStep: `Fix the first reported warning in ${configFile}; otherwise it will be ignored.`,
238
+ }
239
+ : {}),
240
+ facts: configFacts(view, configFile),
241
+ });
242
+ }
243
+ } catch (error) {
244
+ ctx.signal.throwIfAborted();
245
+ checks.push({
246
+ id: "config",
247
+ title: "Configuration",
248
+ status: "fail",
249
+ detail: `Cannot load: ${errorMessage(error)}`,
250
+ nextStep: "Run `untangle init` to write a valid untangle.toml.",
251
+ });
252
+ }
253
+
254
+ if (view === undefined) {
255
+ return emit(ctx, checks, format);
256
+ }
257
+
258
+ // --- 2. Repositories ----------------------------------------------------
259
+ checks.push(
260
+ view.repoIds.length === 0
261
+ ? {
262
+ id: "repositories",
263
+ title: "Repositories",
264
+ status: "fail",
265
+ detail: "The configuration contains no repository, so there is nothing to index.",
266
+ nextStep: "Run `untangle init --force` in the project directory.",
267
+ }
268
+ : {
269
+ id: "repositories",
270
+ title: "Repositories",
271
+ status: "ok",
272
+ detail: `${String(view.repoIds.length)}: ${[...view.repoIds].sort(compareCodepoints).join(", ")}`,
273
+ },
274
+ );
275
+
276
+ // --- 3. Ollama ----------------------------------------------------------
277
+ if (boolFlag(args, "skip-embeddings")) {
278
+ checks.push({
279
+ id: "ollama",
280
+ title: "Ollama",
281
+ status: "warn",
282
+ detail: "Skipped (--skip-embeddings). Ollama availability was not checked.",
283
+ nextStep: "Run `untangle doctor` without --skip-embeddings to perform the check.",
284
+ });
285
+ } else {
286
+ checks.push(await checkOllama(view, ctx, timeoutMs, coldStartTimeoutMs));
287
+ ctx.signal.throwIfAborted();
288
+ }
289
+
290
+ // --- 4. Qdrant und Indexstand ------------------------------------------
291
+ const storageChecks = await checkStorage(view, ctx, timeoutMs);
292
+ ctx.signal.throwIfAborted();
293
+ for (const check of storageChecks) checks.push(check);
294
+
295
+ // --- 5. Extensions ------------------------------------------------------
296
+ checks.push(await checkExtensions(ctx));
297
+ ctx.signal.throwIfAborted();
298
+
299
+ // --- 6. Sprachen --------------------------------------------------------
300
+ checks.push(await checkLanguages());
301
+ ctx.signal.throwIfAborted();
302
+
303
+ return emit(ctx, checks, format);
304
+ },
305
+ };
306
+
307
+ /* --------------------------------------------------------------------- */
308
+
309
+ /**
310
+ * Die Konfigurationsdatei, die fuer dieses Projekt wirklich gilt — oder
311
+ * `undefined`, wenn es keine gibt.
312
+ *
313
+ * `.untangle.json` gewinnt vor `untangle.toml`, weil die Ladeordnung
314
+ * (spec.md §4.1, Ebene 4 vor 4b) genau das tut. Liegen beide da, meldet der
315
+ * Lader das als Warnung; hier wird nur die genannt, die obenauf liegt, damit
316
+ * ein naechster Schritt auf die Datei zeigt, an der eine Aenderung auch
317
+ * wirkt.
318
+ */
319
+ function primaryConfigFile(view: WorkspaceView): string | undefined {
320
+ return view.load.sources.projectConfigPath ?? view.load.sources.tomlConfigPath;
321
+ }
322
+
323
+ function configFacts(
324
+ view: WorkspaceView,
325
+ configFile: string | undefined,
326
+ ): Readonly<Record<string, string | number | boolean>> {
327
+ return {
328
+ projectConfig: configFile ?? "(no untangle.toml or .untangle.json found)",
329
+ tomlConfig: view.load.sources.tomlConfigPath ?? "(none)",
330
+ jsonConfig: view.load.sources.projectConfigPath ?? "(none)",
331
+ localConfig: view.load.sources.localConfigPath ?? "(none)",
332
+ userConfig: view.load.sources.userConfigPath ?? "(none)",
333
+ workspaceId: view.workspaceId,
334
+ embeddingProfileId: view.profileId,
335
+ repositories: view.repoIds.length,
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Ergebnis der Lebenszeichen-Probe. `alive` heisst AUSSCHLIESSLICH: der Dienst
341
+ * hat eine HTTP-Verbindung angenommen und geantwortet. Es heisst NICHT, dass
342
+ * das Modell geladen oder /api/embed brauchbar ist — genau diese Verwechslung
343
+ * ist der Befund WP-228, und sie wird hier nicht wiederholt.
344
+ */
345
+ interface LivenessProbeV1 {
346
+ readonly url: string;
347
+ readonly alive: boolean;
348
+ /** Wortlaut fuer die Ausgabe: entweder der HTTP-Status oder der Grund des Fehlschlags. */
349
+ readonly detail: string;
350
+ readonly elapsedMs: number;
351
+ }
352
+
353
+ /**
354
+ * Die Adressen, an denen der Adapter der Reihe nach sucht (spec.md §3.1,
355
+ * §4.5). Nachgebaut, nicht geraten: `auto` fasst zuerst den nativen
356
+ * Probe-Endpunkt an und faellt dann auf den verwalteten zurueck; ein
357
+ * gesetztes `url` gilt in `auto` als verwalteter Ersatz.
358
+ */
359
+ function livenessCandidates(view: WorkspaceView): readonly string[] {
360
+ const embeddings = view.config.embeddings;
361
+ const raw: readonly (string | undefined)[] =
362
+ embeddings.mode === "external"
363
+ ? [embeddings.url]
364
+ : embeddings.mode === "managed"
365
+ ? [embeddings.url ?? DEFAULT_MANAGED_URL]
366
+ : [DEFAULT_NATIVE_PROBE_URL, embeddings.url ?? DEFAULT_MANAGED_URL];
367
+ const seen = new Set<string>();
368
+ const candidates: string[] = [];
369
+ for (const candidate of raw) {
370
+ if (candidate === undefined || seen.has(candidate)) continue;
371
+ seen.add(candidate);
372
+ candidates.push(candidate);
373
+ }
374
+ return candidates;
375
+ }
376
+
377
+ /** `${base}/api/embed`, ohne doppelten Schraegstrich. */
378
+ function embedEndpointOf(baseUrl: string): string {
379
+ return `${baseUrl.replace(/\/+$/u, "")}/api/embed`;
380
+ }
381
+
382
+ /**
383
+ * EIN GET auf die Basis-URL, mit eigener kurzer Frist. Rein lesend wie der
384
+ * ganze Befehl: es laedt kein Modell und legt nichts an. Jede HTTP-Antwort —
385
+ * auch 404, auch 500 — beweist, dass der Dienst lebt; nur eine abgelehnte,
386
+ * nicht aufloesbare oder stumme Verbindung beweist das Gegenteil.
387
+ */
388
+ async function probeLiveness(view: WorkspaceView, ctx: CommandContext): Promise<LivenessProbeV1> {
389
+ const candidates = livenessCandidates(view);
390
+ let last: LivenessProbeV1 = {
391
+ url: DEFAULT_PROBE_HINT,
392
+ alive: false,
393
+ detail: "the configuration provides no endpoint to probe",
394
+ elapsedMs: 0,
395
+ };
396
+ for (const url of candidates) {
397
+ const startedAt = performance.now();
398
+ const outcome = await withDeadline<number>(
399
+ async (signal) => {
400
+ const response = await fetch(url, { method: "GET", signal });
401
+ // Den Koerper NICHT lesen, aber schliessen: sonst haelt der Socket.
402
+ await response.body?.cancel();
403
+ return response.status;
404
+ },
405
+ LIVENESS_TIMEOUT_MS,
406
+ ctx.signal,
407
+ );
408
+ const elapsedMs = Math.round(performance.now() - startedAt);
409
+ ctx.signal.throwIfAborted();
410
+ if (outcome.ok) {
411
+ return {
412
+ url,
413
+ alive: true,
414
+ elapsedMs,
415
+ detail: `HTTP ${String(outcome.value)} after ${String(elapsedMs)}ms`,
416
+ };
417
+ }
418
+ last = {
419
+ url,
420
+ alive: false,
421
+ elapsedMs,
422
+ detail: outcome.timedOut
423
+ ? `no response within ${String(LIVENESS_TIMEOUT_MS)}ms`
424
+ : errorMessage(outcome.error),
425
+ };
426
+ }
427
+ return last;
428
+ }
429
+
430
+ /**
431
+ * Der Ollama-Test in zwei Stufen.
432
+ *
433
+ * Stufe 1 ist die alte: Bereitschaftsfolge mit der Frist der Oberflaeche.
434
+ * Laeuft sie ab, folgt Stufe 2 — die Unterscheidung, die vorher fehlte.
435
+ * Erst ihr Ergebnis entscheidet, ob hier „antwortet nicht" oder „laedt noch"
436
+ * steht, und ob ueberhaupt weitergewartet wird.
437
+ */
438
+ async function checkOllama(
439
+ view: WorkspaceView,
440
+ ctx: CommandContext,
441
+ timeoutMs: number,
442
+ coldStartTimeoutMs: number,
443
+ ): Promise<CheckResult> {
444
+ const embedder = wireEmbedder(view);
445
+ const startedAt = performance.now();
446
+ const first = await withDeadline<EmbeddingReadinessV1>(
447
+ (signal) => embedder.ensureReady({ signal }),
448
+ timeoutMs,
449
+ ctx.signal,
450
+ );
451
+ ctx.signal.throwIfAborted();
452
+ if (first.ok) {
453
+ return describeReadiness(
454
+ view,
455
+ first.value,
456
+ Math.round(performance.now() - startedAt),
457
+ timeoutMs,
458
+ );
459
+ }
460
+ if (!first.timedOut) {
461
+ return notReady(view, first.error);
462
+ }
463
+
464
+ // --- Stufe 2: lebt der Dienst ueberhaupt? -----------------------------
465
+ const liveness = await probeLiveness(view, ctx);
466
+ if (!liveness.alive) {
467
+ return {
468
+ id: "ollama",
469
+ title: "Ollama",
470
+ status: "fail",
471
+ detail:
472
+ `No response within ${String(timeoutMs)}ms; the follow-up identifies why: ` +
473
+ `no sign of life. A simple GET to ${liveness.url} received no ` +
474
+ `usable response (${liveness.detail}). The service is not responding; this differs from ` +
475
+ "a responding service that is still loading.",
476
+ nextStep: `Start Ollama and fetch the model: \`ollama pull ${view.config.embeddings.model}\`.`,
477
+ facts: { endpoint: liveness.url, lebenszeichen: false, fristMs: timeoutMs },
478
+ };
479
+ }
480
+
481
+ if (coldStartTimeoutMs <= 0) {
482
+ return stillLoading(view, liveness, Math.round(performance.now() - startedAt), 0);
483
+ }
484
+
485
+ // Der Dienst lebt. Weiterwarten ist hier keine Geduldsprobe, sondern die
486
+ // einzige Art, den Kaltstart vom Ausfall zu trennen — und der laufende
487
+ // Ladevorgang wird dabei nicht verworfen (siehe DEFAULT_COLD_START_TIMEOUT_MS).
488
+ note(
489
+ ctx.io,
490
+ `Ollama at ${liveness.url} responds (${liveness.detail}), but the readiness sequence ` +
491
+ `is still running. This is consistent with a cold start; the first model load took ` +
492
+ `31–37 s in measurements. Waiting up to ${String(coldStartTimeoutMs)}ms more; ` +
493
+ "`--cold-start-timeout=0` disables this wait.",
494
+ );
495
+ const second = await withDeadline<EmbeddingReadinessV1>(
496
+ (signal) => embedder.ensureReady({ signal }),
497
+ coldStartTimeoutMs,
498
+ ctx.signal,
499
+ );
500
+ ctx.signal.throwIfAborted();
501
+ const elapsedMs = Math.round(performance.now() - startedAt);
502
+ if (second.ok) return describeReadiness(view, second.value, elapsedMs, timeoutMs);
503
+ if (!second.timedOut) return notReady(view, second.error);
504
+ return stillLoading(view, liveness, elapsedMs, coldStartTimeoutMs);
505
+ }
506
+
507
+ function notReady(view: WorkspaceView, error: unknown): CheckResult {
508
+ return {
509
+ id: "ollama",
510
+ title: "Ollama",
511
+ status: "fail",
512
+ detail: `Not ready: ${errorMessage(error)}`,
513
+ nextStep: `Start Ollama and fetch the model: \`ollama pull ${view.config.embeddings.model}\`.`,
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Der Dienst antwortet, die Bereitschaftsfolge aber nicht. Das wird auch so
519
+ * gesagt: die Ursache ist damit EINGEGRENZT, nicht geklaert — und ein
520
+ * abgebrochener Test bleibt ein Fehlschlag, kein bestandener Test.
521
+ */
522
+ function stillLoading(
523
+ view: WorkspaceView,
524
+ liveness: LivenessProbeV1,
525
+ elapsedMs: number,
526
+ coldStartTimeoutMs: number,
527
+ ): CheckResult {
528
+ return {
529
+ id: "ollama",
530
+ title: "Ollama",
531
+ status: "fail",
532
+ detail:
533
+ `The service at ${liveness.url} RESPONDS (${liveness.detail}), so it is running. ` +
534
+ `However, even after ${String(elapsedMs)}ms it returned no probe embedding. ` +
535
+ "The service is responding but may still be loading or stalled. " +
536
+ "The complete readiness sequence is checked " +
537
+ "(spec.md §9.1), including a probe embedding on /api/embed, the endpoint " +
538
+ "indexing actually uses; an available /api/tags endpoint does not prove this readiness. " +
539
+ "The check was cancelled and therefore did not pass.",
540
+ nextStep:
541
+ coldStartTimeoutMs <= 0
542
+ ? `Allow a cold start: \`untangle doctor --cold-start-timeout=${String(DEFAULT_COLD_START_TIMEOUT_MS)}\`.`
543
+ : `Probe the actual endpoint: \`curl -m 120 -X POST ${embedEndpointOf(liveness.url)} ` +
544
+ `-d '{"model":"${view.config.embeddings.model}","input":["test"]}'\`; if it still gives no response, ` +
545
+ "use `--embedder=offline` for indexing and search in the meantime.",
546
+ facts: {
547
+ endpoint: liveness.url,
548
+ lebenszeichen: true,
549
+ model: view.config.embeddings.model,
550
+ dauerMs: elapsedMs,
551
+ },
552
+ };
553
+ }
554
+
555
+ /**
556
+ * Die bestandene Pruefung — mit der GEMESSENEN Dauer. Hat sie die erste Frist
557
+ * gerissen, steht das dabei: derselbe Lauf ein zweites Mal sieht anders aus,
558
+ * und wer das nicht weiss, haelt den naechsten schnellen Lauf fuer eine
559
+ * Aenderung, die es nie gab.
560
+ */
561
+ function describeReadiness(
562
+ view: WorkspaceView,
563
+ readiness: EmbeddingReadinessV1,
564
+ elapsedMs: number,
565
+ firstBudgetMs: number,
566
+ ): CheckResult {
567
+ if (readiness.dimensions !== view.config.embeddings.dimensions) {
568
+ return {
569
+ id: "ollama",
570
+ title: "Ollama",
571
+ status: "fail",
572
+ detail:
573
+ `Model "${readiness.model}" returns ${String(readiness.dimensions)} dimensions, ` +
574
+ `the configuration specifies ${String(view.config.embeddings.dimensions)}. An index with this ` +
575
+ "mismatch would be unusable (spec.md §4.8).",
576
+ nextStep: `Set \`embeddings.dimensions\` in ${primaryConfigFile(view) ?? "untangle.toml"} to ${String(readiness.dimensions)}.`,
577
+ facts: {
578
+ endpoint: readiness.endpoint,
579
+ model: readiness.model,
580
+ dimensions: readiness.dimensions,
581
+ dauerMs: elapsedMs,
582
+ },
583
+ };
584
+ }
585
+
586
+ const overBudget = elapsedMs > firstBudgetMs;
587
+ // Die Kaltstart-Erklaerung wird nur angeboten, wo sie etwas erklaert. Eine
588
+ // gerissene Frist von 5ms ist keine Modellladung, und sie so zu nennen
589
+ // waere dieselbe Sorte Behauptung, gegen die dieser ganze Befund geht.
590
+ const cold = elapsedMs >= COLD_START_HINT_MS;
591
+ return {
592
+ id: "ollama",
593
+ title: "Ollama",
594
+ status: "ok",
595
+ detail: overBudget
596
+ ? `Ready: ${readiness.model} mit ${String(readiness.dimensions)} dimensions, but only ` +
597
+ `after ${String(elapsedMs)}ms, later than the initial deadline of ` +
598
+ `${String(firstBudgetMs)}ms. The service remained reachable and completed the request.` +
599
+ (cold
600
+ ? " The first model load typically takes this long; a second run " +
601
+ "typically answers in a fraction of that time."
602
+ : "")
603
+ : `Ready: ${readiness.model} mit ${String(readiness.dimensions)} dimensions (${String(elapsedMs)}ms).`,
604
+ facts: {
605
+ endpoint: readiness.endpoint,
606
+ model: readiness.model,
607
+ dimensions: readiness.dimensions,
608
+ probedAt: readiness.probedAt,
609
+ dauerMs: elapsedMs,
610
+ ersteFristGerissen: overBudget,
611
+ },
612
+ };
613
+ }
614
+
615
+ async function checkStorage(
616
+ view: WorkspaceView,
617
+ ctx: CommandContext,
618
+ timeoutMs: number,
619
+ ): Promise<CheckResult[]> {
620
+ let wiring;
621
+ try {
622
+ wiring = wireStorage(view, ctx.env);
623
+ } catch (error) {
624
+ // WP-222: `wireStorage` setzt die gemeinsame Regel aus
625
+ // `cli/vector-store-policy.ts` durch — dieselbe, an die sich `init`
626
+ // haelt. Lehnt sie einen Speicher ab, uebernimmt `doctor` ihre
627
+ // Begruendung und ihren naechsten Schritt WOERTLICH, statt einen eigenen
628
+ // zu erfinden. Genau dieses Erfinden war der Widerspruch: `init`
629
+ // verweigerte den Standardport einer fremden Instanz unter Berufung auf
630
+ // spec.md §3.2, waehrend `doctor` dieselbe Instanz als "[OK ] Qdrant"
631
+ // meldete und empfahl, dorthin zu indizieren.
632
+ return [
633
+ {
634
+ id: view.config.storage.backend,
635
+ title: view.config.storage.backend === "lancedb" ? "LanceDB" : "Qdrant",
636
+ status: "fail",
637
+ detail:
638
+ error instanceof CliError
639
+ ? error.message
640
+ : `Cannot resolve configuration: ${errorMessage(error)}`,
641
+ nextStep:
642
+ error instanceof CliError && error.nextStep !== undefined
643
+ ? error.nextStep
644
+ : view.config.storage.backend === "lancedb"
645
+ ? "Set storage.path to an accessible local directory."
646
+ : `Set \`qdrant.url\` in ${primaryConfigFile(view) ?? "untangle.toml"} to your own instance's address.`,
647
+ },
648
+ ];
649
+ }
650
+
651
+ const local =
652
+ wiring.backend === "lancedb"
653
+ ? await inspectLanceDirectory(wiring.storagePath!, ctx.signal)
654
+ : undefined;
655
+ if (local !== undefined && !local.exists) {
656
+ return local.check.status === "fail"
657
+ ? [local.check]
658
+ : [
659
+ local.check,
660
+ {
661
+ id: "index",
662
+ title: "Index state",
663
+ status: "fail",
664
+ detail: `No local index exists for this embedding profile at ${wiring.storagePath}. Nothing is indexed.`,
665
+ nextStep:
666
+ "Run `untangle index` or `untangle index --embedder=offline` to create the local index.",
667
+ facts: {
668
+ backend: wiring.backend,
669
+ path: wiring.storagePath!,
670
+ collection: wiring.codeCollection,
671
+ },
672
+ },
673
+ ];
674
+ }
675
+
676
+ let indexView = view;
677
+ let collection = wiring.codeCollection;
678
+ let embedder: "service" | "offline" = "service";
679
+ if (local !== undefined) {
680
+ const current = await withDeadline(
681
+ () =>
682
+ resolveIndexState(
683
+ [
684
+ view.root,
685
+ ...(view.config.repositories ?? []).map((repo) => path.resolve(view.root, repo.path)),
686
+ ],
687
+ view.workspaceId,
688
+ storageIndexScope(wiring),
689
+ ),
690
+ timeoutMs,
691
+ ctx.signal,
692
+ );
693
+ ctx.signal.throwIfAborted();
694
+ if (!current.ok)
695
+ return [
696
+ local.check,
697
+ {
698
+ id: "index-profile",
699
+ title: "Current local index profile",
700
+ status: "fail",
701
+ detail: `Cannot inspect the current local profile: ${errorMessage(current.error)}`,
702
+ nextStep: "Rerun `untangle doctor` with a sufficient --timeout.",
703
+ },
704
+ ];
705
+ const selected = selectLanceIndexProfile(view, wiring, current.value);
706
+ if ("check" in selected) return [local.check, selected.check];
707
+ indexView = selected.view;
708
+ collection = selected.collection;
709
+ embedder = selected.embedder;
710
+ }
711
+
712
+ const probe = await withDeadline<boolean>(
713
+ (signal) => wiring.vectorStore.collectionExists(collection, { signal }),
714
+ timeoutMs,
715
+ ctx.signal,
716
+ );
717
+ ctx.signal.throwIfAborted();
718
+ if (!probe.ok) {
719
+ return [
720
+ {
721
+ id: wiring.backend,
722
+ title: wiring.backend === "lancedb" ? "LanceDB" : "Qdrant",
723
+ status: "fail",
724
+ detail: probe.timedOut
725
+ ? `${wiring.storeUrl} did not answer within ${String(timeoutMs)}ms; the check was cancelled.`
726
+ : `${wiring.storeUrl} could not be read: ${errorMessage(probe.error)}`,
727
+ nextStep:
728
+ wiring.backend === "lancedb"
729
+ ? "Check the local storage directory permissions and reported LanceDB error."
730
+ : `Start an owned Qdrant instance on ${wiring.storeUrl} with its own volume; a foreign instance on another port is not a substitute (spec.md §3.2).`,
731
+ facts: { backend: wiring.backend, url: wiring.storeUrl, collection },
732
+ },
733
+ ];
734
+ }
735
+ const exists = probe.value;
736
+
737
+ const reachable: CheckResult = local?.check ?? {
738
+ id: "qdrant",
739
+ title: "Qdrant",
740
+ status: "ok",
741
+ // Die Herkunft der Adresse steht dabei, nicht nur die Adresse. Ein
742
+ // "[OK ] Qdrant" ohne die Angabe, WESSEN Qdrant, war der halbe Befund.
743
+ detail: `Reachable at ${wiring.storeUrl} — ${wiring.decision.reason}.`,
744
+ facts: {
745
+ url: wiring.storeUrl,
746
+ herkunft: wiring.decision.origin,
747
+ collectionPrefix: wiring.collectionPrefix,
748
+ },
749
+ };
750
+
751
+ if (!exists) {
752
+ return [
753
+ reachable,
754
+ {
755
+ id: "index",
756
+ title: "Index state",
757
+ status: "fail",
758
+ detail:
759
+ `Collection "${collection}" for this embedding profile does not exist. ` +
760
+ "Nothing is indexed; search would inspect no data.",
761
+ // Frueher stand hier „Achtung: dieser Befehl ist noch nicht
762
+ // verfuegbar". Das war seit Welle 5 falsch — `index` ist gebaut und
763
+ // laeuft. Ein Diagnosewerkzeug, das ueber den eigenen Befehlssatz
764
+ // Veraltetes behauptet, ist genau die E4-Klasse an der Oberflaeche:
765
+ // eine Aussage, die nichts mehr deckt.
766
+ nextStep: "Run `untangle index` to create and populate the collection.",
767
+ facts: {
768
+ collection,
769
+ ...(local === undefined ? {} : { embeddingProfileId: indexView.profileId, embedder }),
770
+ },
771
+ },
772
+ ];
773
+ }
774
+
775
+ let points = -1;
776
+ const counted = await withDeadline<number>(
777
+ (signal) => wiring.vectorStore.countByFilter(collection, {}, { signal }),
778
+ timeoutMs,
779
+ ctx.signal,
780
+ );
781
+ ctx.signal.throwIfAborted();
782
+ if (counted.ok) {
783
+ points = counted.value;
784
+ } else {
785
+ // Die Zaehlung ist eine Zusatzangabe, kein Test. Schlaegt sie fehl, bleibt
786
+ // der Indexstand trotzdem „vorhanden" — verschwiegen wird das nicht: die
787
+ // Zahl steht dann als -1 in den Fakten.
788
+ }
789
+
790
+ const freshnessProbe = await withDeadline(
791
+ async (signal) => {
792
+ const resolved = await resolveIndexState(
793
+ [
794
+ view.root,
795
+ ...(view.config.repositories ?? []).map((repo) => path.resolve(view.root, repo.path)),
796
+ ],
797
+ view.workspaceId,
798
+ storageIndexScope(wiring),
799
+ );
800
+ const states =
801
+ resolved.kind === "resolved" || resolved.kind === "ambiguous"
802
+ ? resolved.all.filter(
803
+ ({ state, current }) =>
804
+ current &&
805
+ state.embeddingProfileId === indexView.profileId &&
806
+ state.collection === collection &&
807
+ matchesStoredBackend(state, wiring),
808
+ )
809
+ : [];
810
+ return inspectIndexFreshness(indexView, states, { signal, expectedRepoIds: view.repoIds });
811
+ },
812
+ timeoutMs,
813
+ ctx.signal,
814
+ );
815
+ ctx.signal.throwIfAborted();
816
+ const freshness = freshnessProbe.ok ? freshnessProbe.value : undefined;
817
+ const freshnessStatus = freshness?.status ?? "unknown";
818
+
819
+ return [
820
+ reachable,
821
+ {
822
+ id: "index",
823
+ title: "Index state",
824
+ status: points === 0 ? "warn" : "ok",
825
+ detail:
826
+ points < 0
827
+ ? `Collection "${collection}" exists; point count could not be queried.`
828
+ : `Collection "${collection}" with ${String(points)} points.${embedder === "offline" ? " Current profile: offline; vectors carry no semantics." : ""}`,
829
+ ...(points === 0
830
+ ? {
831
+ nextStep: "The collection is empty; run `untangle index` to populate it.",
832
+ }
833
+ : {}),
834
+ facts: {
835
+ collection,
836
+ points,
837
+ ...(local === undefined ? {} : { embeddingProfileId: indexView.profileId, embedder }),
838
+ },
839
+ },
840
+ {
841
+ id: "index-freshness",
842
+ title: "Index freshness",
843
+ status:
844
+ freshnessStatus === "fresh" ? "ok" : freshnessStatus === "incompatible" ? "fail" : "warn",
845
+ detail: `Content hash check: ${freshnessStatus}. ${freshness?.repositories.map((entry) => `${entry.repoId}/${entry.refId}: ${entry.reason}`).join("; ") ?? "A full source check is unavailable."}`,
846
+ ...(freshnessStatus === "fresh"
847
+ ? {}
848
+ : {
849
+ nextStep: "Run `untangle index` or search with `untangle query <text> --refresh`.",
850
+ }),
851
+ facts: {
852
+ status: freshnessStatus,
853
+ ...(freshness === undefined
854
+ ? {}
855
+ : {
856
+ checkedAt: freshness.checkedAt,
857
+ repositories: freshness.repositories
858
+ .map((entry) => `${entry.repoId}/${entry.refId}: ${entry.reason}`)
859
+ .join("; "),
860
+ }),
861
+ },
862
+ },
863
+ ];
864
+ }
865
+
866
+ async function checkExtensions(ctx: CommandContext): Promise<CheckResult> {
867
+ const { discoverExtensions, resolveExtensionRoots } =
868
+ await import("@untangle-code/extension-runtime");
869
+ const { LocalFileSystem } = await import("@untangle-code/storage-local");
870
+ const os = await import("node:os");
871
+
872
+ const roots = resolveExtensionRoots({
873
+ homeDir: os.homedir(),
874
+ platform: process.platform,
875
+ env: ctx.env,
876
+ });
877
+ try {
878
+ const fs = new LocalFileSystem(roots);
879
+ const result = await discoverExtensions(fs, { roots, signal: ctx.signal });
880
+ const rejected = result.rejected.length;
881
+ return {
882
+ id: "extensions",
883
+ title: "Analyzer-Extensions",
884
+ status: rejected > 0 ? "warn" : "ok",
885
+ detail:
886
+ `${String(result.extensions.length)} loaded, ${String(rejected)} rejected, ` +
887
+ `${String(result.ignored.length)} explicitly skipped.`,
888
+ ...(rejected > 0
889
+ ? {
890
+ nextStep: "Run `untangle extensions` for each rejected manifest's reason.",
891
+ }
892
+ : {}),
893
+ facts: { roots: roots.length },
894
+ };
895
+ } catch (error) {
896
+ ctx.signal.throwIfAborted();
897
+ return {
898
+ id: "extensions",
899
+ title: "Analyzer-Extensions",
900
+ status: "warn",
901
+ detail: `Search unavailable: ${errorMessage(error)}`,
902
+ nextStep: "Run `untangle extensions` to see which roots were searched.",
903
+ };
904
+ }
905
+ }
906
+
907
+ /* --------------------------------------------------------------------- */
908
+
909
+ function symbolFor(status: CheckStatus): string {
910
+ return status === "ok" ? "OK " : status === "warn" ? "WARN" : "FAIL";
911
+ }
912
+
913
+ function emit(
914
+ ctx: CommandContext,
915
+ checks: readonly CheckResult[],
916
+ format: "text" | "json",
917
+ ): number {
918
+ const failed = checks.filter((check) => check.status === "fail");
919
+
920
+ if (format === "json") {
921
+ line(ctx.io, JSON.stringify({ schemaVersion: 1, ok: failed.length === 0, checks }, null, 2));
922
+ return failed.length === 0 ? EXIT.ok : EXIT.environment;
923
+ }
924
+
925
+ for (const check of checks) {
926
+ line(ctx.io, `[${symbolFor(check.status)}] ${check.title}`);
927
+ line(ctx.io, ` ${check.detail}`);
928
+ if (check.facts !== undefined) {
929
+ for (const key of Object.keys(check.facts).sort(compareCodepoints)) {
930
+ line(ctx.io, ` · ${key}: ${String(check.facts[key])}`);
931
+ }
932
+ }
933
+ if (check.nextStep !== undefined) {
934
+ line(ctx.io, ` -> ${check.nextStep}`);
935
+ }
936
+ line(ctx.io);
937
+ }
938
+
939
+ if (failed.length === 0) {
940
+ note(ctx.io, "All checks passed.");
941
+ return EXIT.ok;
942
+ }
943
+ note(ctx.io, `${String(failed.length)} check(s) failed.`);
944
+ note(ctx.io, `Next step: ${(failed[0] as CheckResult).nextStep ?? "see above"}`);
945
+ return EXIT.environment;
946
+ }