@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,1032 @@
1
+ /**
2
+ * `untangle query` — der Befehl, um den es geht.
3
+ *
4
+ * Er verdrahtet @untangle-code/search und macht dabei ZWEI Dinge unter EINER
5
+ * Oberflaeche sichtbar (Nutzerwunsch A4):
6
+ *
7
+ * - die SEMANTISCHE Suche (hybrid aus dichtem Vektor, BM25 und exaktem
8
+ * Symbolnamen, fusioniert per RRF), und
9
+ * - die GREP-artige Suche (Literal oder regulaerer Ausdruck) mit
10
+ * Kontextzeilen, Pfad-Ein-/Ausschluss und Trefferbegrenzung.
11
+ *
12
+ * `--explain` ist kein Debug-Anhang, sondern der Kern von vision.md §1d: die
13
+ * Score-Zerlegung beantwortet „warum steht das oben?" — und damit auch die
14
+ * Gegenfrage „warum steht der richtige Treffer NICHT oben?".
15
+ *
16
+ * WICHTIG UND EHRLICH: Gesucht wird im INDEX, nicht auf der Platte. Solange
17
+ * `untangle index` fehlt (siehe dort), ist der Index leer. Dieser Befehl
18
+ * behauptet dann nicht „nichts gefunden", sondern nennt den fehlenden Index
19
+ * als Grund und endet mit Exit-Code 4.
20
+ */
21
+
22
+ import path from "node:path";
23
+
24
+ import { OfflineEmbeddingProvider } from "@untangle-code/core";
25
+ import {
26
+ compareCodepoints,
27
+ isUntangleError,
28
+ embeddingProfileShortId,
29
+ INDEX_CONTEXT_INCOMPLETE_WARNING_CODE,
30
+ uniqueSortedByCodepoints,
31
+ type IndexFreshnessV1,
32
+ type SearchHitV1,
33
+ type SearchResultSetV1,
34
+ type SkipLedgerEntryV1,
35
+ } from "@untangle-code/domain";
36
+ import {
37
+ COMPLEXITY_BIASES,
38
+ GREP_SORTS,
39
+ NO_SUFFICIENT_MATCH_WARNING_CODE,
40
+ OUTPUT_FORMATS,
41
+ SEARCH_ABSTENTIONS,
42
+ SEARCH_MODES,
43
+ SEARCH_SORTS,
44
+ formatGrepResultSet,
45
+ formatHitAsText,
46
+ formatSearchGraphRelationsAsText,
47
+ formatSearchResultSet,
48
+ type ComplexityBias,
49
+ type GrepRequestV1,
50
+ type GrepResultSetV1,
51
+ type GrepSort,
52
+ type OutputFormat,
53
+ type SearchAbstention,
54
+ type SearchMode,
55
+ type SearchSort,
56
+ type UntangleSearchRequestV1,
57
+ } from "@untangle-code/search";
58
+ import { codeCollectionName } from "@untangle-code/storage-qdrant";
59
+
60
+ import {
61
+ boolFlag,
62
+ enumFlag,
63
+ integerFlag,
64
+ listFlag,
65
+ numberFlag,
66
+ stringFlag,
67
+ type FlagSpecs,
68
+ type ParsedArgs,
69
+ } from "../cli/args.js";
70
+ import type { Command, CommandContext } from "../cli/command.js";
71
+ import { withDeadline } from "../cli/deadline.js";
72
+ import { EXIT, EnvironmentError, OperationalError, UsageError } from "../cli/exit.js";
73
+ import { inspectIndexFreshness } from "../cli/index-freshness.js";
74
+ import { refreshWorkspaceIndex } from "../cli/index-refresh.js";
75
+ import {
76
+ resolveIndexState,
77
+ type EmbedderChoice,
78
+ type FoundIndexState,
79
+ type IndexStateResolution,
80
+ } from "../cli/index-state.js";
81
+ import { line, note } from "../cli/io.js";
82
+ import { createSearchGraphEnrichment } from "../cli/search-graph-enrichment.js";
83
+ import { systemClock } from "../cli/system.js";
84
+ import {
85
+ errorMessage,
86
+ loadWorkspace,
87
+ wireEmbedder,
88
+ wireSearchEngine,
89
+ wireStorage,
90
+ matchesStoredBackend,
91
+ storageIndexScope,
92
+ } from "../cli/workspace.js";
93
+
94
+ /**
95
+ * Frist der ganzen Anfrage.
96
+ *
97
+ * Includes retrieval and optional graph enrichment. Semantic/hybrid search
98
+ * also computes a query embedding, which can require loading a service model.
99
+ * The finite deadline keeps an unresponsive terminal from hanging indefinitely.
100
+ */
101
+ export const DEFAULT_QUERY_TIMEOUT_MS = 60_000;
102
+
103
+ const FLAGS: FlagSpecs = {
104
+ embedder: {
105
+ kind: "string",
106
+ placeholder: "auto|service|offline",
107
+ describe:
108
+ "auto selects the last index run's profile; service forces the configured service, offline selects the locally built collection.",
109
+ defaultLabel: "auto",
110
+ },
111
+ root: {
112
+ kind: "string",
113
+ placeholder: "PATH",
114
+ describe: "Project directory; configuration is discovered from here.",
115
+ defaultLabel: "current directory",
116
+ },
117
+ format: {
118
+ kind: "string",
119
+ placeholder: OUTPUT_FORMATS.join("|"),
120
+ describe: "Output format. jsonl is streamable; json contains the complete result.",
121
+ defaultLabel: "text",
122
+ },
123
+ limit: { kind: "number", alias: "n", placeholder: "N", describe: "Number of results." },
124
+ explain: {
125
+ kind: "boolean",
126
+ describe: "Show each result's score breakdown (vision.md §1d).",
127
+ },
128
+ mode: {
129
+ kind: "string",
130
+ placeholder: SEARCH_MODES.join("|"),
131
+ describe: "Independent retrieval and ranking paths: lexical/symbol require no embedding.",
132
+ defaultLabel: "hybrid",
133
+ },
134
+ abstention: {
135
+ kind: "string",
136
+ placeholder: SEARCH_ABSTENTIONS.join("|"),
137
+ describe:
138
+ "Behavior with insufficient evidence: strict returns no hits; evidence returns hits with the verdict.",
139
+ defaultLabel: "strict",
140
+ },
141
+ sort: {
142
+ kind: "string",
143
+ placeholder: SEARCH_SORTS.join("|"),
144
+ describe: "Result sort key.",
145
+ defaultLabel: "relevance",
146
+ },
147
+ content: { kind: "boolean", describe: "Include chunk content (token-intensive)." },
148
+ linked: {
149
+ kind: "boolean",
150
+ describe: "Direct, unambiguously resolved relationships from the persisted symbol graph.",
151
+ },
152
+ "min-score": {
153
+ kind: "number",
154
+ placeholder: "X",
155
+ describe: "Discard results below this score.",
156
+ },
157
+ "complexity-bias": {
158
+ kind: "string",
159
+ placeholder: COMPLEXITY_BIASES.join("|"),
160
+ describe: "Prefer simpler or more complex results (vision.md §1b).",
161
+ defaultLabel: "none",
162
+ },
163
+ path: {
164
+ kind: "list",
165
+ placeholder: "GLOB",
166
+ describe: "Include these paths only. Repeatable.",
167
+ },
168
+ exclude: {
169
+ kind: "list",
170
+ placeholder: "GLOB",
171
+ describe: "Exclude these paths. Repeatable.",
172
+ },
173
+ lang: { kind: "list", placeholder: "ID", describe: "Include these languages only. Repeatable." },
174
+ repo: {
175
+ kind: "list",
176
+ placeholder: "ID",
177
+ describe: "Include these repositories only. Repeatable.",
178
+ },
179
+ ref: { kind: "list", placeholder: "ID", describe: "Include these refs only. Repeatable." },
180
+ symbol: { kind: "list", placeholder: "NAME", describe: "Exact symbol names. Repeatable." },
181
+ workspace: { kind: "string", placeholder: "ID", describe: "Override workspace ID." },
182
+ timeout: {
183
+ kind: "number",
184
+ placeholder: "MS",
185
+ describe: "Deadline for the entire query. Expiry makes the query fail.",
186
+ defaultLabel: String(DEFAULT_QUERY_TIMEOUT_MS),
187
+ },
188
+ refresh: {
189
+ kind: "boolean",
190
+ describe: "Refresh stale checked-out refs before searching; progress goes to stderr.",
191
+ },
192
+ "require-fresh": {
193
+ kind: "boolean",
194
+ describe: "Search only with confirmed current content hashes; otherwise exit 4.",
195
+ },
196
+
197
+ // --- grep-Modus (Nutzerwunsch A4) ---------------------------------------
198
+ grep: {
199
+ kind: "boolean",
200
+ alias: "g",
201
+ describe: "grep mode: literal search in indexed content.",
202
+ },
203
+ regex: { kind: "boolean", alias: "e", describe: "grep mode with a regular expression." },
204
+ "ignore-case": {
205
+ kind: "boolean",
206
+ alias: "i",
207
+ describe: "grep: ignore letter case.",
208
+ },
209
+ word: {
210
+ kind: "boolean",
211
+ alias: "w",
212
+ describe: "grep: whole words only (identifier boundaries).",
213
+ },
214
+ after: {
215
+ kind: "number",
216
+ alias: "A",
217
+ placeholder: "N",
218
+ describe: "grep: context lines after the match.",
219
+ },
220
+ before: {
221
+ kind: "number",
222
+ alias: "B",
223
+ placeholder: "N",
224
+ describe: "grep: context lines before the match.",
225
+ },
226
+ context: {
227
+ kind: "boolean-or-number",
228
+ alias: "C",
229
+ placeholder: "N",
230
+ describe:
231
+ "Search: direct relationships from the persisted file graph. grep: =N or -C N shows context on both sides.",
232
+ },
233
+ "max-matches": {
234
+ kind: "number",
235
+ placeholder: "N",
236
+ describe: "grep: maximum total matches.",
237
+ },
238
+ "max-per-file": { kind: "number", placeholder: "N", describe: "grep: maximum matches per file." },
239
+ "max-scanned": {
240
+ kind: "number",
241
+ placeholder: "N",
242
+ describe: "grep: maximum searched chunks.",
243
+ },
244
+ "grep-sort": {
245
+ kind: "string",
246
+ placeholder: GREP_SORTS.join("|"),
247
+ describe: "grep: match ordering.",
248
+ defaultLabel: "path",
249
+ },
250
+ };
251
+
252
+ export const queryCommand: Command = {
253
+ spec: {
254
+ name: "query",
255
+ implementedBy: "commands/query.js",
256
+ summary: "Search the index with semantic and grep-style queries",
257
+ usage: "untangle query <text> [options]",
258
+ details: [
259
+ "Without --grep/--regex, hybrid search retrieves dense vectors, BM25 and exact symbols independently, then combines them with RRF. --mode selects independent retrieval and ranking paths; lexical/symbol need no embedding service.",
260
+ "Before each search, index freshness is checked against the full current content manifest and returned as indexFreshness. --refresh updates stale checked-out refs; --require-fresh rejects unconfirmed freshness. An outdated lexical index format requires reindexing.",
261
+ "--grep (literal) and --regex search the same index with the same filters and skip ledger. Context lines come from the same chunk and stop at its boundary.",
262
+ "--explain shows the full score breakdown: each component's raw value, weight and contribution, making the ranking inspectable.",
263
+ "Profile discovery: `untangle index` records the embedding profile per repository/ref in the local registry (.untangle/index-states-v1). Without --embedder, query uses the generation-bound current state and reports that choice on stderr. Override with --embedder=service or --embedder=offline.",
264
+ "Search reads the index, not the working tree. Unindexed content cannot be found; the result therefore includes its skip ledger and, for grep, the number of searched chunks.",
265
+ ],
266
+ flags: FLAGS,
267
+ examples: [
268
+ 'untangle query "where is the payment triggered"',
269
+ 'untangle query "charge" --mode=symbol --explain',
270
+ "untangle query --regex \"await\\\\s+fetch\\\\(\" -C 2 --path='src/**' --exclude='**/*.test.ts'",
271
+ 'untangle query "retry" --format=jsonl | jq -r .filePath',
272
+ ],
273
+ exitCodes: [
274
+ [EXIT.ok, "Search completed, including zero-hit results."],
275
+ [EXIT.operational, "Invalid configuration or a storage error."],
276
+ [EXIT.usage, "Invalid arguments."],
277
+ [
278
+ EXIT.environment,
279
+ "No compatible index, or the selected storage/embedding provider is unavailable.",
280
+ ],
281
+ [EXIT.cancelled, "Cancelled."],
282
+ ],
283
+ },
284
+
285
+ async run(ctx: CommandContext, args: ParsedArgs): Promise<number> {
286
+ const format = enumFlag<OutputFormat>(args, "format", OUTPUT_FORMATS, "text");
287
+ const isGrep = boolFlag(args, "grep") || boolFlag(args, "regex");
288
+ const text = args.positionals.join(" ").trim();
289
+ if (text.length === 0) {
290
+ throw new UsageError(
291
+ "Search text is required.",
292
+ isGrep
293
+ ? 'Example: untangle query --regex "await\\\\s+fetch\\\\("'
294
+ : 'Example: untangle query "where is the payment triggered"',
295
+ );
296
+ }
297
+
298
+ const view = loadWorkspace({
299
+ cwd: args.flags["root"] === undefined ? ctx.cwd : String(args.flags["root"]),
300
+ env: ctx.env,
301
+ workspaceIdOverride: stringFlag(args, "workspace"),
302
+ strict: true,
303
+ });
304
+ if (view.load.warnings.length > 0) {
305
+ for (const warning of view.load.warnings) {
306
+ note(ctx.io, `Configuration warning: ${warning.path || "(root)"}: ${warning.message}`);
307
+ }
308
+ }
309
+
310
+ const wiring = wireStorage(view, ctx.env);
311
+ const timeoutMs = integerFlag(args, "timeout", DEFAULT_QUERY_TIMEOUT_MS);
312
+
313
+ // `untangle index --embedder=offline` legt seine Punkte in einer EIGENEN
314
+ // Sammlung ab (eigener `embedding_profile_id`, spec.md §10.1) — sonst
315
+ // mischten netzlose und echte Vektoren in einem Raum, in dem sie nicht
316
+ // vergleichbar sind. Damit dieser Index auch DURCHSUCHBAR ist, kennt
317
+ // `query` denselben Schalter.
318
+ //
319
+ // WP-229: Ihn zu KENNEN reichte aber nicht — man musste ihn auch bei jeder
320
+ // Abfrage wiederholen. `auto` liest deshalb den Indexzustand, den
321
+ // `untangle index` hinterlaesst, und nimmt von dort, WOMIT tatsaechlich
322
+ // indiziert wurde. Geraten wird nichts: gibt es keinen Zustand, gilt die
323
+ // Konfiguration wie bisher, und widersprechen sich mehrere, sagt der
324
+ // Befehl das und laesst den Nutzer entscheiden.
325
+ const selectedRepoIds = listFlag(args, "repo");
326
+ const selectedRefIds = listFlag(args, "ref");
327
+ const indexStateResolution = await resolveIndexState(
328
+ indexStateRoots({
329
+ invocationRoot:
330
+ args.flags["root"] === undefined
331
+ ? ctx.cwd
332
+ : path.resolve(ctx.cwd, String(args.flags["root"])),
333
+ configBaseDir: view.load.sources.configBaseDir,
334
+ repositories: view.config.repositories ?? [],
335
+ selectedRepoIds,
336
+ }),
337
+ view.workspaceId,
338
+ { repoIds: selectedRepoIds, refs: selectedRefIds, ...storageIndexScope(wiring) },
339
+ );
340
+ if (indexStateResolution.kind === "invalid") {
341
+ const details = indexStateResolution.diagnostics
342
+ .map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`)
343
+ .join("; ");
344
+ throw new EnvironmentError(
345
+ `The local index state is invalid; search will not use partial or ` +
346
+ `stale provenance. ${details}`,
347
+ "Rerun the affected indexing job. It publishes the repository/ref registry atomically.",
348
+ );
349
+ }
350
+ const automaticEmbedder =
351
+ enumFlag(args, "embedder", ["auto", "service", "offline"] as const, "auto") === "auto";
352
+ const useOffline = resolveEmbedderChoice(ctx, args, indexStateResolution);
353
+ const embedder = useOffline ? new OfflineEmbeddingProvider(systemClock) : wireEmbedder(view);
354
+ const searchView = useOffline
355
+ ? {
356
+ ...view,
357
+ profile: embedder.profile,
358
+ profileId: embedder.profileId,
359
+ profileShortId: embeddingProfileShortId(embedder.profileId),
360
+ }
361
+ : view;
362
+ const collection = useOffline
363
+ ? codeCollectionName(wiring.collectionPrefix, searchView.profileShortId)
364
+ : wiring.codeCollection;
365
+ const foundIndexStates = queryIndexStatesFromResolution(
366
+ indexStateResolution,
367
+ automaticEmbedder,
368
+ );
369
+ const compatibleIndexStates = foundIndexStates.filter(
370
+ ({ state }) =>
371
+ state.embeddingProfileId === searchView.profileId &&
372
+ state.collection === collection &&
373
+ matchesStoredBackend(state, wiring),
374
+ );
375
+ const freshnessCheck = await withDeadline(
376
+ (signal) =>
377
+ inspectIndexFreshness(view, compatibleIndexStates, {
378
+ signal,
379
+ expectedRepoIds: selectedRepoIds.length > 0 ? selectedRepoIds : view.repoIds,
380
+ expectedRefs: selectedRefIds,
381
+ }),
382
+ timeoutMs,
383
+ ctx.signal,
384
+ );
385
+ ctx.signal.throwIfAborted();
386
+ const indexFreshness: IndexFreshnessV1 = freshnessCheck.ok
387
+ ? freshnessCheck.value
388
+ : {
389
+ status: "unknown",
390
+ checkedAt: systemClock.nowIso(),
391
+ repositories: [],
392
+ };
393
+ if (boolFlag(args, "refresh") && indexFreshness.status !== "fresh") {
394
+ await refreshWorkspaceIndex(ctx, {
395
+ view,
396
+ repoIds: selectedRepoIds,
397
+ refIds: selectedRefIds,
398
+ embedder: useOffline ? "offline" : "auto",
399
+ });
400
+ return queryCommand.run(ctx, {
401
+ ...args,
402
+ flags: { ...args.flags, refresh: false, "require-fresh": true },
403
+ });
404
+ }
405
+ if (
406
+ indexFreshness.status === "incompatible" ||
407
+ (boolFlag(args, "require-fresh") && indexFreshness.status !== "fresh")
408
+ ) {
409
+ throw new EnvironmentError(
410
+ `The search index is ${indexFreshness.status}; this query requires a current, compatible index.`,
411
+ "Run the same query with --refresh or update with `untangle index`.",
412
+ );
413
+ }
414
+ note(
415
+ ctx.io,
416
+ `Index freshness: ${indexFreshness.status} (content hashes checked ${indexFreshness.checkedAt}).`,
417
+ );
418
+ const indexContexts = compatibleIndexStates.flatMap(({ state }) =>
419
+ state.generationId !== undefined
420
+ ? [
421
+ {
422
+ repoId: state.repoId,
423
+ refId: state.refId,
424
+ generationId: state.generationId,
425
+ ...(state.corpusRevisionFingerprint === undefined || indexFreshness.status !== "fresh"
426
+ ? {}
427
+ : { corpusRevisionFingerprint: state.corpusRevisionFingerprint }),
428
+ outcome: state.outcome,
429
+ },
430
+ ]
431
+ : [],
432
+ );
433
+ const effectiveRefIds = queryRefIdsFromIndexStates(selectedRefIds, compatibleIndexStates);
434
+
435
+ // Der fehlende Index ist der haeufigste Fall und verdient eine eigene,
436
+ // eindeutige Antwort — nicht eine leere Trefferliste (E4).
437
+ const probe = await withDeadline<boolean>(
438
+ (signal) => wiring.vectorStore.collectionExists(collection, { signal }),
439
+ timeoutMs,
440
+ ctx.signal,
441
+ );
442
+ if (!probe.ok) {
443
+ ctx.signal.throwIfAborted();
444
+ throw new EnvironmentError(
445
+ probe.timedOut
446
+ ? `${wiring.backend === "qdrant" ? "Qdrant" : "LanceDB"} at ${wiring.storeUrl} did not answer within ${String(timeoutMs)}ms.`
447
+ : `${wiring.backend === "qdrant" ? "Qdrant" : "LanceDB"} at ${wiring.storeUrl} is unreachable: ${errorMessage(probe.error)}`,
448
+ "Run `untangle doctor` for the next step specific to this failure.",
449
+ { cause: probe.error },
450
+ );
451
+ }
452
+ if (!probe.value) {
453
+ if (boolFlag(args, "refresh")) {
454
+ await refreshWorkspaceIndex(ctx, {
455
+ view,
456
+ repoIds: selectedRepoIds,
457
+ refIds: selectedRefIds,
458
+ embedder: useOffline ? "offline" : "auto",
459
+ });
460
+ return queryCommand.run(ctx, {
461
+ ...args,
462
+ flags: { ...args.flags, refresh: false, "require-fresh": true },
463
+ });
464
+ }
465
+ throw new EnvironmentError(
466
+ `No index exists for this embedding profile: collection "${collection}" ` +
467
+ `is absent from ${wiring.storeUrl}. Nothing was searched; this does not ` +
468
+ `mean there are no results.`,
469
+ useOffline
470
+ ? "Run `untangle index --embedder=offline` to build this collection."
471
+ : "Run `untangle index`. Without an available embedding service, use `untangle index --embedder=offline` and select `--embedder=offline` here too.",
472
+ );
473
+ }
474
+
475
+ const engine = wireSearchEngine({
476
+ view: searchView,
477
+ vectorStore: wiring.vectorStore,
478
+ collection,
479
+ embedder,
480
+ indexContexts,
481
+ ...(!isGrep && (boolFlag(args, "context") || boolFlag(args, "linked"))
482
+ ? { graphEnrichment: createSearchGraphEnrichment(wiring.storage.graphStore) }
483
+ : {}),
484
+ });
485
+
486
+ if (isGrep) {
487
+ const request = buildGrepRequest(args, view.workspaceId, text, effectiveRefIds);
488
+ const outcome = await withDeadline<GrepResultSetV1>(
489
+ (signal) => engine.grep(request, { signal }),
490
+ timeoutMs,
491
+ ctx.signal,
492
+ );
493
+ if (!outcome.ok) {
494
+ ctx.signal.throwIfAborted();
495
+ throw toRuntimeError(outcome, wiring.storeUrl, timeoutMs, "grep", useOffline);
496
+ }
497
+ outcome.value.indexFreshness = indexFreshness;
498
+ addFreshnessWarning(outcome.value, indexFreshness);
499
+ renderGrep(ctx, outcome.value, format);
500
+ assertEverythingWasSearched(ctx, outcome.value.skipped, wiring.backend);
501
+ return EXIT.ok;
502
+ }
503
+
504
+ const request = buildSearchRequest(
505
+ args,
506
+ view.workspaceId,
507
+ text,
508
+ selectedRefIds.length === 0 ? undefined : effectiveRefIds,
509
+ );
510
+ const outcome = await withDeadline<SearchResultSetV1>(
511
+ (signal) => engine.search(request, { signal }),
512
+ timeoutMs,
513
+ ctx.signal,
514
+ );
515
+ if (!outcome.ok) {
516
+ ctx.signal.throwIfAborted();
517
+ throw toRuntimeError(outcome, wiring.storeUrl, timeoutMs, request, useOffline);
518
+ }
519
+ outcome.value.indexFreshness = indexFreshness;
520
+ addFreshnessWarning(outcome.value, indexFreshness);
521
+ renderSearch(ctx, outcome.value, format, boolFlag(args, "explain"));
522
+ assertEverythingWasSearched(ctx, outcome.value.skipped, wiring.backend);
523
+ return EXIT.ok;
524
+ },
525
+ };
526
+
527
+ /* --------------------------------------------------------------------- */
528
+
529
+ export function addFreshnessWarning(
530
+ result: Pick<SearchResultSetV1, "warnings">,
531
+ freshness: IndexFreshnessV1,
532
+ ): void {
533
+ if (freshness.status === "fresh") return;
534
+ const warning = {
535
+ code: INDEX_CONTEXT_INCOMPLETE_WARNING_CODE,
536
+ message: `Indexfrische ${freshness.status}: results describe an unconfirmed or stale snapshot. Update with --refresh. This query is not reliable product-complexity evidence.`,
537
+ details: {
538
+ status: freshness.status,
539
+ checkedAt: freshness.checkedAt,
540
+ reasons: freshness.repositories.map((entry) => entry.reason),
541
+ },
542
+ };
543
+ const existingIndex = result.warnings.findIndex(
544
+ (entry) => entry.code === INDEX_CONTEXT_INCOMPLETE_WARNING_CODE,
545
+ );
546
+ const existing = result.warnings[existingIndex];
547
+ if (existing === undefined) result.warnings.push(warning);
548
+ else
549
+ result.warnings[existingIndex] = {
550
+ ...existing,
551
+ message: `${existing.message} ${warning.message}`,
552
+ details: { ...existing.details, indexFreshness: warning.details },
553
+ };
554
+ }
555
+
556
+ /**
557
+ * Welcher Einbetter durchsucht wird — und woher diese Entscheidung stammt
558
+ * (WP-229).
559
+ *
560
+ * `--embedder=offline` und `--embedder=service` sind ausdrueckliche
561
+ * Anweisungen und gewinnen immer. `auto` fragt den Indexzustand: er ist die
562
+ * einzige Quelle, die WEISS, womit gebaut wurde. Was dabei entschieden wird,
563
+ * steht auf stderr — eine Wahl, die der Nutzer nicht sieht, ist eine Wahl,
564
+ * die er nicht ueberstimmen kann.
565
+ */
566
+ export function resolveEmbedderChoice(
567
+ ctx: CommandContext,
568
+ args: ParsedArgs,
569
+ resolution: IndexStateResolution,
570
+ ): boolean {
571
+ const flag = enumFlag(args, "embedder", ["auto", "service", "offline"] as const, "auto");
572
+ if (flag === "offline") return true;
573
+ if (flag === "service") return false;
574
+
575
+ if (resolution.kind === "none") {
576
+ // Kein Zustand heisst: es wurde hier noch nicht indiziert, oder mit einer
577
+ // aelteren Fassung. Dann gilt die Konfiguration — und wenn dazu kein Index
578
+ // existiert, meldet das der Aufrufer mit der Begruendung, die es schon gab.
579
+ return false;
580
+ }
581
+
582
+ if (resolution.kind === "ambiguous") {
583
+ const states = resolution.found
584
+ .map((entry) => `${entry.state.repoId}/${entry.state.refName}: ${entry.state.embedder}`)
585
+ .join(", ");
586
+ throw new UsageError(
587
+ "The current repository/ref states use different embedders; `auto` " +
588
+ `would search only a subset (${states}). Select --embedder=service or ` +
589
+ "--embedder=offline explicitly.",
590
+ "An explicit selection searches only locally registered refs for that profile.",
591
+ );
592
+ }
593
+
594
+ if (resolution.kind === "invalid") {
595
+ // The command rejects this case before selecting an embedder. Keeping the
596
+ // guard here makes the helper exhaustive if it is reused independently.
597
+ throw new EnvironmentError(
598
+ "The local index state is invalid; the embedding profile will not be guessed.",
599
+ "Rerun the affected indexing job.",
600
+ );
601
+ }
602
+
603
+ const state = resolution.found.state;
604
+ note(
605
+ ctx.io,
606
+ `Embedding profile from the last index run: ${describeEmbedder(state.embedder)} ` +
607
+ `(${resolution.found.statePath}). ` +
608
+ "Override with --embedder=service or --embedder=offline.",
609
+ );
610
+ if (state.outcome !== "completed") {
611
+ note(
612
+ ctx.io,
613
+ `Warning: this indexing run ended as "${state.outcome}". Anything it could not finish ` +
614
+ "cannot be found here either.",
615
+ );
616
+ }
617
+ return state.embedder === "offline";
618
+ }
619
+
620
+ /**
621
+ * Wo nach dem Indexzustand gesucht wird. Ohne Repo-Filter gilt die bisherige
622
+ * Reihenfolge: Aufrufswurzel, Konfigurationswurzel, dann alle konfigurierten
623
+ * Repositories codepoint-sortiert. Mit `--repo` duerfen dagegen nur die
624
+ * tatsaechlich ausgewaehlten Repository-Wurzeln die Auto-Embedder-Wahl
625
+ * beeinflussen; ein unbeteiligtes Repository darf keine Ambiguitaet erzeugen.
626
+ */
627
+ export function indexStateRoots(input: {
628
+ readonly invocationRoot: string;
629
+ readonly configBaseDir: string;
630
+ readonly repositories: readonly { readonly id: string; readonly path: string }[];
631
+ readonly selectedRepoIds: readonly string[];
632
+ }): readonly string[] {
633
+ const selected = new Set(input.selectedRepoIds);
634
+ const roots: string[] =
635
+ selected.size === 0 || input.repositories.length === 0
636
+ ? [input.invocationRoot, input.configBaseDir]
637
+ : [];
638
+ const repositories = [...input.repositories]
639
+ .filter((repository) => selected.size === 0 || selected.has(repository.id))
640
+ .sort((a, b) => compareCodepoints(a.id, b.id));
641
+ for (const repository of repositories) {
642
+ roots.push(path.resolve(input.configBaseDir, repository.path));
643
+ }
644
+ return roots;
645
+ }
646
+
647
+ /**
648
+ * Resolves an omitted ref scope to the refs for which the selected collection
649
+ * has local index state. Explicit canonical IDs remain authoritative; an
650
+ * explicit human ref name expands to every compatible canonical ID carrying
651
+ * that name (for example `main` in two selected repositories).
652
+ *
653
+ * The grep path receives these IDs directly. Semantic search receives the
654
+ * richer index contexts and derives the same scope inside the engine, where
655
+ * it can still distinguish an implicit scope for provenance validation. This
656
+ * is deliberately based on profile/collection-compatible states supplied by
657
+ * the caller: an old state for another embedding space must not widen a scan.
658
+ */
659
+ export function queryRefIdsFromIndexStates(
660
+ requestedRefIds: readonly string[],
661
+ compatibleIndexStates: readonly FoundIndexState[],
662
+ ): string[] {
663
+ if (requestedRefIds.length === 0) {
664
+ return uniqueSortedByCodepoints(compatibleIndexStates.map(({ state }) => state.refId));
665
+ }
666
+
667
+ const canonicalIds = new Set(compatibleIndexStates.map(({ state }) => state.refId));
668
+ const resolved: string[] = [];
669
+ for (const requested of requestedRefIds) {
670
+ if (canonicalIds.has(requested)) {
671
+ resolved.push(requested);
672
+ continue;
673
+ }
674
+ const nameMatches = compatibleIndexStates
675
+ .filter(({ state }) => state.refName === requested)
676
+ .map(({ state }) => state.refId);
677
+ // Keep an unknown explicit value. It will remain a restrictive store
678
+ // filter and cannot silently fall back to the implicit/all-ref scope.
679
+ resolved.push(...(nameMatches.length === 0 ? [requested] : nameMatches));
680
+ }
681
+ return uniqueSortedByCodepoints(resolved);
682
+ }
683
+
684
+ /**
685
+ * Auto-routing follows only the generation-checked current pointer of every
686
+ * selected repo/ref. An explicit embedder selection may intentionally reopen
687
+ * an older retained profile and therefore receives the complete registry.
688
+ */
689
+ export function queryIndexStatesFromResolution(
690
+ resolution: Exclude<IndexStateResolution, { readonly kind: "invalid" }>,
691
+ automaticEmbedder: boolean,
692
+ ): readonly FoundIndexState[] {
693
+ if (resolution.kind === "none") return [];
694
+ const all = resolution.all;
695
+ return automaticEmbedder ? all.filter((entry) => entry.current) : all;
696
+ }
697
+
698
+ function describeEmbedder(choice: EmbedderChoice): string {
699
+ return choice === "offline" ? "offline (local, in-process)" : "the configured service";
700
+ }
701
+
702
+ /**
703
+ * Ziele, die ueberhaupt nicht durchsucht werden konnten.
704
+ *
705
+ * DIESE UNTERSCHEIDUNG IST DER KERN VON E4 AN DIESER OBERFLAECHE. Der
706
+ * Abrufkern degradiert bewusst sichtbar: faellt ein Embedding-Profil aus (weil
707
+ * Ollama nicht antwortet), meldet er das als Warnung UND als Ledger-Eintrag
708
+ * und liefert die uebrigen Profile trotzdem. Fuer den Aufrufer sieht das
709
+ * Ergebnis dann aus wie „null Treffer" — und genau das waere die Luege:
710
+ * gesucht wurde nichts.
711
+ *
712
+ * Gemessen an einem blockierten Ollama gab diese Oberflaeche vor der
713
+ * Einfuehrung dieser Pruefung „Keine Treffer." mit Exit-Code 0 aus, obwohl im
714
+ * Ledger `provider_unavailable` stand. Jetzt endet derselbe Fall mit
715
+ * Exit-Code 4 und der Begruendung aus dem Ledger.
716
+ *
717
+ * Erkannt wird der Ausfall an der Kombination, die der Abrufkern EXAKT fuer
718
+ * diesen Fall schreibt: `subjectKind === "search_candidate"` zusammen mit
719
+ * `reason === "provider_unavailable"`. Ein wegen Filtern oder Budget
720
+ * ausgelassenes Ziel traegt stattdessen `excluded_by_configuration` und ist
721
+ * kein Ausfall.
722
+ */
723
+ export function failedTargets(skipped: readonly SkipLedgerEntryV1[]): readonly SkipLedgerEntryV1[] {
724
+ return skipped.filter(
725
+ (entry) => entry.subjectKind === "search_candidate" && entry.reason === "provider_unavailable",
726
+ );
727
+ }
728
+
729
+ /** Wirft, wenn ein Ziel ausgefallen ist — „nichts durchsucht" ist kein Erfolg. */
730
+ function assertEverythingWasSearched(
731
+ ctx: CommandContext,
732
+ skipped: readonly SkipLedgerEntryV1[],
733
+ backend: "lancedb" | "qdrant",
734
+ ): void {
735
+ const failed = failedTargets(skipped);
736
+ if (failed.length === 0) return;
737
+ for (const entry of failed) {
738
+ note(ctx.io, `Unavailable target: ${entry.detail}`);
739
+ }
740
+ throw new EnvironmentError(
741
+ `${String(failed.length)} search target(s) did not respond. The result above is ` +
742
+ "incomplete; this does not mean nothing was found.",
743
+ `Run \`untangle doctor\` to check Ollama and ${backend === "lancedb" ? "LanceDB" : "Qdrant"} independently with separate deadlines.`,
744
+ );
745
+ }
746
+
747
+ export function buildSearchRequest(
748
+ args: ParsedArgs,
749
+ workspaceId: string,
750
+ text: string,
751
+ refIdsOverride?: readonly string[],
752
+ ): UntangleSearchRequestV1 {
753
+ if (typeof args.flags["context"] === "number") {
754
+ throw new UsageError(
755
+ 'Option "--context=N" applies only in grep mode.',
756
+ 'For semantic search, use "--context" without a number.',
757
+ );
758
+ }
759
+ const limit = numberFlag(args, "limit");
760
+ const minScore = numberFlag(args, "min-score");
761
+ const paths = listFlag(args, "path");
762
+ const excludes = listFlag(args, "exclude");
763
+ const languages = listFlag(args, "lang");
764
+ const repositories = listFlag(args, "repo");
765
+ const refs =
766
+ refIdsOverride === undefined ? listFlag(args, "ref") : uniqueSortedByCodepoints(refIdsOverride);
767
+ const symbolNames = listFlag(args, "symbol");
768
+
769
+ return {
770
+ query: text,
771
+ workspaceId,
772
+ ...(limit === undefined ? {} : { limit }),
773
+ ...(minScore === undefined ? {} : { minScore }),
774
+ ...(paths.length === 0 ? {} : { pathGlob: paths }),
775
+ ...(excludes.length === 0 ? {} : { pathExclude: excludes }),
776
+ ...(languages.length === 0 ? {} : { languages }),
777
+ ...(repositories.length === 0 ? {} : { repositories }),
778
+ ...(refs.length === 0 ? {} : { refs }),
779
+ ...(symbolNames.length === 0 ? {} : { symbolNames }),
780
+ mode: enumFlag<SearchMode>(args, "mode", SEARCH_MODES, "hybrid"),
781
+ abstention: enumFlag<SearchAbstention>(args, "abstention", SEARCH_ABSTENTIONS, "strict"),
782
+ sort: enumFlag<SearchSort>(args, "sort", SEARCH_SORTS, "relevance"),
783
+ complexityBias: enumFlag<ComplexityBias>(args, "complexity-bias", COMPLEXITY_BIASES, "none"),
784
+ includeContent: boolFlag(args, "content"),
785
+ includeContext: boolFlag(args, "context"),
786
+ includeLinked: boolFlag(args, "linked"),
787
+ };
788
+ }
789
+
790
+ export function buildGrepRequest(
791
+ args: ParsedArgs,
792
+ workspaceId: string,
793
+ pattern: string,
794
+ refIdsOverride?: readonly string[],
795
+ ): GrepRequestV1 {
796
+ if (args.flags["context"] === true) {
797
+ throw new UsageError(
798
+ 'Option "--context" requires a non-negative integer in grep mode.',
799
+ 'Example: "--context=2" or "-C 2".',
800
+ );
801
+ }
802
+ const both = integerFlag(args, "context", 0);
803
+ const before = integerFlag(args, "before", both);
804
+ const after = integerFlag(args, "after", both);
805
+ const paths = listFlag(args, "path");
806
+ const excludes = listFlag(args, "exclude");
807
+ const languages = listFlag(args, "lang");
808
+ const repositories = listFlag(args, "repo");
809
+ const refs =
810
+ refIdsOverride === undefined ? listFlag(args, "ref") : uniqueSortedByCodepoints(refIdsOverride);
811
+ const maxMatches = numberFlag(args, "max-matches");
812
+ const maxPerFile = numberFlag(args, "max-per-file");
813
+ const maxScanned = numberFlag(args, "max-scanned");
814
+
815
+ return {
816
+ pattern,
817
+ regex: boolFlag(args, "regex"),
818
+ // grep ist per Vorgabe gross-/kleinschreibungsempfindlich, wie das Werkzeug,
819
+ // dessen Namen der Modus traegt. `-i` hebt das auf.
820
+ caseSensitive: !boolFlag(args, "ignore-case"),
821
+ wholeWord: boolFlag(args, "word"),
822
+ workspaceId,
823
+ ...(repositories.length === 0 ? {} : { repositories }),
824
+ ...(refs.length === 0 ? {} : { refs }),
825
+ ...(languages.length === 0 ? {} : { languages }),
826
+ ...(paths.length === 0 ? {} : { pathGlob: paths }),
827
+ ...(excludes.length === 0 ? {} : { pathExclude: excludes }),
828
+ contextLinesBefore: before,
829
+ contextLinesAfter: after,
830
+ ...(maxMatches === undefined ? {} : { maxMatches }),
831
+ ...(maxPerFile === undefined ? {} : { maxMatchesPerFile: maxPerFile }),
832
+ ...(maxScanned === undefined ? {} : { maxScannedChunks: maxScanned }),
833
+ sort: enumFlag<GrepSort>(args, "grep-sort", GREP_SORTS, "path"),
834
+ };
835
+ }
836
+
837
+ /* --------------------------------------------------------------------- */
838
+
839
+ export function renderSearch(
840
+ ctx: CommandContext,
841
+ set: SearchResultSetV1,
842
+ format: OutputFormat,
843
+ explain: boolean,
844
+ ): void {
845
+ if (format !== "text") {
846
+ // WP-230: `line` und nicht `out`. Die Formatierer liefern ihren Text OHNE
847
+ // abschliessenden Zeilenumbruch; ohne ihn klebte der Ledger-Hinweis an der
848
+ // letzten Ausgabezeile, und bei `jsonl` haette die letzte Zeile keinen
849
+ // Abschluss — beides macht eine maschinenlesbare Ausgabe unlesbar.
850
+ line(ctx.io, formatSearchResultSet(set, format));
851
+ reportLedger(ctx, set.skipped.length, set.warnings.length);
852
+ return;
853
+ }
854
+
855
+ // Die Enthaltung wird VOR dem Leer-Zweig bestimmt und unabhaengig von ihm
856
+ // gemeldet. Frueher stand sie ausschliesslich in `if (totalHits === 0)` —
857
+ // eine Enthaltung wurde also aus der Listenlaenge erschlossen. Sobald
858
+ // Treffer trotz Ablehnung stehenbleiben, haette dieser Zweig geschwiegen und
859
+ // eine selbstbewusst aussehende Trefferliste gedruckt.
860
+ const insufficient = set.warnings.some(
861
+ (warning) => warning.code === NO_SUFFICIENT_MATCH_WARNING_CODE,
862
+ );
863
+
864
+ if (set.totalHits === 0) {
865
+ note(
866
+ ctx.io,
867
+ failedTargets(set.skipped).length > 0
868
+ ? "No results, but the search was also incomplete (see below)."
869
+ : insufficient
870
+ ? "No sufficiently relevant results found."
871
+ : "No results.",
872
+ );
873
+ reportLedger(ctx, set.skipped.length, set.warnings.length);
874
+ if (set.skipped.length > 0) {
875
+ note(ctx.io, "Skipped candidates appear in the --format=json skip ledger.");
876
+ }
877
+ return;
878
+ }
879
+
880
+ if (insufficient) {
881
+ note(ctx.io, "No sufficiently relevant results found.");
882
+ }
883
+
884
+ for (const group of set.groups) {
885
+ if (set.profileSeparation) {
886
+ note(ctx.io, `# Profile ${group.embeddingProfileId} (${group.repoIds.join(", ")})`);
887
+ }
888
+ // Vorbehalte unbedingt drucken. Sie standen bisher NUR unter
889
+ // `profileSeparation`, und das ist im ausgelieferten CLI nie wahr:
890
+ // `wireSearchEngine` baut genau ein Profilziel. Der Vorbehalt traegt die
891
+ // drei gemessenen Werte samt Schwellen — er war damit im Textmodus
892
+ // unsichtbar.
893
+ for (const caveat of group.caveats) note(ctx.io, `# Qualification: ${caveat}`);
894
+ for (const hit of group.hits) {
895
+ if (explain) {
896
+ // WP-230: `formatHitAsText` endet ohne Zeilenumbruch. Mit `out` klebte
897
+ // der naechste Treffer — und am Ende der Ledger-Hinweis — an der
898
+ // letzten Zeile der Score-Zerlegung.
899
+ line(ctx.io, formatHitAsText(hit));
900
+ } else {
901
+ line(ctx.io, compactHitLine(hit));
902
+ }
903
+ }
904
+ }
905
+ reportLedger(ctx, set.skipped.length, set.warnings.length);
906
+ }
907
+
908
+ /** Eine Zeile je Treffer — Rang, Ort, Symbol, Score. */
909
+ function compactHitLine(hit: SearchHitV1): string {
910
+ const where = `${hit.filePath}:${String(hit.range.startLine)}-${String(hit.range.endLine)}`;
911
+ const symbol = hit.symbolName === undefined ? "" : ` ${hit.symbolName}`;
912
+ const incomplete =
913
+ hit.incompletePayloadFields === undefined || hit.incompletePayloadFields.length === 0
914
+ ? ""
915
+ : ` [Fallback values: ${[...hit.incompletePayloadFields].sort(compareCodepoints).join(",")}]`;
916
+ const lines = [
917
+ `${String(hit.rank).padStart(3)} ${hit.score.final.toFixed(4)} ${where}${symbol}${incomplete}`,
918
+ ];
919
+ if (hit.graphContext !== undefined) {
920
+ lines.push(...formatSearchGraphRelationsAsText("context", hit.graphContext));
921
+ }
922
+ if (hit.linkedSymbols !== undefined) {
923
+ lines.push(...formatSearchGraphRelationsAsText("linked", hit.linkedSymbols));
924
+ }
925
+ return lines.join("\n");
926
+ }
927
+
928
+ export function renderGrep(ctx: CommandContext, set: GrepResultSetV1, format: OutputFormat): void {
929
+ if (format !== "text") {
930
+ // WP-230, dieselbe Ursache wie oben.
931
+ line(ctx.io, formatGrepResultSet(set, format));
932
+ reportGrepTail(ctx, set);
933
+ return;
934
+ }
935
+
936
+ for (const match of set.matches) {
937
+ for (const before of match.before) {
938
+ line(ctx.io, `${match.filePath}-${String(before.line)}-${before.text}`);
939
+ }
940
+ line(
941
+ ctx.io,
942
+ `${match.filePath}:${String(match.line)}:${String(match.column)}:${match.lineText}`,
943
+ );
944
+ for (const after of match.after) {
945
+ line(ctx.io, `${match.filePath}-${String(after.line)}-${after.text}`);
946
+ }
947
+ }
948
+ reportGrepTail(ctx, set);
949
+ }
950
+
951
+ function reportGrepTail(ctx: CommandContext, set: GrepResultSetV1): void {
952
+ note(
953
+ ctx.io,
954
+ `${String(set.totalMatches)} results, ${String(set.scannedChunks)} chunks searched.`,
955
+ );
956
+ if (set.truncated) {
957
+ note(ctx.io, `Truncated; reason: ${set.truncationReason ?? "unbenannt"}.`);
958
+ }
959
+ reportLedger(ctx, set.skipped.length, set.warnings.length);
960
+ }
961
+
962
+ /**
963
+ * Der Ledger-Hinweis. Er steht auf stderr, weil er Diagnose ist — aber er
964
+ * steht IMMER, wenn es etwas zu melden gibt. Ein Ledger, den niemand sieht,
965
+ * ist kein Ledger.
966
+ */
967
+ function reportLedger(ctx: CommandContext, skipped: number, warnings: number): void {
968
+ if (skipped === 0 && warnings === 0) return;
969
+ note(
970
+ ctx.io,
971
+ `Note: ${String(skipped)} skipped candidates, ${String(warnings)} warning(s). ` +
972
+ "Both are fully available in --format=json.",
973
+ );
974
+ }
975
+
976
+ export function toRuntimeError(
977
+ outcome: { readonly timedOut: boolean; readonly error: unknown },
978
+ qdrantUrl: string,
979
+ timeoutMs: number,
980
+ request: Pick<UntangleSearchRequestV1, "mode" | "includeContext" | "includeLinked"> | "grep",
981
+ useOffline: boolean,
982
+ ): Error {
983
+ const backendLabel = qdrantUrl.startsWith("file:") ? "LanceDB" : "Qdrant";
984
+ if (outcome.timedOut) {
985
+ const mode = request === "grep" ? "grep" : (request.mode ?? "hybrid");
986
+ const usesEmbedding = mode === "semantic" || mode === "hybrid";
987
+ const stages = [`Search retrieval from ${backendLabel} at ${qdrantUrl}`];
988
+ if (usesEmbedding)
989
+ stages.push(useOffline ? "local query embedding" : "Query embedding through Ollama");
990
+ const graphFlags: string[] = [];
991
+ if (request !== "grep" && request.includeContext) graphFlags.push("--context");
992
+ if (request !== "grep" && request.includeLinked) graphFlags.push("--linked");
993
+ if (graphFlags.length > 0)
994
+ stages.push(`Graph enrichment (${graphFlags.join(" ")}) from ${backendLabel}`);
995
+ return new EnvironmentError(
996
+ `The query was cancelled after ${String(timeoutMs)}ms; no result is available. ` +
997
+ `Mode "${mode}" covers ${stages.join("; ")}.`,
998
+ `Run \`untangle doctor\` to check ${backendLabel}${usesEmbedding && !useOffline ? " and Ollama" : ""} with separate deadlines.` +
999
+ (graphFlags.length > 0
1000
+ ? " Compare the runtime with the same query without graph enrichment."
1001
+ : ""),
1002
+ { cause: outcome.error },
1003
+ );
1004
+ }
1005
+ const error = outcome.error;
1006
+ const message = errorMessage(error);
1007
+ if (
1008
+ (isUntangleError(error) &&
1009
+ (error.code === "QDRANT_UNAVAILABLE" ||
1010
+ error.code === "LANCEDB_UNAVAILABLE" ||
1011
+ error.code === "LANCEDB_SCHEMA_INCOMPATIBLE")) ||
1012
+ /QDRANT_UNAVAILABLE|LANCEDB_UNAVAILABLE|fetch failed|ECONNREFUSED|ENOTFOUND|timed out/iu.test(
1013
+ message,
1014
+ )
1015
+ ) {
1016
+ return new EnvironmentError(
1017
+ `The store at ${qdrantUrl} did not answer the query: ${message}`,
1018
+ `Run \`untangle doctor\` to check ${backendLabel} and Ollama independently.`,
1019
+ { cause: error },
1020
+ );
1021
+ }
1022
+ if (/EMBEDDING|OLLAMA|model/iu.test(message)) {
1023
+ return new EnvironmentError(
1024
+ `Could not create the query embedding: ${message}`,
1025
+ "Run `untangle doctor` to check whether Ollama is running and has the model.",
1026
+ { cause: error },
1027
+ );
1028
+ }
1029
+ return new OperationalError(`Search failed: ${message}`, undefined, {
1030
+ cause: error,
1031
+ });
1032
+ }